How the Astryx CLI `layout expand` Command Works and What Grammar It Uses
The astryx layout expand command validates a layout expression written in Astryx's compact (XLE) or outline (XLO) syntax, expands it to a fully-typed TSX component, and optionally writes the result to a file.
In the facebook/astryx repository, the layout expand CLI command bridges high-level layout declarations with production-ready React components. Understanding this pipeline—including its dual-surface grammar and block-module system—helps developers generate maintainable UI scaffolding from concise expressions.
CLI Architecture and Execution Flow
The command is implemented as a seven-stage pipeline. Each stage is traceable to specific source files in packages/cli/.
Stage 1: Command Registration
registerLayout() in packages/cli/clients/cli/commands/layout.mjs defines the layout expand subcommand. It accepts flags including --file, --form, --name, and --loose. This file also handles the global --json flag for machine-readable output.
Stage 2: Expression Input
readExpression() reads the layout string from three possible sources, with a 5 MiB size limit:
- Positional argument
--filepath- Stdin (via
-)
Source: packages/cli/clients/cli/commands/layout.mjs (lines 48-95)
Stage 3: Analysis and Validation
layoutExpand() calls analyze() from packages/cli/api/layout/_adapter.mjs. This parses the expression, binds component references via validate.mjs, and collects errors and warnings before expansion proceeds.
Source: packages/cli/api/layout/expand/expand.mjs (lines 98-102)
Stage 4: Block Module Resolution
All {block} hints in the AST are collected via collectHintNames(). Blocks operate in two modes:
- Import-mode: References application components
- Splice-mode: Reads template blocks, strips asset references, and inlines the result
Source: packages/cli/api/layout/expand/expand.mjs (lines 31-84)
Stage 5: AST to TSX Expansion
expand(doc, registry, options) in packages/cli/foundation/xle/expand.mjs performs the core transformation:
- Walks the validated AST
- Generates JSX tags
- Creates
useStatescaffolding - Resolves slot props
- Emits complete TSX plus metadata (components used, state count, TODOs)
Stage 6: Optional File Write
When a target path is provided, assertWithin() guarantees the write stays within the current working directory. The file is written via fs.writeFileSync.
Source: packages/cli/api/layout/expand/expand.mjs (lines 21-38)
Stage 7: Result Return
The command returns a typed envelope {type:'layout.expand', data:{…}}. Human mode prints formatted results; --json mode emits raw JSON.
Source: packages/cli/clients/cli/commands/layout.mjs (lines 122-150)
The XLE/XLO Grammar System
Astryx supports two surface syntaxes that compile to the same internal AST. The grammar implementation lives in packages/cli/foundation/xle/parse.mjs.
Compact Syntax (XLE)
XLE is one-line, whitespace-insensitive, and Emmet-inspired. Tokens separate at depth-0 whitespace; groups use (); repeats use *N.
V[g6] > C{card-callout}*4
This expands to: a VStack with gap=6 containing four Card components, each with the card-callout block hint.
Outline Syntax (XLO)
XLO is multi-line and indentation-based. It supports explicit constructs for chains (>), slots (@slot:), repeats (repeat N:), and an overlays: section for open-state wiring.
Layout > LayoutHeader pad=0
@title "Dashboard"
LayoutContent
Card
Core Grammar Tokens
The parser recognizes these token types in order of precedence:
| Token | Syntax | Purpose |
|---|---|---|
| ID | #identifier |
Element ID |
| Modifier | .modifier |
Enum/class modifier |
| Payload | "text" or 'text' |
Content payload |
| Attributes | [key=value ...] |
Attribute block |
| Hint | {block-name} |
Block/module reference |
| Repeat | *N |
Repeat count |
| Selected | ! |
Selected state flag |
Attribute Token Grammar
Attribute tokens support:
key=valuepairs- Flags:
+flag - Slots:
@slot=value - Shorthands:
p6(padding),g{min:2}(gap with constraints) - Escaped counters:
$
Lexical Rules
- Identifiers start with a letter; subsequent characters may be letters, digits,
-, or_ - Whitespace is ignored outside quoted strings
- Brackets
()[]{}<>increase parsing depth; parsing terminates at depth-0 whitespace
Parser Dispatch
detectForm() determines whether input is compact or outline. The appropriate parser—parseCompact() or parseOutline()—builds the XLE AST using these node types:
XLENode: Single elementXLEGroup: Parenthesized groupXLEItem: Repeatable unit
Both surfaces share the same attribute-token grammar, so validate.mjs and expand.mjs operate independently of the original dialect.
Practical Usage Examples
Basic Compact Expression
astryx layout expand "V[g6] > C{card-callout}*4"
Human-mode output shows warnings, the written path, and component list.
File-Based Outline with Custom Name
astryx layout expand --file my-layout.txt --name DashboardLayout ./src/DashboardLayout.tsx
Contents of my-layout.txt:
Layout > LayoutHeader pad=0
@title "Dashboard"
LayoutContent
Card
JSON Output for CI Integration
astryx --json layout expand "V[g6] > C{card-callout}*4"
{
"type": "layout.expand",
"data": {
"form": "compact",
"code": "// Generated by `astryx layout expand` …",
"componentsUsed": ["VStack", "Card"],
"states": 2,
"todos": [],
"warnings": [],
"written": null
}
}
Grammar Verification with check
astryx layout check "V[g6] > C{card-callout}*4"
Output:
✓ Valid (parsed as compact)
compact:
V[g6] > C{card-callout}*4
outline:
VStack gap=6
Card {card-callout}
Source File Reference
| File | Purpose |
|---|---|
packages/cli/clients/cli/commands/layout.mjs |
CLI registration and I/O handling |
packages/cli/api/layout/expand/expand.mjs |
Core expansion orchestration |
packages/cli/foundation/xle/parse.mjs |
Dual-syntax grammar parser |
packages/cli/foundation/xle/expand.mjs |
AST-to-TSX code generator |
packages/cli/foundation/xle/validate.mjs |
Component binding and attribute validation |
packages/cli/api/layout/_adapter.mjs |
Shared analysis layer |
Summary
astryx layout expandis a seven-stage pipeline from raw expression to TSX output- XLE (compact) and XLO (outline) are surface syntaxes with identical semantics
- The grammar supports IDs, modifiers, payloads, attributes, block hints, repeats, and selection flags
- Block modules resolve as either imports or inline splices depending on hint context
- The
expand()function infoundation/xle/expand.mjsgeneratesuseStatescaffolding and JSX
Frequently Asked Questions
What is the difference between XLE and XLO syntax?
XLE is compact and line-oriented, ideal for quick CLI usage and embedding in scripts. XLO is verbose and indentation-based, better for version control and complex layouts with overlays. Both parse to the same AST, so validation and expansion behavior is identical.
How does the layout expand command handle block references?
Blocks in {braces} are collected via collectHintNames() and resolved as either import-mode (application components) or splice-mode (template blocks read and inlined without assets). This happens in packages/cli/api/layout/expand/expand.mjs before TSX generation.
Can I use layout expand in CI/CD pipelines?
Yes. Pass --json for machine-parseable output. The command exits with non-zero status on validation errors, and the JSON envelope includes warnings, todos, and componentsUsed for automated checks.
Where is the grammar formally defined?
The grammar implementation is in packages/cli/foundation/xle/parse.mjs. It uses recursive descent with depth tracking for bracket matching. No separate grammar file exists; the parser is hand-written with explicit token precedence for component names, #id, .mod, payloads, attributes, hints, repeats, and flags.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →