@astryxdesign/cli 0.6.3-canary.d0d49a4 → 0.6.3-canary.db4e378
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -72
- package/api/build/build.doc.mjs +3 -3
- package/api/build/build.test.mjs +14 -3
- package/api/build/build.type.d.mts +35 -1
- package/api/build/build.type.mjs +24 -3
- package/api/build/help/help.d.mts +8 -5
- package/api/build/help/help.mjs +56 -6
- package/api/component/component.doc.mjs +12 -3
- package/api/component/component.mjs +31 -2
- package/api/component/component.test.mjs +38 -0
- package/api/component/component.type.d.mts +16 -5
- package/api/component/component.type.mjs +13 -5
- package/api/component/detail/blocks/blocks.d.mts +2 -1
- package/api/component/detail/blocks/blocks.mjs +4 -3
- package/api/component/list/list.d.mts +0 -5
- package/api/component/list/list.mjs +37 -9
- package/api/discover/discover.doc.mjs +1 -1
- package/api/gap-report/gap-report.doc.mjs +7 -4
- package/api/init/init.doc.mjs +4 -0
- package/api/init/init.test.mjs +41 -1
- package/api/init/remove/remove.mjs +1 -1
- package/api/init/run/run.mjs +20 -10
- package/api/integration/add-contribution.component-names.test.mjs +120 -0
- package/api/integration/add-contribution.mjs +11 -0
- package/api/integration/add-contribution.test.mjs +80 -0
- package/api/integration/integrationAdd.doc.mjs +7 -4
- package/api/integration/integrationAddComponent.doc.mjs +1 -1
- package/api/integration/validate-integration.mjs +2 -0
- package/api/integration/validate-integration.test.mjs +91 -0
- package/api/integration/validateIntegration.doc.mjs +1 -1
- package/api/json/envelope-types.test.mjs +76 -0
- package/api/json/parseResponse.doc.mjs +2 -2
- package/api/layout/expand/expand.mjs +7 -5
- package/api/layout/expand/expand.path-safety.test.mjs +53 -0
- package/api/layout/grammar/grammar.mjs +2 -1
- package/api/layout/layoutExpand.doc.mjs +1 -1
- package/api/search/search-return-type.test.mjs +54 -0
- package/api/search/search.d.mts +2 -9
- package/api/search/search.doc.mjs +5 -1
- package/api/search/search.mjs +10 -3
- package/api/search/search.test.mjs +34 -0
- package/api/swizzle/copy/copy.mjs +28 -11
- package/api/swizzle/swizzle.doc.mjs +1 -1
- package/api/template/copy/copy.mjs +17 -23
- package/api/template/copy/copy.test.mjs +17 -0
- package/api/template/template.doc.mjs +2 -1
- package/api/theme/add/add.binary.test.mjs +91 -0
- package/api/theme/add/add.mjs +20 -3
- package/api/theme/add/add.staging.test.mjs +66 -0
- package/api/theme/build/build.mjs +114 -37
- package/api/theme/build/build.public-component-vars.test.mjs +1 -1
- package/api/theme/build/build.receipt-doc.test.mjs +111 -0
- package/api/theme/build/font-warning.mjs +3 -3
- package/api/theme/build/font-warning.test.mjs +5 -2
- package/api/theme/palette/generate/generate.mjs +7 -2
- package/api/theme/palette/generate/generate.test.mjs +96 -0
- package/api/theme/palette/generate/generator.mjs +8 -1
- package/api/theme/palette/generate/generator.test.mjs +10 -0
- package/api/theme/template/template.mjs +11 -2
- package/api/theme/template/template.test.mjs +15 -0
- package/api/theme/themeBuild.doc.mjs +7 -4
- package/api/theme/themeTemplate.doc.mjs +4 -1
- package/api/upgrade/_adapter.mjs +27 -4
- package/api/upgrade/list/list.mjs +2 -1
- package/api/upgrade/list/list.test.mjs +73 -0
- package/api/upgrade/status/status.mjs +2 -2
- package/api/upgrade/upgrade.doc.mjs +2 -1
- package/api/upgrade/upgrade.type.d.mts +4 -0
- package/api/upgrade/upgrade.type.mjs +1 -0
- package/assets/codemods/term-log.mjs +32 -8
- package/assets/codemods/term-log.test.mjs +19 -1
- package/assets/codemods/transform-prop.mjs +109 -0
- package/assets/codemods/transform-prop.test.mjs +95 -0
- package/assets/codemods/transforms/v0.0.14/__tests__/rename-status-variants.test.mjs +86 -165
- package/assets/codemods/transforms/v0.0.14/rename-status-variants.mjs +72 -210
- package/assets/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +83 -115
- package/assets/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +57 -186
- package/assets/codemods/transforms/v0.6.0/__tests__/next-codemods.test.mjs +47 -0
- package/assets/codemods/transforms/v0.6.0/rename-resizable-pixel-bounds.mjs +57 -10
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaInlineRail.tsx +61 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaOverscrollChaining.tsx +126 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaShowcase.tsx +86 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyGroupHeaders.tsx +99 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaStickyPassthrough.tsx +122 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.doc.mjs +14 -0
- package/assets/templates/blocks/components/ScrollableArea/ScrollableAreaTwoAxisBoard.tsx +95 -0
- package/assets/templates/pages/table-tree/page.tsx +1704 -0
- package/assets/templates/pages/table-tree/template.doc.mjs +12 -0
- package/authoring/config/config.doc.mjs +1 -1
- package/authoring/config/debug-composition.test.mjs +92 -0
- package/authoring/config/type.ts +15 -3
- package/clients/cli/command-load-failure.test.mjs +83 -0
- package/clients/cli/commands/blog.mjs +23 -8
- package/clients/cli/commands/blog.test.mjs +42 -1
- package/clients/cli/commands/build-theme.ascii-output.test.mjs +161 -0
- package/clients/cli/commands/build-theme.flag-docs.test.mjs +110 -0
- package/clients/cli/commands/build-theme.mjs +5 -5
- package/clients/cli/commands/build-theme.path-safety.test.mjs +86 -1
- package/clients/cli/commands/build-theme.variants.test.mjs +76 -0
- package/clients/cli/commands/build.doc.mjs +4 -1
- package/clients/cli/commands/build.exit-codes-doc.test.mjs +50 -0
- package/clients/cli/commands/build.mjs +32 -47
- package/clients/cli/commands/build.playbook.test.mjs +75 -0
- package/clients/cli/commands/build.text-fields.test.mjs +40 -0
- package/clients/cli/commands/component/index.mjs +2 -7
- package/clients/cli/commands/component-package.test.mjs +46 -0
- package/clients/cli/commands/component-resolution.test.mjs +21 -0
- package/clients/cli/commands/component.test.mjs +19 -0
- package/clients/cli/commands/detail-levels.test.mjs +2 -2
- package/clients/cli/commands/discover.broken-integration.test.mjs +48 -0
- package/clients/cli/commands/discover.components-flag.test.mjs +97 -0
- package/clients/cli/commands/discover.doc.mjs +4 -2
- package/clients/cli/commands/discover.mjs +3 -3
- package/clients/cli/commands/discover.text-projection.test.mjs +103 -0
- package/clients/cli/commands/doctor-integration.package-json.test.mjs +53 -0
- package/clients/cli/commands/doctor.mjs +6 -16
- package/clients/cli/commands/doctor.test.mjs +42 -0
- package/clients/cli/commands/gap-report.doc.mjs +16 -5
- package/clients/cli/commands/gap-report.test.mjs +72 -0
- package/clients/cli/commands/hook/index.mjs +7 -17
- package/clients/cli/commands/hook.text-projection.test.mjs +45 -0
- package/clients/cli/commands/init.doc.mjs +19 -8
- package/clients/cli/commands/init.flag-help.test.mjs +153 -0
- package/clients/cli/commands/integration-add.controls.test.mjs +132 -0
- package/clients/cli/commands/integration-add.doc.mjs +21 -5
- package/clients/cli/commands/interactive-guard.test.mjs +101 -24
- package/clients/cli/commands/json-contract.test.mjs +33 -0
- package/clients/cli/commands/layout-check.doc.mjs +14 -3
- package/clients/cli/commands/layout-expand.doc.mjs +21 -4
- package/clients/cli/commands/layout.doc.mjs +2 -2
- package/clients/cli/commands/layout.mjs +21 -9
- package/clients/cli/commands/layout.path-help.test.mjs +33 -0
- package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
- package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
- package/clients/cli/commands/no-prompt-wording.test.mjs +94 -0
- package/clients/cli/commands/search.doc.mjs +6 -3
- package/clients/cli/commands/search.mjs +17 -7
- package/clients/cli/commands/search.test.mjs +75 -0
- package/clients/cli/commands/swizzle.doc.mjs +2 -1
- package/clients/cli/commands/swizzle.path-safety.test.mjs +42 -0
- package/clients/cli/commands/template.doc.mjs +28 -6
- package/clients/cli/commands/template.flag-help.test.mjs +117 -0
- package/clients/cli/commands/template.path-help.test.mjs +40 -0
- package/clients/cli/commands/theme-add.doc.mjs +2 -1
- package/clients/cli/commands/theme-build.doc.mjs +7 -6
- package/clients/cli/commands/theme-palette-generate.test.mjs +19 -0
- package/clients/cli/commands/theme-template.behavior.test.mjs +12 -0
- package/clients/cli/commands/theme-template.doc.mjs +1 -1
- package/clients/cli/commands/upgrade.ascii-output.test.mjs +87 -0
- package/clients/cli/commands/upgrade.doc.mjs +17 -6
- package/clients/cli/commands/upgrade.flag-help.test.mjs +188 -0
- package/clients/cli/commands/upgrade.hook-output.test.mjs +88 -0
- package/clients/cli/index.mjs +13 -30
- package/clients/cli/latest-version-env.test.mjs +50 -0
- package/clients/cli/lib/cli-error.test.mjs +7 -0
- package/clients/cli/lib/component-format.mjs +9 -9
- package/clients/cli/lib/component-format.test.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +32 -6
- package/clients/cli/lib/doc-text-ascii.test.mjs +82 -0
- package/clients/cli/lib/exit-codes.test.mjs +97 -0
- package/clients/cli/lib/hook-format.mjs +5 -5
- package/clients/cli/lib/json-shim.mjs +38 -2
- package/clients/cli/lib/json-shim.test.mjs +83 -0
- package/clients/cli/lib/manifest.d.ts +2 -0
- package/clients/cli/lib/manifest.mjs +22 -0
- package/clients/cli/lib/manifest.test.mjs +17 -0
- package/foundation/agent-docs/agent-docs.d.mts +4 -0
- package/foundation/agent-docs/agent-docs.mjs +69 -3
- package/foundation/agent-docs/agent-docs.path-safety.test.mjs +266 -4
- package/foundation/config/integration-debug.test.mjs +28 -3
- package/foundation/config/project.mjs +123 -9
- package/foundation/config/project.test.mjs +126 -0
- package/foundation/discovery/template-adapter.d.mts +14 -5
- package/foundation/discovery/template-adapter.fixture-refs.test.mjs +248 -0
- package/foundation/discovery/template-adapter.integration-isolation.test.mjs +94 -0
- package/foundation/discovery/template-adapter.mjs +292 -25
- package/foundation/discovery/theming-targets.test.mjs +4 -0
- package/foundation/fs/module-loader.d.mts +1 -0
- package/foundation/fs/module-loader.mjs +50 -1
- package/foundation/fs/module-loader.stdout.test.mjs +332 -0
- package/foundation/fs/path-safety.d.mts +3 -2
- package/foundation/fs/path-safety.mjs +49 -19
- package/foundation/fs/path-safety.test.mjs +50 -0
- package/foundation/fs/publish-file-hardlink-unavailable.test.mjs +129 -94
- package/foundation/integrations/autolink.mjs +6 -1
- package/foundation/response/base.d.ts +8 -4
- package/foundation/response/error-codes.doc.mjs +1 -2
- package/foundation/response/error-codes.mjs +1 -1
- package/foundation/response/error-codes.test.mjs +83 -0
- package/foundation/response/json-contract.test.mjs +11 -0
- package/foundation/response/json.d.mts +4 -2
- package/foundation/response/json.mjs +8 -10
- package/foundation/response/response-types.doc.mjs +10 -10
- package/foundation/response/response-types.doc.test.mjs +158 -0
- package/foundation/xle/expand.d.mts +2 -0
- package/foundation/xle/expand.mjs +3 -2
- package/foundation/xle/expand.test.mjs +54 -0
- package/package.json +9 -9
- package/clients/cli/lib/update-check.mjs +0 -83
- package/clients/cli/lib/update-check.test.mjs +0 -137
- package/clients/cli/update-hint-commands.test.mjs +0 -54
|
@@ -1,205 +1,126 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
|
-
import {describe,
|
|
3
|
+
import {describe, expect, it} from 'vitest';
|
|
4
|
+
import transform from '../rename-status-variants.mjs';
|
|
4
5
|
|
|
5
6
|
async function applyTransform(source) {
|
|
6
|
-
const {default: transform} = await import(
|
|
7
|
-
'../rename-status-variants.mjs'
|
|
8
|
-
);
|
|
9
7
|
const jscodeshift = (await import('jscodeshift')).default;
|
|
10
8
|
const j = jscodeshift.withParser('tsx');
|
|
11
9
|
const api = {jscodeshift: j, stats: () => {}, report: () => {}};
|
|
12
|
-
|
|
13
|
-
const result = transform(file, api);
|
|
14
|
-
return result ?? source;
|
|
10
|
+
return transform({source, path: 'test.tsx'}, api) ?? source;
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
describe('rename-status-variants', () => {
|
|
18
|
-
it('renames StatusDot
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
14
|
+
it('renames direct StatusDot literals and ternary branches', async () => {
|
|
15
|
+
const output = await applyTransform(`import {StatusDot} from '@astryxdesign/core';
|
|
16
|
+
const a = <StatusDot variant="positive" />;
|
|
17
|
+
const b = <StatusDot variant={'negative'} />;
|
|
18
|
+
const c = <StatusDot variant={ready ? 'info' : 'positive'} />;`);
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
expect(output).toContain("'error'");
|
|
29
|
-
expect(output).not.toContain("'negative'");
|
|
20
|
+
expect(output).toContain("variant='success'");
|
|
21
|
+
expect(output).toContain("variant={'error'}");
|
|
22
|
+
expect(output).toContain("ready ? 'accent' : 'success'");
|
|
30
23
|
});
|
|
31
24
|
|
|
32
|
-
it('renames
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
expect(output).not.toContain("'info'");
|
|
37
|
-
});
|
|
25
|
+
it('renames literals inside satisfies expressions while preserving the type', async () => {
|
|
26
|
+
const output =
|
|
27
|
+
await applyTransform(`import {StatusDot} from '@astryxdesign/core';
|
|
28
|
+
const view = <StatusDot variant={'positive' satisfies StatusDotVariant} />;`);
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
const input = `<XDSAvatarStatusDot variant="positive" label="Online" />`;
|
|
41
|
-
const output = await applyTransform(input);
|
|
42
|
-
expect(output).toContain("'success'");
|
|
43
|
-
expect(output).not.toContain("'positive'");
|
|
30
|
+
expect(output).toContain("{'success' satisfies StatusDotVariant}");
|
|
44
31
|
});
|
|
45
32
|
|
|
46
|
-
it('renames Icon
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
33
|
+
it('renames Icon, SVGIcon, AvatarStatusDot, and ProgressBar props', async () => {
|
|
34
|
+
const output = await applyTransform(`import {
|
|
35
|
+
Icon,
|
|
36
|
+
SVGIcon,
|
|
37
|
+
AvatarStatusDot,
|
|
38
|
+
ProgressBar,
|
|
39
|
+
} from '@astryxdesign/core';
|
|
40
|
+
const view = <>
|
|
41
|
+
<Icon color="negative" />
|
|
42
|
+
<SVGIcon color="positive" />
|
|
43
|
+
<AvatarStatusDot variant="negative" />
|
|
44
|
+
<ProgressBar variant="positive" />
|
|
45
|
+
</>;`);
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const output = await applyTransform(input);
|
|
56
|
-
expect(output).toContain("'error'");
|
|
57
|
-
expect(output).not.toContain("'negative'");
|
|
47
|
+
expect(output.match(/'error'/g)).toHaveLength(2);
|
|
48
|
+
expect(output.match(/'success'/g)).toHaveLength(2);
|
|
58
49
|
});
|
|
59
50
|
|
|
60
|
-
it('
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
expect(output).not.toContain("'positive'");
|
|
65
|
-
});
|
|
51
|
+
it('maps import aliases and default subpath imports', async () => {
|
|
52
|
+
const output = await applyTransform(`import {StatusDot as Dot} from '@xds/core';
|
|
53
|
+
import IconGlyph from '@astryxdesign/core/Icon';
|
|
54
|
+
const view = <><Dot variant="positive" /><IconGlyph color="negative" /></>;`);
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const output = await applyTransform(input);
|
|
70
|
-
expect(output).toContain("'error'");
|
|
71
|
-
expect(output).not.toContain("'negative'");
|
|
56
|
+
expect(output).toContain("<Dot variant='success'");
|
|
57
|
+
expect(output).toContain("<IconGlyph color='error'");
|
|
72
58
|
});
|
|
73
59
|
|
|
74
|
-
it('
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
expect(output).toContain("'success'");
|
|
78
|
-
expect(output).not.toContain("'positive'");
|
|
79
|
-
});
|
|
60
|
+
it('maps namespace imports', async () => {
|
|
61
|
+
const output = await applyTransform(`import * as Core from '@astryxdesign/core';
|
|
62
|
+
const view = <Core.StatusDot variant="info" />;`);
|
|
80
63
|
|
|
81
|
-
|
|
82
|
-
const input = `<XDSStatusDot variant={isOnline ? 'positive' : 'negative'} label="Status" />`;
|
|
83
|
-
const output = await applyTransform(input);
|
|
84
|
-
expect(output).toContain("'success'");
|
|
85
|
-
expect(output).toContain("'error'");
|
|
86
|
-
expect(output).not.toContain("'positive'");
|
|
87
|
-
expect(output).not.toContain("'negative'");
|
|
64
|
+
expect(output).toContain("variant='accent'");
|
|
88
65
|
});
|
|
89
66
|
|
|
90
|
-
it('
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
67
|
+
it('keeps Badge info distinct while renaming its removed variants', async () => {
|
|
68
|
+
const output = await applyTransform(`import {Badge} from '@astryxdesign/core';
|
|
69
|
+
const view = <>
|
|
70
|
+
<Badge variant="info" />
|
|
71
|
+
<Badge variant="positive" />
|
|
72
|
+
<Badge variant="negative" />
|
|
73
|
+
</>;`);
|
|
97
74
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const output = await applyTransform(input);
|
|
102
|
-
expect(output).toContain("'success'");
|
|
103
|
-
expect(output).toContain("'error'");
|
|
104
|
-
expect(output).not.toContain("'positive'");
|
|
105
|
-
expect(output).not.toContain("'negative'");
|
|
106
|
-
// 'info' is ambiguous (valid on Badge etc.) and this is a context-blind
|
|
107
|
-
// path — the codemod cannot tell which component the options describe, so
|
|
108
|
-
// it is left for human review rather than risk rewriting a valid value.
|
|
109
|
-
expect(output).toContain("'info'");
|
|
110
|
-
expect(output).not.toContain("'accent'");
|
|
111
|
-
// warning and neutral are unchanged
|
|
112
|
-
expect(output).toContain("'warning'");
|
|
113
|
-
expect(output).toContain("'neutral'");
|
|
75
|
+
expect(output).toContain('variant="info"');
|
|
76
|
+
expect(output).toContain("variant='success'");
|
|
77
|
+
expect(output).toContain("variant='error'");
|
|
114
78
|
});
|
|
115
79
|
|
|
116
|
-
it('
|
|
117
|
-
const input =
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
80
|
+
it('leaves variables, helpers, objects, types, and Storybook metadata untouched', async () => {
|
|
81
|
+
const input = `import {StatusDot} from '@astryxdesign/core';
|
|
82
|
+
type CellTone = 'positive' | 'negative' | 'info';
|
|
83
|
+
const args = {variant: 'positive'};
|
|
84
|
+
const options = ['positive', 'negative', 'info'];
|
|
85
|
+
function tone() { return 'negative' as const; }
|
|
86
|
+
const view = <StatusDot variant={tone()} />;`;
|
|
87
|
+
|
|
88
|
+
expect(await applyTransform(input)).toBe(input);
|
|
121
89
|
});
|
|
122
90
|
|
|
123
|
-
it('
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// renamed (no component keeps them), but info is preserved.
|
|
130
|
-
const input = `import { XDSIcon } from '@xds/core/Icon';
|
|
131
|
-
import { XDSBadge, type XDSBadgeVariant } from '@xds/core/Badge';
|
|
132
|
-
const PROGRESS_CONFIG: Record<string, {label: string; variant: XDSBadgeVariant}> = {
|
|
133
|
-
OPEN: {label: 'Open', variant: 'info'},
|
|
134
|
-
DONE: {label: 'Done', variant: 'positive'},
|
|
135
|
-
BLOCKED: {label: 'Blocked', variant: 'negative'},
|
|
136
|
-
};`;
|
|
137
|
-
const output = await applyTransform(input);
|
|
138
|
-
// Ambiguous 'info' on a Badge config is left intact.
|
|
139
|
-
expect(output).toContain("variant: 'info'");
|
|
140
|
-
expect(output).not.toContain("'accent'");
|
|
141
|
-
// Unambiguous values are still migrated (no component retains them).
|
|
142
|
-
expect(output).toContain("variant: 'success'");
|
|
143
|
-
expect(output).toContain("variant: 'error'");
|
|
144
|
-
expect(output).not.toContain("'positive'");
|
|
145
|
-
expect(output).not.toContain("'negative'");
|
|
91
|
+
it('leaves unrelated props untouched', async () => {
|
|
92
|
+
const output = await applyTransform(`import {StatusDot} from '@astryxdesign/core';
|
|
93
|
+
const view = <StatusDot variant="positive" label="positive" />;`);
|
|
94
|
+
|
|
95
|
+
expect(output).toContain("variant='success'");
|
|
96
|
+
expect(output).toContain('label="positive"');
|
|
146
97
|
});
|
|
147
98
|
|
|
148
|
-
it('
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
import { XDSBadge } from '@xds/core/Badge';
|
|
153
|
-
const a = <XDSStatusDot variant="info" label="Status" />;
|
|
154
|
-
const b = <XDSBadge variant="info" label="New" />;`;
|
|
155
|
-
const output = await applyTransform(input);
|
|
156
|
-
// StatusDot's info -> accent (precise), Badge's info stays.
|
|
157
|
-
expect(output).toContain("<XDSStatusDot variant='accent'");
|
|
158
|
-
expect(output).toContain('<XDSBadge variant="info"');
|
|
99
|
+
it('ignores same-named components imported from other packages', async () => {
|
|
100
|
+
const input = `import {StatusDot} from '@other/core';
|
|
101
|
+
const view = <StatusDot variant="positive" />;`;
|
|
102
|
+
expect(await applyTransform(input)).toBe(input);
|
|
159
103
|
});
|
|
160
104
|
|
|
161
|
-
it('
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
expect(output).toContain('
|
|
105
|
+
it('ignores a local component that shadows an imported alias', async () => {
|
|
106
|
+
const output = await applyTransform(`import {StatusDot as Dot} from '@xds/core';
|
|
107
|
+
const migrated = <Dot variant="positive" />;
|
|
108
|
+
function Local(Dot) { return <Dot variant="negative" />; }`);
|
|
109
|
+
|
|
110
|
+
expect(output).toContain("variant='success'");
|
|
111
|
+
expect(output).toContain('variant="negative"');
|
|
167
112
|
});
|
|
168
113
|
|
|
169
|
-
it('returns undefined when no
|
|
170
|
-
const {default: transform} = await import(
|
|
171
|
-
'../rename-status-variants.mjs'
|
|
172
|
-
);
|
|
114
|
+
it('returns undefined when no direct prop needs a change', async () => {
|
|
173
115
|
const jscodeshift = (await import('jscodeshift')).default;
|
|
174
116
|
const j = jscodeshift.withParser('tsx');
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
import { XDSIcon } from '@xds/core/Icon';
|
|
184
|
-
|
|
185
|
-
function StatusIndicator({ isOnline }: { isOnline: boolean }) {
|
|
186
|
-
return (
|
|
187
|
-
<div>
|
|
188
|
-
<XDSStatusDot
|
|
189
|
-
variant={isOnline ? 'positive' : 'negative'}
|
|
190
|
-
label={isOnline ? 'Online' : 'Offline'}
|
|
191
|
-
/>
|
|
192
|
-
<XDSIcon
|
|
193
|
-
icon={isOnline ? CheckIcon : XIcon}
|
|
194
|
-
color={isOnline ? 'positive' : 'negative'}
|
|
195
|
-
/>
|
|
196
|
-
</div>
|
|
197
|
-
);
|
|
198
|
-
}`;
|
|
199
|
-
const output = await applyTransform(input);
|
|
200
|
-
expect(output).toContain("'success'");
|
|
201
|
-
expect(output).toContain("'error'");
|
|
202
|
-
expect(output).not.toContain("'positive'");
|
|
203
|
-
expect(output).not.toContain("'negative'");
|
|
117
|
+
const source = `import {StatusDot} from '@astryxdesign/core';
|
|
118
|
+
const view = <StatusDot variant="success" />;`;
|
|
119
|
+
expect(
|
|
120
|
+
transform(
|
|
121
|
+
{source, path: 'test.tsx'},
|
|
122
|
+
{jscodeshift: j, stats: () => {}, report: () => {}},
|
|
123
|
+
),
|
|
124
|
+
).toBeUndefined();
|
|
204
125
|
});
|
|
205
126
|
});
|
|
@@ -4,86 +4,81 @@
|
|
|
4
4
|
* @file Codemod: Rename status variants positive/negative → success/error
|
|
5
5
|
* @see https://github.com/facebookexperimental/xds/issues/996
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Affected components:
|
|
13
|
-
* - XDSStatusDot: variant="positive|negative|info" → variant="success|error|accent"
|
|
14
|
-
* - XDSAvatarStatusDot: variant="positive|negative" → variant="success|error"
|
|
15
|
-
* - XDSIcon: color="positive|negative" → color="success|error"
|
|
16
|
-
* - XDSProgressBar: variant="positive|negative" → variant="success|error"
|
|
17
|
-
*
|
|
18
|
-
* Transforms JSX attributes, object properties (in files importing affected
|
|
19
|
-
* components), TypeScript type annotations, and Storybook argType options.
|
|
7
|
+
* This transform is deliberately limited to static literals written directly
|
|
8
|
+
* in affected props on imported JSX components. It does not infer through
|
|
9
|
+
* variables, helpers, objects, types, or wrappers: missing an indirect
|
|
10
|
+
* migration is safer than rewriting an unrelated domain using the same words.
|
|
20
11
|
*/
|
|
21
12
|
|
|
13
|
+
import {transformProp} from '../../transform-prop.mjs';
|
|
14
|
+
|
|
22
15
|
export const meta = {
|
|
23
16
|
title: 'Rename status variants positive/negative → success/error',
|
|
24
17
|
description:
|
|
25
|
-
'Renames
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'components (where the component is known); in context-blind locations (object ' +
|
|
29
|
-
'properties, type unions) `info` is left untouched because it is a valid variant on ' +
|
|
30
|
-
'other components such as Badge.',
|
|
18
|
+
'Renames static status values written directly on imported StatusDot, ' +
|
|
19
|
+
'AvatarStatusDot, Icon, SVGIcon, ProgressBar, and Badge JSX elements. ' +
|
|
20
|
+
'Indirect values are left unchanged for manual migration.',
|
|
31
21
|
pr: '#996',
|
|
32
22
|
};
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
const CORE_IMPORT_RE = /^@(astryxdesign|xds)\/core(?:\/|$)/;
|
|
25
|
+
|
|
26
|
+
const COMPONENT_PROPS = new Map([
|
|
27
|
+
['StatusDot', new Set(['variant'])],
|
|
28
|
+
['AvatarStatusDot', new Set(['variant'])],
|
|
29
|
+
['Icon', new Set(['color'])],
|
|
30
|
+
['SVGIcon', new Set(['color'])],
|
|
31
|
+
['ProgressBar', new Set(['variant'])],
|
|
32
|
+
['Badge', new Set(['variant'])],
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const STATUS_RENAMES = new Map([
|
|
36
36
|
['positive', 'success'],
|
|
37
37
|
['negative', 'error'],
|
|
38
38
|
['info', 'accent'],
|
|
39
39
|
]);
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* `info` is a VALID variant on non-target components (notably XDSBadge, whose
|
|
45
|
-
* BadgeVariantMap still has `info`). `positive`/`negative` are not retained by
|
|
46
|
-
* any component post-migration, so renaming them is always safe. Therefore the
|
|
47
|
-
* context-blind heuristic paths (object properties, type unions, and suffixed
|
|
48
|
-
* props on wrapper components) must NOT rewrite `info` — only the precise path,
|
|
49
|
-
* a direct JSX attribute on a known target component, may. Otherwise a
|
|
50
|
-
* `variant: 'info'` on an unrelated Badge config in a file that merely also
|
|
51
|
-
* imports e.g. XDSIcon gets wrongly rewritten to the invalid `accent`.
|
|
52
|
-
*/
|
|
53
|
-
const AMBIGUOUS_VALUES = new Set(['info']);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Components and which props are affected.
|
|
58
|
-
* Key: component name. Value: set of prop names to check.
|
|
59
|
-
*/
|
|
60
|
-
const COMPONENT_PROPS = new Map([
|
|
61
|
-
['XDSStatusDot', new Set(['variant'])],
|
|
62
|
-
['XDSAvatarStatusDot', new Set(['variant'])],
|
|
63
|
-
['XDSIcon', new Set(['color'])],
|
|
64
|
-
['XDSProgressBar', new Set(['variant'])],
|
|
41
|
+
const BADGE_RENAMES = new Map([
|
|
42
|
+
['positive', 'success'],
|
|
43
|
+
['negative', 'error'],
|
|
65
44
|
]);
|
|
66
45
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/** All affected prop names (variant, color). */
|
|
71
|
-
const TARGET_PROP_NAMES = new Set();
|
|
72
|
-
for (const props of COMPONENT_PROPS.values()) {
|
|
73
|
-
for (const p of props) TARGET_PROP_NAMES.add(p);
|
|
46
|
+
/** @param {string} name */
|
|
47
|
+
function canonicalComponentName(name) {
|
|
48
|
+
return name.startsWith('XDS') ? name.slice(3) : name;
|
|
74
49
|
}
|
|
75
50
|
|
|
76
51
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
52
|
+
* @param {any} node
|
|
53
|
+
* @param {ReadonlyMap<string, string>} renames
|
|
54
|
+
* @returns {boolean}
|
|
79
55
|
*/
|
|
80
|
-
function
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
56
|
+
function renameStaticValue(node, renames) {
|
|
57
|
+
if (!node) return false;
|
|
58
|
+
if (
|
|
59
|
+
node.type === 'TSAsExpression' ||
|
|
60
|
+
node.type === 'TSSatisfiesExpression' ||
|
|
61
|
+
node.type === 'TypeCastExpression' ||
|
|
62
|
+
node.type === 'ParenthesizedExpression'
|
|
63
|
+
) {
|
|
64
|
+
return renameStaticValue(node.expression, renames);
|
|
85
65
|
}
|
|
86
|
-
|
|
66
|
+
if (node.type === 'ConditionalExpression') {
|
|
67
|
+
const consequentChanged = renameStaticValue(node.consequent, renames);
|
|
68
|
+
const alternateChanged = renameStaticValue(node.alternate, renames);
|
|
69
|
+
return consequentChanged || alternateChanged;
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
(node.type !== 'StringLiteral' && node.type !== 'Literal') ||
|
|
73
|
+
typeof node.value !== 'string'
|
|
74
|
+
) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const replacement = renames.get(node.value);
|
|
78
|
+
if (!replacement) return false;
|
|
79
|
+
node.value = replacement;
|
|
80
|
+
if (node.raw) node.raw = undefined;
|
|
81
|
+
return true;
|
|
87
82
|
}
|
|
88
83
|
|
|
89
84
|
/**
|
|
@@ -96,158 +91,25 @@ export default function transformer(file, api) {
|
|
|
96
91
|
const root = j(file.source);
|
|
97
92
|
let hasChanges = false;
|
|
98
93
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Ambiguous values (e.g. `info`) are only renamed in a precise context
|
|
118
|
-
// where the target component is known (a direct JSX attribute on a target).
|
|
119
|
-
// In context-blind paths they may belong to a component that legitimately
|
|
120
|
-
// keeps the value (e.g. Badge's `info`), so leave them untouched.
|
|
121
|
-
if (!precise && AMBIGUOUS_VALUES.has(target.value)) return false;
|
|
122
|
-
|
|
123
|
-
target.value = replacement;
|
|
124
|
-
if (target.raw) target.raw = undefined;
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Rename elements in an array (handles TSAsExpression wrapping).
|
|
130
|
-
*/
|
|
131
|
-
function renameArrayElements(/** @type {any} */ node, {precise = false} = {}) {
|
|
132
|
-
let arrayNode = node;
|
|
133
|
-
if (arrayNode.type === 'TSAsExpression') arrayNode = arrayNode.expression;
|
|
134
|
-
if (arrayNode.type !== 'ArrayExpression') return;
|
|
135
|
-
arrayNode.elements.forEach((/** @type {any} */ el) => {
|
|
136
|
-
if (renameValue(el, {precise})) hasChanges = true;
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Determine early: does this file import any target component?
|
|
141
|
-
const importsTarget = [...TARGET_COMPONENTS].some(
|
|
142
|
-
(name) =>
|
|
143
|
-
root.find(j.ImportSpecifier, {imported: {name}}).length > 0,
|
|
94
|
+
transformProp(
|
|
95
|
+
root,
|
|
96
|
+
j,
|
|
97
|
+
{
|
|
98
|
+
matchesImport: source => CORE_IMPORT_RE.test(source),
|
|
99
|
+
components: COMPONENT_PROPS,
|
|
100
|
+
normalizeComponentName: canonicalComponentName,
|
|
101
|
+
},
|
|
102
|
+
(propPath, {component}) => {
|
|
103
|
+
const prop = propPath.node;
|
|
104
|
+
const value =
|
|
105
|
+
prop.value?.type === 'JSXExpressionContainer'
|
|
106
|
+
? prop.value.expression
|
|
107
|
+
: prop.value;
|
|
108
|
+
const renames = component === 'Badge' ? BADGE_RENAMES : STATUS_RENAMES;
|
|
109
|
+
if (renameStaticValue(value, renames)) hasChanges = true;
|
|
110
|
+
},
|
|
144
111
|
);
|
|
145
112
|
|
|
146
|
-
// 1. JSX attributes on target components
|
|
147
|
-
root.find(j.JSXOpeningElement).forEach((/** @type {any} */ path) => {
|
|
148
|
-
const name = path.node.name;
|
|
149
|
-
const componentName = name.type === 'JSXIdentifier' ? name.name : null;
|
|
150
|
-
if (!componentName) return;
|
|
151
|
-
|
|
152
|
-
const isDirectTarget = TARGET_COMPONENTS.has(componentName);
|
|
153
|
-
// For non-target components, only check suffixed props if file imports targets
|
|
154
|
-
if (!isDirectTarget && !importsTarget) return;
|
|
155
|
-
|
|
156
|
-
path.node.attributes.forEach((/** @type {any} */ attr) => {
|
|
157
|
-
if (attr.type !== 'JSXAttribute') return;
|
|
158
|
-
const attrName = attr.name.name;
|
|
159
|
-
|
|
160
|
-
if (isDirectTarget) {
|
|
161
|
-
// Direct targets: only their specific props
|
|
162
|
-
const targetProps = COMPONENT_PROPS.get(componentName);
|
|
163
|
-
if (!targetProps || !targetProps.has(attrName)) return;
|
|
164
|
-
} else {
|
|
165
|
-
// Non-target components: suffixed prop names only
|
|
166
|
-
// (e.g. dotVariant on ChangelogSection wrapping XDSStatusDot)
|
|
167
|
-
if (!matchesPropName(attrName) || TARGET_PROP_NAMES.has(attrName)) return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Only a direct JSX attribute on a target component is a precise context
|
|
171
|
-
// where the component (and thus the validity of `info`) is known. The
|
|
172
|
-
// wrapper/suffixed-prop case is a heuristic and must not touch ambiguous
|
|
173
|
-
// values.
|
|
174
|
-
const precise = isDirectTarget;
|
|
175
|
-
const value = attr.value;
|
|
176
|
-
|
|
177
|
-
// variant="positive" (string literal)
|
|
178
|
-
if (renameValue(value, {precise})) {
|
|
179
|
-
hasChanges = true;
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// variant={'positive'} (expression container)
|
|
184
|
-
if (
|
|
185
|
-
value &&
|
|
186
|
-
value.type === 'JSXExpressionContainer' &&
|
|
187
|
-
value.expression
|
|
188
|
-
) {
|
|
189
|
-
if (renameValue(value.expression, {precise})) {
|
|
190
|
-
hasChanges = true;
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// variant={condition ? 'positive' : 'negative'} (ternary)
|
|
195
|
-
if (value.expression.type === 'ConditionalExpression') {
|
|
196
|
-
if (renameValue(value.expression.consequent, {precise}))
|
|
197
|
-
hasChanges = true;
|
|
198
|
-
if (renameValue(value.expression.alternate, {precise}))
|
|
199
|
-
hasChanges = true;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// 2. Object properties in files importing target components.
|
|
206
|
-
// Handles args objects, config objects, Storybook argTypes, etc.
|
|
207
|
-
if (importsTarget) {
|
|
208
|
-
const PropertyType = j.ObjectProperty ?? j.Property;
|
|
209
|
-
root.find(PropertyType).forEach((/** @type {any} */ path) => {
|
|
210
|
-
const key = path.node.key;
|
|
211
|
-
const keyName =
|
|
212
|
-
key.type === 'Identifier'
|
|
213
|
-
? key.name
|
|
214
|
-
: key.type === 'StringLiteral' || key.type === 'Literal'
|
|
215
|
-
? key.value
|
|
216
|
-
: null;
|
|
217
|
-
|
|
218
|
-
if (!keyName || !matchesPropName(keyName)) return;
|
|
219
|
-
|
|
220
|
-
const value = path.node.value;
|
|
221
|
-
|
|
222
|
-
// variant: 'positive' → variant: 'success'
|
|
223
|
-
if (renameValue(value)) {
|
|
224
|
-
hasChanges = true;
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// variant: { options: ['positive', 'negative', ...] }
|
|
229
|
-
if (value.type === 'ObjectExpression') {
|
|
230
|
-
const optionsProp = value.properties.find(
|
|
231
|
-
(/** @type {any} */ p) => p.key && (p.key.name === 'options' || p.key.value === 'options'),
|
|
232
|
-
);
|
|
233
|
-
if (optionsProp) {
|
|
234
|
-
renameArrayElements(optionsProp.value);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// variant: ['positive', 'negative'] or [...] as Type[]
|
|
239
|
-
renameArrayElements(value);
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// 3. TypeScript type references: 'positive' | 'negative' in union types
|
|
244
|
-
if (importsTarget) {
|
|
245
|
-
root.find(j.TSLiteralType).forEach((/** @type {any} */ path) => {
|
|
246
|
-
const lit = path.node.literal;
|
|
247
|
-
if (renameValue(lit)) hasChanges = true;
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
113
|
if (!hasChanges) return undefined;
|
|
252
114
|
return root.toSource({quote: 'single'});
|
|
253
115
|
}
|