@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
|
@@ -354,31 +354,16 @@ describe('checkImplicitIntegrations', () => {
|
|
|
354
354
|
});
|
|
355
355
|
|
|
356
356
|
it('names the package, the field, and what it contributes', () => {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
components: dir('components'),
|
|
368
|
-
templates: dir('templates'),
|
|
369
|
-
themes: dir('themes'),
|
|
370
|
-
}),
|
|
371
|
-
],
|
|
372
|
-
});
|
|
373
|
-
expect(c.message).toContain('@acme/widgets@1.0.0');
|
|
374
|
-
expect(c.message).toContain('from dependencies');
|
|
375
|
-
expect(c.message).toContain(
|
|
376
|
-
'contributing components, templates, themes',
|
|
377
|
-
);
|
|
378
|
-
expect(c.message).not.toContain('missing on disk');
|
|
379
|
-
} finally {
|
|
380
|
-
fs.rmSync(root, {recursive: true, force: true});
|
|
381
|
-
}
|
|
357
|
+
const c = checkImplicitIntegrations({
|
|
358
|
+
integrations: [
|
|
359
|
+
autolinked({templates: '/abs/templates', themes: '/abs/themes'}),
|
|
360
|
+
],
|
|
361
|
+
});
|
|
362
|
+
expect(c.message).toContain('@acme/widgets@1.0.0');
|
|
363
|
+
expect(c.message).toContain('from dependencies');
|
|
364
|
+
expect(c.message).toContain(
|
|
365
|
+
'contributing components, templates, themes',
|
|
366
|
+
);
|
|
382
367
|
});
|
|
383
368
|
|
|
384
369
|
it('names the declared key too when an npm alias makes them differ', () => {
|
|
@@ -921,100 +906,3 @@ describe('checkDocsProgressiveDisclosure languages', () => {
|
|
|
921
906
|
expect(c.message).not.toMatch(/deploying overview:/);
|
|
922
907
|
});
|
|
923
908
|
});
|
|
924
|
-
|
|
925
|
-
describe('doctor says what it could not check', () => {
|
|
926
|
-
const CORE = {
|
|
927
|
-
'node_modules/@astryxdesign/core/package.json': JSON.stringify({
|
|
928
|
-
name: '@astryxdesign/core',
|
|
929
|
-
version: '0.6.3',
|
|
930
|
-
}),
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
/** Installed dependency whose manifest declares roots that do not exist. */
|
|
934
|
-
const dangling = {
|
|
935
|
-
'node_modules/@acme/dangling/package.json': JSON.stringify({
|
|
936
|
-
name: '@acme/dangling',
|
|
937
|
-
version: '2.0.0',
|
|
938
|
-
}),
|
|
939
|
-
'node_modules/@acme/dangling/astryx.integration.mjs':
|
|
940
|
-
"export default {providerId: 'acme-dangling', components: './components', templates: './templates', docs: './docs'};\n",
|
|
941
|
-
};
|
|
942
|
-
|
|
943
|
-
/** Installed dependency whose manifest cannot be parsed at all. */
|
|
944
|
-
const broken = {
|
|
945
|
-
'node_modules/@acme/broken/package.json': JSON.stringify({
|
|
946
|
-
name: '@acme/broken',
|
|
947
|
-
version: '1.0.0',
|
|
948
|
-
}),
|
|
949
|
-
'node_modules/@acme/broken/astryx.integration.mjs':
|
|
950
|
-
'export default { this is not valid javascript ((\n',
|
|
951
|
-
};
|
|
952
|
-
|
|
953
|
-
/** @param {Record<string, string>} extra @param {string[]} deps */
|
|
954
|
-
const project = (extra, deps) =>
|
|
955
|
-
mkProject({
|
|
956
|
-
'package.json': JSON.stringify({
|
|
957
|
-
name: 'consumer',
|
|
958
|
-
version: '1.0.0',
|
|
959
|
-
dependencies: Object.fromEntries(
|
|
960
|
-
['@astryxdesign/core', ...deps].map(d => [d, '1.0.0']),
|
|
961
|
-
),
|
|
962
|
-
}),
|
|
963
|
-
...CORE,
|
|
964
|
-
...extra,
|
|
965
|
-
});
|
|
966
|
-
|
|
967
|
-
// An unparseable manifest is kept out of the loaded set on purpose, and
|
|
968
|
-
// doctor used to report that no installed dependency ships a manifest.
|
|
969
|
-
it('names an installed dependency whose manifest cannot be loaded', async () => {
|
|
970
|
-
const report = (await doctor({cwd: project(broken, ['@acme/broken'])})).data;
|
|
971
|
-
const check = report.checks.find(c => c.id === 'implicit-integrations');
|
|
972
|
-
|
|
973
|
-
expect(check.status).toBe('info');
|
|
974
|
-
expect(check.message).toContain('@acme/broken');
|
|
975
|
-
expect(check.message).toContain('could not be loaded');
|
|
976
|
-
expect(check.message).not.toContain('no installed dependency ships');
|
|
977
|
-
expect(report.summary.fail).toBe(0);
|
|
978
|
-
}, SLOW);
|
|
979
|
-
|
|
980
|
-
it('does not claim contributions from roots that are missing on disk', async () => {
|
|
981
|
-
const report = (await doctor({cwd: project(dangling, ['@acme/dangling'])}))
|
|
982
|
-
.data;
|
|
983
|
-
const implicit = report.checks.find(c => c.id === 'implicit-integrations');
|
|
984
|
-
const issues = report.checks.find(c => c.id === 'integration-issues');
|
|
985
|
-
|
|
986
|
-
expect(implicit.message).toContain('contributing nothing');
|
|
987
|
-
expect(implicit.message).toContain('missing on disk');
|
|
988
|
-
expect(implicit.message).not.toContain('contributing components');
|
|
989
|
-
expect(issues.status).toBe('warn');
|
|
990
|
-
expect(issues.message).toContain('@acme/dangling');
|
|
991
|
-
}, SLOW);
|
|
992
|
-
|
|
993
|
-
it('still says no dependency ships a manifest when none does', async () => {
|
|
994
|
-
const report = (await doctor({cwd: project({}, [])})).data;
|
|
995
|
-
const check = report.checks.find(c => c.id === 'implicit-integrations');
|
|
996
|
-
|
|
997
|
-
expect(check.message).toBe(
|
|
998
|
-
'None — no installed dependency ships an astryx.integration.* manifest.',
|
|
999
|
-
);
|
|
1000
|
-
}, SLOW);
|
|
1001
|
-
|
|
1002
|
-
it('says how many integrations could not be read, rather than counting silently', () => {
|
|
1003
|
-
/** @type {any} */
|
|
1004
|
-
const ctx = {
|
|
1005
|
-
cwd: '/x',
|
|
1006
|
-
nodeVersion: process.versions.node,
|
|
1007
|
-
coreDir: null,
|
|
1008
|
-
configPath: null,
|
|
1009
|
-
configTheme: null,
|
|
1010
|
-
integrations: [
|
|
1011
|
-
{name: '@acme/ok', __spec: '@acme/ok', providerId: 'ok'},
|
|
1012
|
-
{name: '@acme/bad', __spec: '@acme/bad', __loadError: 'boom'},
|
|
1013
|
-
],
|
|
1014
|
-
};
|
|
1015
|
-
const check = checkProviderIdentity(ctx);
|
|
1016
|
-
|
|
1017
|
-
expect(check.message).toContain('1 loaded integration has its own provider ID.');
|
|
1018
|
-
expect(check.message).toContain('could not be read');
|
|
1019
|
-
});
|
|
1020
|
-
});
|
package/api/index.d.mts
CHANGED
|
@@ -18,7 +18,6 @@ export { integrationAddTheme } from "./integration/add-theme.mjs";
|
|
|
18
18
|
export { integrationPackCheck } from "./integration/pack-check.mjs";
|
|
19
19
|
export { AstryxError } from "./error.mjs";
|
|
20
20
|
export { logger } from "./logger.mjs";
|
|
21
|
-
export * from "../foundation/response/batch.type.mjs";
|
|
22
21
|
export * from "./component/component.type.mjs";
|
|
23
22
|
export * from "./docs/docs.type.mjs";
|
|
24
23
|
export * from "./blog/blog.type.mjs";
|
|
@@ -33,12 +32,14 @@ export * from "./gap-report/gap-report.type.mjs";
|
|
|
33
32
|
export * from "./upgrade/upgrade.type.mjs";
|
|
34
33
|
export * from "./init/init.type.mjs";
|
|
35
34
|
export * from "./doctor/doctor.type.mjs";
|
|
35
|
+
export * from "./layout/layout.type.mjs";
|
|
36
36
|
export * from "./integration/integration-authoring.type.mjs";
|
|
37
37
|
export * from "./integration/pack-check.type.mjs";
|
|
38
38
|
export * from "./integration/validate-integration.type.mjs";
|
|
39
39
|
export * from "./integration/authoring-checks.type.mjs";
|
|
40
40
|
export type Logger = import("./logger.mjs").Logger;
|
|
41
41
|
export { themeBuild, themeAdd, themeTemplate, themeList, themeListAvailable, themeTargets, themePaletteGenerate, generateTonalPalette, listThemes } from "./theme/theme.mjs";
|
|
42
|
+
export { layoutExpand, layoutCheck, layoutGrammar } from "./layout/layout.mjs";
|
|
42
43
|
export { integrationAdd, integrationAddAgentDoc, integrationAddCodemod, integrationAddComponent, integrationAddDoc, integrationAddTemplate } from "./integration/add-contribution.mjs";
|
|
43
44
|
export { validateIntegration, summarizeIssues } from "./integration/validate-integration.mjs";
|
|
44
45
|
export { integrationTemplateConflicts, integrationComponentConflicts, integrationDocConflicts } from "./integration/authoring-checks.mjs";
|
package/api/index.mjs
CHANGED
|
@@ -44,6 +44,7 @@ export {gapReport} from './gap-report/gap-report.mjs';
|
|
|
44
44
|
export {upgrade} from './upgrade/upgrade.mjs';
|
|
45
45
|
export {init} from './init/init.mjs';
|
|
46
46
|
export {doctor} from './doctor/doctor.mjs';
|
|
47
|
+
export {layoutExpand, layoutCheck, layoutGrammar} from './layout/layout.mjs';
|
|
47
48
|
export {
|
|
48
49
|
integrationAdd,
|
|
49
50
|
integrationAddAgentDoc,
|
|
@@ -72,12 +73,10 @@ export {logger} from './logger.mjs';
|
|
|
72
73
|
* @typedef {import('./logger.mjs').Logger} Logger
|
|
73
74
|
*/
|
|
74
75
|
|
|
75
|
-
// ── Types (re-exported from
|
|
76
|
-
// colocated `.type.mjs`) ──────────────────────────────────────────────────
|
|
76
|
+
// ── Types (re-exported from each command's colocated `.type.mjs`) ─────
|
|
77
77
|
// Runtime no-ops (the .type.mjs files are `export {}`); tsc carries these
|
|
78
78
|
// through to the generated api/index.d.mts so the public type surface exposes
|
|
79
|
-
//
|
|
80
|
-
export * from '../foundation/response/batch.type.mjs';
|
|
79
|
+
// every command's Options + response types by name.
|
|
81
80
|
export * from './component/component.type.mjs';
|
|
82
81
|
export * from './docs/docs.type.mjs';
|
|
83
82
|
export * from './blog/blog.type.mjs';
|
|
@@ -92,6 +91,7 @@ export * from './gap-report/gap-report.type.mjs';
|
|
|
92
91
|
export * from './upgrade/upgrade.type.mjs';
|
|
93
92
|
export * from './init/init.type.mjs';
|
|
94
93
|
export * from './doctor/doctor.type.mjs';
|
|
94
|
+
export * from './layout/layout.type.mjs';
|
|
95
95
|
export * from './integration/integration-authoring.type.mjs';
|
|
96
96
|
export * from './integration/pack-check.type.mjs';
|
|
97
97
|
export * from './integration/validate-integration.type.mjs';
|
|
@@ -8,7 +8,7 @@ export function findPackageDir(startDir: string): string;
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {object} WritePlan
|
|
10
10
|
* @property {string} path
|
|
11
|
-
* @property {string
|
|
11
|
+
* @property {string} contents
|
|
12
12
|
* @property {boolean} createOnly
|
|
13
13
|
* @property {Buffer} [expectedOriginal] bytes captured before validation;
|
|
14
14
|
* a different current file is a concurrent edit and must never be overwritten
|
|
@@ -56,10 +56,7 @@ export function packageJsonUpdate(packageFile: string, rootPath: string, manifes
|
|
|
56
56
|
export function applyWrites(plans: WritePlan[]): () => void;
|
|
57
57
|
export type WritePlan = {
|
|
58
58
|
path: string;
|
|
59
|
-
|
|
60
|
-
* a Buffer is written byte for byte
|
|
61
|
-
*/
|
|
62
|
-
contents: string | Buffer;
|
|
59
|
+
contents: string;
|
|
63
60
|
createOnly: boolean;
|
|
64
61
|
/**
|
|
65
62
|
* bytes captured before validation;
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Hosts the atomic staged-write transaction, package.json files-array
|
|
7
7
|
* maintenance, package-dir resolution, and project-path normalization that
|
|
8
|
-
* every `integration add <kind>` command needs.
|
|
9
|
-
* write through the same transaction. Stateless and side-effect-free
|
|
8
|
+
* every `integration add <kind>` command needs. Stateless and side-effect-free
|
|
10
9
|
* outside of {@link applyWrites}.
|
|
11
10
|
*/
|
|
12
11
|
|
|
@@ -41,7 +40,7 @@ export function findPackageDir(startDir) {
|
|
|
41
40
|
/**
|
|
42
41
|
* @typedef {object} WritePlan
|
|
43
42
|
* @property {string} path
|
|
44
|
-
* @property {string
|
|
43
|
+
* @property {string} contents
|
|
45
44
|
* @property {boolean} createOnly
|
|
46
45
|
* @property {Buffer} [expectedOriginal] bytes captured before validation;
|
|
47
46
|
* a different current file is a concurrent edit and must never be overwritten
|
|
@@ -193,29 +192,21 @@ export function packageJsonUpdate(
|
|
|
193
192
|
|
|
194
193
|
// ── Atomic staged-write transaction ─────────────────────────────────
|
|
195
194
|
|
|
196
|
-
/**
|
|
197
|
-
* @param {string} file
|
|
198
|
-
* @returns {boolean} false when the file is still there
|
|
199
|
-
*/
|
|
195
|
+
/** @param {string} file */
|
|
200
196
|
function removeTemporary(file) {
|
|
201
197
|
try {
|
|
202
198
|
fs.rmSync(file, {force: true});
|
|
203
|
-
return true;
|
|
204
199
|
} catch {
|
|
205
200
|
// Best effort. The transaction error remains the actionable failure.
|
|
206
|
-
return false;
|
|
207
201
|
}
|
|
208
202
|
}
|
|
209
203
|
|
|
210
204
|
/**
|
|
211
205
|
* Restore writes that already published. Best-effort so callers preserve
|
|
212
|
-
* the original actionable error
|
|
206
|
+
* the original actionable error.
|
|
213
207
|
* @param {Array<WritePlan & {temporary: string, original: Buffer|null, mode: number}>} published
|
|
214
|
-
* @returns {string[]}
|
|
215
208
|
*/
|
|
216
209
|
function rollbackWrites(published) {
|
|
217
|
-
/** @type {string[]} */
|
|
218
|
-
const unrestored = [];
|
|
219
210
|
for (const plan of [...published].reverse()) {
|
|
220
211
|
let restore = null;
|
|
221
212
|
try {
|
|
@@ -234,18 +225,12 @@ function rollbackWrites(published) {
|
|
|
234
225
|
fs.renameSync(restore, plan.path);
|
|
235
226
|
restore = null;
|
|
236
227
|
}
|
|
237
|
-
} catch
|
|
238
|
-
//
|
|
239
|
-
// leaves this call's bytes, or no bytes, where the original was.
|
|
240
|
-
const gone =
|
|
241
|
-
error instanceof Error &&
|
|
242
|
-
/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT';
|
|
243
|
-
if (!(gone && plan.original == null)) unrestored.push(plan.path);
|
|
228
|
+
} catch {
|
|
229
|
+
// Best effort. A concurrent edit belongs to its writer, not this rollback.
|
|
244
230
|
} finally {
|
|
245
231
|
if (restore != null) removeTemporary(restore);
|
|
246
232
|
}
|
|
247
233
|
}
|
|
248
|
-
return unrestored;
|
|
249
234
|
}
|
|
250
235
|
|
|
251
236
|
/** @param {string} file */
|
|
@@ -340,22 +325,10 @@ export function applyWrites(plans) {
|
|
|
340
325
|
}
|
|
341
326
|
published.push(plan);
|
|
342
327
|
}
|
|
343
|
-
return () =>
|
|
344
|
-
rollbackWrites(published);
|
|
345
|
-
};
|
|
328
|
+
return () => rollbackWrites(published);
|
|
346
329
|
} catch (error) {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
.map(plan => plan.temporary);
|
|
350
|
-
const unrestored = rollbackWrites(published);
|
|
351
|
-
if (error instanceof Error) {
|
|
352
|
-
if (unrestored.length > 0) {
|
|
353
|
-
error.message += ` Could not restore: ${unrestored.join(', ')}.`;
|
|
354
|
-
}
|
|
355
|
-
if (leftovers.length > 0) {
|
|
356
|
-
error.message += ` Could not remove temporary files: ${leftovers.join(', ')}.`;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
330
|
+
for (const plan of staged) removeTemporary(plan.temporary);
|
|
331
|
+
rollbackWrites(published);
|
|
359
332
|
throw error;
|
|
360
333
|
}
|
|
361
334
|
}
|
|
@@ -162,22 +162,6 @@ export function parseNpmPackOutput(output) {
|
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/**
|
|
166
|
-
* Summarize the JSON error npm prints on stdout when `--json` pack fails.
|
|
167
|
-
* @param {string} output
|
|
168
|
-
* @returns {string}
|
|
169
|
-
*/
|
|
170
|
-
function npmPackErrorDetail(output) {
|
|
171
|
-
try {
|
|
172
|
-
const error = JSON.parse(output)?.error;
|
|
173
|
-
return [error?.summary, error?.detail]
|
|
174
|
-
.filter(part => typeof part === 'string' && part.trim() !== '')
|
|
175
|
-
.join(': ');
|
|
176
|
-
} catch {
|
|
177
|
-
return (output || '').trim();
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
165
|
/**
|
|
182
166
|
* Run `npm pack --json` with output directed to `destDir` so no preexisting
|
|
183
167
|
* tgz is overwritten. This intentionally runs the package lifecycle, matching
|
|
@@ -191,25 +175,16 @@ function npmPackErrorDetail(output) {
|
|
|
191
175
|
function runNpmPack(packageDir, destDir) {
|
|
192
176
|
const result = spawnSync(
|
|
193
177
|
'npm',
|
|
194
|
-
[
|
|
195
|
-
'pack',
|
|
196
|
-
'--json',
|
|
197
|
-
'--silent',
|
|
198
|
-
// Lifecycle scripts still run; background mode keeps their output off
|
|
199
|
-
// the stdout that carries the JSON result.
|
|
200
|
-
'--foreground-scripts=false',
|
|
201
|
-
`--pack-destination=${destDir}`,
|
|
202
|
-
],
|
|
178
|
+
['pack', '--json', '--silent', `--pack-destination=${destDir}`],
|
|
203
179
|
{cwd: packageDir, encoding: 'utf-8', timeout: 60_000},
|
|
204
180
|
);
|
|
205
181
|
if (result.error) {
|
|
206
182
|
throw new Error(`Could not start npm pack: ${result.error.message}`);
|
|
207
183
|
}
|
|
208
184
|
if (result.status !== 0) {
|
|
209
|
-
const
|
|
210
|
-
(result.stderr || '').trim() || npmPackErrorDetail(result.stdout);
|
|
185
|
+
const stderr = (result.stderr || '').trim();
|
|
211
186
|
throw new Error(
|
|
212
|
-
`npm pack failed (exit ${result.status})${
|
|
187
|
+
`npm pack failed (exit ${result.status})${stderr ? `: ${stderr}` : ''}.`,
|
|
213
188
|
);
|
|
214
189
|
}
|
|
215
190
|
return parseNpmPackOutput(result.stdout);
|
package/api/json/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export type * from '../gap-report/gap-report.type.mjs';
|
|
|
26
26
|
export type * from '../upgrade/upgrade.type.mjs';
|
|
27
27
|
export type * from '../init/init.type.mjs';
|
|
28
28
|
export type * from '../doctor/doctor.type.mjs';
|
|
29
|
+
export type * from '../layout/layout.type.mjs';
|
|
29
30
|
export type * from '../integration/validate-integration.type.mjs';
|
|
30
31
|
export type * from '../integration/authoring-checks.type.mjs';
|
|
31
32
|
export type * from '../integration/pack-check.type.mjs';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/** @param {import('../../foundation/xle/xle-ast').RawIssue} issue */
|
|
5
|
+
export function formatIssue(issue: import("../../foundation/xle/xle-ast").RawIssue): string;
|
|
6
|
+
/**
|
|
7
|
+
* Parse + validate, throwing structured XDSErrors on failure.
|
|
8
|
+
* Returns {doc, registry, blocks, warnings}.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} expression
|
|
11
|
+
* @param {{form?: 'compact'|'outline'|'auto', loose?: boolean, cwd?: string}} [options]
|
|
12
|
+
*/
|
|
13
|
+
export function analyze(expression: string, { form, loose, cwd }?: {
|
|
14
|
+
form?: "compact" | "outline" | "auto";
|
|
15
|
+
loose?: boolean;
|
|
16
|
+
cwd?: string;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
doc: import("../../foundation/xle/xle-ast").XLEDoc;
|
|
19
|
+
registry: import("../../foundation/xle/xle-ast").Registry;
|
|
20
|
+
blocks: LayoutBlock[];
|
|
21
|
+
errors: import("../../foundation/xle/xle-ast").RawIssue[];
|
|
22
|
+
warnings: import("../../foundation/xle/xle-ast").RawIssue[];
|
|
23
|
+
}>;
|
|
24
|
+
export type LayoutBlock = {
|
|
25
|
+
dirName: string;
|
|
26
|
+
name: string;
|
|
27
|
+
kind: "template" | "component";
|
|
28
|
+
type?: string | undefined;
|
|
29
|
+
description?: string | undefined;
|
|
30
|
+
category?: string | undefined;
|
|
31
|
+
importPath?: string | undefined;
|
|
32
|
+
filePath?: string | undefined;
|
|
33
|
+
isDefault?: boolean | undefined;
|
|
34
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Shared analysis layer for the `layout` command's expand/check leaves.
|
|
5
|
+
*
|
|
6
|
+
* Loads the block catalog (template blocks + app-registered components), builds
|
|
7
|
+
* the branch registry, and parses+validates a layout expression into a doc +
|
|
8
|
+
* issues. Both the expand and check leaves sit on `analyze`; neither parses or
|
|
9
|
+
* validates directly. (The grammar leaf needs none of this — it only reads the
|
|
10
|
+
* registry alias table.)
|
|
11
|
+
*
|
|
12
|
+
* @input expression string (+ options)
|
|
13
|
+
* @output analyze() -> {doc, registry, blocks, errors, warnings}; formatIssue()
|
|
14
|
+
* @position api — shared core over lib/xle; leaves in expand/ + check/ project it
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {AstryxError} from '../error.mjs';
|
|
18
|
+
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
19
|
+
import {parse, XLEParseError} from '../../foundation/xle/parse.mjs';
|
|
20
|
+
import {validate} from '../../foundation/xle/validate.mjs';
|
|
21
|
+
import {buildRegistry} from '../../foundation/xle/registry.mjs';
|
|
22
|
+
import {discoverTemplates} from '../template/template.mjs';
|
|
23
|
+
import {Project} from '../../foundation/config/project.mjs';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} LayoutBlock
|
|
27
|
+
* @property {string} dirName
|
|
28
|
+
* @property {string} name
|
|
29
|
+
* @property {'template'|'component'} kind
|
|
30
|
+
* @property {string} [type]
|
|
31
|
+
* @property {string} [description]
|
|
32
|
+
* @property {string} [category]
|
|
33
|
+
* @property {string} [importPath]
|
|
34
|
+
* @property {string} [filePath]
|
|
35
|
+
* @property {boolean} [isDefault]
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The catalog a `{hint}` can resolve to: template blocks (spliced inline) plus
|
|
40
|
+
* any app-registered local components from astryx.config.mjs
|
|
41
|
+
* `experimental.xle.components` (imported by name). App components are how XLE
|
|
42
|
+
* reaches domain pieces — the KpiCard/chart/drawer set that the
|
|
43
|
+
* @astryxdesign/core registry can't see.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} cwd
|
|
46
|
+
* @returns {Promise<LayoutBlock[]>}
|
|
47
|
+
*/
|
|
48
|
+
async function loadBlocks(cwd) {
|
|
49
|
+
/** @type {LayoutBlock[]} */
|
|
50
|
+
const blocks = [];
|
|
51
|
+
try {
|
|
52
|
+
const all = await discoverTemplates(cwd);
|
|
53
|
+
const templates = all.filter(template => template.type === 'block');
|
|
54
|
+
/** @type {Map<string, import('../../foundation/discovery/template-adapter.mjs').DiscoveredTemplate>} */
|
|
55
|
+
const byId = new Map();
|
|
56
|
+
// Exact ids are the fallback. Active replacement aliases override them in a
|
|
57
|
+
// second pass, matching template() regardless of discovery/display order.
|
|
58
|
+
for (const template of templates) byId.set(template.dirName, template);
|
|
59
|
+
for (const template of templates) {
|
|
60
|
+
if (template.replaces != null) byId.set(template.replaces, template);
|
|
61
|
+
}
|
|
62
|
+
for (const [id, template] of byId) {
|
|
63
|
+
blocks.push({...template, dirName: id, kind: 'template'});
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// discovery is best-effort
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const project = await Project.load(cwd);
|
|
70
|
+
/** @type {Record<string, {from?: string, description?: string, default?: boolean}>} */
|
|
71
|
+
const components = project.config.experimental?.xle?.components ?? {};
|
|
72
|
+
for (const [name, spec] of Object.entries(components)) {
|
|
73
|
+
const importPath = spec.from;
|
|
74
|
+
if (!importPath) continue;
|
|
75
|
+
blocks.push({
|
|
76
|
+
type: 'block',
|
|
77
|
+
kind: 'component',
|
|
78
|
+
dirName: name,
|
|
79
|
+
name,
|
|
80
|
+
description: spec.description ?? '',
|
|
81
|
+
category: 'app',
|
|
82
|
+
importPath,
|
|
83
|
+
isDefault: Boolean(spec.default),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// config is optional
|
|
88
|
+
}
|
|
89
|
+
return blocks;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @param {import('../../foundation/xle/xle-ast').RawIssue} issue */
|
|
93
|
+
export function formatIssue(issue) {
|
|
94
|
+
const where = issue.line != null ? `line ${issue.line}: ` : '';
|
|
95
|
+
return `${where}${issue.message}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse + validate, throwing structured XDSErrors on failure.
|
|
100
|
+
* Returns {doc, registry, blocks, warnings}.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} expression
|
|
103
|
+
* @param {{form?: 'compact'|'outline'|'auto', loose?: boolean, cwd?: string}} [options]
|
|
104
|
+
*/
|
|
105
|
+
export async function analyze(
|
|
106
|
+
expression,
|
|
107
|
+
{form = 'auto', loose = false, cwd = process.cwd()} = {},
|
|
108
|
+
) {
|
|
109
|
+
// Validate inputs in the API (not just the CLI): an empty expression or an
|
|
110
|
+
// unknown --form must error, not silently parse as an empty/compact layout.
|
|
111
|
+
if (typeof expression !== 'string' || expression.trim() === '') {
|
|
112
|
+
throw new AstryxError(
|
|
113
|
+
'Layout expression is empty.',
|
|
114
|
+
undefined,
|
|
115
|
+
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (form !== 'compact' && form !== 'outline' && form !== 'auto') {
|
|
119
|
+
throw new AstryxError(
|
|
120
|
+
`Invalid form "${form}". Must be one of: compact, outline, auto.`,
|
|
121
|
+
undefined,
|
|
122
|
+
ERROR_CODES.ERR_INVALID_OPTION,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const registry =
|
|
126
|
+
/** @type {import('../../foundation/xle/xle-ast').Registry} */ (
|
|
127
|
+
/** @type {unknown} */ (await buildRegistry({cwd}))
|
|
128
|
+
);
|
|
129
|
+
const blocks = await loadBlocks(cwd);
|
|
130
|
+
|
|
131
|
+
/** @type {import('../../foundation/xle/xle-ast').XLEDoc} */
|
|
132
|
+
let doc;
|
|
133
|
+
try {
|
|
134
|
+
doc = parse(expression, {form});
|
|
135
|
+
} catch (e) {
|
|
136
|
+
if (e instanceof XLEParseError) {
|
|
137
|
+
throw new AstryxError(
|
|
138
|
+
`Layout expression syntax error at line ${e.line}, col ${e.col}: ${e.message}`,
|
|
139
|
+
undefined,
|
|
140
|
+
ERROR_CODES.ERR_LAYOUT_PARSE,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
throw e;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const {errors, warnings} = validate(doc, registry, blocks, {loose});
|
|
147
|
+
return {doc, registry, blocks, errors, warnings};
|
|
148
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `astryx layout check "<expr>" [--form compact|outline]`
|
|
6
|
+
* Validates without expanding; echoes both canonical surfaces.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} expression
|
|
9
|
+
* @param {{form?: 'compact'|'outline'|'auto', loose?: boolean, cwd?: string}} [options]
|
|
10
|
+
* @returns {Promise<import('../layout.type.mjs').LayoutCheckResponse>}
|
|
11
|
+
*/
|
|
12
|
+
export function layoutCheck(expression: string, options?: {
|
|
13
|
+
form?: "compact" | "outline" | "auto";
|
|
14
|
+
loose?: boolean;
|
|
15
|
+
cwd?: string;
|
|
16
|
+
}): Promise<import("../layout.type.mjs").LayoutCheckResponse>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file `astryx layout check` leaf — validate a layout expression without
|
|
5
|
+
* expanding, echoing both canonical surfaces (compact + outline). Resolution +
|
|
6
|
+
* validation come from ../_adapter.mjs (analyze); this leaf projects the
|
|
7
|
+
* `layout.check` envelope.
|
|
8
|
+
*
|
|
9
|
+
* @input expression string (+ options)
|
|
10
|
+
* @output {type:'layout.check', data}
|
|
11
|
+
* @position api — leaf over ../_adapter.mjs + lib/xle/print
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {toCompact, toOutline} from '../../../foundation/xle/print.mjs';
|
|
15
|
+
import {analyze, formatIssue} from '../_adapter.mjs';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* `astryx layout check "<expr>" [--form compact|outline]`
|
|
19
|
+
* Validates without expanding; echoes both canonical surfaces.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} expression
|
|
22
|
+
* @param {{form?: 'compact'|'outline'|'auto', loose?: boolean, cwd?: string}} [options]
|
|
23
|
+
* @returns {Promise<import('../layout.type.mjs').LayoutCheckResponse>}
|
|
24
|
+
*/
|
|
25
|
+
export async function layoutCheck(expression, options = {}) {
|
|
26
|
+
const {form = 'auto', loose = false, cwd = process.cwd()} = options;
|
|
27
|
+
const {doc, errors, warnings} = await analyze(expression, {form, loose, cwd});
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
type: 'layout.check',
|
|
31
|
+
data: {
|
|
32
|
+
valid: errors.length === 0,
|
|
33
|
+
form: doc.form,
|
|
34
|
+
errors: errors.map(e => ({...e, formatted: formatIssue(e)})),
|
|
35
|
+
warnings: warnings.map(formatIssue),
|
|
36
|
+
compact: toCompact(doc),
|
|
37
|
+
outline: toOutline(doc),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
|
|
2
|
+
// DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `astryx layout expand "<expr>" [path]`
|
|
6
|
+
*
|
|
7
|
+
* @param {string} expression
|
|
8
|
+
* @param {object} [options]
|
|
9
|
+
* @param {string} [options.targetPath] - write TSX here (validated against cwd)
|
|
10
|
+
* @param {'compact'|'outline'|'auto'} [options.form]
|
|
11
|
+
* @param {boolean} [options.loose] - downgrade unknown {hints} to TODO warnings
|
|
12
|
+
* @param {string} [options.name] - generated component name
|
|
13
|
+
* @param {string} [options.cwd]
|
|
14
|
+
* @returns {Promise<import('../layout.type.mjs').LayoutExpandResponse>}
|
|
15
|
+
*/
|
|
16
|
+
export function layoutExpand(expression: string, options?: {
|
|
17
|
+
targetPath?: string | undefined;
|
|
18
|
+
form?: "compact" | "auto" | "outline" | undefined;
|
|
19
|
+
loose?: boolean | undefined;
|
|
20
|
+
name?: string | undefined;
|
|
21
|
+
cwd?: string | undefined;
|
|
22
|
+
}): Promise<import("../layout.type.mjs").LayoutExpandResponse>;
|