@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.1.4-canary.39ffd21",
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.39ffd21",
79
- "@astryxdesign/lab": "0.1.4-canary.39ffd21",
80
- "@astryxdesign/theme-neutral": "0.1.4-canary.39ffd21",
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.39ffd21",
96
- "@astryxdesign/lab": "0.1.4-canary.39ffd21",
97
- "@astryxdesign/theme-neutral": "0.1.4-canary.39ffd21",
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": {
@@ -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.split('/').pop() === dirName);
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));
@@ -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.split('/').pop()?.toLowerCase();
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 = path.join('.claude', 'CLAUDE.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(/\/Badge\//);
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(/\/Avatar\//);
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(/\/Card\//);
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(/\/Stack\//);
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
- if (pkg?.exports?.[`./${componentName}`]) {
341
- return `@astryxdesign/core/${componentName}`;
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
- if (pkg?.exports?.[`./${topDir}`]) {
352
- return `@astryxdesign/core/${topDir}`;
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
+ }