mirror of
https://gitee.com/zoujingli/ThinkAdmin.git
synced 2026-06-07 04:28:11 +08:00
将 v8 重构分支中残留的 ThinkAdminDeveloper 文本统一调整为 ThinkAdmin,避免迁移到主仓库后继续暴露旧开发仓库名称。 主要内容: - 更新 README 标题与项目描述。 - 统一 PHP 文件头注释中的项目标识。 - 同步调整测试、配置、插件与文档中的旧仓库名称文本。 - 保持旧包删除说明与架构边界测试语义不变,只清理品牌名称残留。
646 lines
30 KiB
PHP
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);
|
|
}
|
|
}
|