bmad-plus 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +36 -8
- package/package.json +6 -4
- package/readme-international/README.de.md +37 -8
- package/readme-international/README.es.md +38 -9
- package/readme-international/README.fr.md +37 -8
- package/src/bmad-plus/agents/agent-orchestrator/SKILL.md +2 -0
- package/src/bmad-plus/module.yaml +270 -220
- package/src/bmad-plus/packs/pack-seo/scripts/seo_apis.py +8 -8
- package/src/bmad-plus/packs/pack-seo/scripts/seo_fetch.py +1 -2
- package/src/bmad-plus/packs/pack-seo/scripts/seo_report.py +0 -1
- package/src/bmad-plus/skills/bmad-plus-autopilot/SKILL.md +1 -1
- package/tools/bmad-plus-npx.js +4 -2
- package/tools/build/adapters.config.js +60 -51
- package/tools/build/check-counts.js +52 -54
- package/tools/build/check-install-contract.js +298 -0
- package/tools/build/generate-adapters.js +252 -56
- package/tools/build/generate.js +187 -10
- package/tools/build/generated-adapters/.codex/AGENTS.md +20 -7
- package/tools/build/generated-adapters/.cursor/rules/bmad-plus.mdc +20 -7
- package/tools/build/generated-adapters/.opencode/AGENTS.md +20 -7
- package/tools/build/generated-adapters/AGENTS.md +20 -7
- package/tools/build/generated-adapters/CLAUDE.md +20 -7
- package/tools/build/generated-adapters/CONVENTIONS.md +20 -7
- package/tools/build/generated-adapters/GEMINI.md +20 -7
- package/tools/build/module.template.yaml +82 -0
- package/tools/cli/bmad-plus-cli.js +16 -1
- package/tools/cli/commands/doctor.js +12 -40
- package/tools/cli/commands/install.js +108 -163
- package/tools/cli/commands/uninstall.js +173 -65
- package/tools/cli/commands/update-check.js +31 -0
- package/tools/cli/commands/update-policy.js +39 -0
- package/tools/cli/commands/update.js +102 -113
- package/tools/cli/i18n.js +60 -0
- package/tools/cli/lib/ide-config.js +4 -261
- package/tools/cli/lib/install-manifest.js +17 -0
- package/tools/cli/lib/installed-adapters.js +89 -0
- package/tools/cli/lib/npm-runner.js +177 -0
- package/tools/cli/lib/pack-copy.js +62 -66
- package/tools/cli/lib/packs.js +437 -3
- package/tools/cli/lib/update-check.js +153 -0
- package/tools/cli/lib/update-dispatch.js +182 -0
- package/tools/cli/lib/update-policy.js +90 -0
- package/tools/cli/lib/update-transaction.js +334 -0
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
* node tools/build/generate-adapters.js --adopt --force # overwrite root files that
|
|
28
28
|
* # lack the AUTO-GENERATED marker
|
|
29
29
|
* node tools/build/generate-adapters.js --out-dir <dir> # write to <dir>
|
|
30
|
+
* node tools/build/generate-adapters.js --target <dir> # user spine + adapters
|
|
31
|
+
* [--packs core,memory] [--tools claude-code,cursor] [--user-name Alice]
|
|
32
|
+
* [--language English] [--force] # explicit foreign overwrite
|
|
30
33
|
* node tools/build/generate-adapters.js --print # print all adapters to stdout
|
|
31
34
|
* node tools/build/generate-adapters.js --check # verify preview adapters match
|
|
32
35
|
* node tools/build/generate-adapters.js --check --adopt # verify ROOT adapters + spine
|
|
@@ -39,12 +42,13 @@
|
|
|
39
42
|
|
|
40
43
|
const fs = require('node:fs');
|
|
41
44
|
const path = require('node:path');
|
|
42
|
-
const { loadRegistry, DEFAULT_REGISTRY_PATH } = require('./generate');
|
|
43
|
-
const { PROJECT_INSTRUCTIONS } = require('./adapters.config');
|
|
45
|
+
const { loadRegistry, buildDerived, DEFAULT_REGISTRY_PATH } = require('./generate');
|
|
46
|
+
const { PROJECT_INSTRUCTIONS, MEMORY_INSTRUCTIONS, UPDATE_INSTRUCTIONS, TOOL_METADATA } = require('./adapters.config');
|
|
44
47
|
|
|
45
48
|
const DEFAULT_OUT_DIR = path.join(__dirname, 'generated-adapters');
|
|
46
49
|
const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
47
50
|
const GENERATED_MARKER = 'AUTO-GENERATED by tools/build/generate-adapters.js';
|
|
51
|
+
const USER_CONFIG_MARKER = 'BMAD+ installed project configuration';
|
|
48
52
|
|
|
49
53
|
/** Per-tool invocation notes. Unknown tools fall back to buildDefaultNotes(). */
|
|
50
54
|
const TOOL_NOTES = {
|
|
@@ -99,6 +103,7 @@ function validateTargets(registry) {
|
|
|
99
103
|
if (typeof t.spine !== 'string' || t.spine === '') {
|
|
100
104
|
throw new Error('registry.yaml: targets.spine must be a non-empty string');
|
|
101
105
|
}
|
|
106
|
+
validateRelativeFile(t.spine);
|
|
102
107
|
if (!Array.isArray(t.adapters) || t.adapters.length === 0) {
|
|
103
108
|
throw new Error('registry.yaml: targets.adapters must be a non-empty list');
|
|
104
109
|
}
|
|
@@ -106,58 +111,55 @@ function validateTargets(registry) {
|
|
|
106
111
|
if (!a || typeof a.tool !== 'string' || a.tool === '' || typeof a.file !== 'string' || a.file === '') {
|
|
107
112
|
throw new Error(`registry.yaml: targets.adapters[${i}] must have string "tool" and "file"`);
|
|
108
113
|
}
|
|
114
|
+
validateRelativeFile(a.file);
|
|
109
115
|
});
|
|
110
116
|
return registry;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
/**
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
function buildRegistryFacts(registry) {
|
|
124
|
-
const packEntries = Object.entries(registry.packs).sort((a, b) => a[1].order - b[1].order);
|
|
125
|
-
const packs = packEntries.map(([id, p]) => {
|
|
126
|
-
const fact = {
|
|
127
|
-
id,
|
|
128
|
-
name: p.cli.name,
|
|
129
|
-
displayName: p.display_name || p.cli.name,
|
|
130
|
-
desc: p.cli.desc,
|
|
131
|
-
agentCount: p.agents.length,
|
|
132
|
-
required: p.required === true,
|
|
119
|
+
/** Convert the canonical derivation into the adapter presentation shape. */
|
|
120
|
+
function factsFromDerived(derived, packs = derived.packOrder) {
|
|
121
|
+
const selected = packs.map(id => {
|
|
122
|
+
const pack = derived.packs[id];
|
|
123
|
+
return {
|
|
124
|
+
id, name: pack.name, displayName: pack.displayName, desc: pack.desc,
|
|
125
|
+
required: pack.required, agentCount: pack.installerAgentCount,
|
|
126
|
+
categoryAgentCount: pack.categoryAgentCount, subAgentCount: pack.subAgentCount,
|
|
127
|
+
workflowCount: pack.workflowCount, frameworkCount: pack.frameworkCount,
|
|
128
|
+
personas: pack.personas,
|
|
133
129
|
};
|
|
134
|
-
const categoryAgents = sumCategories(p, 'agents');
|
|
135
|
-
const categoryWorkflows = sumCategories(p, 'workflows');
|
|
136
|
-
if (categoryAgents > 0) fact.categoryAgentCount = categoryAgents;
|
|
137
|
-
if (Array.isArray(p.sub_agents)) fact.subAgentCount = p.sub_agents.length;
|
|
138
|
-
if (Array.isArray(p.workflows)) fact.workflowCount = p.workflows.length;
|
|
139
|
-
if (categoryWorkflows > 0) fact.workflowCount = (fact.workflowCount || 0) + categoryWorkflows;
|
|
140
|
-
if (Array.isArray(p.compliance_tags) && p.compliance_tags.length > 0) {
|
|
141
|
-
fact.frameworkCount = p.compliance_tags.length;
|
|
142
|
-
}
|
|
143
|
-
return fact;
|
|
144
130
|
});
|
|
145
|
-
|
|
146
131
|
return {
|
|
147
|
-
product:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
packCount: packs.length,
|
|
156
|
-
installerAgentCount: packs.reduce((n, p) => n + p.agentCount, 0),
|
|
157
|
-
packs,
|
|
132
|
+
product: derived.product,
|
|
133
|
+
spine: derived.targets.spine,
|
|
134
|
+
modelsSupported: derived.targets.models_supported,
|
|
135
|
+
packCount: selected.length,
|
|
136
|
+
installerAgentCount: packs.length === derived.packOrder.length
|
|
137
|
+
? derived.installerAgents
|
|
138
|
+
: selected.reduce((total, pack) => total + pack.agentCount, 0),
|
|
139
|
+
packs: selected,
|
|
158
140
|
};
|
|
159
141
|
}
|
|
160
142
|
|
|
143
|
+
/** Counts are calculated only by buildDerived, never from raw lists here. */
|
|
144
|
+
function buildRegistryFacts(registry) {
|
|
145
|
+
return factsFromDerived(buildDerived(registry));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Render registry-owned persona metadata for the selected packs. */
|
|
149
|
+
function renderRoster(facts) {
|
|
150
|
+
const lines = ['## Agents', '', 'To activate an agent, say its name or persona:', ''];
|
|
151
|
+
for (const pack of facts.packs) {
|
|
152
|
+
for (const persona of pack.personas || []) {
|
|
153
|
+
const identity = persona.alias
|
|
154
|
+
? '(' + persona.alias + ') — ' + persona.role
|
|
155
|
+
: '(' + persona.role + ')';
|
|
156
|
+
lines.push('- **' + persona.name + '** ' + identity + ' — ' + persona.description +
|
|
157
|
+
(pack.required ? '' : ' (' + pack.name + ')'));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return lines;
|
|
161
|
+
}
|
|
162
|
+
|
|
161
163
|
/** Group targets.adapters[] by output file → [{ file, tools: [tool, ...] }]. */
|
|
162
164
|
function groupAdaptersByFile(registry) {
|
|
163
165
|
const byFile = new Map();
|
|
@@ -209,6 +211,8 @@ function renderProjectInstructions(facts) {
|
|
|
209
211
|
'<!-- Hand-authored project instructions — single source: tools/build/adapters.config.js. -->',
|
|
210
212
|
'<!-- Edit the config, then regenerate; never edit this file directly. -->',
|
|
211
213
|
'',
|
|
214
|
+
...renderRoster(facts),
|
|
215
|
+
'',
|
|
212
216
|
...PROJECT_INSTRUCTIONS,
|
|
213
217
|
];
|
|
214
218
|
}
|
|
@@ -339,18 +343,167 @@ function generateAllFiles(registry) {
|
|
|
339
343
|
return [spine, ...generateAllAdapters(registry)];
|
|
340
344
|
}
|
|
341
345
|
|
|
346
|
+
/** Installable tools and paths come from the registry; only detection is local. */
|
|
347
|
+
function buildIDEConfigs(derived) {
|
|
348
|
+
validateTargets({ targets: derived.targets });
|
|
349
|
+
return Object.fromEntries(derived.targets.adapters.map(({ tool, file }) => [
|
|
350
|
+
tool, { name: TOOL_METADATA[tool]?.name || tool, configFile: file,
|
|
351
|
+
detect: [...(TOOL_METADATA[tool]?.detect || [])] },
|
|
352
|
+
]));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function selectedPacks(derived, packs) {
|
|
356
|
+
if (packs !== undefined && !Array.isArray(packs)) throw new Error('packs must be an array of pack IDs');
|
|
357
|
+
const selected = new Set(packs === undefined ? derived.packOrder : packs);
|
|
358
|
+
for (const id of selected) {
|
|
359
|
+
if (!Object.hasOwn(derived.packs, id)) throw new Error('Unknown pack: ' + id);
|
|
360
|
+
}
|
|
361
|
+
for (const id of derived.packOrder) if (derived.packs[id].required) selected.add(id);
|
|
362
|
+
return derived.packOrder.filter(id => selected.has(id));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function renderUserSections(derived, { packs, userName = 'Developer', language = 'English' } = {}) {
|
|
366
|
+
const ids = selectedPacks(derived, packs);
|
|
367
|
+
const facts = factsFromDerived(derived, ids);
|
|
368
|
+
const lines = [
|
|
369
|
+
'## Project Context', '',
|
|
370
|
+
'This project uses ' + facts.product.displayName + ', an augmented AI-driven development framework.',
|
|
371
|
+
'Based on ' + facts.product.derivedFrom + ' with multi-role agents, autopilot mode, and parallel execution.',
|
|
372
|
+
'', ...renderRoster(facts), '',
|
|
373
|
+
'## Skills', '',
|
|
374
|
+
'- Load skills from `.agents/skills/`.',
|
|
375
|
+
'- Each agent has a SKILL.md with capabilities, activation protocol, and role-switching rules.',
|
|
376
|
+
'- Auto-activation triggers: `.agents/data/role-triggers.yaml`.',
|
|
377
|
+
'', '## Communication', '',
|
|
378
|
+
'- User name: ' + String(userName).replace(/[\r\n]/g, ' '),
|
|
379
|
+
'- Default language: ' + String(language).replace(/[\r\n]/g, ' ') + ' for user-facing content, English for code and technical docs.',
|
|
380
|
+
];
|
|
381
|
+
if (ids.includes('memory')) lines.push('', ...MEMORY_INSTRUCTIONS);
|
|
382
|
+
lines.push('', ...UPDATE_INSTRUCTIONS, '', ...renderFactsSection(facts), '', ...renderKeyCommands());
|
|
383
|
+
return lines;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function userMarkerLines() {
|
|
387
|
+
return [
|
|
388
|
+
'<!-- ' + GENERATED_MARKER + ' — DO NOT EDIT. -->',
|
|
389
|
+
'<!-- ' + USER_CONFIG_MARKER + ' -->',
|
|
390
|
+
'<!-- Refresh installed adapters with: npx bmad-plus update -->',
|
|
391
|
+
];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Pure user spine renderer; DERIVED also ships in the npm package. */
|
|
395
|
+
function renderUserConfig(derived, options = {}) {
|
|
396
|
+
validateTargets({ targets: derived.targets });
|
|
397
|
+
return [
|
|
398
|
+
...userMarkerLines(), '',
|
|
399
|
+
'# ' + derived.product.displayName + ' — Agent Spine', '',
|
|
400
|
+
'This file is the common project instruction spine for the installed ' + derived.product.displayName + ' tools.', '',
|
|
401
|
+
...renderUserSections(derived, options), '',
|
|
402
|
+
].join('\n');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Pure render of the common spine and every selected registry adapter. */
|
|
406
|
+
function generateUserFiles(derived, options = {}) {
|
|
407
|
+
validateTargets({ targets: derived.targets });
|
|
408
|
+
const available = derived.targets.adapters.map(adapter => adapter.tool);
|
|
409
|
+
const requested = options.tools === undefined ? available : options.tools;
|
|
410
|
+
if (!Array.isArray(requested)) throw new Error('tools must be an array of tool IDs');
|
|
411
|
+
for (const tool of requested) {
|
|
412
|
+
if (!available.includes(tool)) throw new Error('Unknown tool: ' + tool);
|
|
413
|
+
}
|
|
414
|
+
const targets = { ...derived.targets,
|
|
415
|
+
adapters: derived.targets.adapters.filter(adapter => requested.includes(adapter.tool)) };
|
|
416
|
+
const files = [{ file: targets.spine, tools: ['spine'], spine: true,
|
|
417
|
+
content: renderUserConfig(derived, options) }];
|
|
418
|
+
for (const { file, tools } of groupAdaptersByFile({ targets })) {
|
|
419
|
+
if (file === targets.spine) continue;
|
|
420
|
+
const reference = path.posix.relative(path.posix.dirname(file), targets.spine);
|
|
421
|
+
const lines = [];
|
|
422
|
+
if (file.endsWith('.mdc')) lines.push('---',
|
|
423
|
+
'description: "' + derived.product.displayName + ' agents adapter"', 'alwaysApply: true', '---', '');
|
|
424
|
+
lines.push(...userMarkerLines(), '',
|
|
425
|
+
'# ' + derived.product.displayName + ' — Adapter for ' + tools.join(' + '), '',
|
|
426
|
+
'Read the project spine first: [' + targets.spine + '](' + reference + ').',
|
|
427
|
+
'Paths in the shared project instructions below are relative to the project root.', '');
|
|
428
|
+
for (const tool of tools) {
|
|
429
|
+
lines.push('## Tool notes — ' + tool, '');
|
|
430
|
+
for (const note of TOOL_NOTES[tool] || buildDefaultNotes(tool, file)) lines.push('- ' + note);
|
|
431
|
+
lines.push('');
|
|
432
|
+
}
|
|
433
|
+
lines.push(...renderUserSections(derived, options), '');
|
|
434
|
+
files.push({ file, tools, content: lines.join('\n') });
|
|
435
|
+
}
|
|
436
|
+
return files;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Refuse portable-path escapes even when generated input has been modified. */
|
|
440
|
+
function validateRelativeFile(file) {
|
|
441
|
+
if (typeof file !== 'string' || !file || /[\\:]/.test(file) || file.includes('\0') ||
|
|
442
|
+
path.posix.isAbsolute(file) || file.split('/').some(part => !part || part === '.' || part === '..')) {
|
|
443
|
+
throw new Error('Adapter file must be a relative project path: ' + file);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Check existing ancestors before any write; Windows junctions are symlinks here. */
|
|
448
|
+
function safeTarget(outDir, file) {
|
|
449
|
+
validateRelativeFile(file);
|
|
450
|
+
const root = path.resolve(outDir);
|
|
451
|
+
const target = path.resolve(root, file);
|
|
452
|
+
const relative = path.relative(root, target);
|
|
453
|
+
if (!relative || relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
|
|
454
|
+
throw new Error('Adapter path escapes the target project: ' + file);
|
|
455
|
+
}
|
|
456
|
+
let current = path.parse(target).root;
|
|
457
|
+
for (const part of target.slice(current.length).split(path.sep)) {
|
|
458
|
+
current = path.join(current, part);
|
|
459
|
+
let stat;
|
|
460
|
+
try { stat = fs.lstatSync(current); } catch (error) {
|
|
461
|
+
if (error.code === 'ENOENT') break;
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
if (stat.isSymbolicLink()) throw new Error('Adapter path contains a symlink or junction: ' + current);
|
|
465
|
+
if (current === target && !stat.isFile()) throw new Error('Adapter target is not a regular file: ' + current);
|
|
466
|
+
if (current !== target && !stat.isDirectory()) throw new Error('Adapter parent is not a directory: ' + current);
|
|
467
|
+
}
|
|
468
|
+
return target;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Preflight the whole batch to avoid partial writes on a later unsafe path. */
|
|
472
|
+
function writeFiles(outDir, files) {
|
|
473
|
+
for (const { file } of files) safeTarget(outDir, file);
|
|
474
|
+
return files.map(({ file, content }) => {
|
|
475
|
+
const target = safeTarget(outDir, file);
|
|
476
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
477
|
+
safeTarget(outDir, file);
|
|
478
|
+
fs.writeFileSync(target, content, 'utf8');
|
|
479
|
+
return target;
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function writeUserAdapters({ outDir, derived = require('../cli/lib/packs').DERIVED,
|
|
484
|
+
packs, userName, language, tools, force = false } = {}) {
|
|
485
|
+
if (!outDir) throw new Error('A target project directory is required');
|
|
486
|
+
const files = generateUserFiles(derived, { packs, userName, language, tools });
|
|
487
|
+
const blockers = listAdoptionBlockers(outDir, files, USER_CONFIG_MARKER);
|
|
488
|
+
if (blockers.length && !force) {
|
|
489
|
+
throw new Error('REFUSING to overwrite files not owned by this user configuration: ' +
|
|
490
|
+
blockers.join(', ') + '. Review them and use --force to replace them explicitly.');
|
|
491
|
+
}
|
|
492
|
+
return writeFiles(outDir, files);
|
|
493
|
+
}
|
|
494
|
+
|
|
342
495
|
/**
|
|
343
496
|
* Adoption guard: list on-disk targets under outDir that exist but do NOT
|
|
344
497
|
* carry the AUTO-GENERATED marker (i.e. hand-authored files that would be
|
|
345
498
|
* clobbered). Overwriting them requires an explicit --force.
|
|
346
499
|
*/
|
|
347
|
-
function listAdoptionBlockers(outDir, files) {
|
|
500
|
+
function listAdoptionBlockers(outDir, files, marker = GENERATED_MARKER) {
|
|
348
501
|
const blockers = [];
|
|
349
502
|
for (const { file } of files) {
|
|
350
|
-
const target =
|
|
503
|
+
const target = safeTarget(outDir, file);
|
|
351
504
|
if (!fs.existsSync(target)) continue;
|
|
352
505
|
const current = fs.readFileSync(target, 'utf8');
|
|
353
|
-
if (!current.includes(GENERATED_MARKER)) blockers.push(file);
|
|
506
|
+
if (!current.includes(GENERATED_MARKER) || !current.includes(marker)) blockers.push(file);
|
|
354
507
|
}
|
|
355
508
|
return blockers;
|
|
356
509
|
}
|
|
@@ -358,14 +511,7 @@ function listAdoptionBlockers(outDir, files) {
|
|
|
358
511
|
/** Write generated spine + adapters under outDir, preserving relative paths. */
|
|
359
512
|
function writeAdapters({ registryPath = DEFAULT_REGISTRY_PATH, outDir = DEFAULT_OUT_DIR } = {}) {
|
|
360
513
|
const files = generateAllFiles(loadRegistry(registryPath));
|
|
361
|
-
|
|
362
|
-
for (const { file, content } of files) {
|
|
363
|
-
const target = path.join(outDir, file);
|
|
364
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
365
|
-
fs.writeFileSync(target, content, 'utf8');
|
|
366
|
-
written.push(target);
|
|
367
|
-
}
|
|
368
|
-
return written;
|
|
514
|
+
return writeFiles(outDir, files);
|
|
369
515
|
}
|
|
370
516
|
|
|
371
517
|
/**
|
|
@@ -398,13 +544,20 @@ function check({ registryPath = DEFAULT_REGISTRY_PATH, outDir = DEFAULT_OUT_DIR
|
|
|
398
544
|
|
|
399
545
|
function parseOutDir(args) {
|
|
400
546
|
const idx = args.indexOf('--out-dir');
|
|
547
|
+
const targetIdx = args.indexOf('--target');
|
|
548
|
+
if (targetIdx !== -1) {
|
|
549
|
+
if (args.includes('--adopt') || idx !== -1) throw new Error('--target, --adopt and --out-dir are mutually exclusive');
|
|
550
|
+
const value = args[targetIdx + 1];
|
|
551
|
+
if (!value || value.startsWith('--')) throw new Error('--target requires a directory path');
|
|
552
|
+
return path.resolve(value);
|
|
553
|
+
}
|
|
401
554
|
if (args.includes('--adopt')) {
|
|
402
555
|
if (idx !== -1) throw new Error('--adopt and --out-dir are mutually exclusive (--adopt targets the repo root)');
|
|
403
556
|
return REPO_ROOT;
|
|
404
557
|
}
|
|
405
558
|
if (idx === -1) return DEFAULT_OUT_DIR;
|
|
406
559
|
const value = args[idx + 1];
|
|
407
|
-
if (!value) throw new Error('--out-dir requires a directory path');
|
|
560
|
+
if (!value || value.startsWith('--')) throw new Error('--out-dir requires a directory path');
|
|
408
561
|
return path.resolve(value);
|
|
409
562
|
}
|
|
410
563
|
|
|
@@ -417,6 +570,42 @@ function main(argv) {
|
|
|
417
570
|
console.error(err.message);
|
|
418
571
|
return 1;
|
|
419
572
|
}
|
|
573
|
+
if (args.includes('--target')) {
|
|
574
|
+
try {
|
|
575
|
+
const option = (name) => {
|
|
576
|
+
const index = args.indexOf(name);
|
|
577
|
+
if (index === -1) return undefined;
|
|
578
|
+
const value = args[index + 1];
|
|
579
|
+
if (!value || value.startsWith('--')) throw new Error(name + ' requires a value');
|
|
580
|
+
return value;
|
|
581
|
+
};
|
|
582
|
+
const packs = option('--packs');
|
|
583
|
+
const tools = option('--tools');
|
|
584
|
+
const options = { packs: !packs || packs === 'all' ? undefined : packs.split(',').map(value => value.trim()),
|
|
585
|
+
tools: !tools || tools === 'all' ? undefined : tools === 'none' ? [] : tools.split(',').map(value => value.trim()),
|
|
586
|
+
userName: option('--user-name'), language: option('--language') };
|
|
587
|
+
const derived = require('../cli/lib/packs').DERIVED;
|
|
588
|
+
const files = generateUserFiles(derived, options);
|
|
589
|
+
if (args.includes('--print')) {
|
|
590
|
+
for (const { file, content } of files) console.log(file + '\n' + content);
|
|
591
|
+
return 0;
|
|
592
|
+
}
|
|
593
|
+
if (args.includes('--check')) {
|
|
594
|
+
const mismatches = files.filter(({ file, content }) => {
|
|
595
|
+
const target = safeTarget(outDir, file);
|
|
596
|
+
return !fs.existsSync(target) || normalizeEol(fs.readFileSync(target, 'utf8')) !== normalizeEol(content);
|
|
597
|
+
});
|
|
598
|
+
if (mismatches.length) console.error('User adapter drift: ' + mismatches.map(entry => entry.file).join(', '));
|
|
599
|
+
return mismatches.length ? 1 : 0;
|
|
600
|
+
}
|
|
601
|
+
const written = writeUserAdapters({ outDir, derived, ...options, force: args.includes('--force') });
|
|
602
|
+
for (const target of written) console.log('Generated ' + target);
|
|
603
|
+
return 0;
|
|
604
|
+
} catch (error) {
|
|
605
|
+
console.error(error.message);
|
|
606
|
+
return 1;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
420
609
|
const adopting = args.includes('--adopt');
|
|
421
610
|
const label = adopting ? 'root adapters + spine' : 'adapters';
|
|
422
611
|
|
|
@@ -467,6 +656,13 @@ module.exports = {
|
|
|
467
656
|
DEFAULT_OUT_DIR,
|
|
468
657
|
REPO_ROOT,
|
|
469
658
|
GENERATED_MARKER,
|
|
659
|
+
USER_CONFIG_MARKER,
|
|
660
|
+
buildIDEConfigs,
|
|
661
|
+
get IDE_CONFIGS() { return buildIDEConfigs(require('../cli/lib/packs').DERIVED); },
|
|
662
|
+
renderUserConfig,
|
|
663
|
+
generateUserFiles,
|
|
664
|
+
writeUserAdapters,
|
|
665
|
+
safeTarget,
|
|
470
666
|
TOOL_NOTES,
|
|
471
667
|
validateTargets,
|
|
472
668
|
buildRegistryFacts,
|