@astryxdesign/cli 0.4.5-canary.bc34238 → 0.4.5-canary.c2bb31b
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/assets/codemods/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +91 -0
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +28 -4
- package/assets/templates/blocks/components/Selector/SelectorOptionDescriptions.doc.mjs +14 -0
- package/assets/templates/blocks/components/Selector/SelectorOptionDescriptions.tsx +76 -0
- package/package.json +9 -9
|
@@ -212,4 +212,95 @@ describe('drop-xds-prefix-imports', () => {
|
|
|
212
212
|
const output = await applyTransform(input);
|
|
213
213
|
expect(output).toBe(input);
|
|
214
214
|
});
|
|
215
|
+
|
|
216
|
+
it('aliases a component wrapper collision in place (XDS -> Astryx), leaving the local fn untouched', async () => {
|
|
217
|
+
const input = [
|
|
218
|
+
`import {XDSLinkProvider} from '@xds/core/Link';`,
|
|
219
|
+
`export default function LinkProvider({children}) {`,
|
|
220
|
+
` return <XDSLinkProvider component={NextLink}>{children}</XDSLinkProvider>;`,
|
|
221
|
+
`}`,
|
|
222
|
+
].join('\n');
|
|
223
|
+
const output = await applyTransform(input);
|
|
224
|
+
// Import aliased in place, local wrapper + its name untouched.
|
|
225
|
+
expect(output).toContain('import {LinkProvider as AstryxLinkProvider}');
|
|
226
|
+
expect(output).toContain('function LinkProvider({children})');
|
|
227
|
+
// JSX rewritten to the alias -> no self-recursion.
|
|
228
|
+
expect(output).toContain('<AstryxLinkProvider component={NextLink}>');
|
|
229
|
+
expect(output).not.toContain('XDSLinkProvider');
|
|
230
|
+
// No duplicate bare LinkProvider import.
|
|
231
|
+
expect(output).not.toMatch(/import \{LinkProvider\}/);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('aliases a hook collision as use<Astryx>Name (not <Astryx>use)', async () => {
|
|
235
|
+
const input = [
|
|
236
|
+
`import {useXDSToast} from '@xds/core';`,
|
|
237
|
+
`function useToast() {`,
|
|
238
|
+
` return useXDSToast();`,
|
|
239
|
+
`}`,
|
|
240
|
+
].join('\n');
|
|
241
|
+
const output = await applyTransform(input);
|
|
242
|
+
expect(output).toContain('import {useToast as useAstryxToast}');
|
|
243
|
+
expect(output).toContain('function useToast()');
|
|
244
|
+
expect(output).toContain('return useAstryxToast();');
|
|
245
|
+
expect(output).not.toContain('useXDSToast');
|
|
246
|
+
// Must not produce the malformed `Astryxuse...` form.
|
|
247
|
+
expect(output).not.toContain('AstryxuseToast');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('aliases on collision with a local default import binding', async () => {
|
|
251
|
+
const input = [
|
|
252
|
+
`import Link from 'next/link';`,
|
|
253
|
+
`import {XDSLink} from '@xds/core';`,
|
|
254
|
+
`export const a = <XDSLink href="/" />;`,
|
|
255
|
+
`export const b = <Link href="/" />;`,
|
|
256
|
+
].join('\n');
|
|
257
|
+
const output = await applyTransform(input);
|
|
258
|
+
expect(output).toContain('import {Link as AstryxLink}');
|
|
259
|
+
expect(output).toContain(`import Link from 'next/link';`);
|
|
260
|
+
expect(output).toContain('<AstryxLink href="/" />');
|
|
261
|
+
expect(output).toContain('<Link href="/" />');
|
|
262
|
+
expect(output).not.toContain('XDSLink');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('aliases on collision with a local type alias', async () => {
|
|
266
|
+
const input = [
|
|
267
|
+
`import type {XDSTab} from '@xds/core';`,
|
|
268
|
+
`type Tab = {id: string};`,
|
|
269
|
+
`const active: XDSTab = null as any;`,
|
|
270
|
+
`const local: Tab = {id: '1'};`,
|
|
271
|
+
].join('\n');
|
|
272
|
+
const output = await applyTransform(input);
|
|
273
|
+
expect(output).toContain('Tab as AstryxTab');
|
|
274
|
+
expect(output).toContain('type Tab = {id: string};');
|
|
275
|
+
expect(output).toContain('const active: AstryxTab');
|
|
276
|
+
expect(output).toContain('const local: Tab');
|
|
277
|
+
expect(output).not.toContain('XDSTab');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('aliases on collision with a local interface', async () => {
|
|
281
|
+
const input = [
|
|
282
|
+
`import type {XDSTheme} from '@xds/core';`,
|
|
283
|
+
`interface Theme {}`,
|
|
284
|
+
`const t: XDSTheme = null as any;`,
|
|
285
|
+
`const local: Theme = {};`,
|
|
286
|
+
].join('\n');
|
|
287
|
+
const output = await applyTransform(input);
|
|
288
|
+
expect(output).toContain('Theme as AstryxTheme');
|
|
289
|
+
expect(output).toContain('interface Theme {}');
|
|
290
|
+
expect(output).toContain('const t: AstryxTheme');
|
|
291
|
+
expect(output).not.toContain('XDSTheme');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('still bare-renames when there is NO colliding local binding', async () => {
|
|
295
|
+
const input = [
|
|
296
|
+
`import {XDSButton} from '@xds/core';`,
|
|
297
|
+
`export const App = () => <XDSButton label="Hi" />;`,
|
|
298
|
+
].join('\n');
|
|
299
|
+
const output = await applyTransform(input);
|
|
300
|
+
// No local `Button` binding -> existing blind bare-rename behavior kept.
|
|
301
|
+
expect(output).toContain(`import {Button} from '@xds/core';`);
|
|
302
|
+
expect(output).toContain('<Button label="Hi" />');
|
|
303
|
+
expect(output).not.toContain('AstryxButton');
|
|
304
|
+
expect(output).not.toContain('XDSButton');
|
|
305
|
+
});
|
|
215
306
|
});
|
|
@@ -64,6 +64,22 @@ export const meta = {
|
|
|
64
64
|
|
|
65
65
|
const XDS_CORE_SOURCE = /^@xds\/core(\/.*)?$/;
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Compute a collision alias for an XDS-prefixed identifier by replacing the
|
|
69
|
+
* `XDS` segment IN PLACE with `Astryx` (as opposed to prefixing the bare name).
|
|
70
|
+
* This keeps hook names well-formed:
|
|
71
|
+
*
|
|
72
|
+
* XDSButton -> AstryxButton
|
|
73
|
+
* XDSLinkProvider -> AstryxLinkProvider
|
|
74
|
+
* useXDSToast -> useAstryxToast (NOT AstryxuseToast)
|
|
75
|
+
*
|
|
76
|
+
* Only the first `XDS` occurrence is replaced, matching how `bareName` strips a
|
|
77
|
+
* single leading prefix.
|
|
78
|
+
*/
|
|
79
|
+
function aliasName(/** @type {any} */ name) {
|
|
80
|
+
return name.replace('XDS', 'Astryx');
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
/**
|
|
68
84
|
* Compute the bare (unprefixed) name for an XDS-prefixed identifier.
|
|
69
85
|
* Returns null if the name is not XDS-prefixed in a renameable way.
|
|
@@ -190,7 +206,12 @@ export default function transformer(file, api) {
|
|
|
190
206
|
if (!node) return;
|
|
191
207
|
if (
|
|
192
208
|
(node.type === 'FunctionDeclaration' ||
|
|
193
|
-
node.type === 'ClassDeclaration'
|
|
209
|
+
node.type === 'ClassDeclaration' ||
|
|
210
|
+
// Type-only bindings share the module namespace for our purposes: a
|
|
211
|
+
// `type Tab`/`interface Theme` collides with an un-prefixed `XDSTab`/
|
|
212
|
+
// `XDSTheme` type import and would create a duplicate declaration.
|
|
213
|
+
node.type === 'TSTypeAliasDeclaration' ||
|
|
214
|
+
node.type === 'TSInterfaceDeclaration') &&
|
|
194
215
|
node.id
|
|
195
216
|
) {
|
|
196
217
|
existingBindings.add(node.id.name);
|
|
@@ -239,10 +260,13 @@ export default function transformer(file, api) {
|
|
|
239
260
|
if (bare !== importedName && existingBindings.has(bare)) {
|
|
240
261
|
// COLLISION: the bare name is already a top-level binding in this file
|
|
241
262
|
// (e.g. a local `export function CodeBlock` alongside imported
|
|
242
|
-
// `XDSCodeBlock`). Un-prefixing
|
|
243
|
-
//
|
|
263
|
+
// `XDSCodeBlock`, or a `type Tab` alongside `XDSTab`). Un-prefixing
|
|
264
|
+
// directly would create a duplicate declaration (TS2451), so alias the
|
|
265
|
+
// import by replacing `XDS` with `Astryx` in place and rename the
|
|
244
266
|
// import's references to that alias, leaving the local binding intact.
|
|
245
|
-
|
|
267
|
+
// In-place replacement keeps hooks well-formed: `useXDSToast` becomes
|
|
268
|
+
// `useAstryxToast`, not `AstryxuseToast`.
|
|
269
|
+
const alias = aliasName(importedName);
|
|
246
270
|
localRenames.set(importedName, alias);
|
|
247
271
|
existingBindings.add(alias);
|
|
248
272
|
hasChanges = true;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'Selector',
|
|
7
|
+
name: 'Selector — Option descriptions',
|
|
8
|
+
displayName: 'Selector — Option descriptions',
|
|
9
|
+
description:
|
|
10
|
+
'Options carry a description, so the dropdown draws a two-line row. The closed trigger is sized by padding, so it is the size token for a one-line value and exactly one line taller for a two-line one — both on the 4px rhythm. An InputGroup pins the row, so the value folds back onto one line there.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 16 / 9,
|
|
13
|
+
componentsUsed: ['Selector', 'SelectorOption', 'InputGroup', 'Button', 'Stack'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {Selector, SelectorOption} from '@astryxdesign/core/Selector';
|
|
7
|
+
import {InputGroup} from '@astryxdesign/core/InputGroup';
|
|
8
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
9
|
+
import {Stack} from '@astryxdesign/core/Layout';
|
|
10
|
+
import {LockClosedIcon, GlobeAltIcon} from '@heroicons/react/24/outline';
|
|
11
|
+
|
|
12
|
+
const VISIBILITY = [
|
|
13
|
+
{
|
|
14
|
+
value: 'private',
|
|
15
|
+
label: 'Private',
|
|
16
|
+
icon: LockClosedIcon,
|
|
17
|
+
description: 'Only members can access this space and its content.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: 'public',
|
|
21
|
+
label: 'Public',
|
|
22
|
+
icon: GlobeAltIcon,
|
|
23
|
+
description: 'Anyone at the company can find and join this space.',
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export default function SelectorOptionDescriptions() {
|
|
28
|
+
const [condensed, setCondensed] = useState<string | undefined>('private');
|
|
29
|
+
const [full, setFull] = useState<string | undefined>('private');
|
|
30
|
+
const [grouped, setGrouped] = useState<string | undefined>('private');
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Stack direction="vertical" gap={6}>
|
|
34
|
+
<Selector
|
|
35
|
+
style={{width: 320}}
|
|
36
|
+
label="Visibility"
|
|
37
|
+
description="Default trigger: one line at the size token."
|
|
38
|
+
options={VISIBILITY}
|
|
39
|
+
value={condensed}
|
|
40
|
+
onChange={setCondensed}
|
|
41
|
+
/>
|
|
42
|
+
<Selector
|
|
43
|
+
style={{width: 320}}
|
|
44
|
+
label="Visibility"
|
|
45
|
+
description="A stacked SelectorOption: the description gets its own line, and the trigger grows by exactly one line."
|
|
46
|
+
options={VISIBILITY}
|
|
47
|
+
value={full}
|
|
48
|
+
onChange={setFull}
|
|
49
|
+
renderValue={option => (
|
|
50
|
+
<SelectorOption
|
|
51
|
+
icon={option.icon}
|
|
52
|
+
label={option.label ?? option.value}
|
|
53
|
+
description={option.description}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
/>
|
|
57
|
+
<InputGroup label="Visibility">
|
|
58
|
+
<Selector
|
|
59
|
+
label="Visibility"
|
|
60
|
+
isLabelHidden
|
|
61
|
+
options={VISIBILITY}
|
|
62
|
+
value={grouped}
|
|
63
|
+
onChange={setGrouped}
|
|
64
|
+
renderValue={option => (
|
|
65
|
+
<SelectorOption
|
|
66
|
+
icon={option.icon}
|
|
67
|
+
label={option.label ?? option.value}
|
|
68
|
+
description={option.description}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
/>
|
|
72
|
+
<Button label="Save" />
|
|
73
|
+
</InputGroup>
|
|
74
|
+
</Stack>
|
|
75
|
+
);
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.4.5-canary.
|
|
3
|
+
"version": "0.4.5-canary.c2bb31b",
|
|
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",
|
|
@@ -87,10 +87,10 @@
|
|
|
87
87
|
"zod": "^4.4.3"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"@astryxdesign/charts": "0.4.5-canary.
|
|
91
|
-
"@astryxdesign/core": "0.4.5-canary.
|
|
92
|
-
"@astryxdesign/lab": "0.4.5-canary.
|
|
93
|
-
"@astryxdesign/theme-neutral": "0.4.5-canary.
|
|
90
|
+
"@astryxdesign/charts": "0.4.5-canary.c2bb31b",
|
|
91
|
+
"@astryxdesign/core": "0.4.5-canary.c2bb31b",
|
|
92
|
+
"@astryxdesign/lab": "0.4.5-canary.c2bb31b",
|
|
93
|
+
"@astryxdesign/theme-neutral": "0.4.5-canary.c2bb31b",
|
|
94
94
|
"gpt-tokenizer": "^3.4.0"
|
|
95
95
|
},
|
|
96
96
|
"peerDependenciesMeta": {
|
|
@@ -108,10 +108,10 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"devDependencies": {
|
|
111
|
-
"@astryxdesign/charts": "0.4.5-canary.
|
|
112
|
-
"@astryxdesign/core": "0.4.5-canary.
|
|
113
|
-
"@astryxdesign/lab": "0.4.5-canary.
|
|
114
|
-
"@astryxdesign/theme-neutral": "0.4.5-canary.
|
|
111
|
+
"@astryxdesign/charts": "0.4.5-canary.c2bb31b",
|
|
112
|
+
"@astryxdesign/core": "0.4.5-canary.c2bb31b",
|
|
113
|
+
"@astryxdesign/lab": "0.4.5-canary.c2bb31b",
|
|
114
|
+
"@astryxdesign/theme-neutral": "0.4.5-canary.c2bb31b",
|
|
115
115
|
"gpt-tokenizer": "^3.4.0"
|
|
116
116
|
},
|
|
117
117
|
"scripts": {
|