@clearlydev/nx-archweave 0.1.0 → 0.2.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.
Files changed (59) hide show
  1. package/README.md +2 -2
  2. package/dist/eslint/fsd-boundaries.d.ts +27 -10
  3. package/dist/eslint/fsd-policy.d.ts +38 -0
  4. package/dist/eslint/index.d.ts +10 -9
  5. package/dist/eslint/index.js +227 -34
  6. package/dist/eslint/slice-groups.d.ts +12 -0
  7. package/dist/generators/add-agent-rules/generator.js +1175 -23
  8. package/dist/generators/add-backend/generator.js +1127 -75
  9. package/dist/generators/add-fsd-lite/generator.js +1274 -96
  10. package/dist/generators/add-integration/generator.js +4 -35
  11. package/dist/generators/add-review/files/dependabot.yml +7 -0
  12. package/dist/generators/add-review/generator.js +1100 -46
  13. package/dist/generators/doctor/generator.js +784 -131
  14. package/dist/generators/entity/generator.js +251 -32
  15. package/dist/generators/feature/generator.js +251 -32
  16. package/dist/generators/init/generator.js +1955 -549
  17. package/dist/generators/install-integration/generator.js +5 -37
  18. package/dist/generators/page/generator.js +251 -32
  19. package/dist/generators/record-integration/generator.js +5 -36
  20. package/dist/generators/shared/generator.js +251 -32
  21. package/dist/generators/widget/generator.js +251 -32
  22. package/dist/index.js +4 -35
  23. package/package.json +5 -3
  24. package/src/generators/add-agent-rules/schema.json +5 -0
  25. package/src/generators/add-fsd-lite/schema.json +2 -2
  26. package/src/generators/add-integration/schema.json +1 -1
  27. package/src/generators/entity/schema.json +11 -1
  28. package/src/generators/feature/schema.json +11 -1
  29. package/src/generators/init/schema.json +13 -2
  30. package/src/generators/page/schema.json +15 -3
  31. package/src/generators/shared/schema.json +7 -1
  32. package/src/generators/widget/schema.json +11 -1
  33. package/dist/eslint/fsd-boundaries.js +0 -144
  34. package/dist/eslint/tsconfig-aliases.js +0 -114
  35. package/dist/generators/add-agent-rules/render.js +0 -46
  36. package/dist/generators/add-backend/render.js +0 -102
  37. package/dist/generators/add-fsd-lite/render.js +0 -72
  38. package/dist/generators/add-github-templates/render.js +0 -26
  39. package/dist/generators/add-integration/render.js +0 -34
  40. package/dist/generators/add-review/render.js +0 -96
  41. package/dist/generators/allow-builds/render.js +0 -82
  42. package/dist/generators/doctor/integration-report.js +0 -70
  43. package/dist/generators/doctor/render.js +0 -10
  44. package/dist/generators/init/render.js +0 -125
  45. package/dist/generators/init/validation.js +0 -74
  46. package/dist/generators/install-integration/render.js +0 -128
  47. package/dist/generators/record-integration/render.js +0 -31
  48. package/dist/generators/shared/dry-run.js +0 -12
  49. package/dist/generators/shared/frontend-app.js +0 -57
  50. package/dist/generators/shared/project-type.js +0 -39
  51. package/dist/generators/shared/record-pipeline.js +0 -34
  52. package/dist/generators/shared/run-nx.js +0 -24
  53. package/dist/generators/shared/template-files.js +0 -38
  54. package/dist/generators/shared/toolkit-config-file.js +0 -110
  55. package/dist/generators/slice/core.js +0 -185
  56. package/dist/generators/slice/render.js +0 -70
  57. package/dist/lib/nx-plugin.js +0 -7
  58. package/dist/runtime/integrations-composition-root.js +0 -20
  59. package/dist/runtime/node-command-runner.js +0 -81
@@ -1,96 +0,0 @@
1
- /** Line-level LCS diff — small generated files, no diff library. */
2
- export function renderLineDiff(existing, generated) {
3
- const a = existing.split('\n');
4
- const b = generated.split('\n');
5
- const m = a.length;
6
- const n = b.length;
7
- const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
8
- for (let i = m - 1; i >= 0; i--) {
9
- for (let j = n - 1; j >= 0; j--) {
10
- lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
11
- }
12
- }
13
- const out = ['--- existing', '+++ generated'];
14
- let i = 0;
15
- let j = 0;
16
- while (i < m && j < n) {
17
- if (a[i] === b[j]) {
18
- out.push(` ${a[i]}`);
19
- i++;
20
- j++;
21
- }
22
- else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
23
- out.push(`- ${a[i]}`);
24
- i++;
25
- }
26
- else {
27
- out.push(`+ ${b[j]}`);
28
- j++;
29
- }
30
- }
31
- while (i < m)
32
- out.push(`- ${a[i++]}`);
33
- while (j < n)
34
- out.push(`+ ${b[j++]}`);
35
- return out.join('\n');
36
- }
37
- const WORKFLOW_ACTION_LINES = {
38
- created: 'created',
39
- overwritten: 'overwritten with the generated version (per --onConflict=overwrite)',
40
- unchanged: 'unchanged (already matches the generated version)',
41
- proposed: 'NOT written — differs from the generated version (merge proposal below)',
42
- skipped: 'left as-is (per --onConflict=skip)',
43
- alternate: 'generated alongside the existing file',
44
- aborted: 'NOT written',
45
- };
46
- const CONFIG_ACTION_LINES = {
47
- created: 'created with the review field',
48
- updated: 'review field updated',
49
- unchanged: 'review field unchanged',
50
- 'not-updated': 'NOT updated — kept consistent with the workflow file that is actually in effect',
51
- aborted: 'NOT updated',
52
- };
53
- export function renderAddReviewReport(report) {
54
- const lines = [];
55
- if (report.packageManagerNote !== undefined) {
56
- lines.push(`Note: ${report.packageManagerNote}`);
57
- }
58
- if (report.workflowAction === 'aborted') {
59
- lines.push('add-review aborted — nothing was written.');
60
- lines.push(` toolkit.config.json: ${report.configError ?? 'unusable'}`);
61
- lines.push(' Fix the config file manually, then rerun the generator.');
62
- return lines.join('\n');
63
- }
64
- if (report.dryRun) {
65
- lines.push('add-review dry-run — no files were written.');
66
- lines.push('');
67
- lines.push('Generated workflow YAML:');
68
- lines.push(report.workflowYaml ?? '');
69
- lines.push('');
70
- lines.push('toolkit.config.json review field:');
71
- lines.push(report.configSnippetJson ?? '');
72
- lines.push('');
73
- lines.push('Effective decision:');
74
- }
75
- lines.push(` ${report.workflowPath}: ${WORKFLOW_ACTION_LINES[report.workflowAction]}`);
76
- lines.push(` toolkit.config.json: ${CONFIG_ACTION_LINES[report.configAction]}`);
77
- if (report.workflowAction === 'proposed' && report.diff) {
78
- lines.push('');
79
- lines.push(report.diff);
80
- lines.push('');
81
- lines.push('To resolve, rerun with one of:');
82
- lines.push(' --onConflict=overwrite replace the existing file with the generated version');
83
- lines.push(' --onConflict=skip keep the existing file');
84
- lines.push(' --onConflict=alternate-filename write the generated version alongside it');
85
- }
86
- if (report.workflowAction === 'alternate') {
87
- lines.push('');
88
- lines.push(' Review the generated file, then replace the main workflow file with it yourself;');
89
- lines.push(' the config stays pointing at the workflow that is actually in effect until you do.');
90
- }
91
- lines.push('');
92
- lines.push(`Note: the review workflow stays green-skipped until you register the ${report.requiredSecretName} secret.`);
93
- lines.push('Note: this workflow runs with elevated permissions only after it is merged to the default branch —');
94
- lines.push('merging is the first real validation.');
95
- return lines.join('\n');
96
- }
@@ -1,82 +0,0 @@
1
- import { yamlPackageKey } from '@nx-archweave/template-engine';
2
- const ALLOW_BUILDS_LABEL = {
3
- allowed: 'already true',
4
- denied: 'false → true',
5
- pending: 'awaiting a decision → true',
6
- other: 'another value → true',
7
- absent: 'added (true)',
8
- };
9
- export function renderAllowBuildsReport(report) {
10
- const rebuild = `pnpm rebuild ${report.packages.join(' ')}`;
11
- if (report.refused === undefined) {
12
- const lines = [
13
- `allow-builds — ${report.applied ? 'applied' : 'preview (nothing written)'}`,
14
- '',
15
- `pnpm-workspace.yaml › ${report.target}`,
16
- ...report.changes.map((change) => ` ${change.name}: ${label(report.target, change.previous)}`),
17
- '',
18
- ];
19
- if (report.namesUnchecked)
20
- lines.push('The names were not checked: there is no pnpm-lock.yaml.', '');
21
- if (report.applied) {
22
- lines.push(`Running: ${rebuild}`);
23
- }
24
- else {
25
- lines.push(`Then: ${rebuild}`, "Allowing a build lets that package's install script run with your user's permissions.", '', `To apply: pnpm nx g @clearlydev/nx-archweave:allow-builds --packages=${report.packages.join(',')} --apply`);
26
- }
27
- return lines.join('\n');
28
- }
29
- const head = 'allow-builds — refused (nothing written)';
30
- switch (report.refused) {
31
- case 'npm':
32
- return [
33
- head,
34
- 'This is an npm workspace: allow-builds edits pnpm settings only. npm runs dependency install scripts',
35
- 'itself unless ignore-scripts is set, so there is nothing to re-allow; npm 11 records an approval with',
36
- ` npm install-scripts approve ${report.packages.join(' ')}`,
37
- ].join('\n');
38
- case 'yarn':
39
- return [
40
- head,
41
- 'This is a yarn workspace: allow-builds edits pnpm settings only. yarn classic runs dependency',
42
- 'install scripts itself; yarn berry runs them unless .yarnrc.yml sets enableScripts: false —',
43
- 'remove that line (or set it to true) and run `yarn install` to let them run.',
44
- ].join('\n');
45
- case 'bun':
46
- return [
47
- head,
48
- 'This is a bun workspace: allow-builds edits pnpm settings only. bun blocks the install scripts of',
49
- 'dependencies outside its trusted list; `bun pm untrusted` lists the blocked ones, and',
50
- ` bun pm trust ${report.packages.join(' ')}`,
51
- 'runs their scripts and records them in package.json trustedDependencies.',
52
- ].join('\n');
53
- case 'no-package-manager':
54
- return [head, 'There is no pnpm-lock.yaml or pnpm-workspace.yaml here — allow-builds edits pnpm settings only.'].join('\n');
55
- case 'no-workspace-yaml':
56
- return [
57
- head,
58
- 'This pnpm project has no pnpm-workspace.yaml, where pnpm keeps its build decisions. Add one with',
59
- ' allowBuilds:',
60
- ...report.packages.map((name) => ` ${yamlPackageKey(name)}: true`),
61
- `then run "${rebuild}".`,
62
- ].join('\n');
63
- case 'unknown-packages':
64
- return [
65
- head,
66
- `These packages are not in pnpm-lock.yaml: ${report.unknown.join(', ')} — check the names; only an installed package has a build to run.`,
67
- ].join('\n');
68
- case 'unrecognized':
69
- return [
70
- head,
71
- `pnpm-workspace.yaml is in a shape allow-builds does not edit: ${report.reason}.`,
72
- `Instead, allow ${report.packages.join(', ')} in pnpm-workspace.yaml yourself — "<package>: true" under allowBuilds`,
73
- `(pnpm 11; pnpm 10 from 10.28), or a "- <package>" item under onlyBuiltDependencies on older pnpm 10 — then run "${rebuild}".`,
74
- ].join('\n');
75
- }
76
- }
77
- function label(target, previous) {
78
- if (target === 'allowBuilds')
79
- return ALLOW_BUILDS_LABEL[previous];
80
- // a list item is either there or not — denied / pending / other only exist in allowBuilds
81
- return previous === 'allowed' ? 'already listed' : 'added to the list';
82
- }
@@ -1,70 +0,0 @@
1
- import { computeOverallStatus } from '@nx-archweave/shared';
2
- import { runDoctorChecks } from '@nx-archweave/template-engine';
3
- import { detectIntegrationsDrift, readEnabledIntegrationIds, buildIntegrationDoctorChecks, classifyIntegrationDrift, loadManifest, detectWorkspacePackage, verifyClaudeMarketplacePlugin, PLUGIN_CATALOG, } from '@nx-archweave/integrations';
4
- import { createIntegrationsCompositionRoot, } from "../../runtime/integrations-composition-root.js";
5
- import { diskRunNx } from "../shared/run-nx.js";
6
- // Only claude-code is a supported target for now — doctor
7
- // does not yet accept a --target option, so this is the one hardcoded value.
8
- const DOCTOR_TARGET_ENVIRONMENT = 'claude-code';
9
- export async function buildDoctorReportWithIntegrations(root, options = {}, deps = {}) {
10
- const integrationsRoot = deps.integrationsRoot ?? createIntegrationsCompositionRoot();
11
- const baseReport = runDoctorChecks(root);
12
- const declaredIds = readEnabledIntegrationIds(root);
13
- const runNx = diskRunNx(root);
14
- const driftResults = await detectIntegrationsDrift({
15
- workspaceRoot: root,
16
- targetEnvironment: DOCTOR_TARGET_ENVIRONMENT,
17
- declaredIds,
18
- integrations: integrationsRoot.listIntegrations(),
19
- runNx,
20
- });
21
- // Without --live-integrations: no ReadOnlyCommandRunner call happens at
22
- // all — doctor stays fast and available even where the claude CLI is not
23
- // installed, trusting the recorded manifest state exactly as before.
24
- const finalDriftResults = options.liveIntegrations
25
- ? await applyLiveVerification(driftResults, {
26
- root,
27
- declaredIds,
28
- runner: integrationsRoot.runner,
29
- runNx,
30
- })
31
- : driftResults;
32
- const integrationChecks = buildIntegrationDoctorChecks(finalDriftResults);
33
- const checks = [...baseReport.checks, ...integrationChecks];
34
- return { checks, overallStatus: computeOverallStatus(checks) };
35
- }
36
- // Re-derives each catalog plugin's drift result using a live verification signal in
37
- // place of the manifest's frozen `verification` field — every other input to
38
- // classifyIntegrationDrift (declared, detected) is unchanged, so this never
39
- // duplicates the classification logic itself, only substitutes one input to
40
- // it. This is still fully read-only: no manifest or config write, no repair.
41
- async function applyLiveVerification(driftResults, ctx) {
42
- const context = { workspaceRoot: ctx.root, targetEnvironment: DOCTOR_TARGET_ENVIRONMENT };
43
- const manifest = loadManifest(ctx.root);
44
- const live = new Map();
45
- // Only the plugins doctor reports on, and only those this target can verify — each live
46
- // check is one `claude plugin list` call.
47
- const reported = new Set(driftResults.map((result) => result.id));
48
- for (const entry of PLUGIN_CATALOG) {
49
- if (!reported.has(entry.id) || entry.hosts[DOCTOR_TARGET_ENVIRONMENT] === undefined)
50
- continue;
51
- const existingEntry = manifest.integrations[entry.id];
52
- const detected = await detectWorkspacePackage(entry, context);
53
- const { result: liveVerification } = await verifyClaudeMarketplacePlugin(entry, { runner: ctx.runner }, context);
54
- // A stale recorded "passed" must never survive a current live failure —
55
- // the live result always wins over the recorded one
56
- // when --live-integrations is set, never the other way around.
57
- const liveEntry = existingEntry
58
- ? { ...existingEntry, verification: liveVerification.verified ? 'passed' : 'failed' }
59
- : existingEntry;
60
- live.set(entry.id, classifyIntegrationDrift({
61
- id: entry.id,
62
- installable: true,
63
- declared: ctx.declaredIds.includes(entry.id),
64
- manifestEntry: liveEntry,
65
- detected,
66
- runNx: ctx.runNx,
67
- }));
68
- }
69
- return driftResults.map((result) => live.get(result.id) ?? result);
70
- }
@@ -1,10 +0,0 @@
1
- export function renderDoctorReport(report) {
2
- const lines = [`Overall status: ${report.overallStatus}`, ''];
3
- for (const check of report.checks) {
4
- lines.push(`[${check.status.toUpperCase()}] ${check.label}: ${check.message}`);
5
- if (check.suggestedAction) {
6
- lines.push(` → ${check.suggestedAction}`);
7
- }
8
- }
9
- return lines.join('\n');
10
- }
@@ -1,125 +0,0 @@
1
- import { packageManagerCommands } from '@nx-archweave/template-engine';
2
- import { renderAddFsdLiteReport } from "../add-fsd-lite/render.js";
3
- import { renderAddAgentRulesReport } from "../add-agent-rules/render.js";
4
- import { renderAddGithubTemplatesReport } from "../add-github-templates/render.js";
5
- import { renderAddReviewReport } from "../add-review/render.js";
6
- import { renderAddBackendBody } from "../add-backend/render.js";
7
- const SECTION_TITLES = {
8
- fsd: 'add-fsd-lite',
9
- agentRules: 'add-agent-rules',
10
- githubTemplates: 'add-github-templates',
11
- review: 'add-review',
12
- backend: 'add-backend',
13
- };
14
- function renderSectionReport(report) {
15
- switch (report.kind) {
16
- case 'fsd':
17
- return renderAddFsdLiteReport(report.report);
18
- case 'agentRules':
19
- return renderAddAgentRulesReport(report.report);
20
- case 'githubTemplates':
21
- return renderAddGithubTemplatesReport(report.report);
22
- case 'review':
23
- return renderAddReviewReport(report.report);
24
- case 'backend':
25
- return renderAddBackendBody(report.report);
26
- }
27
- }
28
- /** Whether a section left the run in a state that only a `git restore .` can cleanly
29
- * undo — i.e. it aborted outright. Sections that merely proposed/skipped a file (no
30
- * write happened) do not count: there is nothing of theirs to roll back. */
31
- function sectionAborted(section) {
32
- if (section.thrownError !== undefined)
33
- return true;
34
- if (section.report === null)
35
- return false;
36
- if (section.report.kind === 'fsd')
37
- return section.report.report.aborted;
38
- if (section.report.kind === 'review')
39
- return section.report.report.workflowAction === 'aborted';
40
- return false;
41
- }
42
- export function renderInitReport(report) {
43
- const lines = [];
44
- const backendSuffix = report.backend === 'none' ? '' : `, backend: ${report.backend}`;
45
- const header = `init (preset: ${report.preset}, frontend: ${report.frontend}${backendSuffix})`;
46
- lines.push(report.dryRun ? `${header} — dry run` : header);
47
- if (report.refused) {
48
- lines.push('');
49
- lines.push('Refused: the working tree has uncommitted changes.');
50
- lines.push(' init only runs against a clean working tree, so a partial run can always be undone');
51
- lines.push(' with a single `git restore .` — commit or stash your changes, then rerun.');
52
- return lines.join('\n');
53
- }
54
- for (const section of report.sections) {
55
- // backend 'none' must leave the output byte-identical to the frontend-only
56
- // era (silence guarantee) — a disabled backend section is omitted
57
- // entirely, not shown as skipped.
58
- if (section.id === 'backend' && !section.enabled)
59
- continue;
60
- lines.push('');
61
- lines.push(`## ${SECTION_TITLES[section.id]}`);
62
- if (!section.enabled) {
63
- lines.push(section.id === 'fsd' && report.frontend === 'none'
64
- ? ' skipped (backend-only workspace — no frontend to structure)'
65
- : ' skipped (disabled for this run)');
66
- continue;
67
- }
68
- if (section.thrownError !== undefined) {
69
- lines.push(` aborted — unexpected error: ${section.thrownError}`);
70
- lines.push(' Nothing was written for this section. Rerun add-fsd-lite directly once resolved.');
71
- continue;
72
- }
73
- if (section.report !== null) {
74
- lines.push(renderSectionReport(section.report));
75
- }
76
- }
77
- if (report.config !== undefined && (report.config.action !== 'skipped' || report.config.notRecorded.length > 0)) {
78
- lines.push('');
79
- lines.push('## toolkit.config.json');
80
- const fieldList = report.config.fields.join(', ');
81
- switch (report.config.action) {
82
- case 'created':
83
- case 'updated':
84
- lines.push(` ${report.dryRun ? 'would record' : 'recorded'} ${fieldList} (${report.config.action})`);
85
- break;
86
- case 'unchanged':
87
- lines.push(` already records ${fieldList} (unchanged)`);
88
- break;
89
- case 'unrecognized':
90
- lines.push(` not updated — the file is not a JSON object, so init left it alone (it never rewrites a shape it`);
91
- lines.push(` does not recognize). Add ${fieldList} by hand so standalone reruns resolve the same values.`);
92
- break;
93
- case 'skipped':
94
- break;
95
- }
96
- if (report.config.notRecorded.length > 0) {
97
- lines.push(` not recorded: ${report.config.notRecorded.map((n) => n.field).join(', ')} — that section did not apply cleanly`);
98
- lines.push(' this run (aborted, or written as an alternate file). Once resolved, record it with:');
99
- const runNx = packageManagerCommands(report.packageManager ?? 'pnpm').runNx;
100
- for (const entry of report.config.notRecorded)
101
- lines.push(` ${runNx} g @clearlydev/nx-archweave:${entry.rerun}`);
102
- }
103
- }
104
- const ranDirty = report.allowDirty === true && !report.cleanTree && !report.dryRun;
105
- if (ranDirty) {
106
- lines.push('');
107
- lines.push('Note: --allowDirty was set and the working tree already had uncommitted changes —');
108
- lines.push('`git restore .` also reverts those pre-existing changes, not only what this run wrote.');
109
- }
110
- const hadAbort = report.sections.some(sectionAborted);
111
- if (!report.dryRun && hadAbort) {
112
- lines.push('');
113
- if (ranDirty) {
114
- lines.push('One or more sections aborted. Everything this run wrote can be undone with:');
115
- lines.push(' git restore .');
116
- lines.push(' (--allowDirty: this also reverts the pre-existing uncommitted changes noted above.)');
117
- }
118
- else {
119
- lines.push('One or more sections aborted. init never runs against a dirty tree (see above), so everything');
120
- lines.push('this run wrote can be undone in one step:');
121
- lines.push(' git restore .');
122
- }
123
- }
124
- return lines.join('\n');
125
- }
@@ -1,74 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { spawnableCommand } from '@nx-archweave/shared';
3
- export function createProcessExec() {
4
- return (command, args, options) => {
5
- // nx / actionlint may be .cmd shims on Windows, which Node cannot start without cmd.exe.
6
- const spawnable = spawnableCommand(command, args);
7
- const result = spawnSync(spawnable.command, spawnable.args, {
8
- cwd: options?.cwd,
9
- encoding: 'utf-8',
10
- windowsVerbatimArguments: spawnable.windowsVerbatimArguments,
11
- });
12
- return { status: result.status ?? null, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
13
- };
14
- }
15
- /** Runs `exec`, catching anything it throws (e.g. a stub or an unforeseen environment
16
- * failure) so a single check can never take down the rest of validation — the "never
17
- * throws" half of the best-effort contract applies even to the injected dependency, not
18
- * just to non-zero exit codes. */
19
- function safeExec(exec, command, args, options) {
20
- try {
21
- return exec(command, args, options);
22
- }
23
- catch (error) {
24
- return { status: null, stdout: '', stderr: error instanceof Error ? error.message : String(error) };
25
- }
26
- }
27
- /** Post-flush, best-effort validation: never throws, only
28
- * reports via console. Runs after the generator's `GeneratorCallback` fires, once every
29
- * file this run wrote is actually on disk. Neither actionlint nor nx are added as
30
- * dependencies — both are invoked as external binaries, detected at call time. */
31
- export function runPostInitValidation(input, exec) {
32
- console.log('');
33
- console.log('Post-run validation (best-effort):');
34
- if (input.workflowPaths.length === 0) {
35
- console.log(' actionlint: skipped (no GitHub Actions workflow files were written this run)');
36
- }
37
- else {
38
- const versionCheck = safeExec(exec, 'actionlint', ['--version'], { cwd: input.root });
39
- if (versionCheck.status !== 0) {
40
- console.log(' actionlint: not found on PATH — skipped.');
41
- console.log(' Install it from https://github.com/rhysd/actionlint to validate workflow files locally.');
42
- }
43
- else {
44
- const lintResult = safeExec(exec, 'actionlint', input.workflowPaths, { cwd: input.root });
45
- if (lintResult.status === 0) {
46
- console.log(` actionlint: passed (${input.workflowPaths.join(', ')})`);
47
- }
48
- else {
49
- console.log(' actionlint: reported issues —');
50
- for (const line of (lintResult.stdout || lintResult.stderr).split('\n')) {
51
- if (line.length > 0)
52
- console.log(` ${line}`);
53
- }
54
- }
55
- }
56
- }
57
- const nxVersionCheck = safeExec(exec, 'nx', ['--version'], { cwd: input.root });
58
- if (nxVersionCheck.status !== 0) {
59
- console.log(' nx graph: nx not found on PATH — skipped.');
60
- console.log(' Run it via your package manager instead, e.g. `pnpm nx graph`, `yarn nx graph`, `bun nx graph` or `npm exec nx graph`.');
61
- return;
62
- }
63
- const graphResult = safeExec(exec, 'nx', ['graph', `--file=${input.graphOutputFile}`], { cwd: input.root });
64
- if (graphResult.status === 0) {
65
- console.log(` nx graph: valid (written to ${input.graphOutputFile})`);
66
- }
67
- else {
68
- console.log(' nx graph: failed —');
69
- for (const line of (graphResult.stdout || graphResult.stderr).split('\n')) {
70
- if (line.length > 0)
71
- console.log(` ${line}`);
72
- }
73
- }
74
- }
@@ -1,128 +0,0 @@
1
- function describeState(state) {
2
- switch (state.kind) {
3
- case 'absent':
4
- return 'not installed';
5
- case 'already-installed':
6
- return `already installed and enabled at ${state.scope} scope${state.version ? ` (version ${state.version})` : ''}`;
7
- case 'installed-other-scope':
8
- return `installed and enabled, but at ${state.installedScope} scope`;
9
- case 'disabled':
10
- return 'installed but disabled';
11
- case 'ambiguous':
12
- return 'more than one installed plugin matches exactly';
13
- case 'cli-not-found':
14
- return 'unknown — the claude CLI was not found';
15
- case 'cli-error':
16
- return `unknown — the claude CLI could not list plugins (${state.reason})`;
17
- }
18
- }
19
- function describeInstall(detail) {
20
- switch (detail.kind) {
21
- case 'executed':
22
- return 'the official command exited successfully';
23
- case 'manual-only':
24
- return 'no scriptable command exists for this target — nothing was executed';
25
- case 'cli-not-found':
26
- return 'not executed — the claude CLI was not found';
27
- case 'failed-to-start':
28
- return `not executed — the claude CLI could not be started (${detail.code})`;
29
- case 'cli-unsupported':
30
- return 'not executed — this claude CLI does not support scriptable plugin install';
31
- case 'confirmation-required':
32
- return 'stopped — the marketplace declares a command that a person must confirm';
33
- case 'failed':
34
- return `failed${detail.failureCode ? ` (${detail.failureCode})` : ''}`;
35
- case 'timeout':
36
- return 'stopped — the command timed out';
37
- case 'output-too-large':
38
- return 'stopped — the command produced too much output';
39
- }
40
- }
41
- function describeDeclaration(declaration, tool, preview) {
42
- switch (declaration.action) {
43
- case 'updated':
44
- return `toolkit.config.json: "${tool}" ${preview ? 'would be added' : 'added'} to integrations.enabled`;
45
- case 'unchanged':
46
- return `toolkit.config.json: "${tool}" is already declared in integrations.enabled (unchanged)`;
47
- case 'no-config':
48
- return (`toolkit.config.json: not declared — this workspace has no toolkit.config.json (it was not set up with init). ` +
49
- `Add "${tool}" to integrations.enabled there if you want doctor to track the declaration.`);
50
- case 'unrecognized':
51
- return ('toolkit.config.json: not updated — the file is not in a shape nx-archweave recognizes, so it was left alone. ' +
52
- `Add "${tool}" to integrations.enabled by hand.`);
53
- case 'write-failed':
54
- return `toolkit.config.json: not updated — writing it failed (${declaration.message}). Add "${tool}" to integrations.enabled by hand.`;
55
- }
56
- }
57
- // Every value here is a tool id, a URL, a command, a classification, a manifest
58
- // entry (installPath excluded by construction) or a
59
- // CLI message that was home-path-redacted where it was produced (install.ts).
60
- export function renderInstallIntegrationView(view) {
61
- const lines = [
62
- `Integration: ${view.tool} (a separate project — nx-archweave only runs its official installer)`,
63
- `Source repository: ${view.sourceRepository}`,
64
- `Official homepage: ${view.officialHomepage}`,
65
- `License: ${view.license}`,
66
- view.requestedVersion === null
67
- ? 'Version: latest (decided by the marketplace) — the version actually installed is the one recorded'
68
- : `Requested version: ${view.requestedVersion} (not enforced — the marketplace decides; the version actually installed is the one recorded)`,
69
- `Target: ${view.target}`,
70
- `Scope: ${view.scope}`,
71
- `Official command: ${view.command}`,
72
- ...(view.teamMarketplace
73
- ? [`For teammates (project scope), the marketplace is declared in .claude/settings.json first: ${view.teamMarketplace.command}`]
74
- : []),
75
- ...(view.workspaceFiles.length > 0 ? [`Claude Code will update in this workspace: ${view.workspaceFiles.join(', ')}`] : []),
76
- `Current state: ${describeState(view.currentState)}`,
77
- ];
78
- if (view.applyIgnoredForNxDryRun) {
79
- lines.push("Note: --apply was ignored because Nx's --dry-run is set.");
80
- }
81
- if (view.preview && view.currentState.kind === 'absent') {
82
- lines.push('', ...(view.teamMarketplace ? [`Would run: ${view.teamMarketplace.command}`] : []), `Would run: ${view.command}`, `To run it: ${view.rerunCommand}`);
83
- }
84
- else if (view.preview && view.teamMarketplace) {
85
- lines.push('', `Would run: ${view.teamMarketplace.command}`, `To run it: ${view.rerunCommand}`);
86
- }
87
- if (view.teamMarketplace?.detail) {
88
- const { detail } = view.teamMarketplace;
89
- lines.push('', detail.kind === 'executed'
90
- ? 'Marketplace: declared in .claude/settings.json (extraKnownMarketplaces) — teammates get its source from the committed file'
91
- : `Marketplace declaration: ${describeInstall(detail)}${detail.kind === 'failed' ? ` — ${detail.message}` : ''}`);
92
- }
93
- if (view.install) {
94
- lines.push('', `Install: ${describeInstall(view.install.detail)}`);
95
- }
96
- if (view.record) {
97
- const { record } = view;
98
- lines.push('', `Verification: ${record.verification.verified ? 'passed' : 'not verified'}`, ...record.verification.problems.map((problem) => ` ! ${problem}`));
99
- if (record.hasConflict && record.existingEntry) {
100
- lines.push('Conflict: an existing manifest entry differs from what was verified now — the fresh result replaces it.', ` Existing: ${JSON.stringify(record.existingEntry)}`, ` Verified: ${JSON.stringify(record.proposedEntry)}`);
101
- }
102
- const installKind = view.install?.detail.kind;
103
- if (record.proposedEntry) {
104
- if (installKind !== undefined && installKind !== 'executed') {
105
- lines.push(`The install command reported ${installKind}, but a fresh check shows the plugin installed and enabled.`);
106
- }
107
- lines.push(record.wasWritten
108
- ? 'Recorded — the following manifest entry was written to archweave-integrations.json:'
109
- : 'archweave-integrations.json would get this entry:', ` ${JSON.stringify(record.proposedEntry)}`);
110
- }
111
- else if (installKind === 'executed') {
112
- lines.push('The install command exited 0, but the fresh verification did not confirm the plugin — nothing was written.');
113
- }
114
- else {
115
- lines.push('Nothing was written — verification did not succeed via the authoritative claude-cli signal.');
116
- }
117
- }
118
- if (view.declaration) {
119
- lines.push(describeDeclaration(view.declaration, view.tool, view.preview));
120
- }
121
- if (view.guidance.length > 0) {
122
- lines.push('', 'Next steps:', ...view.guidance.map((step) => ` - ${step}`));
123
- }
124
- lines.push('', view.preview
125
- ? '(preview — only read-only checks ran; nothing was installed and no file was written)'
126
- : '(applied — the lines above state exactly what was executed and written)');
127
- return lines.join('\n');
128
- }
@@ -1,31 +0,0 @@
1
- // Never renders installPath or any absolute/home-directory path — the
2
- // manifest entry types used here already exclude installPath by
3
- // construction, and this function only ever reads from
4
- // those already-narrowed values.
5
- export function renderRecordIntegrationView(view) {
6
- const lines = [
7
- `Integration: ${view.tool}`,
8
- `Target: ${view.target}`,
9
- `Source repository: ${view.sourceRepository}`,
10
- `License: ${view.license}`,
11
- `Verification source: ${view.verificationSource}`,
12
- `Current verification: ${view.verification.verified ? 'passed' : 'not verified'}`,
13
- ...view.verification.problems.map((problem) => ` ! ${problem}`),
14
- '',
15
- ];
16
- if (view.hasConflict && view.existingEntry) {
17
- lines.push('Conflict: an existing manifest entry differs from what would be recorded.', ` Existing: ${JSON.stringify(view.existingEntry)}`, ` Proposed: ${JSON.stringify(view.proposedEntry)}`, '');
18
- }
19
- if (view.proposedEntry) {
20
- lines.push(view.wasWritten
21
- ? 'Recorded — the following manifest entry was written:'
22
- : 'The following manifest entry would be recorded:', ` ${JSON.stringify(view.proposedEntry)}`);
23
- }
24
- else {
25
- lines.push('Nothing would be recorded — verification did not succeed via the authoritative claude-cli signal.');
26
- }
27
- lines.push('', view.wasWritten
28
- ? '(recorded — the manifest was written)'
29
- : '(dry-run — no files were changed, no installer was executed, manifest was not written)');
30
- return lines.join('\n');
31
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * `nx g` owns the dry-run flag: Nx 23.1's generate command deletes `dryRun` /
3
- * `dry-run` from the options it hands a generator and then refills the schema
4
- * default, and signals its own `--dry-run` only through `NX_DRY_RUN=true`
5
- * (`nx/src/command-line/generate/generate.js`, `command-object.js`). So an Nx
6
- * entry point that read only `options.dryRun` would always see `false` and print
7
- * "created" for a run Nx never flushes. Programmatic callers (init → the `run*`
8
- * cores, tests) keep passing `dryRun` explicitly; Nx's own flag always wins.
9
- */
10
- export function resolveDryRun(options) {
11
- return process.env['NX_DRY_RUN'] === 'true' || (options.dryRun ?? false);
12
- }