Anyon e7a8c05556 chore(repo): 统一 v8 仓库品牌名称
将 v8 重构分支中残留的 ThinkAdminDeveloper 文本统一调整为 ThinkAdmin,避免迁移到主仓库后继续暴露旧开发仓库名称。

主要内容:

- 更新 README 标题与项目描述。

- 统一 PHP 文件头注释中的项目标识。

- 同步调整测试、配置、插件与文档中的旧仓库名称文本。

- 保持旧包删除说明与架构边界测试语义不变,只清理品牌名称残留。
2026-05-08 16:15:24 +08:00

646 lines
30 KiB
PHP

<?php
declare(strict_types=1);
/**
* +----------------------------------------------------------------------
* | ThinkAdmin Plugin
* +----------------------------------------------------------------------
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
* +----------------------------------------------------------------------
* | Official Website: https://thinkadmin.top
* +----------------------------------------------------------------------
* | Licensed: https://mit-license.org
* | Disclaimer: https://thinkadmin.top/disclaimer
* | Vip Rights: https://thinkadmin.top/vip-introduce
* +----------------------------------------------------------------------
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
* +----------------------------------------------------------------------
*/
namespace plugin\system\tests\helper;
use PHPUnit\Framework\TestCase;
use plugin\system\helper\command\project\PublishCommand;
use think\admin\service\RuntimeService;
use think\App;
use think\console\Command;
use think\console\Input;
use think\console\Output;
/**
* @internal
* @coversNothing
*/
class PublishTest extends TestCase
{
private const MIGRATION_MANIFEST = '.published.json';
private const RESOURCE_MANIFEST = '.published.json';
private string $root;
protected function setUp(): void
{
$this->root = sys_get_temp_dir() . '/thinkadmin-helper-test-' . bin2hex(random_bytes(6));
mkdir($this->root . '/vendor/composer', 0777, true);
$this->createPluginPackage('demo', 'vendor/demo-plugin', 'plugin\demo\Service');
}
protected function tearDown(): void
{
$this->removeTree($this->root);
}
public function testDiscoverWorkspacePackagesFindsLocalPluginComposer(): void
{
$command = new PublishCommand();
function_exists('test_reset_model_makers') && test_reset_model_makers();
$app = new App($this->root);
RuntimeService::init($app);
$command->setApp($app);
$method = new \ReflectionMethod($command, 'discoverWorkspacePackages');
$method->setAccessible(true);
$items = $method->invoke($command);
$this->assertCount(1, $items);
$this->assertSame('vendor/demo-plugin', $items[0]['name']);
$this->assertSame(str_replace('\\', '/', $this->root . '/plugin/demo'), $items[0]['__path']);
}
public function testRunPublishesWorkspaceServicesAndMigrations(): void
{
$this->createPluginPackage(
'system',
'vendor/system-plugin',
'plugin\system\Service',
[
'20241010000001_install_system20241010.php',
'20241010000002_install_storage20241010.php',
]
);
$this->createPluginPackage(
'worker',
'vendor/worker-plugin',
'plugin\worker\Service',
'20241010000008_install_worker20241010.php'
);
$command = $this->newCommand();
$code = $command->run(new Input([]), new Output('buffer'));
$services = require $this->root . '/vendor/services.php';
$versions = require $this->root . '/vendor/versions.php';
$manifest = json_decode((string)file_get_contents($this->root . '/database/migrations/.published.json'), true);
$resourceManifest = json_decode((string)file_get_contents($this->root . '/vendor/' . self::RESOURCE_MANIFEST), true);
$this->assertSame(0, $code);
$this->assertContains('plugin\demo\Service', $services);
$this->assertContains('plugin\system\Service', $services);
$this->assertContains('plugin\worker\Service', $services);
$this->assertSame('System', $versions['vendor/system-plugin']['name']);
$this->assertSame('plugin', $versions['vendor/system-plugin']['type']);
$this->assertSame('plugin', $versions['vendor/worker-plugin']['type']);
$this->assertArrayNotHasKey('vendor/storage-plugin', $versions);
$this->assertFileExists($this->root . '/database/migrations/20241010000001_install_system20241010.php');
$this->assertFileExists($this->root . '/database/migrations/20241010000002_install_storage20241010.php');
$this->assertFileExists($this->root . '/database/migrations/20241010000008_install_worker20241010.php');
$this->assertSame(
'plugin/system/stc/database/20241010000001_install_system20241010.php',
$manifest['20241010000001_install_system20241010.php']['source']
);
$this->assertSame(
'plugin/system/stc/database/20241010000002_install_storage20241010.php',
$manifest['20241010000002_install_storage20241010.php']['source']
);
$this->assertSame([], $resourceManifest);
}
public function testWorkspaceComposerPublishRulesOverrideInstalledMetadata(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [
'stc/runtime/demo.txt' => 'runtime-publish/demo.txt',
],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/runtime', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/runtime/demo.txt', "workspace\n");
file_put_contents($this->root . '/vendor/composer/installed.json', json_encode([
'packages' => [[
'name' => 'vendor/demo-plugin',
'type' => 'think-admin-plugin',
'version' => '0.9.0',
'extra' => [
'think' => ['services' => ['plugin\demo\Service']],
'xadmin' => [
'app' => ['code' => 'demo', 'name' => 'Legacy Demo'],
'publish' => ['copy' => ['stc/legacy.txt' => 'legacy.txt']],
],
],
]],
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$command = $this->newCommand();
$code = $command->run(new Input([]), new Output('buffer'));
$versions = require $this->root . '/vendor/versions.php';
$this->assertSame(0, $code);
$this->assertFileExists($this->root . '/runtime-publish/demo.txt');
$this->assertSame("workspace\n", file_get_contents($this->root . '/runtime-publish/demo.txt'));
$this->assertSame('Demo', $versions['vendor/demo-plugin']['name']);
}
public function testExplicitCopyRulesTakePrecedenceOverDefaultDirectories(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [
'stc/runtime/demo.txt' => 'runtime-publish/demo.txt',
],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/runtime', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/runtime/demo.txt', "runtime\n");
mkdir($this->root . '/plugin/demo/stc/config', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/config/demo.php', "<?php return ['copied' => false];\n");
$command = $this->newCommand();
$code = $command->run(new Input([]), new Output('buffer'));
$this->assertSame(0, $code);
$this->assertFileExists($this->root . '/runtime-publish/demo.txt');
$this->assertFileDoesNotExist($this->root . '/config/demo.php');
}
public function testPluginWithoutCopyRulesDoesNotPublishDefaultDirectories(): void
{
mkdir($this->root . '/plugin/demo/stc/config', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/config/demo.php', "<?php return ['copied' => false];\n");
$command = $this->newCommand();
$code = $command->run(new Input([]), new Output('buffer'));
$this->assertSame(0, $code);
$this->assertFileDoesNotExist($this->root . '/config/demo.php');
}
public function testStructuredExcludeRulesSkipMatchingDirectoryFiles(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [[
'from' => 'stc/public/static/theme',
'to' => 'public/static/theme',
'exclude' => ['*.less', '*.css.map', 'package.json'],
]],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/public/static/theme/css', 0777, true);
mkdir($this->root . '/plugin/demo/stc/public/static/theme/img', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/login.css', "body{}\n");
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/login.less', "@c:#fff;\n");
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/login.css.map', "{}\n");
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/package.json', "{\"name\":\"demo\"}\n");
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/img/demo.txt', "ok\n");
$command = $this->newCommand();
$code = $command->run(new Input([]), new Output('buffer'));
$this->assertSame(0, $code);
$this->assertFileExists($this->root . '/public/static/theme/css/login.css');
$this->assertFileDoesNotExist($this->root . '/public/static/theme/css/login.less');
$this->assertFileDoesNotExist($this->root . '/public/static/theme/css/login.css.map');
$this->assertFileDoesNotExist($this->root . '/public/static/theme/css/package.json');
$this->assertFileExists($this->root . '/public/static/theme/img/demo.txt');
}
public function testKeyValueObjectRuleSupportsExcludeWithoutExplicitFrom(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [
'stc/public/static/theme' => [
'to' => 'public/static/theme',
'exclude' => ['*.less', '*.css.map', 'package.json'],
],
],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/public/static/theme/css', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/login.css', "body{}\n");
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/login.less', "@c:#fff;\n");
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/login.css.map', "{}\n");
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/package.json', "{\"name\":\"demo\"}\n");
$command = $this->newCommand();
$code = $command->run(new Input([]), new Output('buffer'));
$this->assertSame(0, $code);
$this->assertFileExists($this->root . '/public/static/theme/css/login.css');
$this->assertFileDoesNotExist($this->root . '/public/static/theme/css/login.less');
$this->assertFileDoesNotExist($this->root . '/public/static/theme/css/login.css.map');
$this->assertFileDoesNotExist($this->root . '/public/static/theme/css/package.json');
}
public function testBangTargetForcesOverwriteWithoutGlobalForce(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [
'stc/runtime/demo.txt' => '!runtime-publish/demo.txt',
],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/runtime', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/runtime/demo.txt', "new\n");
is_dir($this->root . '/runtime-publish') || mkdir($this->root . '/runtime-publish', 0777, true);
file_put_contents($this->root . '/runtime-publish/demo.txt', "old\n");
$command = $this->newCommand();
$code = $command->run(new Input([]), new Output('buffer'));
$this->assertSame(0, $code);
$this->assertSame("new\n", file_get_contents($this->root . '/runtime-publish/demo.txt'));
}
public function testStructuredForceFlagForcesOverwriteWithoutGlobalForce(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [[
'from' => 'stc/runtime/demo.txt',
'to' => 'runtime-publish/demo.txt',
'force' => true,
]],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/runtime', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/runtime/demo.txt', "new\n");
is_dir($this->root . '/runtime-publish') || mkdir($this->root . '/runtime-publish', 0777, true);
file_put_contents($this->root . '/runtime-publish/demo.txt', "old\n");
$command = $this->newCommand();
$code = $command->run(new Input([]), new Output('buffer'));
$this->assertSame(0, $code);
$this->assertSame("new\n", file_get_contents($this->root . '/runtime-publish/demo.txt'));
}
public function testPublishedResourcesAreTrackedAndUpdatedOnSourceChange(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [
'stc/runtime/demo.txt' => 'runtime-publish/demo.txt',
],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/runtime', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/runtime/demo.txt', "first\n");
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
$this->assertSame("first\n", file_get_contents($this->root . '/runtime-publish/demo.txt'));
file_put_contents($this->root . '/plugin/demo/stc/runtime/demo.txt', "second\n");
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
$this->assertSame("second\n", file_get_contents($this->root . '/runtime-publish/demo.txt'));
$resourceManifest = json_decode((string)file_get_contents($this->root . '/vendor/' . self::RESOURCE_MANIFEST), true, 512, JSON_THROW_ON_ERROR);
$this->assertSame('vendor/demo-plugin', $resourceManifest['runtime-publish/demo.txt']['package']);
$this->assertSame(
'plugin/demo/stc/runtime/demo.txt',
$resourceManifest['runtime-publish/demo.txt']['source']
);
$this->assertSame(
sha1_file($this->root . '/runtime-publish/demo.txt'),
$resourceManifest['runtime-publish/demo.txt']['sha1']
);
}
public function testIgnoreFilePreventsPublishedResourceOverwrite(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [
'stc/runtime/demo.txt' => 'runtime-publish/demo.txt',
],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/runtime', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/runtime/demo.txt', "first\n");
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
is_dir($this->root . '/runtime-publish') || mkdir($this->root . '/runtime-publish', 0777, true);
file_put_contents($this->root . '/runtime-publish/ignore', '');
file_put_contents($this->root . '/plugin/demo/stc/runtime/demo.txt', "second\n");
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
$this->assertSame("first\n", file_get_contents($this->root . '/runtime-publish/demo.txt'));
$resourceManifest = json_decode((string)file_get_contents($this->root . '/vendor/' . self::RESOURCE_MANIFEST), true, 512, JSON_THROW_ON_ERROR);
$this->assertSame(
sha1("first\n"),
$resourceManifest['runtime-publish/demo.txt']['sha1']
);
}
public function testRemovedPluginDeletesUnchangedPublishedResources(): void
{
$this->createPluginPackage('publish-demo', 'vendor/publish-demo', 'plugin\publishdemo\Service', null, [
'stc/runtime/demo.txt' => 'runtime-publish/demo.txt',
]);
mkdir($this->root . '/plugin/publish-demo/stc/runtime', 0777, true);
file_put_contents($this->root . '/plugin/publish-demo/stc/runtime/demo.txt', "demo\n");
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
$this->assertFileExists($this->root . '/runtime-publish/demo.txt');
$this->removeTree($this->root . '/plugin/publish-demo');
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
$this->assertFileDoesNotExist($this->root . '/runtime-publish/demo.txt');
$resourceManifest = json_decode((string)file_get_contents($this->root . '/vendor/' . self::RESOURCE_MANIFEST), true, 512, JSON_THROW_ON_ERROR);
$this->assertArrayNotHasKey('runtime-publish/demo.txt', $resourceManifest);
}
public function testRemovedPluginPreservesChangedPublishedResources(): void
{
$this->createPluginPackage('publish-demo', 'vendor/publish-demo', 'plugin\publishdemo\Service', null, [
'stc/runtime/demo.txt' => 'runtime-publish/demo.txt',
]);
mkdir($this->root . '/plugin/publish-demo/stc/runtime', 0777, true);
file_put_contents($this->root . '/plugin/publish-demo/stc/runtime/demo.txt', "demo\n");
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
file_put_contents($this->root . '/runtime-publish/demo.txt', "local-change\n");
$this->removeTree($this->root . '/plugin/publish-demo');
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
$this->assertSame("local-change\n", file_get_contents($this->root . '/runtime-publish/demo.txt'));
$resourceManifest = json_decode((string)file_get_contents($this->root . '/vendor/' . self::RESOURCE_MANIFEST), true, 512, JSON_THROW_ON_ERROR);
$this->assertArrayHasKey('runtime-publish/demo.txt', $resourceManifest);
}
public function testRuleShrinkRemovesPreviouslyPublishedExcludedFile(): void
{
$manifestFile = $this->root . '/plugin/demo/composer.json';
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [
'stc/public/static/theme' => 'public/static/theme',
],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
mkdir($this->root . '/plugin/demo/stc/public/static/theme/css', 0777, true);
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/login.css', "body{}\n");
file_put_contents($this->root . '/plugin/demo/stc/public/static/theme/css/login.less', "@c:#fff;\n");
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
$this->assertFileExists($this->root . '/public/static/theme/css/login.less');
$manifest = json_decode((string)file_get_contents($manifestFile), true, 512, JSON_THROW_ON_ERROR);
$manifest['extra']['xadmin']['publish'] = [
'copy' => [
'stc/public/static/theme' => [
'to' => 'public/static/theme',
'exclude' => ['*.less'],
],
],
];
file_put_contents($manifestFile, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$command = $this->newCommand();
$this->assertSame(0, $command->run(new Input([]), new Output('buffer')));
$this->assertFileDoesNotExist($this->root . '/public/static/theme/css/login.less');
$this->assertFileExists($this->root . '/public/static/theme/css/login.css');
$resourceManifest = json_decode((string)file_get_contents($this->root . '/vendor/' . self::RESOURCE_MANIFEST), true, 512, JSON_THROW_ON_ERROR);
$this->assertArrayNotHasKey('public/static/theme/css/login.less', $resourceManifest);
$this->assertArrayHasKey('public/static/theme/css/login.css', $resourceManifest);
}
public function testSyncMigrationsRemovesStalePublishedFiles(): void
{
$this->createPluginPackage(
'system',
'vendor/system-plugin',
'plugin\system\Service',
'20241010000001_install_system20241010.php'
);
$targetDir = $this->root . '/database/migrations';
mkdir($targetDir, 0777, true);
file_put_contents($targetDir . '/20241010000002_install_storage20241010.php', "<?php\n");
file_put_contents($targetDir . '/20241010000001_install_system20241010.php', "<?php\n");
file_put_contents($targetDir . '/' . self::MIGRATION_MANIFEST, json_encode([
'20241010000002_install_storage20241010.php' => [
'source' => 'plugin/system/stc/database/20241010000002_install_storage20241010.php',
'mtime' => 1,
],
'20241010000001_install_system20241010.php' => [
'source' => 'plugin/system/stc/database/20241010000001_install_system20241010.php',
'mtime' => 1,
],
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$command = $this->newCommand();
$this->invokeSyncMigrations($command, [
'20241010000001_install_system20241010.php' => $this->root . '/plugin/system/stc/database/20241010000001_install_system20241010.php',
]);
$manifest = json_decode((string)file_get_contents($targetDir . '/' . self::MIGRATION_MANIFEST), true);
$this->assertFileDoesNotExist($targetDir . '/20241010000002_install_storage20241010.php');
$this->assertFileExists($targetDir . '/20241010000001_install_system20241010.php');
$this->assertArrayNotHasKey('20241010000002_install_storage20241010.php', $manifest);
$this->assertArrayHasKey('20241010000001_install_system20241010.php', $manifest);
}
public function testSyncMigrationsRejectsDuplicateVersions(): void
{
$this->createPluginPackage('system-a', 'vendor/system-a', 'plugin\systema\Service', '20241010000011_install_system_a.php');
$this->createPluginPackage('system-b', 'vendor/system-b', 'plugin\systemb\Service', '20241010000011_install_system_b.php');
$command = $this->newCommand();
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage(
'Duplicate migration version [20241010000011] between [20241010000011_install_system_a.php] and [20241010000011_install_system_b.php]'
);
$this->invokeSyncMigrations($command, [
'20241010000011_install_system_a.php' => $this->root . '/plugin/system-a/stc/database/20241010000011_install_system_a.php',
'20241010000011_install_system_b.php' => $this->root . '/plugin/system-b/stc/database/20241010000011_install_system_b.php',
]);
}
public function testSyncMigrationsRemovesConflictingLegacyVersionFile(): void
{
$this->createPluginPackage(
'system',
'vendor/system-plugin',
'plugin\system\Service',
'20241010000001_install_system20241010.php'
);
$targetDir = $this->root . '/database/migrations';
mkdir($targetDir, 0777, true);
file_put_contents($targetDir . '/20241010000001_install_legacy_system.php', "<?php\n// legacy\n");
$command = $this->newCommand();
$this->invokeSyncMigrations($command, [
'20241010000001_install_system20241010.php' => $this->root . '/plugin/system/stc/database/20241010000001_install_system20241010.php',
]);
$manifest = json_decode((string)file_get_contents($targetDir . '/' . self::MIGRATION_MANIFEST), true);
$this->assertFileDoesNotExist($targetDir . '/20241010000001_install_legacy_system.php');
$this->assertFileExists($targetDir . '/20241010000001_install_system20241010.php');
$this->assertSame(
'plugin/system/stc/database/20241010000001_install_system20241010.php',
$manifest['20241010000001_install_system20241010.php']['source']
);
}
public function testSyncMigrationsPreservesUnmanagedRootMigrationFile(): void
{
$this->createPluginPackage(
'system',
'vendor/system-plugin',
'plugin\system\Service',
'20241010000001_install_system20241010.php'
);
$targetDir = $this->root . '/database/migrations';
mkdir($targetDir, 0777, true);
file_put_contents($targetDir . '/20241010000099_project_custom.php', "<?php\n// custom\n");
$command = $this->newCommand();
$this->invokeSyncMigrations($command, [
'20241010000001_install_system20241010.php' => $this->root . '/plugin/system/stc/database/20241010000001_install_system20241010.php',
]);
$manifest = json_decode((string)file_get_contents($targetDir . '/' . self::MIGRATION_MANIFEST), true);
$this->assertFileExists($targetDir . '/20241010000099_project_custom.php');
$this->assertFileExists($targetDir . '/20241010000001_install_system20241010.php');
$this->assertArrayHasKey('20241010000001_install_system20241010.php', $manifest);
$this->assertArrayNotHasKey('20241010000099_project_custom.php', $manifest);
}
/**
* @param null|string|list<string> $migration
*/
private function createPluginPackage(string $code, string $name, string $service, null|string|array $migration = null, array $publish = []): void
{
$path = "{$this->root}/plugin/{$code}";
mkdir($path, 0777, true);
$extra = [
'think' => ['services' => [$service]],
'xadmin' => ['app' => ['code' => $code, 'name' => ucfirst($code)]],
];
if ($publish !== []) {
$extra['xadmin']['publish'] = ['copy' => $publish];
}
file_put_contents($path . '/composer.json', json_encode([
'name' => $name,
'type' => 'think-admin-plugin',
'version' => '1.0.0',
'description' => ucfirst($code),
'extra' => $extra,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
if ($migration) {
mkdir($path . '/stc/database', 0777, true);
foreach ((array)$migration as $filename) {
file_put_contents($path . '/stc/database/' . $filename, "<?php\n");
}
}
}
private function newCommand(): PublishCommand
{
$command = new PublishCommand();
function_exists('test_reset_model_makers') && test_reset_model_makers();
$app = new App($this->root);
RuntimeService::init($app);
$app->config->set([
'default' => 'file',
'stores' => [
'file' => ['type' => 'File', 'path' => $this->root . '/runtime/cache'],
],
], 'cache');
$command->setApp($app);
return $command;
}
/**
* @param array<string, string> $sources
*/
private function invokeSyncMigrations(PublishCommand $command, array $sources, bool $force = false): void
{
$property = new \ReflectionProperty(Command::class, 'output');
$property->setAccessible(true);
$property->setValue($command, new Output('buffer'));
$method = new \ReflectionMethod($command, 'syncMigrations');
$method->setAccessible(true);
$method->invoke($command, $sources, $force);
}
private function removeTree(string $path): void
{
if (!is_dir($path)) {
return;
}
$items = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
}
rmdir($path);
}
}