@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
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file Tests for the pure views over discover sources' catalogs.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {describe, expect, it} from 'vitest';
|
|
8
|
-
import {
|
|
9
|
-
availableEntries,
|
|
10
|
-
catalogState,
|
|
11
|
-
kindLists,
|
|
12
|
-
searchItems,
|
|
13
|
-
withLatest,
|
|
14
|
-
} from './_catalog-view.mjs';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @param {string} name
|
|
18
|
-
* @param {string} integration
|
|
19
|
-
* @param {object} [extra]
|
|
20
|
-
* @returns {any}
|
|
21
|
-
*/
|
|
22
|
-
function entry(name, integration, extra = {}) {
|
|
23
|
-
return {
|
|
24
|
-
package: name,
|
|
25
|
-
integration,
|
|
26
|
-
aliases: [],
|
|
27
|
-
latest: '1.0.0',
|
|
28
|
-
versions: [],
|
|
29
|
-
contributions: [{kind: 'component', name: 'A'}],
|
|
30
|
-
source: 'S',
|
|
31
|
-
...extra,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const CATALOG = {
|
|
36
|
-
sources: [],
|
|
37
|
-
packages: [
|
|
38
|
-
entry('@acme/ui', 'acme-ui', {aliases: ['@acme/ui-classic']}),
|
|
39
|
-
entry('@acme/ui-classic', 'acme-ui', {aliases: ['@acme/ui']}),
|
|
40
|
-
entry('@acme/charts', 'charts', {
|
|
41
|
-
latest: '2.0.0',
|
|
42
|
-
contributions: [{kind: 'theme', name: 'ocean'}],
|
|
43
|
-
}),
|
|
44
|
-
],
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/** @param {string} name @returns {any} */
|
|
48
|
-
const installed = name => ({name, category: name, components: []});
|
|
49
|
-
|
|
50
|
-
describe('availableEntries', () => {
|
|
51
|
-
it('offers each integration once, under the first name a source lists', () => {
|
|
52
|
-
expect(
|
|
53
|
-
availableEntries(CATALOG, [], new Set()).map(e => [e.name, e.aliases]),
|
|
54
|
-
).toEqual([
|
|
55
|
-
['@acme/ui', ['@acme/ui-classic']],
|
|
56
|
-
['@acme/charts', undefined],
|
|
57
|
-
]);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('never offers an integration the project has under another name', () => {
|
|
61
|
-
expect(
|
|
62
|
-
availableEntries(CATALOG, [installed('@acme/ui-classic')], new Set()).map(
|
|
63
|
-
e => e.name,
|
|
64
|
-
),
|
|
65
|
-
).toEqual(['@acme/charts']);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('does not offer a package the project declares, even when it did not load', () => {
|
|
69
|
-
expect(
|
|
70
|
-
availableEntries(CATALOG, [], new Set(['@acme/charts'])).map(e => e.name),
|
|
71
|
-
).toEqual(['@acme/ui']);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('describes an entry the way an installed one is described', () => {
|
|
75
|
-
expect(availableEntries(CATALOG, [], new Set())[1]).toEqual({
|
|
76
|
-
name: '@acme/charts',
|
|
77
|
-
components: [],
|
|
78
|
-
version: '2.0.0',
|
|
79
|
-
themes: ['ocean'],
|
|
80
|
-
source: 'S',
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('catalogState', () => {
|
|
86
|
-
it('names the package the project has instead', () => {
|
|
87
|
-
expect(
|
|
88
|
-
catalogState(CATALOG.packages[0], new Set(['@acme/ui-classic']), new Set()),
|
|
89
|
-
).toEqual({state: 'alias', installedAs: '@acme/ui-classic'});
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('withLatest', () => {
|
|
94
|
-
it('adds the latest release a source knows to an installed package', () => {
|
|
95
|
-
expect(withLatest([installed('@acme/charts')], CATALOG)[0]).toMatchObject({
|
|
96
|
-
latest: '2.0.0',
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('kindLists', () => {
|
|
102
|
-
it('groups items by kind, always with components', () => {
|
|
103
|
-
expect(
|
|
104
|
-
kindLists([
|
|
105
|
-
{kind: 'theme', name: 'ocean'},
|
|
106
|
-
{kind: 'component', name: 'A'},
|
|
107
|
-
]),
|
|
108
|
-
).toEqual({components: ['A'], themes: ['ocean']});
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('searchItems', () => {
|
|
113
|
-
it('covers installed packages and their other items, then what could be added', () => {
|
|
114
|
-
const items = searchItems(
|
|
115
|
-
[{...installed('@acme/widgets'), templates: ['pages/Home']}],
|
|
116
|
-
CATALOG,
|
|
117
|
-
new Set(),
|
|
118
|
-
);
|
|
119
|
-
expect(items.map(i => [i.package, i.kind, i.name, i.installed])).toEqual([
|
|
120
|
-
['@acme/widgets', 'package', '@acme/widgets', true],
|
|
121
|
-
['@acme/widgets', 'template', 'pages/Home', true],
|
|
122
|
-
['@acme/ui', 'package', '@acme/ui', false],
|
|
123
|
-
['@acme/ui', 'component', 'A', false],
|
|
124
|
-
['@acme/charts', 'package', '@acme/charts', false],
|
|
125
|
-
['@acme/charts', 'theme', 'ocean', false],
|
|
126
|
-
]);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
-
* Find one item of a package: an installed package's own templates, docs,
|
|
6
|
-
* themes, and codemods first, then what a discover source lists for the
|
|
7
|
-
* package. Returns null when neither has it, so the caller can report the
|
|
8
|
-
* component lookup's own error.
|
|
9
|
-
*
|
|
10
|
-
* @param {import('../../_adapter.mjs').InstalledPackage[]} packages
|
|
11
|
-
* @param {string} packageName
|
|
12
|
-
* @param {string} itemName
|
|
13
|
-
* @param {{
|
|
14
|
-
* catalog?: import('../../_catalog-view.mjs').CatalogPackage,
|
|
15
|
-
* version?: string,
|
|
16
|
-
* add?: (name: string, version?: string) => string,
|
|
17
|
-
* installedAs?: string,
|
|
18
|
-
* }} [options]
|
|
19
|
-
* @returns {import('../../discover.type.mjs').DiscoverItemResponse | null}
|
|
20
|
-
*/
|
|
21
|
-
export function item(packages: import("../../_adapter.mjs").InstalledPackage[], packageName: string, itemName: string, options?: {
|
|
22
|
-
catalog?: import("../../_catalog-view.mjs").CatalogPackage;
|
|
23
|
-
version?: string;
|
|
24
|
-
add?: (name: string, version?: string) => string;
|
|
25
|
-
installedAs?: string;
|
|
26
|
-
}): import("../../discover.type.mjs").DiscoverItemResponse | null;
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file discover.item leaf — one item a package adds, when it is not an
|
|
5
|
-
* installed component (those keep resolving to their full doc through
|
|
6
|
-
* ../doc).
|
|
7
|
-
*
|
|
8
|
-
* @position api/discover/detail/item — pure projection over the packages and
|
|
9
|
-
* catalog entry resolved by ../../_adapter.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import {KIND_FIELDS} from '../../_adapter.mjs';
|
|
13
|
-
import {defaultVersion} from '../../_catalog-view.mjs';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Find one item of a package: an installed package's own templates, docs,
|
|
17
|
-
* themes, and codemods first, then what a discover source lists for the
|
|
18
|
-
* package. Returns null when neither has it, so the caller can report the
|
|
19
|
-
* component lookup's own error.
|
|
20
|
-
*
|
|
21
|
-
* @param {import('../../_adapter.mjs').InstalledPackage[]} packages
|
|
22
|
-
* @param {string} packageName
|
|
23
|
-
* @param {string} itemName
|
|
24
|
-
* @param {{
|
|
25
|
-
* catalog?: import('../../_catalog-view.mjs').CatalogPackage,
|
|
26
|
-
* version?: string,
|
|
27
|
-
* add?: (name: string, version?: string) => string,
|
|
28
|
-
* installedAs?: string,
|
|
29
|
-
* }} [options]
|
|
30
|
-
* @returns {import('../../discover.type.mjs').DiscoverItemResponse | null}
|
|
31
|
-
*/
|
|
32
|
-
export function item(packages, packageName, itemName, options = {}) {
|
|
33
|
-
const {catalog, version, add, installedAs} = options;
|
|
34
|
-
const pkg = packages.find(p => p.name === packageName);
|
|
35
|
-
if (pkg && (!version || version === pkg.version)) {
|
|
36
|
-
for (const [kind, field] of KIND_FIELDS) {
|
|
37
|
-
if (kind === 'component') continue;
|
|
38
|
-
const names = /** @type {Record<string, unknown>} */ (pkg)[field];
|
|
39
|
-
if (Array.isArray(names) && names.includes(itemName)) {
|
|
40
|
-
return {
|
|
41
|
-
type: 'discover.item',
|
|
42
|
-
data: {
|
|
43
|
-
package: packageName,
|
|
44
|
-
...(pkg.version ? {version: pkg.version} : {}),
|
|
45
|
-
kind,
|
|
46
|
-
name: itemName,
|
|
47
|
-
installed: true,
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const lower = itemName.toLowerCase();
|
|
55
|
-
const found =
|
|
56
|
-
catalog?.contributions.find(c => c.name === itemName) ??
|
|
57
|
-
catalog?.contributions.find(c => c.name.toLowerCase() === lower);
|
|
58
|
-
if (!catalog || !found) return null;
|
|
59
|
-
const shown = version ?? defaultVersion(catalog);
|
|
60
|
-
return {
|
|
61
|
-
type: 'discover.item',
|
|
62
|
-
data: {
|
|
63
|
-
package: packageName,
|
|
64
|
-
...(shown ? {version: shown} : {}),
|
|
65
|
-
kind: found.kind,
|
|
66
|
-
name: found.name,
|
|
67
|
-
...(found.title ? {title: found.title} : {}),
|
|
68
|
-
...(found.summary ? {summary: found.summary} : {}),
|
|
69
|
-
...(found.keywords ? {keywords: found.keywords} : {}),
|
|
70
|
-
installed: pkg != null,
|
|
71
|
-
...(pkg == null && installedAs ? {installedAs} : {}),
|
|
72
|
-
...(pkg == null && !installedAs && add
|
|
73
|
-
? {install: add(packageName, version)}
|
|
74
|
-
: {}),
|
|
75
|
-
source: catalog.source,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file Tests for the discover.item leaf, a pure projection over installed
|
|
5
|
-
* packages and a source's catalog entry.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {describe, expect, it} from 'vitest';
|
|
9
|
-
import {item} from './item.mjs';
|
|
10
|
-
|
|
11
|
-
/** @type {any[]} */
|
|
12
|
-
const PACKAGES = [
|
|
13
|
-
{
|
|
14
|
-
name: '@acme/widgets',
|
|
15
|
-
category: '@acme/widgets',
|
|
16
|
-
version: '1.2.3',
|
|
17
|
-
components: ['Alpha'],
|
|
18
|
-
templates: ['pages/Home'],
|
|
19
|
-
docs: ['guide'],
|
|
20
|
-
},
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
/** @type {any} */
|
|
24
|
-
const CATALOG = {
|
|
25
|
-
package: '@acme/charts',
|
|
26
|
-
integration: 'acme-charts',
|
|
27
|
-
aliases: [],
|
|
28
|
-
latest: '2.0.0',
|
|
29
|
-
source: 'Acme',
|
|
30
|
-
versions: [],
|
|
31
|
-
contributions: [{kind: 'template', name: 'pages/Report', title: 'Report page'}],
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/** @param {string} name @param {string} [version] */
|
|
35
|
-
const add = (name, version) => `pnpm add ${version ? `${name}@${version}` : name}`;
|
|
36
|
-
|
|
37
|
-
describe('discover.item leaf', () => {
|
|
38
|
-
it("finds an installed package's own template, doc, theme, or codemod", () => {
|
|
39
|
-
expect(item(PACKAGES, '@acme/widgets', 'pages/Home')).toEqual({
|
|
40
|
-
type: 'discover.item',
|
|
41
|
-
data: {
|
|
42
|
-
package: '@acme/widgets',
|
|
43
|
-
version: '1.2.3',
|
|
44
|
-
kind: 'template',
|
|
45
|
-
name: 'pages/Home',
|
|
46
|
-
installed: true,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('finds an item a source lists, with the command that adds its package', () => {
|
|
52
|
-
expect(
|
|
53
|
-
item(PACKAGES, '@acme/charts', 'pages/report', {catalog: CATALOG, add}),
|
|
54
|
-
).toEqual({
|
|
55
|
-
type: 'discover.item',
|
|
56
|
-
data: {
|
|
57
|
-
package: '@acme/charts',
|
|
58
|
-
version: '2.0.0',
|
|
59
|
-
kind: 'template',
|
|
60
|
-
name: 'pages/Report',
|
|
61
|
-
title: 'Report page',
|
|
62
|
-
installed: false,
|
|
63
|
-
install: 'pnpm add @acme/charts',
|
|
64
|
-
source: 'Acme',
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('returns null when neither the project nor a source has it', () => {
|
|
70
|
-
expect(item(PACKAGES, '@acme/widgets', 'Nope')).toBeNull();
|
|
71
|
-
expect(item(PACKAGES, '@acme/charts', 'Nope', {catalog: CATALOG})).toBeNull();
|
|
72
|
-
});
|
|
73
|
-
});
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
|
4
|
-
import * as fs from 'node:fs';
|
|
5
|
-
import * as path from 'node:path';
|
|
6
|
-
import {integrationPackCheck} from './pack-check.mjs';
|
|
7
|
-
|
|
8
|
-
let tmpDir;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
tmpDir = fs.mkdtempSync(path.join(process.cwd(), '.astryx-pack-check-'));
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
/** @param {Record<string, string>} scripts */
|
|
19
|
-
function writeThemePackage(scripts) {
|
|
20
|
-
fs.writeFileSync(
|
|
21
|
-
path.join(tmpDir, 'package.json'),
|
|
22
|
-
`${JSON.stringify(
|
|
23
|
-
{
|
|
24
|
-
name: '@acme/widgets',
|
|
25
|
-
version: '1.0.0',
|
|
26
|
-
files: ['astryx.integration.mjs', 'themes'],
|
|
27
|
-
scripts,
|
|
28
|
-
},
|
|
29
|
-
null,
|
|
30
|
-
2,
|
|
31
|
-
)}\n`,
|
|
32
|
-
);
|
|
33
|
-
fs.writeFileSync(
|
|
34
|
-
path.join(tmpDir, 'astryx.integration.mjs'),
|
|
35
|
-
"export default {themes: './themes'};\n",
|
|
36
|
-
);
|
|
37
|
-
const root = path.join(tmpDir, 'themes');
|
|
38
|
-
fs.mkdirSync(path.join(root, 'ocean'), {recursive: true});
|
|
39
|
-
fs.writeFileSync(
|
|
40
|
-
path.join(root, 'ocean', 'oceanTheme.ts'),
|
|
41
|
-
"import {defineTheme} from '@astryxdesign/core/theme';\n\nexport const oceanTheme = defineTheme({name: 'ocean'});\n",
|
|
42
|
-
);
|
|
43
|
-
fs.writeFileSync(
|
|
44
|
-
path.join(root, 'ocean', 'oceanTheme.doc.mjs'),
|
|
45
|
-
`/** @type {import('@astryxdesign/cli/authoring').ThemeDoc} */
|
|
46
|
-
export default {type: 'theme', name: 'ocean', displayName: 'Ocean', description: 'Ocean theme.', maintained: true};
|
|
47
|
-
`,
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
describe('integrationPackCheck with lifecycle script output', () => {
|
|
52
|
-
it('checks the tarball when lifecycle scripts print to stdout', async () => {
|
|
53
|
-
writeThemePackage({
|
|
54
|
-
prepack: 'node -e "console.log(\'building the package\')"',
|
|
55
|
-
prepare: 'node -e "console.log(\'preparing\')"',
|
|
56
|
-
postpack: 'node -e "console.log(\'packed\')"',
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const result = await integrationPackCheck({cwd: tmpDir});
|
|
60
|
-
|
|
61
|
-
expect(result.data.issues).toEqual([]);
|
|
62
|
-
expect(result.data.packable).toBe(true);
|
|
63
|
-
expect(result.data.tarball?.filename).toBe('acme-widgets-1.0.0.tgz');
|
|
64
|
-
expect(result.data.contributions.packed).toEqual(
|
|
65
|
-
result.data.contributions.local,
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('still runs a chatty lifecycle script and compares what it packed', async () => {
|
|
70
|
-
const renameTheme = [
|
|
71
|
-
"console.log('renaming ocean to storm')",
|
|
72
|
-
"const fs=require('fs')",
|
|
73
|
-
"fs.renameSync('themes/ocean','themes/storm')",
|
|
74
|
-
"const p='themes/storm/oceanTheme.doc.mjs'",
|
|
75
|
-
"let x=fs.readFileSync(p,'utf8')",
|
|
76
|
-
"x=x.replace(/name: 'ocean'/, 'name: '+String.fromCharCode(39)+'storm'+String.fromCharCode(39))",
|
|
77
|
-
'fs.writeFileSync(p,x)',
|
|
78
|
-
].join(';');
|
|
79
|
-
writeThemePackage({prepack: `node -e "${renameTheme}"`});
|
|
80
|
-
|
|
81
|
-
const result = await integrationPackCheck({cwd: tmpDir});
|
|
82
|
-
const codes = result.data.issues.map(issue => issue.code);
|
|
83
|
-
|
|
84
|
-
expect(codes).not.toContain('pack_failed');
|
|
85
|
-
expect(codes).toContain('identity_not_packed');
|
|
86
|
-
expect(codes).toContain('packed_identity_unexpected');
|
|
87
|
-
expect(result.data.packable).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('keeps a failing lifecycle script output in the pack_failed issue', async () => {
|
|
91
|
-
writeThemePackage({
|
|
92
|
-
prepack:
|
|
93
|
-
'node -e "console.error(\'prepack exploded\'); process.exit(3)"',
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const result = await integrationPackCheck({cwd: tmpDir});
|
|
97
|
-
const failure = result.data.issues.find(
|
|
98
|
-
issue => issue.code === 'pack_failed',
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
expect(result.data.packable).toBe(false);
|
|
102
|
-
expect(failure?.message).toContain('npm pack failed (exit 3)');
|
|
103
|
-
expect(failure?.message).toContain('prepack exploded');
|
|
104
|
-
});
|
|
105
|
-
});
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* `theme add` writes a theme as one transaction: when a later file fails to
|
|
5
|
-
* publish, every file it already replaced gets its previous bytes back and
|
|
6
|
-
* every file it created is removed. Publishing is forced to fail by wrapping
|
|
7
|
-
* the two calls that publish a staged file: `linkSync` for a new file and
|
|
8
|
-
* `renameSync` for a replacement.
|
|
9
|
-
*
|
|
10
|
-
* Separate file because vi.mock is hoisted and affects the whole module.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
14
|
-
import * as os from 'node:os';
|
|
15
|
-
import * as path from 'node:path';
|
|
16
|
-
|
|
17
|
-
const failures = vi.hoisted(() => ({
|
|
18
|
-
/** Fail the Nth staged-file publish (1-based); 0 disables. */
|
|
19
|
-
publish: 0,
|
|
20
|
-
/** Also fail every rollback restore. */
|
|
21
|
-
restore: false,
|
|
22
|
-
count: 0,
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
vi.mock('node:fs', async importOriginal => {
|
|
26
|
-
const actual = /** @type {typeof import('node:fs')} */ (
|
|
27
|
-
await importOriginal()
|
|
28
|
-
);
|
|
29
|
-
/** @param {string} source */
|
|
30
|
-
const isStaged = source => path.basename(String(source)).includes('.tmp');
|
|
31
|
-
/** @param {string} source */
|
|
32
|
-
const isRestore = source =>
|
|
33
|
-
path.basename(String(source)).includes('.restore-');
|
|
34
|
-
/** @param {string} source @param {string} op */
|
|
35
|
-
const maybeFail = (source, op) => {
|
|
36
|
-
if (isStaged(source) && failures.publish > 0) {
|
|
37
|
-
failures.count++;
|
|
38
|
-
if (failures.count === failures.publish) {
|
|
39
|
-
throw Object.assign(new Error(`EIO: forced ${op} failure`), {
|
|
40
|
-
code: 'EIO',
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (isRestore(source) && failures.restore) {
|
|
45
|
-
throw Object.assign(new Error(`EIO: forced restore failure`), {
|
|
46
|
-
code: 'EIO',
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
return {
|
|
51
|
-
...actual,
|
|
52
|
-
linkSync: vi.fn((source, destination) => {
|
|
53
|
-
maybeFail(source, 'link');
|
|
54
|
-
return actual.linkSync(source, destination);
|
|
55
|
-
}),
|
|
56
|
-
renameSync: vi.fn((source, destination) => {
|
|
57
|
-
maybeFail(source, 'rename');
|
|
58
|
-
return actual.renameSync(source, destination);
|
|
59
|
-
}),
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const fs = await import('node:fs');
|
|
64
|
-
const {themeAdd} = await import('./add.mjs');
|
|
65
|
-
const {listThemes} = await import('../_adapter.mjs');
|
|
66
|
-
|
|
67
|
-
let tmpDir;
|
|
68
|
-
|
|
69
|
-
beforeEach(() => {
|
|
70
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-themeadd-rollback-'));
|
|
71
|
-
failures.publish = 0;
|
|
72
|
-
failures.restore = false;
|
|
73
|
-
failures.count = 0;
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
afterEach(() => {
|
|
77
|
-
failures.publish = 0;
|
|
78
|
-
failures.restore = false;
|
|
79
|
-
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
function stoneFiles() {
|
|
83
|
-
const theme = listThemes().find(entry => entry.slug === 'stone');
|
|
84
|
-
if (!theme) throw new Error('missing bundled theme stone');
|
|
85
|
-
expect(theme.files.length).toBeGreaterThanOrEqual(2);
|
|
86
|
-
return theme.files.map(name =>
|
|
87
|
-
path.join(tmpDir, 'src', 'themes', 'stone', name),
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Entries staging or rollback left behind in the theme directory. */
|
|
92
|
-
function strays() {
|
|
93
|
-
const dir = path.join(tmpDir, 'src', 'themes', 'stone');
|
|
94
|
-
if (!fs.existsSync(dir)) return [];
|
|
95
|
-
return fs
|
|
96
|
-
.readdirSync(dir, {recursive: true})
|
|
97
|
-
.map(String)
|
|
98
|
-
.filter(name => name.includes('.tmp') || name.includes('.restore-'));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
describe('themeAdd rolls back a partial write', () => {
|
|
102
|
-
it('removes every created file when the second publish fails', async () => {
|
|
103
|
-
const files = stoneFiles();
|
|
104
|
-
failures.publish = 2;
|
|
105
|
-
|
|
106
|
-
await expect(themeAdd('stone', {cwd: tmpDir})).rejects.toMatchObject({
|
|
107
|
-
code: 'ERR_WRITE_FAILED',
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
for (const file of files) expect(fs.existsSync(file)).toBe(false);
|
|
111
|
-
expect(strays()).toEqual([]);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('restores every replaced byte when the second publish fails', async () => {
|
|
115
|
-
const files = stoneFiles();
|
|
116
|
-
fs.mkdirSync(path.dirname(files[0]), {recursive: true});
|
|
117
|
-
const before = Buffer.from([0x00, 0xff, 0x0a, 0x62, 0x65, 0x66]);
|
|
118
|
-
fs.writeFileSync(files[0], before);
|
|
119
|
-
failures.publish = 2;
|
|
120
|
-
|
|
121
|
-
await expect(
|
|
122
|
-
themeAdd('stone', {cwd: tmpDir, overwrite: true}),
|
|
123
|
-
).rejects.toMatchObject({code: 'ERR_WRITE_FAILED'});
|
|
124
|
-
|
|
125
|
-
expect(fs.readFileSync(files[0]).equals(before)).toBe(true);
|
|
126
|
-
for (const file of files.slice(1)) expect(fs.existsSync(file)).toBe(false);
|
|
127
|
-
expect(strays()).toEqual([]);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('names a file it could not restore', async () => {
|
|
131
|
-
const files = stoneFiles();
|
|
132
|
-
fs.mkdirSync(path.dirname(files[0]), {recursive: true});
|
|
133
|
-
fs.writeFileSync(files[0], 'before\n');
|
|
134
|
-
failures.publish = 2;
|
|
135
|
-
failures.restore = true;
|
|
136
|
-
|
|
137
|
-
let error;
|
|
138
|
-
try {
|
|
139
|
-
await themeAdd('stone', {cwd: tmpDir, overwrite: true});
|
|
140
|
-
} catch (caught) {
|
|
141
|
-
error = caught;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
expect(error?.code).toBe('ERR_WRITE_FAILED');
|
|
145
|
-
expect(error?.message).toContain('Could not restore');
|
|
146
|
-
expect(error?.message).toContain(files[0]);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('writes every file when nothing fails', async () => {
|
|
150
|
-
const files = stoneFiles();
|
|
151
|
-
|
|
152
|
-
const result = await themeAdd('stone', {cwd: tmpDir});
|
|
153
|
-
|
|
154
|
-
expect(result.type).toBe('theme.add');
|
|
155
|
-
for (const file of files) expect(fs.existsSync(file)).toBe(true);
|
|
156
|
-
expect(strays()).toEqual([]);
|
|
157
|
-
});
|
|
158
|
-
});
|