@astryxdesign/cli 0.1.2-canary.d8e5285 → 0.1.2-canary.dd00d23
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/index.mjs +0 -1
- package/src/commands/build-theme.mjs +0 -117
- package/src/index.mjs +0 -1
- package/src/lib/error-codes.mjs +0 -8
- package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +5 -0
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +4 -1
- package/templates/blocks/components/Lightbox/LightboxShowcase.tsx +3 -4
- package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +5 -0
- package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.doc.mjs +14 -0
- package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.tsx +64 -0
- package/templates/blocks/components/TreeListItem/TreeListItemShowcase.doc.mjs +14 -0
- package/templates/blocks/components/TreeListItem/TreeListItemShowcase.tsx +60 -0
- package/src/api/blog.mjs +0 -192
- package/src/api/blog.test.mjs +0 -144
- package/src/commands/blog.mjs +0 -60
- package/src/commands/build-theme.watch.test.mjs +0 -157
- package/src/lib/site.mjs +0 -20
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.doc.mjs +0 -14
- package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +0 -28
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.doc.mjs +0 -14
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.tsx +0 -34
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.doc.mjs +0 -14
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.tsx +0 -21
- package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.tsx +0 -15
- package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.doc.mjs +0 -14
- package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.tsx +0 -16
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.doc.mjs +0 -19
- package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.tsx +0 -34
- package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.tsx +0 -30
- package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.doc.mjs +0 -14
- package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.tsx +0 -31
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.doc.mjs +0 -14
- package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.tsx +0 -26
- package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.doc.mjs +0 -14
- package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.tsx +0 -32
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.doc.mjs +0 -18
- package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.tsx +0 -32
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.tsx +0 -27
- package/templates/blocks/components/ContextMenu/ContextMenuBasic.doc.mjs +0 -14
- package/templates/blocks/components/ContextMenu/ContextMenuBasic.tsx +0 -32
- package/templates/blocks/components/DialogHeader/DialogHeaderBasic.doc.mjs +0 -14
- package/templates/blocks/components/DialogHeader/DialogHeaderBasic.tsx +0 -30
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.tsx +0 -27
- package/templates/blocks/components/FieldLabel/FieldLabelBasic.doc.mjs +0 -14
- package/templates/blocks/components/FieldLabel/FieldLabelBasic.tsx +0 -20
- package/templates/blocks/components/FieldStatus/FieldStatusBasic.doc.mjs +0 -14
- package/templates/blocks/components/FieldStatus/FieldStatusBasic.tsx +0 -23
- package/templates/blocks/components/FileInput/FileInputBasic.doc.mjs +0 -14
- package/templates/blocks/components/FileInput/FileInputBasic.tsx +0 -22
- package/templates/blocks/components/GridSpan/GridSpanColumns.doc.mjs +0 -14
- package/templates/blocks/components/GridSpan/GridSpanColumns.tsx +0 -38
- package/templates/blocks/components/HStack/HStackBasic.doc.mjs +0 -14
- package/templates/blocks/components/HStack/HStackBasic.tsx +0 -16
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.doc.mjs +0 -14
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx +0 -57
- package/templates/blocks/components/InputGroup/InputGroupBasic.doc.mjs +0 -14
- package/templates/blocks/components/InputGroup/InputGroupBasic.tsx +0 -27
- package/templates/blocks/components/LayoutContent/LayoutContentBasic.doc.mjs +0 -23
- package/templates/blocks/components/LayoutContent/LayoutContentBasic.tsx +0 -40
- package/templates/blocks/components/LayoutFooter/LayoutFooterActions.doc.mjs +0 -22
- package/templates/blocks/components/LayoutFooter/LayoutFooterActions.tsx +0 -41
- package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.doc.mjs +0 -23
- package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.tsx +0 -40
- package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.doc.mjs +0 -22
- package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.tsx +0 -37
- package/templates/blocks/components/Lightbox/LightboxGallery.doc.mjs +0 -14
- package/templates/blocks/components/Lightbox/LightboxGallery.tsx +0 -53
- package/templates/blocks/components/Lightbox/LightboxVideo.doc.mjs +0 -14
- package/templates/blocks/components/Lightbox/LightboxVideo.tsx +0 -27
- package/templates/blocks/components/Lightbox/LightboxZoom.doc.mjs +0 -14
- package/templates/blocks/components/Lightbox/LightboxZoom.tsx +0 -33
- package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.tsx +0 -19
- package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.doc.mjs +0 -23
- package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.tsx +0 -42
- package/templates/blocks/components/NavIcon/NavIconBasic.doc.mjs +0 -14
- package/templates/blocks/components/NavIcon/NavIconBasic.tsx +0 -16
- package/templates/blocks/components/RadioListItem/RadioListItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/RadioListItem/RadioListItemBasic.tsx +0 -30
- package/templates/blocks/components/Resizable/ResizableSidebar.doc.mjs +0 -25
- package/templates/blocks/components/Resizable/ResizableSidebar.tsx +0 -67
- package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.tsx +0 -21
- package/templates/blocks/components/SelectorOption/SelectorOptionBasic.doc.mjs +0 -14
- package/templates/blocks/components/SelectorOption/SelectorOptionBasic.tsx +0 -39
- package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.doc.mjs +0 -14
- package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.tsx +0 -55
- package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.doc.mjs +0 -14
- package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.tsx +0 -38
- package/templates/blocks/components/SideNavItem/SideNavItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/SideNavItem/SideNavItemBasic.tsx +0 -49
- package/templates/blocks/components/SideNavSection/SideNavSectionBasic.doc.mjs +0 -14
- package/templates/blocks/components/SideNavSection/SideNavSectionBasic.tsx +0 -71
- package/templates/blocks/components/StackItem/StackItemFill.doc.mjs +0 -14
- package/templates/blocks/components/StackItem/StackItemFill.tsx +0 -28
- package/templates/blocks/components/TabMenu/TabMenuBasic.doc.mjs +0 -14
- package/templates/blocks/components/TabMenu/TabMenuBasic.tsx +0 -23
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.doc.mjs +0 -14
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.tsx +0 -47
- package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.doc.mjs +0 -14
- package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.tsx +0 -22
- package/templates/blocks/components/TopNavItem/TopNavItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/TopNavItem/TopNavItemBasic.tsx +0 -21
- package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.doc.mjs +0 -20
- package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.tsx +0 -46
- package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.doc.mjs +0 -14
- package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.tsx +0 -16
- package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.tsx +0 -26
- package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.doc.mjs +0 -14
- package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.tsx +0 -43
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.doc.mjs +0 -14
- package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.tsx +0 -43
- package/templates/blocks/components/VStack/VStackBasic.doc.mjs +0 -14
- package/templates/blocks/components/VStack/VStackBasic.tsx +0 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.2-canary.
|
|
3
|
+
"version": "0.1.2-canary.dd00d23",
|
|
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.2-canary.
|
|
79
|
-
"@astryxdesign/lab": "0.1.2-canary.
|
|
80
|
-
"@astryxdesign/theme-neutral": "0.1.2-canary.
|
|
78
|
+
"@astryxdesign/core": "0.1.2-canary.dd00d23",
|
|
79
|
+
"@astryxdesign/lab": "0.1.2-canary.dd00d23",
|
|
80
|
+
"@astryxdesign/theme-neutral": "0.1.2-canary.dd00d23",
|
|
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.2-canary.
|
|
96
|
-
"@astryxdesign/lab": "0.1.2-canary.
|
|
97
|
-
"@astryxdesign/theme-neutral": "0.1.2-canary.
|
|
95
|
+
"@astryxdesign/core": "0.1.2-canary.dd00d23",
|
|
96
|
+
"@astryxdesign/lab": "0.1.2-canary.dd00d23",
|
|
97
|
+
"@astryxdesign/theme-neutral": "0.1.2-canary.dd00d23",
|
|
98
98
|
"gpt-tokenizer": "^2.0.0"
|
|
99
99
|
},
|
|
100
100
|
"scripts": {
|
package/src/api/index.mjs
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
|
|
22
22
|
export {component} from './component.mjs';
|
|
23
23
|
export {docs} from './docs.mjs';
|
|
24
|
-
export {blog} from './blog.mjs';
|
|
25
24
|
export {discover} from './discover.mjs';
|
|
26
25
|
export {template} from './template.mjs';
|
|
27
26
|
export {themeAdd, listThemes} from './theme-add.mjs';
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import * as fs from 'node:fs';
|
|
16
16
|
import * as path from 'node:path';
|
|
17
17
|
import {pathToFileURL, fileURLToPath} from 'node:url';
|
|
18
|
-
import {spawn} from 'node:child_process';
|
|
19
18
|
import {createJiti} from 'jiti';
|
|
20
19
|
import {getRunPrefix} from '../utils/package-manager.mjs';
|
|
21
20
|
import {
|
|
@@ -598,104 +597,6 @@ function validatePrivateVars(themeDef) {
|
|
|
598
597
|
return errors;
|
|
599
598
|
}
|
|
600
599
|
|
|
601
|
-
/**
|
|
602
|
-
* Path to this CLI's real entry (bin/astryx.mjs), resolved from this module's
|
|
603
|
-
* location (src/commands/build-theme.mjs → ../../bin/astryx.mjs). Used to
|
|
604
|
-
* re-invoke `theme build` as a child process in watch mode.
|
|
605
|
-
*/
|
|
606
|
-
function resolveCliBin() {
|
|
607
|
-
const commandsDir = path.dirname(fileURLToPath(import.meta.url));
|
|
608
|
-
return path.resolve(commandsDir, '../../bin/astryx.mjs');
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/**
|
|
612
|
-
* Run a single `theme build` as a child process, reusing the exact
|
|
613
|
-
* single-build code path (and its error handling) rather than duplicating it.
|
|
614
|
-
* Resolves with the child's exit code; never rejects.
|
|
615
|
-
*
|
|
616
|
-
* @param {string} file - The theme file argument, as the user passed it.
|
|
617
|
-
* @param {object} options - Parsed command options (only `out` is forwarded).
|
|
618
|
-
* @returns {Promise<number>}
|
|
619
|
-
*/
|
|
620
|
-
function runThemeBuildOnceChild(file, options) {
|
|
621
|
-
const cliBin = resolveCliBin();
|
|
622
|
-
const args = [cliBin, 'theme', 'build', file];
|
|
623
|
-
if (options.out) args.push('--out', options.out);
|
|
624
|
-
return new Promise(resolve => {
|
|
625
|
-
const child = spawn(process.execPath, args, {
|
|
626
|
-
stdio: 'inherit',
|
|
627
|
-
env: process.env,
|
|
628
|
-
});
|
|
629
|
-
child.on('close', code => resolve(code ?? 0));
|
|
630
|
-
child.on('error', () => resolve(1));
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Watch a theme file and rebuild on change. Runs an initial build, then
|
|
636
|
-
* rebuilds (debounced) whenever the file changes, until interrupted with
|
|
637
|
-
* Ctrl-C. Each rebuild runs in a child process so a build error (which the
|
|
638
|
-
* single-build path reports via a hard exit) is contained and the watcher
|
|
639
|
-
* keeps running.
|
|
640
|
-
*
|
|
641
|
-
* @param {string} file - The theme file argument, as the user passed it.
|
|
642
|
-
* @param {string} filePath - Absolute path to the theme file.
|
|
643
|
-
* @param {object} options - Parsed command options.
|
|
644
|
-
* @returns {Promise<void>} Resolves when the watcher is stopped (Ctrl-C).
|
|
645
|
-
*/
|
|
646
|
-
async function runThemeBuildWatch(file, filePath, options) {
|
|
647
|
-
const rel = path.relative(process.cwd(), filePath);
|
|
648
|
-
|
|
649
|
-
// Initial build.
|
|
650
|
-
await runThemeBuildOnceChild(file, options);
|
|
651
|
-
|
|
652
|
-
humanLog(`\n👀 Watching ${rel} for changes — press Ctrl-C to stop.`);
|
|
653
|
-
|
|
654
|
-
let building = false;
|
|
655
|
-
let queued = false;
|
|
656
|
-
let debounce = null;
|
|
657
|
-
|
|
658
|
-
const rebuild = async () => {
|
|
659
|
-
if (building) {
|
|
660
|
-
// Coalesce changes that land mid-build into a single follow-up run.
|
|
661
|
-
queued = true;
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
building = true;
|
|
665
|
-
humanLog(`\n♻️ Change detected — rebuilding ${rel}...`);
|
|
666
|
-
await runThemeBuildOnceChild(file, options);
|
|
667
|
-
building = false;
|
|
668
|
-
humanLog(`\n👀 Watching ${rel} for changes — press Ctrl-C to stop.`);
|
|
669
|
-
if (queued) {
|
|
670
|
-
queued = false;
|
|
671
|
-
rebuild();
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
|
|
675
|
-
// Some editors replace the file (rename) rather than writing in place, which
|
|
676
|
-
// can drop the watch. Watch the containing directory and filter to our file
|
|
677
|
-
// so edits survive atomic-save/rename.
|
|
678
|
-
const watchDir = path.dirname(filePath);
|
|
679
|
-
const baseName = path.basename(filePath);
|
|
680
|
-
const watcher = fs.watch(watchDir, (_eventType, changed) => {
|
|
681
|
-
if (changed && changed !== baseName) return;
|
|
682
|
-
clearTimeout(debounce);
|
|
683
|
-
// Debounce: editors often emit several events per save.
|
|
684
|
-
debounce = setTimeout(rebuild, 100);
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
await new Promise(resolve => {
|
|
688
|
-
const stop = () => {
|
|
689
|
-
clearTimeout(debounce);
|
|
690
|
-
watcher.close();
|
|
691
|
-
humanLog('\nStopped watching.');
|
|
692
|
-
resolve();
|
|
693
|
-
};
|
|
694
|
-
process.once('SIGINT', stop);
|
|
695
|
-
process.once('SIGTERM', stop);
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
|
|
699
600
|
export function registerTheme(program) {
|
|
700
601
|
const theme = program
|
|
701
602
|
.command('theme')
|
|
@@ -720,10 +621,6 @@ export function registerTheme(program) {
|
|
|
720
621
|
.command('build <file>')
|
|
721
622
|
.description('Compile a defineTheme file to CSS + JS')
|
|
722
623
|
.option('-o, --out <path>', 'Output CSS file path')
|
|
723
|
-
.option(
|
|
724
|
-
'-w, --watch',
|
|
725
|
-
'Rebuild automatically when the theme file changes (Ctrl-C to stop)',
|
|
726
|
-
)
|
|
727
624
|
.action(async (file, options) => {
|
|
728
625
|
const filePath = path.resolve(process.cwd(), file);
|
|
729
626
|
const json = program.opts().json || false;
|
|
@@ -733,20 +630,6 @@ export function registerTheme(program) {
|
|
|
733
630
|
return;
|
|
734
631
|
}
|
|
735
632
|
|
|
736
|
-
// Watch mode: run an initial build, then rebuild on every change to the
|
|
737
|
-
// theme file. Watch is a human-interactive, long-running mode — it is not
|
|
738
|
-
// supported in --json (machine) mode, which expects a single envelope.
|
|
739
|
-
if (options.watch) {
|
|
740
|
-
if (json) {
|
|
741
|
-
cliError('--watch is not supported with --json', {
|
|
742
|
-
code: ERROR_CODES.ERR_THEME_INVALID,
|
|
743
|
-
});
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
await runThemeBuildWatch(file, filePath, options);
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
633
|
if (!json) humanLog(`\nBuilding theme from ${path.relative(process.cwd(), filePath)}...`);
|
|
751
634
|
|
|
752
635
|
// Extract theme definition
|
package/src/index.mjs
CHANGED
|
@@ -245,7 +245,6 @@ const commands = [
|
|
|
245
245
|
{name: 'init', path: './commands/init.mjs', register: 'registerInit'},
|
|
246
246
|
{name: 'component', path: './commands/component/index.mjs', register: 'registerComponent'},
|
|
247
247
|
{name: 'docs', path: './commands/docs.mjs', register: 'registerDocs'},
|
|
248
|
-
{name: 'blog', path: './commands/blog.mjs', register: 'registerBlog'},
|
|
249
248
|
{name: 'swizzle', path: './commands/swizzle.mjs', register: 'registerSwizzle'},
|
|
250
249
|
// agent-docs folded into init — functions still importable from agent-docs.mjs
|
|
251
250
|
{name: 'template', path: './commands/template.mjs', register: 'registerTemplate'},
|
package/src/lib/error-codes.mjs
CHANGED
|
@@ -75,8 +75,6 @@
|
|
|
75
75
|
* | 'ERR_INVALID_VERSION'
|
|
76
76
|
* | 'ERR_DEP_MISSING'
|
|
77
77
|
* | 'ERR_GH_CLI'
|
|
78
|
-
* | 'ERR_UNKNOWN_POST'
|
|
79
|
-
* | 'ERR_FETCH_FAILED'
|
|
80
78
|
* | 'ERR_LAYOUT_PARSE'
|
|
81
79
|
* | 'ERR_LAYOUT_INVALID'
|
|
82
80
|
* )} ErrorCode
|
|
@@ -183,12 +181,6 @@ export const ERROR_CODES = Object.freeze({
|
|
|
183
181
|
/** GitHub CLI (`gh`) is not installed or not authenticated. */
|
|
184
182
|
ERR_GH_CLI: 'ERR_GH_CLI',
|
|
185
183
|
|
|
186
|
-
// ── Blog (read via the published RSS feed) ───────────────────────
|
|
187
|
-
/** No blog post matched the requested slug in the feed. */
|
|
188
|
-
ERR_UNKNOWN_POST: 'ERR_UNKNOWN_POST',
|
|
189
|
-
/** A network fetch (RSS feed or post text) failed. */
|
|
190
|
-
ERR_FETCH_FAILED: 'ERR_FETCH_FAILED',
|
|
191
|
-
|
|
192
184
|
// ── Layout expressions (XLE/XLO) ─────────────────────────────────
|
|
193
185
|
/** A layout expression failed to parse (syntax error, with line/col). */
|
|
194
186
|
ERR_LAYOUT_PARSE: 'ERR_LAYOUT_PARSE',
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
'use client';
|
|
4
4
|
|
|
5
|
+
import {useState} from 'react';
|
|
5
6
|
import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
|
|
6
7
|
|
|
7
8
|
export default function DropdownMenuShowcase() {
|
|
9
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
10
|
+
|
|
8
11
|
return (
|
|
9
12
|
<DropdownMenu
|
|
13
|
+
isMenuOpen={isMenuOpen}
|
|
14
|
+
onOpenChange={setIsMenuOpen}
|
|
10
15
|
button={{label: 'Actions'}}
|
|
11
16
|
items={[
|
|
12
17
|
{label: 'Edit', onClick: () => {}},
|
|
@@ -65,7 +65,10 @@ const TrashIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
|
|
65
65
|
|
|
66
66
|
export default function DropdownMenuItemShowcase() {
|
|
67
67
|
return (
|
|
68
|
-
<DropdownMenu
|
|
68
|
+
<DropdownMenu
|
|
69
|
+
button={{label: 'Actions'}}
|
|
70
|
+
isMenuOpen
|
|
71
|
+
onOpenChange={() => {}}>
|
|
69
72
|
<DropdownMenuItem
|
|
70
73
|
icon={PencilIcon}
|
|
71
74
|
label="Edit"
|
|
@@ -12,10 +12,9 @@ export default function LightboxShowcase() {
|
|
|
12
12
|
isOpen={isOpen}
|
|
13
13
|
onOpenChange={setIsOpen}
|
|
14
14
|
media={{
|
|
15
|
-
src: 'https://
|
|
16
|
-
alt: '
|
|
17
|
-
caption:
|
|
18
|
-
'A scenic coastline with waves rolling onto a sandy beach beneath a clear sky.',
|
|
15
|
+
src: 'https://picsum.photos/id/10/1200/800',
|
|
16
|
+
alt: 'Forest path',
|
|
17
|
+
caption: 'A winding path through the forest',
|
|
19
18
|
}}
|
|
20
19
|
/>
|
|
21
20
|
</>
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
'use client';
|
|
4
4
|
|
|
5
|
+
import {useState} from 'react';
|
|
5
6
|
import {MoreMenu} from '@astryxdesign/core/MoreMenu';
|
|
6
7
|
|
|
7
8
|
export default function MoreMenuShowcase() {
|
|
9
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
10
|
+
|
|
8
11
|
return (
|
|
9
12
|
<MoreMenu
|
|
13
|
+
isMenuOpen={isMenuOpen}
|
|
14
|
+
onOpenChange={setIsMenuOpen}
|
|
10
15
|
items={[
|
|
11
16
|
{label: 'Edit', onClick: () => {}},
|
|
12
17
|
{label: 'Duplicate', onClick: () => {}},
|
|
@@ -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: 'TreeListBranches',
|
|
7
|
+
name: 'TreeListBranches',
|
|
8
|
+
displayName: 'Tree List Branches',
|
|
9
|
+
description: 'Tree with expandable branches at multiple nesting levels.',
|
|
10
|
+
isReady: true,
|
|
11
|
+
isShowcase: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['TreeList'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {TreeList} from '@astryxdesign/core/TreeList';
|
|
6
|
+
|
|
7
|
+
const noop = () => {};
|
|
8
|
+
|
|
9
|
+
export default function TreeListBranchesShowcase() {
|
|
10
|
+
return (
|
|
11
|
+
<TreeList
|
|
12
|
+
items={[
|
|
13
|
+
{
|
|
14
|
+
id: 'design-system',
|
|
15
|
+
label: 'Design System',
|
|
16
|
+
isExpanded: true,
|
|
17
|
+
children: [
|
|
18
|
+
{
|
|
19
|
+
id: 'components',
|
|
20
|
+
label: 'Components',
|
|
21
|
+
isExpanded: true,
|
|
22
|
+
children: [
|
|
23
|
+
{
|
|
24
|
+
id: 'inputs',
|
|
25
|
+
label: 'Inputs',
|
|
26
|
+
children: [
|
|
27
|
+
{id: 'text-input', label: 'TextInput', onClick: noop},
|
|
28
|
+
{id: 'checkbox', label: 'Checkbox', onClick: noop},
|
|
29
|
+
{id: 'selector', label: 'Selector', onClick: noop},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'layout',
|
|
34
|
+
label: 'Layout',
|
|
35
|
+
children: [
|
|
36
|
+
{id: 'stack', label: 'Stack', onClick: noop},
|
|
37
|
+
{id: 'grid', label: 'Grid', onClick: noop},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'tokens',
|
|
44
|
+
label: 'Tokens',
|
|
45
|
+
children: [
|
|
46
|
+
{id: 'colors', label: 'Colors', onClick: noop},
|
|
47
|
+
{id: 'spacing', label: 'Spacing', onClick: noop},
|
|
48
|
+
{id: 'typography', label: 'Typography', onClick: noop},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'utilities',
|
|
55
|
+
label: 'Utilities',
|
|
56
|
+
children: [
|
|
57
|
+
{id: 'hooks', label: 'Hooks', onClick: noop},
|
|
58
|
+
{id: 'helpers', label: 'Helpers', onClick: noop},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
]}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -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: 'TreeListItem',
|
|
7
|
+
name: 'TreeListItem',
|
|
8
|
+
displayName: 'Tree List Item',
|
|
9
|
+
description: 'Tree items with descriptions, icons, badges, selection, disabled state, and nested expand/collapse. TreeList is data-driven: no composed children API.',
|
|
10
|
+
isReady: true,
|
|
11
|
+
isShowcase: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['TreeList', 'Badge', 'Icon', 'Layout', 'Text'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {TreeList} from '@astryxdesign/core/TreeList';
|
|
6
|
+
import {Badge} from '@astryxdesign/core/Badge';
|
|
7
|
+
import {Icon} from '@astryxdesign/core/Icon';
|
|
8
|
+
|
|
9
|
+
const noop = () => {};
|
|
10
|
+
|
|
11
|
+
export default function TreeListItemShowcase() {
|
|
12
|
+
return (
|
|
13
|
+
<TreeList
|
|
14
|
+
style={{width: 400}}
|
|
15
|
+
items={[
|
|
16
|
+
{
|
|
17
|
+
id: 'inbox',
|
|
18
|
+
label: 'Inbox',
|
|
19
|
+
description: '12 unread messages',
|
|
20
|
+
isExpanded: true,
|
|
21
|
+
startContent: <Icon icon="info" size="sm" />,
|
|
22
|
+
endContent: <Badge label="12" variant="info" />,
|
|
23
|
+
children: [
|
|
24
|
+
{
|
|
25
|
+
id: 'primary',
|
|
26
|
+
label: 'Primary',
|
|
27
|
+
isSelected: true,
|
|
28
|
+
startContent: <Icon icon="check" size="sm" />,
|
|
29
|
+
onClick: noop,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'updates',
|
|
33
|
+
label: 'Updates',
|
|
34
|
+
endContent: <Badge label="4" variant="info" />,
|
|
35
|
+
onClick: noop,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'drafts',
|
|
41
|
+
label: 'Drafts',
|
|
42
|
+
description: '3 draft messages',
|
|
43
|
+
startContent: <Icon icon="clock" size="sm" />,
|
|
44
|
+
onClick: noop,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'archive',
|
|
48
|
+
label: 'Archive',
|
|
49
|
+
onClick: noop,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'spam',
|
|
53
|
+
label: 'Spam',
|
|
54
|
+
isDisabled: true,
|
|
55
|
+
startContent: <Icon icon="warning" size="sm" />,
|
|
56
|
+
},
|
|
57
|
+
]}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
package/src/api/blog.mjs
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file Programmatic API for the blog command.
|
|
5
|
-
*
|
|
6
|
-
* The blog is read the same way any feed reader reads it: over the published
|
|
7
|
-
* RSS feed. Listing parses the feed; reading a post fetches the plaintext
|
|
8
|
-
* (.txt) alternate the feed advertises for each item. Nothing here touches the
|
|
9
|
-
* blog's source files — the CLI is just a consumer of the public feed, so the
|
|
10
|
-
* blog's structure can change freely without touching the CLI.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import {AstryxError} from './error.mjs';
|
|
14
|
-
import {ERROR_CODES} from '../lib/error-codes.mjs';
|
|
15
|
-
import {SITE_URL, SITE_ORIGIN} from '../lib/site.mjs';
|
|
16
|
-
|
|
17
|
-
/** Abort a feed/post fetch that hangs, and cap how much we'll read. */
|
|
18
|
-
const FETCH_TIMEOUT_MS = 15000;
|
|
19
|
-
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB — a blog feed is never larger.
|
|
20
|
-
|
|
21
|
-
const FEED_URL = new URL('/rss.xml', SITE_URL).toString();
|
|
22
|
-
|
|
23
|
-
async function fetchText(url) {
|
|
24
|
-
const controller = new AbortController();
|
|
25
|
-
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
26
|
-
let res;
|
|
27
|
-
try {
|
|
28
|
-
res = await fetch(url, {
|
|
29
|
-
signal: controller.signal,
|
|
30
|
-
redirect: 'follow',
|
|
31
|
-
});
|
|
32
|
-
} catch (e) {
|
|
33
|
-
clearTimeout(timer);
|
|
34
|
-
const reason = e.name === 'AbortError' ? 'timed out' : e.message;
|
|
35
|
-
throw new AstryxError(
|
|
36
|
-
`Could not reach ${url}: ${reason}`,
|
|
37
|
-
[],
|
|
38
|
-
ERROR_CODES.ERR_FETCH_FAILED,
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
clearTimeout(timer);
|
|
42
|
-
if (!res.ok) {
|
|
43
|
-
throw new AstryxError(
|
|
44
|
-
`Request to ${url} failed with ${res.status}`,
|
|
45
|
-
[],
|
|
46
|
-
ERROR_CODES.ERR_FETCH_FAILED,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
const body = await res.text();
|
|
50
|
-
if (body.length > MAX_BYTES) {
|
|
51
|
-
throw new AstryxError(
|
|
52
|
-
`Response from ${url} exceeded ${MAX_BYTES} bytes`,
|
|
53
|
-
[],
|
|
54
|
-
ERROR_CODES.ERR_FETCH_FAILED,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
return body;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Defense in depth: a post's plaintext URL comes from feed content. Require it
|
|
62
|
-
* to live on the canonical origin so even a tampered feed can't redirect the
|
|
63
|
-
* CLI to another host.
|
|
64
|
-
*/
|
|
65
|
-
function assertCanonicalOrigin(target) {
|
|
66
|
-
let targetOrigin;
|
|
67
|
-
try {
|
|
68
|
-
targetOrigin = new URL(target).origin;
|
|
69
|
-
} catch {
|
|
70
|
-
throw new AstryxError(
|
|
71
|
-
`Post has an invalid plaintext URL: "${target}"`,
|
|
72
|
-
[],
|
|
73
|
-
ERROR_CODES.ERR_FETCH_FAILED,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
if (targetOrigin !== SITE_ORIGIN) {
|
|
77
|
-
throw new AstryxError(
|
|
78
|
-
`Refusing to fetch post text from a non-canonical origin (${targetOrigin})`,
|
|
79
|
-
[],
|
|
80
|
-
ERROR_CODES.ERR_FETCH_FAILED,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function unescapeXml(value) {
|
|
86
|
-
return value
|
|
87
|
-
.replace(/</g, '<')
|
|
88
|
-
.replace(/>/g, '>')
|
|
89
|
-
.replace(/"/g, '"')
|
|
90
|
-
.replace(/'/g, "'")
|
|
91
|
-
.replace(/&/g, '&');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function tag(item, name) {
|
|
95
|
-
const m = item.match(new RegExp(`<${name}[^>]*>([\\s\\S]*?)</${name}>`));
|
|
96
|
-
return m ? unescapeXml(m[1].trim()) : '';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function tagAll(item, name) {
|
|
100
|
-
const out = [];
|
|
101
|
-
const re = new RegExp(`<${name}[^>]*>([\\s\\S]*?)</${name}>`, 'g');
|
|
102
|
-
let m;
|
|
103
|
-
while ((m = re.exec(item))) out.push(unescapeXml(m[1].trim()));
|
|
104
|
-
return out;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Extract the plaintext alternate href from an <item>. */
|
|
108
|
-
function textHref(item) {
|
|
109
|
-
// Match the atom:link alternate regardless of attribute order/quoting; then
|
|
110
|
-
// confirm it's the text/plain alternate before trusting the href.
|
|
111
|
-
const re = /<atom:link\b[^>]*?\/?>/g;
|
|
112
|
-
let m;
|
|
113
|
-
while ((m = re.exec(item))) {
|
|
114
|
-
const el = m[0];
|
|
115
|
-
if (/rel\s*=\s*["']alternate["']/.test(el) &&
|
|
116
|
-
/type\s*=\s*["']text\/plain["']/.test(el)) {
|
|
117
|
-
const href = el.match(/href\s*=\s*["']([^"']+)["']/);
|
|
118
|
-
if (href) return unescapeXml(href[1]);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Derive a slug from a post link (last path segment). */
|
|
125
|
-
function slugFromLink(link) {
|
|
126
|
-
try {
|
|
127
|
-
const path = new URL(link).pathname.replace(/\/$/, '');
|
|
128
|
-
return path.slice(path.lastIndexOf('/') + 1);
|
|
129
|
-
} catch {
|
|
130
|
-
return link;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function parseFeed(xml) {
|
|
135
|
-
const items = [];
|
|
136
|
-
const re = /<item>([\s\S]*?)<\/item>/g;
|
|
137
|
-
let m;
|
|
138
|
-
while ((m = re.exec(xml))) {
|
|
139
|
-
const raw = m[1];
|
|
140
|
-
const link = tag(raw, 'link');
|
|
141
|
-
items.push({
|
|
142
|
-
slug: slugFromLink(link),
|
|
143
|
-
title: tag(raw, 'title'),
|
|
144
|
-
description: tag(raw, 'description'),
|
|
145
|
-
date: tag(raw, 'pubDate'),
|
|
146
|
-
type: tag(raw, 'category'),
|
|
147
|
-
authors: tagAll(raw, 'author'),
|
|
148
|
-
link,
|
|
149
|
-
textUrl: textHref(raw),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
return items;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* List posts (from the feed), or read one post (via its .txt alternate).
|
|
157
|
-
* Both envelopes carry `feedUrl` so a caller can hit the RSS feed directly.
|
|
158
|
-
* The feed is always the canonical site — there is no user-supplied URL.
|
|
159
|
-
*
|
|
160
|
-
* @param {string} [slug]
|
|
161
|
-
* @returns {Promise<{type: string, data: unknown}>}
|
|
162
|
-
*/
|
|
163
|
-
export async function blog(slug) {
|
|
164
|
-
const xml = await fetchText(FEED_URL);
|
|
165
|
-
const posts = parseFeed(xml);
|
|
166
|
-
|
|
167
|
-
if (!slug) {
|
|
168
|
-
return {type: 'blog.list', data: {feedUrl: FEED_URL, posts}};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const normalized = slug.toLowerCase();
|
|
172
|
-
const post = posts.find(p => p.slug.toLowerCase() === normalized);
|
|
173
|
-
if (!post) {
|
|
174
|
-
throw new AstryxError(
|
|
175
|
-
`No blog post with slug "${slug}"`,
|
|
176
|
-
posts.map(p => ({name: p.slug, reason: 'available post'})),
|
|
177
|
-
ERROR_CODES.ERR_UNKNOWN_POST,
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (!post.textUrl) {
|
|
182
|
-
throw new AstryxError(
|
|
183
|
-
`Post "${slug}" has no plaintext alternate in the feed`,
|
|
184
|
-
[],
|
|
185
|
-
ERROR_CODES.ERR_FETCH_FAILED,
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
assertCanonicalOrigin(post.textUrl);
|
|
190
|
-
const text = await fetchText(post.textUrl);
|
|
191
|
-
return {type: 'blog.detail', data: {...post, feedUrl: FEED_URL, text}};
|
|
192
|
-
}
|