@astryxdesign/cli 0.1.7-canary.04cd8f7 → 0.1.7-canary.1055df2

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.
Files changed (57) hide show
  1. package/docs/working-with-ai.doc.mjs +2 -2
  2. package/package.json +11 -9
  3. package/scripts/postinstall.mjs +74 -0
  4. package/src/codemods/__tests__/registry.test.mjs +1 -0
  5. package/src/codemods/registry.mjs +1 -0
  6. package/src/codemods/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +42 -7
  7. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-module-specifiers.test.mjs +43 -0
  8. package/src/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +102 -3
  9. package/src/codemods/transforms/v0.1.0/migrate-xds-module-specifiers.mjs +72 -6
  10. package/src/codemods/transforms/v0.1.8/__tests__/rename-avatar-size-scale.test.mjs +161 -0
  11. package/src/codemods/transforms/v0.1.8/index.mjs +19 -0
  12. package/src/codemods/transforms/v0.1.8/rename-avatar-size-scale.mjs +234 -0
  13. package/src/commands/agent-docs.mjs +129 -12
  14. package/src/commands/agent-docs.test.mjs +112 -6
  15. package/src/commands/cli-postinstall.test.mjs +42 -0
  16. package/src/commands/interactive-guard.test.mjs +1 -1
  17. package/src/commands/json-contract.test.mjs +1 -1
  18. package/src/commands/setup-nudge.test.mjs +108 -0
  19. package/src/commands/upgrade.mjs +130 -27
  20. package/src/commands/upgrade.test.mjs +154 -0
  21. package/src/index.mjs +35 -0
  22. package/src/types/upgrade.d.ts +39 -2
  23. package/templates/blocks/components/Avatar/AvatarFallbackChain.tsx +4 -4
  24. package/templates/blocks/components/Avatar/AvatarGroup.tsx +2 -2
  25. package/templates/blocks/components/Avatar/AvatarInitialsFallback.tsx +1 -1
  26. package/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -4
  27. package/templates/blocks/components/Avatar/AvatarUserCard.tsx +1 -1
  28. package/templates/blocks/components/Avatar/AvatarWithImage.tsx +4 -4
  29. package/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -3
  30. package/templates/blocks/components/AvatarGroup/AvatarGroupShowcase.tsx +2 -2
  31. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowCustomText.tsx +1 -1
  32. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowDefault.tsx +1 -1
  33. package/templates/blocks/components/AvatarGroupOverflow/AvatarGroupOverflowShowcase.tsx +2 -2
  34. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotShowcase.tsx +3 -3
  35. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +3 -3
  36. package/templates/blocks/components/Carousel/CarouselSnap.tsx +1 -1
  37. package/templates/blocks/components/ChatMessage/ChatMessageAvatarName.tsx +2 -2
  38. package/templates/blocks/components/ChatMessage/ChatMessageMultiBubble.tsx +1 -1
  39. package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleGrouping.tsx +1 -1
  40. package/templates/blocks/components/ChatMessageBubble/ChatMessageBubbleMetadata.tsx +1 -1
  41. package/templates/blocks/components/ChatMessageList/ChatMessageListDensity.tsx +5 -9
  42. package/templates/blocks/components/ChatMessageList/ChatMessageListFullFeatured.tsx +1 -1
  43. package/templates/blocks/components/CodeBlock/CodeBlockTerminal.tsx +1 -1
  44. package/templates/blocks/components/HoverCard/HoverCardShowcase.tsx +1 -1
  45. package/templates/blocks/components/Item/ItemWithMedia.tsx +2 -2
  46. package/templates/blocks/components/ListItem/ListItemWithMedia.tsx +2 -2
  47. package/templates/blocks/components/Pagination/PaginationDotsCarousel.tsx +2 -6
  48. package/templates/blocks/components/Stack/StackFillItem.tsx +2 -6
  49. package/templates/blocks/components/TypeaheadItem/TypeaheadItemShowcase.tsx +1 -1
  50. package/templates/pages/ai-chat/page.tsx +4 -4
  51. package/templates/pages/dashboard-portfolio/page.tsx +3 -11
  52. package/templates/pages/detail-page/page.tsx +5 -12
  53. package/templates/pages/documentation-design/page.tsx +1 -1
  54. package/templates/pages/messaging-shell/page.tsx +6 -8
  55. package/templates/pages/table-grouped/page.tsx +9 -14
  56. package/templates/pages/table-page/page.tsx +7 -9
  57. package/templates/pages/table-page-heatmap-status/page.tsx +5 -13
@@ -42,13 +42,13 @@ export const docs = {
42
42
  },
43
43
  {
44
44
  type: 'prose',
45
- text: 'If you prefer to target a specific file format:',
45
+ text: 'By default this creates `AGENTS.md` (the tool-agnostic standard most agents read). To target a specific tool\'s file instead:',
46
46
  },
47
47
  {
48
48
  type: 'code',
49
49
  lang: 'bash',
50
50
  label: 'Manual options',
51
- code: `npx @astryxdesign/cli init --features agents --agent claude # CLAUDE.md
51
+ code: `npx @astryxdesign/cli init --features agents --agent claude # .claude/CLAUDE.md
52
52
  npx @astryxdesign/cli init --features agents --agent cursor # .cursorrules
53
53
  npx @astryxdesign/cli init --features agents --agent codex # AGENTS.md (Copilot, Codex, etc.)`,
54
54
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.1.7-canary.04cd8f7",
3
+ "version": "0.1.7-canary.1055df2",
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",
@@ -67,6 +67,7 @@
67
67
  "files": [
68
68
  "bin",
69
69
  "src",
70
+ "scripts/postinstall.mjs",
70
71
  "templates",
71
72
  "docs",
72
73
  "CHANGELOG.md"
@@ -78,10 +79,10 @@
78
79
  "zod": "^4.4.3"
79
80
  },
80
81
  "peerDependencies": {
81
- "@astryxdesign/charts": "0.1.7-canary.04cd8f7",
82
- "@astryxdesign/core": "0.1.7-canary.04cd8f7",
83
- "@astryxdesign/lab": "0.1.7-canary.04cd8f7",
84
- "@astryxdesign/theme-neutral": "0.1.7-canary.04cd8f7",
82
+ "@astryxdesign/charts": "0.1.7-canary.1055df2",
83
+ "@astryxdesign/core": "0.1.7-canary.1055df2",
84
+ "@astryxdesign/lab": "0.1.7-canary.1055df2",
85
+ "@astryxdesign/theme-neutral": "0.1.7-canary.1055df2",
85
86
  "gpt-tokenizer": "^3.4.0"
86
87
  },
87
88
  "peerDependenciesMeta": {
@@ -99,14 +100,15 @@
99
100
  }
100
101
  },
101
102
  "devDependencies": {
102
- "@astryxdesign/charts": "0.1.7-canary.04cd8f7",
103
- "@astryxdesign/core": "0.1.7-canary.04cd8f7",
104
- "@astryxdesign/lab": "0.1.7-canary.04cd8f7",
105
- "@astryxdesign/theme-neutral": "0.1.7-canary.04cd8f7",
103
+ "@astryxdesign/charts": "0.1.7-canary.1055df2",
104
+ "@astryxdesign/core": "0.1.7-canary.1055df2",
105
+ "@astryxdesign/lab": "0.1.7-canary.1055df2",
106
+ "@astryxdesign/theme-neutral": "0.1.7-canary.1055df2",
106
107
  "gpt-tokenizer": "^3.4.0"
107
108
  },
108
109
  "scripts": {
109
110
  "astryx": "node bin/astryx.mjs",
111
+ "postinstall": "node scripts/postinstall.mjs",
110
112
  "typecheck:template-docs": "tsc --project tsconfig.template-docs.json",
111
113
  "typecheck:json-api": "tsc --project tsconfig.json-api.json && tsc --project tsconfig.api-contract.json"
112
114
  }
@@ -0,0 +1,74 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file @astryxdesign/cli postinstall — nudge to run `astryx init`.
5
+ *
6
+ * Enforcement layer 2 of making `astryx init` foolproof: when the CLI is
7
+ * installed as a project dependency and the project hasn't run init yet, print a
8
+ * one-line next-step so agents/humans discover it.
9
+ *
10
+ * Reuses the ONE setup check (isAstryxInitialized from agent-docs.mjs — a
11
+ * dep-free import chain, safe at install time). Non-interactive, never fails the
12
+ * install, and stays quiet:
13
+ * - in the monorepo/source build (not under node_modules),
14
+ * - during npx's transient fetch (npx runs the bin — likely `init` — right
15
+ * after, so nudging here would double up), and
16
+ * - once setup is already done.
17
+ */
18
+
19
+ import {fileURLToPath, pathToFileURL} from 'node:url';
20
+
21
+ const HERE = fileURLToPath(import.meta.url);
22
+
23
+ /**
24
+ * Pure decision: should the postinstall print the setup nudge? Split out so the
25
+ * matrix is unit-testable without an actual npm install.
26
+ *
27
+ * @param {object} opts
28
+ * @param {string} opts.scriptPath - Absolute path of this script (location tells
29
+ * us dependency vs monorepo vs npx-cache).
30
+ * @param {string} [opts.npmCommand] - process.env.npm_command ('install', 'exec', …).
31
+ * @param {boolean} opts.isSetUp - Whether the project already ran init.
32
+ * @returns {boolean}
33
+ */
34
+ export function shouldNudge({scriptPath, npmCommand, isSetUp} = {}) {
35
+ if (!scriptPath || !scriptPath.includes('node_modules')) return false; // monorepo/source build
36
+ if (scriptPath.includes('_npx') || npmCommand === 'exec') return false; // npx transient — init runs next
37
+ if (isSetUp) return false; // already set up — stay quiet
38
+ return true;
39
+ }
40
+
41
+ /** @param {string} root @returns {Promise<boolean>} */
42
+ async function projectIsSetUp(root) {
43
+ try {
44
+ const {isAstryxInitialized} = await import('../src/commands/agent-docs.mjs');
45
+ return isAstryxInitialized(root);
46
+ } catch {
47
+ return false; // best-effort — if the check can't load, fall through and nudge
48
+ }
49
+ }
50
+
51
+ async function main() {
52
+ const root = process.env.INIT_CWD || process.cwd();
53
+ const nudge = shouldNudge({
54
+ scriptPath: HERE,
55
+ npmCommand: process.env.npm_command,
56
+ isSetUp: await projectIsSetUp(root),
57
+ });
58
+ if (nudge) {
59
+ // Scoped package form (`@astryxdesign/cli`) — always resolves to us. Bare
60
+ // `npx astryx` would fetch an unrelated look-alike package (see PR #4151).
61
+ // After that lands, switch this to its getCliInvocation() single source of truth.
62
+ process.stdout.write(
63
+ '\nNext step: run `npx @astryxdesign/cli init` to finish setup and install the Astryx agent prompt.\n\n',
64
+ );
65
+ }
66
+ }
67
+
68
+ // Run only when executed directly (`node scripts/postinstall.mjs`), never when
69
+ // imported by tests.
70
+ if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
71
+ main()
72
+ .catch(() => {})
73
+ .finally(() => process.exit(0));
74
+ }
@@ -21,6 +21,7 @@ describe('registry', () => {
21
21
  '0.1.3',
22
22
  '0.1.5',
23
23
  '0.1.7',
24
+ '0.1.8',
24
25
  ]);
25
26
  });
26
27
  });
@@ -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 normally when there is NO local collision', async () => {
169
+ it('un-prefixes override keys inside an @xds/core mock factory', async () => {
170
170
  const input = [
171
- `import {XDSCodeBlock} from '@xds/core/CodeBlock';`,
172
- `export const App = () => <XDSCodeBlock code="x" />;`,
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('{CodeBlock}');
176
- expect(output).toContain('@xds/core/CodeBlock');
177
- expect(output).toContain('<CodeBlock code="x" />');
178
- expect(output).not.toContain('AstryxCodeBlock');
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). Only renames ' +
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
- // 3. Rename references to renamed local bindings
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
- // 4. Rename TS type references in generic type-argument positions.
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, and require module ' +
16
- 'specifiers from @xds/* to the @astryxdesign/* packages used by Astryx v0.1.0.',
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 (!isDynamicImport && !isRequire) return;
122
- const [arg] = path.node.arguments;
123
- if (arg?.type === 'StringLiteral' || arg?.type === 'Literal') {
124
- hasChanges = rewriteLiteral(arg) || hasChanges;
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
  }