@astryxdesign/cli 0.1.0 → 0.1.1-canary.080d887
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 +68 -0
- package/README.md +117 -75
- package/bin/astryx.mjs +22 -7
- package/docs/getting-started.doc.mjs +11 -11
- package/docs/icons.doc.mjs +1 -1
- package/docs/migration.doc.mjs +2 -2
- package/docs/shape.doc.mjs +1 -1
- package/docs/styling.doc.mjs +3 -4
- package/docs/theme.doc.dense.mjs +2 -2
- package/docs/theme.doc.mjs +14 -0
- package/docs/theme.doc.zh.mjs +2 -2
- package/docs/working-with-ai.doc.mjs +4 -4
- package/package.json +20 -9
- package/src/api/discover.mjs +78 -26
- package/src/api/doctor.mjs +3 -3
- package/src/api/layout.mjs +301 -0
- package/src/api/layout.test.mjs +238 -0
- package/src/api/search.mjs +207 -13
- package/src/api/template.mjs +193 -51
- package/src/api/template.test.mjs +2 -0
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/runner.mjs +105 -51
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +116 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +51 -0
- package/src/codemods/transforms/v0.1.0/index.mjs +28 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +230 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +84 -0
- package/src/commands/agent-docs.mjs +119 -66
- package/src/commands/agent-docs.path-safety.test.mjs +1 -1
- package/src/commands/agent-docs.test.mjs +87 -31
- package/src/commands/build-theme.import-path.test.mjs +1 -1
- package/src/commands/build-theme.path-safety.test.mjs +1 -1
- package/src/commands/build-theme.prose.test.mjs +1 -1
- package/src/commands/build.mjs +196 -0
- package/src/commands/component-package.test.mjs +1 -1
- package/src/commands/component.test.mjs +1 -1
- package/src/commands/docs.test.mjs +1 -1
- package/src/commands/doctor.test.mjs +1 -1
- package/src/commands/external-showcase.test.mjs +1 -1
- package/src/commands/gap-report.mjs +17 -9
- package/src/commands/gap-report.test.mjs +21 -16
- package/src/commands/init.mjs +43 -9
- package/src/commands/init.next-steps.test.mjs +46 -0
- package/src/commands/interactive-guard.test.mjs +1 -1
- package/src/commands/json-contract.test.mjs +10 -3
- package/src/commands/layout.mjs +139 -0
- package/src/commands/swizzle-gap-safety.test.mjs +1 -1
- package/src/commands/swizzle.mjs +51 -23
- package/src/commands/swizzle.path-safety.test.mjs +1 -1
- package/src/commands/template.path-safety.test.mjs +1 -1
- package/src/commands/template.test.mjs +1 -1
- package/src/commands/upgrade.mjs +292 -177
- package/src/commands/upgrade.test.mjs +41 -27
- package/src/config.mjs +31 -0
- package/src/config.test.mjs +24 -0
- package/src/index.mjs +5 -0
- package/src/lib/config-schema.mjs +119 -0
- package/src/lib/config.mjs +45 -6
- package/src/lib/config.test.mjs +91 -0
- package/src/lib/error-codes.mjs +11 -0
- package/src/lib/integrations.mjs +155 -0
- package/src/lib/integrations.test.mjs +154 -0
- package/src/lib/levenshtein.mjs +29 -0
- package/src/lib/manifest.mjs +6 -0
- package/src/lib/package-scanner.mjs +31 -7
- package/src/lib/string-utils.mjs +5 -14
- package/src/lib/xle/browser.d.ts +91 -0
- package/src/lib/xle/browser.mjs +120 -0
- package/src/lib/xle/expand.mjs +622 -0
- package/src/lib/xle/parse.mjs +581 -0
- package/src/lib/xle/print.mjs +174 -0
- package/src/lib/xle/registry-core.mjs +170 -0
- package/src/lib/xle/registry.mjs +237 -0
- package/src/lib/xle/splice.mjs +137 -0
- package/src/lib/xle/validate.mjs +356 -0
- package/src/lib/xle/xle.test.mjs +333 -0
- package/src/types/config.d.ts +99 -0
- package/src/types/error-codes.d.ts +1 -0
- package/src/utils/github.mjs +12 -27
- package/src/utils/interactive.mjs +1 -1
- package/src/utils/interactive.test.mjs +2 -0
- package/src/utils/package-manager.mjs +1 -1
- package/src/utils/package-manager.test.mjs +1 -1
- package/src/utils/path-safety.test.mjs +1 -1
- package/src/utils/paths.test.mjs +8 -8
- package/src/utils/update-check.mjs +4 -26
- package/src/utils/update-check.test.mjs +2 -64
- package/templates/blocks/components/AppShell/AppShellContentOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellShowcase.tsx +1 -10
- package/templates/blocks/components/AppShell/AppShellSideNavOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellTopNavOnly.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellTopNavWithSideNav.tsx +1 -9
- package/templates/blocks/components/AppShell/AppShellWithBanner.tsx +1 -9
- package/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +12 -19
- package/templates/blocks/components/Banner/BannerShowcase.tsx +1 -8
- package/templates/blocks/components/Blockquote/BlockquoteShowcase.tsx +1 -8
- package/templates/blocks/components/Carousel/CarouselShowcase.tsx +2 -12
- package/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerShowcase.tsx +6 -9
- package/templates/blocks/components/ChatLayout/ChatLayoutPanelChat.tsx +10 -12
- package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +1 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListShowcase.tsx +1 -9
- package/templates/blocks/components/ChatMessageMetadata/ChatMessageMetadataShowcase.tsx +1 -8
- package/templates/blocks/components/ChatSendButton/ChatSendButtonInComposer.tsx +1 -8
- package/templates/blocks/components/Citation/CitationInlineText.tsx +4 -4
- package/templates/blocks/components/Code/CodeInlineInParagraph.tsx +1 -8
- package/templates/blocks/components/CodeBlock/CodeBlockBashCommand.tsx +1 -1
- package/templates/blocks/components/CodeBlock/CodeBlockJSONConfig.tsx +1 -1
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.doc.mjs +15 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.tsx +26 -0
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemShowcase.tsx +9 -12
- package/templates/blocks/components/ContextMenu/ContextMenuShowcase.tsx +13 -15
- package/templates/blocks/components/DateInput/DateInputDateRange.doc.mjs +2 -2
- package/templates/blocks/components/Divider/DividerShowcase.tsx +1 -8
- package/templates/blocks/components/Divider/DividerVertical.tsx +7 -9
- package/templates/blocks/components/Field/FieldShowcase.tsx +1 -8
- package/templates/blocks/components/FormLayout/FormLayoutHorizontal.tsx +1 -6
- package/templates/blocks/components/Grid/GridResponsiveAutoFit.tsx +1 -9
- package/templates/blocks/components/HoverCard/HoverCardInlineTextHoverCard.tsx +4 -6
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +1 -6
- package/templates/blocks/components/HoverCard/HoverCardProfileHoverCard.tsx +2 -8
- package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -8
- package/templates/blocks/components/OverflowList/OverflowListOverflowBadges.tsx +8 -11
- package/templates/blocks/components/OverflowList/OverflowListOverflowDropdownActions.tsx +9 -12
- package/templates/blocks/components/Overlay/OverlayBottomStrip.tsx +4 -17
- package/templates/blocks/components/Overlay/OverlayHoverReveal.tsx +15 -16
- package/templates/blocks/components/Overlay/OverlayShowcase.tsx +5 -21
- package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -14
- package/templates/blocks/components/Pagination/PaginationPageSize.tsx +12 -14
- package/templates/blocks/components/Pagination/PaginationVariants.tsx +1 -8
- package/templates/blocks/components/Pagination/PaginationWithTable.tsx +2 -14
- package/templates/blocks/components/Slider/SliderShowcase.tsx +10 -1
- package/templates/blocks/components/ToggleButton/ToggleButtonGroup.doc.mjs +1 -1
- package/templates/blocks/components/Tokenizer/TokenizerClear.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerCreatable.tsx +2 -7
- package/templates/blocks/components/Tokenizer/TokenizerEndContent.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerIcon.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerMaxEntries.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerOverflow.tsx +2 -7
- package/templates/blocks/components/Tokenizer/TokenizerShowcase.tsx +1 -6
- package/templates/blocks/components/Tokenizer/TokenizerStates.tsx +4 -9
- package/templates/blocks/components/Toolbar/ToolbarCardHeader.tsx +1 -10
- package/templates/blocks/components/Toolbar/ToolbarSizes.tsx +1 -8
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +1 -8
- package/templates/blocks/components/Toolbar/ToolbarThreeSlot.tsx +1 -10
- package/templates/blocks/components/Toolbar/ToolbarWithTabs.tsx +8 -11
- package/templates/pages/ai-chat/page.tsx +71 -64
- package/templates/pages/ai-chat-landing/page.tsx +8 -12
- package/templates/pages/centered-hero/page.tsx +13 -15
- package/templates/pages/classic-gallery/page.tsx +27 -34
- package/templates/pages/detail-page/page.tsx +18 -18
- package/templates/pages/documentation/page.tsx +42 -58
- package/templates/pages/documentation-design/page.tsx +82 -60
- package/templates/pages/documentation-technical/page.tsx +101 -60
- package/templates/pages/editor/page.tsx +42 -54
- package/templates/pages/file-explorer/page.tsx +13 -16
- package/templates/pages/form-two-column/page.tsx +13 -17
- package/templates/pages/gallery-hero/page.tsx +13 -15
- package/templates/pages/ide/page.tsx +188 -264
- package/templates/pages/library/page.tsx +16 -23
- package/templates/pages/login/page.tsx +14 -18
- package/templates/pages/login-card/page.tsx +14 -18
- package/templates/pages/login-split/page.tsx +50 -48
- package/templates/pages/login-sso/page.tsx +9 -13
- package/templates/pages/mixed-gallery/page.tsx +51 -45
- package/templates/pages/payment-form/page.tsx +56 -70
- package/templates/pages/product-detail/page.tsx +27 -33
- package/templates/pages/product-gallery/page.tsx +7 -13
- package/templates/pages/settings-dialog/page.tsx +35 -43
- package/templates/pages/settings-sidebar/page.tsx +39 -47
- package/templates/pages/side-gallery/page.tsx +6 -9
- package/templates/pages/table-grouped/page.tsx +11 -15
- package/templates/pages/theme-showcase/page.tsx +33 -37
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.doc.mjs +0 -14
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.tsx +0 -67
package/src/commands/upgrade.mjs
CHANGED
|
@@ -3,122 +3,151 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file upgrade command — Full version-to-version upgrade pipeline
|
|
5
5
|
*
|
|
6
|
-
* `astryx upgrade`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* `astryx upgrade` runs codemods that migrate source code from a previous
|
|
7
|
+
* Astryx version to the currently installed version.
|
|
8
|
+
*
|
|
9
|
+
* Consumers should bump/install their Astryx packages first, then run:
|
|
10
|
+
* astryx upgrade --from <old-version> --path <source-dir> --apply
|
|
9
11
|
*
|
|
10
12
|
* Pipeline (--apply):
|
|
11
|
-
* 1.
|
|
12
|
-
* 2.
|
|
13
|
-
* 3.
|
|
14
|
-
* 4. Run codemods for the version range
|
|
15
|
-
* 5. Refresh agent docs (AGENTS.md / CLAUDE.md) if present
|
|
13
|
+
* 1. Read installed @astryxdesign/core (or legacy @xds/core) version
|
|
14
|
+
* 2. Run codemods for --from → installed version
|
|
15
|
+
* 3. Refresh agent docs (AGENTS.md / CLAUDE.md) if present
|
|
16
16
|
*
|
|
17
17
|
* Options:
|
|
18
|
+
* --from <version> Previous version before the dependency upgrade
|
|
18
19
|
* --apply Write changes to disk (default: dry-run)
|
|
19
|
-
* --
|
|
20
|
-
* --
|
|
21
|
-
* --
|
|
22
|
-
* --codemod <name> Run a specific transform only (skips version check)
|
|
23
|
-
* --codemod-only Skip version bump + install, run codemods only
|
|
20
|
+
* --force Run codemods even when from >= installed version
|
|
21
|
+
* --codemod <name> Run a specific transform only
|
|
22
|
+
* --integration <spec> Load an explicit integration package or file
|
|
24
23
|
* --path <dir> Source directory (default: ./src)
|
|
25
24
|
* --install-deps Auto-install jscodeshift without prompting (for CI/LLM)
|
|
26
25
|
*/
|
|
27
26
|
|
|
28
27
|
import * as fs from 'node:fs';
|
|
29
28
|
import * as path from 'node:path';
|
|
30
|
-
import {
|
|
29
|
+
import {execFile} from 'node:child_process';
|
|
30
|
+
import {promisify} from 'node:util';
|
|
31
31
|
import * as p from '@clack/prompts';
|
|
32
32
|
import {ensureJscodeshift} from '../codemods/ensure-jscodeshift.mjs';
|
|
33
|
-
import {
|
|
34
|
-
getTransformsBetween,
|
|
35
|
-
latestVersion,
|
|
36
|
-
} from '../codemods/registry.mjs';
|
|
33
|
+
import {getTransformsBetween, latestVersion} from '../codemods/registry.mjs';
|
|
37
34
|
import {runCodemods} from '../codemods/runner.mjs';
|
|
38
35
|
import {installAgentDocs, discoverAgentDocs} from './agent-docs.mjs';
|
|
39
|
-
import {
|
|
40
|
-
import {isValidSemver, semverGte} from '../utils/semver.mjs';
|
|
36
|
+
import {getRunPrefix} from '../utils/package-manager.mjs';
|
|
37
|
+
import {isValidSemver, semverGte, semverGt} from '../utils/semver.mjs';
|
|
41
38
|
import {jsonOut, jsonError} from '../lib/json.mjs';
|
|
39
|
+
import {loadConfig} from '../lib/config.mjs';
|
|
40
|
+
import {loadIntegrations} from '../lib/integrations.mjs';
|
|
42
41
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
* Detect the installed @astryxdesign/core version from the consumer's package.json.
|
|
46
|
-
* @returns {string|null}
|
|
47
|
-
*/
|
|
48
|
-
function detectCurrentVersion() {
|
|
49
|
-
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
50
|
-
try {
|
|
51
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
52
|
-
const deps = {
|
|
53
|
-
...pkg.peerDependencies,
|
|
54
|
-
...pkg.dependencies,
|
|
55
|
-
...pkg.devDependencies,
|
|
56
|
-
};
|
|
57
|
-
const version = deps['@astryxdesign/core'];
|
|
58
|
-
if (!version) return null;
|
|
59
|
-
// Strip semver range chars (^, ~, >=, etc.)
|
|
60
|
-
return version.replace(/^[^\d]*/, '');
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
43
|
+
const execFileAsync = promisify(execFile);
|
|
65
44
|
|
|
66
45
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* @param {string} targetVersion - Version to bump to (e.g. '0.0.5')
|
|
71
|
-
* @returns {{bumped: string[], pkgPath: string}|null} List of bumped package names, or null if no package.json
|
|
46
|
+
* Detect the installed target version from node_modules.
|
|
47
|
+
* @returns {{version: string, packageName: string}|null}
|
|
72
48
|
*/
|
|
73
|
-
function
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!name.startsWith('@astryxdesign/')) continue;
|
|
87
|
-
|
|
88
|
-
const current = deps[name];
|
|
89
|
-
// Preserve range prefix (^, ~, >=, etc.)
|
|
90
|
-
const prefix = current.match(/^([^\d]*)/)?.[1] ?? '^';
|
|
91
|
-
const newRange = `${prefix}${targetVersion}`;
|
|
92
|
-
|
|
93
|
-
if (current !== newRange) {
|
|
94
|
-
deps[name] = newRange;
|
|
95
|
-
bumped.push(name);
|
|
96
|
-
}
|
|
49
|
+
function detectInstalledTargetVersion() {
|
|
50
|
+
for (const packageName of ['@astryxdesign/core', '@xds/core']) {
|
|
51
|
+
const pkgPath = path.resolve(
|
|
52
|
+
process.cwd(),
|
|
53
|
+
'node_modules',
|
|
54
|
+
...packageName.split('/'),
|
|
55
|
+
'package.json',
|
|
56
|
+
);
|
|
57
|
+
try {
|
|
58
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
59
|
+
if (pkg.version) return {version: pkg.version, packageName};
|
|
60
|
+
} catch {
|
|
61
|
+
// Missing or unreadable package.json — try the next supported package name.
|
|
97
62
|
}
|
|
98
63
|
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
99
66
|
|
|
100
|
-
|
|
67
|
+
function normalizeIntegrationTransforms(integration, from, to) {
|
|
68
|
+
const transforms = [];
|
|
69
|
+
for (const entry of integration.codemods ?? []) {
|
|
70
|
+
const entryFrom = entry.from ?? '0.0.0';
|
|
71
|
+
const entryTo = entry.to ?? to;
|
|
72
|
+
if (semverGte(from, entryTo) || semverGt(entryFrom, to)) continue;
|
|
73
|
+
if (!entry.name)
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Integration ${integration.name ?? integration.__spec} has a codemod without a name.`,
|
|
76
|
+
);
|
|
77
|
+
if (!entry.transform)
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Integration codemod ${entry.name} is missing transform.`,
|
|
80
|
+
);
|
|
81
|
+
const directTransform =
|
|
82
|
+
typeof entry.transform === 'function' ? entry.transform : null;
|
|
83
|
+
if (!directTransform)
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Integration codemod ${entry.name} did not resolve to a function.`,
|
|
86
|
+
);
|
|
87
|
+
transforms.push({
|
|
88
|
+
name: entry.name,
|
|
89
|
+
meta: {
|
|
90
|
+
title:
|
|
91
|
+
entry.title ??
|
|
92
|
+
`${integration.name ?? integration.__spec}: ${entry.name}`,
|
|
93
|
+
description: entry.description ?? '',
|
|
94
|
+
pr: entry.pr,
|
|
95
|
+
fileExtensions: entry.fileExtensions,
|
|
96
|
+
},
|
|
97
|
+
optional: !!entry.optional,
|
|
98
|
+
transform: directTransform,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return transforms.length ? [{version: to, transforms}] : [];
|
|
102
|
+
}
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, indent) + '\n');
|
|
105
|
-
return {bumped, pkgPath};
|
|
104
|
+
function uniqueFiles(files) {
|
|
105
|
+
return [...new Set((files ?? []).filter(Boolean))];
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
108
|
+
async function runPostCodemodHooks(integrations, context, silent) {
|
|
109
|
+
const hooks = integrations.flatMap(integration =>
|
|
110
|
+
(integration.postCodemod ?? []).map(hook => ({integration, hook})),
|
|
111
|
+
);
|
|
112
|
+
if (hooks.length === 0) return;
|
|
113
|
+
|
|
114
|
+
const log = silent ? {info() {}, warn() {}, success() {}, error() {}} : p.log;
|
|
115
|
+
|
|
116
|
+
const run = async (command, args, options = {}) => {
|
|
117
|
+
await execFileAsync(command, args, {
|
|
118
|
+
cwd: options.cwd ?? context.packageDir,
|
|
119
|
+
timeout: options.timeoutMs ?? 300_000,
|
|
120
|
+
stdio: 'pipe',
|
|
121
|
+
encoding: 'utf-8',
|
|
122
|
+
env: {...process.env, ...(options.env ?? {})},
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const ctx = {...context, run};
|
|
127
|
+
for (const {integration, hook} of hooks) {
|
|
128
|
+
const label = `${integration.name ?? integration.__spec}:${hook.name ?? 'postCodemod'}`;
|
|
129
|
+
try {
|
|
130
|
+
if (typeof hook.run === 'function') {
|
|
131
|
+
await hook.run(ctx);
|
|
132
|
+
} else if (typeof hook.command === 'function') {
|
|
133
|
+
const cmd = await hook.command(ctx);
|
|
134
|
+
if (cmd) {
|
|
135
|
+
await run(cmd.command, cmd.args ?? [], {
|
|
136
|
+
cwd: cmd.cwd,
|
|
137
|
+
timeoutMs: cmd.timeoutMs,
|
|
138
|
+
env: cmd.env,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
log.warn(
|
|
143
|
+
`Integration hook ${label} has no run() or command() function; skipping.`,
|
|
144
|
+
);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
log.success(`Post-codemod hook ${label} completed.`);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
log.warn(`Post-codemod hook ${label} failed: ${err.message}`);
|
|
150
|
+
}
|
|
122
151
|
}
|
|
123
152
|
}
|
|
124
153
|
|
|
@@ -129,35 +158,50 @@ export function registerUpgrade(program) {
|
|
|
129
158
|
program
|
|
130
159
|
.command('upgrade')
|
|
131
160
|
.description('Run codemods to migrate between versions')
|
|
161
|
+
.option(
|
|
162
|
+
'--from <version>',
|
|
163
|
+
'Previous version before the dependency upgrade',
|
|
164
|
+
)
|
|
132
165
|
.option('--apply', 'Write changes to disk (default: dry-run)', false)
|
|
133
|
-
.option(
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
.option(
|
|
167
|
+
'--force',
|
|
168
|
+
'Run codemods even if --from is newer than the installed version',
|
|
169
|
+
false,
|
|
170
|
+
)
|
|
136
171
|
.option('--codemod <name>', 'Run a specific transform only')
|
|
137
|
-
.option(
|
|
138
|
-
|
|
139
|
-
|
|
172
|
+
.option(
|
|
173
|
+
'--integration <package-or-file>',
|
|
174
|
+
'Explicit integration package name or integration file path (repeatable)',
|
|
175
|
+
(value, previous) => [...(previous ?? []), value],
|
|
176
|
+
[],
|
|
177
|
+
)
|
|
140
178
|
.option('--path <dir>', 'Source directory to scan', './src')
|
|
141
|
-
.option(
|
|
179
|
+
.option(
|
|
180
|
+
'--install-deps',
|
|
181
|
+
'Auto-install jscodeshift without prompting',
|
|
182
|
+
false,
|
|
183
|
+
)
|
|
142
184
|
.option('--list', 'List available codemods', false)
|
|
143
|
-
.action(async
|
|
185
|
+
.action(async options => {
|
|
144
186
|
const json = program.opts().json || false;
|
|
145
187
|
if (!json) p.intro('Upgrade');
|
|
146
188
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (json) return jsonError(msg, undefined, ERROR_CODES.ERR_INVALID_VERSION);
|
|
189
|
+
if (!options.list && !options.from) {
|
|
190
|
+
const msg =
|
|
191
|
+
'Missing required --from. Install the target version first, then run `astryx upgrade --from <old-version>`.';
|
|
192
|
+
if (json)
|
|
193
|
+
return jsonError(msg, undefined, ERROR_CODES.ERR_INVALID_ARGUMENT);
|
|
153
194
|
p.log.error(msg);
|
|
154
195
|
p.outro('Aborted');
|
|
155
196
|
process.exitCode = 1;
|
|
156
197
|
return;
|
|
157
198
|
}
|
|
158
|
-
|
|
199
|
+
|
|
200
|
+
// Validate --from upfront so callers don't silently accept typos.
|
|
201
|
+
if (!options.list && !isValidSemver(options.from)) {
|
|
159
202
|
const msg = `Invalid --from value: "${options.from}". Expected a semver string like 0.0.5.`;
|
|
160
|
-
if (json)
|
|
203
|
+
if (json)
|
|
204
|
+
return jsonError(msg, undefined, ERROR_CODES.ERR_INVALID_VERSION);
|
|
161
205
|
p.log.error(msg);
|
|
162
206
|
p.outro('Aborted');
|
|
163
207
|
process.exitCode = 1;
|
|
@@ -172,72 +216,113 @@ export function registerUpgrade(program) {
|
|
|
172
216
|
const manifests = await getTransformsBetween('0.0.0', latestVersion);
|
|
173
217
|
for (const {version, transforms} of manifests) {
|
|
174
218
|
for (const {name, meta, optional} of transforms) {
|
|
175
|
-
codemods.push({
|
|
219
|
+
codemods.push({
|
|
220
|
+
name,
|
|
221
|
+
title: meta.title,
|
|
222
|
+
version,
|
|
223
|
+
pr: meta.pr,
|
|
224
|
+
optional: !!optional,
|
|
225
|
+
});
|
|
176
226
|
}
|
|
177
227
|
}
|
|
178
|
-
if (json)
|
|
228
|
+
if (json)
|
|
229
|
+
return jsonOut(
|
|
230
|
+
'upgrade.list',
|
|
231
|
+
codemods.map(({name, title, version, optional}) => ({
|
|
232
|
+
name,
|
|
233
|
+
title,
|
|
234
|
+
version,
|
|
235
|
+
optional,
|
|
236
|
+
})),
|
|
237
|
+
);
|
|
179
238
|
p.log.step('Available codemods:');
|
|
180
239
|
for (const {name, title, pr, optional} of codemods) {
|
|
181
|
-
p.log.info(
|
|
240
|
+
p.log.info(
|
|
241
|
+
` ${name} — ${title}${optional ? ' (optional)' : ''} (${pr})`,
|
|
242
|
+
);
|
|
182
243
|
}
|
|
183
244
|
p.outro('Done');
|
|
184
245
|
return;
|
|
185
246
|
}
|
|
186
247
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const skipBump = options.codemodOnly || skipVersionCheck;
|
|
195
|
-
|
|
196
|
-
// Detect current version (--from overrides package.json)
|
|
197
|
-
const currentVersion = options.from ?? detectCurrentVersion();
|
|
198
|
-
if (!currentVersion && !skipVersionCheck) {
|
|
199
|
-
const msg = 'Could not detect @astryxdesign/core version. Make sure package.json is in the current directory, or use --from <version>.';
|
|
200
|
-
if (json) return jsonError(msg, undefined, ERROR_CODES.ERR_VERSION_DETECT);
|
|
248
|
+
const currentVersion = options.from;
|
|
249
|
+
const installed = detectInstalledTargetVersion();
|
|
250
|
+
if (!installed) {
|
|
251
|
+
const msg =
|
|
252
|
+
'Could not find installed @astryxdesign/core (or legacy @xds/core). Install the target version first, then rerun `astryx upgrade --from <old-version>`.';
|
|
253
|
+
if (json)
|
|
254
|
+
return jsonError(msg, undefined, ERROR_CODES.ERR_VERSION_DETECT);
|
|
201
255
|
p.log.error(msg);
|
|
202
256
|
p.outro('Aborted');
|
|
203
257
|
process.exitCode = 1;
|
|
204
258
|
return;
|
|
205
259
|
}
|
|
260
|
+
const targetVersion = installed.version;
|
|
206
261
|
|
|
207
|
-
|
|
262
|
+
if (!json) {
|
|
263
|
+
p.log.info(`From version: ${currentVersion}`);
|
|
264
|
+
p.log.info(
|
|
265
|
+
`Installed target: ${targetVersion} (${installed.packageName})`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
208
268
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
269
|
+
let integrations;
|
|
270
|
+
try {
|
|
271
|
+
const config = await loadConfig(process.cwd());
|
|
272
|
+
const integrationSpecs = uniqueFiles([
|
|
273
|
+
...(config.integrations ?? []),
|
|
274
|
+
...(options.integration ?? []),
|
|
275
|
+
]);
|
|
276
|
+
integrations = await loadIntegrations(integrationSpecs);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
if (json)
|
|
279
|
+
return jsonError(
|
|
280
|
+
err.message,
|
|
281
|
+
undefined,
|
|
282
|
+
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
283
|
+
);
|
|
284
|
+
p.log.error(err.message);
|
|
285
|
+
p.outro('Aborted');
|
|
286
|
+
process.exitCode = 1;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!json && integrations.length > 0) {
|
|
290
|
+
p.log.info(
|
|
291
|
+
`Integrations: ${integrations.map(i => i.name ?? i.__spec).join(', ')}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
214
294
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
p.log.success('Already up to date — no codemods to run.');
|
|
224
|
-
p.log.info('Use --force to run codemods anyway, or --from <version> to specify the previous version.');
|
|
225
|
-
p.outro('Done');
|
|
226
|
-
return;
|
|
295
|
+
if (!options.force && semverGte(currentVersion, targetVersion)) {
|
|
296
|
+
if (json) {
|
|
297
|
+
return jsonOut('upgrade.status', {
|
|
298
|
+
status: 'up_to_date',
|
|
299
|
+
from: currentVersion,
|
|
300
|
+
to: targetVersion,
|
|
301
|
+
});
|
|
227
302
|
}
|
|
303
|
+
p.log.success('Already up to date — no codemods to run.');
|
|
304
|
+
p.log.info('Use --force to run codemods anyway.');
|
|
305
|
+
p.outro('Done');
|
|
306
|
+
return;
|
|
228
307
|
}
|
|
229
308
|
|
|
230
309
|
// Resolve transforms
|
|
231
|
-
const versionManifests =
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
310
|
+
const versionManifests = [
|
|
311
|
+
...(await getTransformsBetween(currentVersion, targetVersion)),
|
|
312
|
+
...integrations.flatMap(integration =>
|
|
313
|
+
normalizeIntegrationTransforms(
|
|
314
|
+
integration,
|
|
315
|
+
currentVersion,
|
|
316
|
+
targetVersion,
|
|
317
|
+
),
|
|
318
|
+
),
|
|
319
|
+
];
|
|
235
320
|
|
|
236
321
|
if (versionManifests.length === 0) {
|
|
237
322
|
if (json) {
|
|
238
323
|
return jsonOut('upgrade.status', {
|
|
239
324
|
status: 'no_codemods',
|
|
240
|
-
from:
|
|
325
|
+
from: currentVersion,
|
|
241
326
|
to: targetVersion,
|
|
242
327
|
});
|
|
243
328
|
}
|
|
@@ -262,7 +347,8 @@ export function registerUpgrade(program) {
|
|
|
262
347
|
|
|
263
348
|
if (totalTransforms === 0 && totalOptional === 0) {
|
|
264
349
|
const msg = `Codemod "${options.codemod}" not found. Use --list to see available codemods.`;
|
|
265
|
-
if (json)
|
|
350
|
+
if (json)
|
|
351
|
+
return jsonError(msg, undefined, ERROR_CODES.ERR_UNKNOWN_CODEMOD);
|
|
266
352
|
p.log.error(msg);
|
|
267
353
|
p.outro('Aborted');
|
|
268
354
|
process.exitCode = 1;
|
|
@@ -279,34 +365,26 @@ export function registerUpgrade(program) {
|
|
|
279
365
|
}
|
|
280
366
|
}
|
|
281
367
|
|
|
282
|
-
const receipt = {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (!json) p.log.info(`Bumped ${result.bumped.join(', ')} → ${targetVersion}`);
|
|
290
|
-
|
|
291
|
-
const installCmd = getInstallCommand(options.forceInstall);
|
|
292
|
-
if (options.skipInstall) {
|
|
293
|
-
if (!json) p.log.info('Skipping install (--skip-install). Run your package manager manually.');
|
|
294
|
-
} else {
|
|
295
|
-
if (!json) p.log.step(`Running ${installCmd}...`);
|
|
296
|
-
try {
|
|
297
|
-
execSync(installCmd, {stdio: 'inherit', cwd: process.cwd()});
|
|
298
|
-
if (!json) p.log.success('Dependencies installed.');
|
|
299
|
-
} catch {
|
|
300
|
-
if (!json) p.log.warn('Install failed — codemods will still run against existing code.');
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
368
|
+
const receipt = {
|
|
369
|
+
from: currentVersion,
|
|
370
|
+
to: targetVersion,
|
|
371
|
+
codemods: totalTransforms,
|
|
372
|
+
integrations: integrations.map(i => i.name ?? i.__spec),
|
|
373
|
+
agentDocsRefreshed: false,
|
|
374
|
+
};
|
|
305
375
|
|
|
306
376
|
// Ensure jscodeshift is available
|
|
307
|
-
const ready = await ensureJscodeshift({
|
|
377
|
+
const ready = await ensureJscodeshift({
|
|
378
|
+
installDeps: options.installDeps,
|
|
379
|
+
silent: json,
|
|
380
|
+
});
|
|
308
381
|
if (!ready) {
|
|
309
|
-
if (json)
|
|
382
|
+
if (json)
|
|
383
|
+
return jsonError(
|
|
384
|
+
'jscodeshift is required but could not be installed.',
|
|
385
|
+
undefined,
|
|
386
|
+
ERROR_CODES.ERR_DEP_MISSING,
|
|
387
|
+
);
|
|
310
388
|
p.outro('Aborted');
|
|
311
389
|
process.exitCode = 1;
|
|
312
390
|
return;
|
|
@@ -320,6 +398,31 @@ export function registerUpgrade(program) {
|
|
|
320
398
|
silent: json,
|
|
321
399
|
});
|
|
322
400
|
|
|
401
|
+
if (options.apply && integrations.length > 0) {
|
|
402
|
+
const codemodDir = path.resolve(options.path);
|
|
403
|
+
const absoluteChangedFiles = uniqueFiles(
|
|
404
|
+
codemodResult?.writtenFiles ?? [],
|
|
405
|
+
);
|
|
406
|
+
const changedFiles = absoluteChangedFiles.map(file =>
|
|
407
|
+
path.relative(process.cwd(), file),
|
|
408
|
+
);
|
|
409
|
+
const packageChangedFiles = absoluteChangedFiles
|
|
410
|
+
.filter(file => file.startsWith(process.cwd() + path.sep))
|
|
411
|
+
.map(file => path.relative(process.cwd(), file));
|
|
412
|
+
await runPostCodemodHooks(
|
|
413
|
+
integrations,
|
|
414
|
+
{
|
|
415
|
+
packageDir: process.cwd(),
|
|
416
|
+
codemodDir,
|
|
417
|
+
changedFiles,
|
|
418
|
+
absoluteChangedFiles,
|
|
419
|
+
packageChangedFiles,
|
|
420
|
+
apply: options.apply,
|
|
421
|
+
},
|
|
422
|
+
json,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
323
426
|
// Refresh agent docs if any exist (AGENTS.md, CLAUDE.md, .claude/CLAUDE.md, etc.)
|
|
324
427
|
// Always update after --apply; also update during dry-run if files exist,
|
|
325
428
|
// since the index reflects the installed CLI version, not the codemods.
|
|
@@ -330,7 +433,8 @@ export function registerUpgrade(program) {
|
|
|
330
433
|
// Don't inject into files that never had Astryx content.
|
|
331
434
|
const written = installAgentDocs(process.cwd(), {onlyReplace: true});
|
|
332
435
|
receipt.agentDocsRefreshed = written.length > 0;
|
|
333
|
-
if (!json && written.length > 0)
|
|
436
|
+
if (!json && written.length > 0)
|
|
437
|
+
p.log.success(`Agent docs updated: ${written.join(', ')}`);
|
|
334
438
|
} catch {
|
|
335
439
|
if (!json) {
|
|
336
440
|
p.log.warn(
|
|
@@ -340,12 +444,23 @@ export function registerUpgrade(program) {
|
|
|
340
444
|
}
|
|
341
445
|
}
|
|
342
446
|
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
447
|
+
if (codemodResult && typeof codemodResult === 'object') {
|
|
448
|
+
receipt.filesChanged = codemodResult.totalFilesChanged ?? 0;
|
|
449
|
+
receipt.transformsApplied = codemodResult.totalTransformsApplied ?? 0;
|
|
450
|
+
receipt.errors = codemodResult.errors ?? [];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (receipt.errors?.length > 0) {
|
|
454
|
+
const msg = `Upgrade completed with ${receipt.errors.length} codemod error${receipt.errors.length === 1 ? '' : 's'}.`;
|
|
455
|
+
if (json) {
|
|
456
|
+
return jsonError(msg, {receipt}, ERROR_CODES.ERR_CODEMOD_FAILED);
|
|
348
457
|
}
|
|
458
|
+
p.outro('Upgrade failed');
|
|
459
|
+
process.exitCode = 1;
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (json) {
|
|
349
464
|
return jsonOut('upgrade.run', receipt);
|
|
350
465
|
}
|
|
351
466
|
p.outro(options.apply ? 'Upgrade complete' : 'Dry run complete');
|