@astryxdesign/cli 0.1.2-canary.2149fa3 → 0.1.2-canary.22ccb69
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 +0 -1
- package/README.md +49 -55
- package/package.json +19 -7
- package/src/api/component.mjs +253 -12
- package/src/api/discover.mjs +14 -7
- package/src/api/doctor.mjs +10 -25
- 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 +1 -1
- 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/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 +7 -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 +7 -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/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/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/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/DropdownMenu/DropdownMenuShowcase.tsx +0 -1
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +0 -1
- 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/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/MoreMenu/MoreMenuShowcase.tsx +0 -1
- package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
- package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.doc.mjs +14 -0
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.tsx +47 -0
- package/templates/pages/shell-side-nav/page.tsx +0 -1
- 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/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'Lightbox',
|
|
7
|
+
name: 'Lightbox — Gallery',
|
|
8
|
+
displayName: 'Lightbox — Gallery',
|
|
9
|
+
description:
|
|
10
|
+
'A thumbnail grid that opens a fullscreen gallery. Clicking any thumbnail opens the lightbox at that index. Prev/next navigation lets users browse all images without closing.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Lightbox', 'Grid', 'Thumbnail'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useLightbox} from '@astryxdesign/core/Lightbox';
|
|
6
|
+
import {Grid} from '@astryxdesign/core/Grid';
|
|
7
|
+
import {Thumbnail} from '@astryxdesign/core/Thumbnail';
|
|
8
|
+
|
|
9
|
+
const PHOTOS = [
|
|
10
|
+
{
|
|
11
|
+
src: 'https://lookaside.facebook.com/assets/astryx/Neutral-Backpack.png',
|
|
12
|
+
alt: 'Backpack',
|
|
13
|
+
caption: 'A backpack displayed on a neutral background.',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
src: 'https://lookaside.facebook.com/assets/astryx/building.png',
|
|
17
|
+
alt: 'Modern building',
|
|
18
|
+
caption: 'A modern building with a contemporary architectural design.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
src: 'https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png',
|
|
22
|
+
alt: 'Coastal shoreline with ocean waves',
|
|
23
|
+
caption:
|
|
24
|
+
'A scenic coastline with waves rolling onto a sandy beach beneath a clear sky.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
src: 'https://lookaside.facebook.com/assets/astryx/illustrative-vertical-1.png',
|
|
28
|
+
alt: 'Illustrated lakeside landscape at sunset',
|
|
29
|
+
caption:
|
|
30
|
+
'A stylized landscape illustration featuring pink clouds reflected over a calm lake at sunset.',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export default function LightboxGallery() {
|
|
35
|
+
const lightbox = useLightbox({media: PHOTOS});
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<Grid columns={2} gap={2} style={{width: 136}}>
|
|
40
|
+
{PHOTOS.map((photo, i) => (
|
|
41
|
+
<Thumbnail
|
|
42
|
+
key={photo.src}
|
|
43
|
+
src={photo.src}
|
|
44
|
+
alt={photo.alt}
|
|
45
|
+
label={photo.alt}
|
|
46
|
+
onClick={() => lightbox.open(i)}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
</Grid>
|
|
50
|
+
{lightbox.element}
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -12,9 +12,10 @@ export default function LightboxShowcase() {
|
|
|
12
12
|
isOpen={isOpen}
|
|
13
13
|
onOpenChange={setIsOpen}
|
|
14
14
|
media={{
|
|
15
|
-
src: 'https://
|
|
16
|
-
alt: '
|
|
17
|
-
caption:
|
|
15
|
+
src: 'https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png',
|
|
16
|
+
alt: 'Coastal shoreline with ocean waves',
|
|
17
|
+
caption:
|
|
18
|
+
'A scenic coastline with waves rolling onto a sandy beach beneath a clear sky.',
|
|
18
19
|
}}
|
|
19
20
|
/>
|
|
20
21
|
</>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'Lightbox',
|
|
7
|
+
name: 'Lightbox — Video',
|
|
8
|
+
displayName: 'Lightbox — Video',
|
|
9
|
+
description:
|
|
10
|
+
'Opens a video in the lightbox. Native browser controls are available. Zoom and pan are disabled for video items.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Lightbox'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {Lightbox} from '@astryxdesign/core/Lightbox';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
|
|
9
|
+
export default function LightboxVideo() {
|
|
10
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<Button label="Play video" onClick={() => setIsOpen(true)} />
|
|
15
|
+
<Lightbox
|
|
16
|
+
isOpen={isOpen}
|
|
17
|
+
onOpenChange={setIsOpen}
|
|
18
|
+
media={{
|
|
19
|
+
src: 'https://lookaside.facebook.com/assets/?set=astryx&name=Nature-1&density=1',
|
|
20
|
+
alt: 'Flower blooming in time-lapse',
|
|
21
|
+
type: 'video',
|
|
22
|
+
caption: 'A flower blooming in time-lapse',
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'Lightbox',
|
|
7
|
+
name: 'Lightbox — Zoom',
|
|
8
|
+
displayName: 'Lightbox — Zoom',
|
|
9
|
+
description:
|
|
10
|
+
'A lightbox with zoom and pan enabled. Double-click the image to zoom in; drag to pan around. Double-click again or use the close button to exit.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Lightbox', 'Thumbnail'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {Lightbox} from '@astryxdesign/core/Lightbox';
|
|
7
|
+
import {Thumbnail} from '@astryxdesign/core/Thumbnail';
|
|
8
|
+
|
|
9
|
+
export default function LightboxZoom() {
|
|
10
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<Thumbnail
|
|
15
|
+
src="https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png"
|
|
16
|
+
alt="Coastal shoreline with ocean waves"
|
|
17
|
+
label="Coastal shoreline with ocean waves"
|
|
18
|
+
onClick={() => setIsOpen(true)}
|
|
19
|
+
/>
|
|
20
|
+
<Lightbox
|
|
21
|
+
isOpen={isOpen}
|
|
22
|
+
onOpenChange={setIsOpen}
|
|
23
|
+
media={{
|
|
24
|
+
src: 'https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png',
|
|
25
|
+
alt: 'Coastal shoreline with ocean waves',
|
|
26
|
+
caption:
|
|
27
|
+
'A scenic coastline. Double-click to zoom in and drag to pan.',
|
|
28
|
+
}}
|
|
29
|
+
hasZoom
|
|
30
|
+
/>
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'Tab',
|
|
7
|
+
name: 'Tab — Selected Icon',
|
|
8
|
+
displayName: 'Tab — Selected Icon',
|
|
9
|
+
description: 'A tab that changes its icon when selected.',
|
|
10
|
+
isReady: true,
|
|
11
|
+
aspectRatio: 4 / 3,
|
|
12
|
+
componentsUsed: ['TabList', 'Tab'],
|
|
13
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {TabList, Tab} from '@astryxdesign/core/TabList';
|
|
7
|
+
|
|
8
|
+
const StarIcon = (
|
|
9
|
+
<svg
|
|
10
|
+
viewBox="0 0 16 16"
|
|
11
|
+
fill="none"
|
|
12
|
+
stroke="currentColor"
|
|
13
|
+
width="100%"
|
|
14
|
+
height="100%">
|
|
15
|
+
<path d="m8 1.5 2 4.1 4.5.7-3.3 3.2.8 4.5-4-2.1L4 14l.8-4.5-3.3-3.2 4.5-.7L8 1.5Z" />
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const SelectedStarIcon = (
|
|
20
|
+
<svg viewBox="0 0 16 16" fill="currentColor" width="100%" height="100%">
|
|
21
|
+
<path d="m8 1.5 2 4.1 4.5.7-3.3 3.2.8 4.5-4-2.1L4 14l.8-4.5-3.3-3.2 4.5-.7L8 1.5Z" />
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export default function TabWithSelectedIcon() {
|
|
26
|
+
const [value, setValue] = useState('favorites');
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<TabList value={value} onChange={setValue}>
|
|
30
|
+
<Tab
|
|
31
|
+
value="favorites"
|
|
32
|
+
label="Favorites"
|
|
33
|
+
icon={StarIcon}
|
|
34
|
+
selectedIcon={SelectedStarIcon}
|
|
35
|
+
/>
|
|
36
|
+
<Tab value="recent" label="Recent" />
|
|
37
|
+
</TabList>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'ToggleButtonGroup',
|
|
7
|
+
name: 'ToggleButtonGroup — Vertical',
|
|
8
|
+
displayName: 'ToggleButtonGroup — Vertical',
|
|
9
|
+
description:
|
|
10
|
+
'A vertically stacked ToggleButtonGroup using the vertical orientation, shown with both single-select and multi-select behavior — ideal for sidebar-style option lists and vertical toolbars.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: ['ToggleButton', 'ToggleButtonGroup', 'Layout', 'Text'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {ToggleButton, ToggleButtonGroup} from '@astryxdesign/core/ToggleButton';
|
|
7
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
8
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
9
|
+
|
|
10
|
+
export default function ToggleButtonGroupVertical() {
|
|
11
|
+
const [view, setView] = useState<string | null>('grid');
|
|
12
|
+
const [filters, setFilters] = useState<string[]>(['active']);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<VStack gap={4}>
|
|
16
|
+
<VStack gap={1}>
|
|
17
|
+
<Text type="label" color="secondary">
|
|
18
|
+
Single select
|
|
19
|
+
</Text>
|
|
20
|
+
<ToggleButtonGroup
|
|
21
|
+
orientation="vertical"
|
|
22
|
+
value={view}
|
|
23
|
+
onChange={setView}
|
|
24
|
+
label="View mode">
|
|
25
|
+
<ToggleButton value="list" label="List" />
|
|
26
|
+
<ToggleButton value="grid" label="Grid" />
|
|
27
|
+
<ToggleButton value="board" label="Board" />
|
|
28
|
+
</ToggleButtonGroup>
|
|
29
|
+
</VStack>
|
|
30
|
+
<VStack gap={1}>
|
|
31
|
+
<Text type="label" color="secondary">
|
|
32
|
+
Multi select
|
|
33
|
+
</Text>
|
|
34
|
+
<ToggleButtonGroup
|
|
35
|
+
orientation="vertical"
|
|
36
|
+
type="multiple"
|
|
37
|
+
value={filters}
|
|
38
|
+
onChange={setFilters}
|
|
39
|
+
label="Status filters">
|
|
40
|
+
<ToggleButton value="active" label="Active" />
|
|
41
|
+
<ToggleButton value="pending" label="Pending" />
|
|
42
|
+
<ToggleButton value="closed" label="Closed" />
|
|
43
|
+
</ToggleButtonGroup>
|
|
44
|
+
</VStack>
|
|
45
|
+
</VStack>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
import {describe, it, expect} from 'vitest';
|
|
4
|
-
|
|
5
|
-
async function applyConfigCodemod(files) {
|
|
6
|
-
const {default: transform} =
|
|
7
|
-
await import('../migrate-xds-config-surfaces.mjs');
|
|
8
|
-
const config = {
|
|
9
|
-
packageJson: files['package.json']
|
|
10
|
-
? {path: 'package.json', source: files['package.json']}
|
|
11
|
-
: null,
|
|
12
|
-
astryxConfig: files['astryx.config.mjs']
|
|
13
|
-
? {path: 'astryx.config.mjs', source: files['astryx.config.mjs']}
|
|
14
|
-
: null,
|
|
15
|
-
xdsConfig: files['xds.config.mjs']
|
|
16
|
-
? {path: 'xds.config.mjs', source: files['xds.config.mjs']}
|
|
17
|
-
: null,
|
|
18
|
-
};
|
|
19
|
-
return transform({path: process.cwd(), source: ''}, {config});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe('migrate-xds-config-surfaces', () => {
|
|
23
|
-
it('extracts package.json xds config into astryx.config.mjs and removes the package key in place', async () => {
|
|
24
|
-
const input = `{
|
|
25
|
-
"dependencies": {"@xds/core":"^0.0.15"},
|
|
26
|
-
"xds" : {"theme":"neutral","docs":"./src"}
|
|
27
|
-
}
|
|
28
|
-
`;
|
|
29
|
-
|
|
30
|
-
const result = await applyConfigCodemod({'package.json': input});
|
|
31
|
-
expect(result.errors ?? []).toEqual([]);
|
|
32
|
-
expect(result.changes).toEqual([
|
|
33
|
-
{
|
|
34
|
-
path: 'package.json',
|
|
35
|
-
source: `{
|
|
36
|
-
"dependencies": {"@xds/core":"^0.0.15"}
|
|
37
|
-
}
|
|
38
|
-
`,
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
path: 'astryx.config.mjs',
|
|
42
|
-
source: `export default {
|
|
43
|
-
"theme": "neutral",
|
|
44
|
-
"docs": "./src"
|
|
45
|
-
};
|
|
46
|
-
`,
|
|
47
|
-
},
|
|
48
|
-
]);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('extracts package.json astryx config into astryx.config.mjs too', async () => {
|
|
52
|
-
const result = await applyConfigCodemod({
|
|
53
|
-
'package.json': `{"astryx":{"theme":"neutral"}}\n`,
|
|
54
|
-
});
|
|
55
|
-
expect(result.changes).toEqual([
|
|
56
|
-
{path: 'package.json', source: `{}\n`},
|
|
57
|
-
{
|
|
58
|
-
path: 'astryx.config.mjs',
|
|
59
|
-
source: `export default {
|
|
60
|
-
"theme": "neutral"
|
|
61
|
-
};
|
|
62
|
-
`,
|
|
63
|
-
},
|
|
64
|
-
]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('bails when package.json contains both xds and astryx config keys', async () => {
|
|
68
|
-
const result = await applyConfigCodemod({
|
|
69
|
-
'package.json': `{"xds":{"theme":"default"},"astryx":{"theme":"neutral"}}\n`,
|
|
70
|
-
});
|
|
71
|
-
expect(result.errors?.[0]?.error).toMatch(/both xds and astryx/);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('does not rewrite dependency keys in package.json', async () => {
|
|
75
|
-
const input = `{"dependencies":{"@xds/core":"^0.0.15"}}\n`;
|
|
76
|
-
const result = await applyConfigCodemod({'package.json': input});
|
|
77
|
-
expect(result.changes).toEqual([]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('bails when package config and astryx.config.mjs both exist', async () => {
|
|
81
|
-
const result = await applyConfigCodemod({
|
|
82
|
-
'package.json': `{"xds":{"theme":"neutral"}}\n`,
|
|
83
|
-
'astryx.config.mjs': `export default {};\n`,
|
|
84
|
-
});
|
|
85
|
-
expect(result.errors?.[0]?.error).toMatch(/migrate the config manually/);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('bails when package config and xds.config.mjs both exist', async () => {
|
|
89
|
-
const result = await applyConfigCodemod({
|
|
90
|
-
'package.json': `{"xds":{"theme":"neutral"}}\n`,
|
|
91
|
-
'xds.config.mjs': `export default {};\n`,
|
|
92
|
-
});
|
|
93
|
-
expect(result.errors?.[0]?.error).toMatch(/migrate the config manually/);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('renames xds.config.mjs to astryx.config.mjs when no package config exists', async () => {
|
|
97
|
-
const result = await applyConfigCodemod({
|
|
98
|
-
'xds.config.mjs': "export default { theme: 'neutral' };\n",
|
|
99
|
-
});
|
|
100
|
-
expect(result.changes).toEqual([
|
|
101
|
-
{
|
|
102
|
-
path: 'astryx.config.mjs',
|
|
103
|
-
source: "export default { theme: 'neutral' };\n",
|
|
104
|
-
},
|
|
105
|
-
{path: 'xds.config.mjs', delete: true},
|
|
106
|
-
]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('bails when xds.config.mjs and astryx.config.mjs both exist', async () => {
|
|
110
|
-
const result = await applyConfigCodemod({
|
|
111
|
-
'xds.config.mjs': `export default {};\n`,
|
|
112
|
-
'astryx.config.mjs': `export default {};\n`,
|
|
113
|
-
});
|
|
114
|
-
expect(result.errors?.[0]?.error).toMatch(/migrate the config manually/);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file Codemod: migrate XDS config surfaces to Astryx v0.1.0
|
|
5
|
-
*
|
|
6
|
-
* The v0.1.0 CLI reads astryx.config.mjs. This config codemod migrates
|
|
7
|
-
* legacy xds.config.mjs and package.json's top-level `xds` config without
|
|
8
|
-
* changing dependency names or versions.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export const meta = {
|
|
12
|
-
title: 'Migrate xds config surfaces to astryx',
|
|
13
|
-
description:
|
|
14
|
-
'Moves legacy package.json xds config into astryx.config.mjs, or renames ' +
|
|
15
|
-
'xds.config.mjs when no package.json config exists. Bails out if both exist.',
|
|
16
|
-
pr: '#3092',
|
|
17
|
-
codemodType: 'config',
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
function findTopLevelKey(source, targetKey) {
|
|
21
|
-
let depth = 0;
|
|
22
|
-
let inString = false;
|
|
23
|
-
let escaped = false;
|
|
24
|
-
let stringStart = -1;
|
|
25
|
-
|
|
26
|
-
for (let i = 0; i < source.length; i++) {
|
|
27
|
-
const ch = source[i];
|
|
28
|
-
|
|
29
|
-
if (inString) {
|
|
30
|
-
if (escaped) {
|
|
31
|
-
escaped = false;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (ch === '\\') {
|
|
35
|
-
escaped = true;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if (ch !== '"') continue;
|
|
39
|
-
|
|
40
|
-
inString = false;
|
|
41
|
-
const rawKey = source.slice(stringStart + 1, i);
|
|
42
|
-
if (depth !== 1 || rawKey !== targetKey) continue;
|
|
43
|
-
|
|
44
|
-
let j = i + 1;
|
|
45
|
-
while (/\s/.test(source[j] ?? '')) j++;
|
|
46
|
-
if (source[j] !== ':') continue;
|
|
47
|
-
|
|
48
|
-
const valueStart = j + 1;
|
|
49
|
-
const value = readJsonValue(source, valueStart);
|
|
50
|
-
if (!value) return null;
|
|
51
|
-
return {keyStart: stringStart, keyEnd: i + 1, colon: j, ...value};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (ch === '"') {
|
|
55
|
-
inString = true;
|
|
56
|
-
stringStart = i;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (ch === '{' || ch === '[') depth++;
|
|
60
|
-
else if (ch === '}' || ch === ']') depth--;
|
|
61
|
-
}
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function readJsonValue(source, start) {
|
|
66
|
-
let i = start;
|
|
67
|
-
while (/\s/.test(source[i] ?? '')) i++;
|
|
68
|
-
const valueStart = i;
|
|
69
|
-
let inString = false;
|
|
70
|
-
let escaped = false;
|
|
71
|
-
let depth = 0;
|
|
72
|
-
|
|
73
|
-
for (; i < source.length; i++) {
|
|
74
|
-
const ch = source[i];
|
|
75
|
-
if (inString) {
|
|
76
|
-
if (escaped) {
|
|
77
|
-
escaped = false;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (ch === '\\') {
|
|
81
|
-
escaped = true;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (ch === '"') inString = false;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (ch === '"') {
|
|
88
|
-
inString = true;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (ch === '{' || ch === '[') {
|
|
92
|
-
depth++;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (ch === '}' || ch === ']') {
|
|
96
|
-
if (depth === 0) break;
|
|
97
|
-
depth--;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (depth === 0 && ch === ',') break;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const valueEnd = i;
|
|
104
|
-
const rawValue = source.slice(valueStart, valueEnd).trim();
|
|
105
|
-
try {
|
|
106
|
-
JSON.parse(rawValue);
|
|
107
|
-
} catch {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
return {valueStart, valueEnd, rawValue, terminator: source[i]};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function removeTopLevelProperty(source, property) {
|
|
114
|
-
let removeStart = property.keyStart;
|
|
115
|
-
let removeEnd = property.valueEnd;
|
|
116
|
-
|
|
117
|
-
if (source[removeEnd] === ',') {
|
|
118
|
-
removeEnd++;
|
|
119
|
-
if (source[removeEnd] === '\n') removeEnd++;
|
|
120
|
-
return source.slice(0, removeStart) + source.slice(removeEnd);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
let i = removeStart - 1;
|
|
124
|
-
while (/\s/.test(source[i] ?? '')) i--;
|
|
125
|
-
if (source[i] === ',') {
|
|
126
|
-
const whitespaceBetweenCommaAndKey = source.slice(i + 1, removeStart);
|
|
127
|
-
const preservedWhitespace = whitespaceBetweenCommaAndKey.includes('\n')
|
|
128
|
-
? '\n'
|
|
129
|
-
: '';
|
|
130
|
-
return source.slice(0, i) + preservedWhitespace + source.slice(removeEnd);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return source.slice(0, removeStart) + source.slice(removeEnd);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function formatConfigSource(rawConfig) {
|
|
137
|
-
const parsed = JSON.parse(rawConfig);
|
|
138
|
-
return `export default ${JSON.stringify(parsed, null, 2)};\n`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function migratePackageJsonConfig(config) {
|
|
142
|
-
if (!config.packageJson) return {changes: []};
|
|
143
|
-
|
|
144
|
-
const packageJson = config.packageJson.source;
|
|
145
|
-
const xdsConfig = findTopLevelKey(packageJson, 'xds');
|
|
146
|
-
const astryxConfig = findTopLevelKey(packageJson, 'astryx');
|
|
147
|
-
if (!xdsConfig && !astryxConfig) return {changes: []};
|
|
148
|
-
|
|
149
|
-
if (config.astryxConfig) {
|
|
150
|
-
return {
|
|
151
|
-
errors: [
|
|
152
|
-
{
|
|
153
|
-
file: 'package.json',
|
|
154
|
-
error:
|
|
155
|
-
'package.json contains xds/astryx config and astryx.config.mjs already exists; migrate the config manually.',
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (xdsConfig && astryxConfig) {
|
|
162
|
-
return {
|
|
163
|
-
errors: [
|
|
164
|
-
{
|
|
165
|
-
file: 'package.json',
|
|
166
|
-
error:
|
|
167
|
-
'package.json contains both xds and astryx config keys; migrate the config manually.',
|
|
168
|
-
},
|
|
169
|
-
],
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const configProperty = astryxConfig ?? xdsConfig;
|
|
174
|
-
const packageJsonWithoutConfig = removeTopLevelProperty(
|
|
175
|
-
packageJson,
|
|
176
|
-
configProperty,
|
|
177
|
-
);
|
|
178
|
-
return {
|
|
179
|
-
changes: [
|
|
180
|
-
{path: 'package.json', source: packageJsonWithoutConfig},
|
|
181
|
-
{
|
|
182
|
-
path: 'astryx.config.mjs',
|
|
183
|
-
source: formatConfigSource(configProperty.rawValue),
|
|
184
|
-
},
|
|
185
|
-
],
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function migrateXdsConfigFile(config) {
|
|
190
|
-
if (!config.xdsConfig) return {changes: []};
|
|
191
|
-
if (config.astryxConfig) {
|
|
192
|
-
return {
|
|
193
|
-
errors: [
|
|
194
|
-
{
|
|
195
|
-
file: 'xds.config.mjs',
|
|
196
|
-
error:
|
|
197
|
-
'xds.config.mjs and astryx.config.mjs both exist; migrate the config manually.',
|
|
198
|
-
},
|
|
199
|
-
],
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
changes: [
|
|
204
|
-
{path: 'astryx.config.mjs', source: config.xdsConfig.source},
|
|
205
|
-
{path: 'xds.config.mjs', delete: true},
|
|
206
|
-
],
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export default function transformer(_file, api) {
|
|
211
|
-
const config = api.config ?? {};
|
|
212
|
-
const packageResult = migratePackageJsonConfig(config);
|
|
213
|
-
if (packageResult.errors?.length) return packageResult;
|
|
214
|
-
if (packageResult.changes?.length) {
|
|
215
|
-
if (config.xdsConfig) {
|
|
216
|
-
return {
|
|
217
|
-
errors: [
|
|
218
|
-
{
|
|
219
|
-
file: 'package.json',
|
|
220
|
-
error:
|
|
221
|
-
'package.json contains xds/astryx config and xds.config.mjs exists; migrate the config manually.',
|
|
222
|
-
},
|
|
223
|
-
],
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
return packageResult;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return migrateXdsConfigFile(config);
|
|
230
|
-
}
|