@astryxdesign/cli 0.1.7 → 0.1.8-canary.0c4d8d7
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 +40 -0
- package/README.md +115 -19
- package/docs/cli-integrations.doc.mjs +150 -0
- package/docs/elevation.doc.mjs +79 -1
- package/docs/getting-started.doc.mjs +9 -9
- package/docs/migration.doc.mjs +18 -18
- package/docs/principles.doc.dense.mjs +1 -1
- package/docs/principles.doc.mjs +6 -6
- package/docs/principles.doc.zh.mjs +1 -1
- package/docs/styling-libraries.doc.mjs +3 -3
- package/docs/styling.doc.mjs +4 -4
- package/docs/theme.doc.dense.mjs +2 -2
- package/docs/theme.doc.mjs +7 -7
- package/docs/theme.doc.zh.mjs +1 -1
- package/docs/tokens.doc.mjs +1 -1
- package/docs/working-with-ai.doc.mjs +19 -19
- package/package.json +13 -11
- package/scripts/postinstall.mjs +74 -0
- package/src/api/doctor.mjs +3 -3
- package/src/api/error.mjs +2 -2
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/ensure-jscodeshift.mjs +11 -27
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/run-codemod.mjs +1 -1
- package/src/codemods/runner.mjs +2 -2
- package/src/codemods/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +42 -7
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +43 -0
- package/src/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +102 -3
- package/src/codemods/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +72 -6
- package/src/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +161 -0
- package/src/codemods/transforms/v0.1.8/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +234 -0
- package/src/commands/agent-docs.mjs +136 -20
- package/src/commands/agent-docs.test.mjs +123 -10
- package/src/commands/build-theme.mjs +10 -71
- package/src/commands/build.mjs +15 -15
- package/src/commands/cli-postinstall.test.mjs +42 -0
- package/src/commands/component/index.mjs +4 -4
- package/src/commands/discover.mjs +7 -5
- package/src/commands/docs.mjs +4 -4
- package/src/commands/hook/index.mjs +4 -4
- package/src/commands/init.mjs +48 -152
- package/src/commands/init.next-steps.test.mjs +1 -1
- package/src/commands/interactive-guard.test.mjs +19 -22
- package/src/commands/json-contract.test.mjs +2 -2
- package/src/commands/layout.mjs +1 -1
- package/src/commands/search.mjs +4 -4
- package/src/commands/setup-nudge.test.mjs +108 -0
- package/src/commands/swizzle.mjs +12 -35
- package/src/commands/template.mjs +11 -31
- package/src/commands/upgrade.mjs +138 -32
- package/src/commands/upgrade.test.mjs +155 -1
- package/src/index.mjs +40 -6
- package/src/lib/cli-error.mjs +2 -2
- package/src/lib/component-format.mjs +2 -1
- package/src/lib/json-shim.mjs +1 -1
- package/src/lib/json.mjs +3 -3
- package/src/lib/term-log.mjs +48 -0
- package/src/types/api.d.ts +4 -9
- package/src/types/base.d.ts +24 -3
- package/src/types/build.d.ts +23 -0
- package/src/types/index.d.ts +1 -0
- package/src/types/swizzle.d.ts +4 -0
- package/src/types/template-api.d.ts +4 -1
- package/src/types/upgrade.d.ts +39 -2
- package/src/utils/package-manager.mjs +108 -4
- package/src/utils/package-manager.test.mjs +108 -1
- package/src/utils/path-safety.mjs +0 -18
- package/src/utils/update-check.mjs +5 -4
- package/src/utils/update-check.test.mjs +3 -3
- package/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +4 -4
- package/templates/blocks/components/Avatar/AvatarGroup.tsx +2 -2
- package/templates/blocks/components/Avatar/AvatarInitialsFallback.tsx +1 -1
- package/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -4
- package/templates/blocks/components/Avatar/AvatarUserCard.tsx +1 -1
- package/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
- package/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
- package/templates/blocks/components/AvatarGroup/AvatarGroupShowcase.tsx +2 -2
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowCustomText.tsx +1 -1
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowDefault.tsx +1 -1
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowShowcase.tsx +2 -2
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotShowcase.tsx +3 -3
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +3 -3
- package/templates/blocks/components/Banner/BannerFloating.doc.mjs +14 -0
- package/templates/blocks/components/Banner/BannerFloating.tsx +16 -0
- package/templates/blocks/components/Button/ButtonFloating.doc.mjs +14 -0
- package/templates/blocks/components/Button/ButtonFloating.tsx +37 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.doc.mjs +14 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.tsx +23 -0
- package/templates/blocks/components/Card/CardElevations.doc.mjs +14 -0
- package/templates/blocks/components/Card/CardElevations.tsx +32 -0
- package/templates/blocks/components/Card/ClickableCardElevated.doc.mjs +14 -0
- package/templates/blocks/components/Card/ClickableCardElevated.tsx +21 -0
- package/templates/blocks/components/Card/SelectableCardElevated.doc.mjs +14 -0
- package/templates/blocks/components/Card/SelectableCardElevated.tsx +37 -0
- package/templates/blocks/components/Carousel/CarouselSnap.tsx +1 -1
- package/templates/blocks/components/ChatComposer/ChatComposerFlat.doc.mjs +14 -0
- package/templates/blocks/components/ChatComposer/ChatComposerFlat.tsx +75 -0
- package/templates/blocks/components/ChatMessage/ChatMessageAvatarName.tsx +2 -2
- package/templates/blocks/components/ChatMessage/ChatMessageMultiBubble.tsx +1 -1
- package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleGrouping.tsx +1 -1
- package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleMetadata.tsx +1 -1
- package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +5 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -1
- package/templates/blocks/components/CodeBlock/CodeBlockTerminal.tsx +1 -1
- package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -1
- package/templates/blocks/components/IconButton/IconButtonFloating.doc.mjs +14 -0
- package/templates/blocks/components/IconButton/IconButtonFloating.tsx +37 -0
- package/templates/blocks/components/Item/ItemWithMedia.tsx +2 -2
- package/templates/blocks/components/ListItem/ListItemWithMedia.tsx +2 -2
- package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.doc.mjs +14 -0
- package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.tsx +39 -0
- package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.doc.mjs +14 -0
- package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.tsx +44 -0
- package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -6
- package/templates/blocks/components/Stack/StackFillItem.tsx +2 -6
- package/templates/blocks/components/TabList/TabListTabsWithActions.doc.mjs +1 -1
- package/templates/blocks/components/TabList/TabListTabsWithActions.tsx +2 -7
- package/templates/blocks/components/Table/TableRowStatusTable.doc.mjs +14 -0
- package/templates/blocks/components/Table/TableRowStatusTable.tsx +59 -0
- package/templates/blocks/components/Thumbnail/ThumbnailElevated.doc.mjs +14 -0
- package/templates/blocks/components/Thumbnail/ThumbnailElevated.tsx +31 -0
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemShowcase.tsx +1 -1
- package/templates/pages/ai-chat/page.tsx +4 -4
- package/templates/pages/dashboard-portfolio/page.tsx +3 -11
- package/templates/pages/detail-page/page.tsx +5 -12
- package/templates/pages/documentation-design/page.tsx +1 -1
- package/templates/pages/messaging-shell/page.tsx +6 -8
- package/templates/pages/table-grouped/page.tsx +9 -14
- package/templates/pages/table-page/page.tsx +7 -9
- package/templates/pages/table-page-heatmap-status/page.tsx +5 -13
- package/docs/integration-authoring.md +0 -105
- package/src/utils/interactive.mjs +0 -76
- package/src/utils/interactive.test.mjs +0 -70
|
@@ -6,25 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
|
-
import * as p from '@clack/prompts';
|
|
10
|
-
import {isNonInteractive} from '../utils/path-safety.mjs';
|
|
11
9
|
import {jsonOut, humanLog} from '../lib/json.mjs';
|
|
12
10
|
import {cliError} from '../lib/cli-error.mjs';
|
|
13
11
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
14
12
|
import {template as templateApi} from '../api/template.mjs';
|
|
15
13
|
import {Project} from '../lib/project.mjs';
|
|
16
14
|
import {warnOnIntegrationIssues} from '../lib/integration-warnings.mjs';
|
|
15
|
+
import {getCliInvocation} from '../utils/package-manager.mjs';
|
|
17
16
|
|
|
18
17
|
export {discoverTemplates, listTemplates} from '../api/template.mjs';
|
|
19
18
|
|
|
20
|
-
function isCancel(value) {
|
|
21
|
-
if (p.isCancel(value)) {
|
|
22
|
-
p.cancel('Cancelled.');
|
|
23
|
-
process.exit(0);
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
19
|
export function registerTemplate(program) {
|
|
29
20
|
program
|
|
30
21
|
.command('template [name] [path]')
|
|
@@ -36,6 +27,7 @@ export function registerTemplate(program) {
|
|
|
36
27
|
.option('-f, --overwrite', 'Overwrite existing files without prompting')
|
|
37
28
|
.action(async (name, targetPath, options) => {
|
|
38
29
|
const json = program.opts().json || false;
|
|
30
|
+
const run = getCliInvocation();
|
|
39
31
|
|
|
40
32
|
// Non-blocking nudge: if any configured integration has validation
|
|
41
33
|
// issues, print one compact line to stderr pointing at
|
|
@@ -59,23 +51,11 @@ export function registerTemplate(program) {
|
|
|
59
51
|
const collision = await detectTemplateCollision(name, targetPath);
|
|
60
52
|
if (collision && !options.overwrite) {
|
|
61
53
|
const rel = path.relative(process.cwd(), collision) || collision;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const confirmed = isCancel(
|
|
70
|
-
await p.confirm({
|
|
71
|
-
message: `Overwrite existing file ${rel}?`,
|
|
72
|
-
initialValue: false,
|
|
73
|
-
}),
|
|
74
|
-
);
|
|
75
|
-
if (!confirmed) {
|
|
76
|
-
humanLog('Aborted. Re-run with --overwrite to replace the file.');
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
54
|
+
const msg =
|
|
55
|
+
`Refusing to overwrite existing file ${rel}. ` +
|
|
56
|
+
`Re-run with --overwrite (or -f) to replace it.`;
|
|
57
|
+
cliError(msg, {code: ERROR_CODES.ERR_FILE_EXISTS});
|
|
58
|
+
return;
|
|
79
59
|
}
|
|
80
60
|
}
|
|
81
61
|
|
|
@@ -120,10 +100,10 @@ export function registerTemplate(program) {
|
|
|
120
100
|
for (const t of blocks) renderEntry(t);
|
|
121
101
|
}
|
|
122
102
|
humanLog('\nUsage:');
|
|
123
|
-
humanLog(
|
|
124
|
-
humanLog(
|
|
125
|
-
humanLog(
|
|
126
|
-
humanLog(
|
|
103
|
+
humanLog(` ${run} template <id> [target-path] Scaffold page or block`);
|
|
104
|
+
humanLog(` ${run} template <id> --skeleton Layout reference`);
|
|
105
|
+
humanLog(` ${run} template --list --type block List only blocks`);
|
|
106
|
+
humanLog(` ${run} template --list --package <pkg> List from one package\n`);
|
|
127
107
|
break;
|
|
128
108
|
}
|
|
129
109
|
|
package/src/commands/upgrade.mjs
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Pipeline (--apply):
|
|
13
13
|
* 1. Read installed @astryxdesign/core (or legacy @xds/core) version
|
|
14
|
-
* 2.
|
|
15
|
-
*
|
|
14
|
+
* 2. Refresh the managed agent-docs block (AGENTS.md / CLAUDE.md) to the
|
|
15
|
+
* installed version — runs on EVERY path (including the up-to-date /
|
|
16
|
+
* no-codemods short-circuits), because the block documents the installed
|
|
17
|
+
* library, not the codemod outcome. Dry-run reports without writing.
|
|
18
|
+
* 3. Run codemods for --from → installed version
|
|
16
19
|
*
|
|
17
20
|
* Options:
|
|
18
21
|
* --from <version> Previous version before the dependency upgrade
|
|
@@ -37,7 +40,7 @@ import * as fs from 'node:fs';
|
|
|
37
40
|
import * as path from 'node:path';
|
|
38
41
|
import {execFile} from 'node:child_process';
|
|
39
42
|
import {promisify} from 'node:util';
|
|
40
|
-
import * as p from '
|
|
43
|
+
import * as p from '../lib/term-log.mjs';
|
|
41
44
|
import {ensureJscodeshift} from '../codemods/ensure-jscodeshift.mjs';
|
|
42
45
|
import {getTransformsBetween, latestVersion} from '../codemods/registry.mjs';
|
|
43
46
|
import {runCodemods} from '../codemods/runner.mjs';
|
|
@@ -46,8 +49,8 @@ import {
|
|
|
46
49
|
selectIntegrationCodemods,
|
|
47
50
|
} from '../codemods/integration-discovery.mjs';
|
|
48
51
|
import {runIntegrationCodemods} from '../codemods/integration-runner.mjs';
|
|
49
|
-
import {installAgentDocs,
|
|
50
|
-
import {
|
|
52
|
+
import {installAgentDocs, inspectAgentDocs} from './agent-docs.mjs';
|
|
53
|
+
import {getCliInvocation, formatCliCommand} from '../utils/package-manager.mjs';
|
|
51
54
|
import {isValidSemver, semverGte} from '../utils/semver.mjs';
|
|
52
55
|
import {jsonOut, jsonError} from '../lib/json.mjs';
|
|
53
56
|
import {Project} from '../lib/project.mjs';
|
|
@@ -135,6 +138,103 @@ async function runPostCodemodHooks(hooks, context, silent) {
|
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Refresh (or, in dry-run, report) the managed agent-docs block after a version
|
|
143
|
+
* bump. The block (`<!-- ASTRYX:START --> … <!-- ASTRYX:END -->`) documents the
|
|
144
|
+
* INSTALLED library — its version, component index, and agent rules — so it must
|
|
145
|
+
* be re-synced on EVERY upgrade path, including the up-to-date / no-codemods
|
|
146
|
+
* short-circuits where no source file changes. That was the gap in #4168: an
|
|
147
|
+
* agent reading a stale index queries missing components and follows superseded
|
|
148
|
+
* rules. Three cases, one detection pass:
|
|
149
|
+
*
|
|
150
|
+
* - `stale` — a managed block records an older version (or a legacy XDS
|
|
151
|
+
* marker). `--apply` rewrites it; dry-run reports the pending
|
|
152
|
+
* refresh as a loud next step and writes nothing.
|
|
153
|
+
* - `missing` — core is installed but no managed block exists anywhere (the repo
|
|
154
|
+
* never ran `init`, or its agent docs were never marked). We never
|
|
155
|
+
* silently create docs mid-upgrade; we nudge to run `init`.
|
|
156
|
+
* - `current` — every block already matches the installed version: stay silent.
|
|
157
|
+
*
|
|
158
|
+
* @param {{cwd: string, installedVersion: string, apply: boolean, json: boolean}} ctx
|
|
159
|
+
* @returns {import('../types/upgrade').AgentDocsSummary}
|
|
160
|
+
*/
|
|
161
|
+
export function refreshAgentDocs({cwd, installedVersion, apply, json}) {
|
|
162
|
+
const inspection = inspectAgentDocs(cwd, installedVersion);
|
|
163
|
+
/** @type {import('../types/upgrade').AgentDocsSummary} */
|
|
164
|
+
const summary = {
|
|
165
|
+
status: inspection.status,
|
|
166
|
+
installedVersion,
|
|
167
|
+
fromVersions: inspection.blockVersions,
|
|
168
|
+
files: [],
|
|
169
|
+
refreshed: false,
|
|
170
|
+
action: 'none',
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Never initialized — don't silently create docs during an upgrade; nudge.
|
|
174
|
+
if (inspection.status === 'missing') {
|
|
175
|
+
summary.action = 'nudge-init';
|
|
176
|
+
if (!json) {
|
|
177
|
+
p.log.warn(
|
|
178
|
+
`No Astryx agent-docs block found — AI agents have no component index. Run \`${formatCliCommand('astryx init --features agents')}\` to install it.`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return summary;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (inspection.status === 'current') return summary;
|
|
185
|
+
|
|
186
|
+
// Stale.
|
|
187
|
+
summary.files = inspection.staleFiles;
|
|
188
|
+
const fromLabel = summary.fromVersions.length
|
|
189
|
+
? `v${summary.fromVersions.join(', v')}`
|
|
190
|
+
: 'an unknown version';
|
|
191
|
+
|
|
192
|
+
if (!apply) {
|
|
193
|
+
// Dry-run: report the pending change, never write.
|
|
194
|
+
summary.action = 'would-refresh';
|
|
195
|
+
if (!json) {
|
|
196
|
+
p.log.warn(
|
|
197
|
+
`Agent docs are stale: block is at ${fromLabel}, installed is v${installedVersion}. Re-run with --apply to refresh (${summary.files.join(', ')}).`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return summary;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Apply: rewrite only files that already carry a marker (onlyReplace).
|
|
204
|
+
try {
|
|
205
|
+
const written = installAgentDocs(cwd, {onlyReplace: true});
|
|
206
|
+
summary.refreshed = written.length > 0;
|
|
207
|
+
summary.files = written;
|
|
208
|
+
if (summary.refreshed) {
|
|
209
|
+
summary.action = 'refreshed';
|
|
210
|
+
if (!json) {
|
|
211
|
+
p.log.success(
|
|
212
|
+
`Agent docs refreshed → v${installedVersion} (from ${fromLabel}): ${written.join(', ')}`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
// We detected a stale marked block but rewrote nothing, and
|
|
217
|
+
// installAgentDocs did not throw — the block markers are malformed (e.g. a
|
|
218
|
+
// START with no matching END, so the writer can't safely splice it). Don't
|
|
219
|
+
// fail silently: the block is exactly the artifact agents rely on.
|
|
220
|
+
summary.action = 'error';
|
|
221
|
+
if (!json) {
|
|
222
|
+
p.log.warn(
|
|
223
|
+
`Agent docs look stale but couldn't be refreshed — the <!-- ASTRYX:START -->/<!-- ASTRYX:END --> markers may be malformed. Run \`${formatCliCommand('astryx init --features agents')}\` to reinstall the block.`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
summary.action = 'error';
|
|
229
|
+
if (!json) {
|
|
230
|
+
p.log.warn(
|
|
231
|
+
`Could not refresh agent docs. Run \`${formatCliCommand('astryx init --features agents')}\` to update them manually.`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return summary;
|
|
236
|
+
}
|
|
237
|
+
|
|
138
238
|
/**
|
|
139
239
|
* Register the `upgrade` command (codemod-driven version migration).
|
|
140
240
|
*/
|
|
@@ -176,7 +276,7 @@ export function registerUpgrade(program) {
|
|
|
176
276
|
|
|
177
277
|
if (!options.list && !options.from) {
|
|
178
278
|
const msg =
|
|
179
|
-
|
|
279
|
+
`Missing required --from. Install the target version first, then run \`${getCliInvocation()} upgrade --from <old-version>\`.`;
|
|
180
280
|
if (json)
|
|
181
281
|
return jsonError(msg, undefined, ERROR_CODES.ERR_INVALID_ARGUMENT);
|
|
182
282
|
p.log.error(msg);
|
|
@@ -237,7 +337,7 @@ export function registerUpgrade(program) {
|
|
|
237
337
|
const installed = detectInstalledTargetVersion();
|
|
238
338
|
if (!installed) {
|
|
239
339
|
const msg =
|
|
240
|
-
|
|
340
|
+
`Could not find installed @astryxdesign/core (or legacy @xds/core). Install the target version first, then rerun \`${getCliInvocation()} upgrade --from <old-version>\`.`;
|
|
241
341
|
if (json)
|
|
242
342
|
return jsonError(msg, undefined, ERROR_CODES.ERR_VERSION_DETECT);
|
|
243
343
|
p.log.error(msg);
|
|
@@ -254,6 +354,19 @@ export function registerUpgrade(program) {
|
|
|
254
354
|
);
|
|
255
355
|
}
|
|
256
356
|
|
|
357
|
+
// Sync the managed agent-docs block to the installed version FIRST. It
|
|
358
|
+
// documents the installed library (version + component index + rules),
|
|
359
|
+
// independent of any source codemods, so it must be refreshed on every
|
|
360
|
+
// path below — including the up-to-date / no-codemods short-circuits that
|
|
361
|
+
// return before codemods ever run (issue #4168). Dry-run reports without
|
|
362
|
+
// writing. Folded into every terminal payload as `agentDocs`.
|
|
363
|
+
const agentDocs = refreshAgentDocs({
|
|
364
|
+
cwd: process.cwd(),
|
|
365
|
+
installedVersion: targetVersion,
|
|
366
|
+
apply: options.apply,
|
|
367
|
+
json,
|
|
368
|
+
});
|
|
369
|
+
|
|
257
370
|
// ───────────────────────────────────────────────────────────────────
|
|
258
371
|
// PIPELINE ORDERING
|
|
259
372
|
//
|
|
@@ -275,6 +388,7 @@ export function registerUpgrade(program) {
|
|
|
275
388
|
status: 'up_to_date',
|
|
276
389
|
from: currentVersion,
|
|
277
390
|
to: targetVersion,
|
|
391
|
+
agentDocs,
|
|
278
392
|
});
|
|
279
393
|
}
|
|
280
394
|
p.log.success('Already up to date — no codemods to run.');
|
|
@@ -390,6 +504,9 @@ export function registerUpgrade(program) {
|
|
|
390
504
|
const codemodFlags = coreConfigCodemodNames
|
|
391
505
|
.map(name => `--codemod ${name}`)
|
|
392
506
|
.join(' ');
|
|
507
|
+
// Canonical (bare) form — this is a structured, machine-executable
|
|
508
|
+
// field in the --json envelope. The human print below is made
|
|
509
|
+
// install-aware via formatCliCommand.
|
|
393
510
|
const suggestedCommand = `astryx upgrade --from ${currentVersion} ${codemodFlags} --apply`;
|
|
394
511
|
const guidance =
|
|
395
512
|
'Your astryx.config currently fails strict validation, but a pending ' +
|
|
@@ -406,10 +523,11 @@ export function registerUpgrade(program) {
|
|
|
406
523
|
suggestedCommand,
|
|
407
524
|
message: guidance,
|
|
408
525
|
note: 'Integrations are skipped in this preview; they will be processed on the --apply run.',
|
|
526
|
+
agentDocs,
|
|
409
527
|
});
|
|
410
528
|
}
|
|
411
529
|
p.log.warn(guidance);
|
|
412
|
-
p.log.info(` ${suggestedCommand}`);
|
|
530
|
+
p.log.info(` ${formatCliCommand(suggestedCommand)}`);
|
|
413
531
|
p.log.info(
|
|
414
532
|
'Integrations are skipped in this preview; they will be processed on the --apply run.',
|
|
415
533
|
);
|
|
@@ -417,11 +535,12 @@ export function registerUpgrade(program) {
|
|
|
417
535
|
return;
|
|
418
536
|
}
|
|
419
537
|
// Genuine config error (apply mode, OR dry-run with no pending core
|
|
420
|
-
// config codemod that would fix it): abort as before.
|
|
538
|
+
// config codemod that would fix it): abort as before. The agent-docs
|
|
539
|
+
// refresh already ran (it's independent of config), so surface it.
|
|
421
540
|
if (json)
|
|
422
541
|
return jsonError(
|
|
423
542
|
err.message,
|
|
424
|
-
|
|
543
|
+
{agentDocs},
|
|
425
544
|
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
426
545
|
);
|
|
427
546
|
p.log.error(err.message);
|
|
@@ -499,6 +618,7 @@ export function registerUpgrade(program) {
|
|
|
499
618
|
status: 'no_codemods',
|
|
500
619
|
from: currentVersion,
|
|
501
620
|
to: targetVersion,
|
|
621
|
+
agentDocs,
|
|
502
622
|
});
|
|
503
623
|
}
|
|
504
624
|
p.log.success('No codemods available for this version range.');
|
|
@@ -510,7 +630,7 @@ export function registerUpgrade(program) {
|
|
|
510
630
|
if (totalTransforms === 0 && totalOptional === 0) {
|
|
511
631
|
const msg = `Codemod "${options.codemod}" not found. Use --list to see available codemods.`;
|
|
512
632
|
if (json)
|
|
513
|
-
return jsonError(msg,
|
|
633
|
+
return jsonError(msg, {agentDocs}, ERROR_CODES.ERR_UNKNOWN_CODEMOD);
|
|
514
634
|
p.log.error(msg);
|
|
515
635
|
p.outro('Aborted');
|
|
516
636
|
process.exitCode = 1;
|
|
@@ -532,7 +652,10 @@ export function registerUpgrade(program) {
|
|
|
532
652
|
to: targetVersion,
|
|
533
653
|
codemods: totalTransforms,
|
|
534
654
|
integrations: integrations.map(i => i.name ?? i.__spec),
|
|
535
|
-
|
|
655
|
+
// Refreshed up front (before the gates above), so the receipt just
|
|
656
|
+
// reports what happened. `agentDocsRefreshed` kept for back-compat.
|
|
657
|
+
agentDocsRefreshed: agentDocs.refreshed,
|
|
658
|
+
agentDocs,
|
|
536
659
|
};
|
|
537
660
|
|
|
538
661
|
// Run file-based integration codemods alongside the core registry
|
|
@@ -604,26 +727,9 @@ export function registerUpgrade(program) {
|
|
|
604
727
|
}
|
|
605
728
|
}
|
|
606
729
|
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
const existingDocs = discoverAgentDocs(process.cwd());
|
|
611
|
-
if (existingDocs.length > 0) {
|
|
612
|
-
try {
|
|
613
|
-
// onlyReplace: only update files that already have Astryx markers.
|
|
614
|
-
// Don't inject into files that never had Astryx content.
|
|
615
|
-
const written = installAgentDocs(process.cwd(), {onlyReplace: true});
|
|
616
|
-
receipt.agentDocsRefreshed = written.length > 0;
|
|
617
|
-
if (!json && written.length > 0)
|
|
618
|
-
p.log.success(`Agent docs updated: ${written.join(', ')}`);
|
|
619
|
-
} catch {
|
|
620
|
-
if (!json) {
|
|
621
|
-
p.log.warn(
|
|
622
|
-
`Could not update agent docs. Run \`${getRunPrefix()} astryx init --features agents\` to update manually.`,
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
730
|
+
// NOTE: the managed agent-docs block was already refreshed up front (see
|
|
731
|
+
// refreshAgentDocs after installed-version detection), so it stays in sync
|
|
732
|
+
// even on the short-circuit paths that return before this point.
|
|
627
733
|
|
|
628
734
|
receipt.filesChanged = mergedFilesChanged;
|
|
629
735
|
receipt.transformsApplied = mergedTransformsApplied;
|
|
@@ -6,6 +6,7 @@ import * as path from 'node:path';
|
|
|
6
6
|
import * as os from 'node:os';
|
|
7
7
|
import {Command} from 'commander';
|
|
8
8
|
import {registerUpgrade} from './upgrade.mjs';
|
|
9
|
+
import {generateCompressedIndex} from './agent-docs.mjs';
|
|
9
10
|
|
|
10
11
|
let tmpDir;
|
|
11
12
|
let originalCwd;
|
|
@@ -24,7 +25,7 @@ beforeEach(() => {
|
|
|
24
25
|
logCalls.push(args.join(' '));
|
|
25
26
|
});
|
|
26
27
|
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
27
|
-
//
|
|
28
|
+
// Some human logs are written straight to process.stdout — capture that too.
|
|
28
29
|
vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
|
|
29
30
|
stdoutCalls.push(typeof chunk === 'string' ? chunk : chunk.toString());
|
|
30
31
|
return true;
|
|
@@ -158,3 +159,156 @@ describe('upgrade --list dedup', () => {
|
|
|
158
159
|
expect(names.length).toBe(unique.size);
|
|
159
160
|
});
|
|
160
161
|
});
|
|
162
|
+
|
|
163
|
+
// Issue #4168 — `upgrade` must refresh the managed agent-docs block after a
|
|
164
|
+
// version bump, on EVERY path (the block documents the installed library, not
|
|
165
|
+
// the codemod outcome), and must NOT write during a dry run.
|
|
166
|
+
describe('upgrade agent-docs refresh (#4168)', () => {
|
|
167
|
+
/** Write an agent-doc file with a managed block generated for `version`. */
|
|
168
|
+
function writeAgentBlock(rel, version) {
|
|
169
|
+
const filePath = path.join(tmpDir, rel);
|
|
170
|
+
fs.mkdirSync(path.dirname(filePath), {recursive: true});
|
|
171
|
+
fs.writeFileSync(filePath, `# Doc\n\n${generateCompressedIndex(version)}\n`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
it('refreshes a stale block with --apply, even when there are no codemods (up-to-date path)', async () => {
|
|
175
|
+
writePkg();
|
|
176
|
+
writeInstalledCore('0.0.15');
|
|
177
|
+
writeAgentBlock('AGENTS.md', '0.0.1');
|
|
178
|
+
|
|
179
|
+
// --from == installed → up-to-date short-circuit: no codemods run, yet the
|
|
180
|
+
// stale block must still be brought current. This is the exact gap in #4168.
|
|
181
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
182
|
+
|
|
183
|
+
expect(result.type).toBe('upgrade.status');
|
|
184
|
+
expect(result.data.status).toBe('up_to_date');
|
|
185
|
+
expect(result.data.agentDocs.status).toBe('stale');
|
|
186
|
+
expect(result.data.agentDocs.action).toBe('refreshed');
|
|
187
|
+
expect(result.data.agentDocs.refreshed).toBe(true);
|
|
188
|
+
expect(result.data.agentDocs.fromVersions).toEqual(['0.0.1']);
|
|
189
|
+
expect(result.data.agentDocs.files).toContain('AGENTS.md');
|
|
190
|
+
|
|
191
|
+
const content = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
192
|
+
expect(content).toMatch(/Astryx v0\.0\.15 ·/);
|
|
193
|
+
expect(content).not.toMatch(/Astryx v0\.0\.1 ·/);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('reports a stale block on a dry run WITHOUT writing', async () => {
|
|
197
|
+
writePkg();
|
|
198
|
+
writeInstalledCore('0.0.15');
|
|
199
|
+
writeAgentBlock('AGENTS.md', '0.0.1');
|
|
200
|
+
const before = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
201
|
+
|
|
202
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15']); // no --apply
|
|
203
|
+
|
|
204
|
+
expect(result.data.agentDocs.status).toBe('stale');
|
|
205
|
+
expect(result.data.agentDocs.action).toBe('would-refresh');
|
|
206
|
+
expect(result.data.agentDocs.refreshed).toBe(false);
|
|
207
|
+
// The file must be untouched by a dry run.
|
|
208
|
+
expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(before);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('refreshes a stale block on the no-codemods path (--apply)', async () => {
|
|
212
|
+
writePkg();
|
|
213
|
+
writeInstalledCore('0.0.11'); // (0.0.10, 0.0.11] has no registered codemods
|
|
214
|
+
writeSourceFile();
|
|
215
|
+
writeAgentBlock('CLAUDE.md', '0.0.1');
|
|
216
|
+
|
|
217
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.10', '--apply', '--path', 'src']);
|
|
218
|
+
|
|
219
|
+
expect(result.type).toBe('upgrade.status');
|
|
220
|
+
expect(result.data.status).toBe('no_codemods');
|
|
221
|
+
expect(result.data.agentDocs.refreshed).toBe(true);
|
|
222
|
+
expect(fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8')).toMatch(/Astryx v0\.0\.11 ·/);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('nudges to run init when core is installed but no managed block exists', async () => {
|
|
226
|
+
writePkg();
|
|
227
|
+
writeInstalledCore('0.0.15');
|
|
228
|
+
// No agent-doc files at all.
|
|
229
|
+
|
|
230
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
231
|
+
|
|
232
|
+
expect(result.data.agentDocs.status).toBe('missing');
|
|
233
|
+
expect(result.data.agentDocs.action).toBe('nudge-init');
|
|
234
|
+
expect(result.data.agentDocs.refreshed).toBe(false);
|
|
235
|
+
// Must NOT silently create docs during an upgrade.
|
|
236
|
+
expect(fs.existsSync(path.join(tmpDir, 'AGENTS.md'))).toBe(false);
|
|
237
|
+
expect(fs.existsSync(path.join(tmpDir, '.claude', 'CLAUDE.md'))).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('treats an agent file without our markers as "never initialized" (missing)', async () => {
|
|
241
|
+
writePkg();
|
|
242
|
+
writeInstalledCore('0.0.15');
|
|
243
|
+
fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), '# Agents\n\nHand-written, no astryx block.\n');
|
|
244
|
+
const before = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
245
|
+
|
|
246
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
247
|
+
|
|
248
|
+
expect(result.data.agentDocs.status).toBe('missing');
|
|
249
|
+
expect(result.data.agentDocs.action).toBe('nudge-init');
|
|
250
|
+
// An unmarked file is left exactly as-is.
|
|
251
|
+
expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(before);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('does not silently no-op a stale block with malformed markers (START without END)', async () => {
|
|
255
|
+
writePkg();
|
|
256
|
+
writeInstalledCore('0.0.15');
|
|
257
|
+
// A corrupted block: START + version header but no matching END marker, so
|
|
258
|
+
// the writer can't safely splice it. This must not crash, corrupt the file,
|
|
259
|
+
// or falsely claim success — it should surface an error and nudge re-init.
|
|
260
|
+
const corrupted =
|
|
261
|
+
'# A\n\n<!-- ASTRYX:START -->\nAstryx v0.0.1 · 9 components\nguidance, but no end marker\n';
|
|
262
|
+
fs.writeFileSync(path.join(tmpDir, 'AGENTS.md'), corrupted);
|
|
263
|
+
|
|
264
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
265
|
+
|
|
266
|
+
expect(result.data.agentDocs.status).toBe('stale');
|
|
267
|
+
expect(result.data.agentDocs.action).toBe('error');
|
|
268
|
+
expect(result.data.agentDocs.refreshed).toBe(false);
|
|
269
|
+
// File left byte-for-byte intact — never corrupted.
|
|
270
|
+
expect(fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8')).toBe(corrupted);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('stays silent when the block already matches the installed version', async () => {
|
|
274
|
+
writePkg();
|
|
275
|
+
writeInstalledCore('0.0.15');
|
|
276
|
+
writeAgentBlock('AGENTS.md', '0.0.15');
|
|
277
|
+
|
|
278
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
279
|
+
|
|
280
|
+
expect(result.data.agentDocs.status).toBe('current');
|
|
281
|
+
expect(result.data.agentDocs.action).toBe('none');
|
|
282
|
+
expect(result.data.agentDocs.refreshed).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('migrates a legacy XDS block to the current Astryx block (--apply)', async () => {
|
|
286
|
+
writePkg();
|
|
287
|
+
writeInstalledCore('0.0.15');
|
|
288
|
+
fs.writeFileSync(
|
|
289
|
+
path.join(tmpDir, 'CLAUDE.md'),
|
|
290
|
+
'# Claude\n\n<!-- XDS:START -->\nlegacy index\n<!-- XDS:END -->\n\nMore rules.\n',
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const result = await runJson(['--json', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
294
|
+
|
|
295
|
+
expect(result.data.agentDocs.status).toBe('stale');
|
|
296
|
+
expect(result.data.agentDocs.refreshed).toBe(true);
|
|
297
|
+
const content = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
|
|
298
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
299
|
+
expect(content).toMatch(/Astryx v0\.0\.15 ·/);
|
|
300
|
+
expect(content).not.toContain('legacy index');
|
|
301
|
+
expect(content).toContain('More rules.');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('surfaces agentDocs in the human output on the up-to-date path', async () => {
|
|
305
|
+
writePkg();
|
|
306
|
+
writeInstalledCore('0.0.15');
|
|
307
|
+
writeAgentBlock('AGENTS.md', '0.0.1');
|
|
308
|
+
|
|
309
|
+
const program = createProgram();
|
|
310
|
+
await program.parseAsync(['node', 'astryx', 'upgrade', '--from', '0.0.15', '--apply']);
|
|
311
|
+
const output = stdoutCalls.join('') + logCalls.join('\n');
|
|
312
|
+
expect(output).toMatch(/Agent docs refreshed/i);
|
|
313
|
+
});
|
|
314
|
+
});
|
package/src/index.mjs
CHANGED
|
@@ -12,13 +12,14 @@ import {fileURLToPath} from 'node:url';
|
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import {checkForUpdate} from './utils/update-check.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import {getCliInvocation} from './utils/package-manager.mjs';
|
|
16
16
|
import {API_VERSION, setJsonMode} from './lib/json.mjs';
|
|
17
17
|
import {buildManifest} from './lib/manifest.mjs';
|
|
18
18
|
import {cliError} from './lib/cli-error.mjs';
|
|
19
19
|
import {ERROR_CODES} from './lib/error-codes.mjs';
|
|
20
20
|
import {levenshteinDistance} from './lib/string-utils.mjs';
|
|
21
21
|
import {installJsonShim} from './lib/json-shim.mjs';
|
|
22
|
+
import {isAstryxInitialized} from './commands/agent-docs.mjs';
|
|
22
23
|
|
|
23
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
25
|
|
|
@@ -174,7 +175,7 @@ function fullCommandName(actionCommand) {
|
|
|
174
175
|
*
|
|
175
176
|
* If --json is set on a command that is not on the JSON_SUPPORTED allowlist,
|
|
176
177
|
* emit a structured error envelope and exit 1 — without running the command's
|
|
177
|
-
* action (so no filesystem mutations, no
|
|
178
|
+
* action (so no filesystem mutations, no interactive prompts, no spawned processes).
|
|
178
179
|
*
|
|
179
180
|
* This is the single source of truth for "command does not support --json".
|
|
180
181
|
* Individual commands should NOT re-check this; they may assume that if their
|
|
@@ -237,6 +238,40 @@ program.hook('postAction', (thisCommand, actionCommand) => {
|
|
|
237
238
|
}
|
|
238
239
|
});
|
|
239
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Enforcement layer 3 — setup nudge. If this project hasn't run `astryx init`
|
|
243
|
+
* yet (no Astryx marker in any agent-doc file — see isAstryxInitialized), remind
|
|
244
|
+
* the user/agent that setup is missing.
|
|
245
|
+
*
|
|
246
|
+
* Uses `preAction` (not postAction) so it fires for EVERY valid command — even
|
|
247
|
+
* ones whose action errors or calls process.exit (postAction is skipped then).
|
|
248
|
+
*
|
|
249
|
+
* Suppressed in --json: that is machine output with a strict clean stdout+stderr
|
|
250
|
+
* contract (json-shim.test: "error envelopes have empty stderr"), and --json
|
|
251
|
+
* consumers parse stdout, not stderr — a stderr nudge would not reach them anyway.
|
|
252
|
+
* The core/cli postinstall layers already nudge at install time regardless of
|
|
253
|
+
* --json; a machine-readable nudge could later be an envelope field. Also skipped
|
|
254
|
+
* for the installer commands themselves and outside a project (no package.json).
|
|
255
|
+
*/
|
|
256
|
+
const SETUP_NUDGE_EXEMPT = new Set(['init', 'agent-docs']);
|
|
257
|
+
program.hook('preAction', (thisCommand, actionCommand) => {
|
|
258
|
+
try {
|
|
259
|
+
if (program.opts().json) return; // machine mode — keep --json output clean
|
|
260
|
+
if (SETUP_NUDGE_EXEMPT.has(actionCommand.name())) return;
|
|
261
|
+
const cwd = process.cwd();
|
|
262
|
+
if (!fs.existsSync(path.join(cwd, 'package.json'))) return; // not a project
|
|
263
|
+
if (isAstryxInitialized(cwd)) return; // already set up — stay quiet
|
|
264
|
+
// Same wording as the core/cli postinstall nudges. #4151's getCliInvocation()
|
|
265
|
+
// renders the correct form for THIS project — scoped `npx @astryxdesign/cli`
|
|
266
|
+
// one-off, or `<pm> astryx` when installed — never the bare `npx astryx` footgun.
|
|
267
|
+
console.error(
|
|
268
|
+
`\nNext step: run \`${getCliInvocation(cwd)} init\` to finish setup and install the Astryx agent prompt.`,
|
|
269
|
+
);
|
|
270
|
+
} catch {
|
|
271
|
+
// Never let the nudge break a command.
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
240
275
|
/**
|
|
241
276
|
* Command registry — each command is lazy-loaded so a broken command
|
|
242
277
|
* doesn't take down the entire CLI.
|
|
@@ -305,15 +340,14 @@ program
|
|
|
305
340
|
console.log(` ${c.name}${tag}`);
|
|
306
341
|
if (c.description) console.log(` ${c.description}`);
|
|
307
342
|
}
|
|
308
|
-
console.log(`\nRun
|
|
343
|
+
console.log(`\nRun \`${getCliInvocation()} manifest --json\` for the full structured manifest.\n`);
|
|
309
344
|
});
|
|
310
345
|
|
|
311
346
|
// Hidden command used by package.json postinstall scripts
|
|
312
347
|
program
|
|
313
348
|
.command('postinstall', {hidden: true})
|
|
314
349
|
.action(() => {
|
|
315
|
-
const
|
|
316
|
-
const r = `${run} xds`;
|
|
350
|
+
const r = getCliInvocation();
|
|
317
351
|
const pad = (s, len) => s + ' '.repeat(Math.max(0, len - s.length));
|
|
318
352
|
const W = 49; // inner width of the box
|
|
319
353
|
const line = (s) => ` │ ${pad(s, W)}│`;
|
|
@@ -323,7 +357,7 @@ ${line('')}
|
|
|
323
357
|
${line(' Design system installed!')}
|
|
324
358
|
${line('')}
|
|
325
359
|
${line(' Get started:')}
|
|
326
|
-
${line(` ${r} init
|
|
360
|
+
${line(` ${r} init Setup + AI agent docs`)}
|
|
327
361
|
${line(` ${r} --help See all commands`)}
|
|
328
362
|
${line('')}
|
|
329
363
|
${line(' Or run directly:')}
|
package/src/lib/cli-error.mjs
CHANGED
|
@@ -55,8 +55,8 @@ import {ERROR_CODES} from './error-codes.mjs';
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Suggestion object — matches the shape used by API errors and the JSON
|
|
58
|
-
* envelope's `suggestions` field.
|
|
59
|
-
* @typedef {
|
|
58
|
+
* envelope's `suggestions` field. Canonical definition lives in types/base.
|
|
59
|
+
* @typedef {import('../types/base').Suggestion} Suggestion
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
/**
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {discoverComponents, findComponentReadme, resolveImportPath} from './component-discovery.mjs';
|
|
8
8
|
import {loadDocs} from './component-loader.mjs';
|
|
9
|
+
import {getCliInvocation} from '../utils/package-manager.mjs';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Derive the `defineTheme` component-override key from a theming target.
|
|
@@ -81,7 +82,7 @@ function formatSubComponent(comp) {
|
|
|
81
82
|
if (table) {
|
|
82
83
|
out.push(table + '\n');
|
|
83
84
|
} else {
|
|
84
|
-
out.push(`See
|
|
85
|
+
out.push(`See \`${getCliInvocation()} component ${comp.name}\` for props and usage.\n`);
|
|
85
86
|
}
|
|
86
87
|
return out;
|
|
87
88
|
}
|
package/src/lib/json-shim.mjs
CHANGED
|
@@ -102,7 +102,7 @@ export function buildHelpEnvelope(cmd) {
|
|
|
102
102
|
* for both success and error).
|
|
103
103
|
*
|
|
104
104
|
* @param {string} message
|
|
105
|
-
* @param {
|
|
105
|
+
* @param {import('../types/base').Suggestion[]} [suggestions]
|
|
106
106
|
* @param {string} [code] - Stable machine-readable error code (error-codes.mjs).
|
|
107
107
|
*/
|
|
108
108
|
function emitJsonError(message, suggestions, code) {
|