Procedure for incorporating a third-party / standalone-distributed baserCMS plugin into the baserCMS main body (monorepo)Core pluginas. Reference: plugins/bc-blog The existing core plugins such as these are the "correct" configuration.
Prerequisites and division of roles
- Code deprecation handling for version migration (5.2→5.3 / PHP8.5) is
basercms-plugin-5x-updateskill.- Execution, aggregation, and isolation of unit tests is
basercms-unittestskill.- This document focuses on thestructural conversionfrom "normal plugin configuration → core plugin configuration".
0. Fundamental Principles / Final Form
- The core plugin is
plugins/<bc-name>/is placed inits own.git/vendor//bin//composer.lock/phpunit.xml.dist/tests/bootstrap.php/tests/TestApp/does not have. The test isthe application-wide test infrastructureis used for execution. - Dependencies, autoloading, test registration, and CI registration areon the root sideconsolidated into.
- Example of the final form (
plugins/bc-blog):README.md / composer.json / config/ / config.php / src/ / tests/ / webroot/(+ if necessarywebpack.config.js)。
1. Change the plugin name to the core plugin convention
If the name already conforms to the convention, skip this section. The rename work itself for the details of the naming convention requires a separate bulk namespace replacement (this skill only specifies the "naming convention accompanying core-ization").
- Folder name: use kebab case, and
bc-Add prefix.- CamelCase はhyphen-separatedto. Example:
CuMcp→bc-mcp、MyAwesomePlugin→bc-my-awesome-plugin。
- CamelCase はhyphen-separatedto. Example:
- namespace / plugin class: in Pascal case,
Bcprefix. Example:BcMcp, main classBcMcpPlugin。 - composer package name:
baserproject/<bc-name>. Example:baserproject/bc-mcp。 - Correspondence between folder name (kebab) and namespace (Pascal):
bc-mcp⇔BcMcp。 - Accompanying the naming, perform bulk replacement of
namespace/use/ string literals ('plugin' => 'Xxx'/ URL slug/xxx, command names, configuration keys), and also changecomposer.jsonのname/autoload/extra.cakephp.plugin-name(to prevent accidental hits, replace only the leading token, and preserve sub-hierarchy names likeMcp/BcBlog).
2. Delete the plugin-side standalone artifacts
Delete things that are unnecessary or harmful in the monorepo (vendor duplication, nested git).
plugins/<bc-name>/
.git ← nested リポジトリを削除(親 monorepo に取り込む)
vendor/ ← 自前 vendor を削除(ルート vendor を使う)
bin/ ← 自前 cake バイナリを削除(ROOT/bin/cake を使う)
composer.lock ← 削除
phpunit.xml.dist ← 削除(ルート phpunit.xml.dist に testsuite を登録する)
tests/bootstrap.php ← 削除(ルート tests/bootstrap.php を使う)
tests/TestApp/ ← 削除(ルート tests/TestApp を使う)
.github/ ← 単体 CI は不要(split 先リポジトリでも別管理)。既存コアに無ければ削除
.gitignore ← 既存コアに無ければ削除
VERSION.txt ← 削除(バージョンは本体 monorepo 管理になる)
CHANGELOG.md ← 削除(変更履歴も本体管理)
.phpunit.cache / .phpunit.result.cache / .DS_Store / .idea ← 削除
- Match the final form to the existing core (
plugins/bc-blog):README.md / composer.json / config/ / config.php / src/ / tests/ / webroot/(+ if necessarytemplates/・webpack.config.js)。 - ⚠️
.gitdeletion is irreversible. If you need the independent history of nested git, back it up in advance.
3. Route .gitignore Adjustment (2 types, note that the direction is reversed)
3-1. Make the plugin body a "tracked target" (ignore exclusion !)
Route .gitignore は /plugins/* でIgnore all pluginsand only the core plugin ! でwhitelistis done. Add the core-ized plugin to the tracked targets with !/plugins/<bc-name> (If you forget this, it will not be git add and will not be incorporated)。
# plugins
/plugins/*
!/plugins/baser-core
...
!/plugins/bc-seo
!/plugins/bc-mcp ← 追加(無視の除外=追跡対象化)
/plugins/*/vendor
/plugins/*/composer.lock
- Check:
git check-ignore -v plugins/<bc-name>/composer.json(if nothing is returned, it is a tracked target),git status --short plugins/<bc-name>。
3-2. "Ignore" the webroot symlink
baserCMS は webroot/<plugin_underscored> → plugins/<bc-name>/webroot generate a symlink to (example webroot/bc_mcp). The actual entity is plugins/<bc-name>/webroot is in, soThe link side is ignored at the root .gitignore (same ordering as other cores).Do it (same ordering as other cores).
/webroot/bc_blog
/webroot/bc_custom_content
/webroot/bc_mcp ← 追加(アンダースコア表記)
Note the direction:
plugins/<bc-name>is tracked (excluded by!) /webroot/<underscored>is ignored。
4. Move the tests onto the "full run"
4-1. Route phpunit.xml.dist add a testsuite to (⚠️ placement order is important)
(The plugin-side phpunit.xml.dist was deleted in step 2.What you add is the root-side one。)
<testsuite name="BcMcp">
<directory>plugins/bc-mcp/tests/TestCase</directory>
</testsuite>
- ⚠️ A plugin that has DB tables but is not placed in
defaultInstallCorePluginsmust be placed "before" theBcInstallertestsuite。BcInstallerのInstallationsControllerTest::testStep3(mode=createDb)はthe actual install processdrops all tables and re-creates them with only plugins with defaultInstall. If placed after it, your own plugin's tables are not re-created and your own tests run with them missing, making all actions return 500 (existing non-default cores such as BcContentLink/BcCustomContent etc. are also all placed before BcInstaller).- Example:
... BcSeoplaceBcInstallerimmediately beforeBcMcpimmediately after it. - If you choose the policy of putting it in defaultInstall, it may also be placed after BcInstaller (because createDb re-creates it).
- Example:
4-2. Add to the migration/plugin list (⚠️ there are multiple locations)
The plugin performs DB migration (config/Migrations), add it toall plugin lists that create/rebuild tables in the test DB. If even one location is missed, only that plugin's tables will be missing during a full run, resulting in Could not describe columns on <table>(→ controller initialization failure, causing all actions of that plugin to return 500).It passes in a standalone testsuite and fails only in a full run, which is its characteristic (for isolation, also see the test infrastructure section of basercms-plugin-5x-update ).
- Route
tests/bootstrap.phpのMigrator::runMany(initial schema construction)(new Migrator())->runMany([ ['plugin' => 'BaserCore'], ... ['plugin' => 'BcMcp'], // 追加 ]); - The $plugins array (multiple) of tests that do "drop all tables → re-migrate". These are
deleteTables()/deleteAllTables()でdrop all tables , then re-migrates with a hardcoded list. If it is not here, the tables will not be restored, and since it is positioned at the beginning in test order,it remains missing for all subsequent tests(in fact, when bc-mcp was added,oauth2_clientsdisappeared because of this, and all OAuth2 tests returned 500).Known locations (both required):plugins/baser-core/tests/TestCase/Service/BcDatabaseServiceTest.php→test_deleteTablesForMigrations()の$pluginsplugins/bc-installer/tests/TestCase/Service/Admin/InstallationsAdminServiceTest.php→test_deleteAllTables()の$plugins
$plugins = [ 'BaserCore', ..., 'BcMail', 'BcMcp', /* 追加 */ 'BcSearchIndex', ... ];
- Check for omissions (important): This kind of rebuild list is scattered across multiple files.Always grep to check all of them:
Check whether the new plugin is present in each matched list (if even one is missed, it fails only in a full run).grep -rn "'BcWidgetArea'," plugins --include="*.php" # 末尾要素で「コア列挙リスト」を検出 - A reliable detection method:
BcTestCase::setUpAt the beginning of, insert a temporary measurement that "records the existence of the target tables in each test",Run the full suiteandOK→MISSINGIdentify the transition points (the OK test immediately before the transition is the drop source). All droppers can be identified at once. Remove the instrumentation after confirming. - Isolation diagnosis (which test makes the table disappear):
BcTestCase::setUpInsert temporary instrumentation at the beginning (ConnectionManager::get('test')->getSchemaCollection()->listTables()record in each test whether the target table exists), and the OK test immediately before the first MISSING is the drop source. Be sure to remove it after confirming.
4-3. Plugin composer.json Simplify to core plugin format
require-dev(baser-core, etc.) /scripts・config.allow-plugins・minimum-stability delete.vendor-dir add.If there are external runtime dependencies, require leave them in(later, monorepo-builder merge consolidates them into the root).
{
"name": "baserproject/bc-mcp",
"description": "BcMcp plugin for baserCMS",
"homepage": "https://basercms.net",
"type": "cakephp-plugin",
"license": "MIT",
"vendor-dir": "../../vendor",
"require": { // 外部依存が無ければ require ごと省略可(bc-blog 等)
"php": "^8.1",
"league/oauth2-server": "^8.5"
// ...プラグイン固有の外部依存
},
"autoload": { "psr-4": { "BcMcp\\": "src/" } },
"autoload-dev": { "psr-4": { "BcMcp\\Test\\": "tests/" } }
}
- When a test uses another core's Test namespace (
BcBlog\Test, etc.) orApp\(TestApp),Routecomposer.jsonの autoload-dev is provided by, so the plugin-side autoload-dev should use its ownTestis sufficient.
5. Awareness of the system (baserCMS / monorepo)
5-1. plugins/baser-core/config/setting.php の corePlugins add to
'corePlugins' => [ ..., 'BcMail', 'BcMcp', 'BcSeo', ... ],
5-2. defaultInstallCorePlugins Check whether it can be added to
baserCMS Automatically enabled on new installationIf so, add it (same as BcBlog/BcMail, etc.). If it is an optional feature/opt-in,do not add(corePlugins only = manually enabled from the admin screen). → Confirm the project policy with the user.
5-3. Route composer.json Consolidation into (autoload / autoload-dev / replace / require)
In principle vendor/bin/monorepo-builder merge consolidate withdo (monorepo-builder.php の packageDirectories([... '/plugins']) が plugins/ Automatically scan under, and each composer.json merge into the route +replace manage).
docker compose exec <container> sh -c "cd /var/www/html && vendor/bin/monorepo-builder merge"
- ⚠️ merge はGuard version consistency across all packages. If there is a version discrepancy in a local-only plugin outside the monorepo (e.g., EgaRyu / BcUpdateSupporter, etc.), it will be
Found conflicting package versionsでblock. In that case, align the version of the plugin in question, or when blocked, manually make edits equivalent to merge(see below). - Manual editing equivalent to merge (root
composer.json):autoload.psr-4に"BcMcp\\": "plugins/bc-mcp/src/"autoload-dev.psr-4に"BcMcp\\Test\\": "plugins/bc-mcp/tests/"requirethe plugin's external dependencies (league/oauth2-serveretc.,ext-*including)replaceに"baserproject/bc-mcp": "<monorepo版>"(align with other cores)
- After applying, dependency acquisition and autoload regeneration:
docker compose exec <container> sh -c "cd /var/www/html && composer update <added-packages> --no-interaction"
5-4. phpdoc.dist.xml に src Add path
<path>plugins/bc-mail/src</path>
<path>plugins/bc-mcp/src</path> <!-- 追加 -->
<path>plugins/bc-search-index/src</path>
5-5. .github/workflows/split_monorepo.yml Add split target to
- local_path: 'bc-mcp'
split_repository: 'bc-mcp'
6. Execution verification in the full test suite
docker compose exec <container> sh -c "cd /var/www/html && vendor/bin/phpunit --testsuite BcMcp"
- Prerequisites that the standalone-only bootstrap provided (external processes, environment variables, keys, etc.) arenot in the full bootstrap, so failures are likely to occur immediately after migration. Representative examples:
- External process dependency: When proxy integration tests, etc. requirean actual server process, in standalone the bootstrap started it. On the full side,start it at the beginning of the relevant test(reuse it if already started). For the startup command, use
ROOT/bin/cake <command>(the app's cake and core-registered commands).setUpnot the entireOnly for individual tests that require a serveradd a guard (so startup waiting does not propagate to other tests). ⚠️ Startup detection based only on "process existence (pid file)" is insufficient. Even if the process starts, the port bind may not be ready in time, and the proxy connection target (e.g.127.0.0.1:{port}) gets connection refused = 500 (a classic case that passes locally but fails only in CI).Wait by polling until it can actually connect. If it cannot be reached, do not hide it withmarkTestSkippedbut fail explicitly withassertTrueetc.(skipping would swallow server startup defects). This policy is also described in.github/instructions/basercms.instructions.md"Development, Testing, and Build".private function requireMcpServer(): void { $m = new McpServerManger(); $cfg = $m->getServerConfig(); if (!$m->isServerRunning()) { $m->startMcpServer($cfg); } $host = $cfg['host'] ?? '127.0.0.1'; $port = (int)($cfg['port'] ?? 3000); $deadline = microtime(true) + 15.0; $reachable = false; while (microtime(true) < $deadline) { $c = @fsockopen($host, $port, $e, $s, 1); if ($c) { fclose($c); $reachable = true; break; } usleep(300000); } $this->assertTrue($reachable, "MCP サーバー({$host}:{$port})へ接続できませんでした"); } // 実サーバーが要るテストの先頭で $this->requireMcpServer(); - Migration not executed: omission of the addition in 4-2 → fails due to missing table.
- Plugin not bootstrapped / sub-plugin not loaded etc. refer to the
basercms-plugin-5x-updateskill (T-3 to T-5).
- External process dependency: When proxy integration tests, etc. requirean actual server process, in standalone the bootstrap started it. On the full side,start it at the beginning of the relevant test(reuse it if already started). For the startup command, use
- Finally, also confirm that there is no regression in the full test suite.
7. Confirmation of read-only repositories for monorepo split
split_monorepo splits each plugin into baserproject/<split_repository> のread-only repositorypush to.If the target repository does not exist, the split fails, so confirm in advance whether it has been created.
gh repo view baserproject/bc-mcp --json name,url,visibility
- If not created, create it (an external, irreversible operation. Confirm with the user before executing).
ghFirst confirm whether creation is possible with: the authenticated account can create it if it has the target org (baserproject) admin, and the token hasreposcope.gh auth status # ログイン/スコープ確認(repo が必要) gh api user/memberships/orgs/baserproject # role が "admin" か確認- For creation,match the settings of an existing split repository (e.g.
baserproject/bc-blog): public / description[READ-ONLY] <PascalName>/ homepagehttps://basercms.net。gh repo create baserproject/bc-mcp --public -d "[READ-ONLY] BcMcp" -h https://basercms.net - If you do not have permission, ask a baserCMS administrator to create it (do not create it under a different owner without authorization).
checklist
- The plugin name follows the core convention (
bc-prefix / kebab /Bcnamespace/baserproject/bc-*) -
.git/vendor/bin/composer.lock/phpunit.xml.dist/tests/bootstrap.php/TestApp/VERSION.txt/CHANGELOG.mddelete - route
.gitignore:!/plugins/<bc-name>make it tracked with/webroot/<underscored>ignore - route
phpunit.xml.distadd testsuite to - Add to all migration list locations (if there are migrations): root
tests/bootstrap.phpのrunMany+BcDatabaseServiceTest::test_deleteTablesForMigrationsの$plugins - plugin
composer.jsonsimplify to the core format (keep external requires) -
corePluginsadd to /defaultInstallCorePluginsconfirm policy - route
composer.jsonconsolidate into (merge or manual) +composer update -
phpdoc.dist.xml/split_monorepo.ymladd to - testsuite OK in the full test (external process dependencies are started in setUp)
- confirm the existence of read-only repositories for split
Note:
.claude/skills/Skills added or updated in are not immediately reflected in the current session (discovered at startup).Restart Claude Codethen/basercms-core-plugin-convertcan be called as.