@astryxdesign/cli 0.1.7-canary.cecb773 → 0.1.7-canary.d91c851
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/package.json +9 -9
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +42 -7
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +43 -0
- package/src/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +102 -3
- package/src/codemods/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +72 -6
- package/src/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +161 -0
- package/src/codemods/transforms/v0.1.8/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +234 -0
- package/src/commands/agent-docs.mjs +72 -0
- package/src/commands/agent-docs.test.mjs +86 -0
- package/src/commands/upgrade.mjs +130 -27
- package/src/commands/upgrade.test.mjs +154 -0
- package/src/types/upgrade.d.ts +39 -2
- package/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +4 -4
- package/templates/blocks/components/Avatar/AvatarGroup.tsx +2 -2
- package/templates/blocks/components/Avatar/AvatarInitialsFallback.tsx +1 -1
- package/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -4
- package/templates/blocks/components/Avatar/AvatarUserCard.tsx +1 -1
- package/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
- package/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
- package/templates/blocks/components/AvatarGroup/AvatarGroupShowcase.tsx +2 -2
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowCustomText.tsx +1 -1
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowDefault.tsx +1 -1
- package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowShowcase.tsx +2 -2
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotShowcase.tsx +3 -3
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +3 -3
- package/templates/blocks/components/Carousel/CarouselSnap.tsx +1 -1
- package/templates/blocks/components/ChatMessage/ChatMessageAvatarName.tsx +2 -2
- package/templates/blocks/components/ChatMessage/ChatMessageMultiBubble.tsx +1 -1
- package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleGrouping.tsx +1 -1
- package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleMetadata.tsx +1 -1
- package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +5 -9
- package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -1
- package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -1
- package/templates/blocks/components/Item/ItemWithMedia.tsx +2 -2
- package/templates/blocks/components/ListItem/ListItemWithMedia.tsx +2 -2
- package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -6
- package/templates/blocks/components/Stack/StackFillItem.tsx +2 -6
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemShowcase.tsx +1 -1
- package/templates/pages/ai-chat/page.tsx +4 -4
- package/templates/pages/dashboard-portfolio/page.tsx +3 -11
- package/templates/pages/detail-page/page.tsx +5 -12
- package/templates/pages/documentation-design/page.tsx +1 -1
- package/templates/pages/messaging-shell/page.tsx +6 -8
- package/templates/pages/table-grouped/page.tsx +9 -14
- package/templates/pages/table-page/page.tsx +7 -9
- package/templates/pages/table-page-heatmap-status/page.tsx +5 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.7-canary.
|
|
3
|
+
"version": "0.1.7-canary.d91c851",
|
|
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",
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"zod": "^4.4.3"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@astryxdesign/charts": "0.1.7-canary.
|
|
83
|
-
"@astryxdesign/core": "0.1.7-canary.
|
|
84
|
-
"@astryxdesign/lab": "0.1.7-canary.
|
|
85
|
-
"@astryxdesign/theme-neutral": "0.1.7-canary.
|
|
82
|
+
"@astryxdesign/charts": "0.1.7-canary.d91c851",
|
|
83
|
+
"@astryxdesign/core": "0.1.7-canary.d91c851",
|
|
84
|
+
"@astryxdesign/lab": "0.1.7-canary.d91c851",
|
|
85
|
+
"@astryxdesign/theme-neutral": "0.1.7-canary.d91c851",
|
|
86
86
|
"gpt-tokenizer": "^3.4.0"
|
|
87
87
|
},
|
|
88
88
|
"peerDependenciesMeta": {
|
|
@@ -100,10 +100,10 @@
|
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
|
-
"@astryxdesign/charts": "0.1.7-canary.
|
|
104
|
-
"@astryxdesign/core": "0.1.7-canary.
|
|
105
|
-
"@astryxdesign/lab": "0.1.7-canary.
|
|
106
|
-
"@astryxdesign/theme-neutral": "0.1.7-canary.
|
|
103
|
+
"@astryxdesign/charts": "0.1.7-canary.d91c851",
|
|
104
|
+
"@astryxdesign/core": "0.1.7-canary.d91c851",
|
|
105
|
+
"@astryxdesign/lab": "0.1.7-canary.d91c851",
|
|
106
|
+
"@astryxdesign/theme-neutral": "0.1.7-canary.d91c851",
|
|
107
107
|
"gpt-tokenizer": "^3.4.0"
|
|
108
108
|
},
|
|
109
109
|
"scripts": {
|
|
@@ -22,6 +22,7 @@ const registry = new Map([
|
|
|
22
22
|
['0.1.3', () => import('./transforms/v0.1.3/index.mjs')],
|
|
23
23
|
['0.1.5', () => import('./transforms/v0.1.5/index.mjs')],
|
|
24
24
|
['0.1.7', () => import('./transforms/v0.1.7/index.mjs')],
|
|
25
|
+
['0.1.8', () => import('./transforms/v0.1.8/index.mjs')],
|
|
25
26
|
]);
|
|
26
27
|
|
|
27
28
|
// Re-export from the shared utility so registry callers and other consumers
|
|
@@ -166,15 +166,50 @@ describe('drop-xds-prefix-imports', () => {
|
|
|
166
166
|
expect(output).toContain('export const other = Card;');
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
-
it('un-prefixes
|
|
169
|
+
it('un-prefixes override keys inside an @xds/core mock factory', async () => {
|
|
170
170
|
const input = [
|
|
171
|
-
`
|
|
172
|
-
`
|
|
171
|
+
`vi.mock('@xds/core/Text', async orig => ({`,
|
|
172
|
+
` ...(await orig<typeof import('@xds/core/Text')>()),`,
|
|
173
|
+
` useXDSTruncation: () => ({ref: vi.fn(), isTruncated: true, fullText: ''}),`,
|
|
174
|
+
`}));`,
|
|
173
175
|
].join('\n');
|
|
174
176
|
const output = await applyTransform(input);
|
|
175
|
-
expect(output).toContain('
|
|
176
|
-
expect(output).toContain('
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
expect(output).toContain('useTruncation:');
|
|
178
|
+
expect(output).not.toContain('useXDSTruncation');
|
|
179
|
+
// This codemod does not touch the module-path string (that is the
|
|
180
|
+
// module-specifiers codemod's job); the @xds/core/Text path stays here.
|
|
181
|
+
expect(output).toContain(`vi.mock('@xds/core/Text'`);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('un-prefixes component override keys inside a bare @xds/core jest.mock factory', async () => {
|
|
185
|
+
const input = [
|
|
186
|
+
`jest.mock('@xds/core', () => ({`,
|
|
187
|
+
` XDSButton: () => null,`,
|
|
188
|
+
` useXDSToast: () => ({}),`,
|
|
189
|
+
`}));`,
|
|
190
|
+
].join('\n');
|
|
191
|
+
const output = await applyTransform(input);
|
|
192
|
+
expect(output).toContain('Button:');
|
|
193
|
+
expect(output).toContain('useToast:');
|
|
194
|
+
expect(output).not.toContain('XDSButton');
|
|
195
|
+
expect(output).not.toContain('useXDSToast');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('does NOT touch mock factory keys for a non-@xds package', async () => {
|
|
199
|
+
const input = [
|
|
200
|
+
`vi.mock('some-other-pkg', () => ({`,
|
|
201
|
+
` useXDSFoo: () => 1,`,
|
|
202
|
+
`}));`,
|
|
203
|
+
].join('\n');
|
|
204
|
+
const output = await applyTransform(input);
|
|
205
|
+
expect(output).toBe(input);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('does NOT touch useXDS keys in an unrelated object literal (not a mock factory)', async () => {
|
|
209
|
+
const input = [`const config = {`, ` useXDSWhatever: true,`, `};`].join(
|
|
210
|
+
'\n',
|
|
211
|
+
);
|
|
212
|
+
const output = await applyTransform(input);
|
|
213
|
+
expect(output).toBe(input);
|
|
179
214
|
});
|
|
180
215
|
});
|
|
@@ -89,4 +89,47 @@ describe('migrate-xds-module-specifiers', () => {
|
|
|
89
89
|
expect(output).not.toContain('defaultTheme');
|
|
90
90
|
expect(output).not.toContain('neutralTheme as');
|
|
91
91
|
});
|
|
92
|
+
|
|
93
|
+
it('rewrites the mocked module path in vi.mock (including the inline import() type arg)', async () => {
|
|
94
|
+
const input = [
|
|
95
|
+
"vi.mock('@xds/core/Text', async orig => ({",
|
|
96
|
+
" ...(await orig<typeof import('@xds/core/Text')>()),",
|
|
97
|
+
'}));',
|
|
98
|
+
].join('\n');
|
|
99
|
+
const output = await applyTransform(input);
|
|
100
|
+
expect(output).toMatch(/vi\.mock\(['"]@astryxdesign\/core\/Text['"]/);
|
|
101
|
+
// The inline dynamic import() type argument is rewritten too.
|
|
102
|
+
expect(output).toMatch(/import\(['"]@astryxdesign\/core\/Text['"]\)/);
|
|
103
|
+
expect(output).not.toContain('@xds/');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('rewrites the mocked module path in jest.mock (bare @xds/core)', async () => {
|
|
107
|
+
const output = await applyTransform(
|
|
108
|
+
"jest.mock('@xds/core', () => ({}));",
|
|
109
|
+
'test.ts',
|
|
110
|
+
);
|
|
111
|
+
expect(output).toMatch(/jest\.mock\(['"]@astryxdesign\/core['"]/);
|
|
112
|
+
expect(output).not.toContain('@xds/');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('rewrites vi.doMock / jest.doMock and a bare mock() call', async () => {
|
|
116
|
+
const output = await applyTransform(
|
|
117
|
+
[
|
|
118
|
+
"vi.doMock('@xds/lab/Thing', () => ({}));",
|
|
119
|
+
"jest.doMock('@xds/core/Text', () => ({}));",
|
|
120
|
+
"mock('@xds/core', () => ({}));",
|
|
121
|
+
].join('\n'),
|
|
122
|
+
'test.ts',
|
|
123
|
+
);
|
|
124
|
+
expect(output).toMatch(/vi\.doMock\(['"]@astryxdesign\/lab\/Thing['"]/);
|
|
125
|
+
expect(output).toMatch(/jest\.doMock\(['"]@astryxdesign\/core\/Text['"]/);
|
|
126
|
+
expect(output).toMatch(/[^.]mock\(['"]@astryxdesign\/core['"]/);
|
|
127
|
+
expect(output).not.toContain('@xds/');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('does NOT rewrite the mock path for a non-@xds package', async () => {
|
|
131
|
+
const input = "vi.mock('some-other-pkg', () => ({useXDSFoo: () => 1}));";
|
|
132
|
+
const output = await applyTransform(input);
|
|
133
|
+
expect(output).toBe(input);
|
|
134
|
+
});
|
|
92
135
|
});
|
|
@@ -35,11 +35,19 @@
|
|
|
35
35
|
* including type references in generic type-argument positions such as
|
|
36
36
|
* `useMemo<XDSTableColumn<Issue>[]>(...)`
|
|
37
37
|
* 3. Re-exports from @xds/core: export {XDSButton} from '@xds/core/Button'
|
|
38
|
+
* 4. Object-property keys inside a test-framework mock factory for an
|
|
39
|
+
* `@xds/core` module: the partial-mock override in
|
|
40
|
+
* `vi.mock('@xds/core/Text', async orig => ({...await orig(),
|
|
41
|
+
* useXDSTruncation: () => ...}))` must be un-prefixed to `useTruncation`,
|
|
42
|
+
* otherwise the key targets a name the renamed module no longer exports and
|
|
43
|
+
* the mock silently overrides nothing.
|
|
38
44
|
*
|
|
39
45
|
* Does NOT touch:
|
|
40
46
|
* - import source paths (`@xds/core/Button` stays -- subpath dirs are unchanged
|
|
41
47
|
* by the prefix migration)
|
|
42
48
|
* - identifiers not bound to an @xds/core import
|
|
49
|
+
* - object-property keys outside an `@xds/core` mock factory (an unrelated
|
|
50
|
+
* `{useXDSFoo: ...}` literal, or a mock of some other package, is untouched)
|
|
43
51
|
* - the `XDSBaseProps`-style names only when they are NOT imported from core
|
|
44
52
|
*/
|
|
45
53
|
|
|
@@ -48,7 +56,8 @@ export const meta = {
|
|
|
48
56
|
description:
|
|
49
57
|
'Rewrites @xds/core imports and their usages from the prefixed names ' +
|
|
50
58
|
'(XDSButton, useXDSTheme, XDSIconRegistry, XDSButtonProps) to their bare ' +
|
|
51
|
-
'names (Button, useTheme, IconRegistry, ButtonProps)
|
|
59
|
+
'names (Button, useTheme, IconRegistry, ButtonProps), including override ' +
|
|
60
|
+
'keys in a vi.mock/jest.mock factory for an @xds/core module. Only renames ' +
|
|
52
61
|
'bindings imported from @xds/core, leaving unrelated identifiers untouched.',
|
|
53
62
|
pr: '#2880',
|
|
54
63
|
};
|
|
@@ -74,6 +83,81 @@ function bareName(name) {
|
|
|
74
83
|
return null;
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
// A test-framework mock of an `@xds/core` module (`vi.mock('@xds/core/Text',
|
|
87
|
+
// factory)`). We only rewrite override keys inside such a factory, so scope the
|
|
88
|
+
// detection tightly: callee must be `vi.mock`/`vi.doMock`/`jest.mock`/
|
|
89
|
+
// `jest.doMock` (or a bare `mock`) AND the first argument must be a string
|
|
90
|
+
// literal that resolves to `@xds/core` (bare or subpath).
|
|
91
|
+
const MOCK_METHOD_NAMES = new Set(['mock', 'doMock']);
|
|
92
|
+
const MOCK_OBJECT_NAMES = new Set(['vi', 'jest']);
|
|
93
|
+
|
|
94
|
+
function isMockCallee(callee) {
|
|
95
|
+
if (
|
|
96
|
+
callee.type === 'MemberExpression' &&
|
|
97
|
+
!callee.computed &&
|
|
98
|
+
callee.object.type === 'Identifier' &&
|
|
99
|
+
MOCK_OBJECT_NAMES.has(callee.object.name) &&
|
|
100
|
+
callee.property.type === 'Identifier' &&
|
|
101
|
+
MOCK_METHOD_NAMES.has(callee.property.name)
|
|
102
|
+
) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return callee.type === 'Identifier' && callee.name === 'mock';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isXdsCoreMockCall(node) {
|
|
109
|
+
if (node.type !== 'CallExpression' || !isMockCallee(node.callee))
|
|
110
|
+
return false;
|
|
111
|
+
const [arg] = node.arguments;
|
|
112
|
+
const value =
|
|
113
|
+
arg && (arg.type === 'StringLiteral' || arg.type === 'Literal')
|
|
114
|
+
? arg.value
|
|
115
|
+
: null;
|
|
116
|
+
return typeof value === 'string' && XDS_CORE_SOURCE.test(value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Rename XDS-prefixed object-property KEYS (not values, not nested objects)
|
|
120
|
+
// within the given AST subtree. Used only on a recognized @xds/core mock
|
|
121
|
+
// factory, so the scope guard lives at the call site.
|
|
122
|
+
function renameMockFactoryKeys(node, seen, onChange) {
|
|
123
|
+
if (!node || typeof node !== 'object') return;
|
|
124
|
+
if (Array.isArray(node)) {
|
|
125
|
+
for (const child of node) renameMockFactoryKeys(child, seen, onChange);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (seen.has(node)) return;
|
|
129
|
+
seen.add(node);
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
(node.type === 'ObjectProperty' || node.type === 'Property') &&
|
|
133
|
+
!node.computed &&
|
|
134
|
+
node.key &&
|
|
135
|
+
node.key.type === 'Identifier'
|
|
136
|
+
) {
|
|
137
|
+
const bare = bareName(node.key.name);
|
|
138
|
+
if (bare && bare !== node.key.name) {
|
|
139
|
+
node.key.name = bare;
|
|
140
|
+
onChange();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const key of Object.keys(node)) {
|
|
145
|
+
if (
|
|
146
|
+
key === 'loc' ||
|
|
147
|
+
key === 'start' ||
|
|
148
|
+
key === 'end' ||
|
|
149
|
+
key === 'range' ||
|
|
150
|
+
key === 'comments' ||
|
|
151
|
+
key === 'leadingComments' ||
|
|
152
|
+
key === 'trailingComments' ||
|
|
153
|
+
key === 'tokens'
|
|
154
|
+
) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
renameMockFactoryKeys(node[key], seen, onChange);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
77
161
|
export default function transformer(file, api) {
|
|
78
162
|
const j = api.jscodeshift;
|
|
79
163
|
const root = j(file.source);
|
|
@@ -190,11 +274,26 @@ export default function transformer(file, api) {
|
|
|
190
274
|
}
|
|
191
275
|
});
|
|
192
276
|
|
|
277
|
+
// 3. Un-prefix override keys inside @xds/core mock factories. Independent of
|
|
278
|
+
// the import bindings above: the override key is a factory object property,
|
|
279
|
+
// not a reference to an imported binding, so it must match the renamed export
|
|
280
|
+
// name of the mocked module regardless of what the file imports.
|
|
281
|
+
const seenMockNodes = new Set();
|
|
282
|
+
root.find(j.CallExpression).forEach(path => {
|
|
283
|
+
if (!isXdsCoreMockCall(path.node)) return;
|
|
284
|
+
// Only walk the factory argument(s), never the module-path string.
|
|
285
|
+
for (const arg of path.node.arguments.slice(1)) {
|
|
286
|
+
renameMockFactoryKeys(arg, seenMockNodes, () => {
|
|
287
|
+
hasChanges = true;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
193
292
|
if (localRenames.size === 0) {
|
|
194
293
|
return hasChanges ? root.toSource() : undefined;
|
|
195
294
|
}
|
|
196
295
|
|
|
197
|
-
//
|
|
296
|
+
// 4. Rename references to renamed local bindings
|
|
198
297
|
// Identifiers (type refs, value refs, etc.)
|
|
199
298
|
root.find(j.Identifier).forEach(path => {
|
|
200
299
|
const newName = localRenames.get(path.node.name);
|
|
@@ -231,7 +330,7 @@ export default function transformer(file, api) {
|
|
|
231
330
|
}
|
|
232
331
|
});
|
|
233
332
|
|
|
234
|
-
//
|
|
333
|
+
// 5. Rename TS type references in generic type-argument positions.
|
|
235
334
|
//
|
|
236
335
|
// `j.find(j.Identifier)` (and `j.find(j.TSTypeReference)`) do NOT visit
|
|
237
336
|
// identifiers nested inside generic type arguments with the tsx/babel parser
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
export const meta = {
|
|
13
13
|
title: 'Migrate module specifiers from @xds/* to @astryxdesign/*',
|
|
14
14
|
description:
|
|
15
|
-
'Updates JS/TS import, export, dynamic import,
|
|
16
|
-
'
|
|
15
|
+
'Updates JS/TS import, export, dynamic import, require, TS import-type, ' +
|
|
16
|
+
'and test-framework mock (vi.mock / jest.mock) module specifiers from ' +
|
|
17
|
+
'@xds/* to the @astryxdesign/* packages used by Astryx v0.1.0.',
|
|
17
18
|
pr: '#3092',
|
|
18
19
|
fileExtensions: ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'],
|
|
19
20
|
};
|
|
@@ -73,6 +74,30 @@ function isCollapsedThemeSource(value) {
|
|
|
73
74
|
return false;
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
// Test-framework mock calls take the mocked module path as their first
|
|
78
|
+
// argument (`vi.mock('@xds/core/Text', factory)`). That path is a plain string
|
|
79
|
+
// literal -- not an ImportDeclaration source -- so it slips past the import/
|
|
80
|
+
// export handlers and, left unrewritten, the mock would target the stale
|
|
81
|
+
// `@xds/*` path and stop intercepting the renamed `@astryxdesign/*` import.
|
|
82
|
+
// Recognized callees: `vi.mock`, `vi.doMock`, `jest.mock`, `jest.doMock`, and
|
|
83
|
+
// a bare `mock(...)` (destructured from the test runner).
|
|
84
|
+
const MOCK_METHOD_NAMES = new Set(['mock', 'doMock']);
|
|
85
|
+
const MOCK_OBJECT_NAMES = new Set(['vi', 'jest']);
|
|
86
|
+
|
|
87
|
+
function isMockCall(callee) {
|
|
88
|
+
if (
|
|
89
|
+
callee.type === 'MemberExpression' &&
|
|
90
|
+
!callee.computed &&
|
|
91
|
+
callee.object.type === 'Identifier' &&
|
|
92
|
+
MOCK_OBJECT_NAMES.has(callee.object.name) &&
|
|
93
|
+
callee.property.type === 'Identifier' &&
|
|
94
|
+
MOCK_METHOD_NAMES.has(callee.property.name)
|
|
95
|
+
) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return callee.type === 'Identifier' && callee.name === 'mock';
|
|
99
|
+
}
|
|
100
|
+
|
|
76
101
|
function remapThemeExportNames(importPath, j) {
|
|
77
102
|
let changed = false;
|
|
78
103
|
importPath.node.specifiers = (importPath.node.specifiers || []).map(spec => {
|
|
@@ -118,12 +143,53 @@ export default function transformer(file, api) {
|
|
|
118
143
|
const isRequire =
|
|
119
144
|
path.node.callee.type === 'Identifier' &&
|
|
120
145
|
path.node.callee.name === 'require';
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
if (isDynamicImport || isRequire || isMockCall(path.node.callee)) {
|
|
147
|
+
const [arg] = path.node.arguments;
|
|
148
|
+
if (arg?.type === 'StringLiteral' || arg?.type === 'Literal') {
|
|
149
|
+
hasChanges = rewriteLiteral(arg) || hasChanges;
|
|
150
|
+
}
|
|
125
151
|
}
|
|
126
152
|
});
|
|
127
153
|
|
|
154
|
+
// TS `import(...)` TYPE specifiers -- e.g. the `import('@xds/core/Text')` in
|
|
155
|
+
// `typeof import('@xds/core/Text')` -- parse to a `TSImportType` node, not a
|
|
156
|
+
// dynamic-import CallExpression, so the handler above never reaches them.
|
|
157
|
+
// ast-types' traversal also doesn't descend into a `TSImportType` nested in a
|
|
158
|
+
// generic type-argument position (e.g. `orig<typeof import('@xds/core/Text')>()`),
|
|
159
|
+
// so `j.find(j.TSImportType)` misses it. Walk the raw AST to cover every
|
|
160
|
+
// position. Re-visiting a node the loop above handled is harmless: the
|
|
161
|
+
// literal is already rewritten, so the second pass is a no-op.
|
|
162
|
+
const seenTypeImportNodes = new Set();
|
|
163
|
+
const rewriteTSImportTypes = node => {
|
|
164
|
+
if (!node || typeof node !== 'object') return;
|
|
165
|
+
if (Array.isArray(node)) {
|
|
166
|
+
for (const child of node) rewriteTSImportTypes(child);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (seenTypeImportNodes.has(node)) return;
|
|
170
|
+
seenTypeImportNodes.add(node);
|
|
171
|
+
|
|
172
|
+
if (node.type === 'TSImportType' && node.argument) {
|
|
173
|
+
hasChanges = rewriteLiteral(node.argument) || hasChanges;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const key of Object.keys(node)) {
|
|
177
|
+
if (
|
|
178
|
+
key === 'loc' ||
|
|
179
|
+
key === 'start' ||
|
|
180
|
+
key === 'end' ||
|
|
181
|
+
key === 'range' ||
|
|
182
|
+
key === 'comments' ||
|
|
183
|
+
key === 'leadingComments' ||
|
|
184
|
+
key === 'trailingComments' ||
|
|
185
|
+
key === 'tokens'
|
|
186
|
+
) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
rewriteTSImportTypes(node[key]);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
rewriteTSImportTypes(root.get().node);
|
|
193
|
+
|
|
128
194
|
return hasChanges ? root.toSource() : undefined;
|
|
129
195
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
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} = await import('../rename-avatar-size-scale.mjs');
|
|
7
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
8
|
+
const j = jscodeshift.withParser('tsx');
|
|
9
|
+
const api = {jscodeshift: j, stats: () => {}, report: () => {}};
|
|
10
|
+
const file = {source, path: 'test.tsx'};
|
|
11
|
+
const result = transform(file, api);
|
|
12
|
+
return result ?? source;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('rename-avatar-size-scale', () => {
|
|
16
|
+
it('renames every named size on an Avatar JSX size attribute', async () => {
|
|
17
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
18
|
+
const a = <Avatar name="A" size="tiny" />;
|
|
19
|
+
const b = <Avatar name="B" size="xsmall" />;
|
|
20
|
+
const c = <Avatar name="C" size="small" />;
|
|
21
|
+
const d = <Avatar name="D" size="medium" />;
|
|
22
|
+
const e = <Avatar name="E" size="large" />;`;
|
|
23
|
+
const output = await applyTransform(input);
|
|
24
|
+
expect(output).toContain(`size='xsm'`);
|
|
25
|
+
expect(output).toContain(`size='sm'`);
|
|
26
|
+
expect(output).toContain(`size='md'`);
|
|
27
|
+
expect(output).toContain(`size='lg'`);
|
|
28
|
+
expect(output).toContain(`size='xl'`);
|
|
29
|
+
for (const old of ['tiny', 'xsmall', 'small', 'medium', 'large']) {
|
|
30
|
+
expect(output).not.toContain(`size="${old}"`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renames size on AvatarGroup', async () => {
|
|
35
|
+
const input = `import {AvatarGroup} from '@astryxdesign/core';
|
|
36
|
+
const x = <AvatarGroup size="medium">{kids}</AvatarGroup>;`;
|
|
37
|
+
const output = await applyTransform(input);
|
|
38
|
+
expect(output).toContain(`size='lg'`);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('leaves numeric sizes untouched', async () => {
|
|
42
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
43
|
+
const x = <Avatar name="A" size={48} />;`;
|
|
44
|
+
const output = await applyTransform(input);
|
|
45
|
+
expect(output).toContain('size={48}');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('is alias-aware', async () => {
|
|
49
|
+
const input = `import {Avatar as Face} from '@astryxdesign/core';
|
|
50
|
+
const x = <Face name="A" size="small" />;`;
|
|
51
|
+
const output = await applyTransform(input);
|
|
52
|
+
expect(output).toContain(`size='md'`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renames the subpath and legacy import sources', async () => {
|
|
56
|
+
const sub = `import {Avatar} from '@astryxdesign/core/Avatar';
|
|
57
|
+
const x = <Avatar name="A" size="large" />;`;
|
|
58
|
+
expect(await applyTransform(sub)).toContain(`size='xl'`);
|
|
59
|
+
const legacy = `import {Avatar} from '@xds/core';
|
|
60
|
+
const x = <Avatar name="A" size="tiny" />;`;
|
|
61
|
+
expect(await applyTransform(legacy)).toContain(`size='xsm'`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('renames a size inside a ternary on an Avatar element', async () => {
|
|
65
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
66
|
+
const x = <Avatar name="A" size={big ? 'large' : 'small'} />;`;
|
|
67
|
+
const output = await applyTransform(input);
|
|
68
|
+
expect(output).toContain(`big ? 'xl' : 'md'`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('renames the UNIQUE names tiny/xsmall in Storybook options and size args', async () => {
|
|
72
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
73
|
+
const meta = {
|
|
74
|
+
argTypes: {size: {control: 'select', options: ['tiny', 'xsmall']}},
|
|
75
|
+
args: {size: 'tiny'},
|
|
76
|
+
};`;
|
|
77
|
+
const output = await applyTransform(input);
|
|
78
|
+
expect(output).toContain(`['xsm', 'sm']`);
|
|
79
|
+
expect(output).toContain(`size: 'xsm'`);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('renames a FULL Storybook options array (unique name unlocks ambiguous members)', async () => {
|
|
83
|
+
// The presence of a unique name (tiny/xsmall) proves the whole array is the
|
|
84
|
+
// Avatar size enum, so small/medium/large in it are safe to rename too.
|
|
85
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
86
|
+
const meta = {
|
|
87
|
+
argTypes: {size: {control: 'select', options: ['tiny', 'xsmall', 'small', 'medium', 'large']}},
|
|
88
|
+
};`;
|
|
89
|
+
const output = await applyTransform(input);
|
|
90
|
+
expect(output).toContain(`['xsm', 'sm', 'md', 'lg', 'xl']`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('renames a standalone Avatar-size array literal used with .map()', async () => {
|
|
94
|
+
const input = `import {AvatarGroup} from '@astryxdesign/core';
|
|
95
|
+
const sizes = (['tiny', 'xsmall', 'small', 'medium', 'large'] as const).map(s => s);`;
|
|
96
|
+
const output = await applyTransform(input);
|
|
97
|
+
expect(output).toContain(`['xsm', 'sm', 'md', 'lg', 'xl']`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('does NOT rename an array of ambiguous words with no unique Avatar name', async () => {
|
|
101
|
+
// Without tiny/xsmall, the array could be anything (priorities, densities),
|
|
102
|
+
// so ambiguous members are left untouched.
|
|
103
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
104
|
+
const densities = ['small', 'medium', 'large'] as const;`;
|
|
105
|
+
const output = await applyTransform(input);
|
|
106
|
+
expect(output).toContain(`['small', 'medium', 'large']`);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('renames a UNIQUE name in a size-typed union literal', async () => {
|
|
110
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
111
|
+
type Props = {size: 'tiny' | 'xsmall'};`;
|
|
112
|
+
const output = await applyTransform(input);
|
|
113
|
+
expect(output).toContain(`'xsm' | 'sm'`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// --- Precision guards: ambiguous common words must NOT be corrupted in
|
|
117
|
+
// --- context-blind positions, even in files that import Avatar. ---
|
|
118
|
+
|
|
119
|
+
it('does NOT rename ambiguous names in an unrelated union type', async () => {
|
|
120
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
121
|
+
type TaskPriority = 'urgent' | 'high' | 'medium' | 'low' | 'none';`;
|
|
122
|
+
const output = await applyTransform(input);
|
|
123
|
+
expect(output).toContain(`'medium'`);
|
|
124
|
+
expect(output).not.toContain(`'lg'`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('does NOT rename an ambiguous name in a non-size object property', async () => {
|
|
128
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
129
|
+
const cfg = {priority: 'medium', density: 'large'};`;
|
|
130
|
+
const output = await applyTransform(input);
|
|
131
|
+
expect(output).toContain(`priority: 'medium'`);
|
|
132
|
+
expect(output).toContain(`density: 'large'`);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('does NOT rename an ambiguous name in a size-keyed object property (component unknown)', async () => {
|
|
136
|
+
// A `size: 'small'` object entry could belong to any component; renaming it
|
|
137
|
+
// blind would be unsafe, so it is left for manual migration.
|
|
138
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
139
|
+
const args = {size: 'small'};`;
|
|
140
|
+
const output = await applyTransform(input);
|
|
141
|
+
expect(output).toContain(`size: 'small'`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('does not touch files that never import Avatar/AvatarGroup', async () => {
|
|
145
|
+
const input = `import {Badge} from '@astryxdesign/core';
|
|
146
|
+
const x = <Badge size="small" />;
|
|
147
|
+
type P = 'tiny' | 'xsmall';`;
|
|
148
|
+
const output = await applyTransform(input);
|
|
149
|
+
expect(output).toContain(`size="small"`);
|
|
150
|
+
expect(output).toContain(`'tiny' | 'xsmall'`);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('does not rename an unrelated non-size string in an Avatar file', async () => {
|
|
154
|
+
const input = `import {Avatar} from '@astryxdesign/core';
|
|
155
|
+
const label = 'small';
|
|
156
|
+
const x = <Avatar name="A" size="small" />;`;
|
|
157
|
+
const output = await applyTransform(input);
|
|
158
|
+
expect(output).toContain(`const label = 'small'`);
|
|
159
|
+
expect(output).toContain(`size='md'`);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file v0.1.8 transform manifest
|
|
5
|
+
*
|
|
6
|
+
* Lists all codemods for the v0.1.8 release in the order they should run.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import renameAvatarSizeScale, {
|
|
10
|
+
meta as renameAvatarSizeScaleMeta,
|
|
11
|
+
} from './rename-avatar-size-scale.mjs';
|
|
12
|
+
|
|
13
|
+
export default [
|
|
14
|
+
{
|
|
15
|
+
name: 'rename-avatar-size-scale',
|
|
16
|
+
transform: renameAvatarSizeScale,
|
|
17
|
+
meta: renameAvatarSizeScaleMeta,
|
|
18
|
+
},
|
|
19
|
+
];
|