@astryxdesign/cli 0.6.4-canary.ed2e54e → 0.6.4
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 +66 -64
- package/api/component/_adapter.d.mts +0 -25
- package/api/component/_adapter.mjs +5 -59
- package/api/component/component.d.mts +3 -6
- package/api/component/component.doc.mjs +10 -23
- package/api/component/component.mjs +9 -249
- package/api/component/component.type.d.mts +0 -25
- package/api/component/component.type.mjs +0 -44
- package/api/discover/_adapter.d.mts +6 -114
- package/api/discover/_adapter.mjs +17 -372
- package/api/discover/detail/detail.d.mts +6 -18
- package/api/discover/detail/detail.mjs +13 -67
- package/api/discover/detail/detail.test.mjs +0 -85
- package/api/discover/discover.d.mts +9 -3
- package/api/discover/discover.doc.mjs +18 -61
- package/api/discover/discover.mjs +36 -220
- package/api/discover/discover.test.mjs +2 -11
- package/api/discover/discover.type.d.mts +8 -147
- package/api/discover/discover.type.mjs +12 -102
- package/api/discover/list/list.d.mts +6 -20
- package/api/discover/list/list.mjs +12 -45
- package/api/discover/list/list.test.mjs +0 -46
- package/api/discover/search/search.d.mts +16 -18
- package/api/discover/search/search.mjs +56 -102
- package/api/discover/search/search.test.mjs +10 -144
- package/api/docs/docs.test.mjs +0 -2
- package/api/doctor/doctor.d.mts +3 -8
- package/api/doctor/doctor.mjs +9 -90
- package/api/doctor/doctor.test.mjs +10 -122
- package/api/index.d.mts +2 -1
- package/api/index.mjs +4 -4
- package/api/integration/add-helpers.d.mts +2 -5
- package/api/integration/add-helpers.mjs +9 -36
- package/api/integration/pack-check.mjs +3 -28
- package/api/json/index.ts +1 -0
- package/api/layout/_adapter.d.mts +34 -0
- package/api/layout/_adapter.mjs +148 -0
- package/api/layout/check/check.d.mts +16 -0
- package/api/layout/check/check.mjs +40 -0
- package/api/layout/expand/expand.d.mts +22 -0
- package/api/layout/expand/expand.mjs +155 -0
- package/api/layout/expand/expand.path-safety.test.mjs +53 -0
- package/api/layout/grammar/grammar.d.mts +13 -0
- package/api/layout/grammar/grammar.mjs +87 -0
- package/api/layout/layout.d.mts +6 -0
- package/api/layout/layout.mjs +17 -0
- package/api/layout/layout.test.mjs +297 -0
- package/api/layout/layout.type.d.mts +89 -0
- package/api/layout/layout.type.mjs +103 -0
- package/api/layout/layoutCheck.doc.d.mts +11 -0
- package/api/layout/layoutCheck.doc.mjs +85 -0
- package/api/layout/layoutExpand.doc.d.mts +11 -0
- package/api/layout/layoutExpand.doc.mjs +107 -0
- package/api/layout/layoutGrammar.doc.d.mts +11 -0
- package/api/layout/layoutGrammar.doc.mjs +57 -0
- package/api/search/search.test.mjs +0 -18
- package/api/template/template-integration.test.mjs +65 -1
- package/api/template/template.mjs +1 -1
- package/api/theme/add/add.mjs +25 -17
- package/api/theme/add/add.staging.test.mjs +23 -40
- package/api/theme/build/build.family.test.mjs +12 -7
- package/api/theme/build/build.mjs +18 -8
- package/api/upgrade/run/run.mjs +4 -6
- package/api/upgrade/upgrade.type.mjs +2 -2
- package/assets/codemods/__tests__/runner.test.mjs +1 -3
- package/assets/codemods/integration-runner.mjs +3 -3
- package/assets/codemods/runner.mjs +4 -5
- package/assets/docs/internationalization.doc.mjs +5 -7
- package/assets/docs/tree/integrations.doc.mjs +1 -20
- package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
- package/authoring/config/config.doc.mjs +1 -9
- package/authoring/config/parse.d.mts +0 -2
- package/authoring/config/parse.mjs +0 -19
- package/authoring/config/parse.test.mjs +0 -8
- package/authoring/config/type.ts +2 -13
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/index.d.mts +0 -1
- package/authoring/index.d.ts +0 -10
- package/authoring/index.mjs +0 -1
- package/clients/cli/command-result-coverage.test.mjs +7 -7
- package/clients/cli/commands/component/index.mjs +55 -152
- package/clients/cli/commands/component-ownership.test.mjs +0 -89
- package/clients/cli/commands/component.doc.mjs +6 -23
- package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
- package/clients/cli/commands/discover.doc.mjs +9 -53
- package/clients/cli/commands/discover.mjs +118 -393
- package/clients/cli/commands/docs.test.mjs +0 -29
- package/clients/cli/commands/layout-check.doc.mjs +65 -0
- package/clients/cli/commands/layout-expand.doc.mjs +83 -0
- package/clients/cli/commands/layout-grammar.doc.mjs +30 -0
- package/clients/cli/commands/layout.doc.mjs +34 -0
- package/clients/cli/commands/layout.error-codes.test.mjs +66 -0
- package/clients/cli/commands/layout.exit-parity.test.mjs +41 -0
- package/clients/cli/commands/layout.mjs +275 -0
- 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/text-json-parity.test.mjs +17 -0
- package/clients/cli/index.mjs +4 -0
- package/clients/cli/lib/exit-codes.test.mjs +8 -1
- package/clients/cli/lib/json-shim.mjs +14 -24
- package/clients/cli/lib/json-shim.test.mjs +20 -6
- package/clients/cli/lib/manifest.mjs +8 -3
- package/clients/cli/lib/manifest.test.mjs +2 -5
- package/foundation/discovery/authoring-self-docs.mjs +0 -1
- package/foundation/discovery/template-adapter.mjs +1 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +12 -0
- package/foundation/doc-compiler/tree.test.mjs +1 -9
- package/foundation/integrations/integrations.d.mts +1 -14
- package/foundation/integrations/integrations.mjs +1 -41
- package/foundation/integrations/integrations.test.mjs +0 -31
- package/foundation/response/response-types.doc.mjs +21 -15
- package/foundation/response/response-types.doc.test.mjs +0 -23
- package/foundation/xle/browser.d.mts +3 -3
- package/foundation/xle/browser.mjs +3 -3
- package/foundation/xle/expand.mjs +2 -2
- package/foundation/xle/parse.mjs +1 -1
- package/foundation/xle/print.mjs +2 -2
- package/foundation/xle/splice.mjs +1 -1
- package/package.json +9 -9
- package/api/discover/_adapter.test.mjs +0 -215
- package/api/discover/_catalog-view.d.mts +0 -115
- package/api/discover/_catalog-view.mjs +0 -203
- package/api/discover/_catalog-view.test.mjs +0 -128
- package/api/discover/detail/item/item.d.mts +0 -26
- package/api/discover/detail/item/item.mjs +0 -78
- package/api/discover/detail/item/item.test.mjs +0 -73
- package/api/integration/pack-check.lifecycle-output.test.mjs +0 -105
- package/api/theme/add/add.rollback.test.mjs +0 -158
- package/api/theme/build/build.rollback.test.mjs +0 -148
- package/api/upgrade/run/files-changed.test.mjs +0 -111
- package/assets/codemods/file-count.test.mjs +0 -163
- package/assets/docs/tree/component-lookups.doc.mjs +0 -149
- package/authoring/discover/discover.doc.d.mts +0 -13
- package/authoring/discover/discover.doc.mjs +0 -138
- package/authoring/discover/parse.d.mts +0 -24
- package/authoring/discover/parse.mjs +0 -128
- package/authoring/discover/parse.test.mjs +0 -124
- package/authoring/discover/type.ts +0 -87
- package/clients/cli/commands/component-batch.test.mjs +0 -341
- package/clients/cli/commands/discover.sources.test.mjs +0 -267
- package/clients/cli/lib/parse-error-format.test.mjs +0 -81
- package/foundation/response/batch.type.d.mts +0 -33
- package/foundation/response/batch.type.mjs +0 -34
|
@@ -6,15 +6,14 @@ import * as os from 'node:os';
|
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import {themeAdd} from './add.mjs';
|
|
8
8
|
import {listThemes} from '../_adapter.mjs';
|
|
9
|
+
import {isErrorCode} from '../../../foundation/response/error-codes.mjs';
|
|
9
10
|
|
|
10
11
|
let tmpDir;
|
|
11
12
|
let outsideDir;
|
|
12
13
|
|
|
13
14
|
beforeEach(() => {
|
|
14
15
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-themeadd-staging-'));
|
|
15
|
-
outsideDir = fs.mkdtempSync(
|
|
16
|
-
path.join(os.tmpdir(), 'astryx-themeadd-outside-'),
|
|
17
|
-
);
|
|
16
|
+
outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-themeadd-outside-'));
|
|
18
17
|
});
|
|
19
18
|
|
|
20
19
|
afterEach(() => {
|
|
@@ -23,61 +22,45 @@ afterEach(() => {
|
|
|
23
22
|
});
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
|
-
*
|
|
25
|
+
* Put a symlink where `theme add` stages the first file of `slug`.
|
|
27
26
|
* @param {string} slug
|
|
27
|
+
* @param {string} target
|
|
28
28
|
*/
|
|
29
|
-
function
|
|
29
|
+
function plantStagingLink(slug, target) {
|
|
30
30
|
const theme = listThemes().find(entry => entry.slug === slug);
|
|
31
31
|
if (!theme) throw new Error(`missing bundled theme ${slug}`);
|
|
32
|
-
const
|
|
32
|
+
const first = theme.files[0];
|
|
33
|
+
const dest = path.join(tmpDir, 'src', 'themes', slug, first);
|
|
33
34
|
fs.mkdirSync(path.dirname(dest), {recursive: true});
|
|
35
|
+
fs.symlinkSync(target, `${dest}.${process.pid}.tmp`);
|
|
34
36
|
return dest;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
// Staging names are unpredictable and created exclusively, so an entry planted
|
|
38
|
-
// at a predictable name beside the destination is never written through.
|
|
39
39
|
describe('themeAdd staging writes stay inside the project', () => {
|
|
40
|
-
it('
|
|
40
|
+
it('refuses a staging path that links outside the project', async () => {
|
|
41
41
|
const victim = path.join(outsideDir, 'victim.txt');
|
|
42
42
|
fs.writeFileSync(victim, 'outside\n');
|
|
43
|
-
const dest =
|
|
44
|
-
fs.symlinkSync(victim, `${dest}.${process.pid}.tmp`);
|
|
45
|
-
|
|
46
|
-
await themeAdd('stone', {cwd: tmpDir});
|
|
43
|
+
const dest = plantStagingLink('stone', victim);
|
|
47
44
|
|
|
45
|
+
await expect(themeAdd('stone', {cwd: tmpDir})).rejects.toMatchObject({
|
|
46
|
+
code: 'ERR_PATH_TRAVERSAL',
|
|
47
|
+
});
|
|
48
48
|
expect(fs.readFileSync(victim, 'utf-8')).toBe('outside\n');
|
|
49
|
-
expect(fs.
|
|
49
|
+
expect(fs.existsSync(dest)).toBe(false);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it('never creates a file through a dangling
|
|
52
|
+
it('never creates a file through a dangling staging link', async () => {
|
|
53
53
|
const victim = path.join(outsideDir, 'created.txt');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
await themeAdd('stone', {cwd: tmpDir});
|
|
57
|
-
|
|
58
|
-
expect(fs.existsSync(victim)).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('refuses to replace a destination that links outside the project', async () => {
|
|
62
|
-
const victim = path.join(outsideDir, 'victim.txt');
|
|
63
|
-
fs.writeFileSync(victim, 'outside\n');
|
|
64
|
-
const dest = firstDestination('stone');
|
|
65
|
-
fs.symlinkSync(victim, dest);
|
|
54
|
+
plantStagingLink('stone', victim);
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
56
|
+
let error;
|
|
57
|
+
try {
|
|
58
|
+
await themeAdd('stone', {cwd: tmpDir});
|
|
59
|
+
} catch (caught) {
|
|
60
|
+
error = caught;
|
|
61
|
+
}
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
const victim = path.join(outsideDir, 'created.txt');
|
|
76
|
-
fs.symlinkSync(victim, firstDestination('stone'));
|
|
77
|
-
|
|
78
|
-
await expect(themeAdd('stone', {cwd: tmpDir})).rejects.toMatchObject({
|
|
79
|
-
code: 'ERR_PATH_TRAVERSAL',
|
|
80
|
-
});
|
|
63
|
+
expect(isErrorCode(error?.code)).toBe(true);
|
|
81
64
|
expect(fs.existsSync(victim)).toBe(false);
|
|
82
65
|
});
|
|
83
66
|
});
|
|
@@ -251,18 +251,23 @@ describe('themeBuildFamily()', () => {
|
|
|
251
251
|
const dir = makeDir();
|
|
252
252
|
const files = writeFamily(dir);
|
|
253
253
|
const outputDir = path.join(dir, 'themes');
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
const blockedTmp = path.join(
|
|
255
|
+
outputDir,
|
|
256
|
+
`${FAMILY_KEY}.js.${process.pid}.tmp`,
|
|
257
|
+
);
|
|
258
|
+
fs.mkdirSync(blockedTmp);
|
|
257
259
|
|
|
258
260
|
await expect(build(dir, files)).rejects.toThrow(
|
|
259
261
|
/Failed to write theme outputs/,
|
|
260
262
|
);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
for (const output of OUTPUTS) {
|
|
264
|
+
expect(fs.existsSync(path.join(outputDir, output))).toBe(false);
|
|
265
|
+
}
|
|
263
266
|
expect(
|
|
264
|
-
fs.
|
|
265
|
-
|
|
267
|
+
fs.existsSync(
|
|
268
|
+
path.join(outputDir, `${FAMILY_KEY}.css.${process.pid}.tmp`),
|
|
269
|
+
),
|
|
270
|
+
).toBe(false);
|
|
266
271
|
});
|
|
267
272
|
|
|
268
273
|
it('rejects invalid graphs and keys before touching an existing trio', async () => {
|
|
@@ -53,7 +53,6 @@ import {
|
|
|
53
53
|
} from '../../../foundation/fs/path-safety.mjs';
|
|
54
54
|
import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
55
55
|
import {AstryxError} from '../../error.mjs';
|
|
56
|
-
import {applyWrites} from '../../integration/add-helpers.mjs';
|
|
57
56
|
import {logger} from '../../logger.mjs';
|
|
58
57
|
import {loadComponentDoc} from '../../../foundation/discovery/component-loader.mjs';
|
|
59
58
|
import {
|
|
@@ -225,15 +224,26 @@ function staleBuildOutputs(writes, cwd) {
|
|
|
225
224
|
/** @param {Array<{dest: string, content: string}>} writes */
|
|
226
225
|
function writeBuildOutputs(writes) {
|
|
227
226
|
if (writes.length === 0) return;
|
|
227
|
+
/** @type {Array<{tmp: string, dest: string}>} */
|
|
228
|
+
const staged = [];
|
|
228
229
|
try {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
)
|
|
230
|
+
fs.mkdirSync(path.dirname(writes[0].dest), {recursive: true});
|
|
231
|
+
for (const write of writes) {
|
|
232
|
+
const tmp = `${write.dest}.${process.pid}.tmp`;
|
|
233
|
+
fs.writeFileSync(tmp, write.content);
|
|
234
|
+
staged.push({tmp, dest: write.dest});
|
|
235
|
+
}
|
|
236
|
+
for (const stagedWrite of staged) {
|
|
237
|
+
fs.renameSync(stagedWrite.tmp, stagedWrite.dest);
|
|
238
|
+
}
|
|
236
239
|
} catch (error) {
|
|
240
|
+
for (const stagedWrite of staged) {
|
|
241
|
+
try {
|
|
242
|
+
fs.rmSync(stagedWrite.tmp, {force: true});
|
|
243
|
+
} catch {
|
|
244
|
+
// Best effort: the command still fails and never reports success.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
237
247
|
const message = `Failed to write theme outputs: ${/** @type {Error} */ (error).message}`;
|
|
238
248
|
throw new AstryxError(message, undefined, ERROR_CODES.ERR_WRITE_FAILED);
|
|
239
249
|
}
|
package/api/upgrade/run/run.mjs
CHANGED
|
@@ -114,7 +114,7 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
114
114
|
// Resolve the source dir against the API's cwd (not process.cwd()) so a
|
|
115
115
|
// programmatic caller in another directory scans the right tree. Confine it to
|
|
116
116
|
// cwd: --apply rewrites files in place, so a `..`-escaping or out-of-tree
|
|
117
|
-
// absolute --path must be rejected (parity with template/theme/swizzle,
|
|
117
|
+
// absolute --path must be rejected (parity with template/theme/swizzle/layout,
|
|
118
118
|
// and this is the most destructive command). allowAbsolute permits an absolute
|
|
119
119
|
// path that still resolves inside cwd.
|
|
120
120
|
let path_;
|
|
@@ -437,11 +437,9 @@ export async function run(options = {}, {cwd = process.cwd()} = {}) {
|
|
|
437
437
|
|
|
438
438
|
const registryResult = await reconcileCompositions();
|
|
439
439
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
...(integrationResult?.changedFiles ?? []),
|
|
444
|
-
]).size;
|
|
440
|
+
const mergedFilesChanged =
|
|
441
|
+
(coreResult?.totalFilesChanged ?? 0) +
|
|
442
|
+
(integrationResult?.totalFilesChanged ?? 0);
|
|
445
443
|
const mergedTransformsApplied =
|
|
446
444
|
(coreResult?.totalTransformsApplied ?? 0) +
|
|
447
445
|
(integrationResult?.totalTransformsApplied ?? 0);
|
|
@@ -119,11 +119,11 @@
|
|
|
119
119
|
* @property {RegistryCompositionSummary} [data.registryCompositions]
|
|
120
120
|
* @property {boolean} [data.complete] False when protected required changes remain.
|
|
121
121
|
* @property {'ERR_CODEMOD_PROTECTED'} [data.errorCode] Stable incomplete-result code when complete is false.
|
|
122
|
-
* @property {number} [data.filesChanged]
|
|
122
|
+
* @property {number} [data.filesChanged] Total files changed across core + integration codemods (apply mode).
|
|
123
123
|
* @property {string[]} [data.modifiedFiles] Project-relative files changed or previewed.
|
|
124
124
|
* @property {ProtectedCodemodFile[]} [data.protectedFiles] Protected files that still require a codemod change after regeneration.
|
|
125
125
|
* @property {Array<{file: string, location?: string, reason: string}>} [data.declinedCandidates] Candidates left unchanged because proof was insufficient.
|
|
126
|
-
* @property {number} [data.transformsApplied] Total
|
|
126
|
+
* @property {number} [data.transformsApplied] Total transforms that reported a change.
|
|
127
127
|
* @property {Array<{file: string, codemod: string, error: string}>} [data.errors] Per-codemod errors, when any codemod failed.
|
|
128
128
|
*/
|
|
129
129
|
|
|
@@ -46,9 +46,7 @@ describe('runCodemods — ordered dry-run state', () => {
|
|
|
46
46
|
silent: true,
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
expect(preview.totalFilesChanged).toBe(1);
|
|
51
|
-
expect(preview.totalTransformsApplied).toBe(2);
|
|
49
|
+
expect(preview.totalFilesChanged).toBe(2);
|
|
52
50
|
expect(preview.changedFiles).toEqual([
|
|
53
51
|
path.join(srcDir, 'a.ts'),
|
|
54
52
|
path.join(srcDir, 'a.ts'),
|
|
@@ -67,6 +67,7 @@ export function runIntegrationCodemods(
|
|
|
67
67
|
/** In-memory pipeline state keeps ordered dry-runs equivalent to apply. */
|
|
68
68
|
const virtualContents = new Map(providedContents ?? []);
|
|
69
69
|
|
|
70
|
+
let totalFilesChanged = 0;
|
|
70
71
|
let totalTransformsApplied = 0;
|
|
71
72
|
/** @type {string[]} */
|
|
72
73
|
const changedFiles = [];
|
|
@@ -114,6 +115,7 @@ export function runIntegrationCodemods(
|
|
|
114
115
|
protection,
|
|
115
116
|
contents: virtualContents,
|
|
116
117
|
});
|
|
118
|
+
totalFilesChanged += r.filesChanged;
|
|
117
119
|
totalTransformsApplied += r.filesChanged;
|
|
118
120
|
changedFiles.push(...r.changedFiles);
|
|
119
121
|
writtenFiles.push(...r.writtenFiles);
|
|
@@ -143,6 +145,7 @@ export function runIntegrationCodemods(
|
|
|
143
145
|
protection,
|
|
144
146
|
contents: virtualContents,
|
|
145
147
|
});
|
|
148
|
+
totalFilesChanged += r.filesChanged;
|
|
146
149
|
totalTransformsApplied += r.filesChanged;
|
|
147
150
|
changedFiles.push(...r.changedFiles);
|
|
148
151
|
writtenFiles.push(...r.writtenFiles);
|
|
@@ -151,9 +154,6 @@ export function runIntegrationCodemods(
|
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
// A file several codemods changed is one file; transforms count each change.
|
|
155
|
-
const totalFilesChanged = new Set(changedFiles).size;
|
|
156
|
-
|
|
157
157
|
return {
|
|
158
158
|
totalFilesChanged,
|
|
159
159
|
totalTransformsApplied,
|
|
@@ -486,6 +486,7 @@ export async function runCodemods(
|
|
|
486
486
|
(await import('jscodeshift')).default
|
|
487
487
|
);
|
|
488
488
|
|
|
489
|
+
let totalFilesChanged = 0;
|
|
489
490
|
let totalTransformsApplied = 0;
|
|
490
491
|
let totalValidationBlocked = 0;
|
|
491
492
|
/** @type {Array<{file: string, codemod: string, error: string}>} */
|
|
@@ -553,6 +554,7 @@ export async function runCodemods(
|
|
|
553
554
|
protectionWriteCount = writtenFiles.length;
|
|
554
555
|
}
|
|
555
556
|
if (result.filesChanged > 0) {
|
|
557
|
+
totalFilesChanged += result.filesChanged;
|
|
556
558
|
totalTransformsApplied += 1;
|
|
557
559
|
}
|
|
558
560
|
continue;
|
|
@@ -575,6 +577,7 @@ export async function runCodemods(
|
|
|
575
577
|
changedFiles.push(...result.changedFiles);
|
|
576
578
|
writtenFiles.push(...result.writtenFiles);
|
|
577
579
|
if (result.filesChanged > 0) {
|
|
580
|
+
totalFilesChanged += result.filesChanged;
|
|
578
581
|
totalTransformsApplied += result.filesChanged;
|
|
579
582
|
}
|
|
580
583
|
continue;
|
|
@@ -589,6 +592,7 @@ export async function runCodemods(
|
|
|
589
592
|
protectedFiles.push(...result.protectedFiles);
|
|
590
593
|
changedFiles.push(...result.changedFiles);
|
|
591
594
|
writtenFiles.push(...result.writtenFiles);
|
|
595
|
+
totalFilesChanged += result.filesChanged;
|
|
592
596
|
totalTransformsApplied += result.filesChanged;
|
|
593
597
|
totalValidationBlocked += result.errors.filter(
|
|
594
598
|
error =>
|
|
@@ -626,11 +630,6 @@ export async function runCodemods(
|
|
|
626
630
|
);
|
|
627
631
|
}
|
|
628
632
|
|
|
629
|
-
// A file several codemods changed is one file. `totalTransformsApplied` is
|
|
630
|
-
// unchanged: a code or config codemod counts each file it changed, and a
|
|
631
|
-
// project codemod counts once. The two answer different questions.
|
|
632
|
-
const totalFilesChanged = new Set(changedFiles).size;
|
|
633
|
-
|
|
634
633
|
if (protectedFiles.length > 0) {
|
|
635
634
|
const files = [...new Set(protectedFiles.map(item => item.file))];
|
|
636
635
|
log.warn(
|
|
@@ -41,17 +41,15 @@ function App() {
|
|
|
41
41
|
lang: 'tsx',
|
|
42
42
|
label: 'Load an astryx locale catalog',
|
|
43
43
|
code: `import {InternationalizationProvider} from '@astryxdesign/core/i18n';
|
|
44
|
-
import
|
|
44
|
+
import fr from '@astryxdesign/core/locales/fr.json';
|
|
45
45
|
|
|
46
|
-
<InternationalizationProvider
|
|
47
|
-
locale="fr-FR"
|
|
48
|
-
messages={{'fr-FR': frFR}}>
|
|
46
|
+
<InternationalizationProvider locale="fr" messages={{fr}}>
|
|
49
47
|
<App />
|
|
50
48
|
</InternationalizationProvider>;`,
|
|
51
49
|
},
|
|
52
50
|
{
|
|
53
51
|
type: 'prose',
|
|
54
|
-
text: 'Astryx ships English
|
|
52
|
+
text: 'Astryx ships English today, with first-party translations for other locales on the roadmap. Until a locale is available from `@astryxdesign/core/locales/*`, apps can pass a local catalog with the same shape. See `@astryxdesign/core/locales/en.json` for the current key inventory. Missing keys fall back through the locale chain to English (for example, `pt-BR` walks to `pt`, then to shipped `en`).',
|
|
55
53
|
},
|
|
56
54
|
{
|
|
57
55
|
type: 'prose',
|
|
@@ -292,7 +290,7 @@ export default function App() {
|
|
|
292
290
|
},
|
|
293
291
|
{
|
|
294
292
|
type: 'prose',
|
|
295
|
-
text: '`Catalog` types
|
|
293
|
+
text: '`Catalog` types a single locale file; `MessagesByLocale` types the map passed to `messages`. A catalog entry uses the same `{defaultMessage, description?}` shape as `@astryxdesign/core/locales/en.json`.',
|
|
296
294
|
},
|
|
297
295
|
],
|
|
298
296
|
},
|
|
@@ -309,7 +307,7 @@ export default function App() {
|
|
|
309
307
|
lang: 'tsx',
|
|
310
308
|
label: 'Turn on pseudo-localization',
|
|
311
309
|
code: `import {InternationalizationProvider} from '@astryxdesign/core/i18n';
|
|
312
|
-
import pseudo from '@astryxdesign/core/locales/pseudo.
|
|
310
|
+
import pseudo from '@astryxdesign/core/locales/pseudo.json';
|
|
313
311
|
|
|
314
312
|
<InternationalizationProvider locale="pseudo" messages={{pseudo}}>
|
|
315
313
|
<App />
|
|
@@ -440,25 +440,6 @@ export const docs = {
|
|
|
440
440
|
},
|
|
441
441
|
],
|
|
442
442
|
},
|
|
443
|
-
{
|
|
444
|
-
title: 'Discover source',
|
|
445
|
-
category: 'guide',
|
|
446
|
-
content: [
|
|
447
|
-
{
|
|
448
|
-
type: 'prose',
|
|
449
|
-
text: '`astryx discover` lists the integrations an app has and, through discover sources, the ones it could add, with every version and what each one adds. An integration can supply a source by exporting an async function named `discover` from its integration module. Like `debug`, it is a named export, not a manifest field, so a CLI that predates it simply does not read it.',
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
type: 'code',
|
|
453
|
-
lang: 'typescript',
|
|
454
|
-
code: "// astryx.integration.ts\nimport type {DiscoverSource} from '@astryxdesign/cli/authoring';\n\nexport const discover: DiscoverSource = async ({signal, package: name, version}) => {\n // Every package you know about, each with every version and what the\n // requested (else latest) version adds.\n return readCatalog({signal, name, version});\n};\n\nexport default {\n components: './components',\n};",
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
type: 'prose',
|
|
458
|
-
text: 'A project can set the same function as `discover` in `astryx.config`. Discover calls every source, the project one first and then each integration in load order, and one that throws, runs past 30 seconds, or returns an invalid catalog never hides the others. It keeps the last good answer from each source in the per-user cache and uses it, with its date, when the source cannot be reached. Discover only reads: it prints the command that adds a package and never runs it. `astryx docs authoring discover-source` has the catalog shape.',
|
|
459
|
-
},
|
|
460
|
-
],
|
|
461
|
-
},
|
|
462
443
|
{
|
|
463
444
|
title: 'How It Works',
|
|
464
445
|
category: 'guide',
|
|
@@ -469,7 +450,7 @@ export const docs = {
|
|
|
469
450
|
},
|
|
470
451
|
{
|
|
471
452
|
type: 'prose',
|
|
472
|
-
text: 'Runtime integration features — `debug
|
|
453
|
+
text: 'Runtime integration features — `debug` and `gapReport` — use named exports from the integration module rather than fields in the default manifest. The CLI discovers them alongside the manifest but loads them through the composition rules in `spec:AST-031`: every configured handler runs additively, each in isolation with its own copy of the event.',
|
|
473
454
|
},
|
|
474
455
|
{
|
|
475
456
|
type: 'prose',
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import {useState} from 'react';
|
|
6
6
|
import {InternationalizationProvider} from '@astryxdesign/core/i18n';
|
|
7
|
-
import frFR from '@astryxdesign/core/locales/fr-FR.
|
|
7
|
+
import frFR from '@astryxdesign/core/locales/fr-FR.json';
|
|
8
8
|
import {Stack} from '@astryxdesign/core/Layout';
|
|
9
9
|
import {
|
|
10
10
|
SegmentedControl,
|
|
@@ -60,14 +60,6 @@ export const doc = {
|
|
|
60
60
|
example:
|
|
61
61
|
"{ audience: 'internal', async handle(report, {signal}) { return sendGap(report, {signal}); } }",
|
|
62
62
|
},
|
|
63
|
-
{
|
|
64
|
-
name: 'discover',
|
|
65
|
-
type: 'DiscoverSource',
|
|
66
|
-
description:
|
|
67
|
-
'Tell `astryx discover` which integrations this project could add: an async function that returns a catalog. An integration can provide one too, as a `discover` named export from its manifest. Discover calls every source, yours first, and one that fails never hides the others. Discover only reads; your package manager installs.',
|
|
68
|
-
example:
|
|
69
|
-
"async ({signal, package: name, version}) => fetchCatalog({signal, name, version})",
|
|
70
|
-
},
|
|
71
63
|
{
|
|
72
64
|
name: 'experimental',
|
|
73
65
|
type: '{ xle?: { components?: Record<string, XleComponent> } }',
|
|
@@ -77,7 +69,7 @@ export const doc = {
|
|
|
77
69
|
name: 'experimental.xle.components',
|
|
78
70
|
type: 'Record<string, XleComponent>',
|
|
79
71
|
description:
|
|
80
|
-
'
|
|
72
|
+
'Custom components the layout expander (XLE) may emit, keyed by tag.',
|
|
81
73
|
},
|
|
82
74
|
],
|
|
83
75
|
},
|
|
@@ -30,7 +30,6 @@ export type XleComponent = import("./type.js").XleComponent;
|
|
|
30
30
|
export type DebugConfig = import("./type.js").DebugConfig;
|
|
31
31
|
export type DebugEventHandler = import("../debug/type.js").DebugEventHandler;
|
|
32
32
|
export type GapReportHandler = import("../gap-report/type.js").GapReportHandler;
|
|
33
|
-
export type DiscoverSource = import("../discover/type.js").DiscoverSource;
|
|
34
33
|
import { z } from 'zod';
|
|
35
34
|
declare const configSchema: z.ZodObject<{
|
|
36
35
|
integrations: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -49,7 +48,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
49
48
|
}, z.core.$strict>>;
|
|
50
49
|
debug: z.ZodOptional<z.ZodType<import("../debug/type.js").DebugEventHandler, any, z.core.$ZodTypeInternals<import("../debug/type.js").DebugEventHandler, any>>>;
|
|
51
50
|
gapReport: z.ZodOptional<z.ZodType<import("../gap-report/type.js").GapReportHandler, any, z.core.$ZodTypeInternals<import("../gap-report/type.js").GapReportHandler, any>>>;
|
|
52
|
-
discover: z.ZodOptional<z.ZodType<import("../discover/type.js").DiscoverSource, any, z.core.$ZodTypeInternals<import("../discover/type.js").DiscoverSource, any>>>;
|
|
53
51
|
experimental: z.ZodOptional<z.ZodObject<{
|
|
54
52
|
xle: z.ZodOptional<z.ZodObject<{
|
|
55
53
|
components: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
import {z} from 'zod';
|
|
13
13
|
import {formatZodError} from '../_shared/errors.mjs';
|
|
14
14
|
import {parseGapReportHandler} from '../gap-report/parse.mjs';
|
|
15
|
-
import {parseDiscoverSource} from '../discover/parse.mjs';
|
|
16
15
|
|
|
17
16
|
/** @typedef {import('./type.js').AstryxConfig} AstryxConfig */
|
|
18
17
|
/** @typedef {import('./type.js').PostCodemodHook} PostCodemodHook */
|
|
@@ -20,7 +19,6 @@ import {parseDiscoverSource} from '../discover/parse.mjs';
|
|
|
20
19
|
/** @typedef {import('./type.js').DebugConfig} DebugConfig */
|
|
21
20
|
/** @typedef {import('../debug/type.js').DebugEventHandler} DebugEventHandler */
|
|
22
21
|
/** @typedef {import('../gap-report/type.js').GapReportHandler} GapReportHandler */
|
|
23
|
-
/** @typedef {import('../discover/type.js').DiscoverSource} DiscoverSource */
|
|
24
22
|
|
|
25
23
|
// Typed `z.custom` so `z.infer` reproduces the real function type (not `unknown`).
|
|
26
24
|
const buildCommand = /** @type {z.ZodType<PostCodemodHook['buildCommand']>} */ (
|
|
@@ -68,22 +66,6 @@ const gapReportHandlerSchema = /** @type {z.ZodType<GapReportHandler>} */ (
|
|
|
68
66
|
)
|
|
69
67
|
);
|
|
70
68
|
|
|
71
|
-
// The same check an integration's `discover` named export passes. Typed
|
|
72
|
-
// z.custom preserves the public function type.
|
|
73
|
-
const discoverSourceSchema = /** @type {z.ZodType<DiscoverSource>} */ (
|
|
74
|
-
z.custom(
|
|
75
|
-
value => {
|
|
76
|
-
try {
|
|
77
|
-
parseDiscoverSource(value, 'discover');
|
|
78
|
-
return true;
|
|
79
|
-
} catch {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
{message: 'Expected a discover source function'},
|
|
84
|
-
)
|
|
85
|
-
);
|
|
86
|
-
|
|
87
69
|
const configSchema = z
|
|
88
70
|
.object({
|
|
89
71
|
integrations: z.array(z.string()).optional(),
|
|
@@ -94,7 +76,6 @@ const configSchema = z
|
|
|
94
76
|
.optional(),
|
|
95
77
|
debug: debugSchema.optional(),
|
|
96
78
|
gapReport: gapReportHandlerSchema.optional(),
|
|
97
|
-
discover: discoverSourceSchema.optional(),
|
|
98
79
|
experimental: z
|
|
99
80
|
.object({
|
|
100
81
|
xle: z
|
|
@@ -63,14 +63,6 @@ describe('parseConfig (load boundary)', () => {
|
|
|
63
63
|
).toEqual({audience: 'internal', handle});
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
it('accepts a discover source function and refuses anything else', () => {
|
|
67
|
-
const discover = async () => ({});
|
|
68
|
-
expect(parseConfig({discover}).discover).toBe(discover);
|
|
69
|
-
expect(reason({discover: 'https://example.com/catalog.json'})).toContain(
|
|
70
|
-
'discover',
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
66
|
it('rejects obsolete or extended gap-report handler shapes', () => {
|
|
75
67
|
expect(reason({gapReport: {command: './report.mjs'}})).toContain(
|
|
76
68
|
'gapReport',
|
package/authoring/config/type.ts
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
import type {DebugEventHandler} from '../debug/type.js';
|
|
13
13
|
import type {GapReportHandler} from '../gap-report/type.js';
|
|
14
|
-
import type {DiscoverSource} from '../discover/type.js';
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* A command to run as part of a post-codemod hook. Returned by a hook's
|
|
@@ -97,16 +96,6 @@ export interface AstryxConfig {
|
|
|
97
96
|
debug?: DebugConfig;
|
|
98
97
|
/** Route gap reports through a project-owned handler. See {@link GapReportHandler}. */
|
|
99
98
|
gapReport?: GapReportHandler;
|
|
100
|
-
/**
|
|
101
|
-
* Tell `astryx discover` about integrations this project could add. See
|
|
102
|
-
* {@link DiscoverSource}.
|
|
103
|
-
*
|
|
104
|
-
* An integration can provide a source too, as a `discover` named export from
|
|
105
|
-
* its `astryx.integration.*` module. Discover calls every source: this one
|
|
106
|
-
* first, then each integration's in load order, and one that fails never
|
|
107
|
-
* hides the others.
|
|
108
|
-
*/
|
|
109
|
-
discover?: DiscoverSource;
|
|
110
99
|
/**
|
|
111
100
|
* EXPERIMENTAL — shape may change and is not part of the stable config
|
|
112
101
|
* contract. Provisional home for features still being proven out.
|
|
@@ -115,8 +104,8 @@ export interface AstryxConfig {
|
|
|
115
104
|
/** Experimental XLE (layout expression) configuration. */
|
|
116
105
|
xle?: {
|
|
117
106
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
107
|
+
* Register app-local components so XLE layout expressions can
|
|
108
|
+
* reference them by name via {hint}. Keyed by component name.
|
|
120
109
|
*/
|
|
121
110
|
components?: Record<string, XleComponent>;
|
|
122
111
|
};
|
|
@@ -75,7 +75,7 @@ export interface CommandDoc extends AuthoredDocGraphFields {
|
|
|
75
75
|
args?: CommandArgDoc[];
|
|
76
76
|
/** Flags/options. */
|
|
77
77
|
options?: CommandOptionDoc[];
|
|
78
|
-
/** Subcommand names (for command groups like `theme`). */
|
|
78
|
+
/** Subcommand names (for command groups like `theme` / `layout`). */
|
|
79
79
|
subcommands?: string[];
|
|
80
80
|
/** Terminal examples. */
|
|
81
81
|
examples?: CommandExampleDoc[];
|
package/authoring/index.d.mts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
export { parseConfig } from "./config/parse.mjs";
|
|
5
5
|
export { parseIntegration } from "./integration/parse.mjs";
|
|
6
|
-
export { parseDiscoverCatalog } from "./discover/parse.mjs";
|
|
7
6
|
export { parseCodemod } from "./codemod/parse.mjs";
|
|
8
7
|
export { parseDebugEvent } from "./debug/parse.mjs";
|
|
9
8
|
export { parseDoc } from "./doctypes/parse.mjs";
|
package/authoring/index.d.ts
CHANGED
|
@@ -44,15 +44,6 @@ export type {
|
|
|
44
44
|
GapReportTarget,
|
|
45
45
|
GapReportHandlerReceipt,
|
|
46
46
|
} from './gap-report/type.js'; // gap-report handler contract
|
|
47
|
-
export type {
|
|
48
|
-
DiscoverSource,
|
|
49
|
-
DiscoverSourceContext,
|
|
50
|
-
DiscoverCatalog,
|
|
51
|
-
DiscoverPackage,
|
|
52
|
-
DiscoverVersion,
|
|
53
|
-
DiscoverContribution,
|
|
54
|
-
DiscoverKind,
|
|
55
|
-
} from './discover/type.js'; // discover source contract
|
|
56
47
|
export type {AstryxCodemod, AstryxConfigCodemod} from './codemod/type.js'; // codemods/*
|
|
57
48
|
|
|
58
49
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -76,7 +67,6 @@ export {
|
|
|
76
67
|
parseGapReportHandler,
|
|
77
68
|
parseGapReportReceipt,
|
|
78
69
|
} from './gap-report/parse.mjs';
|
|
79
|
-
export {parseDiscoverCatalog} from './discover/parse.mjs';
|
|
80
70
|
export {parseCodemod} from './codemod/parse.mjs';
|
|
81
71
|
export {parseDebugEvent} from './debug/parse.mjs';
|
|
82
72
|
|
package/authoring/index.mjs
CHANGED
|
@@ -20,7 +20,6 @@ export {
|
|
|
20
20
|
parseGapReportHandler,
|
|
21
21
|
parseGapReportReceipt,
|
|
22
22
|
} from './gap-report/parse.mjs';
|
|
23
|
-
export {parseDiscoverCatalog} from './discover/parse.mjs';
|
|
24
23
|
export {parseCodemod} from './codemod/parse.mjs';
|
|
25
24
|
export {parseDebugEvent} from './debug/parse.mjs';
|
|
26
25
|
export {parseDoc} from './doctypes/parse.mjs';
|
|
@@ -29,17 +29,17 @@ import {program} from './index.mjs';
|
|
|
29
29
|
import {reportsResult, reportsResultVia} from './lib/define-command.mjs';
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Commands with no action of their own:
|
|
33
|
-
* list and does nothing else
|
|
32
|
+
* Commands with no action of their own: bare `astryx layout` prints its
|
|
33
|
+
* subcommand list and does nothing else, so there is no run to report on.
|
|
34
34
|
*
|
|
35
35
|
* Pinned as a SET rather than skipped silently — if a command joins this list,
|
|
36
36
|
* that is a real change to what the CLI does and it should be read, not
|
|
37
|
-
* absorbed.
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* absorbed. (`theme` is deliberately NOT here: it HAS an action — the one that
|
|
38
|
+
* rejects an unknown subcommand — so it goes through the converter like any
|
|
39
|
+
* other. Its bare form prints help and never returns from that action, which
|
|
40
|
+
* the recorder covers where help is recorded, not here.)
|
|
41
41
|
*/
|
|
42
|
-
const NO_ACTION_OF_THEIR_OWN = [];
|
|
42
|
+
const NO_ACTION_OF_THEIR_OWN = ['layout'];
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Commands that record for themselves instead of returning a descriptor.
|