@astryxdesign/cli 0.1.4-canary.39ffd21 → 0.1.4-canary.3b5f42e
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 +7 -7
- package/src/api/component.mjs +2 -1
- package/src/api/template.mjs +14 -3
- package/src/commands/agent-docs.mjs +2 -1
- package/src/commands/component-resolution.test.mjs +4 -4
- package/src/lib/component-discovery.mjs +8 -4
- package/templates/blocks/components/BaseTypeahead/BaseTypeaheadCustomSearch.doc.mjs +14 -0
- package/templates/blocks/components/BaseTypeahead/BaseTypeaheadCustomSearch.tsx +58 -0
- package/templates/blocks/components/CodeBlock/CodeBlockTerminal.doc.mjs +14 -0
- package/templates/blocks/components/CodeBlock/CodeBlockTerminal.tsx +24 -0
- package/templates/blocks/components/NavHeadingMenu/NavHeadingMenuShowcase.doc.mjs +15 -0
- package/templates/blocks/components/NavHeadingMenu/NavHeadingMenuShowcase.tsx +45 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.4-canary.
|
|
3
|
+
"version": "0.1.4-canary.3b5f42e",
|
|
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",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"zod": "^4.4.3"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@astryxdesign/core": "0.1.4-canary.
|
|
79
|
-
"@astryxdesign/lab": "0.1.4-canary.
|
|
80
|
-
"@astryxdesign/theme-neutral": "0.1.4-canary.
|
|
78
|
+
"@astryxdesign/core": "0.1.4-canary.3b5f42e",
|
|
79
|
+
"@astryxdesign/lab": "0.1.4-canary.3b5f42e",
|
|
80
|
+
"@astryxdesign/theme-neutral": "0.1.4-canary.3b5f42e",
|
|
81
81
|
"gpt-tokenizer": "^2.0.0"
|
|
82
82
|
},
|
|
83
83
|
"peerDependenciesMeta": {
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@astryxdesign/core": "0.1.4-canary.
|
|
96
|
-
"@astryxdesign/lab": "0.1.4-canary.
|
|
97
|
-
"@astryxdesign/theme-neutral": "0.1.4-canary.
|
|
95
|
+
"@astryxdesign/core": "0.1.4-canary.3b5f42e",
|
|
96
|
+
"@astryxdesign/lab": "0.1.4-canary.3b5f42e",
|
|
97
|
+
"@astryxdesign/theme-neutral": "0.1.4-canary.3b5f42e",
|
|
98
98
|
"gpt-tokenizer": "^2.0.0"
|
|
99
99
|
},
|
|
100
100
|
"scripts": {
|
package/src/api/component.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
11
12
|
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
12
13
|
import {findCoreDir, discoverExternalPackages} from '../utils/paths.mjs';
|
|
13
14
|
import {
|
|
@@ -560,7 +561,7 @@ export async function component(name, options = {}) {
|
|
|
560
561
|
|
|
561
562
|
// Examples: blocks in the component's own directory, or
|
|
562
563
|
// componentsUsed match for sub-components without a directory.
|
|
563
|
-
const ownDir = allBlocks.filter(b => b.category
|
|
564
|
+
const ownDir = allBlocks.filter(b => path.basename(b.category) === dirName);
|
|
564
565
|
const examples = ownDir.length > 0
|
|
565
566
|
? ownDir
|
|
566
567
|
: allBlocks.filter(b => b.componentsUsed?.some(c => c === dirName));
|
package/src/api/template.mjs
CHANGED
|
@@ -63,6 +63,17 @@ const DEMO_IMAGE_PATTERNS = [
|
|
|
63
63
|
/https?:\/\/(?:[\w-]+\.)*lookaside\.facebook\.com\/[^\s'"`)]+/g,
|
|
64
64
|
];
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Normalize path into Unix path (using forward slashes) for consistent comparison
|
|
68
|
+
* across Windows, macOS, and Linux.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} p - The file path to normalize.
|
|
71
|
+
* @returns {string} The normalized Unix path.
|
|
72
|
+
*/
|
|
73
|
+
function toPosixPath(p) {
|
|
74
|
+
return p.replace(/\\/g, '/');
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
/**
|
|
67
78
|
* Replace demo image references with a self-contained placeholder data URI so
|
|
68
79
|
* scaffolded pages render with zero setup. Builders drop in their own images.
|
|
@@ -144,7 +155,7 @@ async function discoverBlocks() {
|
|
|
144
155
|
const tsxPath = path.join(path.dirname(docPath), basename + '.tsx');
|
|
145
156
|
if (!fs.existsSync(tsxPath)) continue;
|
|
146
157
|
const doc = await loadDocModule(docPath);
|
|
147
|
-
const relPath = path.relative(BLOCKS_DIR, path.dirname(docPath));
|
|
158
|
+
const relPath = toPosixPath(path.relative(BLOCKS_DIR, path.dirname(docPath)));
|
|
148
159
|
blocks.push({
|
|
149
160
|
type: 'block',
|
|
150
161
|
dirName: basename,
|
|
@@ -180,7 +191,7 @@ async function discoverExternalBlocks(cwd = process.cwd()) {
|
|
|
180
191
|
const tsxPath = path.join(path.dirname(docPath), basename + '.tsx');
|
|
181
192
|
if (!fs.existsSync(tsxPath)) continue;
|
|
182
193
|
const doc = await loadDocModule(docPath);
|
|
183
|
-
const relPath = path.relative(ext.blocksDir, path.dirname(docPath));
|
|
194
|
+
const relPath = toPosixPath(path.relative(ext.blocksDir, path.dirname(docPath)));
|
|
184
195
|
blocks.push({
|
|
185
196
|
type: 'block',
|
|
186
197
|
dirName: basename,
|
|
@@ -428,7 +439,7 @@ export async function findShowcase(componentName, cwd, options) {
|
|
|
428
439
|
|
|
429
440
|
// Priority 1: own directory (components/Badge/ for "Badge")
|
|
430
441
|
const dirMatch = showcases.find(b => {
|
|
431
|
-
const catDir = b.category
|
|
442
|
+
const catDir = path.basename(b.category).toLowerCase();
|
|
432
443
|
return catDir === lc;
|
|
433
444
|
});
|
|
434
445
|
if (dirMatch) return toResult(dirMatch);
|
|
@@ -29,7 +29,8 @@ import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
|
29
29
|
|
|
30
30
|
const AGENTS_MD = 'AGENTS.md';
|
|
31
31
|
const CLAUDE_MD = 'CLAUDE.md';
|
|
32
|
-
const CLAUDE_DIR_MD =
|
|
32
|
+
const CLAUDE_DIR_MD = '.claude/CLAUDE.md'; // cross-platform literal
|
|
33
|
+
|
|
33
34
|
|
|
34
35
|
const MARKER_START = '<!-- ASTRYX:START -->';
|
|
35
36
|
const MARKER_END = '<!-- ASTRYX:END -->';
|
|
@@ -78,13 +78,13 @@ describe('findShowcase() priority', () => {
|
|
|
78
78
|
it('Badge resolves to Badge dir, not a sibling that uses Badge', async () => {
|
|
79
79
|
const result = await findShowcase('Badge');
|
|
80
80
|
expect(result).not.toBeNull();
|
|
81
|
-
expect(result.filePath).toMatch(
|
|
81
|
+
expect(result.filePath).toMatch(/[/\\]Badge[/\\]/);
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
it('Avatar resolves to Avatar dir, not AvatarStatusDot', async () => {
|
|
85
85
|
const result = await findShowcase('Avatar');
|
|
86
86
|
expect(result).not.toBeNull();
|
|
87
|
-
expect(result.filePath).toMatch(
|
|
87
|
+
expect(result.filePath).toMatch(/[/\\]Avatar[/\\]/);
|
|
88
88
|
expect(result.filePath).not.toMatch(/AvatarStatusDot/);
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -92,7 +92,7 @@ describe('findShowcase() priority', () => {
|
|
|
92
92
|
const result = await findShowcase('ClickableCard');
|
|
93
93
|
expect(result).not.toBeNull();
|
|
94
94
|
expect(result.name).toBe('ClickableCard');
|
|
95
|
-
expect(result.filePath).toMatch(
|
|
95
|
+
expect(result.filePath).toMatch(/[/\\]Card[/\\]/);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
it('SelectableCard resolves via componentsUsed in Card/', async () => {
|
|
@@ -104,7 +104,7 @@ describe('findShowcase() priority', () => {
|
|
|
104
104
|
it('Stack resolves to Stack dir despite componentsUsed elsewhere', async () => {
|
|
105
105
|
const result = await findShowcase('Stack');
|
|
106
106
|
expect(result).not.toBeNull();
|
|
107
|
-
expect(result.filePath).toMatch(
|
|
107
|
+
expect(result.filePath).toMatch(/[/\\]Stack[/\\]/);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
it('returns null for nonexistent component', async () => {
|
|
@@ -335,10 +335,13 @@ export function resolveImportPath(coreDir, componentName) {
|
|
|
335
335
|
? JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
|
|
336
336
|
: null;
|
|
337
337
|
|
|
338
|
+
const exportKeys = Object.keys(pkg?.exports || {});
|
|
339
|
+
|
|
338
340
|
// Priority 1: exact subpath export matching the component name (e.g. ./Heading)
|
|
339
341
|
// This allows convenience re-export directories to win over the source directory.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
+
const exactMatch = exportKeys.find(k => k.toLowerCase() === `./${componentName}`.toLowerCase());
|
|
343
|
+
if (exactMatch) {
|
|
344
|
+
return `@astryxdesign/core/${exactMatch.slice(2)}`;
|
|
342
345
|
}
|
|
343
346
|
|
|
344
347
|
const sourcePath = findComponentSource(coreDir, componentName);
|
|
@@ -348,8 +351,9 @@ export function resolveImportPath(coreDir, componentName) {
|
|
|
348
351
|
const relToSrc = path.relative(srcDir, sourcePath);
|
|
349
352
|
const topDir = relToSrc.split(path.sep)[0];
|
|
350
353
|
|
|
351
|
-
|
|
352
|
-
|
|
354
|
+
const topMatch = exportKeys.find(k => k.toLowerCase() === `./${topDir}`.toLowerCase());
|
|
355
|
+
if (topMatch) {
|
|
356
|
+
return `@astryxdesign/core/${topMatch.slice(2)}`;
|
|
353
357
|
}
|
|
354
358
|
|
|
355
359
|
return '@astryxdesign/core';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'BaseTypeahead',
|
|
7
|
+
name: 'BaseTypeahead — Custom Search Bar',
|
|
8
|
+
displayName: 'BaseTypeahead — Custom Search Bar',
|
|
9
|
+
description:
|
|
10
|
+
'BaseTypeahead embedded inside a custom-styled wrapper. The wrapper provides its own border and icon chrome; anchorRef positions the dropdown relative to it. Use this pattern when Typeahead\'s built-in field layout does not fit your composition.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 16 / 9,
|
|
13
|
+
componentsUsed: ['BaseTypeahead', 'Icon', 'Layout', 'Text'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useRef, useState} from 'react';
|
|
6
|
+
import {BaseTypeahead, createStaticSource} from '@astryxdesign/core/Typeahead';
|
|
7
|
+
import type {SearchableItem} from '@astryxdesign/core/Typeahead';
|
|
8
|
+
import {Icon} from '@astryxdesign/core/Icon';
|
|
9
|
+
import {HStack, VStack} from '@astryxdesign/core/Layout';
|
|
10
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
11
|
+
import {MagnifyingGlassIcon} from '@heroicons/react/24/outline';
|
|
12
|
+
|
|
13
|
+
const frameworks: SearchableItem[] = [
|
|
14
|
+
{id: 'react', label: 'React'},
|
|
15
|
+
{id: 'vue', label: 'Vue'},
|
|
16
|
+
{id: 'angular', label: 'Angular'},
|
|
17
|
+
{id: 'svelte', label: 'Svelte'},
|
|
18
|
+
{id: 'solid', label: 'SolidJS'},
|
|
19
|
+
{id: 'remix', label: 'Remix'},
|
|
20
|
+
{id: 'next', label: 'Next.js'},
|
|
21
|
+
{id: 'nuxt', label: 'Nuxt'},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const source = createStaticSource(frameworks);
|
|
25
|
+
|
|
26
|
+
export default function BaseTypeaheadCustomSearch() {
|
|
27
|
+
const [value, setValue] = useState<SearchableItem | null>(null);
|
|
28
|
+
const wrapperRef = useRef<HTMLElement>(null);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<VStack gap={3} style={{width: 320}}>
|
|
32
|
+
<HStack
|
|
33
|
+
ref={wrapperRef}
|
|
34
|
+
gap={2}
|
|
35
|
+
vAlign="center"
|
|
36
|
+
style={{
|
|
37
|
+
border: '1px solid var(--color-border)',
|
|
38
|
+
borderRadius: 'var(--radius-control)',
|
|
39
|
+
padding: '6px 10px',
|
|
40
|
+
background: 'var(--color-surface)',
|
|
41
|
+
}}>
|
|
42
|
+
<Icon icon={MagnifyingGlassIcon} size="sm" color="secondary" />
|
|
43
|
+
<BaseTypeahead
|
|
44
|
+
searchSource={source}
|
|
45
|
+
value={value}
|
|
46
|
+
onChange={setValue}
|
|
47
|
+
anchorRef={wrapperRef}
|
|
48
|
+
placeholder="Search frameworks…"
|
|
49
|
+
hasEntriesOnFocus
|
|
50
|
+
debounceMs={0}
|
|
51
|
+
/>
|
|
52
|
+
</HStack>
|
|
53
|
+
<Text type="supporting" color="secondary">
|
|
54
|
+
{value != null ? `Selected: ${value.label}` : 'No selection'}
|
|
55
|
+
</Text>
|
|
56
|
+
</VStack>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'CodeBlock',
|
|
7
|
+
name: 'Code — Terminal',
|
|
8
|
+
displayName: 'Code — Terminal',
|
|
9
|
+
description:
|
|
10
|
+
'A dark terminal-style command block: a bash CodeBlock wrapped in SyntaxTheme with the GitHub Dark preset, copy button on, and no line numbers. Use for shell sessions or CLI output that should read as a terminal even on light pages — reach for a dark syntax preset instead of hand-rolling a dark box with custom CSS.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 16 / 9,
|
|
13
|
+
componentsUsed: ['CodeBlock', 'SyntaxTheme'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {SyntaxTheme} from '@astryxdesign/core/theme';
|
|
6
|
+
import {githubDark} from '@astryxdesign/core/theme/syntax';
|
|
7
|
+
import {CodeBlock} from '@astryxdesign/core/CodeBlock';
|
|
8
|
+
|
|
9
|
+
const commands = `$ astryx init --features agents
|
|
10
|
+
✓ AI agent docs installed → .claude/CLAUDE.md
|
|
11
|
+
$ pnpm astryx component CodeBlock --dense`;
|
|
12
|
+
|
|
13
|
+
export default function CodeBlockTerminal() {
|
|
14
|
+
return (
|
|
15
|
+
<SyntaxTheme theme={githubDark}>
|
|
16
|
+
<CodeBlock
|
|
17
|
+
code={commands}
|
|
18
|
+
language="bash"
|
|
19
|
+
hasCopyButton
|
|
20
|
+
style={{width: '100%', maxWidth: 480}}
|
|
21
|
+
/>
|
|
22
|
+
</SyntaxTheme>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'NavHeadingMenu',
|
|
7
|
+
name: 'NavHeadingMenu',
|
|
8
|
+
displayName: 'Nav Heading Menu',
|
|
9
|
+
description:
|
|
10
|
+
'NavHeadingMenu passed as the menu prop of SideNavHeading, letting the heading act as a product switcher popover trigger.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
isShowcase: true,
|
|
13
|
+
aspectRatio: 4 / 3,
|
|
14
|
+
componentsUsed: ['SideNav', 'SideNavHeading', 'NavHeadingMenu', 'NavHeadingMenuItem'],
|
|
15
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {SideNav, SideNavHeading} from '@astryxdesign/core/SideNav';
|
|
6
|
+
import {NavHeadingMenu, NavHeadingMenuItem} from '@astryxdesign/core/NavMenu';
|
|
7
|
+
|
|
8
|
+
function AppIcon() {
|
|
9
|
+
return (
|
|
10
|
+
<svg
|
|
11
|
+
width="20"
|
|
12
|
+
height="20"
|
|
13
|
+
viewBox="0 0 24 24"
|
|
14
|
+
fill="none"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
strokeWidth="1.5">
|
|
17
|
+
<path
|
|
18
|
+
strokeLinecap="round"
|
|
19
|
+
strokeLinejoin="round"
|
|
20
|
+
d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25a2.25 2.25 0 0 1-2.25-2.25v-2.25Z"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function NavHeadingMenuShowcase() {
|
|
27
|
+
return (
|
|
28
|
+
<SideNav
|
|
29
|
+
header={
|
|
30
|
+
<SideNavHeading
|
|
31
|
+
heading="Acme Platform"
|
|
32
|
+
icon={<AppIcon />}
|
|
33
|
+
menu={
|
|
34
|
+
<NavHeadingMenu size="lg">
|
|
35
|
+
<NavHeadingMenuItem label="Dashboard" href="/dashboard" />
|
|
36
|
+
<NavHeadingMenuItem label="Analytics" href="/analytics" />
|
|
37
|
+
<NavHeadingMenuItem label="Settings" href="/settings" />
|
|
38
|
+
</NavHeadingMenu>
|
|
39
|
+
}
|
|
40
|
+
/>
|
|
41
|
+
}>
|
|
42
|
+
{null}
|
|
43
|
+
</SideNav>
|
|
44
|
+
);
|
|
45
|
+
}
|