@astryxdesign/cli 0.1.4-canary.ec9f8b2 → 0.1.4-canary.efa3ea5
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/docs/working-with-ai.doc.mjs +1 -1
- package/package.json +10 -10
- package/src/codemods/__tests__/registry.test.mjs +0 -1
- package/src/codemods/registry.mjs +0 -1
- package/src/commands/build-theme.mjs +8 -2
- package/src/commands/build-theme.variants.test.mjs +18 -7
- package/templates/blocks/components/AspectRatio/AspectRatioCircleImage.tsx +6 -1
- package/templates/blocks/components/AspectRatio/AspectRatioImageGallery.tsx +7 -2
- package/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +5 -2
- package/templates/blocks/components/AspectRatio/AspectRatioSquareImage.tsx +6 -1
- package/templates/blocks/components/AspectRatio/AspectRatioWidescreen.tsx +6 -1
- package/templates/blocks/components/Collapsible/CollapsibleDividedAccordion.doc.mjs +1 -1
- package/templates/blocks/components/Collapsible/CollapsibleDividedAccordion.tsx +1 -1
- package/templates/pages/kanban-board/page.tsx +13 -37
- package/src/codemods/transforms/v0.1.5/__tests__/rename-switch-label-spacing-default-to-hug.test.mjs +0 -93
- package/src/codemods/transforms/v0.1.5/index.mjs +0 -19
- package/src/codemods/transforms/v0.1.5/rename-switch-label-spacing-default-to-hug.mjs +0 -140
|
@@ -125,7 +125,7 @@ If you don't know all three, run \`npx astryx init --features agents\` to genera
|
|
|
125
125
|
lang: 'json',
|
|
126
126
|
label: 'package.json',
|
|
127
127
|
code: `"scripts": {
|
|
128
|
-
"
|
|
128
|
+
"xds": "node node_modules/@astryxdesign/cli/bin/astryx.mjs"
|
|
129
129
|
}`,
|
|
130
130
|
},
|
|
131
131
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.4-canary.
|
|
3
|
+
"version": "0.1.4-canary.efa3ea5",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -68,17 +68,17 @@
|
|
|
68
68
|
"CHANGELOG.md"
|
|
69
69
|
],
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@clack/prompts": "^1.
|
|
71
|
+
"@clack/prompts": "^1.5.1",
|
|
72
72
|
"commander": "^12.1.0",
|
|
73
73
|
"jiti": "^2.7.0",
|
|
74
74
|
"jscodeshift": "^17.3.0",
|
|
75
75
|
"zod": "^4.4.3"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@astryxdesign/charts": "0.1.4-canary.
|
|
79
|
-
"@astryxdesign/core": "0.1.4-canary.
|
|
80
|
-
"@astryxdesign/lab": "0.1.4-canary.
|
|
81
|
-
"@astryxdesign/theme-neutral": "0.1.4-canary.
|
|
78
|
+
"@astryxdesign/charts": "0.1.4-canary.efa3ea5",
|
|
79
|
+
"@astryxdesign/core": "0.1.4-canary.efa3ea5",
|
|
80
|
+
"@astryxdesign/lab": "0.1.4-canary.efa3ea5",
|
|
81
|
+
"@astryxdesign/theme-neutral": "0.1.4-canary.efa3ea5",
|
|
82
82
|
"gpt-tokenizer": "^3.4.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
@@ -96,10 +96,10 @@
|
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
|
-
"@astryxdesign/charts": "0.1.4-canary.
|
|
100
|
-
"@astryxdesign/core": "0.1.4-canary.
|
|
101
|
-
"@astryxdesign/lab": "0.1.4-canary.
|
|
102
|
-
"@astryxdesign/theme-neutral": "0.1.4-canary.
|
|
99
|
+
"@astryxdesign/charts": "0.1.4-canary.efa3ea5",
|
|
100
|
+
"@astryxdesign/core": "0.1.4-canary.efa3ea5",
|
|
101
|
+
"@astryxdesign/lab": "0.1.4-canary.efa3ea5",
|
|
102
|
+
"@astryxdesign/theme-neutral": "0.1.4-canary.efa3ea5",
|
|
103
103
|
"gpt-tokenizer": "^3.4.0"
|
|
104
104
|
},
|
|
105
105
|
"scripts": {
|
|
@@ -20,7 +20,6 @@ const registry = new Map([
|
|
|
20
20
|
['0.1.0', () => import('./transforms/v0.1.0/index.mjs')],
|
|
21
21
|
['0.1.2', () => import('./transforms/v0.1.2/index.mjs')],
|
|
22
22
|
['0.1.3', () => import('./transforms/v0.1.3/index.mjs')],
|
|
23
|
-
['0.1.5', () => import('./transforms/v0.1.5/index.mjs')],
|
|
24
23
|
]);
|
|
25
24
|
|
|
26
25
|
// Re-export from the shared utility so registry callers and other consumers
|
|
@@ -939,9 +939,15 @@ export function registerTheme(program) {
|
|
|
939
939
|
if (component.length > 0) {
|
|
940
940
|
const componentInner = component.join('\n\n');
|
|
941
941
|
const componentScope = `@scope (${scopeSelector}) to (${scopeTo}) {\n${componentInner}\n}`;
|
|
942
|
-
//
|
|
942
|
+
// light-dark() needs a color-scheme declaration in this bundle, but a
|
|
943
|
+
// bare `:root { color-scheme: light dark }` outranks reset.css's
|
|
944
|
+
// `html[data-theme]` mapping (astryx-theme layer comes after reset),
|
|
945
|
+
// silently defeating `<Theme mode>` forcing on <html>. Mirror the
|
|
946
|
+
// attribute-keyed mapping so the declaration follows the active mode.
|
|
943
947
|
const colorSchemeDecl = componentScope.includes('light-dark(')
|
|
944
|
-
? ' :root { color-scheme: light dark; }\n
|
|
948
|
+
? ' :root { color-scheme: light dark; }\n' +
|
|
949
|
+
' html[data-theme="light"] { color-scheme: light; }\n' +
|
|
950
|
+
' html[data-theme="dark"] { color-scheme: dark; }\n\n'
|
|
945
951
|
: '';
|
|
946
952
|
cssParts.push(
|
|
947
953
|
`@layer astryx-theme {\n${colorSchemeDecl}${componentScope}\n}`,
|
|
@@ -28,10 +28,20 @@ import * as fs from 'node:fs';
|
|
|
28
28
|
import * as path from 'node:path';
|
|
29
29
|
import * as os from 'node:os';
|
|
30
30
|
import {fileURLToPath} from 'node:url';
|
|
31
|
-
import {ensureCoreBuilt} from './ensure-core-built.mjs';
|
|
32
31
|
|
|
33
32
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
34
33
|
const CLI_BIN = path.resolve(__dirname, '../../bin/astryx.mjs');
|
|
34
|
+
const REPO_ROOT = path.resolve(__dirname, '../../../..');
|
|
35
|
+
const CORE_THEME_ENTRY = path.join(
|
|
36
|
+
REPO_ROOT,
|
|
37
|
+
'packages/core/dist/theme/index.js',
|
|
38
|
+
);
|
|
39
|
+
// The fix reads core's shipped component declarations to decide whether an
|
|
40
|
+
// interface is augmentable; those .d.ts files come from the same core build.
|
|
41
|
+
const CORE_BUTTON_DTS = path.join(
|
|
42
|
+
REPO_ROOT,
|
|
43
|
+
'packages/core/dist/Button/index.d.ts',
|
|
44
|
+
);
|
|
35
45
|
|
|
36
46
|
function runCli(args, cwd) {
|
|
37
47
|
try {
|
|
@@ -58,13 +68,14 @@ function writeTheme(dir, contents) {
|
|
|
58
68
|
return file;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
|
-
// Build core through the shared lock helper — this suite previously ran its
|
|
62
|
-
// own unguarded `if (!exists) pnpm -F core build`, and when Vitest scheduled
|
|
63
|
-
// it alongside the other build-theme suites on a fresh checkout, the
|
|
64
|
-
// concurrent builds collided on packages/core/dist (core's build starts by
|
|
65
|
-
// wiping dist), nondeterministically breaking whichever suite was mid-read.
|
|
66
71
|
beforeAll(() => {
|
|
67
|
-
|
|
72
|
+
if (!fs.existsSync(CORE_THEME_ENTRY) || !fs.existsSync(CORE_BUTTON_DTS)) {
|
|
73
|
+
execFileSync('pnpm', ['-F', '@astryxdesign/core', 'build'], {
|
|
74
|
+
cwd: REPO_ROOT,
|
|
75
|
+
stdio: 'pipe',
|
|
76
|
+
timeout: 180_000,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
68
79
|
}, 200_000);
|
|
69
80
|
|
|
70
81
|
let tmpDir;
|
|
@@ -8,10 +8,15 @@ import {Center} from '@astryxdesign/core/Center';
|
|
|
8
8
|
export default function AspectRatioCircleImage() {
|
|
9
9
|
return (
|
|
10
10
|
<Center width={300}>
|
|
11
|
-
<AspectRatio ratio={1} shape="ellipse"
|
|
11
|
+
<AspectRatio ratio={1} shape="ellipse">
|
|
12
12
|
<img
|
|
13
13
|
src="https://lookaside.facebook.com/assets/astryx/light-home-square-1.png"
|
|
14
14
|
alt="Circular image"
|
|
15
|
+
style={{
|
|
16
|
+
width: '100%',
|
|
17
|
+
height: '100%',
|
|
18
|
+
objectFit: 'cover',
|
|
19
|
+
}}
|
|
15
20
|
/>
|
|
16
21
|
</AspectRatio>
|
|
17
22
|
</Center>
|
|
@@ -25,11 +25,16 @@ export default function AspectRatioImageGallery() {
|
|
|
25
25
|
<Center width={600}>
|
|
26
26
|
<Grid columns={3} gap={4} width="100%">
|
|
27
27
|
{images.map(({id, alt}) => (
|
|
28
|
-
<AspectRatio key={id} ratio={4 / 3}
|
|
28
|
+
<AspectRatio key={id} ratio={4 / 3}>
|
|
29
29
|
<img
|
|
30
30
|
src="https://lookaside.facebook.com/assets/astryx/illustrative-horizontal-1.png"
|
|
31
31
|
alt={alt}
|
|
32
|
-
style={{
|
|
32
|
+
style={{
|
|
33
|
+
width: '100%',
|
|
34
|
+
height: '100%',
|
|
35
|
+
objectFit: 'cover',
|
|
36
|
+
borderRadius: 8,
|
|
37
|
+
}}
|
|
33
38
|
/>
|
|
34
39
|
</AspectRatio>
|
|
35
40
|
))}
|
|
@@ -34,13 +34,16 @@ export default function AspectRatioShowcase() {
|
|
|
34
34
|
<VStack key={label} gap={2} hAlign="center">
|
|
35
35
|
<AspectRatio
|
|
36
36
|
ratio={ratio}
|
|
37
|
-
fit="cover"
|
|
38
37
|
style={{
|
|
39
38
|
height: 120,
|
|
40
39
|
width: 'auto',
|
|
41
40
|
borderRadius: 'var(--radius-container)',
|
|
42
41
|
}}>
|
|
43
|
-
<img
|
|
42
|
+
<img
|
|
43
|
+
src={src}
|
|
44
|
+
alt={alt}
|
|
45
|
+
style={{width: '100%', height: '100%', objectFit: 'cover'}}
|
|
46
|
+
/>
|
|
44
47
|
</AspectRatio>
|
|
45
48
|
<Text type="supporting" color="secondary">
|
|
46
49
|
{label}
|
|
@@ -8,10 +8,15 @@ import {Center} from '@astryxdesign/core/Center';
|
|
|
8
8
|
export default function AspectRatioSquareImage() {
|
|
9
9
|
return (
|
|
10
10
|
<Center width={300}>
|
|
11
|
-
<AspectRatio ratio={1}
|
|
11
|
+
<AspectRatio ratio={1}>
|
|
12
12
|
<img
|
|
13
13
|
src="https://lookaside.facebook.com/assets/astryx/light-home-square-1.png"
|
|
14
14
|
alt="1:1 square"
|
|
15
|
+
style={{
|
|
16
|
+
width: '100%',
|
|
17
|
+
height: '100%',
|
|
18
|
+
objectFit: 'cover',
|
|
19
|
+
}}
|
|
15
20
|
/>
|
|
16
21
|
</AspectRatio>
|
|
17
22
|
</Center>
|
|
@@ -8,10 +8,15 @@ import {Center} from '@astryxdesign/core/Center';
|
|
|
8
8
|
export default function AspectRatioWidescreen() {
|
|
9
9
|
return (
|
|
10
10
|
<Center width={600}>
|
|
11
|
-
<AspectRatio ratio={16 / 9}
|
|
11
|
+
<AspectRatio ratio={16 / 9}>
|
|
12
12
|
<img
|
|
13
13
|
src="https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png"
|
|
14
14
|
alt="16:9 widescreen"
|
|
15
|
+
style={{
|
|
16
|
+
width: '100%',
|
|
17
|
+
height: '100%',
|
|
18
|
+
objectFit: 'cover',
|
|
19
|
+
}}
|
|
15
20
|
/>
|
|
16
21
|
</AspectRatio>
|
|
17
22
|
</Center>
|
|
@@ -6,7 +6,7 @@ export const doc = {
|
|
|
6
6
|
exampleFor: 'Collapsible',
|
|
7
7
|
name: 'Collapsible — Divided Accordion',
|
|
8
8
|
displayName: 'Collapsible — Divided Accordion',
|
|
9
|
-
description: 'FAQ-style accordion using the
|
|
9
|
+
description: 'FAQ-style accordion using the dividers prop on CollapsibleGroup: built-in row hairlines and density padding with zero custom CSS. Use for FAQs, settings lists, and nav sections.',
|
|
10
10
|
isReady: true,
|
|
11
11
|
aspectRatio: 4 / 3,
|
|
12
12
|
componentsUsed: ['Collapsible', 'CollapsibleGroup', 'Text'],
|
|
@@ -8,7 +8,7 @@ import {Text} from '@astryxdesign/core/Text';
|
|
|
8
8
|
export default function CollapsibleDividedAccordion() {
|
|
9
9
|
return (
|
|
10
10
|
<div style={{width: '100%', maxWidth: 400}}>
|
|
11
|
-
<CollapsibleGroup type="single"
|
|
11
|
+
<CollapsibleGroup type="single" dividers="between" defaultValue="reset">
|
|
12
12
|
<Collapsible trigger="How do I reset my password?" value="reset">
|
|
13
13
|
<Text type="body">
|
|
14
14
|
Go to Settings → Security → Change Password. You'll receive a
|
|
@@ -439,11 +439,8 @@ export default function KanbanBoardTemplate() {
|
|
|
439
439
|
let cb = columnRefCbs.current.get(id);
|
|
440
440
|
if (!cb) {
|
|
441
441
|
cb = el => {
|
|
442
|
-
if (el) {
|
|
443
|
-
|
|
444
|
-
} else {
|
|
445
|
-
columnEls.current.delete(id);
|
|
446
|
-
}
|
|
442
|
+
if (el) {columnEls.current.set(id, el);}
|
|
443
|
+
else {columnEls.current.delete(id);}
|
|
447
444
|
};
|
|
448
445
|
columnRefCbs.current.set(id, cb);
|
|
449
446
|
}
|
|
@@ -454,11 +451,8 @@ export default function KanbanBoardTemplate() {
|
|
|
454
451
|
let cb = cardRefCbs.current.get(id);
|
|
455
452
|
if (!cb) {
|
|
456
453
|
cb = el => {
|
|
457
|
-
if (el) {
|
|
458
|
-
|
|
459
|
-
} else {
|
|
460
|
-
cardEls.current.delete(id);
|
|
461
|
-
}
|
|
454
|
+
if (el) {cardEls.current.set(id, el);}
|
|
455
|
+
else {cardEls.current.delete(id);}
|
|
462
456
|
};
|
|
463
457
|
cardRefCbs.current.set(id, cb);
|
|
464
458
|
}
|
|
@@ -493,9 +487,7 @@ export default function KanbanBoardTemplate() {
|
|
|
493
487
|
): DropTarget | null => {
|
|
494
488
|
for (const [colId, el] of Array.from(columnEls.current.entries())) {
|
|
495
489
|
const r = el.getBoundingClientRect();
|
|
496
|
-
if (px < r.left || px > r.right || py < r.top || py > r.bottom) {
|
|
497
|
-
continue;
|
|
498
|
-
}
|
|
490
|
+
if (px < r.left || px > r.right || py < r.top || py > r.bottom) {continue;}
|
|
499
491
|
|
|
500
492
|
const ids = itemsByColumn[colId]
|
|
501
493
|
.filter(it => it.id !== draggedId)
|
|
@@ -504,9 +496,7 @@ export default function KanbanBoardTemplate() {
|
|
|
504
496
|
let index = ids.length;
|
|
505
497
|
for (let i = 0; i < ids.length; i++) {
|
|
506
498
|
const cardEl = cardEls.current.get(ids[i]);
|
|
507
|
-
if (!cardEl) {
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
499
|
+
if (!cardEl) {continue;}
|
|
510
500
|
const cr = cardEl.getBoundingClientRect();
|
|
511
501
|
if (py < cr.top + cr.height / 2) {
|
|
512
502
|
index = i;
|
|
@@ -523,27 +513,21 @@ export default function KanbanBoardTemplate() {
|
|
|
523
513
|
const commitDrag = (id: string, target: DropTarget) => {
|
|
524
514
|
setItems(prev => {
|
|
525
515
|
const moved = prev.find(it => it.id === id);
|
|
526
|
-
if (!moved) {
|
|
527
|
-
return prev;
|
|
528
|
-
}
|
|
516
|
+
if (!moved) {return prev;}
|
|
529
517
|
|
|
530
518
|
const rest = prev.filter(it => it.id !== id);
|
|
531
519
|
const updated: WorkItem = {...moved, column: target.column};
|
|
532
520
|
const colItems = rest.filter(it => it.column === target.column);
|
|
533
521
|
const anchor = colItems[target.index];
|
|
534
522
|
|
|
535
|
-
if (!anchor) {
|
|
536
|
-
return [...rest, updated];
|
|
537
|
-
}
|
|
523
|
+
if (!anchor) {return [...rest, updated];}
|
|
538
524
|
const at = rest.indexOf(anchor);
|
|
539
525
|
return [...rest.slice(0, at), updated, ...rest.slice(at)];
|
|
540
526
|
});
|
|
541
527
|
};
|
|
542
528
|
|
|
543
529
|
const onCardPointerDown = (e: ReactPointerEvent, id: string) => {
|
|
544
|
-
if (e.button !== 0) {
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
530
|
+
if (e.button !== 0) {return;}
|
|
547
531
|
// Let the card's own controls (the actions menu) handle the press.
|
|
548
532
|
if (
|
|
549
533
|
(e.target as HTMLElement).closest(
|
|
@@ -554,9 +538,7 @@ export default function KanbanBoardTemplate() {
|
|
|
554
538
|
}
|
|
555
539
|
|
|
556
540
|
const el = cardEls.current.get(id);
|
|
557
|
-
if (!el) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
541
|
+
if (!el) {return;}
|
|
560
542
|
|
|
561
543
|
const rect = el.getBoundingClientRect();
|
|
562
544
|
const startX = e.clientX;
|
|
@@ -592,9 +574,7 @@ export default function KanbanBoardTemplate() {
|
|
|
592
574
|
|
|
593
575
|
const onUp = () => {
|
|
594
576
|
teardownRef.current?.();
|
|
595
|
-
if (started && target) {
|
|
596
|
-
commitDrag(id, target);
|
|
597
|
-
}
|
|
577
|
+
if (started && target) {commitDrag(id, target);}
|
|
598
578
|
setDrag(null);
|
|
599
579
|
};
|
|
600
580
|
|
|
@@ -614,9 +594,7 @@ export default function KanbanBoardTemplate() {
|
|
|
614
594
|
|
|
615
595
|
// Suppress selection while dragging and detach listeners on unmount.
|
|
616
596
|
useEffect(() => {
|
|
617
|
-
if (!isDragging) {
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
597
|
+
if (!isDragging) {return;}
|
|
620
598
|
const previous = document.body.style.userSelect;
|
|
621
599
|
document.body.style.userSelect = 'none';
|
|
622
600
|
return () => {
|
|
@@ -634,9 +612,7 @@ export default function KanbanBoardTemplate() {
|
|
|
634
612
|
const ghostTarget =
|
|
635
613
|
drag && drag.target && drag.target.column === colId ? drag : null;
|
|
636
614
|
|
|
637
|
-
if (visible.length === 0 && !ghostTarget) {
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
615
|
+
if (visible.length === 0 && !ghostTarget) {return null;}
|
|
640
616
|
|
|
641
617
|
const nodes: ReactNode[] = visible.map(it => (
|
|
642
618
|
<BoardCard
|
package/src/codemods/transforms/v0.1.5/__tests__/rename-switch-label-spacing-default-to-hug.test.mjs
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
import {describe, it, expect} from 'vitest';
|
|
4
|
-
|
|
5
|
-
async function applyTransform(source) {
|
|
6
|
-
const {default: transform} =
|
|
7
|
-
await import('../rename-switch-label-spacing-default-to-hug.mjs');
|
|
8
|
-
const jscodeshift = (await import('jscodeshift')).default;
|
|
9
|
-
const j = jscodeshift.withParser('tsx');
|
|
10
|
-
const api = {jscodeshift: j, stats: () => {}, report: () => {}};
|
|
11
|
-
const file = {source, path: 'test.tsx'};
|
|
12
|
-
const result = transform(file, api);
|
|
13
|
-
return result ?? source;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('rename-switch-label-spacing-default-to-hug', () => {
|
|
17
|
-
it('renames Switch labelSpacing="default" to labelSpacing="hug"', async () => {
|
|
18
|
-
const input = `<Switch label="Notify" value={on} labelSpacing="default" />`;
|
|
19
|
-
const output = await applyTransform(input);
|
|
20
|
-
expect(output).toContain("'hug'");
|
|
21
|
-
expect(output).not.toContain('default');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("renames expression-container labelSpacing={'default'}", async () => {
|
|
25
|
-
const input = `<Switch label="Notify" value={on} labelSpacing={'default'} />`;
|
|
26
|
-
const output = await applyTransform(input);
|
|
27
|
-
expect(output).toContain("'hug'");
|
|
28
|
-
expect(output).not.toContain("'default'");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('renames default inside a ternary labelSpacing expression', async () => {
|
|
32
|
-
const input = `<Switch label="Notify" value={on} labelSpacing={isRow ? 'spread' : 'default'} />`;
|
|
33
|
-
const output = await applyTransform(input);
|
|
34
|
-
expect(output).toContain("'hug'");
|
|
35
|
-
expect(output).toContain("'spread'");
|
|
36
|
-
expect(output).not.toContain("'default'");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('does not change labelSpacing="spread"', async () => {
|
|
40
|
-
const input = `<Switch label="Notify" value={on} labelSpacing="spread" />`;
|
|
41
|
-
const output = await applyTransform(input);
|
|
42
|
-
expect(output).toBe(input);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('does not touch labelSpacing="default" on non-Switch components', async () => {
|
|
46
|
-
const input = `<LegacyToggle labelSpacing="default" />`;
|
|
47
|
-
const output = await applyTransform(input);
|
|
48
|
-
expect(output).toBe(input);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('does not touch other props with a "default" value on Switch', async () => {
|
|
52
|
-
const input = `<Switch label="Notify" value={on} data-variant="default" />`;
|
|
53
|
-
const output = await applyTransform(input);
|
|
54
|
-
expect(output).toBe(input);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('does not touch unrelated "default" string literals', async () => {
|
|
58
|
-
const input = `const theme = {mode: 'default', label: 'Default Mode'};`;
|
|
59
|
-
const output = await applyTransform(input);
|
|
60
|
-
expect(output).toBe(input);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('renames object labelSpacing prop in files importing Switch', async () => {
|
|
64
|
-
const input = `import {Switch} from '@astryxdesign/core/Switch';
|
|
65
|
-
const cfg = {labelSpacing: 'default'};`;
|
|
66
|
-
const output = await applyTransform(input);
|
|
67
|
-
expect(output).toContain("labelSpacing: 'hug'");
|
|
68
|
-
expect(output).not.toContain("'default'");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("renames labelSpacing: 'default' as const in files importing Switch", async () => {
|
|
72
|
-
const input = `import {Switch} from '@astryxdesign/core/Switch';
|
|
73
|
-
const ROWS = [{labelSpacing: 'default' as const, label: 'Adjacent'}];`;
|
|
74
|
-
const output = await applyTransform(input);
|
|
75
|
-
expect(output).toContain("'hug' as const");
|
|
76
|
-
expect(output).not.toContain("'default'");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('does not rename object labelSpacing prop when Switch is not imported', async () => {
|
|
80
|
-
const input = `const cfg = {labelSpacing: 'default'};`;
|
|
81
|
-
const output = await applyTransform(input);
|
|
82
|
-
expect(output).toBe(input);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('renames Storybook argTypes options in files importing Switch', async () => {
|
|
86
|
-
const input = `import {Switch} from '@astryxdesign/core/Switch';
|
|
87
|
-
const meta = {argTypes: {labelSpacing: {control: 'select', options: ['default', 'spread']}}};`;
|
|
88
|
-
const output = await applyTransform(input);
|
|
89
|
-
expect(output).toContain("'hug'");
|
|
90
|
-
expect(output).toContain("'spread'");
|
|
91
|
-
expect(output).not.toContain("'default'");
|
|
92
|
-
});
|
|
93
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file v0.1.5 transform manifest
|
|
5
|
-
*
|
|
6
|
-
* Lists all codemods for the v0.1.5 release in the order they should run.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import renameSwitchLabelSpacingDefaultToHug, {
|
|
10
|
-
meta as renameSwitchLabelSpacingDefaultToHugMeta,
|
|
11
|
-
} from './rename-switch-label-spacing-default-to-hug.mjs';
|
|
12
|
-
|
|
13
|
-
export default [
|
|
14
|
-
{
|
|
15
|
-
name: 'rename-switch-label-spacing-default-to-hug',
|
|
16
|
-
transform: renameSwitchLabelSpacingDefaultToHug,
|
|
17
|
-
meta: renameSwitchLabelSpacingDefaultToHugMeta,
|
|
18
|
-
},
|
|
19
|
-
];
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file Codemod: Rename Switch labelSpacing "default" to "hug"
|
|
5
|
-
* @see https://github.com/facebook/astryx/issues/2889
|
|
6
|
-
*
|
|
7
|
-
* The `labelSpacing="default"` value is renamed to `labelSpacing="hug"` so
|
|
8
|
-
* the name describes the behavior (label and switch hug each other), matching
|
|
9
|
-
* the behavioral register of the sibling `spread` value and the existing
|
|
10
|
-
* `hug`/`fill` layout vocabulary on TabList and SegmentedControl. The old
|
|
11
|
-
* `default` value keeps working as a deprecated alias, so this codemod is a
|
|
12
|
-
* cleanup, not a build fix.
|
|
13
|
-
*
|
|
14
|
-
* Transforms (scoped to Switch):
|
|
15
|
-
* - <Switch labelSpacing="default" /> → <Switch labelSpacing="hug" />
|
|
16
|
-
* - <Switch labelSpacing={'default'} /> → <Switch labelSpacing={'hug'} />
|
|
17
|
-
* - <Switch labelSpacing={cond ? 'default' : x}> → <Switch labelSpacing={cond ? 'hug' : x}>
|
|
18
|
-
* - Storybook argTypes: labelSpacing: { options: [..., 'default', ...] }
|
|
19
|
-
* → replaces 'default' with 'hug' (files importing Switch)
|
|
20
|
-
* - Object properties: { labelSpacing: 'default' } / { labelSpacing: 'default' as const }
|
|
21
|
-
* → { labelSpacing: 'hug' } (files importing Switch)
|
|
22
|
-
*
|
|
23
|
-
* The transform deliberately does NOT touch bare `'default'` strings that are
|
|
24
|
-
* not a `labelSpacing` value (an extremely common string otherwise), so it is
|
|
25
|
-
* safe to run across an entire codebase.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export const meta = {
|
|
29
|
-
title: 'Rename Switch labelSpacing "default" to "hug"',
|
|
30
|
-
description:
|
|
31
|
-
'Renames the `labelSpacing="default"` value to `labelSpacing="hug"` on ' +
|
|
32
|
-
'Switch. The old value keeps working as a deprecated alias. Also updates ' +
|
|
33
|
-
'Storybook labelSpacing argTypes options and object-literal `labelSpacing` ' +
|
|
34
|
-
'props in files that import Switch.',
|
|
35
|
-
pr: '#2889',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const OLD_VALUE = 'default';
|
|
39
|
-
const NEW_VALUE = 'hug';
|
|
40
|
-
|
|
41
|
-
/** Prop whose value is being renamed. */
|
|
42
|
-
const TARGET_PROP = 'labelSpacing';
|
|
43
|
-
|
|
44
|
-
/** Components whose `labelSpacing` prop accepts a SwitchLabelSpacing value. */
|
|
45
|
-
const TARGET_COMPONENTS = new Set(['Switch']);
|
|
46
|
-
|
|
47
|
-
export default function transformer(file, api) {
|
|
48
|
-
const j = api.jscodeshift;
|
|
49
|
-
const root = j(file.source);
|
|
50
|
-
let hasChanges = false;
|
|
51
|
-
|
|
52
|
-
/** Rewrite a string-literal node when it equals the old value. */
|
|
53
|
-
function renameStringLiteral(node) {
|
|
54
|
-
if (!node) return false;
|
|
55
|
-
if (
|
|
56
|
-
(node.type === 'StringLiteral' || node.type === 'Literal') &&
|
|
57
|
-
node.value === OLD_VALUE
|
|
58
|
-
) {
|
|
59
|
-
node.value = NEW_VALUE;
|
|
60
|
-
if (node.raw) node.raw = `'${NEW_VALUE}'`;
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Recursively rewrite the old value inside ternary/logical expressions. */
|
|
67
|
-
function renameInExpression(node) {
|
|
68
|
-
if (!node) return false;
|
|
69
|
-
let changed = false;
|
|
70
|
-
if (node.type === 'StringLiteral' || node.type === 'Literal') {
|
|
71
|
-
changed = renameStringLiteral(node) || changed;
|
|
72
|
-
} else if (node.type === 'ConditionalExpression') {
|
|
73
|
-
changed = renameInExpression(node.consequent) || changed;
|
|
74
|
-
changed = renameInExpression(node.alternate) || changed;
|
|
75
|
-
} else if (node.type === 'LogicalExpression') {
|
|
76
|
-
changed = renameInExpression(node.left) || changed;
|
|
77
|
-
changed = renameInExpression(node.right) || changed;
|
|
78
|
-
} else if (node.type === 'TSAsExpression') {
|
|
79
|
-
// `'default' as const` → rewrite the inner expression
|
|
80
|
-
changed = renameInExpression(node.expression) || changed;
|
|
81
|
-
}
|
|
82
|
-
return changed;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// --- 1. JSX attribute: labelSpacing="default" / labelSpacing={'default'} on Switch ---
|
|
86
|
-
root.find(j.JSXOpeningElement).forEach(path => {
|
|
87
|
-
const name = path.node.name;
|
|
88
|
-
const componentName = name.type === 'JSXIdentifier' ? name.name : null;
|
|
89
|
-
if (!componentName || !TARGET_COMPONENTS.has(componentName)) return;
|
|
90
|
-
|
|
91
|
-
path.node.attributes.forEach(attr => {
|
|
92
|
-
if (attr.type !== 'JSXAttribute') return;
|
|
93
|
-
if (!attr.name || attr.name.name !== TARGET_PROP) return;
|
|
94
|
-
|
|
95
|
-
const value = attr.value;
|
|
96
|
-
if (!value) return;
|
|
97
|
-
|
|
98
|
-
if (value.type === 'StringLiteral' || value.type === 'Literal') {
|
|
99
|
-
if (renameStringLiteral(value)) hasChanges = true;
|
|
100
|
-
} else if (value.type === 'JSXExpressionContainer') {
|
|
101
|
-
if (renameInExpression(value.expression)) hasChanges = true;
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Object-property / argTypes transforms only run in files that use a target
|
|
107
|
-
// component — keeps unrelated `{ labelSpacing: 'default' }` strings safe.
|
|
108
|
-
const importsTarget =
|
|
109
|
-
root
|
|
110
|
-
.find(j.ImportSpecifier)
|
|
111
|
-
.filter(p => TARGET_COMPONENTS.has(p.node.imported?.name))
|
|
112
|
-
.size() > 0;
|
|
113
|
-
|
|
114
|
-
if (importsTarget) {
|
|
115
|
-
const PropertyType = j.ObjectProperty ?? j.Property;
|
|
116
|
-
|
|
117
|
-
// --- 2. Object property: { labelSpacing: 'default' } / 'default' as const ---
|
|
118
|
-
root.find(PropertyType, {key: {name: TARGET_PROP}}).forEach(path => {
|
|
119
|
-
if (renameInExpression(path.node.value)) hasChanges = true;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// --- 3. Storybook argTypes: labelSpacing: { options: [..., 'default', ...] } ---
|
|
123
|
-
root.find(PropertyType, {key: {name: TARGET_PROP}}).forEach(path => {
|
|
124
|
-
const value = path.node.value;
|
|
125
|
-
if (!value || value.type !== 'ObjectExpression') return;
|
|
126
|
-
|
|
127
|
-
const optionsProp = value.properties.find(
|
|
128
|
-
p => p.key && (p.key.name === 'options' || p.key.value === 'options'),
|
|
129
|
-
);
|
|
130
|
-
if (optionsProp && optionsProp.value.type === 'ArrayExpression') {
|
|
131
|
-
optionsProp.value.elements.forEach(el => {
|
|
132
|
-
if (renameStringLiteral(el)) hasChanges = true;
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (!hasChanges) return undefined;
|
|
139
|
-
return root.toSource({quote: 'single'});
|
|
140
|
-
}
|