@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.
- package/docs/working-with-ai.doc.mjs +2 -2
- package/package.json +11 -9
- package/scripts/postinstall.mjs +74 -0
- 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 +129 -12
- package/src/commands/agent-docs.test.mjs +112 -6
- package/src/commands/cli-postinstall.test.mjs +42 -0
- package/src/commands/interactive-guard.test.mjs +1 -1
- package/src/commands/json-contract.test.mjs +1 -1
- package/src/commands/setup-nudge.test.mjs +108 -0
- package/src/commands/upgrade.mjs +130 -27
- package/src/commands/upgrade.test.mjs +154 -0
- package/src/index.mjs +35 -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/CodeBlock/CodeBlockTerminal.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
|
@@ -42,13 +42,13 @@ export const docs = {
|
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
type: 'prose',
|
|
45
|
-
text: '
|
|
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.
|
|
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.
|
|
82
|
-
"@astryxdesign/core": "0.1.7-canary.
|
|
83
|
-
"@astryxdesign/lab": "0.1.7-canary.
|
|
84
|
-
"@astryxdesign/theme-neutral": "0.1.7-canary.
|
|
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.
|
|
103
|
-
"@astryxdesign/core": "0.1.7-canary.
|
|
104
|
-
"@astryxdesign/lab": "0.1.7-canary.
|
|
105
|
-
"@astryxdesign/theme-neutral": "0.1.7-canary.
|
|
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
|
+
}
|
|
@@ -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
|
}
|