@astryxdesign/cli 0.1.2 → 0.1.3
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 +64 -1
- package/README.md +49 -55
- package/docs/layout.doc.dense.mjs +90 -0
- package/docs/layout.doc.mjs +160 -0
- package/docs/principles.doc.dense.mjs +2 -2
- package/docs/principles.doc.mjs +4 -0
- package/docs/principles.doc.zh.mjs +2 -2
- package/docs/theme.doc.mjs +2 -2
- package/package.json +13 -1
- package/src/api/blog.mjs +192 -0
- package/src/api/blog.test.mjs +144 -0
- package/src/api/component.mjs +253 -12
- package/src/api/discover.mjs +14 -7
- package/src/api/doctor.mjs +10 -25
- package/src/api/index.mjs +1 -0
- package/src/api/layout.mjs +11 -10
- package/src/api/layout.test.mjs +4 -1
- package/src/api/template-integration.test.mjs +225 -0
- package/src/api/template.mjs +242 -84
- package/src/api/validate-integration.mjs +370 -0
- package/src/api/validate-integration.test.mjs +222 -0
- package/src/codemod.mjs +93 -0
- package/src/codemod.test.mjs +134 -0
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/__tests__/runner.test.mjs +103 -0
- package/src/codemods/integration-discovery.mjs +168 -0
- package/src/codemods/integration-discovery.test.mjs +234 -0
- package/src/codemods/integration-runner.mjs +109 -0
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/run-codemod.mjs +207 -0
- package/src/codemods/runner.mjs +73 -96
- package/src/codemods/transforms/v0.0.15/index.mjs +0 -13
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +67 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +61 -0
- package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +104 -0
- package/src/codemods/transforms/{v0.0.15 → v0.1.0}/drop-xds-prefix-imports.mjs +15 -4
- package/src/codemods/transforms/v0.1.0/index.mjs +43 -6
- package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +77 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +78 -0
- package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +360 -0
- package/src/codemods/transforms/v0.1.3/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +260 -0
- package/src/commands/agent-docs.mjs +4 -1
- package/src/commands/blog.mjs +60 -0
- package/src/commands/build-theme.import-path.test.mjs +2 -12
- package/src/commands/build-theme.mjs +117 -0
- package/src/commands/build-theme.prose.test.mjs +5 -15
- package/src/commands/build-theme.watch.test.mjs +149 -0
- package/src/commands/component/index.mjs +44 -10
- package/src/commands/component-ownership.test.mjs +227 -0
- package/src/commands/discover.mjs +5 -16
- package/src/commands/doctor.test.mjs +3 -3
- package/src/commands/ensure-core-built.mjs +120 -0
- package/src/commands/json-contract.test.mjs +0 -32
- package/src/commands/swizzle.mjs +224 -235
- package/src/commands/swizzle.path-safety.test.mjs +3 -3
- package/src/commands/swizzle.routing.test.mjs +279 -0
- package/src/commands/template.mjs +33 -37
- package/src/commands/upgrade.config-ordering.test.mjs +273 -0
- package/src/commands/upgrade.integration-policy.test.mjs +204 -0
- package/src/commands/upgrade.mjs +349 -170
- package/src/commands/validate-integration.mjs +110 -0
- package/src/commands/validate-integration.test.mjs +124 -0
- package/src/config.mjs +7 -20
- package/src/config.test.mjs +89 -13
- package/src/index.mjs +8 -3
- package/src/integration.mjs +19 -0
- package/src/lib/component-discovery.mjs +177 -0
- package/src/lib/config-cache.mjs +91 -0
- package/src/lib/config-cache.test.mjs +83 -0
- package/src/lib/config-schema.mjs +41 -55
- package/src/lib/error-codes.mjs +15 -12
- package/src/lib/integration-warnings.mjs +62 -0
- package/src/lib/integration-warnings.test.mjs +102 -0
- package/src/lib/integrations.mjs +92 -115
- package/src/lib/integrations.test.mjs +88 -107
- package/src/lib/manifest.mjs +5 -2
- package/src/lib/module-loader.mjs +80 -0
- package/src/lib/module-loader.test.mjs +106 -0
- package/src/lib/project.mjs +502 -0
- package/src/lib/project.test.mjs +308 -0
- package/src/lib/site.mjs +20 -0
- package/src/template.mjs +73 -0
- package/src/template.test.mjs +127 -0
- package/src/types/api.d.ts +2 -6
- package/src/types/base.d.ts +3 -9
- package/src/types/codemod.d.ts +81 -0
- package/src/types/component.d.ts +27 -2
- package/src/types/config.d.ts +56 -85
- package/src/types/error-codes.d.ts +5 -3
- package/src/types/index.d.ts +0 -1
- package/src/types/integration.d.ts +29 -0
- package/src/types/swizzle.d.ts +9 -2
- package/src/types/template-api.d.ts +54 -0
- package/src/types/template.d.ts +10 -7
- package/src/types/upgrade.d.ts +29 -0
- package/src/types/validate-integration.d.ts +24 -0
- package/src/utils/github.mjs +0 -237
- package/src/utils/interactive.mjs +2 -2
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.doc.mjs +14 -0
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +28 -0
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.doc.mjs +14 -0
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.tsx +34 -0
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.doc.mjs +14 -0
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.tsx +21 -0
- package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.tsx +15 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.doc.mjs +14 -0
- package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.tsx +16 -0
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.doc.mjs +19 -0
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.tsx +34 -0
- package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.tsx +30 -0
- package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.doc.mjs +14 -0
- package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.tsx +31 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.tsx +26 -0
- package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.tsx +32 -0
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.doc.mjs +18 -0
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.tsx +32 -0
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.tsx +27 -0
- package/templates/blocks/components/ContextMenu/ContextMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/ContextMenu/ContextMenuBasic.tsx +32 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +46 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +52 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +14 -0
- package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +43 -0
- package/templates/blocks/components/DialogHeader/DialogHeaderBasic.doc.mjs +14 -0
- package/templates/blocks/components/DialogHeader/DialogHeaderBasic.tsx +30 -0
- package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +0 -6
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.tsx +27 -0
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +1 -5
- package/templates/blocks/components/FieldLabel/FieldLabelBasic.doc.mjs +14 -0
- package/templates/blocks/components/FieldLabel/FieldLabelBasic.tsx +20 -0
- package/templates/blocks/components/FieldStatus/FieldStatusBasic.doc.mjs +14 -0
- package/templates/blocks/components/FieldStatus/FieldStatusBasic.tsx +23 -0
- package/templates/blocks/components/FileInput/FileInputBasic.doc.mjs +14 -0
- package/templates/blocks/components/FileInput/FileInputBasic.tsx +22 -0
- package/templates/blocks/components/GridSpan/GridSpanColumns.doc.mjs +14 -0
- package/templates/blocks/components/GridSpan/GridSpanColumns.tsx +38 -0
- package/templates/blocks/components/HStack/HStackBasic.doc.mjs +14 -0
- package/templates/blocks/components/HStack/HStackBasic.tsx +16 -0
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx +57 -0
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
- package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
- package/templates/blocks/components/InputGroup/InputGroupBasic.doc.mjs +14 -0
- package/templates/blocks/components/InputGroup/InputGroupBasic.tsx +27 -0
- package/templates/blocks/components/LayoutContent/LayoutContentBasic.doc.mjs +23 -0
- package/templates/blocks/components/LayoutContent/LayoutContentBasic.tsx +40 -0
- package/templates/blocks/components/LayoutFooter/LayoutFooterActions.doc.mjs +22 -0
- package/templates/blocks/components/LayoutFooter/LayoutFooterActions.tsx +41 -0
- package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.doc.mjs +23 -0
- package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.tsx +40 -0
- package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.doc.mjs +22 -0
- package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.tsx +37 -0
- package/templates/blocks/components/Lightbox/LightboxGallery.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxGallery.tsx +53 -0
- package/templates/blocks/components/Lightbox/LightboxShowcase.tsx +4 -3
- package/templates/blocks/components/Lightbox/LightboxVideo.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxVideo.tsx +27 -0
- package/templates/blocks/components/Lightbox/LightboxZoom.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxZoom.tsx +33 -0
- package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +1 -1
- package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.tsx +19 -0
- package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.doc.mjs +23 -0
- package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.tsx +42 -0
- package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +0 -6
- package/templates/blocks/components/NavIcon/NavIconBasic.doc.mjs +14 -0
- package/templates/blocks/components/NavIcon/NavIconBasic.tsx +16 -0
- package/templates/blocks/components/RadioListItem/RadioListItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/RadioListItem/RadioListItemBasic.tsx +30 -0
- package/templates/blocks/components/Resizable/ResizableSidebar.doc.mjs +25 -0
- package/templates/blocks/components/Resizable/ResizableSidebar.tsx +67 -0
- package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.tsx +21 -0
- package/templates/blocks/components/SelectorOption/SelectorOptionBasic.doc.mjs +14 -0
- package/templates/blocks/components/SelectorOption/SelectorOptionBasic.tsx +39 -0
- package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.tsx +55 -0
- package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.tsx +38 -0
- package/templates/blocks/components/SideNavItem/SideNavItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavItem/SideNavItemBasic.tsx +49 -0
- package/templates/blocks/components/SideNavSection/SideNavSectionBasic.doc.mjs +14 -0
- package/templates/blocks/components/SideNavSection/SideNavSectionBasic.tsx +71 -0
- package/templates/blocks/components/StackItem/StackItemFill.doc.mjs +14 -0
- package/templates/blocks/components/StackItem/StackItemFill.tsx +28 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
- package/templates/blocks/components/TabMenu/TabMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/TabMenu/TabMenuBasic.tsx +23 -0
- package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +1 -1
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.doc.mjs +14 -0
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.tsx +47 -0
- package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.tsx +22 -0
- package/templates/blocks/components/TopNavItem/TopNavItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavItem/TopNavItemBasic.tsx +21 -0
- package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.doc.mjs +20 -0
- package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.tsx +46 -0
- package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.tsx +16 -0
- package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.tsx +26 -0
- package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.doc.mjs +14 -0
- package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.tsx +43 -0
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.doc.mjs +14 -0
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.tsx +43 -0
- package/templates/blocks/components/VStack/VStackBasic.doc.mjs +14 -0
- package/templates/blocks/components/VStack/VStackBasic.tsx +20 -0
- package/templates/pages/kanban-board/page.tsx +729 -0
- package/templates/pages/kanban-board/template.doc.mjs +12 -0
- package/templates/pages/shell-nav/page.tsx +321 -0
- package/templates/pages/shell-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-side-nav/page.tsx +241 -0
- package/templates/pages/shell-side-nav/template.doc.mjs +12 -0
- package/templates/pages/shell-top-nav/page.tsx +224 -0
- package/templates/pages/shell-top-nav/template.doc.mjs +12 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +0 -116
- package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +0 -230
- package/src/commands/gap-report.mjs +0 -464
- package/src/commands/gap-report.test.mjs +0 -168
- package/src/commands/swizzle-gap-safety.test.mjs +0 -273
- package/src/lib/config.mjs +0 -113
- package/src/lib/config.test.mjs +0 -91
- package/src/types/gap-report.d.ts +0 -29
- package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.doc.mjs +0 -14
- package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.tsx +0 -64
- package/templates/blocks/components/TreeListItem/TreeListItemShowcase.doc.mjs +0 -14
- package/templates/blocks/components/TreeListItem/TreeListItemShowcase.tsx +0 -60
- /package/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Invoke the transform directly with a configured jscodeshift api, mirroring
|
|
9
|
+
* how other transform tests (and `runConfigCodemod`) drive transforms via the
|
|
10
|
+
* unified `(file, api) => string | null | undefined` contract.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} source
|
|
13
|
+
* @param {string} [filePath]
|
|
14
|
+
* @returns {Promise<string|null>} the rewritten source, or null for no-op
|
|
15
|
+
*/
|
|
16
|
+
async function applyTransform(source, filePath = 'astryx.config.mjs') {
|
|
17
|
+
const {default: transform} = await import(
|
|
18
|
+
'../migrate-layout-components-to-experimental.mjs'
|
|
19
|
+
);
|
|
20
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
21
|
+
const j = jscodeshift.withParser('babel');
|
|
22
|
+
const api = {jscodeshift: j, stats: () => {}, report: () => {}};
|
|
23
|
+
return transform({source, path: filePath}, api);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('migrate-layout-components-to-experimental — meta', () => {
|
|
27
|
+
it('declares itself as a config codemod', async () => {
|
|
28
|
+
const {meta} = await import(
|
|
29
|
+
'../migrate-layout-components-to-experimental.mjs'
|
|
30
|
+
);
|
|
31
|
+
expect(meta.codemodType).toBe('config');
|
|
32
|
+
expect(meta.title).toMatch(/layout\.components/);
|
|
33
|
+
expect(typeof meta.description).toBe('string');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('migrate-layout-components-to-experimental — string → {from}', () => {
|
|
38
|
+
it('normalizes a string entry to { from } (named import semantics)', async () => {
|
|
39
|
+
const input = `export default {
|
|
40
|
+
layout: {
|
|
41
|
+
components: {
|
|
42
|
+
KpiCard: '@/components/KpiCard',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
`;
|
|
47
|
+
const output = await applyTransform(input);
|
|
48
|
+
expect(output).not.toBeNull();
|
|
49
|
+
// No longer under layout.
|
|
50
|
+
expect(output).not.toMatch(/layout\s*:/);
|
|
51
|
+
// Relocated under experimental.xle.components as { from }.
|
|
52
|
+
expect(output).toContain('experimental');
|
|
53
|
+
expect(output).toContain('xle');
|
|
54
|
+
expect(output).toMatch(/KpiCard:\s*\{\s*from:\s*['"]@\/components\/KpiCard['"]/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('normalizes multiple string entries', async () => {
|
|
58
|
+
const input = `export default {
|
|
59
|
+
layout: {
|
|
60
|
+
components: {
|
|
61
|
+
KpiCard: '@/components/KpiCard',
|
|
62
|
+
Banner: '@/components/Banner',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
`;
|
|
67
|
+
const output = await applyTransform(input);
|
|
68
|
+
expect(output).toMatch(/KpiCard:\s*\{\s*from:\s*['"]@\/components\/KpiCard['"]/);
|
|
69
|
+
expect(output).toMatch(/Banner:\s*\{\s*from:\s*['"]@\/components\/Banner['"]/);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('migrate-layout-components-to-experimental — object entries carried unchanged', () => {
|
|
74
|
+
it('preserves default: true (default import semantics)', async () => {
|
|
75
|
+
const input = `export default {
|
|
76
|
+
layout: {
|
|
77
|
+
components: {
|
|
78
|
+
Chart: { from: '@/c/Chart', default: true },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
`;
|
|
83
|
+
const output = await applyTransform(input);
|
|
84
|
+
expect(output).toContain("from: '@/c/Chart'");
|
|
85
|
+
expect(output).toContain('default: true');
|
|
86
|
+
expect(output).toContain('experimental');
|
|
87
|
+
expect(output).toContain('xle');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('preserves description', async () => {
|
|
91
|
+
const input = `export default {
|
|
92
|
+
layout: {
|
|
93
|
+
components: {
|
|
94
|
+
Chart: { from: '@/c/Chart', description: 'A chart' },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
`;
|
|
99
|
+
const output = await applyTransform(input);
|
|
100
|
+
expect(output).toContain("from: '@/c/Chart'");
|
|
101
|
+
expect(output).toContain("description: 'A chart'");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('preserves from/description/default and key order', async () => {
|
|
105
|
+
const input = `export default {
|
|
106
|
+
layout: {
|
|
107
|
+
components: {
|
|
108
|
+
Chart: { from: '@/c/Chart', description: 'A chart', default: true },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
`;
|
|
113
|
+
const output = await applyTransform(input);
|
|
114
|
+
// Order: from, then description, then default.
|
|
115
|
+
expect(output).toMatch(
|
|
116
|
+
/from:\s*'@\/c\/Chart',\s*description:\s*'A chart',\s*default:\s*true/,
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('migrates a mix of string and object entries', async () => {
|
|
121
|
+
const input = `export default {
|
|
122
|
+
layout: {
|
|
123
|
+
components: {
|
|
124
|
+
KpiCard: '@/components/KpiCard',
|
|
125
|
+
Chart: { from: '@/c/Chart', default: true },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
`;
|
|
130
|
+
const output = await applyTransform(input);
|
|
131
|
+
expect(output).toMatch(/KpiCard:\s*\{\s*from:\s*['"]@\/components\/KpiCard['"]/);
|
|
132
|
+
expect(output).toContain("from: '@/c/Chart'");
|
|
133
|
+
expect(output).toContain('default: true');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('migrate-layout-components-to-experimental — wrapper forms', () => {
|
|
138
|
+
it('handles bare object literal default export', async () => {
|
|
139
|
+
const input = `export default {
|
|
140
|
+
layout: { components: { KpiCard: '@/components/KpiCard' } },
|
|
141
|
+
};
|
|
142
|
+
`;
|
|
143
|
+
const output = await applyTransform(input);
|
|
144
|
+
expect(output).toContain('experimental');
|
|
145
|
+
expect(output).toContain('xle');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('handles createConfig({ ... }) wrapper', async () => {
|
|
149
|
+
const input = `import { createConfig } from '@astryxdesign/cli';
|
|
150
|
+
|
|
151
|
+
export default createConfig({
|
|
152
|
+
layout: { components: { KpiCard: '@/components/KpiCard' } },
|
|
153
|
+
});
|
|
154
|
+
`;
|
|
155
|
+
const output = await applyTransform(input);
|
|
156
|
+
expect(output).not.toBeNull();
|
|
157
|
+
expect(output).toContain('createConfig(');
|
|
158
|
+
expect(output).toContain('experimental');
|
|
159
|
+
expect(output).toContain('xle');
|
|
160
|
+
expect(output).toMatch(/KpiCard:\s*\{\s*from:\s*['"]@\/components\/KpiCard['"]/);
|
|
161
|
+
expect(output).not.toMatch(/layout\s*:/);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('migrate-layout-components-to-experimental — placement', () => {
|
|
166
|
+
it('adds xle to a pre-existing experimental (no xle)', async () => {
|
|
167
|
+
const input = `export default {
|
|
168
|
+
experimental: { foo: true },
|
|
169
|
+
layout: { components: { KpiCard: '@/components/KpiCard' } },
|
|
170
|
+
};
|
|
171
|
+
`;
|
|
172
|
+
const output = await applyTransform(input);
|
|
173
|
+
expect(output).toContain('foo: true');
|
|
174
|
+
expect(output).toContain('xle');
|
|
175
|
+
expect(output).toMatch(/KpiCard:\s*\{\s*from:/);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('migrate-layout-components-to-experimental — no-op cases', () => {
|
|
180
|
+
it('returns null when there is no layout', async () => {
|
|
181
|
+
const input = `export default { theme: 'dark' };\n`;
|
|
182
|
+
const output = await applyTransform(input);
|
|
183
|
+
expect(output).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('returns null when layout has no components', async () => {
|
|
187
|
+
const input = `export default { layout: { foo: 1 } };\n`;
|
|
188
|
+
// layout exists but has no components → no-op (return null) BEFORE the
|
|
189
|
+
// "extra key" check, because there is nothing to migrate.
|
|
190
|
+
const output = await applyTransform(input);
|
|
191
|
+
expect(output).toBeNull();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('returns null on an already-migrated config (idempotence-ish)', async () => {
|
|
195
|
+
const input = `export default {
|
|
196
|
+
experimental: { xle: { components: { KpiCard: { from: '@/components/KpiCard' } } } },
|
|
197
|
+
};
|
|
198
|
+
`;
|
|
199
|
+
const output = await applyTransform(input);
|
|
200
|
+
expect(output).toBeNull();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('migrate-layout-components-to-experimental — bail (throw) cases', () => {
|
|
205
|
+
it('throws when experimental.xle already exists', async () => {
|
|
206
|
+
const input = `export default {
|
|
207
|
+
experimental: { xle: { components: {} } },
|
|
208
|
+
layout: { components: { KpiCard: '@/components/KpiCard' } },
|
|
209
|
+
};
|
|
210
|
+
`;
|
|
211
|
+
await expect(applyTransform(input)).rejects.toThrow(/experimental\.xle/);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('throws when layout has an extra key besides components', async () => {
|
|
215
|
+
const input = `export default {
|
|
216
|
+
layout: {
|
|
217
|
+
components: { KpiCard: '@/components/KpiCard' },
|
|
218
|
+
spacing: 8,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
`;
|
|
222
|
+
await expect(applyTransform(input)).rejects.toThrow(/other than/);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('throws when an entry value is an identifier', async () => {
|
|
226
|
+
const input = `const Kpi = '@/components/KpiCard';
|
|
227
|
+
export default {
|
|
228
|
+
layout: { components: { KpiCard: Kpi } },
|
|
229
|
+
};
|
|
230
|
+
`;
|
|
231
|
+
await expect(applyTransform(input)).rejects.toThrow(/cannot be safely migrated/);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('throws when an entry value is a call expression', async () => {
|
|
235
|
+
const input = `export default {
|
|
236
|
+
layout: { components: { KpiCard: resolve('@/components/KpiCard') } },
|
|
237
|
+
};
|
|
238
|
+
`;
|
|
239
|
+
await expect(applyTransform(input)).rejects.toThrow(/cannot be safely migrated/);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('throws on a non-object default export (variable reference)', async () => {
|
|
243
|
+
const input = `const config = { layout: { components: {} } };
|
|
244
|
+
export default config;
|
|
245
|
+
`;
|
|
246
|
+
await expect(applyTransform(input)).rejects.toThrow(/statically analyze/);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('migrate-layout-components-to-experimental — end-to-end via runConfigCodemod', () => {
|
|
251
|
+
it('rewrites astryx.config.mjs on disk through the unified runner', async () => {
|
|
252
|
+
const {runConfigCodemod} = await import('../../../run-codemod.mjs');
|
|
253
|
+
const {default: transform, meta} = await import(
|
|
254
|
+
'../migrate-layout-components-to-experimental.mjs'
|
|
255
|
+
);
|
|
256
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
257
|
+
|
|
258
|
+
const originalCwd = process.cwd();
|
|
259
|
+
const tmpDir = fs.mkdtempSync(
|
|
260
|
+
path.join(process.cwd(), '.astryx-v013-codemod-test-'),
|
|
261
|
+
);
|
|
262
|
+
try {
|
|
263
|
+
process.chdir(tmpDir);
|
|
264
|
+
fs.writeFileSync(
|
|
265
|
+
path.join(tmpDir, 'package.json'),
|
|
266
|
+
JSON.stringify({name: 'consumer'}),
|
|
267
|
+
);
|
|
268
|
+
fs.writeFileSync(
|
|
269
|
+
path.join(tmpDir, 'astryx.config.mjs'),
|
|
270
|
+
`export default {
|
|
271
|
+
layout: { components: { KpiCard: '@/components/KpiCard' } },
|
|
272
|
+
};
|
|
273
|
+
`,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const entry = {
|
|
277
|
+
id: 'migrate-layout-components-to-experimental',
|
|
278
|
+
type: 'config',
|
|
279
|
+
codemod: {title: meta.title, transform},
|
|
280
|
+
package: 'core',
|
|
281
|
+
version: '0.1.3',
|
|
282
|
+
};
|
|
283
|
+
const log = {
|
|
284
|
+
step() {},
|
|
285
|
+
info() {},
|
|
286
|
+
success() {},
|
|
287
|
+
warn() {},
|
|
288
|
+
error() {},
|
|
289
|
+
message() {},
|
|
290
|
+
};
|
|
291
|
+
const result = runConfigCodemod(entry, {apply: true, log, jscodeshift});
|
|
292
|
+
|
|
293
|
+
expect(result.errors).toHaveLength(0);
|
|
294
|
+
expect(result.filesChanged).toBe(1);
|
|
295
|
+
const rewritten = fs.readFileSync(
|
|
296
|
+
path.join(tmpDir, 'astryx.config.mjs'),
|
|
297
|
+
'utf-8',
|
|
298
|
+
);
|
|
299
|
+
expect(rewritten).toContain('experimental');
|
|
300
|
+
expect(rewritten).toContain('xle');
|
|
301
|
+
expect(rewritten).toMatch(/KpiCard:\s*\{\s*from:/);
|
|
302
|
+
expect(rewritten).not.toMatch(/layout\s*:/);
|
|
303
|
+
} finally {
|
|
304
|
+
process.chdir(originalCwd);
|
|
305
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('surfaces a thrown bail as a structured error through runConfigCodemod', async () => {
|
|
310
|
+
const {runConfigCodemod} = await import('../../../run-codemod.mjs');
|
|
311
|
+
const {default: transform, meta} = await import(
|
|
312
|
+
'../migrate-layout-components-to-experimental.mjs'
|
|
313
|
+
);
|
|
314
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
315
|
+
|
|
316
|
+
const originalCwd = process.cwd();
|
|
317
|
+
const tmpDir = fs.mkdtempSync(
|
|
318
|
+
path.join(process.cwd(), '.astryx-v013-codemod-test-'),
|
|
319
|
+
);
|
|
320
|
+
try {
|
|
321
|
+
process.chdir(tmpDir);
|
|
322
|
+
fs.writeFileSync(
|
|
323
|
+
path.join(tmpDir, 'package.json'),
|
|
324
|
+
JSON.stringify({name: 'consumer'}),
|
|
325
|
+
);
|
|
326
|
+
fs.writeFileSync(
|
|
327
|
+
path.join(tmpDir, 'astryx.config.mjs'),
|
|
328
|
+
`export default {
|
|
329
|
+
experimental: { xle: { components: {} } },
|
|
330
|
+
layout: { components: { KpiCard: '@/components/KpiCard' } },
|
|
331
|
+
};
|
|
332
|
+
`,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const entry = {
|
|
336
|
+
id: 'migrate-layout-components-to-experimental',
|
|
337
|
+
type: 'config',
|
|
338
|
+
codemod: {title: meta.title, transform},
|
|
339
|
+
package: 'core',
|
|
340
|
+
version: '0.1.3',
|
|
341
|
+
};
|
|
342
|
+
const log = {
|
|
343
|
+
step() {},
|
|
344
|
+
info() {},
|
|
345
|
+
success() {},
|
|
346
|
+
warn() {},
|
|
347
|
+
error() {},
|
|
348
|
+
message() {},
|
|
349
|
+
};
|
|
350
|
+
const result = runConfigCodemod(entry, {apply: true, log, jscodeshift});
|
|
351
|
+
|
|
352
|
+
expect(result.filesChanged).toBe(0);
|
|
353
|
+
expect(result.errors).toHaveLength(1);
|
|
354
|
+
expect(result.errors[0].error).toMatch(/experimental\.xle/);
|
|
355
|
+
} finally {
|
|
356
|
+
process.chdir(originalCwd);
|
|
357
|
+
fs.rmSync(tmpDir, {recursive: true, force: true});
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file v0.1.3 transform manifest
|
|
5
|
+
*
|
|
6
|
+
* Lists all codemods for the v0.1.3 release in the order they should run.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import migrateLayoutComponentsToExperimental, {
|
|
10
|
+
meta as migrateLayoutComponentsToExperimentalMeta,
|
|
11
|
+
} from './migrate-layout-components-to-experimental.mjs';
|
|
12
|
+
|
|
13
|
+
export default [
|
|
14
|
+
{
|
|
15
|
+
name: 'migrate-layout-components-to-experimental',
|
|
16
|
+
transform: migrateLayoutComponentsToExperimental,
|
|
17
|
+
meta: migrateLayoutComponentsToExperimentalMeta,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Codemod: migrate astryx.config `layout.components` to
|
|
5
|
+
* `experimental.xle.components`.
|
|
6
|
+
*
|
|
7
|
+
* The published 0.1.2 CLI read XLE app-component registration from
|
|
8
|
+
* `astryx.config.*` under `layout.components`. The next release relocates this
|
|
9
|
+
* to `experimental.xle.components`, validated by a strict schema that REJECTS
|
|
10
|
+
* unknown keys — so any consumer that still has `layout.components` would
|
|
11
|
+
* hard-error on upgrade. This codemod performs the straight relocation.
|
|
12
|
+
*
|
|
13
|
+
* The semantics are IDENTICAL between the old and new shapes; this is purely a
|
|
14
|
+
* MOVE plus a string→object normalization. No meaning is changed:
|
|
15
|
+
*
|
|
16
|
+
* OLD `layout.components` entry forms (from the 0.1.2 shim):
|
|
17
|
+
* - string `'X'` → a NAMED import; the KEY is both the local name and the
|
|
18
|
+
* export name.
|
|
19
|
+
* - object `{ from, description?, default? }` → `default: true` means a
|
|
20
|
+
* default import; otherwise a named import (key = export name).
|
|
21
|
+
*
|
|
22
|
+
* NEW `experimental.xle.components` entry shape (already shipped on the
|
|
23
|
+
* branch; unchanged here): `{ from, description?, default? }`, with the same
|
|
24
|
+
* semantics.
|
|
25
|
+
*
|
|
26
|
+
* So the migration is:
|
|
27
|
+
* - string `'X'` → `{ from: 'X' }`
|
|
28
|
+
* - object `{ from, description?, default? }` → carried over UNCHANGED
|
|
29
|
+
* (from/description/default preserved, key order preserved).
|
|
30
|
+
*
|
|
31
|
+
* The transform recognizes the config default export as EITHER a bare object
|
|
32
|
+
* literal (`export default { ... }`) or a `createConfig({ ... })` call wrapping
|
|
33
|
+
* an object literal (`export default createConfig({ ... })`). Anything else is
|
|
34
|
+
* not statically analyzable and the transform THROWS with a clear
|
|
35
|
+
* migrate-manually message (the unified config-codemod contract has no
|
|
36
|
+
* structured-error return — `runConfigCodemod` surfaces a thrown `err.message`
|
|
37
|
+
* as `{file, codemod, error}`).
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export const meta = {
|
|
41
|
+
title: 'Migrate astryx.config layout.components to experimental.xle.components',
|
|
42
|
+
description:
|
|
43
|
+
'Relocates XLE app-component registration from the legacy ' +
|
|
44
|
+
'`layout.components` config key to `experimental.xle.components`. String ' +
|
|
45
|
+
'entries (`Name: "@/path"`) are normalized to `{ from: "@/path" }` ' +
|
|
46
|
+
'(named import, key = export name); object entries are carried over ' +
|
|
47
|
+
'unchanged (from/description/default preserved). Bails with a clear ' +
|
|
48
|
+
'migrate-manually error when the config cannot be statically analyzed or ' +
|
|
49
|
+
'when merging would be unsafe.',
|
|
50
|
+
codemodType: 'config',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const MANUAL =
|
|
54
|
+
'migrate layout.components to experimental.xle.components manually';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {{source: string, path: string}} file
|
|
58
|
+
* @param {{jscodeshift: Function}} api
|
|
59
|
+
* @returns {string|null}
|
|
60
|
+
*/
|
|
61
|
+
export default function transformer(file, api) {
|
|
62
|
+
const j = api.jscodeshift;
|
|
63
|
+
const root = j(file.source);
|
|
64
|
+
|
|
65
|
+
const defaultExports = root.find(j.ExportDefaultDeclaration);
|
|
66
|
+
if (defaultExports.size() === 0) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`could not statically analyze astryx config (no default export); ${MANUAL}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const declaration = defaultExports.paths()[0].node.declaration;
|
|
73
|
+
|
|
74
|
+
// Resolve the config object literal from either form:
|
|
75
|
+
// export default { ... }
|
|
76
|
+
// export default createConfig({ ... })
|
|
77
|
+
const configObject = resolveConfigObject(j, declaration);
|
|
78
|
+
if (!configObject) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`could not statically analyze astryx config; ${MANUAL}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Locate the top-level `layout` property and its `components` value.
|
|
85
|
+
const layoutProp = findObjectProperty(j, configObject, 'layout');
|
|
86
|
+
if (!layoutProp) {
|
|
87
|
+
return null; // no layout → nothing to migrate
|
|
88
|
+
}
|
|
89
|
+
if (!j.ObjectExpression.check(layoutProp.value)) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`astryx config \`layout\` is not an object literal; ${MANUAL}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const componentsProp = findObjectProperty(j, layoutProp.value, 'components');
|
|
96
|
+
if (!componentsProp) {
|
|
97
|
+
return null; // no layout.components → nothing to migrate
|
|
98
|
+
}
|
|
99
|
+
if (!j.ObjectExpression.check(componentsProp.value)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`astryx config \`layout.components\` is not an object literal; ${MANUAL}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// `components` was the only documented `layout` key. Anything else is
|
|
106
|
+
// unexpected and we cannot safely guess intent.
|
|
107
|
+
const otherLayoutKeys = layoutProp.value.properties.filter(
|
|
108
|
+
p => p !== componentsProp,
|
|
109
|
+
);
|
|
110
|
+
if (otherLayoutKeys.length > 0) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`astryx config \`layout\` has keys other than \`components\`; ${MANUAL}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Build the migrated `components` object literal entry by entry.
|
|
117
|
+
const migratedComponents = buildMigratedComponents(j, componentsProp.value);
|
|
118
|
+
|
|
119
|
+
// Placement into `experimental.xle.components`.
|
|
120
|
+
const experimentalProp = findObjectProperty(j, configObject, 'experimental');
|
|
121
|
+
if (experimentalProp) {
|
|
122
|
+
if (!j.ObjectExpression.check(experimentalProp.value)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`astryx config \`experimental\` is not an object literal; ${MANUAL}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const xleProp = findObjectProperty(j, experimentalProp.value, 'xle');
|
|
128
|
+
if (xleProp) {
|
|
129
|
+
// Can't safely merge into a pre-existing experimental.xle.
|
|
130
|
+
throw new Error(
|
|
131
|
+
`astryx config already defines \`experimental.xle\`; ${MANUAL}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
experimentalProp.value.properties.push(
|
|
135
|
+
j.objectProperty(j.identifier('xle'), buildXleObject(j, migratedComponents)),
|
|
136
|
+
);
|
|
137
|
+
} else {
|
|
138
|
+
configObject.properties.push(
|
|
139
|
+
j.objectProperty(
|
|
140
|
+
j.identifier('experimental'),
|
|
141
|
+
j.objectExpression([
|
|
142
|
+
j.objectProperty(j.identifier('xle'), buildXleObject(j, migratedComponents)),
|
|
143
|
+
]),
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Remove the migrated data from `layout`. Since `components` was the only
|
|
149
|
+
// key (asserted above), remove the entire `layout` property.
|
|
150
|
+
configObject.properties = configObject.properties.filter(
|
|
151
|
+
p => p !== layoutProp,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return root.toSource();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Resolve the wrapped config object literal from a default-export declaration.
|
|
159
|
+
* Handles `{ ... }` and `createConfig({ ... })`.
|
|
160
|
+
* @returns {object|null} the ObjectExpression node, or null if not analyzable
|
|
161
|
+
*/
|
|
162
|
+
function resolveConfigObject(j, declaration) {
|
|
163
|
+
if (!declaration) return null;
|
|
164
|
+
if (j.ObjectExpression.check(declaration)) {
|
|
165
|
+
return declaration;
|
|
166
|
+
}
|
|
167
|
+
if (
|
|
168
|
+
j.CallExpression.check(declaration) &&
|
|
169
|
+
declaration.arguments.length >= 1 &&
|
|
170
|
+
j.ObjectExpression.check(declaration.arguments[0])
|
|
171
|
+
) {
|
|
172
|
+
return declaration.arguments[0];
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Find a (non-computed, non-spread) property on an object literal by key name.
|
|
179
|
+
* Supports Identifier and string-literal keys.
|
|
180
|
+
* @returns {object|undefined} the property node
|
|
181
|
+
*/
|
|
182
|
+
function findObjectProperty(j, objectExpression, name) {
|
|
183
|
+
return objectExpression.properties.find(prop => {
|
|
184
|
+
if (
|
|
185
|
+
!j.ObjectProperty.check(prop) &&
|
|
186
|
+
!j.Property.check(prop)
|
|
187
|
+
) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
if (prop.computed) return false;
|
|
191
|
+
const key = prop.key;
|
|
192
|
+
if (j.Identifier.check(key)) return key.name === name;
|
|
193
|
+
if (j.StringLiteral.check(key)) return key.value === name;
|
|
194
|
+
if (j.Literal.check(key)) return key.value === name;
|
|
195
|
+
return false;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Build the migrated `components` ObjectExpression from the old one.
|
|
201
|
+
* @returns {object} ObjectExpression
|
|
202
|
+
*/
|
|
203
|
+
function buildMigratedComponents(j, oldComponents) {
|
|
204
|
+
const newProps = oldComponents.properties.map(prop => {
|
|
205
|
+
if (
|
|
206
|
+
(!j.ObjectProperty.check(prop) && !j.Property.check(prop)) ||
|
|
207
|
+
prop.computed
|
|
208
|
+
) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`astryx config \`layout.components\` has a non-literal entry; ${MANUAL}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const value = prop.value;
|
|
215
|
+
|
|
216
|
+
// string `'X'` → `{ from: 'X' }`
|
|
217
|
+
if (j.StringLiteral.check(value) || (j.Literal.check(value) && typeof value.value === 'string')) {
|
|
218
|
+
return j.objectProperty(
|
|
219
|
+
cloneKey(j, prop.key),
|
|
220
|
+
j.objectExpression([
|
|
221
|
+
j.objectProperty(j.identifier('from'), cloneStringLiteral(j, value)),
|
|
222
|
+
]),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// object `{ from, description?, default? }` → carried over UNCHANGED
|
|
227
|
+
if (j.ObjectExpression.check(value)) {
|
|
228
|
+
return j.objectProperty(cloneKey(j, prop.key), value);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// anything else (identifier, call, ternary, etc.) → cannot safely migrate
|
|
232
|
+
throw new Error(
|
|
233
|
+
`astryx config \`layout.components\` entry is neither a string nor an ` +
|
|
234
|
+
`object literal and cannot be safely migrated; ${MANUAL}`,
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return j.objectExpression(newProps);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Build `{ components: <obj> }` for `experimental.xle`. */
|
|
242
|
+
function buildXleObject(j, migratedComponents) {
|
|
243
|
+
return j.objectExpression([
|
|
244
|
+
j.objectProperty(j.identifier('components'), migratedComponents),
|
|
245
|
+
]);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Clone a property key node so we don't reuse a node across the tree. */
|
|
249
|
+
function cloneKey(j, key) {
|
|
250
|
+
if (j.Identifier.check(key)) return j.identifier(key.name);
|
|
251
|
+
if (j.StringLiteral.check(key)) return j.stringLiteral(key.value);
|
|
252
|
+
if (j.Literal.check(key)) return j.literal(key.value);
|
|
253
|
+
return key;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Build a fresh string literal preserving the original raw quoting if possible. */
|
|
257
|
+
function cloneStringLiteral(j, value) {
|
|
258
|
+
if (j.StringLiteral.check(value)) return j.stringLiteral(value.value);
|
|
259
|
+
return j.literal(value.value);
|
|
260
|
+
}
|
|
@@ -180,6 +180,9 @@ export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPre
|
|
|
180
180
|
// Rules — the top error-preventers.
|
|
181
181
|
lines.push('RULES:');
|
|
182
182
|
lines.push('- No <div> — components do all layout/spacing. Full page → AppShell; sidebar nav → SideNav.');
|
|
183
|
+
lines.push('- Frame first: pick the shell (AppShell / Layout+LayoutPanel) and budget regions in px BEFORE writing content (`astryx docs layout`).');
|
|
184
|
+
lines.push('- Dense data = rows (Table, List/Item) edge-to-edge — never Card-wrapped list items. Card = dashboard widgets, galleries, settings groups only.');
|
|
185
|
+
lines.push('- Status → StatusDot/Token; Badge only for counts and enumerated states, never decoration.');
|
|
183
186
|
// Styling guidance tailored to the project's configured system — never
|
|
184
187
|
// recommend a path that isn't compiled here (xstyle needs the StyleX compiler;
|
|
185
188
|
// utilities need Tailwind). Tokens are always the source of truth.
|
|
@@ -207,7 +210,7 @@ export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPre
|
|
|
207
210
|
.sort();
|
|
208
211
|
if (topics.length > 0) lines.push(` docs <topic> ${topics.join(', ')}`);
|
|
209
212
|
}
|
|
210
|
-
lines.push(' swizzle <Name> eject component source
|
|
213
|
+
lines.push(' swizzle <Name> eject component source for deep customization');
|
|
211
214
|
lines.push(' upgrade --apply run after any @astryxdesign/core bump');
|
|
212
215
|
lines.push(MARKER_END);
|
|
213
216
|
|