@astryxdesign/cli 0.6.3-canary.db4e378 → 0.6.3-canary.ddb63c3
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 +20 -1
- package/api/blog/blog.doc.mjs +1 -0
- package/api/build/build.doc.mjs +1 -0
- package/api/component/component.doc.mjs +1 -0
- package/api/discover/discover.doc.mjs +1 -0
- package/api/docs/_adapter.d.mts +3 -8
- package/api/docs/_adapter.mjs +13 -105
- package/api/docs/docs.doc.mjs +1 -0
- package/api/docs/list/list.mjs +3 -7
- package/api/doctor/doctor.d.mts +25 -3
- package/api/doctor/doctor.doc.mjs +1 -0
- package/api/doctor/doctor.mjs +185 -13
- package/api/doctor/doctor.test.mjs +326 -1
- package/api/gap-report/gap-report.doc.mjs +1 -0
- package/api/hook/_adapter.mjs +19 -5
- package/api/hook/hook.doc.mjs +1 -0
- package/api/hook/list/list.d.mts +1 -1
- package/api/hook/list/list.mjs +69 -17
- package/api/init/init.doc.mjs +1 -0
- package/api/integration/add-contribution.mjs +7 -7
- package/api/integration/add-contribution.test.mjs +37 -3
- package/api/integration/add-theme.mjs +34 -64
- package/api/integration/add-theme.test.mjs +105 -21
- package/api/integration/authoring-checks.test.mjs +8 -4
- package/api/integration/integration-block-exports.test.mjs +10 -6
- package/api/integration/integrationAdd.doc.mjs +1 -0
- package/api/integration/integrationAddAgentDoc.doc.mjs +1 -0
- package/api/integration/integrationAddCodemod.doc.mjs +1 -0
- package/api/integration/integrationAddComponent.doc.mjs +1 -0
- package/api/integration/integrationAddDoc.doc.mjs +1 -0
- package/api/integration/integrationAddTemplate.doc.mjs +1 -0
- package/api/integration/integrationAddTheme.doc.mjs +6 -5
- package/api/integration/integrationComponentConflicts.doc.mjs +1 -0
- package/api/integration/integrationDocConflicts.doc.mjs +1 -0
- package/api/integration/integrationPackCheck.doc.mjs +1 -0
- package/api/integration/integrationTemplateConflicts.doc.mjs +1 -0
- package/api/integration/pack-check.test.mjs +17 -47
- package/api/integration/summarizeIssues.doc.mjs +1 -0
- package/api/integration/validate-integration-fixes.test.mjs +1389 -0
- package/api/integration/validate-integration.mjs +50 -102
- package/api/integration/validate-integration.test.mjs +85 -23
- package/api/integration/validate-unread-theme-folders.test.mjs +110 -0
- package/api/integration/validateIntegration.doc.mjs +1 -0
- package/api/json/assertResponse.doc.mjs +1 -0
- package/api/json/isError.doc.mjs +1 -0
- package/api/json/parseResponse.doc.mjs +1 -0
- package/api/layout/layoutCheck.doc.mjs +1 -0
- package/api/layout/layoutExpand.doc.mjs +1 -0
- package/api/layout/layoutGrammar.doc.mjs +1 -0
- package/api/search/search.doc.mjs +1 -0
- package/api/search/search.mjs +16 -6
- package/api/swizzle/swizzle.doc.mjs +1 -0
- package/api/template/template-suffix.test.mjs +41 -21
- package/api/template/template.doc.mjs +1 -0
- package/api/theme/_adapter.d.mts +2 -3
- package/api/theme/_adapter.mjs +4 -5
- package/api/theme/add/add.binary.test.mjs +10 -17
- package/api/theme/add/add.test.mjs +14 -1
- package/api/theme/generateTonalPalette.doc.mjs +1 -0
- package/api/theme/integration-themes.test.mjs +39 -28
- package/api/theme/list/list.test.mjs +19 -20
- package/api/theme/listThemes.doc.mjs +6 -5
- package/api/theme/themeAdd.doc.mjs +4 -3
- package/api/theme/themeBuild.doc.mjs +1 -0
- package/api/theme/themeList.doc.mjs +6 -3
- package/api/theme/themeListAvailable.doc.mjs +5 -3
- package/api/theme/themePaletteGenerate.doc.mjs +1 -0
- package/api/theme/themeTargets.doc.mjs +1 -0
- package/api/theme/themeTemplate.doc.mjs +1 -0
- package/api/upgrade/_adapter.d.mts +12 -3
- package/api/upgrade/_adapter.mjs +81 -66
- package/api/upgrade/provider-agreement.test.mjs +152 -0
- package/api/upgrade/run/run.mjs +1 -0
- package/api/upgrade/upgrade.doc.mjs +1 -0
- package/assets/codemods/integration-discovery.mjs +8 -2
- package/assets/codemods/integration-discovery.test.mjs +15 -0
- package/assets/codemods/runner.mjs +115 -5
- package/assets/codemods/transforms/next/__tests__/migrate-theme-catalog-to-descriptors.test.mjs +216 -0
- package/assets/codemods/transforms/next/index.mjs +11 -1
- package/assets/codemods/transforms/next/migrate-theme-catalog-to-descriptors.mjs +141 -0
- package/assets/docs/cli-integrations.doc.mjs +17 -30
- package/assets/docs/cli.doc.mjs +15 -0
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/templates/themes/butter/butterTheme.doc.mjs +11 -0
- package/assets/templates/themes/chocolate/chocolateTheme.doc.mjs +11 -0
- package/assets/templates/themes/gothic/gothicTheme.doc.mjs +11 -0
- package/assets/templates/themes/matcha/matchaTheme.doc.mjs +11 -0
- package/assets/templates/themes/neutral/neutralTheme.doc.mjs +11 -0
- package/assets/templates/themes/stone/stoneTheme.doc.mjs +11 -0
- package/assets/templates/themes/y2k/y2kTheme.doc.mjs +11 -0
- package/authoring/codemod/codemod.doc.mjs +1 -1
- package/authoring/config/config.doc.mjs +1 -1
- package/authoring/debug/debug.doc.d.mts +11 -0
- package/authoring/debug/debug.doc.mjs +182 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +1 -1
- package/authoring/doctypes/command/command.doc.mjs +1 -1
- package/authoring/doctypes/command/type.ts +1 -1
- package/authoring/doctypes/component/type.ts +2 -2
- package/authoring/doctypes/doctypes-new.test.mjs +48 -6
- package/authoring/doctypes/enum/enum.doc.mjs +1 -1
- package/authoring/doctypes/enum/type.ts +1 -1
- package/authoring/doctypes/function/function.doc.mjs +1 -1
- package/authoring/doctypes/function/type.ts +1 -1
- package/authoring/doctypes/hook/type.ts +2 -2
- package/authoring/doctypes/load-contract.test.mjs +2 -1
- package/authoring/doctypes/parse.d.mts +4 -2
- package/authoring/doctypes/parse.mjs +7 -3
- package/authoring/doctypes/reference/reference.doc.mjs +1 -1
- package/authoring/doctypes/reference/type.ts +2 -2
- package/authoring/doctypes/schema/schema.doc.mjs +1 -1
- package/authoring/doctypes/schema/type.ts +1 -2
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/theme/parse.d.mts +35 -0
- package/authoring/doctypes/theme/parse.mjs +76 -0
- package/authoring/doctypes/theme/theme.doc.d.mts +9 -0
- package/authoring/doctypes/theme/theme.doc.mjs +79 -0
- package/authoring/doctypes/theme/type.ts +42 -0
- package/authoring/doctypes/types.ts +2 -1
- package/authoring/gap-report/gap-report.doc.d.mts +12 -0
- package/authoring/gap-report/gap-report.doc.mjs +183 -0
- package/authoring/index.d.mts +1 -0
- package/authoring/index.d.ts +6 -4
- package/authoring/index.mjs +2 -1
- package/authoring/integration/integration.doc.mjs +2 -2
- package/authoring/integration/type.ts +3 -9
- package/clients/cli/__tests__/cliManifest.test.ts +27 -29
- package/clients/cli/commands/blog.doc.mjs +1 -1
- package/clients/cli/commands/build.doc.mjs +1 -1
- package/clients/cli/commands/component.doc.mjs +1 -1
- package/clients/cli/commands/discover.doc.mjs +1 -1
- package/clients/cli/commands/docs.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-components.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.doc.mjs +1 -1
- package/clients/cli/commands/doctor-integration.test.mjs +15 -7
- package/clients/cli/commands/doctor.doc.mjs +1 -1
- package/clients/cli/commands/gap-report.doc.mjs +1 -1
- package/clients/cli/commands/hook.doc.mjs +1 -1
- package/clients/cli/commands/init.doc.mjs +1 -1
- package/clients/cli/commands/integration-add.controls.test.mjs +1 -1
- package/clients/cli/commands/integration-add.doc.mjs +1 -1
- package/clients/cli/commands/integration-pack.doc.mjs +1 -1
- package/clients/cli/commands/integration-real-world.test.mjs +3 -9
- package/clients/cli/commands/integration.doc.mjs +1 -1
- package/clients/cli/commands/layout-check.doc.mjs +1 -1
- package/clients/cli/commands/layout-expand.doc.mjs +1 -1
- package/clients/cli/commands/layout-grammar.doc.mjs +1 -1
- package/clients/cli/commands/layout.doc.mjs +1 -1
- package/clients/cli/commands/manifest.doc.mjs +1 -1
- package/clients/cli/commands/search.doc.mjs +1 -1
- package/clients/cli/commands/swizzle.doc.mjs +1 -1
- package/clients/cli/commands/template.doc.mjs +1 -1
- package/clients/cli/commands/theme-add.doc.mjs +2 -2
- package/clients/cli/commands/theme-build.doc.mjs +1 -1
- package/clients/cli/commands/theme-list.doc.mjs +2 -2
- package/clients/cli/commands/theme-palette-generate.doc.mjs +3 -3
- package/clients/cli/commands/theme-palette.doc.mjs +1 -1
- package/clients/cli/commands/theme-targets.doc.mjs +1 -1
- package/clients/cli/commands/theme-template.doc.mjs +1 -1
- package/clients/cli/commands/theme.doc.mjs +1 -1
- package/clients/cli/commands/upgrade.doc.mjs +1 -1
- package/clients/cli/lib/hook-format.mjs +14 -5
- package/foundation/config/project-themes.test.mjs +11 -19
- package/foundation/config/project.d.mts +8 -0
- package/foundation/config/project.mjs +50 -21
- package/foundation/config/project.test.mjs +3 -16
- package/foundation/discovery/authoring-self-docs.d.mts +6 -0
- package/foundation/discovery/authoring-self-docs.mjs +17 -7
- package/foundation/discovery/authoring-surface.d.mts +74 -0
- package/foundation/discovery/authoring-surface.mjs +525 -0
- package/foundation/discovery/authoring-surface.test.mjs +392 -0
- package/foundation/discovery/cli-self-docs.d.mts +113 -0
- package/foundation/discovery/cli-self-docs.mjs +514 -0
- package/foundation/discovery/cli-self-docs.test.mjs +437 -0
- package/foundation/discovery/component-loader.d.mts +35 -38
- package/foundation/discovery/component-loader.mjs +53 -222
- package/foundation/discovery/docs-discovery.d.mts +3 -2
- package/foundation/discovery/docs-discovery.mjs +8 -12
- package/foundation/discovery/docs-discovery.test.mjs +8 -5
- package/foundation/discovery/template-adapter.d.mts +7 -0
- package/foundation/discovery/template-adapter.mjs +27 -40
- package/foundation/discovery/template-adapter.test.mjs +15 -0
- package/foundation/discovery/theme-discovery.d.mts +67 -7
- package/foundation/discovery/theme-discovery.mjs +916 -186
- package/foundation/discovery/theme-discovery.test.mjs +613 -219
- package/foundation/doc-compiler/bundle.d.mts +47 -0
- package/foundation/doc-compiler/bundle.mjs +218 -0
- package/foundation/doc-compiler/bundle.test.mjs +255 -0
- package/foundation/doc-compiler/compile.d.mts +200 -0
- package/foundation/doc-compiler/compile.mjs +254 -5
- package/foundation/doc-compiler/diagnostics.d.mts +126 -0
- package/foundation/doc-compiler/diagnostics.mjs +277 -0
- package/foundation/doc-compiler/doc-compiler.test.mjs +28 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +1642 -0
- package/foundation/doc-compiler/import.d.mts +24 -0
- package/foundation/doc-compiler/import.mjs +59 -0
- package/foundation/doc-compiler/inputs.d.mts +102 -0
- package/foundation/doc-compiler/inputs.mjs +286 -0
- package/foundation/doc-compiler/inputs.test.mjs +299 -0
- package/foundation/doc-compiler/ir.d.mts +13 -0
- package/foundation/doc-compiler/ir.mjs +189 -12
- package/foundation/doc-compiler/lower-doc.test.mjs +492 -0
- package/foundation/doc-compiler/overlays.d.mts +37 -0
- package/foundation/doc-compiler/overlays.mjs +206 -0
- package/foundation/doc-compiler/parse-readable.d.mts +9 -0
- package/foundation/doc-compiler/parse-readable.mjs +29 -0
- package/foundation/doc-compiler/read.d.mts +126 -0
- package/foundation/doc-compiler/read.mjs +320 -0
- package/foundation/doc-compiler/read.test.mjs +313 -0
- package/foundation/doc-compiler/source.d.mts +33 -0
- package/foundation/doc-compiler/source.mjs +128 -0
- package/foundation/integrations/autolink.d.mts +58 -1
- package/foundation/integrations/autolink.mjs +143 -57
- package/foundation/integrations/contribution-fixes.d.mts +145 -0
- package/foundation/integrations/contribution-fixes.mjs +1284 -0
- package/foundation/integrations/contribution-inventory.d.mts +1 -1
- package/foundation/integrations/contribution-inventory.mjs +17 -20
- package/foundation/integrations/contribution-inventory.test.mjs +67 -27
- package/foundation/integrations/integrations.d.mts +3 -0
- package/foundation/integrations/integrations.mjs +11 -97
- package/foundation/integrations/provider-ledger.test.mjs +275 -0
- package/foundation/integrations/provider-resolution.d.mts +152 -0
- package/foundation/integrations/provider-resolution.mjs +576 -0
- package/foundation/integrations/provider-resolution.test.mjs +369 -0
- package/foundation/integrations/theme-descriptor.d.mts +8 -0
- package/foundation/integrations/theme-descriptor.mjs +44 -0
- package/foundation/integrations/validate-contributions.mjs +104 -10
- package/foundation/response/error-codes.doc.mjs +2 -2
- package/foundation/response/error-codes.mjs +1 -1
- package/foundation/response/response-types.doc.mjs +1 -1
- package/foundation/response/response.doc.mjs +1 -1
- package/foundation/text/string-utils.mjs +22 -10
- package/package.json +9 -9
- package/assets/templates/themes/manifest.json +0 -95
|
@@ -77,6 +77,7 @@ describe('integrationAdd component', () => {
|
|
|
77
77
|
path.join(tmpDir, 'components/MyWidget.doc.mjs'),
|
|
78
78
|
'utf-8',
|
|
79
79
|
);
|
|
80
|
+
expect(doc).toContain("@astryxdesign/cli/authoring').ComponentDoc");
|
|
80
81
|
expect(doc).toContain("type: 'component'");
|
|
81
82
|
expect(doc).toContain("name: 'MyWidget'");
|
|
82
83
|
expect(doc).toContain('props: []');
|
|
@@ -304,6 +305,7 @@ describe('integrationAdd doc', () => {
|
|
|
304
305
|
path.join(tmpDir, 'docs/my-guide.doc.mjs'),
|
|
305
306
|
'utf-8',
|
|
306
307
|
);
|
|
308
|
+
expect(doc).toContain("@astryxdesign/cli/authoring').ReferenceDoc");
|
|
307
309
|
expect(doc).toContain("type: 'generic'");
|
|
308
310
|
expect(doc).toContain("name: 'my-guide'");
|
|
309
311
|
|
|
@@ -390,13 +392,14 @@ describe('integrationAdd template', () => {
|
|
|
390
392
|
expect(result.data.kind).toBe('template');
|
|
391
393
|
expect(result.data.name).toBe('my-widget');
|
|
392
394
|
expect(result.data.root).toEqual({path: './templates', created: true});
|
|
393
|
-
expect(result.data.files).toContain('templates/my-widget.
|
|
395
|
+
expect(result.data.files).toContain('templates/my-widget.doc.mjs');
|
|
394
396
|
expect(result.data.files).toContain('templates/my-widget.tsx');
|
|
395
397
|
|
|
396
398
|
const spec = fs.readFileSync(
|
|
397
|
-
path.join(tmpDir, 'templates/my-widget.
|
|
399
|
+
path.join(tmpDir, 'templates/my-widget.doc.mjs'),
|
|
398
400
|
'utf-8',
|
|
399
401
|
);
|
|
402
|
+
expect(spec).toContain("@astryxdesign/cli/authoring').TemplateDoc");
|
|
400
403
|
expect(spec).toContain("type: 'page'");
|
|
401
404
|
expect(spec).toContain("name: 'my-widget'");
|
|
402
405
|
|
|
@@ -432,10 +435,11 @@ describe('integrationAdd template', () => {
|
|
|
432
435
|
templateType: 'block',
|
|
433
436
|
});
|
|
434
437
|
const spec = fs.readFileSync(
|
|
435
|
-
path.join(tmpDir, 'templates/my-card.
|
|
438
|
+
path.join(tmpDir, 'templates/my-card.doc.mjs'),
|
|
436
439
|
'utf-8',
|
|
437
440
|
);
|
|
438
441
|
expect(spec).toContain("type: 'block'");
|
|
442
|
+
expect(spec).toContain('aspectRatio: 1');
|
|
439
443
|
});
|
|
440
444
|
|
|
441
445
|
it('rejects an invalid template type', async () => {
|
|
@@ -676,6 +680,35 @@ describe('custom root preservation', () => {
|
|
|
676
680
|
});
|
|
677
681
|
});
|
|
678
682
|
|
|
683
|
+
describe('typed descriptor conformance', () => {
|
|
684
|
+
it('emits a strongly typed .doc.mjs for every canonical item writer', async () => {
|
|
685
|
+
setup();
|
|
686
|
+
await integrationAdd('component', 'MyWidget', {cwd: tmpDir});
|
|
687
|
+
await integrationAdd('doc', 'my-guide', {cwd: tmpDir});
|
|
688
|
+
await integrationAdd('template', 'my-page', {cwd: tmpDir});
|
|
689
|
+
await integrationAdd('theme', 'ocean', {cwd: tmpDir});
|
|
690
|
+
|
|
691
|
+
// Codemod and agent-doc keep their released formats for compatibility;
|
|
692
|
+
// no new kind may copy those exceptions.
|
|
693
|
+
for (const [relativePath, type] of [
|
|
694
|
+
['components/MyWidget.doc.mjs', 'ComponentDoc'],
|
|
695
|
+
['docs/my-guide.doc.mjs', 'ReferenceDoc'],
|
|
696
|
+
['templates/my-page.doc.mjs', 'TemplateDoc'],
|
|
697
|
+
['themes/ocean/oceanTheme.doc.mjs', 'ThemeDoc'],
|
|
698
|
+
]) {
|
|
699
|
+
const source = fs.readFileSync(path.join(tmpDir, relativePath), 'utf-8');
|
|
700
|
+
expect(source).toContain(`@astryxdesign/cli/authoring').${type}`);
|
|
701
|
+
expect(source).toContain('export default {');
|
|
702
|
+
}
|
|
703
|
+
expect(
|
|
704
|
+
fs.existsSync(path.join(tmpDir, 'templates/my-page.template.mjs')),
|
|
705
|
+
).toBe(false);
|
|
706
|
+
expect(fs.existsSync(path.join(tmpDir, 'themes/manifest.json'))).toBe(
|
|
707
|
+
false,
|
|
708
|
+
);
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
|
|
679
712
|
describe('public per-kind APIs', () => {
|
|
680
713
|
it('exposes narrow functions for every non-theme contribution kind', async () => {
|
|
681
714
|
setup();
|
|
@@ -711,6 +744,7 @@ describe('public dispatcher', () => {
|
|
|
711
744
|
const result = await integrationAdd('theme', 'ocean', {cwd: tmpDir});
|
|
712
745
|
expect(result.data).toMatchObject({kind: 'theme', name: 'ocean'});
|
|
713
746
|
expect(result.data.files).toContain('themes/ocean/oceanTheme.ts');
|
|
747
|
+
expect(result.data.files).toContain('themes/ocean/oceanTheme.doc.mjs');
|
|
714
748
|
});
|
|
715
749
|
|
|
716
750
|
it('refuses a kind-specific option on the wrong kind', async () => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file `astryx integration add theme` — scaffold one
|
|
5
|
-
*
|
|
6
|
-
* first use.
|
|
4
|
+
* @file `astryx integration add theme` — scaffold one strongly typed,
|
|
5
|
+
* same-stem source/descriptor pair into an integration package and declare the
|
|
6
|
+
* themes root on first use.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import * as fs from 'node:fs';
|
|
@@ -15,16 +15,14 @@ import {
|
|
|
15
15
|
PathSafetyError,
|
|
16
16
|
sanitizeName,
|
|
17
17
|
} from '../../foundation/fs/path-safety.mjs';
|
|
18
|
-
import {
|
|
19
|
-
discoverThemeCatalog,
|
|
20
|
-
THEME_MANIFEST_BASENAME,
|
|
21
|
-
} from '../../foundation/discovery/theme-discovery.mjs';
|
|
18
|
+
import {discoverThemeDirectory} from '../../foundation/discovery/theme-discovery.mjs';
|
|
22
19
|
import {
|
|
23
20
|
findLocalIntegrationManifestOrNull,
|
|
24
21
|
IntegrationRootConflictError,
|
|
25
22
|
patchIntegrationRoot,
|
|
26
23
|
} from '../../foundation/integrations/manifest-writer.mjs';
|
|
27
24
|
import {loadManifestObject} from '../../foundation/integrations/integrations.mjs';
|
|
25
|
+
import {themeDescriptorSource} from '../../foundation/integrations/theme-descriptor.mjs';
|
|
28
26
|
import {assertContributionVisible} from '../../foundation/integrations/contribution-inventory.mjs';
|
|
29
27
|
import {
|
|
30
28
|
applyWrites,
|
|
@@ -70,6 +68,7 @@ function themeIdentity(slug) {
|
|
|
70
68
|
displayName,
|
|
71
69
|
exportName: `${identifier}Theme`,
|
|
72
70
|
entry: `${identifier}Theme.ts`,
|
|
71
|
+
descriptor: `${identifier}Theme.doc.mjs`,
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
74
|
|
|
@@ -78,9 +77,20 @@ function themeSource(identity) {
|
|
|
78
77
|
return `import {defineTheme} from '@astryxdesign/core/theme';\n\nexport const ${identity.exportName} = defineTheme({\n name: '${identity.slug}',\n});\n`;
|
|
79
78
|
}
|
|
80
79
|
|
|
80
|
+
/** @param {{slug: string, displayName: string}} identity */
|
|
81
|
+
function themeDescriptor(identity) {
|
|
82
|
+
return themeDescriptorSource({
|
|
83
|
+
type: 'theme',
|
|
84
|
+
name: identity.slug,
|
|
85
|
+
displayName: identity.displayName,
|
|
86
|
+
description: `${identity.displayName} theme.`,
|
|
87
|
+
maintained: true,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
81
91
|
/**
|
|
82
|
-
* Read
|
|
83
|
-
*
|
|
92
|
+
* Read bytes back through the same discovery seam Project uses. A write is not
|
|
93
|
+
* successful until the requested slug resolves.
|
|
84
94
|
* @param {string} packageDir
|
|
85
95
|
* @param {string} manifestFile
|
|
86
96
|
* @param {string} owner
|
|
@@ -176,6 +186,9 @@ export async function integrationAddTheme(name, options = {}) {
|
|
|
176
186
|
dryRun: true,
|
|
177
187
|
createIfMissing: true,
|
|
178
188
|
});
|
|
189
|
+
if (fs.existsSync(root)) {
|
|
190
|
+
discoverThemeDirectory(root, owner);
|
|
191
|
+
}
|
|
179
192
|
} catch (error) {
|
|
180
193
|
if (error instanceof IntegrationRootConflictError) {
|
|
181
194
|
throw new AstryxError(
|
|
@@ -197,72 +210,29 @@ export async function integrationAddTheme(name, options = {}) {
|
|
|
197
210
|
const sourceFile = assertWithin(identity.entry, themeDir, {
|
|
198
211
|
label: 'theme source file',
|
|
199
212
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
`Refusing to overwrite existing file ${projectPath(path.relative(packageDir, sourceFile))}.`,
|
|
203
|
-
undefined,
|
|
204
|
-
ERROR_CODES.ERR_FILE_EXISTS,
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const catalogFile = assertWithin(THEME_MANIFEST_BASENAME, root, {
|
|
209
|
-
label: 'theme catalog manifest',
|
|
213
|
+
const descriptorFile = assertWithin(identity.descriptor, themeDir, {
|
|
214
|
+
label: 'theme descriptor file',
|
|
210
215
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
let catalogOriginal = null;
|
|
215
|
-
if (fs.existsSync(catalogFile)) {
|
|
216
|
-
try {
|
|
217
|
-
catalogOriginal = fs.readFileSync(catalogFile);
|
|
218
|
-
catalog = JSON.parse(catalogOriginal.toString('utf-8'));
|
|
219
|
-
// The discovery seam is the schema/source-of-truth validator. The write
|
|
220
|
-
// plan carries catalogOriginal, so any concurrent change between this read
|
|
221
|
-
// and publish is refused rather than overwritten with this snapshot.
|
|
222
|
-
discoverThemeCatalog(root, owner);
|
|
223
|
-
} catch (error) {
|
|
216
|
+
// An existing folder is filled; only a file this would write is refused.
|
|
217
|
+
for (const file of [sourceFile, descriptorFile]) {
|
|
218
|
+
if (fs.existsSync(file)) {
|
|
224
219
|
throw new AstryxError(
|
|
225
|
-
|
|
220
|
+
`Refusing to overwrite existing file ${projectPath(path.relative(packageDir, file))}.`,
|
|
226
221
|
undefined,
|
|
227
|
-
ERROR_CODES.
|
|
222
|
+
ERROR_CODES.ERR_FILE_EXISTS,
|
|
228
223
|
);
|
|
229
224
|
}
|
|
230
225
|
}
|
|
231
|
-
if (
|
|
232
|
-
catalog.themes.some(
|
|
233
|
-
theme =>
|
|
234
|
-
typeof theme.slug === 'string' &&
|
|
235
|
-
theme.slug.toLowerCase() === identity.slug.toLowerCase(),
|
|
236
|
-
)
|
|
237
|
-
) {
|
|
238
|
-
throw new AstryxError(
|
|
239
|
-
`Theme "${identity.slug}" already exists in ${projectPath(path.relative(packageDir, catalogFile))}.`,
|
|
240
|
-
undefined,
|
|
241
|
-
ERROR_CODES.ERR_FILE_EXISTS,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
catalog.themes.push({
|
|
246
|
-
slug: identity.slug,
|
|
247
|
-
displayName: identity.displayName,
|
|
248
|
-
description: `${identity.displayName} theme.`,
|
|
249
|
-
maintained: true,
|
|
250
|
-
entry: identity.entry,
|
|
251
|
-
exportName: identity.exportName,
|
|
252
|
-
files: [identity.entry],
|
|
253
|
-
});
|
|
254
226
|
|
|
255
227
|
/** @type {import('./add-helpers.mjs').WritePlan[]} */
|
|
256
|
-
const plans = [
|
|
257
|
-
plans.push(
|
|
228
|
+
const plans = [
|
|
258
229
|
{path: sourceFile, contents: themeSource(identity), createOnly: true},
|
|
259
230
|
{
|
|
260
|
-
path:
|
|
261
|
-
contents:
|
|
262
|
-
createOnly:
|
|
263
|
-
expectedOriginal: catalogOriginal ?? undefined,
|
|
231
|
+
path: descriptorFile,
|
|
232
|
+
contents: themeDescriptor(identity),
|
|
233
|
+
createOnly: true,
|
|
264
234
|
},
|
|
265
|
-
|
|
235
|
+
];
|
|
266
236
|
const packageUpdate = packageJsonUpdate(
|
|
267
237
|
packageFile,
|
|
268
238
|
rootPath,
|
|
@@ -32,7 +32,7 @@ afterEach(() => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
describe('integrationAddTheme', () => {
|
|
35
|
-
it('writes a
|
|
35
|
+
it('writes a typed same-stem descriptor and source with no central catalog', async () => {
|
|
36
36
|
setup();
|
|
37
37
|
const result = await integrationAddTheme('ocean', {cwd: tmpDir});
|
|
38
38
|
|
|
@@ -45,7 +45,7 @@ describe('integrationAddTheme', () => {
|
|
|
45
45
|
manifest: 'astryx.integration.mjs',
|
|
46
46
|
files: [
|
|
47
47
|
'themes/ocean/oceanTheme.ts',
|
|
48
|
-
'themes/
|
|
48
|
+
'themes/ocean/oceanTheme.doc.mjs',
|
|
49
49
|
'package.json',
|
|
50
50
|
'astryx.integration.mjs',
|
|
51
51
|
],
|
|
@@ -56,21 +56,17 @@ describe('integrationAddTheme', () => {
|
|
|
56
56
|
expect(
|
|
57
57
|
fs.readFileSync(path.join(tmpDir, 'themes/ocean/oceanTheme.ts'), 'utf-8'),
|
|
58
58
|
).toContain('export const oceanTheme = defineTheme');
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
files: ['oceanTheme.ts'],
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
});
|
|
59
|
+
|
|
60
|
+
const descriptor = fs.readFileSync(
|
|
61
|
+
path.join(tmpDir, 'themes/ocean/oceanTheme.doc.mjs'),
|
|
62
|
+
'utf-8',
|
|
63
|
+
);
|
|
64
|
+
expect(descriptor).toContain("@astryxdesign/cli/authoring').ThemeDoc");
|
|
65
|
+
expect(descriptor).toContain("type: 'theme'");
|
|
66
|
+
expect(descriptor).toContain("name: 'ocean'");
|
|
67
|
+
expect(fs.existsSync(path.join(tmpDir, 'themes/manifest.json'))).toBe(
|
|
68
|
+
false,
|
|
69
|
+
);
|
|
74
70
|
expect(
|
|
75
71
|
fs.readFileSync(path.join(tmpDir, 'astryx.integration.mjs'), 'utf-8'),
|
|
76
72
|
).toContain("themes: './themes'");
|
|
@@ -96,6 +92,7 @@ describe('integrationAddTheme', () => {
|
|
|
96
92
|
expect(result.data.dryRun).toBe(true);
|
|
97
93
|
expect(result.data.root).toEqual({path: './themes', created: true});
|
|
98
94
|
expect(result.data.files).toContain('themes/ocean/oceanTheme.ts');
|
|
95
|
+
expect(result.data.files).toContain('themes/ocean/oceanTheme.doc.mjs');
|
|
99
96
|
expect(fs.existsSync(path.join(tmpDir, 'themes'))).toBe(false);
|
|
100
97
|
expect(
|
|
101
98
|
fs.readFileSync(path.join(tmpDir, 'astryx.integration.mjs'), 'utf-8'),
|
|
@@ -125,22 +122,69 @@ describe('integrationAddTheme', () => {
|
|
|
125
122
|
expect(
|
|
126
123
|
fs.existsSync(path.join(tmpDir, 'src/themes/ocean/oceanTheme.ts')),
|
|
127
124
|
).toBe(true);
|
|
125
|
+
expect(
|
|
126
|
+
fs.existsSync(path.join(tmpDir, 'src/themes/ocean/oceanTheme.doc.mjs')),
|
|
127
|
+
).toBe(true);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
it('
|
|
130
|
+
it('keeps each theme to its own folder: a second theme touches no shared file', async () => {
|
|
131
|
+
setup({manifest: "export default {themes: './themes'};\n"});
|
|
132
|
+
await integrationAddTheme('ocean', {cwd: tmpDir});
|
|
133
|
+
const shared = ['package.json', 'astryx.integration.mjs'].map(file =>
|
|
134
|
+
fs.readFileSync(path.join(tmpDir, file), 'utf-8'),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const result = await integrationAddTheme('reef', {cwd: tmpDir});
|
|
138
|
+
|
|
139
|
+
expect(result.data.files).toEqual([
|
|
140
|
+
'themes/reef/reefTheme.ts',
|
|
141
|
+
'themes/reef/reefTheme.doc.mjs',
|
|
142
|
+
]);
|
|
143
|
+
expect(fs.readdirSync(path.join(tmpDir, 'themes')).sort()).toEqual([
|
|
144
|
+
'ocean',
|
|
145
|
+
'reef',
|
|
146
|
+
]);
|
|
147
|
+
expect(
|
|
148
|
+
['package.json', 'astryx.integration.mjs'].map(file =>
|
|
149
|
+
fs.readFileSync(path.join(tmpDir, file), 'utf-8'),
|
|
150
|
+
),
|
|
151
|
+
).toEqual(shared);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('refuses an obsolete central catalog without changing its bytes', async () => {
|
|
131
155
|
setup();
|
|
132
156
|
fs.mkdirSync(path.join(tmpDir, 'themes'));
|
|
133
157
|
const catalog = path.join(tmpDir, 'themes', 'manifest.json');
|
|
134
|
-
fs.writeFileSync(catalog, '{
|
|
158
|
+
fs.writeFileSync(catalog, '{"version":1}\n');
|
|
135
159
|
|
|
136
160
|
await expect(
|
|
137
161
|
integrationAddTheme('ocean', {cwd: tmpDir}),
|
|
138
162
|
).rejects.toMatchObject({code: 'ERR_THEME_INVALID'});
|
|
139
|
-
expect(fs.readFileSync(catalog, 'utf-8')).toBe('{
|
|
163
|
+
expect(fs.readFileSync(catalog, 'utf-8')).toBe('{"version":1}\n');
|
|
140
164
|
expect(fs.existsSync(path.join(tmpDir, 'themes', 'ocean'))).toBe(false);
|
|
141
165
|
});
|
|
142
166
|
|
|
143
|
-
it('refuses
|
|
167
|
+
it('refuses an invalid existing descriptor without changing it', async () => {
|
|
168
|
+
setup();
|
|
169
|
+
const existing = path.join(tmpDir, 'themes', 'forest');
|
|
170
|
+
fs.mkdirSync(existing, {recursive: true});
|
|
171
|
+
const descriptor = path.join(existing, 'forestTheme.doc.mjs');
|
|
172
|
+
fs.writeFileSync(descriptor, 'export default {type: "theme"};\n');
|
|
173
|
+
fs.writeFileSync(
|
|
174
|
+
path.join(existing, 'forestTheme.ts'),
|
|
175
|
+
'export const forestTheme = {};\n',
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
await expect(
|
|
179
|
+
integrationAddTheme('ocean', {cwd: tmpDir}),
|
|
180
|
+
).rejects.toMatchObject({code: 'ERR_THEME_INVALID'});
|
|
181
|
+
expect(fs.readFileSync(descriptor, 'utf-8')).toBe(
|
|
182
|
+
'export default {type: "theme"};\n',
|
|
183
|
+
);
|
|
184
|
+
expect(fs.existsSync(path.join(tmpDir, 'themes', 'ocean'))).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('refuses a duplicate valid theme directory', async () => {
|
|
144
188
|
setup();
|
|
145
189
|
await integrationAddTheme('ocean', {cwd: tmpDir});
|
|
146
190
|
await expect(
|
|
@@ -148,6 +192,46 @@ describe('integrationAddTheme', () => {
|
|
|
148
192
|
).rejects.toMatchObject({code: 'ERR_FILE_EXISTS'});
|
|
149
193
|
});
|
|
150
194
|
|
|
195
|
+
it('refuses an existing invalid theme directory', async () => {
|
|
196
|
+
setup();
|
|
197
|
+
fs.mkdirSync(path.join(tmpDir, 'themes', 'ocean'), {recursive: true});
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
path.join(tmpDir, 'themes', 'ocean', 'oceanTheme.ts'),
|
|
200
|
+
'export const oceanTheme = {};\n',
|
|
201
|
+
);
|
|
202
|
+
await expect(
|
|
203
|
+
integrationAddTheme('ocean', {cwd: tmpDir}),
|
|
204
|
+
).rejects.toMatchObject({code: 'ERR_THEME_INVALID'});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it.each([
|
|
208
|
+
['an empty folder', []],
|
|
209
|
+
['a folder holding .gitkeep', ['.gitkeep']],
|
|
210
|
+
['a folder holding NOTES.md', ['NOTES.md']],
|
|
211
|
+
])('fills %s', async (_, files) => {
|
|
212
|
+
setup();
|
|
213
|
+
const dir = path.join(tmpDir, 'themes', 'ocean');
|
|
214
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
215
|
+
for (const file of files) fs.writeFileSync(path.join(dir, file), 'x\n');
|
|
216
|
+
const result = await integrationAddTheme('ocean', {cwd: tmpDir});
|
|
217
|
+
expect(result.data.written).toBe(true);
|
|
218
|
+
expect(fs.readdirSync(dir).sort()).toEqual(
|
|
219
|
+
[...files, 'oceanTheme.doc.mjs', 'oceanTheme.ts'].sort(),
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('refuses to overwrite a file it would write', async () => {
|
|
224
|
+
setup();
|
|
225
|
+
await integrationAddTheme('ocean', {cwd: tmpDir});
|
|
226
|
+
await expect(
|
|
227
|
+
integrationAddTheme('ocean', {cwd: tmpDir}),
|
|
228
|
+
).rejects.toMatchObject({
|
|
229
|
+
code: 'ERR_FILE_EXISTS',
|
|
230
|
+
message:
|
|
231
|
+
'Refusing to overwrite existing file themes/ocean/oceanTheme.ts.',
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
151
235
|
it.each(['../ocean', 'Ocean', 'ocean theme', '.ocean'])(
|
|
152
236
|
'refuses unsafe or noncanonical name %s',
|
|
153
237
|
async name => {
|
|
@@ -33,7 +33,7 @@ function writeIntegration({id, name, type = 'block', root = tmpDir}) {
|
|
|
33
33
|
const stem = path.join(root, 'templates', id);
|
|
34
34
|
fs.mkdirSync(path.dirname(stem), {recursive: true});
|
|
35
35
|
fs.writeFileSync(
|
|
36
|
-
`${stem}.
|
|
36
|
+
`${stem}.doc.mjs`,
|
|
37
37
|
`export default {type: '${type}', name: ${JSON.stringify(name)}, description: 'fixture'};\n`,
|
|
38
38
|
);
|
|
39
39
|
fs.writeFileSync(
|
|
@@ -176,7 +176,7 @@ describe('integrationTemplateConflicts', () => {
|
|
|
176
176
|
);
|
|
177
177
|
fs.mkdirSync(path.join(tmpDir, 'templates'), {recursive: true});
|
|
178
178
|
fs.writeFileSync(
|
|
179
|
-
path.join(tmpDir, 'templates', 'broken.
|
|
179
|
+
path.join(tmpDir, 'templates', 'broken.doc.mjs'),
|
|
180
180
|
`throw new Error('broken fixture');\n`,
|
|
181
181
|
);
|
|
182
182
|
fs.writeFileSync(
|
|
@@ -221,7 +221,9 @@ describe('integrationComponentConflicts', () => {
|
|
|
221
221
|
expect(core).toBeDefined();
|
|
222
222
|
writeComponentIntegration(core.name);
|
|
223
223
|
|
|
224
|
-
const result = await integrationComponentConflicts(undefined, {
|
|
224
|
+
const result = await integrationComponentConflicts(undefined, {
|
|
225
|
+
cwd: tmpDir,
|
|
226
|
+
});
|
|
225
227
|
|
|
226
228
|
expect(result.type).toBe('integration.component-conflicts');
|
|
227
229
|
expect(result.data.issues).toEqual([]);
|
|
@@ -240,7 +242,9 @@ describe('integrationComponentConflicts', () => {
|
|
|
240
242
|
it('does not flag an integration-only component name', async () => {
|
|
241
243
|
writeComponentIntegration('AcmeOnlyWidget');
|
|
242
244
|
|
|
243
|
-
const result = await integrationComponentConflicts(undefined, {
|
|
245
|
+
const result = await integrationComponentConflicts(undefined, {
|
|
246
|
+
cwd: tmpDir,
|
|
247
|
+
});
|
|
244
248
|
|
|
245
249
|
expect(result.data.conflicts).toEqual([]);
|
|
246
250
|
expect(result.data.issues).toEqual([]);
|
|
@@ -51,7 +51,7 @@ const ESBUILD_BIN = resolveBin('esbuild/bin/esbuild');
|
|
|
51
51
|
let tmpDir;
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* Build an @acme/widgets fixture: a block template (`.template.ts`
|
|
54
|
+
* Build an @acme/widgets fixture: a block template (released `.template.ts` compatibility descriptor +
|
|
55
55
|
* same-stem `.tsx` source) under `templates/`, plus an astryx.integration
|
|
56
56
|
* manifest, plus a caller-supplied `exports` map.
|
|
57
57
|
* @param {Record<string, string> | undefined} exportsMap
|
|
@@ -71,7 +71,7 @@ function makeWidgets(exportsMap) {
|
|
|
71
71
|
path.join(pkgDir, 'astryx.integration.mjs'),
|
|
72
72
|
`export default { templates: './templates' };\n`,
|
|
73
73
|
);
|
|
74
|
-
// The
|
|
74
|
+
// The released `.template.*` compatibility form remains resolvable.
|
|
75
75
|
fs.writeFileSync(
|
|
76
76
|
path.join(blockDir, 'GaugeShowcase.template.ts'),
|
|
77
77
|
`export default { name: 'Gauge showcase', description: 'A gauge.' };\n`,
|
|
@@ -127,10 +127,14 @@ function writeTsconfig() {
|
|
|
127
127
|
/** @returns {{ok: boolean, out: string}} */
|
|
128
128
|
function runTsc() {
|
|
129
129
|
try {
|
|
130
|
-
execFileSync(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
execFileSync(
|
|
131
|
+
process.execPath,
|
|
132
|
+
[TSC_BIN, '--noEmit', '-p', 'tsconfig.json'],
|
|
133
|
+
{
|
|
134
|
+
cwd: tmpDir,
|
|
135
|
+
stdio: 'pipe',
|
|
136
|
+
},
|
|
137
|
+
);
|
|
134
138
|
return {ok: true, out: ''};
|
|
135
139
|
} catch (e) {
|
|
136
140
|
return {ok: false, out: `${e.stdout ?? ''}${e.stderr ?? ''}`};
|
|
@@ -5,10 +5,11 @@ export const doc = {
|
|
|
5
5
|
type: 'function',
|
|
6
6
|
kind: 'api',
|
|
7
7
|
name: 'integrationAddTheme',
|
|
8
|
+
namespace: 'cli/api',
|
|
8
9
|
displayName: 'integrationAddTheme()',
|
|
9
10
|
summary: 'Add a source theme to an integration package.',
|
|
10
11
|
description:
|
|
11
|
-
'Creates an integration manifest when the package has none, writes a minimal named-export defineTheme source file
|
|
12
|
+
'Creates an integration manifest when the package has none, writes a minimal named-export defineTheme source file plus its strongly typed same-stem ThemeDoc, declares the optional themes root on first use, and includes the manifest and root in package.json files when the package already uses an allowlist. Every path is confined to the package and existing theme directories are never overwritten.',
|
|
12
13
|
importPath: '@astryxdesign/cli/api',
|
|
13
14
|
signature:
|
|
14
15
|
'integrationAddTheme(name: string, options?: IntegrationAddThemeOptions): Promise<IntegrationAddResponse>',
|
|
@@ -17,7 +18,7 @@ export const doc = {
|
|
|
17
18
|
{
|
|
18
19
|
name: 'name',
|
|
19
20
|
type: 'string',
|
|
20
|
-
description: 'Lowercase kebab-case theme name and
|
|
21
|
+
description: 'Lowercase kebab-case theme name and directory slug.',
|
|
21
22
|
required: true,
|
|
22
23
|
},
|
|
23
24
|
{
|
|
@@ -51,7 +52,7 @@ export const doc = {
|
|
|
51
52
|
},
|
|
52
53
|
{
|
|
53
54
|
code: 'ERR_FILE_EXISTS',
|
|
54
|
-
when: 'the theme slug
|
|
55
|
+
when: 'the theme slug directory already exists',
|
|
55
56
|
},
|
|
56
57
|
{
|
|
57
58
|
code: 'ERR_PATH_TRAVERSAL',
|
|
@@ -63,7 +64,7 @@ export const doc = {
|
|
|
63
64
|
},
|
|
64
65
|
{
|
|
65
66
|
code: 'ERR_THEME_INVALID',
|
|
66
|
-
when: 'the package, integration manifest, or theme
|
|
67
|
+
when: 'the package, integration manifest, or theme descriptor is invalid',
|
|
67
68
|
},
|
|
68
69
|
{
|
|
69
70
|
code: 'ERR_WRITE_FAILED',
|
|
@@ -80,6 +81,6 @@ export const doc = {
|
|
|
80
81
|
code: "await integrationAddTheme('ocean', {dryRun: true});",
|
|
81
82
|
},
|
|
82
83
|
],
|
|
83
|
-
command: 'integration add',
|
|
84
|
+
command: 'integration add theme',
|
|
84
85
|
related: ['themeList', 'themeAdd', 'validateIntegration'],
|
|
85
86
|
};
|