@juangcarmona/lighthouse 0.6.0 → 0.8.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/README.md +7 -4
- package/assets/command-aliases.json +11 -0
- package/assets/profiles/catalog.json +1019 -17
- package/assets/profiles/mobile-expo/README.md +19 -0
- package/assets/profiles/mobile-expo/SKILL.md +14 -0
- package/assets/profiles/mobile-expo/adoption.md +11 -0
- package/assets/profiles/mobile-expo/guardrails.md +14 -0
- package/assets/profiles/mobile-expo/testing.md +13 -0
- package/assets/profiles/mobile-maui/README.md +19 -0
- package/assets/profiles/mobile-maui/SKILL.md +14 -0
- package/assets/profiles/mobile-maui/adoption.md +11 -0
- package/assets/profiles/mobile-maui/guardrails.md +13 -0
- package/assets/profiles/mobile-maui/testing.md +13 -0
- package/assets/profiles/pwa-capability/README.md +19 -0
- package/assets/profiles/pwa-capability/SKILL.md +14 -0
- package/assets/profiles/pwa-capability/adoption.md +9 -0
- package/assets/profiles/pwa-capability/guardrails.md +13 -0
- package/assets/profiles/pwa-capability/offline.md +11 -0
- package/assets/profiles/pwa-capability/testing.md +15 -0
- package/assets/profiles/react-web-platform/README.md +24 -0
- package/assets/profiles/react-web-platform/SKILL.md +18 -0
- package/assets/profiles/react-web-platform/adoption.md +17 -0
- package/assets/profiles/react-web-platform/guardrails.md +17 -0
- package/assets/profiles/react-web-platform/structure.md +36 -0
- package/assets/profiles/react-web-platform/testing.md +15 -0
- package/assets/profiles/web-bff/README.md +22 -0
- package/assets/profiles/web-bff/SKILL.md +16 -0
- package/assets/profiles/web-bff/adoption.md +9 -0
- package/assets/profiles/web-bff/security.md +16 -0
- package/assets/profiles/web-bff/testing.md +14 -0
- package/assets/schemas/command-aliases.schema.json +35 -0
- package/assets/schemas/init-plan.schema.json +40 -0
- package/assets/schemas/profile-catalog.schema.json +286 -20
- package/assets/skills/init/SKILL.md +30 -0
- package/package.json +5 -3
- package/src/aliases.mjs +56 -0
- package/src/create-dotnet.mjs +5 -1
- package/src/init-plan.mjs +62 -0
- package/src/lighthouse.mjs +443 -14
- package/src/profiles.mjs +78 -5
package/src/lighthouse.mjs
CHANGED
|
@@ -7,9 +7,12 @@ import { basename, dirname, resolve } from 'node:path';
|
|
|
7
7
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
8
8
|
import { evaluateGuardrails, readGuardrailCatalog } from './guardrails.mjs';
|
|
9
9
|
import { composeProfiles, normalizeApplication, profileCompositionResult, readProfileCatalog } from './profiles.mjs';
|
|
10
|
+
import { hasUnresolvedQuestions, manifestFromInitPlan, validateInitPlan } from './init-plan.mjs';
|
|
11
|
+
import { readCommandAliases, verifyCommandAliases } from './aliases.mjs';
|
|
10
12
|
let sdlcCapabilities;
|
|
11
13
|
let sdlcReconcile;
|
|
12
14
|
let sdlcGithub;
|
|
15
|
+
let sdlcAliases;
|
|
13
16
|
try {
|
|
14
17
|
[sdlcCapabilities, sdlcReconcile, sdlcGithub] = await Promise.all([
|
|
15
18
|
import('@juangcarmona/lighthouse-sdlc/capabilities'),
|
|
@@ -17,6 +20,7 @@ try {
|
|
|
17
20
|
import('@juangcarmona/lighthouse-sdlc/github'),
|
|
18
21
|
]);
|
|
19
22
|
} catch { /* Standalone generated starters can run without the optional SDLC package. */ }
|
|
23
|
+
try { sdlcAliases = await import('@juangcarmona/lighthouse-sdlc/aliases'); } catch { /* Older standalone installations may not publish aliases. */ }
|
|
20
24
|
|
|
21
25
|
const root = process.cwd();
|
|
22
26
|
const packageRequire = createRequire(import.meta.url);
|
|
@@ -41,19 +45,28 @@ const managedPaths = [
|
|
|
41
45
|
'.husky/pre-push',
|
|
42
46
|
'.github/workflows/lighthouse.yml',
|
|
43
47
|
'.lighthouse/sdlc.json',
|
|
48
|
+
'.agents/skills/lighthouse-init/SKILL.md',
|
|
44
49
|
upgradeStateRelative,
|
|
50
|
+
'.lighthouse/commands.json',
|
|
45
51
|
];
|
|
46
52
|
const profileManagedPaths = Object.freeze({
|
|
47
53
|
'visual-theme': ['docs/design/DESIGN.md', '.agents/skills/lighthouse-ui/SKILL.md'],
|
|
48
54
|
'dotnet-backend': ['docs/profiles/dotnet/README.md', 'docs/profiles/dotnet/adoption.md', 'docs/profiles/dotnet/architecture.md', 'docs/profiles/dotnet/guardrails.md', 'docs/profiles/dotnet/practices.md', 'docs/profiles/dotnet/testing-and-development.md', 'docs/profiles/dotnet/use-cases.md', '.agents/skills/lighthouse-dotnet/SKILL.md'],
|
|
55
|
+
'react-web-platform': ['docs/profiles/react-web-platform/README.md', 'docs/profiles/react-web-platform/adoption.md', 'docs/profiles/react-web-platform/structure.md', 'docs/profiles/react-web-platform/guardrails.md', 'docs/profiles/react-web-platform/testing.md', '.agents/skills/lighthouse-react-web/SKILL.md'],
|
|
56
|
+
'web-bff': ['docs/profiles/web-bff/README.md', 'docs/profiles/web-bff/adoption.md', 'docs/profiles/web-bff/security.md', 'docs/profiles/web-bff/testing.md', '.agents/skills/lighthouse-web-bff/SKILL.md'],
|
|
57
|
+
'pwa-capability': ['docs/profiles/pwa-capability/README.md', 'docs/profiles/pwa-capability/adoption.md', 'docs/profiles/pwa-capability/guardrails.md', 'docs/profiles/pwa-capability/offline.md', 'docs/profiles/pwa-capability/testing.md', '.agents/skills/lighthouse-pwa/SKILL.md'],
|
|
58
|
+
'mobile-expo': ['docs/profiles/mobile-expo/README.md', 'docs/profiles/mobile-expo/adoption.md', 'docs/profiles/mobile-expo/guardrails.md', 'docs/profiles/mobile-expo/testing.md', '.agents/skills/lighthouse-mobile-expo/SKILL.md'],
|
|
59
|
+
'mobile-maui': ['docs/profiles/mobile-maui/README.md', 'docs/profiles/mobile-maui/adoption.md', 'docs/profiles/mobile-maui/guardrails.md', 'docs/profiles/mobile-maui/testing.md', '.agents/skills/lighthouse-mobile-maui/SKILL.md'],
|
|
49
60
|
});
|
|
50
61
|
const previousManagedPaths = managedPaths.slice(0, -1);
|
|
51
62
|
const legacyManagedPaths = [projection];
|
|
52
|
-
const
|
|
63
|
+
const preSemanticManagedPaths = [projection, 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.lighthouse/sdlc.json', upgradeStateRelative];
|
|
64
|
+
const preSemanticWithoutUpgradePaths = preSemanticManagedPaths.slice(0, -1);
|
|
65
|
+
const supportedProfiles = readProfileCatalog().profiles.map(({ id }) => id);
|
|
53
66
|
const branchPattern = '^(feat|fix|docs|refactor|test|chore|build|ci|perf|revert)/[1-9]\\d*-[a-z0-9]+(?:-[a-z0-9]+)*$';
|
|
54
|
-
const authorityVersions = { productShape: '0.22.0', arcShape: '0.1.
|
|
67
|
+
const authorityVersions = { productShape: '0.22.0', arcShape: '0.1.4' };
|
|
55
68
|
const upgradeSchema = './node_modules/@juangcarmona/lighthouse/assets/schemas/lighthouse-upgrade.schema.json';
|
|
56
|
-
const usage = 'Usage: lighthouse <init|create-dotnet|profiles|authorities|reconcile|upgrade|capabilities|verify> [--name <product>] [--profile <profile>] [--scope <harness|sdlc|all>] [--format <text|json>] [--dry-run] [--apply]';
|
|
69
|
+
const usage = 'Usage: lighthouse <init|aliases|create-dotnet|profiles|authorities|reconcile|upgrade|capabilities|verify> [--name <product>] [--plan-file <path>] [--profile <profile>] [--scope <harness|sdlc|all>] [--format <text|json>] [--adopt-existing] [--dry-run] [--apply]';
|
|
57
70
|
|
|
58
71
|
const defaultRepository = () => ({
|
|
59
72
|
layout: [...layout],
|
|
@@ -68,8 +81,8 @@ function managedPathsFor(manifest) {
|
|
|
68
81
|
|
|
69
82
|
function upgradeScopesFor(manifest) {
|
|
70
83
|
return {
|
|
71
|
-
harness: [projection, 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', ...((manifest?.profiles ?? []).flatMap((profile) => profileManagedPaths[profile] ?? []))],
|
|
72
|
-
sdlc: ['.lighthouse/sdlc.json'],
|
|
84
|
+
harness: [projection, 'scripts/lighthouse-guard.mjs', '.husky/commit-msg', '.husky/pre-push', '.github/workflows/lighthouse.yml', '.agents/skills/lighthouse-init/SKILL.md', ...((manifest?.profiles ?? []).flatMap((profile) => profileManagedPaths[profile] ?? []))],
|
|
85
|
+
sdlc: ['.lighthouse/sdlc.json', '.lighthouse/commands.json'],
|
|
73
86
|
};
|
|
74
87
|
}
|
|
75
88
|
|
|
@@ -96,6 +109,46 @@ function profileAssets(manifest) {
|
|
|
96
109
|
catch (error) { throw new Error(`Cannot read the installed Lighthouse .NET profile: ${error.message}`); }
|
|
97
110
|
}
|
|
98
111
|
}
|
|
112
|
+
if ((manifest?.profiles ?? []).includes('react-web-platform')) {
|
|
113
|
+
const files = ['README.md', 'adoption.md', 'structure.md', 'guardrails.md', 'testing.md', 'SKILL.md'];
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
const target = file === 'SKILL.md' ? '.agents/skills/lighthouse-react-web/SKILL.md' : `docs/profiles/react-web-platform/${file}`;
|
|
116
|
+
try { assets[target] = readFileSync(new URL(`../assets/profiles/react-web-platform/${file}`, import.meta.url), 'utf8'); }
|
|
117
|
+
catch (error) { throw new Error(`Cannot read the installed Lighthouse React web profile: ${error.message}`); }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if ((manifest?.profiles ?? []).includes('web-bff')) {
|
|
121
|
+
const files = ['README.md', 'adoption.md', 'security.md', 'testing.md', 'SKILL.md'];
|
|
122
|
+
for (const file of files) {
|
|
123
|
+
const target = file === 'SKILL.md' ? '.agents/skills/lighthouse-web-bff/SKILL.md' : `docs/profiles/web-bff/${file}`;
|
|
124
|
+
try { assets[target] = readFileSync(new URL(`../assets/profiles/web-bff/${file}`, import.meta.url), 'utf8'); }
|
|
125
|
+
catch (error) { throw new Error(`Cannot read the installed Lighthouse web BFF profile: ${error.message}`); }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if ((manifest?.profiles ?? []).includes('pwa-capability')) {
|
|
129
|
+
const files = ['README.md', 'adoption.md', 'guardrails.md', 'offline.md', 'testing.md', 'SKILL.md'];
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
const target = file === 'SKILL.md' ? '.agents/skills/lighthouse-pwa/SKILL.md' : `docs/profiles/pwa-capability/${file}`;
|
|
132
|
+
try { assets[target] = readFileSync(new URL(`../assets/profiles/pwa-capability/${file}`, import.meta.url), 'utf8'); }
|
|
133
|
+
catch (error) { throw new Error(`Cannot read the installed Lighthouse PWA capability: ${error.message}`); }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if ((manifest?.profiles ?? []).includes('mobile-expo')) {
|
|
137
|
+
const files = ['README.md', 'adoption.md', 'guardrails.md', 'testing.md', 'SKILL.md'];
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
const target = file === 'SKILL.md' ? '.agents/skills/lighthouse-mobile-expo/SKILL.md' : `docs/profiles/mobile-expo/${file}`;
|
|
140
|
+
try { assets[target] = readFileSync(new URL(`../assets/profiles/mobile-expo/${file}`, import.meta.url), 'utf8'); }
|
|
141
|
+
catch (error) { throw new Error(`Cannot read the installed Lighthouse Expo mobile profile: ${error.message}`); }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if ((manifest?.profiles ?? []).includes('mobile-maui')) {
|
|
145
|
+
const files = ['README.md', 'adoption.md', 'guardrails.md', 'testing.md', 'SKILL.md'];
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
const target = file === 'SKILL.md' ? '.agents/skills/lighthouse-mobile-maui/SKILL.md' : `docs/profiles/mobile-maui/${file}`;
|
|
148
|
+
try { assets[target] = readFileSync(new URL(`../assets/profiles/mobile-maui/${file}`, import.meta.url), 'utf8'); }
|
|
149
|
+
catch (error) { throw new Error(`Cannot read the installed Lighthouse .NET MAUI mobile profile: ${error.message}`); }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
99
152
|
return assets;
|
|
100
153
|
}
|
|
101
154
|
|
|
@@ -225,7 +278,7 @@ function render(manifest) {
|
|
|
225
278
|
const sdlcLine = selectedSdlc ? `- SDLC package: \`${selectedSdlc.name}@${selectedSdlc.version}\`\n` : '';
|
|
226
279
|
const selectedUi = manifest.packages?.ui;
|
|
227
280
|
const uiLine = selectedUi ? `- UI package: \`${selectedUi.name}@${selectedUi.version}\`\n` : '';
|
|
228
|
-
return `<!-- Generated by Lighthouse. Edit lighthouse.json and regenerate this file. -->\n\n# ${manifest.product} repository baseline\n\n- Baseline: \`${manifest.baseline}\`\n- Product: \`${manifest.product}\`\n- Profiles: ${manifest.profiles.length ? manifest.profiles.map((x) => `\`${x}\``).join(', ') : 'none'}\n${sdlcLine}${uiLine}- ProductShape: ${manifest.integrations.productShape ?? 'not adopted'}\n- ArcShape: ${manifest.integrations.arcShape ?? 'not adopted'}\n- Public site: \`${publicSite}\`\n- Repository roots: ${repository.layout.map((x) => `\`${x}/\``).join(', ')}\n- Commit convention: \`${repository.conventions.commits}\`\n\n## Delivery contract\n\nProductShape remains the source of product intent and Product Changes. ArcShape remains the source of architecture documentation and decisions. Lighthouse installs the repository baseline and verifies the selected integrations without replacing either authority.\n\nThe repository keeps source code in \`src/\`, documentation in \`docs/\`, infrastructure in \`infra/\` and repository automation in \`scripts/\`. A project may record a narrow, owned exception, but an initializer never overwrites an existing project file.\n\nThe selected SDLC package owns lifecycle skills, commands, meta-commands, routes and gates. The \`.lighthouse/\` control plane records the selected package and host adapters without storing agent runtime data.\n\nWhen the public site is enabled, its publication is allowed only after ProductShape and ArcShape validate successfully. A missing or invalid authority stops the build before deployment and reports the command that can repair the missing input.\n\nThis file is managed. Put repository-specific policy in its own documentation and keep this projection generated from \`lighthouse.json\`.\n`;
|
|
281
|
+
return `<!-- Generated by Lighthouse. Edit lighthouse.json and regenerate this file. -->\n\n# ${manifest.product} repository baseline\n\n- Baseline: \`${manifest.baseline}\`\n- Product: \`${manifest.product}\`\n- Profiles: ${manifest.profiles.length ? manifest.profiles.map((x) => `\`${x}\``).join(', ') : 'none'}\n${sdlcLine}${uiLine}- ProductShape: ${manifest.integrations.productShape ?? 'not adopted'}\n- ArcShape: ${manifest.integrations.arcShape ?? 'not adopted'}\n- Public site: \`${publicSite}\`\n- Repository roots: ${repository.layout.map((x) => `\`${x}/\``).join(', ')}\n- Commit convention: \`${repository.conventions.commits}\`\n\n## Delivery contract\n\nProductShape remains the source of product intent and Product Changes. ArcShape remains the source of architecture documentation and decisions. Lighthouse installs the repository baseline and verifies the selected integrations without replacing either authority.\n\nThe repository keeps source code in \`src/\`, documentation in \`docs/\`, infrastructure in \`infra/\` and repository automation in \`scripts/\`. A project may record a narrow, owned exception, but an initializer never overwrites an existing project file.\n\nThe selected SDLC package owns lifecycle skills, commands, meta-commands, routes and gates. The \`.lighthouse/\` control plane records the selected package, command aliases and host adapters without storing agent runtime data. Use \`lighthouse aliases --format json\` or \`.lighthouse/commands.json\` to discover canonical and short agent commands.\n\nWhen the public site is enabled, its publication is allowed only after ProductShape and ArcShape validate successfully. A missing or invalid authority stops the build before deployment and reports the command that can repair the missing input.\n\nThis file is managed. Put repository-specific policy in its own documentation and keep this projection generated from \`lighthouse.json\`.\n`;
|
|
229
282
|
}
|
|
230
283
|
|
|
231
284
|
function guardrailScript(manifest) {
|
|
@@ -309,6 +362,59 @@ jobs:
|
|
|
309
362
|
`;
|
|
310
363
|
}
|
|
311
364
|
|
|
365
|
+
function readSdlcCommandAliases() {
|
|
366
|
+
try {
|
|
367
|
+
const path = packageRequire.resolve('@juangcarmona/lighthouse-sdlc/command-aliases.json');
|
|
368
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
369
|
+
} catch { return null; }
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function commandAliasesProjection() {
|
|
373
|
+
const contracts = [{ contract: readCommandAliases(), packageVersion: cliPackage.version }];
|
|
374
|
+
const sdlc = readSdlcCommandAliases();
|
|
375
|
+
if (sdlc) contracts.push({ contract: sdlc, packageVersion: sdlcPackage.version });
|
|
376
|
+
const aliases = contracts.flatMap(({ contract, packageVersion }) => (contract.aliases ?? []).map((alias) => ({
|
|
377
|
+
...alias,
|
|
378
|
+
owner: contract.package.name,
|
|
379
|
+
packageVersion,
|
|
380
|
+
})));
|
|
381
|
+
return {
|
|
382
|
+
schemaVersion: 1,
|
|
383
|
+
contractVersion: 1,
|
|
384
|
+
canonicalNamespace: '/lighthouse',
|
|
385
|
+
aliasNamespace: '/lh',
|
|
386
|
+
packages: contracts.map(({ contract, packageVersion }) => ({
|
|
387
|
+
name: contract.package.name,
|
|
388
|
+
version: packageVersion,
|
|
389
|
+
contractVersion: contract.contractVersion,
|
|
390
|
+
aliases: contract.aliases,
|
|
391
|
+
})),
|
|
392
|
+
aliases,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function commandAliasProjectionText(projection) {
|
|
397
|
+
return `${JSON.stringify(projection, null, 2)}\n`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function commandAliases(format = 'text') {
|
|
401
|
+
const harness = readCommandAliases();
|
|
402
|
+
const errors = await verifyCommandAliases(harness);
|
|
403
|
+
const sdlc = readSdlcCommandAliases();
|
|
404
|
+
if (!sdlc) errors.push({ code: 'aliases.sdlc-unavailable', message: 'The installed SDLC package does not publish command aliases; upgrade it before using the SDLC alias namespace.' });
|
|
405
|
+
else if (sdlcAliases) errors.push(...(await sdlcAliases.verifyCommandAliases(sdlc)));
|
|
406
|
+
else errors.push({ code: 'aliases.sdlc-validator-unavailable', message: 'The installed SDLC package does not publish its alias validator.' });
|
|
407
|
+
const projection = commandAliasesProjection();
|
|
408
|
+
const report = { ...projection, status: errors.length ? 'invalid' : 'valid', valid: errors.length === 0, errors };
|
|
409
|
+
if (format === 'json') console.log(JSON.stringify(report, null, 2));
|
|
410
|
+
else {
|
|
411
|
+
console.log(`Lighthouse command aliases: ${report.status}`);
|
|
412
|
+
for (const alias of report.aliases) console.log(` ${alias.alias} -> ${alias.canonical}`);
|
|
413
|
+
for (const error of report.errors) console.error(` ${error.code}: ${error.message}`);
|
|
414
|
+
}
|
|
415
|
+
if (errors.length) process.exitCode = 1;
|
|
416
|
+
}
|
|
417
|
+
|
|
312
418
|
function managedFiles(manifest) {
|
|
313
419
|
const files = {
|
|
314
420
|
[projection]: render(manifest),
|
|
@@ -316,10 +422,12 @@ function managedFiles(manifest) {
|
|
|
316
422
|
'.husky/commit-msg': commitHook(),
|
|
317
423
|
'.husky/pre-push': prePushHook(),
|
|
318
424
|
'.github/workflows/lighthouse.yml': workflow(),
|
|
425
|
+
'.agents/skills/lighthouse-init/SKILL.md': readFileSync(new URL('../assets/skills/init/SKILL.md', import.meta.url), 'utf8'),
|
|
319
426
|
};
|
|
320
427
|
Object.assign(files, profileAssets(manifest));
|
|
321
428
|
const controlPlane = sdlcControlPlane(manifest);
|
|
322
429
|
if (controlPlane) files['.lighthouse/sdlc.json'] = controlPlane;
|
|
430
|
+
files['.lighthouse/commands.json'] = commandAliasProjectionText(commandAliasesProjection());
|
|
323
431
|
return files;
|
|
324
432
|
}
|
|
325
433
|
|
|
@@ -541,7 +649,13 @@ function validate(manifest) {
|
|
|
541
649
|
const previousCompleteManaged = Array.isArray(manifest.managed)
|
|
542
650
|
&& manifest.managed.length === previousManagedPaths.length
|
|
543
651
|
&& manifest.managed.every((entry, index) => entry === previousManagedPaths[index]);
|
|
544
|
-
|
|
652
|
+
const preSemanticManaged = Array.isArray(manifest.managed)
|
|
653
|
+
&& manifest.managed.length === preSemanticManagedPaths.length
|
|
654
|
+
&& manifest.managed.every((entry, index) => entry === preSemanticManagedPaths[index]);
|
|
655
|
+
const preSemanticWithoutUpgrade = Array.isArray(manifest.managed)
|
|
656
|
+
&& manifest.managed.length === preSemanticWithoutUpgradePaths.length
|
|
657
|
+
&& manifest.managed.every((entry, index) => entry === preSemanticWithoutUpgradePaths[index]);
|
|
658
|
+
if (!legacyManaged && !profilelessManaged && !previousCompleteManaged && !completeManaged && !preSemanticManaged && !preSemanticWithoutUpgrade) {
|
|
545
659
|
problems.push(`managed must be ${legacyManagedPaths.join(', ')}, the previous Lighthouse guardrail set or the selected profile set`);
|
|
546
660
|
}
|
|
547
661
|
if ('repository' in manifest) {
|
|
@@ -592,6 +706,103 @@ function productNameFromArgs(args) {
|
|
|
592
706
|
return value;
|
|
593
707
|
}
|
|
594
708
|
|
|
709
|
+
function valueFromArgs(args, name) {
|
|
710
|
+
const index = args.indexOf(name);
|
|
711
|
+
if (index !== -1) return args[index + 1];
|
|
712
|
+
return args.find((arg) => arg.startsWith(`${name}=`))?.slice(name.length + 1);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
async function readInitPlan(planFile) {
|
|
716
|
+
let plan;
|
|
717
|
+
try { plan = JSON.parse(await readFile(resolve(root, planFile), 'utf8')); }
|
|
718
|
+
catch (error) { throw new Error(`Cannot read initialization plan ${planFile}: ${error.message}`); }
|
|
719
|
+
const problems = validateInitPlan(plan, supportedProfiles);
|
|
720
|
+
if (problems.length) throw new Error(`Invalid initialization plan:\n${problems.map((problem) => ` ${problem}`).join('\n')}`);
|
|
721
|
+
return plan;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function printInitializationPlan(plan, result, format) {
|
|
725
|
+
const report = {
|
|
726
|
+
schemaVersion: 1,
|
|
727
|
+
intent: plan.intent,
|
|
728
|
+
product: plan.product,
|
|
729
|
+
application: plan.application,
|
|
730
|
+
profiles: plan.profiles,
|
|
731
|
+
reference: plan.reference ?? null,
|
|
732
|
+
openQuestions: plan.openQuestions,
|
|
733
|
+
status: result.plan.conflicts.length || plan.openQuestions.length ? 'blocked' : 'ready',
|
|
734
|
+
manifest: {
|
|
735
|
+
product: result.manifest.product,
|
|
736
|
+
application: result.manifest.application,
|
|
737
|
+
profiles: result.manifest.profiles,
|
|
738
|
+
},
|
|
739
|
+
operations: result.plan.operations.map(({ path, action }) => ({ path, action })),
|
|
740
|
+
conflicts: result.plan.conflicts,
|
|
741
|
+
apply: 'lighthouse init --plan-file <path> --apply',
|
|
742
|
+
};
|
|
743
|
+
if (format === 'json') {
|
|
744
|
+
console.log(JSON.stringify(report, null, 2));
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
console.log('Lighthouse semantic initialization plan:');
|
|
748
|
+
console.log(` product ${plan.product}`);
|
|
749
|
+
console.log(` intent ${plan.intent}`);
|
|
750
|
+
console.log(` application ${plan.application.shape} (${plan.application.stacks.join(', ') || 'no stack'})`);
|
|
751
|
+
console.log(` profiles ${plan.profiles.join(', ') || 'none'}`);
|
|
752
|
+
console.log(` reference ${plan.reference?.kind ?? 'none'}`);
|
|
753
|
+
if (plan.openQuestions.length) for (const question of plan.openQuestions) console.error(` question ${question}`);
|
|
754
|
+
for (const operation of result.plan.operations) console.log(` ${operation.action} ${operation.path}`);
|
|
755
|
+
for (const conflict of result.plan.conflicts) console.error(` conflict ${conflict}`);
|
|
756
|
+
console.log(' default is dry-run; apply only after review with --apply');
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
async function initializeFromPlan(args, planFile) {
|
|
760
|
+
const plan = await readInitPlan(planFile);
|
|
761
|
+
const format = formatFromArgs(args);
|
|
762
|
+
const apply = args.includes('--apply');
|
|
763
|
+
const dryRun = args.includes('--dry-run') || !apply;
|
|
764
|
+
if (args.includes('--apply') && args.includes('--dry-run')) throw new Error('--apply and --dry-run cannot be used together.');
|
|
765
|
+
if (format === 'json' && apply) throw new Error('--format json is available for semantic initialization dry runs; omit it when applying.');
|
|
766
|
+
const existing = await readManifest();
|
|
767
|
+
let manifest;
|
|
768
|
+
if (existing) {
|
|
769
|
+
const problems = validate(existing);
|
|
770
|
+
if (problems.length) throw new Error(problems.map((problem) => `Invalid lighthouse.json: ${problem}`).join('\n'));
|
|
771
|
+
if (existing.product !== plan.product) throw new Error(`Initialization plan targets ${plan.product}, but lighthouse.json belongs to ${existing.product}.`);
|
|
772
|
+
if (JSON.stringify(existing.profiles ?? []) !== JSON.stringify(plan.profiles)) throw new Error('Initialization plan profiles differ from lighthouse.json; resolve the selection before applying.');
|
|
773
|
+
if (JSON.stringify(normalizeApplication(existing.application)) !== JSON.stringify(plan.application)) throw new Error('Initialization plan application differs from lighthouse.json; resolve the selection before applying.');
|
|
774
|
+
manifest = existing;
|
|
775
|
+
} else {
|
|
776
|
+
manifest = manifestFromInitPlan(plan, defaultManifest);
|
|
777
|
+
}
|
|
778
|
+
const problems = validate(manifest);
|
|
779
|
+
if (problems.length) throw new Error(problems.map((problem) => `Invalid lighthouse.json: ${problem}`).join('\n'));
|
|
780
|
+
const composition = composeProfiles(readProfileCatalog(), manifest);
|
|
781
|
+
if (composition.status === 'failed') throw new Error(`Initialization plan has incompatible profiles:\n${composition.selected.flatMap((profile) => profile.problems).map((problem) => ` ${problem}`).join('\n')}`);
|
|
782
|
+
const initialization = { manifest, plan: await planInitialization(manifest, !existing) };
|
|
783
|
+
printInitializationPlan(plan, initialization, format);
|
|
784
|
+
if (initialization.plan.conflicts.length) {
|
|
785
|
+
if (dryRun) process.exitCode = 1;
|
|
786
|
+
else throw new Error('Initialization stopped; resolve the reported conflicts and run the plan again.');
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (hasUnresolvedQuestions(plan)) {
|
|
790
|
+
if (dryRun) process.exitCode = 1;
|
|
791
|
+
else throw new Error('Initialization stopped; answer the open questions before using --apply.');
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (dryRun) return;
|
|
795
|
+
if (!existing) await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx' });
|
|
796
|
+
for (const operation of initialization.plan.operations.filter((item) => item.path.endsWith('/'))) await mkdir(resolve(root, operation.path), { recursive: true });
|
|
797
|
+
const files = managedFiles(manifest);
|
|
798
|
+
for (const [file, content] of Object.entries(files)) {
|
|
799
|
+
await writeFile(resolve(root, file), content, { flag: 'wx', mode: file.startsWith('.husky/') ? 0o755 : 0o644 });
|
|
800
|
+
if (file.startsWith('.husky/')) await chmod(resolve(root, file), 0o755);
|
|
801
|
+
}
|
|
802
|
+
await writeUpgradeState(manifest, null, files);
|
|
803
|
+
console.log(`Initialized ${manifest.product} from the semantic plan. Run lighthouse verify.`);
|
|
804
|
+
}
|
|
805
|
+
|
|
595
806
|
async function planInitialization(manifest, manifestMissing) {
|
|
596
807
|
const operations = [];
|
|
597
808
|
const conflicts = [];
|
|
@@ -649,6 +860,8 @@ function printReconciliationPlan(plan) {
|
|
|
649
860
|
}
|
|
650
861
|
|
|
651
862
|
async function initialize(args) {
|
|
863
|
+
const planFile = valueFromArgs(args, '--plan-file');
|
|
864
|
+
if (planFile) return initializeFromPlan(args, planFile);
|
|
652
865
|
let manifest = await readManifest();
|
|
653
866
|
const manifestMissing = !manifest;
|
|
654
867
|
const selectedProfiles = profileArguments(args);
|
|
@@ -809,6 +1022,25 @@ const authorityCommands = [
|
|
|
809
1022
|
},
|
|
810
1023
|
];
|
|
811
1024
|
|
|
1025
|
+
const authorityAdoptionCommands = [
|
|
1026
|
+
{
|
|
1027
|
+
id: 'productShape',
|
|
1028
|
+
label: 'ProductShape',
|
|
1029
|
+
packageName: '@prodshape/cli',
|
|
1030
|
+
command: 'prodshape',
|
|
1031
|
+
path: 'docs/product/model',
|
|
1032
|
+
validateArgs: ['validate', '--format', 'json'],
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
id: 'arcShape',
|
|
1036
|
+
label: 'ArcShape',
|
|
1037
|
+
packageName: '@arcshape/cli',
|
|
1038
|
+
command: 'arcshape',
|
|
1039
|
+
path: 'docs/architecture',
|
|
1040
|
+
validateArgs: ['validate', '--docs', 'docs/architecture', '--links', '--citations', '--format', 'json'],
|
|
1041
|
+
},
|
|
1042
|
+
];
|
|
1043
|
+
|
|
812
1044
|
function providerExecutable(authority) {
|
|
813
1045
|
const packageJson = packageRequire.resolve(`${authority.packageName}/package.json`);
|
|
814
1046
|
const metadata = JSON.parse(readFileSync(packageJson, 'utf8'));
|
|
@@ -842,6 +1074,132 @@ function runAuthorityCommand(authority, dryRun) {
|
|
|
842
1074
|
};
|
|
843
1075
|
}
|
|
844
1076
|
|
|
1077
|
+
function parseProviderReport(output) {
|
|
1078
|
+
const text = output.trim();
|
|
1079
|
+
try { return JSON.parse(text); } catch { /* The provider may prefix JSON with a diagnostic line. */ }
|
|
1080
|
+
const start = text.indexOf('{');
|
|
1081
|
+
const end = text.lastIndexOf('}');
|
|
1082
|
+
if (start === -1 || end <= start) return null;
|
|
1083
|
+
try { return JSON.parse(text.slice(start, end + 1)); } catch { return null; }
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function authorityStatus(present, validation, recordedVersion, providerVersion) {
|
|
1087
|
+
if (!present) return 'absent';
|
|
1088
|
+
if (validation.status === 'unavailable') return 'unavailable';
|
|
1089
|
+
if (!validation.valid) return 'invalid';
|
|
1090
|
+
if (recordedVersion && recordedVersion !== providerVersion) return 'drifted';
|
|
1091
|
+
return recordedVersion ? 'adopted' : 'ready';
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
async function runAuthorityValidation(authority, manifest) {
|
|
1095
|
+
const providerVersion = installedPackageVersion(authority.packageName, 'package.json');
|
|
1096
|
+
const present = await isDirectory(resolve(root, authority.path));
|
|
1097
|
+
const command = `${authority.command} ${authority.validateArgs.join(' ')}`;
|
|
1098
|
+
if (!present) {
|
|
1099
|
+
return {
|
|
1100
|
+
id: authority.id,
|
|
1101
|
+
label: authority.label,
|
|
1102
|
+
provider: authority.packageName,
|
|
1103
|
+
version: providerVersion,
|
|
1104
|
+
path: authority.path,
|
|
1105
|
+
command,
|
|
1106
|
+
recordedVersion: manifest.integrations[authority.id],
|
|
1107
|
+
status: 'absent',
|
|
1108
|
+
valid: false,
|
|
1109
|
+
diagnostics: [`${authority.path} is missing`],
|
|
1110
|
+
nextAction: `Create the ${authority.label} authority through its supported initializer, then rerun lighthouse authorities --adopt-existing.`,
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (!providerVersion) {
|
|
1114
|
+
return {
|
|
1115
|
+
id: authority.id,
|
|
1116
|
+
label: authority.label,
|
|
1117
|
+
provider: authority.packageName,
|
|
1118
|
+
version: null,
|
|
1119
|
+
path: authority.path,
|
|
1120
|
+
command,
|
|
1121
|
+
recordedVersion: manifest.integrations[authority.id],
|
|
1122
|
+
status: 'unavailable',
|
|
1123
|
+
valid: false,
|
|
1124
|
+
diagnostics: [`${authority.packageName} is not installed`],
|
|
1125
|
+
nextAction: `Install ${authority.packageName}, then rerun lighthouse authorities --adopt-existing.`,
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
let result;
|
|
1129
|
+
try {
|
|
1130
|
+
result = spawnSync(process.execPath, [providerExecutable(authority), ...authority.validateArgs], {
|
|
1131
|
+
cwd: root,
|
|
1132
|
+
encoding: 'utf8',
|
|
1133
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1134
|
+
});
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
return {
|
|
1137
|
+
id: authority.id,
|
|
1138
|
+
label: authority.label,
|
|
1139
|
+
provider: authority.packageName,
|
|
1140
|
+
version: providerVersion,
|
|
1141
|
+
path: authority.path,
|
|
1142
|
+
command,
|
|
1143
|
+
recordedVersion: manifest.integrations[authority.id],
|
|
1144
|
+
status: 'unavailable',
|
|
1145
|
+
valid: false,
|
|
1146
|
+
diagnostics: [error.message],
|
|
1147
|
+
nextAction: `Run ${command} directly, repair the provider and rerun lighthouse authorities --adopt-existing.`,
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;
|
|
1151
|
+
const report = parseProviderReport(output);
|
|
1152
|
+
const valid = result.status === 0 && report?.valid !== false;
|
|
1153
|
+
const status = authorityStatus(present, { status: result.status === null ? 'unavailable' : 'completed', valid }, manifest.integrations[authority.id], providerVersion);
|
|
1154
|
+
return {
|
|
1155
|
+
id: authority.id,
|
|
1156
|
+
label: authority.label,
|
|
1157
|
+
provider: authority.packageName,
|
|
1158
|
+
version: providerVersion,
|
|
1159
|
+
path: authority.path,
|
|
1160
|
+
command,
|
|
1161
|
+
recordedVersion: manifest.integrations[authority.id],
|
|
1162
|
+
status,
|
|
1163
|
+
valid,
|
|
1164
|
+
diagnostics: report?.diagnostics ?? (valid ? [] : [output.trim().split('\n').filter(Boolean).slice(-3).join(' ') || `${authority.label} validation failed`]),
|
|
1165
|
+
nextAction: status === 'invalid' ? `Run ${command} directly, repair the reported authority and rerun lighthouse authorities --adopt-existing.` : null,
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
async function authorityAdoptionPlan(manifest) {
|
|
1170
|
+
const authorities = await Promise.all(authorityAdoptionCommands.map((authority) => runAuthorityValidation(authority, manifest)));
|
|
1171
|
+
const blocking = authorities.filter((authority) => ['absent', 'invalid', 'unavailable'].includes(authority.status));
|
|
1172
|
+
const changed = authorities.some((authority) => ['ready', 'drifted'].includes(authority.status));
|
|
1173
|
+
const status = blocking.length ? 'blocked' : changed ? 'ready' : 'adopted';
|
|
1174
|
+
return {
|
|
1175
|
+
schemaVersion: 1,
|
|
1176
|
+
command: 'lighthouse authorities --adopt-existing',
|
|
1177
|
+
status,
|
|
1178
|
+
remoteWrites: 'none',
|
|
1179
|
+
providerWrites: 'none',
|
|
1180
|
+
authorities,
|
|
1181
|
+
operations: changed ? [
|
|
1182
|
+
{ action: 'record', path: 'lighthouse.json', values: Object.fromEntries(authorities.map((authority) => [authority.id, authority.version])) },
|
|
1183
|
+
{ action: 'regenerate', path: 'LIGHTHOUSE.md' },
|
|
1184
|
+
] : [],
|
|
1185
|
+
apply: 'lighthouse authorities --adopt-existing --apply',
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function printAuthorityAdoptionPlan(plan) {
|
|
1190
|
+
console.log(`Lighthouse existing-authority adoption plan: ${plan.status}`);
|
|
1191
|
+
console.log(' ProductShape and ArcShape provider files remain untouched.');
|
|
1192
|
+
console.log(' Remote writes: none.');
|
|
1193
|
+
for (const authority of plan.authorities) {
|
|
1194
|
+
const version = authority.version ?? 'unavailable';
|
|
1195
|
+
console.log(` ${authority.label}: ${authority.status} (${version}) ${authority.path}`);
|
|
1196
|
+
for (const diagnostic of authority.diagnostics) console.error(` ${diagnostic}`);
|
|
1197
|
+
if (authority.nextAction) console.error(` next: ${authority.nextAction}`);
|
|
1198
|
+
}
|
|
1199
|
+
for (const operation of plan.operations) console.log(` ${operation.action} ${operation.path}`);
|
|
1200
|
+
console.log(' default is dry-run; apply only after review with --apply');
|
|
1201
|
+
}
|
|
1202
|
+
|
|
845
1203
|
async function writeManifestAtomically(manifest) {
|
|
846
1204
|
const temporary = `${manifestPath}.tmp-${process.pid}`;
|
|
847
1205
|
await writeFile(temporary, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx' });
|
|
@@ -862,7 +1220,7 @@ async function ensureProjectionReady(manifest) {
|
|
|
862
1220
|
|
|
863
1221
|
async function writeProjection(manifest) {
|
|
864
1222
|
const target = resolve(root, projection);
|
|
865
|
-
await
|
|
1223
|
+
await writeTextAtomically(target, render(manifest));
|
|
866
1224
|
}
|
|
867
1225
|
|
|
868
1226
|
async function authorities(args) {
|
|
@@ -900,6 +1258,37 @@ async function authorities(args) {
|
|
|
900
1258
|
console.log(`Recorded ProductShape ${authorityVersions.productShape} and ArcShape ${authorityVersions.arcShape} in lighthouse.json.`);
|
|
901
1259
|
}
|
|
902
1260
|
|
|
1261
|
+
async function adoptExistingAuthorities(args) {
|
|
1262
|
+
const manifest = await readManifest();
|
|
1263
|
+
if (!manifest) throw new Error('Cannot read lighthouse.json: file does not exist; run lighthouse init --name <product>.');
|
|
1264
|
+
const problems = validate(manifest);
|
|
1265
|
+
if (problems.length) throw new Error(problems.map((problem) => `Invalid lighthouse.json: ${problem}`).join('\n'));
|
|
1266
|
+
const apply = args.includes('--apply');
|
|
1267
|
+
const dryRun = args.includes('--dry-run') || !apply;
|
|
1268
|
+
const format = formatFromArgs(args);
|
|
1269
|
+
if (args.includes('--apply') && args.includes('--dry-run')) throw new Error('--apply and --dry-run cannot be used together.');
|
|
1270
|
+
if (format === 'json' && apply) throw new Error('--format json is available for existing-authority adoption dry runs; omit it when applying.');
|
|
1271
|
+
const plan = await authorityAdoptionPlan(manifest);
|
|
1272
|
+
if (format === 'json') console.log(JSON.stringify(plan, null, 2));
|
|
1273
|
+
else printAuthorityAdoptionPlan(plan);
|
|
1274
|
+
if (dryRun) {
|
|
1275
|
+
if (plan.status === 'blocked') process.exitCode = 1;
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (plan.status === 'blocked') throw new Error('Existing-authority adoption stopped; resolve the reported provider conditions and rerun the plan.');
|
|
1279
|
+
await ensureProjectionReady(manifest);
|
|
1280
|
+
const updated = {
|
|
1281
|
+
...manifest,
|
|
1282
|
+
integrations: Object.fromEntries(plan.authorities.map((authority) => [authority.id, authority.version])),
|
|
1283
|
+
};
|
|
1284
|
+
if (JSON.stringify(updated) !== JSON.stringify(manifest)) await writeManifestAtomically(updated);
|
|
1285
|
+
if (!await exists(resolve(root, projection))) await writeProjection(updated);
|
|
1286
|
+
else if (await readFile(resolve(root, projection), 'utf8') !== render(updated)) await writeProjection(updated);
|
|
1287
|
+
const verified = await authorityAdoptionPlan(updated);
|
|
1288
|
+
if (verified.status !== 'adopted') throw new Error('Existing-authority adoption was written but did not verify; rerun lighthouse authorities --adopt-existing and inspect the provider report.');
|
|
1289
|
+
console.log('Recorded and verified the existing ProductShape and ArcShape authorities. Provider-owned files were not changed.');
|
|
1290
|
+
}
|
|
1291
|
+
|
|
903
1292
|
function gitBranch() {
|
|
904
1293
|
try {
|
|
905
1294
|
return execFileSync('git', ['branch', '--show-current'], {
|
|
@@ -949,6 +1338,40 @@ async function verify(manifest, format = 'text') {
|
|
|
949
1338
|
const profileResult = profileCompositionResult(profileComposition);
|
|
950
1339
|
report.results.push(profileResult);
|
|
951
1340
|
report.summary[profileResult.status] = (report.summary[profileResult.status] ?? 0) + 1;
|
|
1341
|
+
if (validManifest) {
|
|
1342
|
+
const recordedAuthorities = [manifest.integrations.productShape, manifest.integrations.arcShape];
|
|
1343
|
+
const anyRecorded = recordedAuthorities.some((version) => version !== null);
|
|
1344
|
+
const authorityPlan = anyRecorded ? await authorityAdoptionPlan(manifest) : null;
|
|
1345
|
+
const authorityStatuses = authorityPlan?.authorities.map((authority) => authority.status) ?? [];
|
|
1346
|
+
const authorityStatus = !anyRecorded ? 'unconfigured'
|
|
1347
|
+
: authorityStatuses.includes('unavailable') ? 'unavailable'
|
|
1348
|
+
: authorityStatuses.some((status) => ['absent', 'invalid'].includes(status)) ? 'failed'
|
|
1349
|
+
: authorityStatuses.includes('drifted') ? 'drift'
|
|
1350
|
+
: 'passed';
|
|
1351
|
+
const authorityProblems = !anyRecorded
|
|
1352
|
+
? ['ProductShape and ArcShape are not recorded; run lighthouse authorities --adopt-existing after reviewing its dry-run plan.']
|
|
1353
|
+
: authorityPlan.authorities.filter((authority) => authority.status !== 'adopted').flatMap((authority) => [
|
|
1354
|
+
`${authority.label} is ${authority.status}${authority.diagnostics.length ? `: ${authority.diagnostics.join('; ')}` : ''}`,
|
|
1355
|
+
...(authority.nextAction ? [authority.nextAction] : []),
|
|
1356
|
+
]);
|
|
1357
|
+
const authorityResult = {
|
|
1358
|
+
id: 'foundation.authorities',
|
|
1359
|
+
owner: 'lighthouse',
|
|
1360
|
+
level: anyRecorded ? 'required' : 'recommended',
|
|
1361
|
+
kind: 'hard',
|
|
1362
|
+
verification: 'deterministic',
|
|
1363
|
+
status: authorityStatus,
|
|
1364
|
+
evidence: authorityPlan?.authorities.map((authority) => ({ id: authority.id, path: authority.path, version: authority.version, recordedVersion: authority.recordedVersion, command: authority.command })) ?? [],
|
|
1365
|
+
problems: authorityProblems,
|
|
1366
|
+
nextAction: authorityProblems.length ? 'lighthouse authorities --adopt-existing' : null,
|
|
1367
|
+
};
|
|
1368
|
+
report.results.push(authorityResult);
|
|
1369
|
+
report.summary[authorityStatus] = (report.summary[authorityStatus] ?? 0) + 1;
|
|
1370
|
+
if (authorityResult.level === 'required' && !['passed', 'not-applicable'].includes(authorityStatus)) {
|
|
1371
|
+
report.blocking.push(authorityResult.id);
|
|
1372
|
+
report.problems.push(...authorityProblems);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
952
1375
|
const adoption = Object.values(capabilityAdoption(await readUpgradeState()));
|
|
953
1376
|
for (const item of adoption) {
|
|
954
1377
|
if (item.status !== 'adopted' || !item.recordPath || await exists(resolve(root, item.recordPath))) continue;
|
|
@@ -1167,7 +1590,8 @@ for (let index = 0; index < commandArgs.length; index += 1) {
|
|
|
1167
1590
|
const arg = commandArgs[index];
|
|
1168
1591
|
if (command === 'capabilities' && index === 0 && ['select', 'decline', 'plan', 'apply'].includes(arg)) continue;
|
|
1169
1592
|
if (arg === '--dry-run') continue;
|
|
1170
|
-
if (arg === '--apply') { if (!['authorities', 'reconcile', 'upgrade'].includes(command)) invalidArgument = true; continue; }
|
|
1593
|
+
if (arg === '--apply') { if (!['init', 'authorities', 'reconcile', 'upgrade'].includes(command)) invalidArgument = true; continue; }
|
|
1594
|
+
if (arg === '--adopt-existing') { if (command !== 'authorities') invalidArgument = true; continue; }
|
|
1171
1595
|
if (arg === '--name') { if (!commandArgs[index + 1]) invalidArgument = true; else index += 1; continue; }
|
|
1172
1596
|
if (arg.startsWith('--name=') && arg.length > '--name='.length) continue;
|
|
1173
1597
|
if (arg === '--profile') {
|
|
@@ -1180,12 +1604,12 @@ for (let index = 0; index < commandArgs.length; index += 1) {
|
|
|
1180
1604
|
continue;
|
|
1181
1605
|
}
|
|
1182
1606
|
if (arg === '--format') {
|
|
1183
|
-
if (!['profiles', 'verify', 'upgrade', 'capabilities'].includes(command) || !['text', 'json'].includes(commandArgs[index + 1])) invalidArgument = true;
|
|
1607
|
+
if (!['init', 'aliases', 'profiles', 'authorities', 'verify', 'upgrade', 'capabilities'].includes(command) || !['text', 'json'].includes(commandArgs[index + 1])) invalidArgument = true;
|
|
1184
1608
|
else index += 1;
|
|
1185
1609
|
continue;
|
|
1186
1610
|
}
|
|
1187
1611
|
if (arg.startsWith('--format=') && ['text', 'json'].includes(arg.slice('--format='.length))) {
|
|
1188
|
-
if (!['profiles', 'verify', 'upgrade', 'capabilities'].includes(command)) invalidArgument = true;
|
|
1612
|
+
if (!['init', 'aliases', 'profiles', 'authorities', 'verify', 'upgrade', 'capabilities'].includes(command)) invalidArgument = true;
|
|
1189
1613
|
continue;
|
|
1190
1614
|
}
|
|
1191
1615
|
if (arg === '--scope') {
|
|
@@ -1198,19 +1622,24 @@ for (let index = 0; index < commandArgs.length; index += 1) {
|
|
|
1198
1622
|
continue;
|
|
1199
1623
|
}
|
|
1200
1624
|
if (['--id', '--provider', '--repo', '--input', '--plan-file', '--authorize'].includes(arg)) {
|
|
1201
|
-
if (!commandArgs[index + 1]) invalidArgument = true;
|
|
1625
|
+
if (!commandArgs[index + 1] || (arg === '--plan-file' && !['init', 'capabilities'].includes(command))) invalidArgument = true;
|
|
1202
1626
|
else index += 1;
|
|
1203
1627
|
continue;
|
|
1204
1628
|
}
|
|
1205
|
-
if (['--id=', '--provider=', '--repo=', '--input=', '--plan-file=', '--authorize='].some((prefix) => arg.startsWith(prefix) && arg.length > prefix.length))
|
|
1629
|
+
if (['--id=', '--provider=', '--repo=', '--input=', '--plan-file=', '--authorize='].some((prefix) => arg.startsWith(prefix) && arg.length > prefix.length)) {
|
|
1630
|
+
if (arg.startsWith('--plan-file=') && !['init', 'capabilities'].includes(command)) invalidArgument = true;
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1206
1633
|
invalidArgument = true;
|
|
1207
1634
|
}
|
|
1208
|
-
if (!['init', 'profiles', 'authorities', 'reconcile', 'upgrade', 'capabilities', 'verify'].includes(command) || invalidArgument) {
|
|
1635
|
+
if (!['init', 'aliases', 'profiles', 'authorities', 'reconcile', 'upgrade', 'capabilities', 'verify'].includes(command) || invalidArgument) {
|
|
1209
1636
|
console.error(usage);
|
|
1210
1637
|
process.exit(2);
|
|
1211
1638
|
}
|
|
1212
1639
|
try {
|
|
1213
1640
|
if (command === 'init') await initialize(args.slice(1));
|
|
1641
|
+
else if (command === 'aliases') await commandAliases(formatFromArgs(commandArgs));
|
|
1642
|
+
else if (command === 'authorities' && commandArgs.includes('--adopt-existing')) await adoptExistingAuthorities(args.slice(1));
|
|
1214
1643
|
else if (command === 'authorities') await authorities(args.slice(1));
|
|
1215
1644
|
else if (command === 'reconcile') await reconcile(args.slice(1));
|
|
1216
1645
|
else if (command === 'upgrade') await upgrade(args.slice(1));
|