@astryxdesign/cli 0.6.4 → 0.6.5-canary.031021b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +97 -90
- package/api/build/build.doc.mjs +6 -1
- package/api/build/build.test.mjs +22 -0
- package/api/build/kit/kit.mjs +44 -5
- package/api/component/_adapter.d.mts +25 -0
- package/api/component/_adapter.mjs +59 -5
- package/api/component/component.d.mts +6 -3
- package/api/component/component.doc.mjs +37 -17
- package/api/component/component.mjs +249 -9
- package/api/component/component.type.d.mts +25 -0
- package/api/component/component.type.mjs +44 -0
- package/api/discover/_adapter.d.mts +114 -6
- package/api/discover/_adapter.mjs +372 -17
- package/api/discover/_adapter.test.mjs +215 -0
- package/api/discover/_catalog-view.d.mts +115 -0
- package/api/discover/_catalog-view.mjs +203 -0
- package/api/discover/_catalog-view.test.mjs +128 -0
- package/api/discover/detail/detail.d.mts +18 -6
- package/api/discover/detail/detail.mjs +67 -13
- package/api/discover/detail/detail.test.mjs +85 -0
- package/api/discover/detail/item/item.d.mts +26 -0
- package/api/discover/detail/item/item.mjs +78 -0
- package/api/discover/detail/item/item.test.mjs +73 -0
- package/api/discover/discover.d.mts +3 -9
- package/api/discover/discover.doc.mjs +61 -18
- package/api/discover/discover.mjs +220 -36
- package/api/discover/discover.test.mjs +11 -2
- package/api/discover/discover.type.d.mts +147 -8
- package/api/discover/discover.type.mjs +102 -12
- package/api/discover/list/list.d.mts +20 -6
- package/api/discover/list/list.mjs +45 -12
- package/api/discover/list/list.test.mjs +46 -0
- package/api/discover/search/search.d.mts +18 -16
- package/api/discover/search/search.mjs +102 -56
- package/api/discover/search/search.test.mjs +144 -10
- package/api/docs/_adapter.d.mts +8 -3
- package/api/docs/_adapter.mjs +14 -6
- package/api/docs/docOverlays.test.mjs +27 -1
- package/api/docs/docs.doc.mjs +2 -2
- package/api/doctor/doctor.d.mts +8 -3
- package/api/doctor/doctor.doc.mjs +17 -8
- package/api/doctor/doctor.mjs +90 -9
- package/api/doctor/doctor.test.mjs +122 -10
- package/api/doctor/doctor.type.d.mts +1 -1
- package/api/doctor/doctor.type.mjs +1 -1
- package/api/gap-report/gap-report.doc.mjs +19 -10
- package/api/hook/hook.doc.mjs +6 -3
- package/api/index.d.mts +1 -0
- package/api/index.mjs +5 -3
- package/api/init/init.doc.mjs +17 -12
- package/api/integration/add-helpers.d.mts +5 -2
- package/api/integration/add-helpers.mjs +36 -9
- package/api/integration/add-theme.mjs +22 -1
- package/api/integration/add-theme.test.mjs +34 -0
- package/api/integration/authoring-checks.mjs +2 -2
- package/api/integration/integrationPackCheck.doc.mjs +3 -3
- package/api/integration/pack-check.lifecycle-output.test.mjs +107 -0
- package/api/integration/pack-check.mjs +82 -9
- package/api/integration/pack-check.test.mjs +90 -0
- package/api/integration/pack-check.type.mjs +1 -1
- package/api/json/assertResponse.doc.mjs +1 -1
- package/api/json/isError.doc.mjs +1 -1
- package/api/search/search.d.mts +27 -1
- package/api/search/search.doc.mjs +2 -2
- package/api/search/search.mjs +228 -16
- package/api/swizzle/swizzle.doc.mjs +7 -5
- package/api/template/copy/copy.mjs +1 -1
- package/api/template/copy/copy.test.mjs +9 -0
- package/api/template/template.doc.mjs +2 -1
- package/api/theme/add/add.mjs +17 -25
- package/api/theme/add/add.rollback.test.mjs +158 -0
- package/api/theme/add/add.staging.test.mjs +40 -23
- package/api/theme/build/build.family.test.mjs +7 -12
- package/api/theme/build/build.mjs +8 -18
- package/api/theme/build/build.rollback.test.mjs +148 -0
- package/api/theme/generateTonalPalette.doc.mjs +1 -2
- package/api/theme/listThemes.doc.mjs +1 -1
- package/api/theme/themeAdd.doc.mjs +9 -10
- package/api/theme/themeBuild.doc.mjs +13 -13
- package/api/theme/themeList.doc.mjs +1 -1
- package/api/theme/themeListAvailable.doc.mjs +2 -1
- package/api/theme/themePaletteGenerate.doc.mjs +15 -8
- package/api/theme/themeTargets.doc.mjs +3 -2
- package/api/theme/themeTemplate.doc.mjs +2 -1
- package/api/upgrade/run/files-changed.test.mjs +111 -0
- package/api/upgrade/run/run.mjs +5 -3
- package/api/upgrade/upgrade.doc.mjs +24 -22
- package/api/upgrade/upgrade.type.mjs +2 -2
- package/assets/codemods/__tests__/runner.test.mjs +3 -1
- package/assets/codemods/file-count.test.mjs +163 -0
- package/assets/codemods/integration-runner.mjs +3 -3
- package/assets/codemods/runner.mjs +5 -4
- package/assets/docs/README.md +4 -2
- package/assets/docs/browser-support.doc.mjs +11 -11
- package/assets/docs/color.doc.mjs +8 -2
- package/assets/docs/elevation.doc.mjs +6 -4
- package/assets/docs/getting-started.doc.mjs +5 -16
- package/assets/docs/icons.doc.mjs +2 -21
- package/assets/docs/illustrations.doc.mjs +7 -15
- package/assets/docs/internationalization.doc.mjs +7 -5
- package/assets/docs/layout.doc.dense.mjs +130 -82
- package/assets/docs/layout.doc.mjs +133 -77
- package/assets/docs/migration.doc.mjs +19 -21
- package/assets/docs/motion.doc.mjs +16 -3
- package/assets/docs/principles.doc.dense.mjs +5 -5
- package/assets/docs/principles.doc.mjs +8 -0
- package/assets/docs/principles.doc.zh.mjs +6 -6
- package/assets/docs/shape.doc.mjs +8 -3
- package/assets/docs/spacing.doc.mjs +7 -2
- package/assets/docs/styling-libraries.doc.mjs +6 -2
- package/assets/docs/styling.doc.mjs +19 -23
- package/assets/docs/theme.doc.dense.mjs +58 -18
- package/assets/docs/theme.doc.mjs +57 -47
- package/assets/docs/theme.doc.zh.mjs +9 -8
- package/assets/docs/tokens.doc.dense.mjs +2 -2
- package/assets/docs/tokens.doc.mjs +389 -8
- package/assets/docs/tokens.doc.zh.mjs +2 -2
- package/assets/docs/tree/add-a-component.doc.mjs +75 -0
- package/assets/docs/tree/add-a-theme.doc.mjs +85 -0
- package/assets/docs/tree/add-a-topic.doc.mjs +144 -0
- package/assets/docs/tree/agent-guidance.doc.mjs +138 -0
- package/assets/docs/tree/block-template.doc.mjs +130 -0
- package/assets/docs/tree/build-the-template.doc.mjs +28 -0
- package/assets/docs/tree/building-blocks.doc.mjs +46 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +137 -0
- package/assets/docs/tree/checks.doc.mjs +119 -0
- package/assets/docs/tree/codemods.doc.mjs +147 -0
- package/assets/docs/tree/component-family.doc.mjs +113 -0
- package/assets/docs/tree/component-imports.doc.mjs +69 -0
- package/assets/docs/tree/component-lookups.doc.mjs +149 -0
- package/assets/docs/tree/components.doc.mjs +23 -0
- package/assets/docs/tree/configuration.doc.mjs +23 -0
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +182 -0
- package/assets/docs/tree/define-the-theme.doc.mjs +118 -0
- package/assets/docs/tree/describe-the-component.doc.mjs +57 -0
- package/assets/docs/tree/docs.doc.mjs +21 -0
- package/assets/docs/tree/document-the-template.doc.mjs +28 -0
- package/assets/docs/tree/document-the-theme.doc.mjs +68 -0
- package/assets/docs/tree/export-template-assets.doc.mjs +147 -0
- package/assets/docs/tree/extend-or-replace.doc.mjs +103 -0
- package/assets/docs/tree/fonts-and-assets.doc.mjs +106 -0
- package/assets/docs/tree/generate-a-palette.doc.mjs +66 -0
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +105 -0
- package/assets/docs/tree/help.doc.mjs +16 -0
- package/assets/docs/tree/integrations.doc.mjs +25 -451
- package/assets/docs/tree/links.doc.mjs +98 -0
- package/assets/docs/tree/package-and-test.doc.mjs +32 -0
- package/assets/docs/tree/page-template.doc.mjs +71 -0
- package/assets/docs/tree/publishing.doc.mjs +111 -0
- package/assets/docs/tree/quick-start.doc.mjs +272 -0
- package/assets/docs/tree/replace-a-core-component.doc.mjs +104 -0
- package/assets/docs/tree/replace-a-core-template.doc.mjs +172 -0
- package/assets/docs/tree/sections-and-placement.doc.mjs +108 -0
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +59 -0
- package/assets/docs/tree/ship.doc.mjs +16 -0
- package/assets/docs/tree/short-and-findable.doc.mjs +108 -0
- package/assets/docs/tree/single-component.doc.mjs +165 -0
- package/assets/docs/tree/start-a-template.doc.mjs +143 -0
- package/assets/docs/tree/subcomponent.doc.mjs +115 -0
- package/assets/docs/tree/template-assets.doc.mjs +64 -0
- package/assets/docs/tree/template-doc-overview.doc.mjs +109 -0
- package/assets/docs/tree/template-fonts.doc.mjs +102 -0
- package/assets/docs/tree/template-grading-rubric.doc.mjs +452 -0
- package/assets/docs/tree/template-icons.doc.mjs +97 -0
- package/assets/docs/tree/template-images-media.doc.mjs +127 -0
- package/assets/docs/tree/template-styles.doc.mjs +93 -0
- package/assets/docs/tree/templates.doc.mjs +34 -0
- package/assets/docs/tree/test-in-an-app.doc.mjs +115 -0
- package/assets/docs/tree/test-template-in-app.doc.mjs +128 -0
- package/assets/docs/tree/themes.doc.mjs +39 -0
- package/assets/docs/tree/troubleshooting.doc.mjs +149 -0
- package/assets/docs/tree/upgrading.doc.mjs +103 -0
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +51 -0
- package/assets/docs/tree/verify-packed-template.doc.mjs +77 -0
- package/assets/docs/tree/versioning.doc.mjs +161 -0
- package/assets/docs/tree/write-good-templates.doc.mjs +64 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +154 -0
- package/assets/docs/typography.doc.mjs +24 -4
- package/assets/docs/working-with-ai.doc.mjs +30 -22
- package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
- package/authoring/config/config.doc.mjs +9 -1
- package/authoring/config/parse.d.mts +2 -0
- package/authoring/config/parse.mjs +19 -0
- package/authoring/config/parse.test.mjs +8 -0
- package/authoring/config/type.ts +11 -0
- package/authoring/discover/discover.doc.d.mts +13 -0
- package/authoring/discover/discover.doc.mjs +138 -0
- package/authoring/discover/parse.d.mts +24 -0
- package/authoring/discover/parse.mjs +128 -0
- package/authoring/discover/parse.test.mjs +124 -0
- package/authoring/discover/type.ts +87 -0
- package/authoring/doctypes/_schema.d.mts +3 -2
- package/authoring/doctypes/_schema.mjs +6 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +3 -3
- package/authoring/doctypes/base/type.ts +4 -2
- package/authoring/doctypes/component/component.doc.mjs +6 -0
- package/authoring/doctypes/component/type.ts +8 -0
- package/authoring/doctypes/reference/reference.doc.mjs +7 -0
- package/authoring/doctypes/reference/type.ts +5 -0
- package/authoring/doctypes/schema/schema.doc.mjs +2 -2
- package/authoring/doctypes/template/template.doc.mjs +1 -1
- package/authoring/doctypes/template/type.ts +2 -2
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +10 -0
- package/authoring/index.mjs +1 -0
- package/authoring/integration/integration.doc.mjs +12 -10
- package/clients/cli/commands/component/index.mjs +152 -55
- package/clients/cli/commands/component-batch.test.mjs +341 -0
- package/clients/cli/commands/component-ownership.test.mjs +89 -0
- package/clients/cli/commands/component.doc.mjs +27 -9
- package/clients/cli/commands/discover.doc.mjs +53 -9
- package/clients/cli/commands/discover.mjs +393 -118
- package/clients/cli/commands/discover.sources.test.mjs +267 -0
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/docs.mjs +60 -17
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +3 -2
- package/clients/cli/commands/doctor-integration.test.mjs +53 -0
- package/clients/cli/commands/doctor.doc.mjs +3 -1
- package/clients/cli/commands/doctor.mjs +49 -5
- package/clients/cli/commands/gap-report.doc.mjs +10 -9
- package/clients/cli/commands/init.doc.mjs +9 -6
- package/clients/cli/commands/integration-add.doc.mjs +9 -9
- package/clients/cli/commands/integration-authoring.test.mjs +61 -10
- package/clients/cli/commands/integration-pack.doc.mjs +5 -9
- package/clients/cli/commands/integration-real-world.test.mjs +1 -1
- package/clients/cli/commands/integration-verify.doc.mjs +22 -0
- package/clients/cli/commands/integration.doc.mjs +4 -4
- package/clients/cli/commands/integration.mjs +74 -43
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +10 -3
- package/clients/cli/commands/search.mjs +21 -2
- package/clients/cli/commands/search.test.mjs +21 -4
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/text-json-parity.test.mjs +7 -1
- package/clients/cli/commands/theme-add.doc.mjs +1 -1
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -2
- package/clients/cli/commands/theme-palette.doc.mjs +1 -2
- package/clients/cli/commands/theme-targets.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +2 -1
- package/clients/cli/commands/upgrade.doc.mjs +62 -3
- package/clients/cli/index.mjs +28 -6
- package/clients/cli/lib/define-command.mjs +28 -4
- package/clients/cli/lib/define-command.test.mjs +54 -0
- package/clients/cli/lib/exit-codes.test.mjs +17 -1
- package/clients/cli/lib/json-shim.mjs +24 -14
- package/clients/cli/lib/manifest.mjs +18 -5
- package/clients/cli/lib/manifest.test.mjs +5 -2
- package/clients/cli/lib/parse-error-format.test.mjs +81 -0
- package/foundation/agent-docs/agent-docs.mjs +1 -1
- package/foundation/discovery/authoring-self-docs.mjs +1 -0
- package/foundation/discovery/authoring-self-docs.test.mjs +6 -2
- package/foundation/discovery/cli-self-docs.mjs +16 -2
- package/foundation/discovery/cli-self-docs.test.mjs +20 -0
- package/foundation/discovery/docs-discovery.mjs +5 -1
- package/foundation/discovery/docs-discovery.test.mjs +21 -0
- package/foundation/discovery/docs-section-key.d.mts +1 -1
- package/foundation/discovery/docs-section-key.mjs +1 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +3 -2
- package/foundation/doc-compiler/inputs.test.mjs +0 -1
- package/foundation/doc-compiler/tree.d.mts +4 -0
- package/foundation/doc-compiler/tree.mjs +6 -1
- package/foundation/integrations/cli-requirement.d.mts +26 -6
- package/foundation/integrations/cli-requirement.mjs +46 -11
- package/foundation/integrations/cli-requirement.test.mjs +7 -2
- package/foundation/integrations/contribution-inventory.mjs +1 -1
- package/foundation/integrations/integrations.d.mts +14 -1
- package/foundation/integrations/integrations.mjs +41 -1
- package/foundation/integrations/integrations.test.mjs +31 -0
- package/foundation/response/batch.type.d.mts +33 -0
- package/foundation/response/batch.type.mjs +34 -0
- package/foundation/response/error-codes.doc.mjs +6 -8
- package/foundation/response/error-codes.test.mjs +30 -5
- package/foundation/response/response-types.doc.d.mts +4 -3
- package/foundation/response/response-types.doc.mjs +40 -10
- package/foundation/response/response-types.doc.test.mjs +23 -0
- package/foundation/response/response.doc.mjs +11 -10
- package/package.json +9 -9
- package/api/docs/docs.test.mjs +0 -243
- package/api/docs/integration-tree.test.mjs +0 -555
- package/api/docs/integrationDocs.test.mjs +0 -314
- package/api/search/search.test.mjs +0 -512
- package/assets/docs/tree/integrations.test.mjs +0 -62
- package/assets/docs/tree/writing-docs.doc.mjs +0 -286
- package/clients/cli/commands/docs.test.mjs +0 -294
- package/foundation/agent-docs/agent-docs.test.mjs +0 -1159
- package/foundation/doc-compiler/tree.test.mjs +0 -598
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
});
|
|
@@ -6,14 +6,15 @@ 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';
|
|
10
9
|
|
|
11
10
|
let tmpDir;
|
|
12
11
|
let outsideDir;
|
|
13
12
|
|
|
14
13
|
beforeEach(() => {
|
|
15
14
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-themeadd-staging-'));
|
|
16
|
-
outsideDir = fs.mkdtempSync(
|
|
15
|
+
outsideDir = fs.mkdtempSync(
|
|
16
|
+
path.join(os.tmpdir(), 'astryx-themeadd-outside-'),
|
|
17
|
+
);
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
afterEach(() => {
|
|
@@ -22,45 +23,61 @@ afterEach(() => {
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
*
|
|
26
|
+
* Where `theme add` writes the first file of `slug`.
|
|
26
27
|
* @param {string} slug
|
|
27
|
-
* @param {string} target
|
|
28
28
|
*/
|
|
29
|
-
function
|
|
29
|
+
function firstDestination(slug) {
|
|
30
30
|
const theme = listThemes().find(entry => entry.slug === slug);
|
|
31
31
|
if (!theme) throw new Error(`missing bundled theme ${slug}`);
|
|
32
|
-
const
|
|
33
|
-
const dest = path.join(tmpDir, 'src', 'themes', slug, first);
|
|
32
|
+
const dest = path.join(tmpDir, 'src', 'themes', slug, theme.files[0]);
|
|
34
33
|
fs.mkdirSync(path.dirname(dest), {recursive: true});
|
|
35
|
-
fs.symlinkSync(target, `${dest}.${process.pid}.tmp`);
|
|
36
34
|
return dest;
|
|
37
35
|
}
|
|
38
36
|
|
|
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('never writes through a link planted at a predictable staging name', async () => {
|
|
41
41
|
const victim = path.join(outsideDir, 'victim.txt');
|
|
42
42
|
fs.writeFileSync(victim, 'outside\n');
|
|
43
|
-
const dest =
|
|
43
|
+
const dest = firstDestination('stone');
|
|
44
|
+
fs.symlinkSync(victim, `${dest}.${process.pid}.tmp`);
|
|
45
|
+
|
|
46
|
+
await themeAdd('stone', {cwd: tmpDir});
|
|
44
47
|
|
|
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.lstatSync(dest).isFile()).toBe(true);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it('never creates a file through a dangling staging
|
|
52
|
+
it('never creates a file through a dangling link at a predictable staging name', async () => {
|
|
53
53
|
const victim = path.join(outsideDir, 'created.txt');
|
|
54
|
-
|
|
54
|
+
fs.symlinkSync(victim, `${firstDestination('stone')}.${process.pid}.tmp`);
|
|
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);
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
await expect(
|
|
68
|
+
themeAdd('stone', {cwd: tmpDir, overwrite: true}),
|
|
69
|
+
).rejects.toMatchObject({code: 'ERR_PATH_TRAVERSAL'});
|
|
70
|
+
expect(fs.readFileSync(victim, 'utf-8')).toBe('outside\n');
|
|
71
|
+
expect(fs.lstatSync(dest).isSymbolicLink()).toBe(true);
|
|
72
|
+
});
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
it('never creates a file through a dangling destination link', async () => {
|
|
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
|
+
});
|
|
64
81
|
expect(fs.existsSync(victim)).toBe(false);
|
|
65
82
|
});
|
|
66
83
|
});
|
|
@@ -251,23 +251,18 @@ describe('themeBuildFamily()', () => {
|
|
|
251
251
|
const dir = makeDir();
|
|
252
252
|
const files = writeFamily(dir);
|
|
253
253
|
const outputDir = path.join(dir, 'themes');
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
);
|
|
258
|
-
fs.mkdirSync(blockedTmp);
|
|
254
|
+
// A directory where the JS output goes fails its staging after the CSS
|
|
255
|
+
// output was already staged.
|
|
256
|
+
fs.mkdirSync(path.join(outputDir, `${FAMILY_KEY}.js`));
|
|
259
257
|
|
|
260
258
|
await expect(build(dir, files)).rejects.toThrow(
|
|
261
259
|
/Failed to write theme outputs/,
|
|
262
260
|
);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
261
|
+
expect(fs.existsSync(path.join(outputDir, `${FAMILY_KEY}.css`))).toBe(false);
|
|
262
|
+
expect(fs.existsSync(path.join(outputDir, `${FAMILY_KEY}.d.ts`))).toBe(false);
|
|
266
263
|
expect(
|
|
267
|
-
fs.
|
|
268
|
-
|
|
269
|
-
),
|
|
270
|
-
).toBe(false);
|
|
264
|
+
fs.readdirSync(outputDir).filter(name => name.includes('.tmp-')),
|
|
265
|
+
).toEqual([]);
|
|
271
266
|
});
|
|
272
267
|
|
|
273
268
|
it('rejects invalid graphs and keys before touching an existing trio', async () => {
|
|
@@ -53,6 +53,7 @@ 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';
|
|
56
57
|
import {logger} from '../../logger.mjs';
|
|
57
58
|
import {loadComponentDoc} from '../../../foundation/discovery/component-loader.mjs';
|
|
58
59
|
import {
|
|
@@ -224,26 +225,15 @@ function staleBuildOutputs(writes, cwd) {
|
|
|
224
225
|
/** @param {Array<{dest: string, content: string}>} writes */
|
|
225
226
|
function writeBuildOutputs(writes) {
|
|
226
227
|
if (writes.length === 0) return;
|
|
227
|
-
/** @type {Array<{tmp: string, dest: string}>} */
|
|
228
|
-
const staged = [];
|
|
229
228
|
try {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
fs.renameSync(stagedWrite.tmp, stagedWrite.dest);
|
|
238
|
-
}
|
|
229
|
+
applyWrites(
|
|
230
|
+
writes.map(write => ({
|
|
231
|
+
path: write.dest,
|
|
232
|
+
contents: write.content,
|
|
233
|
+
createOnly: false,
|
|
234
|
+
})),
|
|
235
|
+
);
|
|
239
236
|
} 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
|
-
}
|
|
247
237
|
const message = `Failed to write theme outputs: ${/** @type {Error} */ (error).message}`;
|
|
248
238
|
throw new AstryxError(message, undefined, ERROR_CODES.ERR_WRITE_FAILED);
|
|
249
239
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `theme build` writes its CSS, JS, and declarations as one transaction: when
|
|
5
|
+
* a later output fails to publish, every output it already replaced gets its
|
|
6
|
+
* previous bytes back and every output it created is removed. Publishing is
|
|
7
|
+
* forced to fail by wrapping the two calls that publish a staged file:
|
|
8
|
+
* `linkSync` for a new file and `renameSync` for a replacement.
|
|
9
|
+
*
|
|
10
|
+
* Separate file because vi.mock is hoisted and affects the whole module.
|
|
11
|
+
* `themeBuild` needs a built core; the `node` project's globalSetup builds it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
|
15
|
+
import * as os from 'node:os';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
|
|
18
|
+
const failures = vi.hoisted(() => ({
|
|
19
|
+
/** Fail the Nth staged-file publish (1-based); 0 disables. */
|
|
20
|
+
publish: 0,
|
|
21
|
+
count: 0,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock('node:fs', async importOriginal => {
|
|
25
|
+
const actual = /** @type {typeof import('node:fs')} */ (
|
|
26
|
+
await importOriginal()
|
|
27
|
+
);
|
|
28
|
+
/** @param {string} source @param {string} op */
|
|
29
|
+
const maybeFail = (source, op) => {
|
|
30
|
+
if (!path.basename(String(source)).includes('.tmp')) return;
|
|
31
|
+
if (failures.publish === 0) return;
|
|
32
|
+
failures.count++;
|
|
33
|
+
if (failures.count === failures.publish) {
|
|
34
|
+
throw Object.assign(new Error(`EIO: forced ${op} failure`), {
|
|
35
|
+
code: 'EIO',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
...actual,
|
|
41
|
+
linkSync: vi.fn((source, destination) => {
|
|
42
|
+
maybeFail(source, 'link');
|
|
43
|
+
return actual.linkSync(source, destination);
|
|
44
|
+
}),
|
|
45
|
+
renameSync: vi.fn((source, destination) => {
|
|
46
|
+
maybeFail(source, 'rename');
|
|
47
|
+
return actual.renameSync(source, destination);
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const fs = await import('node:fs');
|
|
53
|
+
const {themeBuild} = await import('./build.mjs');
|
|
54
|
+
|
|
55
|
+
vi.setConfig({testTimeout: 30000});
|
|
56
|
+
|
|
57
|
+
let tmpDir;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
tmpDir = fs.mkdtempSync(
|
|
61
|
+
path.join(os.tmpdir(), 'astryx-theme-build-rollback-'),
|
|
62
|
+
);
|
|
63
|
+
failures.publish = 0;
|
|
64
|
+
failures.count = 0;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
failures.publish = 0;
|
|
69
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Write a source for the theme named `rollbacktheme`. Each call uses a new
|
|
74
|
+
* file so the loader cannot serve an earlier version from its cache.
|
|
75
|
+
* @param {string} file @param {string} background
|
|
76
|
+
*/
|
|
77
|
+
function writeTheme(file, background) {
|
|
78
|
+
fs.writeFileSync(
|
|
79
|
+
path.join(tmpDir, file),
|
|
80
|
+
`export default { name: 'rollbacktheme', tokens: { '--color-bg': '${background}' } };\n`,
|
|
81
|
+
);
|
|
82
|
+
return file;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const OUTPUTS = ['rollbacktheme.css', 'rollbacktheme.js', 'rollbacktheme.d.ts'];
|
|
86
|
+
|
|
87
|
+
/** @returns {Map<string, Buffer | null>} */
|
|
88
|
+
function readOutputs() {
|
|
89
|
+
return new Map(
|
|
90
|
+
OUTPUTS.map(name => {
|
|
91
|
+
const file = path.join(tmpDir, name);
|
|
92
|
+
return [name, fs.existsSync(file) ? fs.readFileSync(file) : null];
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function strays() {
|
|
98
|
+
return fs
|
|
99
|
+
.readdirSync(tmpDir)
|
|
100
|
+
.filter(name => name.includes('.tmp') || name.includes('.restore-'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe('themeBuild rolls back a partial write', () => {
|
|
104
|
+
it('removes every created output when the second publish fails', async () => {
|
|
105
|
+
const source = writeTheme('first.mjs', '#0a0a0a');
|
|
106
|
+
failures.publish = 2;
|
|
107
|
+
|
|
108
|
+
await expect(themeBuild(source, {}, {cwd: tmpDir})).rejects.toMatchObject({
|
|
109
|
+
code: 'ERR_WRITE_FAILED',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
for (const bytes of readOutputs().values()) expect(bytes).toBeNull();
|
|
113
|
+
expect(strays()).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('restores every replaced output when the second publish fails', async () => {
|
|
117
|
+
const first = await themeBuild(
|
|
118
|
+
writeTheme('first.mjs', '#0a0a0a'),
|
|
119
|
+
{},
|
|
120
|
+
{cwd: tmpDir},
|
|
121
|
+
);
|
|
122
|
+
expect(first?.type).toBe('theme.build');
|
|
123
|
+
const before = readOutputs();
|
|
124
|
+
for (const bytes of before.values()) expect(bytes).not.toBeNull();
|
|
125
|
+
|
|
126
|
+
const next = writeTheme('next.mjs', '#fafafa');
|
|
127
|
+
failures.publish = 2;
|
|
128
|
+
await expect(themeBuild(next, {}, {cwd: tmpDir})).rejects.toMatchObject({
|
|
129
|
+
code: 'ERR_WRITE_FAILED',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const after = readOutputs();
|
|
133
|
+
for (const name of OUTPUTS) {
|
|
134
|
+
expect(
|
|
135
|
+
after.get(name)?.equals(/** @type {Buffer} */ (before.get(name))),
|
|
136
|
+
).toBe(true);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The failed build really had different bytes to write.
|
|
140
|
+
failures.publish = 0;
|
|
141
|
+
await themeBuild(next, {}, {cwd: tmpDir});
|
|
142
|
+
const css = readOutputs().get('rollbacktheme.css');
|
|
143
|
+
expect(
|
|
144
|
+
css?.equals(/** @type {Buffer} */ (before.get('rollbacktheme.css'))),
|
|
145
|
+
).toBe(false);
|
|
146
|
+
expect(strays()).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -29,7 +29,7 @@ export const doc = {
|
|
|
29
29
|
name: 'input',
|
|
30
30
|
type: 'TonalPaletteGenerationInput',
|
|
31
31
|
description:
|
|
32
|
-
'Families and seeds plus optional modes, shared stops, anchors, vibrancy from 0 to 100 (default 50), and neutral profile.
|
|
32
|
+
'Families and seeds plus optional modes, shared stops, anchors, vibrancy from 0 to 100 (default 50), and neutral profile.',
|
|
33
33
|
required: true,
|
|
34
34
|
},
|
|
35
35
|
],
|
|
@@ -72,6 +72,5 @@ export const doc = {
|
|
|
72
72
|
code: "generateTonalPalette({stops: [12.5, 50], families: [{id: 'blue', seed: '#0074e2'}]});",
|
|
73
73
|
},
|
|
74
74
|
],
|
|
75
|
-
command: 'theme palette generate',
|
|
76
75
|
related: ['themePaletteGenerate'],
|
|
77
76
|
};
|
|
@@ -13,7 +13,7 @@ export const doc = {
|
|
|
13
13
|
displayName: 'listThemes()',
|
|
14
14
|
summary: 'Read the CLI bundled-theme descriptors.',
|
|
15
15
|
description:
|
|
16
|
-
|
|
16
|
+
"Synchronously returns the themes bundled with the CLI, including each one's entry file, export name and file list. Bundled themes only; use themeListAvailable() to include themes from installed integrations.",
|
|
17
17
|
importPath: '@astryxdesign/cli/api',
|
|
18
18
|
signature: 'listThemes(): BundledTheme[]',
|
|
19
19
|
keywords: ['theme', 'themes', 'descriptor', 'bundled', 'adapter', 'list'],
|
|
@@ -44,6 +44,7 @@ export const doc = {
|
|
|
44
44
|
type: 'string',
|
|
45
45
|
description:
|
|
46
46
|
'Project directory used for integration discovery and target paths.',
|
|
47
|
+
default: 'process.cwd()',
|
|
47
48
|
},
|
|
48
49
|
{
|
|
49
50
|
name: 'options.package',
|
|
@@ -57,11 +58,6 @@ export const doc = {
|
|
|
57
58
|
description:
|
|
58
59
|
'Copy receipt with slug, displayName, maintained flag, owner package, outputDir, entry, exportName, and files.',
|
|
59
60
|
},
|
|
60
|
-
{
|
|
61
|
-
type: 'theme.list',
|
|
62
|
-
description:
|
|
63
|
-
'The CLI list affordance routes a bare `astryx theme add` or `--list` to themeListAvailable() and returns every available theme with its owner.',
|
|
64
|
-
},
|
|
65
61
|
],
|
|
66
62
|
throws: [
|
|
67
63
|
{
|
|
@@ -74,7 +70,10 @@ export const doc = {
|
|
|
74
70
|
when: 'the selected installed package has a blocking integration or theme-descriptor error',
|
|
75
71
|
},
|
|
76
72
|
{code: 'ERR_PATH_TRAVERSAL', when: 'the target path escapes cwd'},
|
|
77
|
-
{
|
|
73
|
+
{
|
|
74
|
+
code: 'ERR_NO_SOURCE',
|
|
75
|
+
when: 'the bundled theme descriptors cannot be read, or a theme file to copy is missing',
|
|
76
|
+
},
|
|
78
77
|
{
|
|
79
78
|
code: 'ERR_FILE_EXISTS',
|
|
80
79
|
when: 'a destination exists and overwrite is not set',
|
|
@@ -82,12 +81,12 @@ export const doc = {
|
|
|
82
81
|
{code: 'ERR_WRITE_FAILED', when: 'writing files fails'},
|
|
83
82
|
],
|
|
84
83
|
examples: [
|
|
85
|
-
{label: 'Copy a bundled theme', code: "await themeAdd('
|
|
84
|
+
{label: 'Copy a bundled theme', code: "await themeAdd('butter');"},
|
|
86
85
|
{
|
|
87
|
-
label: '
|
|
88
|
-
code: "await themeAdd('
|
|
86
|
+
label: 'Name the owner package and the destination',
|
|
87
|
+
code: "await themeAdd('butter', {package: '@astryxdesign/cli', targetPath: 'src/brand-theme'});",
|
|
89
88
|
},
|
|
90
89
|
],
|
|
91
90
|
command: 'theme add',
|
|
92
|
-
related: ['
|
|
91
|
+
related: ['themeListAvailable', 'themeTemplate', 'listThemes'],
|
|
93
92
|
};
|
|
@@ -14,14 +14,13 @@ export const doc = {
|
|
|
14
14
|
name: 'themeBuild',
|
|
15
15
|
namespace: 'cli/api',
|
|
16
16
|
displayName: 'themeBuild()',
|
|
17
|
-
summary:
|
|
17
|
+
summary:
|
|
18
|
+
'Compile a defineTheme() file to scoped CSS, a JS module, and type declarations, or check committed outputs for drift in CI.',
|
|
18
19
|
description:
|
|
19
|
-
'The compiler behind `astryx theme build`. Reads a file that calls defineTheme() and
|
|
20
|
-
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'theme adds custom prop values). When another build step emits the icon registry, ' +
|
|
24
|
-
'{iconsSpecifier} declares the fully specified module path for the generated JS import. ' +
|
|
20
|
+
'The compiler behind `astryx theme build`. Reads a file that calls defineTheme() and ' +
|
|
21
|
+
'writes a scoped CSS file, a JS module that re-exports the built theme, and a .d.ts ' +
|
|
22
|
+
'(plus an optional .variants.d.ts when the theme adds custom prop values). It uses ' +
|
|
23
|
+
"@astryxdesign/core's own generator, so the CSS matches what the <Theme> runtime emits. " +
|
|
25
24
|
'With {check: true} it writes nothing and instead compares ' +
|
|
26
25
|
'each output against disk, returning the drift: the CI guard for committed, generated theme CSS.',
|
|
27
26
|
importPath: '@astryxdesign/cli/api',
|
|
@@ -48,7 +47,7 @@ export const doc = {
|
|
|
48
47
|
name: 'options.out',
|
|
49
48
|
type: 'string',
|
|
50
49
|
description:
|
|
51
|
-
'Override the output CSS path
|
|
50
|
+
'Override the output CSS path. The .js, .d.ts and any .variants.d.ts are written in the same directory, named after the theme (<name>.js), not after the CSS file. A relative path must stay within cwd.',
|
|
52
51
|
},
|
|
53
52
|
{
|
|
54
53
|
name: 'options.check',
|
|
@@ -61,13 +60,14 @@ export const doc = {
|
|
|
61
60
|
name: 'options.iconsSpecifier',
|
|
62
61
|
type: 'string',
|
|
63
62
|
description:
|
|
64
|
-
'Override the
|
|
63
|
+
'Override the import specifier of the icon registry in the generated JS module, for example ./icons.mjs. Takes effect only when the theme sets icons: to a named import; when omitted, the source specifier is kept.',
|
|
65
64
|
},
|
|
66
65
|
{
|
|
67
66
|
name: 'ctx.cwd',
|
|
68
67
|
type: 'string',
|
|
69
68
|
description:
|
|
70
|
-
'Directory the theme file and
|
|
69
|
+
'Directory the theme file, a relative out path, and the returned output paths resolve against.',
|
|
70
|
+
default: 'process.cwd()',
|
|
71
71
|
},
|
|
72
72
|
],
|
|
73
73
|
returns: [
|
|
@@ -79,7 +79,7 @@ export const doc = {
|
|
|
79
79
|
{
|
|
80
80
|
type: 'theme.build.check',
|
|
81
81
|
description:
|
|
82
|
-
'The {check: true} receipt: theme name, an upToDate flag, the stale outputs (each {path, reason: "missing" | "outdated"}), and the full list of checked paths. Writes nothing.',
|
|
82
|
+
'The {check: true} receipt: theme name, an upToDate flag, the stale outputs (each {path, reason: "missing" | "outdated"}), and the full list of checked paths. Writes nothing. Resolves to null, like a normal build, when the theme produces no CSS.',
|
|
83
83
|
},
|
|
84
84
|
],
|
|
85
85
|
throws: [
|
|
@@ -102,7 +102,7 @@ export const doc = {
|
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
code: 'ERR_CORE_INCOMPATIBLE',
|
|
105
|
-
when: 'the
|
|
105
|
+
when: 'the installed @astryxdesign/core does not export generateAdaptationCSS and the theme either declares ordered adaptations or has lineage whose adaptation use could not be observed (upgrade core)',
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
108
|
code: 'ERR_WRITE_FAILED',
|
|
@@ -124,5 +124,5 @@ export const doc = {
|
|
|
124
124
|
},
|
|
125
125
|
],
|
|
126
126
|
command: 'theme build',
|
|
127
|
-
related: ['themeAdd', '
|
|
127
|
+
related: ['themeTemplate', 'themeAdd', 'themeListAvailable', 'listThemes'],
|
|
128
128
|
};
|
|
@@ -13,7 +13,7 @@ export const doc = {
|
|
|
13
13
|
displayName: 'themeList()',
|
|
14
14
|
summary: 'List themes bundled with this CLI build.',
|
|
15
15
|
description:
|
|
16
|
-
'
|
|
16
|
+
'Synchronous list of the themes bundled with the CLI. It does not include themes from installed integrations; use themeListAvailable() for the list `astryx theme list` shows.',
|
|
17
17
|
importPath: '@astryxdesign/cli/api',
|
|
18
18
|
signature: 'themeList(): ThemeListResponse',
|
|
19
19
|
keywords: ['theme', 'list', 'themes', 'bundled', 'available'],
|
|
@@ -13,7 +13,7 @@ export const doc = {
|
|
|
13
13
|
displayName: 'themeListAvailable()',
|
|
14
14
|
summary: 'List bundled and installed integration themes.',
|
|
15
15
|
description:
|
|
16
|
-
'
|
|
16
|
+
'Lists the bundled themes plus source themes from integrations installed in cwd, each with its owner package. If the project configuration cannot be read, it falls back to the bundled themes.',
|
|
17
17
|
importPath: '@astryxdesign/cli/api',
|
|
18
18
|
signature:
|
|
19
19
|
'themeListAvailable(options?: {cwd?: string, package?: string}): Promise<ThemeListResponse>',
|
|
@@ -24,6 +24,7 @@ export const doc = {
|
|
|
24
24
|
type: 'string',
|
|
25
25
|
description:
|
|
26
26
|
'Project directory whose installed integrations contribute themes.',
|
|
27
|
+
default: 'process.cwd()',
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
30
|
name: 'options.package',
|