@astryxdesign/cli 0.6.3-canary.d0d49a4 → 0.6.3-canary.dea6813
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -18
- package/api/build/build.doc.mjs +3 -3
- package/api/build/build.test.mjs +14 -3
- package/api/build/build.type.d.mts +35 -1
- package/api/build/build.type.mjs +24 -3
- package/api/build/help/help.d.mts +8 -5
- package/api/build/help/help.mjs +56 -6
- package/api/component/component.doc.mjs +11 -2
- package/api/component/component.mjs +29 -0
- package/api/component/component.test.mjs +38 -0
- package/api/component/component.type.d.mts +11 -1
- package/api/component/component.type.mjs +8 -1
- package/api/discover/discover.doc.mjs +1 -1
- package/api/gap-report/gap-report.doc.mjs +7 -4
- package/api/init/init.doc.mjs +4 -0
- package/api/init/init.test.mjs +41 -1
- package/api/init/remove/remove.mjs +1 -1
- package/api/init/run/run.mjs +20 -10
- package/api/integration/add-contribution.component-names.test.mjs +120 -0
- package/api/integration/add-contribution.mjs +11 -0
- package/api/integration/add-contribution.test.mjs +80 -0
- package/api/integration/integrationAdd.doc.mjs +7 -4
- package/api/integration/integrationAddComponent.doc.mjs +1 -1
- package/api/integration/validate-integration.mjs +2 -0
- package/api/integration/validate-integration.test.mjs +91 -0
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/json/envelope-types.test.mjs +76 -0
- package/api/json/parseResponse.doc.mjs +2 -2
- package/api/layout/expand/expand.mjs +7 -5
- package/api/layout/expand/expand.path-safety.test.mjs +53 -0
- package/api/layout/grammar/grammar.mjs +2 -1
- package/api/layout/layoutExpand.doc.mjs +1 -1
- package/api/search/search-return-type.test.mjs +54 -0
- package/api/search/search.d.mts +2 -9
- package/api/search/search.doc.mjs +5 -1
- package/api/search/search.mjs +10 -3
- package/api/search/search.test.mjs +34 -0
- package/api/swizzle/copy/copy.mjs +28 -11
- package/api/swizzle/swizzle.doc.mjs +1 -1
- package/api/template/copy/copy.mjs +17 -23
- package/api/template/copy/copy.test.mjs +17 -0
- package/api/template/template.doc.mjs +2 -1
- package/api/theme/add/add.binary.test.mjs +91 -0
- package/api/theme/add/add.mjs +20 -3
- package/api/theme/add/add.staging.test.mjs +66 -0
- package/api/theme/build/build.mjs +114 -37
- package/api/theme/build/build.public-component-vars.test.mjs +1 -1
- package/api/theme/build/build.receipt-doc.test.mjs +111 -0
- package/api/theme/build/font-warning.mjs +3 -3
- package/api/theme/build/font-warning.test.mjs +5 -2
- package/api/theme/palette/generate/generate.mjs +7 -2
- package/api/theme/palette/generate/generate.test.mjs +96 -0
- package/api/theme/palette/generate/generator.mjs +8 -1
- package/api/theme/palette/generate/generator.test.mjs +10 -0
- package/api/theme/template/template.mjs +11 -2
- package/api/theme/template/template.test.mjs +15 -0
- package/api/theme/themeBuild.doc.mjs +7 -4
- package/api/theme/themeTemplate.doc.mjs +4 -1
- package/api/upgrade/_adapter.mjs +27 -4
- package/api/upgrade/list/list.mjs +2 -1
- package/api/upgrade/list/list.test.mjs +73 -0
- package/api/upgrade/status/status.mjs +2 -2
- package/api/upgrade/upgrade.doc.mjs +2 -1
- package/api/upgrade/upgrade.type.d.mts +4 -0
- package/api/upgrade/upgrade.type.mjs +1 -0
- package/assets/codemods/term-log.mjs +32 -8
- package/assets/codemods/term-log.test.mjs +19 -1
- package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
- package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
- package/assets/templates/pages/table-tree/page.tsx +1704 -0
- package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
- package/authoring/config/config.doc.mjs +1 -1
- package/authoring/config/debug-composition.test.mjs +92 -0
- package/authoring/config/type.ts +15 -3
- package/clients/cli/command-load-failure.test.mjs +83 -0
- package/clients/cli/commands/blog.mjs +23 -8
- package/clients/cli/commands/blog.test.mjs +42 -1
- package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
- package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
- package/clients/cli/commands/build-theme.mjs +5 -5
- package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
- package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
- package/clients/cli/commands/build.doc.mjs +4 -1
- package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
- package/clients/cli/commands/build.mjs +32 -47
- package/clients/cli/commands/build.playbook.test.mjs +75 -0
- package/clients/cli/commands/build.text-fields.test.mjs +40 -0
- package/clients/cli/commands/component/index.mjs +2 -7
- package/clients/cli/commands/component-package.test.mjs +18 -0
- package/clients/cli/commands/component-resolution.test.mjs +21 -0
- package/clients/cli/commands/component.test.mjs +19 -0
- package/clients/cli/commands/detail-levels.test.mjs +2 -2
- package/clients/cli/commands/discover.broken-integration.test.mjs +48 -0
- package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
- package/clients/cli/commands/discover.doc.mjs +4 -2
- package/clients/cli/commands/discover.mjs +3 -3
- package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
- package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
- package/clients/cli/commands/doctor.mjs +6 -16
- package/clients/cli/commands/doctor.test.mjs +42 -0
- package/clients/cli/commands/gap-report.doc.mjs +16 -5
- package/clients/cli/commands/gap-report.test.mjs +72 -0
- package/clients/cli/commands/hook/index.mjs +7 -17
- package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
- package/clients/cli/commands/init.doc.mjs +19 -8
- package/clients/cli/commands/init.flag-help.test.mjs +153 -0
- package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
- package/clients/cli/commands/integration-add.doc.mjs +21 -5
- package/clients/cli/commands/interactive-guard.test.mjs +101 -24
- package/clients/cli/commands/json-contract.test.mjs +33 -0
- package/clients/cli/commands/layout-check.doc.mjs +14 -3
- package/clients/cli/commands/layout-expand.doc.mjs +21 -4
- package/clients/cli/commands/layout.doc.mjs +2 -2
- package/clients/cli/commands/layout.mjs +21 -9
- package/clients/cli/commands/layout.path-help.test.mjs +33 -0
- package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
- package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
- package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
- package/clients/cli/commands/search.doc.mjs +6 -3
- package/clients/cli/commands/search.mjs +17 -7
- package/clients/cli/commands/search.test.mjs +75 -0
- package/clients/cli/commands/swizzle.doc.mjs +2 -1
- package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
- package/clients/cli/commands/template.doc.mjs +28 -6
- package/clients/cli/commands/template.flag-help.test.mjs +117 -0
- package/clients/cli/commands/template.path-help.test.mjs +40 -0
- package/clients/cli/commands/theme-add.doc.mjs +2 -1
- package/clients/cli/commands/theme-build.doc.mjs +7 -6
- package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
- package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
- package/clients/cli/commands/theme-template.doc.mjs +1 -1
- package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
- package/clients/cli/commands/upgrade.doc.mjs +17 -6
- package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
- package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
- package/clients/cli/index.mjs +13 -30
- package/clients/cli/latest-version-env.test.mjs +50 -0
- package/clients/cli/lib/cli-error.test.mjs +7 -0
- package/clients/cli/lib/component-format.mjs +9 -9
- package/clients/cli/lib/component-format.test.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +32 -6
- package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
- package/clients/cli/lib/exit-codes.test.mjs +97 -0
- package/clients/cli/lib/hook-format.mjs +5 -5
- package/clients/cli/lib/json-shim.mjs +38 -2
- package/clients/cli/lib/json-shim.test.mjs +83 -0
- package/clients/cli/lib/manifest.d.ts +2 -0
- package/clients/cli/lib/manifest.mjs +22 -0
- package/clients/cli/lib/manifest.test.mjs +17 -0
- package/foundation/agent-docs/agent-docs.d.mts +4 -0
- package/foundation/agent-docs/agent-docs.mjs +69 -3
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
- package/foundation/config/integration-debug.test.mjs +28 -3
- package/foundation/config/project.mjs +123 -9
- package/foundation/config/project.test.mjs +126 -0
- package/foundation/discovery/template-adapter.d.mts +14 -5
- package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
- package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
- package/foundation/discovery/template-adapter.mjs +292 -25
- package/foundation/discovery/theming-targets.test.mjs +4 -0
- package/foundation/fs/module-loader.d.mts +1 -0
- package/foundation/fs/module-loader.mjs +50 -1
- package/foundation/fs/module-loader.stdout.test.mjs +332 -0
- package/foundation/fs/path-safety.d.mts +3 -2
- package/foundation/fs/path-safety.mjs +49 -19
- package/foundation/fs/path-safety.test.mjs +50 -0
- package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
- package/foundation/integrations/autolink.mjs +6 -1
- package/foundation/response/base.d.ts +8 -4
- package/foundation/response/error-codes.doc.mjs +1 -2
- package/foundation/response/error-codes.mjs +1 -1
- package/foundation/response/error-codes.test.mjs +83 -0
- package/foundation/response/json-contract.test.mjs +11 -0
- package/foundation/response/json.d.mts +4 -2
- package/foundation/response/json.mjs +8 -10
- package/foundation/response/response-types.doc.mjs +3 -3
- package/foundation/xle/expand.d.mts +2 -0
- package/foundation/xle/expand.mjs +3 -2
- package/foundation/xle/expand.test.mjs +54 -0
- package/package.json +9 -9
- package/clients/cli/lib/update-check.mjs +0 -83
- package/clients/cli/lib/update-check.test.mjs +0 -137
- package/clients/cli/update-hint-commands.test.mjs +0 -54
|
@@ -73,7 +73,7 @@ export const doc = {
|
|
|
73
73
|
{
|
|
74
74
|
type: 'theme.build',
|
|
75
75
|
description:
|
|
76
|
-
'Build receipt: theme name
|
|
76
|
+
'Build receipt {name, tokenCount, componentCount, sizeKB, outputs, warnings, notices}: the theme name; how many tokens (portable plus theme-local) and component targets it overrides; the CSS size in KB; the written outputs {css, js, dts, and variantsDts when custom prop values were augmented}; warnings, the defects the author should fix, including exact canonical replacements for deprecated component target keys and declarations the generator dropped; and notices, advisories about a correct theme, such as a font it names but does not load. Resolves to null instead when the theme produced no CSS (nothing to build).',
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
type: 'theme.build.check',
|
|
@@ -87,10 +87,13 @@ export const doc = {
|
|
|
87
87
|
code: 'ERR_THEME_LOAD',
|
|
88
88
|
when: 'the file cannot be loaded or parsed into a defineTheme() result',
|
|
89
89
|
},
|
|
90
|
-
{
|
|
90
|
+
{
|
|
91
|
+
code: 'ERR_THEME_INVALID',
|
|
92
|
+
when: 'the resolved theme is invalid, for example it has no name, or a custom Heading type has no standalone rule with a declaration the compiler can emit',
|
|
93
|
+
},
|
|
91
94
|
{
|
|
92
95
|
code: 'ERR_PATH_TRAVERSAL',
|
|
93
|
-
when: 'the theme name contains a path separator or traversal marker',
|
|
96
|
+
when: 'the theme name contains a path separator or traversal marker, or a relative `out` path resolves outside cwd (including through a symlink)',
|
|
94
97
|
},
|
|
95
98
|
{
|
|
96
99
|
code: 'ERR_CORE_NOT_FOUND',
|
|
@@ -102,7 +105,7 @@ export const doc = {
|
|
|
102
105
|
},
|
|
103
106
|
{
|
|
104
107
|
code: 'ERR_WRITE_FAILED',
|
|
105
|
-
when: 'writing the outputs fails (staged temp files are rolled back)',
|
|
108
|
+
when: 'creating the output directory or writing the outputs fails (staged temp files are rolled back)',
|
|
106
109
|
},
|
|
107
110
|
],
|
|
108
111
|
examples: [
|
|
@@ -51,7 +51,10 @@ export const doc = {
|
|
|
51
51
|
'Receipt: the path (relative to cwd), whether it was written, and the reason it was not. `exists` when a file was already there, which is a success, not a failure.',
|
|
52
52
|
},
|
|
53
53
|
],
|
|
54
|
-
throws: [
|
|
54
|
+
throws: [
|
|
55
|
+
{code: 'ERR_PATH_TRAVERSAL', when: 'the target path escapes cwd'},
|
|
56
|
+
{code: 'ERR_WRITE_FAILED', when: 'the template file cannot be written'},
|
|
57
|
+
],
|
|
55
58
|
examples: [
|
|
56
59
|
{label: 'Write it at the project root', code: 'themeTemplate();'},
|
|
57
60
|
{
|
package/api/upgrade/_adapter.mjs
CHANGED
|
@@ -104,6 +104,27 @@ export function uniqueFiles(files) {
|
|
|
104
104
|
];
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Run app code with its stdout writes sent to stderr. Stdout carries only the
|
|
109
|
+
* command's own output, so a hook that prints cannot corrupt `--json`.
|
|
110
|
+
* @template T
|
|
111
|
+
* @param {() => T | Promise<T>} fn
|
|
112
|
+
* @returns {Promise<T>}
|
|
113
|
+
*/
|
|
114
|
+
async function withStdoutOnStderr(fn) {
|
|
115
|
+
const realWrite = process.stdout.write;
|
|
116
|
+
process.stdout.write = /** @type {any} */ (
|
|
117
|
+
function (/** @type {any} */ chunk, /** @type {any[]} */ ...rest) {
|
|
118
|
+
return process.stderr.write(chunk, ...rest);
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
try {
|
|
122
|
+
return await fn();
|
|
123
|
+
} finally {
|
|
124
|
+
process.stdout.write = realWrite;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
107
128
|
/**
|
|
108
129
|
* Run the app config's post-codemod hooks (config.hooks.postCodemod).
|
|
109
130
|
* Dry-run PREVIEWS (buildCommand still called, so a throw fails); apply executes.
|
|
@@ -123,7 +144,9 @@ export async function runPostCodemodHooks(hooks, context) {
|
|
|
123
144
|
);
|
|
124
145
|
}
|
|
125
146
|
|
|
126
|
-
const cmd = await
|
|
147
|
+
const cmd = await withStdoutOnStderr(() =>
|
|
148
|
+
hook.buildCommand({packageDir, files}),
|
|
149
|
+
);
|
|
127
150
|
if (!cmd) {
|
|
128
151
|
logger.log(`Post-codemod hook ${label} produced no command; skipping.`);
|
|
129
152
|
continue;
|
|
@@ -147,7 +170,7 @@ export async function runPostCodemodHooks(hooks, context) {
|
|
|
147
170
|
env: {...process.env, ...(cmd.options?.env ?? {})},
|
|
148
171
|
}),
|
|
149
172
|
);
|
|
150
|
-
logger.log(
|
|
173
|
+
logger.log(`[ok] Post-codemod hook ${label} completed.`);
|
|
151
174
|
}
|
|
152
175
|
}
|
|
153
176
|
|
|
@@ -186,7 +209,7 @@ export async function prepareAgentDocsRefresh({
|
|
|
186
209
|
if (initial.status === 'missing') {
|
|
187
210
|
summary.action = 'nudge-init';
|
|
188
211
|
logger.warn(
|
|
189
|
-
`No Astryx agent-docs block found
|
|
212
|
+
`No Astryx agent-docs block found - AI agents have no component index. Run \`${formatCliCommand('astryx init --features agents')}\` to install it.`,
|
|
190
213
|
);
|
|
191
214
|
return {cwd, renderedBlock: null, summary};
|
|
192
215
|
}
|
|
@@ -240,7 +263,7 @@ export function applyAgentDocsRefresh(plan) {
|
|
|
240
263
|
summary.files = written;
|
|
241
264
|
summary.action = summary.refreshed ? 'refreshed' : 'error';
|
|
242
265
|
if (summary.refreshed) {
|
|
243
|
-
logger.log(
|
|
266
|
+
logger.log(`[ok] Agent docs refreshed -> ${written.join(', ')}`);
|
|
244
267
|
} else {
|
|
245
268
|
logger.warn(
|
|
246
269
|
`Agent docs look stale but couldn't be refreshed. Run \`${formatCliCommand('astryx init --features agents')}\` to reinstall the block.`,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import {collectAllCodemods} from '../_adapter.mjs';
|
|
12
12
|
import {logger} from '../../logger.mjs';
|
|
13
|
+
import {toAscii} from '../../../assets/codemods/term-log.mjs';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* List every available codemod (oldest→newest).
|
|
@@ -19,7 +20,7 @@ export async function list() {
|
|
|
19
20
|
const codemods = await collectAllCodemods();
|
|
20
21
|
logger.log('Available codemods:');
|
|
21
22
|
for (const {name, title, pr, optional} of codemods) {
|
|
22
|
-
logger.log(` ${name}
|
|
23
|
+
logger.log(` ${name} - ${toAscii(title)}${optional ? ' (optional)' : ''} (${pr})`);
|
|
23
24
|
}
|
|
24
25
|
logger.log('Done\n');
|
|
25
26
|
return {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Colocated tests for the upgrade.list leaf: every field an entry
|
|
5
|
+
* carries is declared on the canonical `UpgradeListEntry` type, and every
|
|
6
|
+
* declared field is present on every entry.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {describe, it, expect} from 'vitest';
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import {fileURLToPath} from 'node:url';
|
|
12
|
+
import ts from 'typescript';
|
|
13
|
+
import {list} from './list.mjs';
|
|
14
|
+
|
|
15
|
+
const TYPE_FILE = fileURLToPath(
|
|
16
|
+
new URL('../upgrade.type.mjs', import.meta.url),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Property names a JSDoc `@typedef {object}` declares, parsed from the file.
|
|
21
|
+
* @param {string} typedefName
|
|
22
|
+
* @returns {string[]}
|
|
23
|
+
*/
|
|
24
|
+
function declaredProperties(typedefName) {
|
|
25
|
+
const source = ts.createSourceFile(
|
|
26
|
+
TYPE_FILE,
|
|
27
|
+
fs.readFileSync(TYPE_FILE, 'utf8'),
|
|
28
|
+
ts.ScriptTarget.Latest,
|
|
29
|
+
true,
|
|
30
|
+
ts.ScriptKind.JS,
|
|
31
|
+
);
|
|
32
|
+
/** @type {string[]} */
|
|
33
|
+
const names = [];
|
|
34
|
+
for (const statement of source.statements) {
|
|
35
|
+
for (const doc of /** @type {any} */ (statement).jsDoc ?? []) {
|
|
36
|
+
for (const tag of doc.tags ?? []) {
|
|
37
|
+
if (
|
|
38
|
+
!ts.isJSDocTypedefTag(tag) ||
|
|
39
|
+
tag.name?.getText(source) !== typedefName
|
|
40
|
+
) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const literal = tag.typeExpression;
|
|
44
|
+
if (literal && ts.isJSDocTypeLiteral(literal)) {
|
|
45
|
+
for (const property of literal.jsDocPropertyTags ?? []) {
|
|
46
|
+
names.push(property.name.getText(source));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return names.sort();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('upgrade.list entries', () => {
|
|
56
|
+
it('carry exactly the fields UpgradeListEntry declares', async () => {
|
|
57
|
+
const declared = declaredProperties('UpgradeListEntry');
|
|
58
|
+
expect(declared.length).toBeGreaterThan(0);
|
|
59
|
+
|
|
60
|
+
const {type, data} = await list();
|
|
61
|
+
expect(type).toBe('upgrade.list');
|
|
62
|
+
expect(data.length).toBeGreaterThan(0);
|
|
63
|
+
for (const entry of data) {
|
|
64
|
+
expect(Object.keys(entry).sort()).toEqual(declared);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('marks optional codemods with a boolean', async () => {
|
|
69
|
+
const {data} = await list();
|
|
70
|
+
for (const entry of data) expect(typeof entry.optional).toBe('boolean');
|
|
71
|
+
expect(data.some(entry => entry.optional)).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -24,7 +24,7 @@ import {logger} from '../../logger.mjs';
|
|
|
24
24
|
* @returns {import('../upgrade.type.mjs').UpgradeStatusResponse}
|
|
25
25
|
*/
|
|
26
26
|
export function statusUpToDate({from, to, agentDocs, registryCompositions}) {
|
|
27
|
-
logger.log('
|
|
27
|
+
logger.log('[ok] Already up to date - no codemods to run.');
|
|
28
28
|
logger.log('Use --force to run codemods anyway.');
|
|
29
29
|
logger.log(registryCompositions?.ok === false ? 'Finished with unresolved registry items\n' : 'Done\n');
|
|
30
30
|
return {
|
|
@@ -45,7 +45,7 @@ export function statusUpToDate({from, to, agentDocs, registryCompositions}) {
|
|
|
45
45
|
* @returns {import('../upgrade.type.mjs').UpgradeStatusResponse}
|
|
46
46
|
*/
|
|
47
47
|
export function statusNoCodemods({from, to, agentDocs, registryCompositions}) {
|
|
48
|
-
logger.log('
|
|
48
|
+
logger.log('[ok] No codemods available for this version range.');
|
|
49
49
|
logger.log(registryCompositions?.ok === false ? 'Finished with unresolved registry items\n' : 'Done\n');
|
|
50
50
|
return {
|
|
51
51
|
type: 'upgrade.status',
|
|
@@ -80,7 +80,8 @@ export const doc = {
|
|
|
80
80
|
{
|
|
81
81
|
name: 'options.installDeps',
|
|
82
82
|
type: 'boolean',
|
|
83
|
-
description:
|
|
83
|
+
description:
|
|
84
|
+
'Install jscodeshift when it is missing; otherwise a missing jscodeshift throws ERR_DEP_MISSING.',
|
|
84
85
|
},
|
|
85
86
|
{
|
|
86
87
|
name: 'options.registry',
|
|
@@ -12,6 +12,10 @@ export type UpgradeListEntry = {
|
|
|
12
12
|
name: string;
|
|
13
13
|
title: string;
|
|
14
14
|
version: string;
|
|
15
|
+
/**
|
|
16
|
+
* True when the codemod runs only if named with `--codemod`.
|
|
17
|
+
*/
|
|
18
|
+
optional: boolean;
|
|
15
19
|
};
|
|
16
20
|
/**
|
|
17
21
|
* State of the managed agent-docs block (`<!-- ASTRYX:START --> … END -->`)
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* `p.log.step(...)`, `p.log.success(...)`, etc. All output routes through
|
|
12
12
|
* `humanLog`, the CLI's stdout-discipline primitive, which is a no-op in
|
|
13
13
|
* `--json` mode — so these human logs can never corrupt a JSON envelope.
|
|
14
|
+
* Every line is plain ASCII: level prefixes are ASCII and typographic glyphs
|
|
15
|
+
* in a message are transliterated before it is printed.
|
|
14
16
|
*
|
|
15
17
|
* The side-effecting API commands (upgrade/init/themeBuild) do NOT use this;
|
|
16
18
|
* they write through the shared `logger` (api/logger.mjs).
|
|
@@ -18,8 +20,30 @@
|
|
|
18
20
|
|
|
19
21
|
import {humanLog} from '../../foundation/response/json.mjs';
|
|
20
22
|
|
|
21
|
-
/** @
|
|
22
|
-
const
|
|
23
|
+
/** @type {Array<[RegExp, string]>} */
|
|
24
|
+
const ASCII_GLYPHS = [
|
|
25
|
+
[/[\u2014\u2013]/g, '-'],
|
|
26
|
+
[/\u2192/g, '->'],
|
|
27
|
+
[/\u2713/g, '[ok]'],
|
|
28
|
+
[/\u2717/g, '!!'],
|
|
29
|
+
[/\u26a0\ufe0f?/g, '!'],
|
|
30
|
+
[/\u2022/g, '-'],
|
|
31
|
+
[/\u2026/g, '...'],
|
|
32
|
+
[/[\u2018\u2019]/g, "'"],
|
|
33
|
+
[/[\u201c\u201d]/g, '"'],
|
|
34
|
+
[/\u00a0/g, ' '],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A message as plain ASCII: typographic glyphs become their ASCII spelling.
|
|
39
|
+
* @param {unknown} msg
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
export function toAscii(msg) {
|
|
43
|
+
let text = msg === undefined || msg === null ? '' : String(msg);
|
|
44
|
+
for (const [glyph, ascii] of ASCII_GLYPHS) text = text.replace(glyph, ascii);
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
23
47
|
|
|
24
48
|
/**
|
|
25
49
|
* Human-facing log surface (the small `log` API codemods use). All lines go to
|
|
@@ -28,15 +52,15 @@ const toStr = (msg) => (msg === undefined || msg === null ? '' : String(msg));
|
|
|
28
52
|
*/
|
|
29
53
|
export const log = {
|
|
30
54
|
/** @param {unknown} msg */
|
|
31
|
-
message: (msg) => humanLog(
|
|
55
|
+
message: (msg) => humanLog(toAscii(msg)),
|
|
32
56
|
/** @param {unknown} msg */
|
|
33
|
-
info: (msg) => humanLog(
|
|
57
|
+
info: (msg) => humanLog(toAscii(msg)),
|
|
34
58
|
/** @param {unknown} msg */
|
|
35
|
-
step: (msg) => humanLog(
|
|
59
|
+
step: (msg) => humanLog(toAscii(msg)),
|
|
36
60
|
/** @param {unknown} msg */
|
|
37
|
-
success: (msg) => humanLog(
|
|
61
|
+
success: (msg) => humanLog(`[ok] ${toAscii(msg)}`),
|
|
38
62
|
/** @param {unknown} msg */
|
|
39
|
-
warn: (msg) => humanLog(
|
|
63
|
+
warn: (msg) => humanLog(`! ${toAscii(msg)}`),
|
|
40
64
|
/** @param {unknown} msg */
|
|
41
|
-
error: (msg) => humanLog(
|
|
65
|
+
error: (msg) => humanLog(`!! ${toAscii(msg)}`),
|
|
42
66
|
};
|
|
@@ -51,7 +51,7 @@ describe('sanity: helpers are live in human mode', () => {
|
|
|
51
51
|
setJsonMode(false);
|
|
52
52
|
p.log.success('done');
|
|
53
53
|
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
54
|
-
expect(logSpy).toHaveBeenCalledWith('
|
|
54
|
+
expect(logSpy).toHaveBeenCalledWith('[ok] done');
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
it('every log helper emits exactly one stdout line in human mode', () => {
|
|
@@ -60,3 +60,21 @@ describe('sanity: helpers are live in human mode', () => {
|
|
|
60
60
|
expect(logSpy).toHaveBeenCalledTimes(6);
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
describe('ASCII output', () => {
|
|
65
|
+
it('prints ASCII level prefixes and transliterates glyphs in messages', () => {
|
|
66
|
+
setJsonMode(false);
|
|
67
|
+
p.log.success('src/a.tsx');
|
|
68
|
+
p.log.warn('stale \u2014 refresh');
|
|
69
|
+
p.log.error(' \u2717 src/b.tsx \u2014 rename \u2192 failed');
|
|
70
|
+
p.log.info(' \u2022 name \u2014 title\u2026');
|
|
71
|
+
const lines = logSpy.mock.calls.map(call => call.join(' '));
|
|
72
|
+
expect(lines).toEqual([
|
|
73
|
+
'[ok] src/a.tsx',
|
|
74
|
+
'! stale - refresh',
|
|
75
|
+
'!! !! src/b.tsx - rename -> failed',
|
|
76
|
+
' - name - title...',
|
|
77
|
+
]);
|
|
78
|
+
for (const line of lines) expect(line).not.toMatch(/[\u0080-\uFFFF]/);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -220,4 +220,51 @@ function wrapper(useResizable) {
|
|
|
220
220
|
const output = await apply('rename-resizable-pixel-bounds', input);
|
|
221
221
|
expect(output).toBe(input);
|
|
222
222
|
});
|
|
223
|
+
|
|
224
|
+
it('renames bounds when useResizable comes from a namespace import', async () => {
|
|
225
|
+
const input = `import * as Astryx from '@astryxdesign/core';
|
|
226
|
+
const region = Astryx.useResizable({defaultSize: 240, minSizePx: 120, maxSizePx: 480});`;
|
|
227
|
+
const output = await apply('rename-resizable-pixel-bounds', input);
|
|
228
|
+
expect(output).toContain('minSize: 120');
|
|
229
|
+
expect(output).toContain('maxSize: 480');
|
|
230
|
+
expect(output).not.toContain('minSizePx');
|
|
231
|
+
expect(output).not.toContain('maxSizePx');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('leaves a namespace that does not come from Astryx alone', async () => {
|
|
235
|
+
const input = `import * as Other from 'other-resizable';
|
|
236
|
+
const region = Other.useResizable({minSizePx: 120});`;
|
|
237
|
+
const output = await apply('rename-resizable-pixel-bounds', input);
|
|
238
|
+
expect(output).toContain('minSizePx: 120');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('does not rewrite a parameter that shadows the namespace import', async () => {
|
|
242
|
+
const input = `import * as Astryx from '@astryxdesign/core';
|
|
243
|
+
function build(Astryx) {
|
|
244
|
+
return Astryx.useResizable({minSizePx: 120});
|
|
245
|
+
}`;
|
|
246
|
+
const output = await apply('rename-resizable-pixel-bounds', input);
|
|
247
|
+
expect(output).toContain('minSizePx: 120');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it.each([
|
|
251
|
+
['as const', '{defaultSize: 240, minSizePx: 120} as const'],
|
|
252
|
+
['satisfies', '{defaultSize: 240, minSizePx: 120} satisfies object'],
|
|
253
|
+
])('renames a static configuration wrapped in %s', async (_label, config) => {
|
|
254
|
+
const input = `import {useResizable} from '@astryxdesign/core/Resizable';
|
|
255
|
+
const region = useResizable(${config});`;
|
|
256
|
+
const output = await apply('rename-resizable-pixel-bounds', input);
|
|
257
|
+
expect(output).toContain('minSize: 120');
|
|
258
|
+
expect(output).not.toContain('minSizePx');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('renames wrapped multi-region configurations', async () => {
|
|
262
|
+
const input = `import {useResizable} from '@astryxdesign/core';
|
|
263
|
+
const regions = useResizable({regions: {
|
|
264
|
+
nav: {defaultSize: 240, minSizePx: 120} as const,
|
|
265
|
+
} as const});`;
|
|
266
|
+
const output = await apply('rename-resizable-pixel-bounds', input);
|
|
267
|
+
expect(output).toContain('minSize: 120');
|
|
268
|
+
expect(output).not.toContain('minSizePx');
|
|
269
|
+
});
|
|
223
270
|
});
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* released conflict rule (the unified property wins when both are present).
|
|
9
9
|
* A spread or computed property can change precedence invisibly, so dynamic
|
|
10
10
|
* objects stay unchanged and receive a manual-migration TODO instead.
|
|
11
|
+
* Named and namespace imports both count, and a type-only wrapper (`as`,
|
|
12
|
+
* `satisfies`, a non-null assertion, parentheses) does not make a
|
|
13
|
+
* configuration dynamic.
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
export const meta = {
|
|
@@ -33,6 +36,26 @@ const MANUAL_MIGRATION_COMMENT =
|
|
|
33
36
|
'or computed property. Rename minSizePx/maxSizePx to minSize/maxSize ' +
|
|
34
37
|
'manually without changing property precedence. ';
|
|
35
38
|
|
|
39
|
+
const TYPE_WRAPPERS = new Set([
|
|
40
|
+
'TSAsExpression',
|
|
41
|
+
'TSSatisfiesExpression',
|
|
42
|
+
'TSTypeAssertion',
|
|
43
|
+
'TSNonNullExpression',
|
|
44
|
+
'ParenthesizedExpression',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The expression under type-only wrappers such as `{...} as const`.
|
|
49
|
+
* @param {any} node
|
|
50
|
+
*/
|
|
51
|
+
function unwrap(node) {
|
|
52
|
+
let current = node;
|
|
53
|
+
while (current && TYPE_WRAPPERS.has(current.type)) {
|
|
54
|
+
current = current.expression;
|
|
55
|
+
}
|
|
56
|
+
return current;
|
|
57
|
+
}
|
|
58
|
+
|
|
36
59
|
/** @param {any} property */
|
|
37
60
|
function staticPropertyName(property) {
|
|
38
61
|
if (property.computed) return null;
|
|
@@ -128,6 +151,8 @@ export default function transformer(file, api) {
|
|
|
128
151
|
const root = j(file.source);
|
|
129
152
|
/** @type {Map<string, any>} */
|
|
130
153
|
const bindings = new Map();
|
|
154
|
+
/** @type {Map<string, any>} */
|
|
155
|
+
const namespaces = new Map();
|
|
131
156
|
|
|
132
157
|
root.find(j.ImportDeclaration).forEach((/** @type {any} */ path) => {
|
|
133
158
|
if (!IMPORT_SOURCES.has(path.node.source.value)) return;
|
|
@@ -138,17 +163,40 @@ export default function transformer(file, api) {
|
|
|
138
163
|
) {
|
|
139
164
|
const local = specifier.local?.name ?? 'useResizable';
|
|
140
165
|
bindings.set(local, path.scope.lookup(local) ?? path.scope);
|
|
166
|
+
} else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
167
|
+
const local = specifier.local.name;
|
|
168
|
+
namespaces.set(local, path.scope.lookup(local) ?? path.scope);
|
|
141
169
|
}
|
|
142
170
|
}
|
|
143
171
|
});
|
|
144
|
-
if (bindings.size === 0) return undefined;
|
|
172
|
+
if (bindings.size === 0 && namespaces.size === 0) return undefined;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @param {any} callee
|
|
176
|
+
* @param {any} scope
|
|
177
|
+
*/
|
|
178
|
+
const isUseResizable = (callee, scope) => {
|
|
179
|
+
if (callee.type === 'Identifier') {
|
|
180
|
+
return (
|
|
181
|
+
bindings.has(callee.name) &&
|
|
182
|
+
scope.lookup(callee.name) === bindings.get(callee.name)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return (
|
|
186
|
+
callee.type === 'MemberExpression' &&
|
|
187
|
+
!callee.computed &&
|
|
188
|
+
callee.object.type === 'Identifier' &&
|
|
189
|
+
callee.property.type === 'Identifier' &&
|
|
190
|
+
callee.property.name === 'useResizable' &&
|
|
191
|
+
namespaces.has(callee.object.name) &&
|
|
192
|
+
scope.lookup(callee.object.name) === namespaces.get(callee.object.name)
|
|
193
|
+
);
|
|
194
|
+
};
|
|
145
195
|
|
|
146
196
|
let changed = false;
|
|
147
197
|
root.find(j.CallExpression).forEach((/** @type {any} */ path) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (path.scope.lookup(callee.name) !== bindings.get(callee.name)) return;
|
|
151
|
-
const config = path.node.arguments[0];
|
|
198
|
+
if (!isUseResizable(path.node.callee, path.scope)) return;
|
|
199
|
+
const config = unwrap(path.node.arguments[0]);
|
|
152
200
|
if (config?.type !== 'ObjectExpression') return;
|
|
153
201
|
|
|
154
202
|
changed = migrateObject(j, config) || changed;
|
|
@@ -158,13 +206,12 @@ export default function transformer(file, api) {
|
|
|
158
206
|
staticPropertyName(property) === 'regions',
|
|
159
207
|
);
|
|
160
208
|
if (!regions || !('value' in regions)) return;
|
|
161
|
-
const regionsObject = regions.value;
|
|
209
|
+
const regionsObject = unwrap(regions.value);
|
|
162
210
|
if (regionsObject?.type !== 'ObjectExpression') return;
|
|
163
211
|
for (const region of regionsObject.properties) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
changed = migrateObject(j, region.value) || changed;
|
|
212
|
+
const regionConfig = 'value' in region ? unwrap(region.value) : null;
|
|
213
|
+
if (regionConfig?.type !== 'ObjectExpression') continue;
|
|
214
|
+
changed = migrateObject(j, regionConfig) || changed;
|
|
168
215
|
}
|
|
169
216
|
});
|
|
170
217
|
|