@astryxdesign/cli 0.1.1 → 0.1.2-canary.043f3fd
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 +50 -0
- package/docs/theme.doc.mjs +2 -2
- package/docs/working-with-ai.doc.mjs +6 -6
- package/package.json +31 -8
- package/src/api/component.mjs +253 -12
- package/src/api/discover.mjs +87 -28
- package/src/api/doctor.mjs +7 -22
- package/src/api/index.mjs +1 -0
- package/src/api/layout.mjs +302 -0
- package/src/api/layout.test.mjs +241 -0
- package/src/api/template-integration.test.mjs +225 -0
- package/src/api/template.mjs +421 -122
- package/src/api/template.test.mjs +2 -0
- package/src/api/theme-add.mjs +182 -0
- package/src/api/validate-integration.mjs +370 -0
- package/src/api/validate-integration.test.mjs +222 -0
- package/src/codemod.mjs +93 -0
- package/src/codemod.test.mjs +134 -0
- package/src/codemods/__tests__/registry.test.mjs +2 -0
- package/src/codemods/__tests__/runner.test.mjs +103 -0
- package/src/codemods/integration-discovery.mjs +168 -0
- package/src/codemods/integration-discovery.test.mjs +234 -0
- package/src/codemods/integration-runner.mjs +109 -0
- package/src/codemods/registry.mjs +2 -0
- package/src/codemods/run-codemod.mjs +207 -0
- package/src/codemods/runner.mjs +73 -96
- package/src/codemods/transforms/v0.0.15/index.mjs +0 -13
- package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +81 -0
- package/src/codemods/transforms/{v0.0.15 → v0.1.0}/drop-xds-prefix-imports.mjs +15 -4
- package/src/codemods/transforms/v0.1.0/index.mjs +15 -6
- package/src/codemods/transforms/v0.1.2/__tests__/rename-text-color-active-to-accent.test.mjs +120 -0
- package/src/codemods/transforms/v0.1.2/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.2/rename-text-color-active-to-accent.mjs +136 -0
- package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +360 -0
- package/src/codemods/transforms/v0.1.3/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +260 -0
- package/src/commands/build-theme.import-path.test.mjs +22 -2
- package/src/commands/build-theme.mjs +185 -16
- package/src/commands/component/index.mjs +44 -10
- package/src/commands/component-ownership.test.mjs +227 -0
- package/src/commands/discover.mjs +5 -16
- package/src/commands/doctor.test.mjs +3 -3
- package/src/commands/init.mjs +34 -8
- package/src/commands/init.next-steps.test.mjs +46 -0
- package/src/commands/json-contract.test.mjs +0 -32
- package/src/commands/layout.mjs +139 -0
- package/src/commands/swizzle.mjs +241 -224
- package/src/commands/swizzle.path-safety.test.mjs +3 -3
- package/src/commands/swizzle.routing.test.mjs +279 -0
- package/src/commands/template.mjs +33 -37
- package/src/commands/upgrade.config-ordering.test.mjs +273 -0
- package/src/commands/upgrade.integration-policy.test.mjs +204 -0
- package/src/commands/upgrade.mjs +349 -239
- package/src/commands/validate-integration.mjs +110 -0
- package/src/commands/validate-integration.test.mjs +124 -0
- package/src/config.mjs +18 -0
- package/src/config.test.mjs +100 -0
- package/src/index.mjs +13 -3
- package/src/integration.mjs +19 -0
- package/src/lib/component-discovery.mjs +177 -0
- package/src/lib/config-cache.mjs +91 -0
- package/src/lib/config-cache.test.mjs +83 -0
- package/src/lib/config-schema.mjs +105 -0
- package/src/lib/error-codes.mjs +18 -12
- package/src/lib/integration-warnings.mjs +62 -0
- package/src/lib/integration-warnings.test.mjs +102 -0
- package/src/lib/integrations.mjs +132 -0
- package/src/lib/integrations.test.mjs +135 -0
- package/src/lib/levenshtein.mjs +29 -0
- package/src/lib/manifest.mjs +15 -2
- package/src/lib/module-loader.mjs +80 -0
- package/src/lib/module-loader.test.mjs +106 -0
- package/src/lib/package-scanner.mjs +31 -7
- package/src/lib/project.mjs +502 -0
- package/src/lib/project.test.mjs +308 -0
- package/src/lib/string-utils.mjs +5 -14
- package/src/lib/xle/browser.d.ts +100 -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/template.mjs +73 -0
- package/src/template.test.mjs +127 -0
- package/src/types/api.d.ts +2 -6
- package/src/types/base.d.ts +3 -9
- package/src/types/codemod.d.ts +81 -0
- package/src/types/component.d.ts +27 -2
- package/src/types/config.d.ts +70 -0
- package/src/types/error-codes.d.ts +5 -3
- package/src/types/index.d.ts +0 -1
- package/src/types/integration.d.ts +29 -0
- package/src/types/swizzle.d.ts +9 -2
- package/src/types/template-api.d.ts +54 -0
- package/src/types/template.d.ts +10 -7
- package/src/types/upgrade.d.ts +29 -0
- package/src/types/validate-integration.d.ts +24 -0
- package/src/utils/github.mjs +0 -252
- package/src/utils/interactive.mjs +2 -2
- package/templates/blocks/components/Card/ClickableCardWithNestedButton.doc.mjs +1 -1
- package/templates/blocks/components/Card/SelectableCardMulti.doc.mjs +1 -1
- package/templates/blocks/components/ChatComposerDrawer/ChatComposerDrawerWithProgress.tsx +1 -1
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonShowcase.tsx +3 -3
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.doc.mjs +15 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyShowcase.tsx +26 -0
- package/templates/blocks/components/DateInput/DateInputDateRange.doc.mjs +2 -2
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +46 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +52 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +43 -0
- package/templates/blocks/components/FileInput/FileInputShowcase.tsx +4 -2
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +14 -0
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.tsx +43 -0
- package/templates/blocks/components/MultiSelector/MultiSelectorShowcase.tsx +6 -2
- package/templates/blocks/components/NumberInput/NumberInputShowcase.tsx +6 -2
- package/templates/blocks/components/Outline/OutlineShowcase.doc.mjs +15 -0
- package/templates/blocks/components/Outline/OutlineShowcase.tsx +22 -0
- package/templates/blocks/components/RadioList/RadioListShowcase.tsx +8 -1
- package/templates/blocks/components/SegmentedControl/SegmentedControlShowcase.tsx +3 -1
- package/templates/blocks/components/Selector/SelectorShowcase.tsx +4 -1
- package/templates/blocks/components/Slider/SliderShowcase.tsx +10 -1
- package/templates/blocks/components/Tab/TabShowcase.tsx +3 -1
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
- package/templates/blocks/components/TabList/TabListShowcase.tsx +3 -1
- package/templates/blocks/components/TabMenu/TabMenuShowcase.tsx +3 -1
- package/templates/blocks/components/Table/ColumnResizeHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Table/ColumnResizeHookUsage.tsx +59 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.tsx +104 -0
- package/templates/blocks/components/Text/TextColors.tsx +20 -5
- package/templates/blocks/components/TextArea/TextAreaShowcase.tsx +4 -2
- package/templates/blocks/components/TextInput/TextInputShowcase.tsx +4 -2
- package/templates/blocks/components/Thumbnail/ThumbnailDisabled.tsx +11 -6
- package/templates/blocks/components/Thumbnail/ThumbnailGallery.tsx +43 -7
- package/templates/blocks/components/Thumbnail/ThumbnailRemovable.tsx +40 -3
- package/templates/blocks/components/Thumbnail/ThumbnailShowcase.tsx +6 -5
- package/templates/blocks/components/Thumbnail/ThumbnailStates.tsx +11 -6
- package/templates/blocks/components/Timestamp/TimestampColors.tsx +2 -2
- package/templates/blocks/components/ToggleButton/ToggleButtonGroup.doc.mjs +1 -1
- package/templates/blocks/components/Tokenizer/TokenizerShowcase.tsx +8 -6
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.doc.mjs +2 -2
- package/templates/blocks/components/Toolbar/ToolbarTableFilter.tsx +23 -14
- package/templates/blocks/components/Typeahead/TypeaheadShowcase.tsx +6 -2
- package/templates/pages/shell-nav/page.tsx +321 -0
- package/templates/pages/shell-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-side-nav/page.tsx +242 -0
- package/templates/pages/shell-side-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-top-nav/page.tsx +224 -0
- package/templates/pages/shell-top-nav/template.doc.mjs +12 -0
- package/templates/pages/theme-showcase/page.tsx +8 -19
- package/templates/themes/butter/butterTheme.ts +916 -0
- package/templates/themes/butter/icons.tsx +77 -0
- package/templates/themes/chocolate/chocolateTheme.ts +230 -0
- package/templates/themes/chocolate/icons.tsx +77 -0
- package/templates/themes/gothic/gothicTheme.ts +657 -0
- package/templates/themes/gothic/icons.tsx +77 -0
- package/templates/themes/manifest.json +90 -0
- package/templates/themes/matcha/icons.tsx +67 -0
- package/templates/themes/matcha/matchaTheme.ts +247 -0
- package/templates/themes/neutral/icons.tsx +77 -0
- package/templates/themes/neutral/neutralTheme.ts +603 -0
- package/templates/themes/stone/icons.tsx +77 -0
- package/templates/themes/stone/stoneTheme.ts +652 -0
- package/templates/themes/y2k/icons.tsx +67 -0
- package/templates/themes/y2k/y2kTheme.ts +617 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +0 -116
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +0 -230
- package/src/commands/gap-report.mjs +0 -456
- package/src/commands/gap-report.test.mjs +0 -163
- package/src/commands/swizzle-gap-safety.test.mjs +0 -273
- package/src/lib/config.mjs +0 -86
- package/src/lib/config.test.mjs +0 -42
- package/src/types/gap-report.d.ts +0 -29
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.doc.mjs +0 -14
- package/templates/blocks/components/MoreMenu/MoreMenuInToolbar.tsx +0 -57
- /package/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
package/src/commands/upgrade.mjs
CHANGED
|
@@ -15,29 +15,44 @@
|
|
|
15
15
|
* 3. Refresh agent docs (AGENTS.md / CLAUDE.md) if present
|
|
16
16
|
*
|
|
17
17
|
* Options:
|
|
18
|
-
* --from <version>
|
|
19
|
-
* --apply
|
|
20
|
-
* --force
|
|
21
|
-
* --codemod <name>
|
|
22
|
-
* --
|
|
23
|
-
*
|
|
24
|
-
* --
|
|
18
|
+
* --from <version> Previous version before the dependency upgrade
|
|
19
|
+
* --apply Write changes to disk (default: dry-run)
|
|
20
|
+
* --force Run codemods even when from >= installed version
|
|
21
|
+
* --codemod <name> Run a specific transform only
|
|
22
|
+
* --skip-codemod <name…> Exclude named codemods (variadic). Use this to
|
|
23
|
+
* re-run past a codemod that failed at execution time.
|
|
24
|
+
* --integration <spec> Load an explicit integration package or file
|
|
25
|
+
* --path <dir> Source directory (default: ./src)
|
|
26
|
+
* --install-deps Auto-install jscodeshift without prompting (for CI/LLM)
|
|
27
|
+
*
|
|
28
|
+
* Integration error policy:
|
|
29
|
+
* - A DISCOVERY/definition error for an integration (bad manifest/export,
|
|
30
|
+
* duplicate ids, missing root) SKIPS that integration's codemods and warns
|
|
31
|
+
* (via the integration-issue nudge) — it does NOT hard-fail the upgrade.
|
|
32
|
+
* - An EXECUTION-time failure (a transform THROWS while rewriting files)
|
|
33
|
+
* ABORTS the upgrade (nonzero exit) so a partial write never proceeds.
|
|
25
34
|
*/
|
|
26
35
|
|
|
27
36
|
import * as fs from 'node:fs';
|
|
28
37
|
import * as path from 'node:path';
|
|
29
|
-
import {pathToFileURL} from 'node:url';
|
|
30
38
|
import {execFile} from 'node:child_process';
|
|
31
39
|
import {promisify} from 'node:util';
|
|
32
40
|
import * as p from '@clack/prompts';
|
|
33
41
|
import {ensureJscodeshift} from '../codemods/ensure-jscodeshift.mjs';
|
|
34
42
|
import {getTransformsBetween, latestVersion} from '../codemods/registry.mjs';
|
|
35
43
|
import {runCodemods} from '../codemods/runner.mjs';
|
|
44
|
+
import {
|
|
45
|
+
discoverIntegrationCodemods,
|
|
46
|
+
selectIntegrationCodemods,
|
|
47
|
+
} from '../codemods/integration-discovery.mjs';
|
|
48
|
+
import {runIntegrationCodemods} from '../codemods/integration-runner.mjs';
|
|
36
49
|
import {installAgentDocs, discoverAgentDocs} from './agent-docs.mjs';
|
|
37
50
|
import {getRunPrefix} from '../utils/package-manager.mjs';
|
|
38
|
-
import {isValidSemver, semverGte
|
|
51
|
+
import {isValidSemver, semverGte} from '../utils/semver.mjs';
|
|
39
52
|
import {jsonOut, jsonError} from '../lib/json.mjs';
|
|
40
|
-
import {
|
|
53
|
+
import {Project} from '../lib/project.mjs';
|
|
54
|
+
import {loadIntegrations} from '../lib/integrations.mjs';
|
|
55
|
+
import {warnOnIntegrationIssues} from '../lib/integration-warnings.mjs';
|
|
41
56
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
42
57
|
|
|
43
58
|
const execFileAsync = promisify(execFile);
|
|
@@ -64,159 +79,59 @@ function detectInstalledTargetVersion() {
|
|
|
64
79
|
return null;
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
function
|
|
68
|
-
return (
|
|
69
|
-
spec.startsWith('.') ||
|
|
70
|
-
spec.startsWith('/') ||
|
|
71
|
-
spec.endsWith('.mjs') ||
|
|
72
|
-
spec.endsWith('.js')
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function resolvePackageDir(packageName) {
|
|
77
|
-
const parts = packageName.split('/');
|
|
78
|
-
return path.resolve(process.cwd(), 'node_modules', ...parts);
|
|
82
|
+
function uniqueFiles(files) {
|
|
83
|
+
return [...new Set((files ?? []).filter(Boolean))];
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const manifestPath = pkg.astryx?.integration ?? pkg.xds?.integration;
|
|
98
|
-
if (!manifestPath) {
|
|
99
|
-
throw new Error(
|
|
100
|
-
`Package "${spec}" does not declare astryx.integration (or legacy xds.integration) in package.json.`,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
return path.resolve(packageDir, manifestPath);
|
|
104
|
-
}
|
|
86
|
+
/**
|
|
87
|
+
* Run the app config's post-codemod hooks (config.hooks.postCodemod).
|
|
88
|
+
*
|
|
89
|
+
* Each hook's `buildCommand({packageDir, files})` returns a command to run
|
|
90
|
+
* (or a nullish value to skip). In dry-run mode we only PREVIEW — buildCommand
|
|
91
|
+
* is called (so a throw still fails the run) but the command is never executed.
|
|
92
|
+
* In apply mode the commands run in order via execFile; a nonzero exit (or a
|
|
93
|
+
* buildCommand throw) fails the upgrade.
|
|
94
|
+
*
|
|
95
|
+
* @param {import('../types/config').PostCodemodHook[]} hooks
|
|
96
|
+
* @param {{packageDir: string, files: string[], apply: boolean}} context
|
|
97
|
+
* @param {boolean} silent
|
|
98
|
+
*/
|
|
99
|
+
async function runPostCodemodHooks(hooks, context, silent) {
|
|
100
|
+
if (!hooks || hooks.length === 0) return;
|
|
105
101
|
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
for (const spec of specs) {
|
|
109
|
-
const file = resolveIntegrationFile(spec);
|
|
110
|
-
const mod = await import(pathToFileURL(file).href);
|
|
111
|
-
const integration = mod.default ?? mod.integration ?? mod;
|
|
112
|
-
if (!integration || typeof integration !== 'object') {
|
|
113
|
-
throw new Error(`Integration ${spec} did not export an object.`);
|
|
114
|
-
}
|
|
115
|
-
const integrationDir = path.dirname(file);
|
|
116
|
-
if (Array.isArray(integration.codemods)) {
|
|
117
|
-
for (const codemod of integration.codemods) {
|
|
118
|
-
if (typeof codemod.transform === 'string') {
|
|
119
|
-
const transformPath = path.resolve(integrationDir, codemod.transform);
|
|
120
|
-
const transformMod = await import(pathToFileURL(transformPath).href);
|
|
121
|
-
codemod.transform =
|
|
122
|
-
transformMod.default ?? transformMod.transform ?? transformMod;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
integrations.push({
|
|
127
|
-
...integration,
|
|
128
|
-
__file: file,
|
|
129
|
-
__dir: integrationDir,
|
|
130
|
-
__spec: spec,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
return integrations;
|
|
134
|
-
}
|
|
102
|
+
const log = silent ? {info() {}, warn() {}, success() {}, error() {}} : p.log;
|
|
103
|
+
const {packageDir, files, apply} = context;
|
|
135
104
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const entryTo = entry.to ?? to;
|
|
141
|
-
if (semverGte(from, entryTo) || semverGt(entryFrom, to)) continue;
|
|
142
|
-
if (!entry.name)
|
|
143
|
-
throw new Error(
|
|
144
|
-
`Integration ${integration.name ?? integration.__spec} has a codemod without a name.`,
|
|
145
|
-
);
|
|
146
|
-
if (!entry.transform)
|
|
105
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
106
|
+
const hook = hooks[i];
|
|
107
|
+
const label = hook.name ?? `postCodemod[${i}]`;
|
|
108
|
+
if (typeof hook.buildCommand !== 'function') {
|
|
147
109
|
throw new Error(
|
|
148
|
-
`
|
|
110
|
+
`Post-codemod hook ${label} is missing a buildCommand function.`,
|
|
149
111
|
);
|
|
150
|
-
|
|
151
|
-
typeof entry.transform === 'function' ? entry.transform : null;
|
|
152
|
-
if (!directTransform)
|
|
153
|
-
throw new Error(
|
|
154
|
-
`Integration codemod ${entry.name} did not resolve to a function.`,
|
|
155
|
-
);
|
|
156
|
-
transforms.push({
|
|
157
|
-
name: entry.name,
|
|
158
|
-
meta: {
|
|
159
|
-
title:
|
|
160
|
-
entry.title ??
|
|
161
|
-
`${integration.name ?? integration.__spec}: ${entry.name}`,
|
|
162
|
-
description: entry.description ?? '',
|
|
163
|
-
pr: entry.pr,
|
|
164
|
-
fileExtensions: entry.fileExtensions,
|
|
165
|
-
},
|
|
166
|
-
optional: !!entry.optional,
|
|
167
|
-
transform: directTransform,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
return transforms.length ? [{version: to, transforms}] : [];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function uniqueFiles(files) {
|
|
174
|
-
return [...new Set((files ?? []).filter(Boolean))];
|
|
175
|
-
}
|
|
112
|
+
}
|
|
176
113
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
114
|
+
const cmd = await hook.buildCommand({packageDir, files});
|
|
115
|
+
if (!cmd) {
|
|
116
|
+
log.info(`Post-codemod hook ${label} produced no command; skipping.`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
182
119
|
|
|
183
|
-
|
|
120
|
+
if (!apply) {
|
|
121
|
+
const preview = [cmd.command, ...(cmd.args ?? [])].join(' ');
|
|
122
|
+
log.info(`Post-codemod hook ${label} (dry run): ${preview}`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
184
125
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
timeout: options.timeoutMs ?? 300_000,
|
|
126
|
+
await execFileAsync(cmd.command, cmd.args ?? [], {
|
|
127
|
+
cwd: cmd.options?.cwd ?? packageDir,
|
|
128
|
+
timeout: cmd.options?.timeout ?? 300_000,
|
|
189
129
|
stdio: 'pipe',
|
|
190
130
|
encoding: 'utf-8',
|
|
191
|
-
|
|
131
|
+
...cmd.options,
|
|
132
|
+
env: {...process.env, ...(cmd.options?.env ?? {})},
|
|
192
133
|
});
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const ctx = {...context, run};
|
|
196
|
-
for (const {integration, hook} of hooks) {
|
|
197
|
-
const label = `${integration.name ?? integration.__spec}:${hook.name ?? 'postCodemod'}`;
|
|
198
|
-
try {
|
|
199
|
-
if (typeof hook.run === 'function') {
|
|
200
|
-
await hook.run(ctx);
|
|
201
|
-
} else if (typeof hook.command === 'function') {
|
|
202
|
-
const cmd = await hook.command(ctx);
|
|
203
|
-
if (cmd) {
|
|
204
|
-
await run(cmd.command, cmd.args ?? [], {
|
|
205
|
-
cwd: cmd.cwd,
|
|
206
|
-
timeoutMs: cmd.timeoutMs,
|
|
207
|
-
env: cmd.env,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
log.warn(
|
|
212
|
-
`Integration hook ${label} has no run() or command() function; skipping.`,
|
|
213
|
-
);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
log.success(`Post-codemod hook ${label} completed.`);
|
|
217
|
-
} catch (err) {
|
|
218
|
-
log.warn(`Post-codemod hook ${label} failed: ${err.message}`);
|
|
219
|
-
}
|
|
134
|
+
log.success(`Post-codemod hook ${label} completed.`);
|
|
220
135
|
}
|
|
221
136
|
}
|
|
222
137
|
|
|
@@ -238,6 +153,10 @@ export function registerUpgrade(program) {
|
|
|
238
153
|
false,
|
|
239
154
|
)
|
|
240
155
|
.option('--codemod <name>', 'Run a specific transform only')
|
|
156
|
+
.option(
|
|
157
|
+
'--skip-codemod <name...>',
|
|
158
|
+
'Exclude named codemods (repeatable). Re-run past a failed codemod by skipping it.',
|
|
159
|
+
)
|
|
241
160
|
.option(
|
|
242
161
|
'--integration <package-or-file>',
|
|
243
162
|
'Explicit integration package name or integration file path (repeatable)',
|
|
@@ -335,15 +254,170 @@ export function registerUpgrade(program) {
|
|
|
335
254
|
);
|
|
336
255
|
}
|
|
337
256
|
|
|
257
|
+
// ───────────────────────────────────────────────────────────────────
|
|
258
|
+
// PIPELINE ORDERING
|
|
259
|
+
//
|
|
260
|
+
// CORE codemods run BEFORE the consumer's config is loaded. `Project.load`
|
|
261
|
+
// STRICT-validates astryx.config.* and THROWS on unknown keys — but a core
|
|
262
|
+
// CONFIG codemod (e.g. v0.1.3 migrate-layout-components, signalled by
|
|
263
|
+
// `meta.codemodType === 'config'`) is precisely what repairs an otherwise
|
|
264
|
+
// -invalid config. Loading first created a chicken-and-egg: the config was
|
|
265
|
+
// rejected before the codemod that would fix it ever ran. Core codemods
|
|
266
|
+
// read files directly (config codemods via runConfigCodemod read
|
|
267
|
+
// astryx.config.*; code codemods scan --path), so they do NOT need the
|
|
268
|
+
// loaded config. We run them here, then load config, then sequence the
|
|
269
|
+
// integration codemods (which DO require a valid loaded config).
|
|
270
|
+
// ───────────────────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
if (!options.force && semverGte(currentVersion, targetVersion)) {
|
|
273
|
+
if (json) {
|
|
274
|
+
return jsonOut('upgrade.status', {
|
|
275
|
+
status: 'up_to_date',
|
|
276
|
+
from: currentVersion,
|
|
277
|
+
to: targetVersion,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
p.log.success('Already up to date — no codemods to run.');
|
|
281
|
+
p.log.info('Use --force to run codemods anyway.');
|
|
282
|
+
p.outro('Done');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Resolve CORE transforms from the registry. These do not need the loaded
|
|
287
|
+
// config. Integration codemods are discovered later, AFTER the config
|
|
288
|
+
// loads successfully (they require a valid config to resolve).
|
|
289
|
+
const versionManifests = [
|
|
290
|
+
...(await getTransformsBetween(currentVersion, targetVersion)),
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
// Does the selected core set include >=1 CONFIG codemod? A config codemod
|
|
294
|
+
// is the established convention `meta.codemodType === 'config'` (see
|
|
295
|
+
// `toUnifiedEntry` in runner.mjs). This drives the graceful dry-run catch
|
|
296
|
+
// around `Project.load` below: a fixable config error is only "expected"
|
|
297
|
+
// when a pending core config codemod would repair it.
|
|
298
|
+
const coreConfigCodemodNames = [];
|
|
299
|
+
for (const {transforms} of versionManifests) {
|
|
300
|
+
for (const t of transforms) {
|
|
301
|
+
if (options.codemod && t.name !== options.codemod) continue;
|
|
302
|
+
if (t.meta?.codemodType === 'config') {
|
|
303
|
+
coreConfigCodemodNames.push(t.name);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const hasCoreConfigCodemod = coreConfigCodemodNames.length > 0;
|
|
308
|
+
|
|
309
|
+
// Codemods explicitly excluded via --skip-codemod, matched by the same
|
|
310
|
+
// identifier the run loop uses (core transform `t.name`, integration
|
|
311
|
+
// codemod `c.id`). Lets a user re-run past a codemod that failed at
|
|
312
|
+
// execution time.
|
|
313
|
+
const skipCodemods = new Set(options.skipCodemod ?? []);
|
|
314
|
+
|
|
315
|
+
// Count CORE transforms (optional codemods only count when explicitly
|
|
316
|
+
// requested). Integration counts are added after discovery below.
|
|
317
|
+
let totalTransforms = 0;
|
|
318
|
+
let totalOptional = 0;
|
|
319
|
+
for (const {transforms} of versionManifests) {
|
|
320
|
+
for (const t of transforms) {
|
|
321
|
+
if (options.codemod && t.name !== options.codemod) continue;
|
|
322
|
+
if (skipCodemods.has(t.name)) continue;
|
|
323
|
+
if (t.optional && !options.codemod) {
|
|
324
|
+
totalOptional++;
|
|
325
|
+
} else {
|
|
326
|
+
totalTransforms++;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Ensure jscodeshift is available before running any codemod.
|
|
332
|
+
const ready = await ensureJscodeshift({
|
|
333
|
+
installDeps: options.installDeps,
|
|
334
|
+
silent: json,
|
|
335
|
+
});
|
|
336
|
+
if (!ready) {
|
|
337
|
+
if (json)
|
|
338
|
+
return jsonError(
|
|
339
|
+
'jscodeshift is required but could not be installed.',
|
|
340
|
+
undefined,
|
|
341
|
+
ERROR_CODES.ERR_DEP_MISSING,
|
|
342
|
+
);
|
|
343
|
+
p.outro('Aborted');
|
|
344
|
+
process.exitCode = 1;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// STEP 3 — Run CORE codemods FIRST (before loading config). In --apply,
|
|
349
|
+
// core config codemods WRITE the repaired config to disk; in dry-run they
|
|
350
|
+
// only PREVIEW. This is what makes the v0.1.3 config codemod reachable on
|
|
351
|
+
// a config that the strict loader would otherwise reject.
|
|
352
|
+
const codemodResult = await runCodemods(versionManifests, {
|
|
353
|
+
apply: options.apply,
|
|
354
|
+
path: options.path,
|
|
355
|
+
codemod: options.codemod,
|
|
356
|
+
skipCodemods,
|
|
357
|
+
silent: json,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// STEP 4 — Load the consumer's config (STRICT validation; unchanged). On
|
|
361
|
+
// --apply this now sees the repaired config the core codemod just wrote.
|
|
362
|
+
// We need `hooks.postCodemod` and the configured integration specs from
|
|
363
|
+
// here. Wrap in a graceful dry-run catch (see below).
|
|
364
|
+
// Assigned inside the try below; every catch branch returns, so these
|
|
365
|
+
// are always set before any later read.
|
|
338
366
|
let integrations;
|
|
367
|
+
let postCodemodHooks;
|
|
368
|
+
let integrationVersionGroups;
|
|
339
369
|
try {
|
|
340
|
-
const
|
|
370
|
+
const project = await Project.load(process.cwd());
|
|
371
|
+
postCodemodHooks = project.config.hooks?.postCodemod ?? [];
|
|
341
372
|
const integrationSpecs = uniqueFiles([
|
|
342
|
-
...(
|
|
373
|
+
...(project.integrations ?? []),
|
|
343
374
|
...(options.integration ?? []),
|
|
344
375
|
]);
|
|
345
376
|
integrations = await loadIntegrations(integrationSpecs);
|
|
346
377
|
} catch (err) {
|
|
378
|
+
// GRACEFUL DRY-RUN CATCH. A config that fails strict validation is the
|
|
379
|
+
// EXPECTED, fixable case ONLY when we are in dry-run AND a pending core
|
|
380
|
+
// config codemod just PREVIEWED a change to the config — i.e. the very
|
|
381
|
+
// codemod that would repair it. (Merely having a config codemod in the
|
|
382
|
+
// range is not enough: it may be a no-op on this config, in which case
|
|
383
|
+
// the validation error is genuine and we must abort. A config codemod
|
|
384
|
+
// that THREW reports zero would-change files, so it also fails this
|
|
385
|
+
// gate and aborts below — preserving the strictness contract.) This is
|
|
386
|
+
// the reason this PR reorders the pipeline.
|
|
387
|
+
const codemodWouldFixConfig =
|
|
388
|
+
hasCoreConfigCodemod && (codemodResult?.totalFilesChanged ?? 0) > 0;
|
|
389
|
+
if (!options.apply && codemodWouldFixConfig) {
|
|
390
|
+
const codemodFlags = coreConfigCodemodNames
|
|
391
|
+
.map(name => `--codemod ${name}`)
|
|
392
|
+
.join(' ');
|
|
393
|
+
const suggestedCommand = `astryx upgrade --from ${currentVersion} ${codemodFlags} --apply`;
|
|
394
|
+
const guidance =
|
|
395
|
+
'Your astryx.config currently fails strict validation, but a pending ' +
|
|
396
|
+
'config codemod would repair it. This dry run previewed the fix without ' +
|
|
397
|
+
'writing. Re-run with --apply to apply it, or run just the config codemod(s) ' +
|
|
398
|
+
'now:';
|
|
399
|
+
if (json) {
|
|
400
|
+
return jsonOut('upgrade.status', {
|
|
401
|
+
status: 'config_fixable',
|
|
402
|
+
from: currentVersion,
|
|
403
|
+
to: targetVersion,
|
|
404
|
+
configError: err.message,
|
|
405
|
+
configCodemods: coreConfigCodemodNames,
|
|
406
|
+
suggestedCommand,
|
|
407
|
+
message: guidance,
|
|
408
|
+
note: 'Integrations are skipped in this preview; they will be processed on the --apply run.',
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
p.log.warn(guidance);
|
|
412
|
+
p.log.info(` ${suggestedCommand}`);
|
|
413
|
+
p.log.info(
|
|
414
|
+
'Integrations are skipped in this preview; they will be processed on the --apply run.',
|
|
415
|
+
);
|
|
416
|
+
p.outro('Dry run complete');
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
// Genuine config error (apply mode, OR dry-run with no pending core
|
|
420
|
+
// config codemod that would fix it): abort as before.
|
|
347
421
|
if (json)
|
|
348
422
|
return jsonError(
|
|
349
423
|
err.message,
|
|
@@ -355,39 +429,71 @@ export function registerUpgrade(program) {
|
|
|
355
429
|
process.exitCode = 1;
|
|
356
430
|
return;
|
|
357
431
|
}
|
|
432
|
+
|
|
358
433
|
if (!json && integrations.length > 0) {
|
|
359
434
|
p.log.info(
|
|
360
435
|
`Integrations: ${integrations.map(i => i.name ?? i.__spec).join(', ')}`,
|
|
361
436
|
);
|
|
362
437
|
}
|
|
363
438
|
|
|
364
|
-
if
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
439
|
+
// Non-blocking nudge: if any configured integration has validation
|
|
440
|
+
// issues, print one compact line to stderr pointing at
|
|
441
|
+
// validate-integration. Best-effort; suppressed in --json mode. This
|
|
442
|
+
// depends on integrations being loaded, so it lives after the successful
|
|
443
|
+
// config load (it is skipped on the graceful dry-run path above, where
|
|
444
|
+
// integrations were never loaded).
|
|
445
|
+
try {
|
|
446
|
+
await warnOnIntegrationIssues(integrations, {json});
|
|
447
|
+
} catch {
|
|
448
|
+
// Never let the nudge break the upgrade.
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// STEP 5 — Discover + run INTEGRATION codemods (only reached on a
|
|
452
|
+
// successful config load). A broken integration (bad export, invalid
|
|
453
|
+
// schema, duplicate id, missing root) is a DEFINITION error: skip that
|
|
454
|
+
// integration's codemods and warn (via the nudge above), rather than
|
|
455
|
+
// hard-failing the upgrade. An EXECUTION-time failure (a transform
|
|
456
|
+
// throwing) is handled later by the codemod-error gate, which still
|
|
457
|
+
// aborts the upgrade.
|
|
458
|
+
const integrationCodemodsByVersion = new Map();
|
|
459
|
+
for (const integration of integrations) {
|
|
460
|
+
if (!integration?.codemods) continue;
|
|
461
|
+
try {
|
|
462
|
+
const byVersion = await discoverIntegrationCodemods([integration]);
|
|
463
|
+
for (const [version, list] of byVersion) {
|
|
464
|
+
const existing = integrationCodemodsByVersion.get(version);
|
|
465
|
+
if (existing) existing.push(...list);
|
|
466
|
+
else integrationCodemodsByVersion.set(version, [...list]);
|
|
467
|
+
}
|
|
468
|
+
} catch {
|
|
469
|
+
// Skip this integration's codemods; the validate-integration nudge
|
|
470
|
+
// above surfaces the underlying issue. Best-effort, non-blocking.
|
|
371
471
|
}
|
|
372
|
-
p.log.success('Already up to date — no codemods to run.');
|
|
373
|
-
p.log.info('Use --force to run codemods anyway.');
|
|
374
|
-
p.outro('Done');
|
|
375
|
-
return;
|
|
376
472
|
}
|
|
473
|
+
integrationVersionGroups = selectIntegrationCodemods(
|
|
474
|
+
integrationCodemodsByVersion,
|
|
475
|
+
currentVersion,
|
|
476
|
+
targetVersion,
|
|
477
|
+
);
|
|
478
|
+
const hasIntegrationCodemods = integrationVersionGroups.some(
|
|
479
|
+
g => g.codemods.length > 0,
|
|
480
|
+
);
|
|
377
481
|
|
|
378
|
-
//
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
482
|
+
// Add integration transforms to the run counts.
|
|
483
|
+
for (const {codemods} of integrationVersionGroups) {
|
|
484
|
+
for (const c of codemods) {
|
|
485
|
+
if (options.codemod && c.id !== options.codemod) continue;
|
|
486
|
+
if (skipCodemods.has(c.id)) continue;
|
|
487
|
+
if (c.codemod.isOptional && !options.codemod) {
|
|
488
|
+
totalOptional++;
|
|
489
|
+
} else {
|
|
490
|
+
totalTransforms++;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
389
494
|
|
|
390
|
-
|
|
495
|
+
// No codemods at all for this range (neither core nor integration).
|
|
496
|
+
if (versionManifests.length === 0 && !hasIntegrationCodemods) {
|
|
391
497
|
if (json) {
|
|
392
498
|
return jsonOut('upgrade.status', {
|
|
393
499
|
status: 'no_codemods',
|
|
@@ -400,20 +506,7 @@ export function registerUpgrade(program) {
|
|
|
400
506
|
return;
|
|
401
507
|
}
|
|
402
508
|
|
|
403
|
-
//
|
|
404
|
-
let totalTransforms = 0;
|
|
405
|
-
let totalOptional = 0;
|
|
406
|
-
for (const {transforms} of versionManifests) {
|
|
407
|
-
for (const t of transforms) {
|
|
408
|
-
if (options.codemod && t.name !== options.codemod) continue;
|
|
409
|
-
if (t.optional && !options.codemod) {
|
|
410
|
-
totalOptional++;
|
|
411
|
-
} else {
|
|
412
|
-
totalTransforms++;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
509
|
+
// A named `--codemod` that matched nothing (across core + integration).
|
|
417
510
|
if (totalTransforms === 0 && totalOptional === 0) {
|
|
418
511
|
const msg = `Codemod "${options.codemod}" not found. Use --list to see available codemods.`;
|
|
419
512
|
if (json)
|
|
@@ -442,54 +535,73 @@ export function registerUpgrade(program) {
|
|
|
442
535
|
agentDocsRefreshed: false,
|
|
443
536
|
};
|
|
444
537
|
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
538
|
+
// Run file-based integration codemods alongside the core registry
|
|
539
|
+
// codemods (config codemods first, then code codemods), ordered by
|
|
540
|
+
// version. Their results are merged into the receipt below.
|
|
541
|
+
let integrationResult = null;
|
|
542
|
+
if (hasIntegrationCodemods) {
|
|
543
|
+
if (!json) p.log.step('Applying integration codemods...');
|
|
544
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
545
|
+
integrationResult = runIntegrationCodemods(integrationVersionGroups, {
|
|
546
|
+
apply: options.apply,
|
|
547
|
+
path: options.path,
|
|
548
|
+
codemod: options.codemod,
|
|
549
|
+
skipCodemods,
|
|
550
|
+
jscodeshift,
|
|
551
|
+
silent: json,
|
|
552
|
+
});
|
|
460
553
|
}
|
|
461
554
|
|
|
462
|
-
// Run codemods
|
|
463
|
-
const codemodResult = await runCodemods(versionManifests, {
|
|
464
|
-
apply: options.apply,
|
|
465
|
-
path: options.path,
|
|
466
|
-
codemod: options.codemod,
|
|
467
|
-
silent: json,
|
|
468
|
-
});
|
|
469
555
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
);
|
|
475
|
-
|
|
556
|
+
// Merge core + integration codemod results into a single accounting so
|
|
557
|
+
// hooks, receipts, and error gating see both.
|
|
558
|
+
const mergedFilesChanged =
|
|
559
|
+
(codemodResult?.totalFilesChanged ?? 0) +
|
|
560
|
+
(integrationResult?.totalFilesChanged ?? 0);
|
|
561
|
+
const mergedTransformsApplied =
|
|
562
|
+
(codemodResult?.totalTransformsApplied ?? 0) +
|
|
563
|
+
(integrationResult?.totalTransformsApplied ?? 0);
|
|
564
|
+
const mergedWrittenFiles = [
|
|
565
|
+
...(codemodResult?.writtenFiles ?? []),
|
|
566
|
+
...(integrationResult?.writtenFiles ?? []),
|
|
567
|
+
];
|
|
568
|
+
const mergedErrors = [
|
|
569
|
+
...(codemodResult?.errors ?? []),
|
|
570
|
+
...(integrationResult?.errors ?? []),
|
|
571
|
+
];
|
|
572
|
+
|
|
573
|
+
// Post-codemod hooks come from the app config (config.hooks.postCodemod).
|
|
574
|
+
// They run only when codemods actually changed files. In apply mode the
|
|
575
|
+
// commands execute (nonzero exit fails the upgrade); in dry-run mode we
|
|
576
|
+
// only preview the resolved command (a buildCommand throw still fails).
|
|
577
|
+
const changedFileCount = mergedFilesChanged;
|
|
578
|
+
if (postCodemodHooks.length > 0 && changedFileCount > 0) {
|
|
579
|
+
const absoluteChangedFiles = uniqueFiles(mergedWrittenFiles);
|
|
580
|
+
const files = absoluteChangedFiles.map(file =>
|
|
476
581
|
path.relative(process.cwd(), file),
|
|
477
582
|
);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
583
|
+
try {
|
|
584
|
+
await runPostCodemodHooks(
|
|
585
|
+
postCodemodHooks,
|
|
586
|
+
{
|
|
587
|
+
packageDir: process.cwd(),
|
|
588
|
+
files,
|
|
589
|
+
apply: options.apply,
|
|
590
|
+
},
|
|
591
|
+
json,
|
|
592
|
+
);
|
|
593
|
+
} catch (err) {
|
|
594
|
+
if (json)
|
|
595
|
+
return jsonError(
|
|
596
|
+
`Post-codemod hook failed: ${err.message}`,
|
|
597
|
+
{receipt},
|
|
598
|
+
ERROR_CODES.ERR_CODEMOD_FAILED,
|
|
599
|
+
);
|
|
600
|
+
p.log.error(`Post-codemod hook failed: ${err.message}`);
|
|
601
|
+
p.outro('Upgrade failed');
|
|
602
|
+
process.exitCode = 1;
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
493
605
|
}
|
|
494
606
|
|
|
495
607
|
// Refresh agent docs if any exist (AGENTS.md, CLAUDE.md, .claude/CLAUDE.md, etc.)
|
|
@@ -507,17 +619,15 @@ export function registerUpgrade(program) {
|
|
|
507
619
|
} catch {
|
|
508
620
|
if (!json) {
|
|
509
621
|
p.log.warn(
|
|
510
|
-
`Could not update agent docs. Run \`${getRunPrefix()} astryx
|
|
622
|
+
`Could not update agent docs. Run \`${getRunPrefix()} astryx init --features agents\` to update manually.`,
|
|
511
623
|
);
|
|
512
624
|
}
|
|
513
625
|
}
|
|
514
626
|
}
|
|
515
627
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
receipt.errors = codemodResult.errors ?? [];
|
|
520
|
-
}
|
|
628
|
+
receipt.filesChanged = mergedFilesChanged;
|
|
629
|
+
receipt.transformsApplied = mergedTransformsApplied;
|
|
630
|
+
receipt.errors = mergedErrors;
|
|
521
631
|
|
|
522
632
|
if (receipt.errors?.length > 0) {
|
|
523
633
|
const msg = `Upgrade completed with ${receipt.errors.length} codemod error${receipt.errors.length === 1 ? '' : 's'}.`;
|