diff --git a/.claude/AGENT-NAMING-GUIDE.md b/.claude/AGENT-NAMING-GUIDE.md index 4c43b7a..a097f22 100644 --- a/.claude/AGENT-NAMING-GUIDE.md +++ b/.claude/AGENT-NAMING-GUIDE.md @@ -4,7 +4,7 @@ ## Naming Convention -All 55 agents use unique, hyphenated names (e.g., `frontend-developer`, `figma-react-converter`). There are no naming conflicts in the current agent set. +All 56 agents use unique, hyphenated names (e.g., `frontend-developer`, `figma-react-converter`). There are no naming conflicts in the current agent set. Agent files live in `.claude/agents/` as `.md`. diff --git a/.claude/CUSTOM-AGENTS-GUIDE.md b/.claude/CUSTOM-AGENTS-GUIDE.md index 346bb65..c98ca9c 100644 --- a/.claude/CUSTOM-AGENTS-GUIDE.md +++ b/.claude/CUSTOM-AGENTS-GUIDE.md @@ -1,7 +1,7 @@ # Custom Agents Guide **Last Updated:** 2026-06-11 -**Total Agents:** 55 +**Total Agents:** 56 **Location:** `.claude/agents/` Agents are auto-selected by Claude Code based on task context, or you can request one explicitly. @@ -44,6 +44,7 @@ Agents are auto-selected by Claude Code based on task context, or you can reques | astro-converter | Design-to-Astro hybrid conversion (zero-JS .astro statics + React islands) | Building Astro apps where most components are static and a few are interactive React islands | | conversation-designer | Natural-language descriptions → concrete design decisions, design briefs, and HTML mockups for Figma generation | Powering /build-from-conversation: authoring design-brief.json and the mockups captured into Figma | | asset-cataloger | Image/asset semantic mapping and validation | Mapping hash-named exports to meaningful names, validating asset usage | +| indesign-to-react | InDesign (IDML/PDF) → typed React components, design tokens, and Storybook stories via @aurelius/pipeline | Converting an exported `.idml` or PDF handoff into a React starting point | ## Testing & QA @@ -141,7 +142,7 @@ User: "Use the accessibility-auditor agent to check this page" ## Agent + Skill Integration -Agents work alongside the 22 custom skills in `.claude/skills/`: +Agents work alongside the 23 custom skills in `.claude/skills/`: | Agent | Complementary Skill | |-------|-------------------| diff --git a/.claude/agents/indesign-to-react.md b/.claude/agents/indesign-to-react.md new file mode 100644 index 0000000..39d3aab --- /dev/null +++ b/.claude/agents/indesign-to-react.md @@ -0,0 +1,72 @@ +--- +name: indesign-to-react +description: Converts Adobe InDesign sources (exported .idml packages or PDFs) into typed React components, design tokens, and Storybook stories using the @aurelius/pipeline InDesign pipeline. Reads the generation report and proposes concrete follow-ups (unmapped frames, font fallbacks, missing alt text, semantic-tag refinements). Non-destructive: works on a feature branch, never commits to main. +tools: Write, Read, MultiEdit, Bash, Grep, Glob, AskUserQuestion, TaskOutput, TodoWrite +model: opus +permissionMode: bypassPermissions +--- + +You are an InDesign-to-React conversion specialist. You turn the artifacts designers actually deliver — an exported InDesign **IDML** package or a **PDF** — into a coherent, typed React component set with design tokens and Storybook stories, then guide the developer through the manual touch-ups print-to-web always needs. + +You orchestrate the `@aurelius/pipeline` package (the IDML/PDF parser, the design-token mapper, and the React component generator). You do not re-implement parsing or generation — you run the pipeline, read its reports, and act on them. + +## Operating principles + +- **Non-destructive.** Always work on a feature branch (e.g. `indesign/`). Never write to `main`. Never overwrite a developer's hand-edited components without confirming. +- **Deterministic.** The generator produces byte-identical output for the same input; reruns must not churn unrelated files. +- **Honest about fidelity.** PDF and even IDML are reconstructed heuristically. Surface what was inferred vs. read; never claim pixel-perfection. + +## Procedure + +### 1. Validate input + +- Confirm the input is a `.idml` or `.pdf`. If both a `.idml` and a `.pdf` exist for the same design, prefer IDML (richer style metadata) unless the user asks for PDF parity (`--source-priority pdf`). +- Check the file exists and is readable. For IDML, it must be a zip with a `designmap.xml`. + +### 2. Create a feature branch + +```bash +git checkout -b indesign/ +``` + +### 3. Run the pipeline + +```bash +# Build the pipeline package once if needed. +pnpm --filter @aurelius/pipeline build +# Convert: parse → tokens → components, into the chosen output dir. +node packages/pipeline/dist/pipeline-cli.js pipeline indesign \ + --target --styling --output +``` + +Honor `aurelius.config.json` (`indesign` section) for the default target, styling, and output when present; explicit flags win. + +### 4. Review the generation report + +Read `/indesign-pipeline-report.md` (and the `.json` for machine use). Turn each section into concrete, prioritized follow-ups: + +- **Accessibility TODOs** — every `` ships with an empty `alt`. Propose real alt text per image (or mark decorative with `alt=""`), and flag heading-order issues. +- **Unmapped IR nodes** — graphic/vector frames with no JSX mapping. Suggest whether to drop them, replace with a background, or hand-build. +- **Font fallbacks** — families resolved by name to a web stack. Ask the developer to confirm the substitution or supply a `--font-map`/web font. +- **Out-of-gamut colors** (from the parse warnings) — CMYK/Lab colors clamped to sRGB may shift; confirm brand colors. +- **Semantic-tag refinements** — the role→tag inference (`h1`–`h6`/`p`/`figcaption`) is heuristic; review headings and lists. + +### 5. Verify + +- Type-check the generated components (`tsc --noEmit` in the host project, or rely on the pipeline's CI smoke). +- Render a Storybook story or the component to confirm it mounts. + +### 6. Summarize and commit + +- Commit the generated files and a short summary of the follow-ups on the feature branch. +- Present the developer a checklist of the touch-ups (alt text, font confirmation, unmapped frames) and the report location. Do not merge to `main`. + +## What you do not do + +- You do not guarantee pixel-perfect reconstruction — generated layout is a token-spaced, semantic flow meant for manual refinement. +- You do not edit the `@aurelius/pipeline` source to "fix" a single conversion; file an issue if the parser/generator is wrong. + +## Related + +- Skill: `indesign-conversion` (when-to-use, prerequisites, gotchas, worked example). +- Docs: `docs/pipelines/indesign.md` (exporting, fidelity, accessibility checklist, troubleshooting). diff --git a/.claude/skills/README.md b/.claude/skills/README.md index c40756f..dd4f020 100644 --- a/.claude/skills/README.md +++ b/.claude/skills/README.md @@ -1,7 +1,7 @@ # Skills Catalog **Last Updated:** 2026-06-11 -**Total Skills:** 22 +**Total Skills:** 23 **Location:** `.claude/skills/` Skills are documentation-based workflows that trigger automatically when relevant keywords appear in conversation. They provide systematic guidance, not tool integrations. @@ -138,6 +138,14 @@ These skills provide patterns and best practices. They trigger on relevant keywo - **Triggers:** `/export-design-system`, "export design system", "publishable component library" - **Output:** `packages/` pnpm workspace (tokens + component library) +### InDesign Skills + +#### 23. indesign-conversion + +- **Purpose:** Converts an exported InDesign IDML package or PDF into typed React components, design tokens (Tailwind preset + `tokens.css`/`tokens.ts` + Style Dictionary JSON), and Storybook stories via the `@aurelius/pipeline` InDesign pipeline; reads the generation report to propose follow-ups. +- **Triggers:** "InDesign to React", "IDML", "PDF to React", "indesign pipeline", "brochure to React", "aurelius pipeline indesign" +- **Output:** A React project (`components`, `stories`, `index.ts`, `tokens/`, extracted assets, plus Markdown + JSON reports) + --- ## Pipeline Flow diff --git a/.claude/skills/indesign-conversion/SKILL.md b/.claude/skills/indesign-conversion/SKILL.md new file mode 100644 index 0000000..3a4a56f --- /dev/null +++ b/.claude/skills/indesign-conversion/SKILL.md @@ -0,0 +1,64 @@ +--- +name: indesign-conversion +description: Convert an exported Adobe InDesign IDML package or PDF into typed React components, design tokens, and Storybook stories via the @aurelius/pipeline InDesign pipeline. Use when a designer hands you an .idml or .pdf and you need a starting React component set. Keywords: InDesign to React, IDML, PDF to React, indesign pipeline, design tokens from InDesign, brochure to React, print to web, aurelius pipeline indesign +--- + +# InDesign Conversion + +## Overview + +This skill drives the InDesign pipeline end to end: it parses an exported **IDML** package or **PDF** into a normalized intermediate representation (IR), maps the styles and swatches to design tokens (a Tailwind preset, `tokens.css`, `tokens.ts`, and a Style Dictionary JSON), and generates typed React components with Storybook stories. It pairs with the `indesign-to-react` agent, which reviews the generation report and proposes follow-ups. + +## When to use + +- A designer delivered an exported **`.idml`** or **`.pdf`** (the normal handoff) and you want a styled React starting point rather than rebuilding by hand. +- You need design tokens (colors, type scale, spacing) derived directly from the InDesign document. + +Do **not** use it for live Figma/Canva designs (use those pipelines) or when you need pixel-perfect reconstruction — this produces a usable, token-driven scaffold for manual refinement. + +## Prerequisites + +- An exported **`.idml`** (File → Export → InDesign Markup) or a **PDF** exported from InDesign. Source `.indd` files are not supported — export first. +- The `@aurelius/pipeline` package built (`pnpm --filter @aurelius/pipeline build`). +- For best fidelity, prefer IDML when available — it carries named paragraph/character styles and swatches that PDF lacks. + +## Expected outputs + +Into `` (default `./src/indesign`): + +- `*.tsx` — one typed component per spread, plus `*.stories.tsx` and an `index.ts` barrel. +- `tokens/` — `tokens.ts`, `tokens.css`, `tailwind.preset.ts`, `design-tokens.json`. +- `public/indesign/` — extracted images, referenced by the components. +- `indesign-pipeline-report.md` and `indesign-pipeline-report.json` — produced files, staged assets, unmapped frames, and accessibility TODOs. + +## Worked example + +Using the committed fixture (`tests/fixtures/indesign/sample.idml`): + +```bash +pnpm --filter @aurelius/pipeline build +node packages/pipeline/dist/pipeline-cli.js pipeline indesign \ + tests/fixtures/indesign/sample.idml \ + --target react --styling tailwind --output ./out/indesign +``` + +This writes `out/indesign/Spread1.tsx` (a `
` with an `

` heading and an ``), `Spread1.stories.tsx`, `index.ts`, the token files under `out/indesign/tokens/`, and the two reports. The report's **Accessibility TODOs** section will list the hero image's missing alt text — add it before shipping. + +Then review and refine: + +1. Open `indesign-pipeline-report.md`; address each TODO (alt text, unmapped frames, font fallbacks). +2. Confirm the Tailwind preset is wired into the host project's `tailwind.config.ts` (or, for CSS Modules, that `tokens.css` is imported). +3. Render a story in Storybook to confirm the component mounts. + +## Common gotchas + +- **CMYK → sRGB shifts.** Print colors are converted to sRGB without ICC profiles; CMYK/Lab swatches may shift and out-of-gamut colors are clamped. Confirm brand colors against the design. +- **Missing / non-embedded fonts.** Print fonts are rarely web fonts; the mapper resolves families by name to a web stack (`config/font-map.json`). Confirm substitutions or supply a `--font-map`. +- **Oversized print images.** Exported images are often print-resolution. Optimize/resize the files in `public/indesign/` for the web. +- **Accessibility gaps.** Every generated `` has an empty `alt`; headings are inferred heuristically. Always do an accessibility pass. +- **PDF reconstruction.** PDFs have no named styles — text frames, columns, and style buckets are inferred from positions and font sizes. Expect to merge/split a few blocks by hand. + +## Related + +- Agent: `indesign-to-react`. +- Docs: `docs/pipelines/indesign.md`, `docs/pipeline/indesign-pdf-fidelity.md`. diff --git a/.gitattributes b/.gitattributes index a2727c6..1652c5a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,6 @@ templates/**/*.tpl linguist-generated=true # Lock files are vendored pnpm-lock.yaml linguist-generated=true + +# Binary test fixtures (InDesign packages are zip archives) +*.idml binary diff --git a/.prettierignore b/.prettierignore index 09fad60..dcc4626 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,3 +8,4 @@ templates/ docs/ app/ *.md +*.idml diff --git a/CLAUDE.md b/CLAUDE.md index 0e2c1ee..6daba8d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,8 +17,8 @@ The framework is designed for: ``` project-root/ ├── .claude/ # Claude Code configuration -│ ├── agents/ # 55 specialized agents -│ ├── skills/ # 22 React-specific skills +│ ├── agents/ # 56 specialized agents +│ ├── skills/ # 23 React-specific skills │ ├── commands/ # Custom slash commands │ ├── hooks/ # Hook scripts (automated hooks configured in settings.json) │ └── pipeline.config.json # Pipeline thresholds, iteration limits, app types @@ -214,15 +214,15 @@ pnpm tsc --noEmit # Type check without emitting --- -### Custom Agents (55 Total) +### Custom Agents (56 Total) -55 specialized agents covering the full product lifecycle: +56 specialized agents covering the full product lifecycle: | Category | Count | Key Agents | |----------|-------|------------| | Engineering | 12 | frontend-developer, backend-architect, rapid-prototyper, test-writer-fixer, error-boundary-architect, migration-specialist, i18n-engineer, animation-optimizer, bundle-analyzer | | Design | 5 | ui-designer, ux-researcher, brand-guardian | -| Design-to-Code | 8 | figma-react-converter, canva-react-converter, astro-converter, asset-cataloger, vue-converter, svelte-converter, react-native-converter, conversation-designer | +| Design-to-Code | 9 | figma-react-converter, canva-react-converter, astro-converter, asset-cataloger, vue-converter, svelte-converter, react-native-converter, conversation-designer, indesign-to-react | | Testing & QA | 7 | visual-qa-agent, accessibility-auditor, api-tester, performance-benchmarker | | Product | 3 | sprint-prioritizer, feedback-synthesizer, trend-researcher | | Marketing | 7 | content-creator, growth-hacker, app-store-optimizer | @@ -238,7 +238,7 @@ Agents are invoked automatically based on task context. --- -### Skills (22 Total) +### Skills (23 Total) | Skill | Purpose | Triggers | |-------|---------|----------| @@ -539,6 +539,7 @@ Claude: [Uses test-writer-fixer agent] /build-from-screenshot # Full autonomous screenshot pipeline /build-from-conversation [description] # Conversational pipeline: interview → generated Figma → build /export-design-system [flags] # Export components + tokens as publishable pnpm workspace +aurelius pipeline indesign [--target ...] [--styling ...] [--output ...] # InDesign IDML/PDF → React ``` **Quality Verification:** @@ -616,7 +617,7 @@ node scripts/metrics-dashboard.js summary # Quick metrics summary --- -**Last Updated:** 2026-06-11 -**Architecture:** 55 agents, 22 skills, 4 plugins + gh CLI, Figma + Canva + Playwright MCP, 40 scripts, 8 hooks, 5 renderers (nextjs, vite, astro, sveltekit, expo) +**Last Updated:** 2026-06-20 +**Architecture:** 56 agents, 23 skills, 4 plugins + gh CLI, Figma + Canva + Playwright MCP, 40 scripts, 8 hooks, 5 renderers (nextjs, vite, astro, sveltekit, expo) > **Keeping counts in sync:** When adding or removing agents, skills, scripts, or hooks, update all count references across the project. Search for the old count number in `*.md` files to find all references: `CLAUDE.md`, `README.md`, `CONTRIBUTING.md`, `docs/onboarding/`, `docs/react-development/`, and `.claude/AGENT-NAMING-GUIDE.md`. The agent and skill counts are enforced automatically by `scripts/check-doc-counts.sh` (run in CI and on pre-commit), which recounts `.claude/agents/` and `.claude/skills/` and fails on any documented count that disagrees. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee7baf8..a37bd33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -137,7 +137,7 @@ Because the bump is derived from commit history, **conventional commit messages ## Claude Code Agents -Aurelius includes 55 specialized Claude Code agents and 22 skills that automate significant portions of the development workflow — from design-to-code conversion to testing, accessibility, and deployment. +Aurelius includes 56 specialized Claude Code agents and 23 skills that automate significant portions of the development workflow — from design-to-code conversion to testing, accessibility, and deployment. If you have Claude Code installed, these agents and skills are available to you automatically when working in this repository. They can assist with component development, test writing, visual QA, and much more. diff --git a/README.md b/README.md index 769f12a..55e20b1 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ A Claude Code-integrated multi-framework app development framework with TypeScri ## What This Framework Provides -- **55 Custom Agents** -- Specialized AI agents for engineering, design, testing, marketing, operations, and more -- **22 Development Skills** -- Automated workflows for Figma/Canva/screenshot/conversation conversion, TDD, E2E testing, visual QA, state management, forms, auth, animation, SEO, and more +- **56 Custom Agents** -- Specialized AI agents for engineering, design, testing, marketing, operations, and more +- **23 Development Skills** -- Automated workflows for Figma/Canva/screenshot/conversation conversion, TDD, E2E testing, visual QA, state management, forms, auth, animation, SEO, and more - **10-Phase Design-to-React Pipeline** -- Convert Figma or Canva designs into fully working, tested React apps with a single command -- or just describe the app and let `/build-from-conversation` generate the Figma design first - **App-Type Awareness** -- Tailored build and test strategies for web apps, Chrome extensions, and PWAs - **Testing Stack** -- Vitest, React Testing Library, Playwright (cross-browser), Storybook, and pixel-diff visual QA @@ -85,8 +85,8 @@ project-root/ │ ├── canva-to-react/ # Canva pipeline guide │ └── react-development/ # Development standards ├── .claude/ # Claude Code configuration -│ ├── agents/ # 55 custom agents -│ ├── skills/ # 22 development skills +│ ├── agents/ # 56 custom agents +│ ├── skills/ # 23 development skills │ ├── commands/ # Slash commands (/build-from-figma, /lint, /test) │ ├── pipeline.config.json # Pipeline thresholds and app-type definitions │ ├── CUSTOM-AGENTS-GUIDE.md # Agent catalog @@ -117,6 +117,18 @@ No design file needed -- describe the app and the pipeline designs it first: - Phase C1: The conversation-designer agent renders the brief as HTML mockups, which are captured into a **real Figma file** via the Figma MCP (`generate_figma_design`) - Handoff: The generated Figma URL feeds `/build-from-figma`, which runs unchanged (figma-intake fast-paths the interview) +### Build from InDesign (IDML / PDF) + +``` +aurelius pipeline indesign brochure.pdf --target react --styling tailwind --output ./src/indesign +``` + +For print-first sources, InDesign joins Figma and Canva as a first-class input. An +exported **IDML** package or **PDF** (the normal designer handoff) is parsed into the +shared IR, mapped to design tokens, and turned into typed React components plus +Storybook stories — driven by the `indesign-to-react` agent or the +`indesign-conversion` skill. See [`docs/pipelines/indesign.md`](docs/pipelines/indesign.md). + ## The Figma-to-React Pipeline ### How It Works @@ -147,7 +159,7 @@ All thresholds and behavior are configurable in `.claude/pipeline.config.json`: - Lighthouse minimums (performance: 80, accessibility: 90) - App-type-specific E2E strategies -## 55 Custom Agents +## 56 Custom Agents Agents are auto-selected by Claude Code based on your task: @@ -165,7 +177,7 @@ Agents are auto-selected by Claude Code based on your task: Full catalog: `.claude/CUSTOM-AGENTS-GUIDE.md` -## 22 Development Skills +## 23 Development Skills ### Pipeline Skills @@ -283,7 +295,7 @@ Details: `.claude/PLUGINS-REFERENCE.md` |----------|----------|-------------| | **Developer onboarding** | `docs/onboarding/README.md` | Start here -- quickstart, architecture, configuration, troubleshooting | | Quickstart guide | `docs/onboarding/quickstart.md` | Clone to running project in 10 minutes | -| Architecture overview | `docs/onboarding/architecture.md` | All 55 agents, 22 skills, 4 pipelines, and how they connect | +| Architecture overview | `docs/onboarding/architecture.md` | All 56 agents, 23 skills, 4 pipelines, and how they connect | | Pipeline configuration | `docs/onboarding/pipeline-configuration.md` | Every setting in pipeline.config.json explained | | Troubleshooting FAQ | `docs/onboarding/troubleshooting.md` | Common issues and solutions | | Project instructions | `CLAUDE.md` | Full project config for Claude Code | @@ -291,8 +303,9 @@ Details: `.claude/PLUGINS-REFERENCE.md` | React standards | `docs/react-development/README.md` | TypeScript, Tailwind, testing conventions | | Canva pipeline guide | `docs/canva-to-react/README.md` | Canva pipeline overview and troubleshooting | | Conversation pipeline guide | `docs/conversation-to-app/README.md` | Conversational app creation via generated Figma designs | -| Agent catalog | `.claude/CUSTOM-AGENTS-GUIDE.md` | All 55 agents with use cases | -| Skills catalog | `.claude/skills/README.md` | All 22 skills with triggers | +| InDesign pipeline guide | `docs/pipelines/indesign.md` | Convert exported IDML/PDF into React components, tokens, and Storybook stories | +| Agent catalog | `.claude/CUSTOM-AGENTS-GUIDE.md` | All 56 agents with use cases | +| Skills catalog | `.claude/skills/README.md` | All 23 skills with triggers | | Plugin reference | `.claude/PLUGINS-REFERENCE.md` | Plugin configuration and commands | | Scripts reference | `scripts/README.md` | All scripts with usage examples | | Templates reference | `templates/README.md` | Starter configs and how to use them | diff --git a/docs/guides/agent-creation.md b/docs/guides/agent-creation.md index fd463f1..2cc9542 100644 --- a/docs/guides/agent-creation.md +++ b/docs/guides/agent-creation.md @@ -2,7 +2,7 @@ Agents are specialized Markdown files with YAML frontmatter that live in `.claude/agents/`. Each agent defines a persona, a set of allowed tools, and detailed instructions that shape how Claude Code behaves when the agent is selected. Claude Code reads the `description` field to decide which agent best matches a given task, then loads that agent's instructions as the system prompt for the session. -This framework ships with 55 agents covering engineering, design, testing, marketing, and operations. You can add your own by following the conventions below. +This framework ships with 56 agents covering engineering, design, testing, marketing, and operations. You can add your own by following the conventions below. ## File Location diff --git a/docs/onboarding/README.md b/docs/onboarding/README.md index 13e8138..94bbb3c 100644 --- a/docs/onboarding/README.md +++ b/docs/onboarding/README.md @@ -11,7 +11,7 @@ This guide will get you productive with the framework quickly, whether you are b | Document | What You Will Learn | |----------|-------------------| | [Quickstart Guide](quickstart.md) | Clone, install, create your first project, and run your first pipeline in under 10 minutes | -| [Architecture Overview](architecture.md) | How the 55 agents, 22 skills, 4 pipelines, and 8 hooks fit together | +| [Architecture Overview](architecture.md) | How the 56 agents, 23 skills, 4 pipelines, and 8 hooks fit together | | [Pipeline Configuration](pipeline-configuration.md) | Every setting in `pipeline.config.json` explained, with examples | | [Troubleshooting FAQ](troubleshooting.md) | Common issues, error messages, and how to resolve them | | [Framework Guides](../guides/README.md) | Deep dives into design tokens, visual QA, caching, hooks, error recovery, agent creation, and framework-specific workflows | @@ -52,8 +52,8 @@ Optional (for specific workflows): - [Main README](../../README.md) -- Project overview - [Contributing Guide](../../CONTRIBUTING.md) -- Branch naming, PR process, commit conventions -- [Agent Catalog](../../.claude/CUSTOM-AGENTS-GUIDE.md) -- All 55 agents with use cases -- [Skills Catalog](../../.claude/skills/README.md) -- All 22 skills with triggers +- [Agent Catalog](../../.claude/CUSTOM-AGENTS-GUIDE.md) -- All 56 agents with use cases +- [Skills Catalog](../../.claude/skills/README.md) -- All 23 skills with triggers - [Plugin Reference](../../.claude/PLUGINS-REFERENCE.md) -- Installed plugins and commands - [Pipeline Config](../../.claude/pipeline.config.json) -- Thresholds and app-type definitions - [React Development Standards](../react-development/README.md) -- TypeScript, Tailwind, testing conventions diff --git a/docs/onboarding/architecture.md b/docs/onboarding/architecture.md index 86eb9b1..4d4721a 100644 --- a/docs/onboarding/architecture.md +++ b/docs/onboarding/architecture.md @@ -15,7 +15,7 @@ This document explains how Aurelius is structured and how its components work to ┌────────────────────┼─────────────────────┐ │ │ │ ┌───────▼──────┐ ┌────────▼────────┐ ┌────────▼────────┐ - │ 55 Agents │ │ 22 Skills │ │ 4 Plugins │ + │ 56 Agents │ │ 23 Skills │ │ 4 Plugins │ │ (specialized │ │ (workflow │ │ (extensions: │ │ task workers) │ │ automation) │ │ memory, git, │ └───────┬──────┘ └────────┬────────┘ │ superpowers) │ @@ -38,7 +38,7 @@ This document explains how Aurelius is structured and how its components work to --- -## Agents (55 Total) +## Agents (56 Total) Agents are specialized Claude Code sub-processes that handle complex, multi-step tasks. Each agent is a markdown file in `.claude/agents/` with frontmatter defining its tools, capabilities, and instructions. Claude Code selects agents automatically based on your task context. @@ -144,7 +144,7 @@ Agents are specialized Claude Code sub-processes that handle complex, multi-step --- -## Skills (22 Total) +## Skills (23 Total) Skills are automated workflows triggered by slash commands or keyword detection. Unlike agents (which are general-purpose workers), skills encode specific multi-step processes. diff --git a/docs/onboarding/quickstart.md b/docs/onboarding/quickstart.md index 4479d15..c874c51 100644 --- a/docs/onboarding/quickstart.md +++ b/docs/onboarding/quickstart.md @@ -108,7 +108,7 @@ Captures the page, analyzes it with vision, and builds a working app. Supports a ## 5. Using Claude Code Agents -When you work with Claude Code in this repository, **55 specialized agents** are available automatically. You do not need to invoke them manually -- Claude Code selects the right agent based on your task. +When you work with Claude Code in this repository, **56 specialized agents** are available automatically. You do not need to invoke them manually -- Claude Code selects the right agent based on your task. ### Examples @@ -187,8 +187,8 @@ The pipeline auto-detects the framework from `package.json` if `outputTarget` is ``` Aurelius/ ├── .claude/ -│ ├── agents/ # 55 specialized agents -│ ├── skills/ # 22 development skills +│ ├── agents/ # 56 specialized agents +│ ├── skills/ # 23 development skills │ ├── commands/ # Slash commands (/build-from-figma, /lint, /test) │ ├── hooks/ # Hook scripts (8 automated hooks) │ ├── pipeline.config.json # All pipeline thresholds and behavior diff --git a/docs/pipelines/indesign.md b/docs/pipelines/indesign.md new file mode 100644 index 0000000..18400f9 --- /dev/null +++ b/docs/pipelines/indesign.md @@ -0,0 +1,108 @@ +# InDesign → React Pipeline + +Convert Adobe InDesign designs — brochures, pitch decks, editorial layouts — into +typed React components, design tokens, and Storybook stories. InDesign joins +Figma and Canva as a first-class Aurelius design-to-code source. + +Both inputs designers actually deliver are **first-class**: + +- **IDML** (`.idml`) — preferred. Carries named paragraph/character styles, + swatches, fonts, frames, and master pages. +- **PDF** (`.pdf`) — the most common real-world handoff. Structure is + reconstructed heuristically (see [PDF fidelity](./../pipeline/indesign-pdf-fidelity.md)). + +> We do not aim for pixel-perfect reconstruction. The goal is a usable, styled, +> token-driven scaffold that the React generator produces and a developer +> refines. + +## Quick start + +```bash +pnpm --filter @aurelius/pipeline build + +# Designer handed you a PDF (or an IDML) — generate a React project: +node packages/pipeline/dist/pipeline-cli.js pipeline indesign brochure.pdf \ + --target react --styling tailwind --output ./src/indesign +``` + +Or drive it conversationally with the **`indesign-to-react` agent** (it runs the +pipeline on a feature branch and proposes follow-ups from the report). + +## Exporting from InDesign + +**IDML (preferred):** `File → Export…`, choose **InDesign Markup (IDML)**. This +is a zip of XML with full style/swatch/frame metadata. + +**PDF:** `File → Export…`, choose **Adobe PDF**. For the best text extraction, +**do not outline/flatten text** (outlined type becomes vector paths with no +extractable text). Embed or keep standard fonts; keep images at a reasonable +resolution. + +## Output + +Into `` (default `./src/indesign`): + +| Path | Contents | +| ---- | -------- | +| `*.tsx` | One typed component per spread | +| `*.stories.tsx` | A Storybook story per component, with extracted content as default args | +| `index.ts` | Barrel re-exporting every component | +| `tokens/` | `tokens.ts`, `tokens.css`, `tailwind.preset.ts`, `design-tokens.json` | +| `public/indesign/` | Extracted images referenced by the components | +| `indesign-pipeline-report.md` / `.json` | Files, assets, unmapped frames, a11y TODOs | + +Components carry typed props (`heading?`, `body?`, `image?`, …) defaulted to the +extracted content, so you override content while keeping layout. + +## CLI + +``` +aurelius pipeline indesign [options] + + --target next | vite | astro | react (default react) + --styling tailwind | css-modules (default tailwind) + --output output directory (default ./src/indesign) + --json print the machine-readable JSON report on stdout + -h, --help show usage +``` + +Defaults are read from `aurelius.config.json` (`{ "indesign": { "target", +"styling", "output" } }`) when present; explicit flags win. + +## Fidelity expectations + +- **Layout** is a semantic, token-spaced flow (flex column; grid for multi-column), + not absolute pixel positioning. +- **Colors** convert CMYK/Lab/RGB to sRGB without ICC profiles; out-of-gamut + colors are clamped and flagged. +- **Fonts** are resolved by name to web stacks; print fonts are rarely web fonts. +- **PDF** specifically reconstructs text frames, columns, and heading/body/caption + buckets heuristically — see the [PDF fidelity guide](./../pipeline/indesign-pdf-fidelity.md). + +## Accessibility checklist + +Generated components are a starting point — always run an accessibility pass: + +- [ ] Add real `alt` text to every `` (the generator emits empty `alt`); mark + decorative images `alt=""`. +- [ ] Verify heading order (`h1` → `h2` → …); the role→tag inference is heuristic. +- [ ] Confirm color contrast after the CMYK→sRGB conversion. +- [ ] Replace any `figcaption` not associated with a `figure` where appropriate. +- [ ] Check that reconstructed reading order matches the intended order. + +## Troubleshooting + +| Symptom | Cause / fix | +| ------- | ----------- | +| `VECTOR_ONLY_PAGE` warning, no text | Text was outlined in the PDF, or the page is pure vector art. Re-export the PDF without outlining text, or provide the IDML. | +| Wrong fonts in the output | `NO_EMBEDDED_FONTS` — confirm the mapped web stack or pass `--font-map`. | +| Colors look off | CMYK/Lab → sRGB shift; confirm brand colors against the design. | +| Images missing | Pass `--output` so images extract to `public/indesign/`, and move that folder into your project's `public/`. | +| Components don't pick up tokens | Wire `tokens/tailwind.preset.ts` into your `tailwind.config.ts`, or import `tokens/tokens.css` for the CSS Modules path. | + +## See also + +- [PDF fidelity guide](./../pipeline/indesign-pdf-fidelity.md) +- Agent: `.claude/agents/indesign-to-react.md` +- Skill: `.claude/skills/indesign-conversion/SKILL.md` +- Package: [`@aurelius/pipeline`](../../packages/pipeline/README.md) diff --git a/docs/react-development/README.md b/docs/react-development/README.md index 17a2384..45647f3 100644 --- a/docs/react-development/README.md +++ b/docs/react-development/README.md @@ -175,8 +175,8 @@ project-root/ ├── templates/ # Starter configs (shared, Next.js, Vite, Chrome ext) ├── docs/ # Documentation └── .claude/ # Claude Code configuration - ├── agents/ # 55 custom agents - ├── skills/ # 22 development skills + ├── agents/ # 56 custom agents + ├── skills/ # 23 development skills └── pipeline.config.json ``` @@ -203,8 +203,8 @@ pnpm build - `docs/figma-to-react/README.md` -- Figma-to-React conversion pipeline - `docs/canva-to-react/README.md` -- Canva-to-React conversion pipeline -- `.claude/skills/README.md` -- Skills catalog (22 skills) -- `.claude/CUSTOM-AGENTS-GUIDE.md` -- Agent catalog (55 agents) +- `.claude/skills/README.md` -- Skills catalog (23 skills) +- `.claude/CUSTOM-AGENTS-GUIDE.md` -- Agent catalog (56 agents) - `.claude/PLUGINS-REFERENCE.md` -- Plugin reference - `scripts/README.md` -- Scripts reference - `templates/README.md` -- Template configs reference diff --git a/packages/pipeline/package.json b/packages/pipeline/package.json index 2b5a03a..543b84e 100644 --- a/packages/pipeline/package.json +++ b/packages/pipeline/package.json @@ -6,6 +6,7 @@ "type": "module", "license": "MIT", "bin": { + "aurelius": "./dist/pipeline-cli.js", "aurelius-indesign": "./dist/indesign/cli.js" }, "exports": { diff --git a/packages/pipeline/src/pipeline-cli.ts b/packages/pipeline/src/pipeline-cli.ts new file mode 100644 index 0000000..1e11702 --- /dev/null +++ b/packages/pipeline/src/pipeline-cli.ts @@ -0,0 +1,264 @@ +#!/usr/bin/env node +/** + * `aurelius pipeline indesign` — end-to-end CLI. + * + * Orchestrates the InDesign pipeline: parse (IDML or PDF) → map design tokens → + * generate React components, writing a project tree plus human-readable Markdown + * and machine-readable JSON reports. Honors `aurelius.config.json` for defaults. + * + * `runPipelineCli` is pure (returns captured output, no `process.exit`) so it is + * unit-testable; the module tail wires it to `process` when run as `aurelius`. + */ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { isAbsolute, join } from "node:path"; +import { pathToFileURL } from "node:url"; +import { generateComponents } from "./react/index.js"; +import type { Framework, GenerationResult, StyleMode } from "./react/model.js"; +import { parseSourceFile } from "./source.js"; +import { emitAll } from "./tokens/emit.js"; +import { mapDocumentToTokens } from "./tokens/mapper.js"; + +export interface PipelineCliResult { + code: number; + stdout: string; + stderr: string; +} + +export type Target = "react" | "next" | "vite" | "astro"; + +const TARGETS: Target[] = ["react", "next", "vite", "astro"]; +const STYLINGS: StyleMode[] = ["tailwind", "css-modules"]; + +const USAGE = `aurelius pipeline indesign — convert an InDesign IDML or PDF into a React project + +Usage: + aurelius pipeline indesign [options] + +Options: + --target Output target: next | vite | astro | react (default react) + --styling Styling mode: tailwind | css-modules (default tailwind) + --output Output directory (default ./src/indesign) + --json Print the machine-readable JSON report on stdout + -h, --help Show this help + +Writes components, Storybook stories, design tokens, extracted assets, and both +a Markdown and a JSON report into . Honors aurelius.config.json +({ "indesign": { "target", "styling", "output" } }) when present; flags win. +`; + +interface ParsedArgs { + input?: string; + target?: Target; + styling?: StyleMode; + output?: string; + json: boolean; + help: boolean; + error?: string; +} + +function parseArgs(argv: string[]): ParsedArgs { + const args: ParsedArgs = { json: false, help: false }; + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + switch (arg) { + case "--json": + args.json = true; + break; + case "-h": + case "--help": + args.help = true; + break; + case "--target": { + const value = argv[++i]; + if (value && (TARGETS as string[]).includes(value)) args.target = value as Target; + else + args.error = `Invalid --target: ${value ?? "(missing)"} (expected ${TARGETS.join("|")})`; + break; + } + case "--styling": { + const value = argv[++i]; + if (value && (STYLINGS as string[]).includes(value)) args.styling = value as StyleMode; + else + args.error = `Invalid --styling: ${value ?? "(missing)"} (expected ${STYLINGS.join("|")})`; + break; + } + case "--output": { + const value = argv[++i]; + if (value) args.output = value; + else args.error = "--output requires a directory"; + break; + } + default: + if (arg && arg.startsWith("-")) args.error = `Unknown option: ${arg}`; + else if (arg && args.input === undefined) args.input = arg; + } + } + return args; +} + +interface AureliusConfig { + target?: Target; + styling?: StyleMode; + output?: string; +} + +/** Best-effort load of `aurelius.config.json` (`indesign` section or top-level). */ +function loadConfig(cwd: string): AureliusConfig { + const path = join(cwd, "aurelius.config.json"); + if (!existsSync(path)) return {}; + try { + const raw = JSON.parse(readFileSync(path, "utf-8")) as Record; + const section = (raw.indesign ?? raw) as AureliusConfig; + const config: AureliusConfig = {}; + if (section.target && (TARGETS as string[]).includes(section.target)) + config.target = section.target; + if (section.styling && (STYLINGS as string[]).includes(section.styling)) + config.styling = section.styling; + if (typeof section.output === "string") config.output = section.output; + return config; + } catch { + return {}; + } +} + +function frameworkFor(target: Target): Framework { + return target === "next" ? "next" : "react"; +} + +/** Run `aurelius pipeline indesign …`, returning captured output and exit code. */ +export async function runPipelineCli( + argv: string[], + cwd: string = process.cwd(), +): Promise { + // Allow the full `pipeline indesign …` form or just the flags. + let rest = argv; + if (rest[0] === "pipeline") rest = rest.slice(1); + if (rest[0] === "indesign") rest = rest.slice(1); + + const args = parseArgs(rest); + if (args.error) return { code: 2, stdout: "", stderr: `${args.error}\n\n${USAGE}` }; + if (args.help) return { code: 0, stdout: USAGE, stderr: "" }; + if (!args.input) + return { code: 2, stdout: "", stderr: `Error: no input file provided\n\n${USAGE}` }; + + const config = loadConfig(cwd); + const target = args.target ?? config.target ?? "react"; + const styling = args.styling ?? config.styling ?? "tailwind"; + const output = args.output ?? config.output ?? join("src", "indesign"); + const outDir = isAbsolute(output) ? output : join(cwd, output); + const assetDir = join(outDir, "public", "indesign"); + + let document; + try { + const result = await parseSourceFile(args.input, { assetDir }); + document = result.document; + } catch (err) { + return { code: 1, stdout: "", stderr: `Error: ${toMessage(err)}\n` }; + } + + const tokenResult = mapDocumentToTokens(document); + const tokenFiles = emitAll(tokenResult.tokens); + const generation = generateComponents(document, tokenResult, { + styleMode: styling, + framework: frameworkFor(target), + }); + const jsonReport = buildJsonReport(generation, document.meta.source, target, styling); + + try { + mkdirSync(outDir, { recursive: true }); + for (const file of generation.files) writeFileSync(join(outDir, file.path), file.contents); + const tokensDir = join(outDir, "tokens"); + mkdirSync(tokensDir, { recursive: true }); + for (const [name, contents] of Object.entries(tokenFiles)) { + writeFileSync(join(tokensDir, name), contents); + } + writeFileSync( + join(outDir, "indesign-pipeline-report.json"), + JSON.stringify(jsonReport, null, 2), + ); + } catch (err) { + return { + code: 1, + stdout: "", + stderr: `Error: cannot write to "${output}": ${toMessage(err)}\n`, + }; + } + + if (args.json) { + return { code: 0, stdout: `${JSON.stringify(jsonReport, null, 2)}\n`, stderr: "" }; + } + return { code: 0, stdout: formatSummary(jsonReport, output), stderr: "" }; +} + +interface JsonReport { + source: "idml" | "pdf" | "unknown"; + target: Target; + styling: StyleMode; + components: Array<{ name: string; files: string[] }>; + assets: GenerationResult["assets"]; + unmapped: string[]; + accessibility: string[]; +} + +function buildJsonReport( + generation: GenerationResult, + source: "idml" | "pdf" | undefined, + target: Target, + styling: StyleMode, +): JsonReport { + return { + source: source ?? "unknown", + target, + styling, + components: generation.componentNames.map((name) => ({ + name, + files: generation.files.filter((f) => f.path.startsWith(name)).map((f) => f.path), + })), + assets: generation.assets, + unmapped: generation.unmapped, + accessibility: generation.a11y, + }; +} + +function formatSummary(report: JsonReport, output: string): string { + const lines: string[] = []; + lines.push("aurelius pipeline indesign"); + lines.push(` source: ${report.source}`); + lines.push(` target: ${report.target}`); + lines.push(` styling: ${report.styling}`); + lines.push(` output: ${output}`); + lines.push( + ` components: ${report.components.length} (${report.components.map((c) => c.name).join(", ") || "none"})`, + ); + lines.push(` assets: ${report.assets.length}`); + lines.push(` unmapped: ${report.unmapped.length}`); + lines.push(` a11y TODOs: ${report.accessibility.length}`); + lines.push( + ` reports: ${join(output, "indesign-pipeline-report.md")}, indesign-pipeline-report.json`, + ); + if (report.accessibility.length > 0) { + lines.push("", "Accessibility TODOs"); + for (const item of report.accessibility) lines.push(` ${item}`); + } + return `${lines.join("\n")}\n`; +} + +function toMessage(err: unknown): string { + return err instanceof Error ? err.message : String(err); +} + +/* ── Direct-execution entry point (bin: aurelius) ────────────────────────── */ + +const invokedPath = process.argv[1]; +if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) { + runPipelineCli(process.argv.slice(2)) + .then((result) => { + if (result.stdout) process.stdout.write(result.stdout); + if (result.stderr) process.stderr.write(result.stderr); + process.exit(result.code); + }) + .catch((err: unknown) => { + process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`); + process.exit(1); + }); +} diff --git a/packages/pipeline/src/react/__tests__/compile-harness.ts b/packages/pipeline/src/react/__tests__/compile-harness.ts new file mode 100644 index 0000000..583c483 --- /dev/null +++ b/packages/pipeline/src/react/__tests__/compile-harness.ts @@ -0,0 +1,91 @@ +/** + * Shared test harness: type-check generated React files with strict `tsc` and + * server-render a generated Tailwind component. Used by the generator unit + * tests and the end-to-end smoke test. + */ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { join } from "node:path"; +import ts from "typescript"; +import type { GeneratedFile } from "../model.js"; + +const SHIMS = ` +declare module "*.module.css" { + const classes: { readonly [key: string]: string }; + export default classes; +} +declare module "@storybook/react" { + export interface Meta { component?: T; title?: string } + export type StoryObj = { args?: Record }; +} +declare module "next/image" { + import type { ComponentType } from "react"; + const Image: ComponentType<{ src: string; alt: string; width?: number; height?: number; className?: string }>; + export default Image; +} +`; + +/** Type-check generated .ts/.tsx files with strict tsc + React JSX; return errors. */ +export function compile(files: GeneratedFile[]): string[] { + // Place the temp dir under node_modules so `react`/`react/jsx-runtime` resolve. + const dir = mkdtempSync(join(process.cwd(), "node_modules", ".gen-tsc-")); + try { + const roots: string[] = []; + for (const file of files) { + if (!/\.(ts|tsx)$/.test(file.path)) continue; + const target = join(dir, file.path); + writeFileSync(target, file.contents); + roots.push(target); + } + const shim = join(dir, "shims.d.ts"); + writeFileSync(shim, SHIMS); + roots.push(shim); + + const program = ts.createProgram(roots, { + strict: true, + noEmit: true, + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + jsx: ts.JsxEmit.ReactJSX, + esModuleInterop: true, + skipLibCheck: true, + }); + return ts + .getPreEmitDiagnostics(program) + .map( + (d) => + `${d.file?.fileName.split(/[/\\]/).pop() ?? ""}: ${ts.flattenDiagnosticMessageText(d.messageText, "\n")}`, + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +/** Transpile a Tailwind component to CJS, evaluate it, and server-render it. */ +export function render(tsxSource: string, componentName: string): string { + const js = ts.transpileModule(tsxSource, { + compilerOptions: { + jsx: ts.JsxEmit.ReactJSX, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2022, + esModuleInterop: true, + }, + }).outputText; + + const nodeRequire = createRequire(import.meta.url); + const shimRequire = (id: string): unknown => { + if (id === "react") return nodeRequire("react"); + if (id === "react/jsx-runtime") return nodeRequire("react/jsx-runtime"); + throw new Error(`unexpected import: ${id}`); + }; + const mod = { exports: {} as Record }; + new Function("require", "exports", "module", js)(shimRequire, mod.exports, mod); + + const React = nodeRequire("react") as typeof import("react"); + const { renderToStaticMarkup } = nodeRequire( + "react-dom/server", + ) as typeof import("react-dom/server"); + const Component = (mod.exports[componentName] ?? mod.exports.default) as React.ComponentType; + return renderToStaticMarkup(React.createElement(Component)); +} diff --git a/packages/pipeline/src/react/__tests__/e2e.test.ts b/packages/pipeline/src/react/__tests__/e2e.test.ts new file mode 100644 index 0000000..e6d59a9 --- /dev/null +++ b/packages/pipeline/src/react/__tests__/e2e.test.ts @@ -0,0 +1,106 @@ +/** + * End-to-end smoke test: run the full `aurelius pipeline indesign` flow against + * the committed fixture and assert the generated React project builds (strict + * tsc) and renders (Storybook smoke ≈ server-render) without errors. + */ +import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { fileURLToPath } from "node:url"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { runPipelineCli } from "../../pipeline-cli.js"; +import type { GeneratedFile } from "../model.js"; +import { compile, render } from "./compile-harness.js"; + +const FIXTURE = fileURLToPath( + new URL("../../../../../tests/fixtures/indesign/sample.idml", import.meta.url), +); + +const tempDirs: string[] = []; +function tempDir(): string { + const dir = mkdtempSync(join(tmpdir(), "idml-e2e-")); + tempDirs.push(dir); + return dir; +} +afterEach(() => { + while (tempDirs.length > 0) { + const dir = tempDirs.pop(); + if (dir) rmSync(dir, { recursive: true, force: true }); + } +}); + +function readGeneratedTs(dir: string): GeneratedFile[] { + return readdirSync(dir) + .filter((name) => /\.(ts|tsx)$/.test(name)) + .map((name) => ({ path: name, contents: readFileSync(join(dir, name), "utf-8") })); +} + +describe("aurelius pipeline indesign — end to end", () => { + it("has a committed IDML fixture", () => { + expect(existsSync(FIXTURE)).toBe(true); + }); + + it("generates a React project that builds and renders from the committed fixture", async () => { + const out = join(tempDir(), "indesign"); + const result = await runPipelineCli(["pipeline", "indesign", FIXTURE, "--output", out]); + expect(result.code).toBe(0); + + // Project tree: components + stories + barrel + token files + reports. + const files = readdirSync(out); + expect(files.some((f) => f.endsWith(".tsx") && !f.includes("stories"))).toBe(true); + expect(files).toContain("index.ts"); + expect(files).toContain("indesign-pipeline-report.md"); + expect(files).toContain("indesign-pipeline-report.json"); + expect(existsSync(join(out, "tokens", "tokens.ts"))).toBe(true); + + // "Builds": the generated TypeScript type-checks under strict React JSX. + expect(compile(readGeneratedTs(out))).toEqual([]); + + // "Storybook smoke": the component server-renders without runtime errors. + const component = readdirSync(out).find((f) => f.endsWith(".tsx") && !f.includes("stories"))!; + const name = component.replace(/\.tsx$/, ""); + const html = render(readFileSync(join(out, component), "utf-8"), name); + expect(html).toContain(" { + const result = await runPipelineCli(["pipeline", "indesign", "--help"]); + expect(result.code).toBe(0); + expect(result.stdout).toContain("aurelius pipeline indesign"); + expect(result.stdout).toContain("--target"); + expect(result.stdout).toContain("--styling"); + }); + + it("emits a machine-readable JSON report with --json", async () => { + const out = join(tempDir(), "indesign"); + const result = await runPipelineCli([ + "pipeline", + "indesign", + FIXTURE, + "--output", + out, + "--json", + ]); + expect(result.code).toBe(0); + const report = JSON.parse(result.stdout); + expect(report.source).toBe("idml"); + expect(Array.isArray(report.components)).toBe(true); + expect(report.components.length).toBeGreaterThan(0); + }); + + it("supports css-modules via --styling", async () => { + const out = join(tempDir(), "indesign"); + const result = await runPipelineCli([ + "pipeline", + "indesign", + FIXTURE, + "--output", + out, + "--styling", + "css-modules", + ]); + expect(result.code).toBe(0); + expect(readdirSync(out).some((f) => f.endsWith(".module.css"))).toBe(true); + }); +}); diff --git a/packages/pipeline/src/react/__tests__/generator.test.ts b/packages/pipeline/src/react/__tests__/generator.test.ts index dac4b42..aa471c6 100644 --- a/packages/pipeline/src/react/__tests__/generator.test.ts +++ b/packages/pipeline/src/react/__tests__/generator.test.ts @@ -1,7 +1,3 @@ -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; -import { createRequire } from "node:module"; -import { join } from "node:path"; -import ts from "typescript"; import { describe, expect, it } from "vitest"; import { buildSampleIdml } from "../../indesign/__tests__/idml-fixtures.js"; import { parseIdml } from "../../indesign/parser.js"; @@ -9,88 +5,7 @@ import { mapDocumentToTokens } from "../../tokens/mapper.js"; import { imageHeavyPdf, textHeavyPdf } from "../../pdf/__tests__/pdf-fixtures.js"; import { parsePdf } from "../../pdf/parser.js"; import { generateComponents } from "../generator.js"; -import type { GeneratedFile } from "../model.js"; - -const SHIMS = ` -declare module "*.module.css" { - const classes: { readonly [key: string]: string }; - export default classes; -} -declare module "@storybook/react" { - export interface Meta { component?: T; title?: string } - export type StoryObj = { args?: Record }; -} -declare module "next/image" { - import type { ComponentType } from "react"; - const Image: ComponentType<{ src: string; alt: string; width?: number; height?: number; className?: string }>; - export default Image; -} -`; - -/** Type-check generated .ts/.tsx files with strict tsc + React JSX; return errors. */ -function compile(files: GeneratedFile[]): string[] { - // Place the temp dir under node_modules so `react`/`react/jsx-runtime` resolve. - const dir = mkdtempSync(join(process.cwd(), "node_modules", ".gen-tsc-")); - try { - const roots: string[] = []; - for (const file of files) { - if (!/\.(ts|tsx)$/.test(file.path)) continue; - const target = join(dir, file.path); - writeFileSync(target, file.contents); - roots.push(target); - } - const shim = join(dir, "shims.d.ts"); - writeFileSync(shim, SHIMS); - roots.push(shim); - - const program = ts.createProgram(roots, { - strict: true, - noEmit: true, - target: ts.ScriptTarget.ES2022, - module: ts.ModuleKind.ESNext, - moduleResolution: ts.ModuleResolutionKind.Bundler, - jsx: ts.JsxEmit.ReactJSX, - esModuleInterop: true, - skipLibCheck: true, - }); - return ts - .getPreEmitDiagnostics(program) - .map( - (d) => - `${d.file?.fileName.split(/[/\\]/).pop() ?? ""}: ${ts.flattenDiagnosticMessageText(d.messageText, "\n")}`, - ); - } finally { - rmSync(dir, { recursive: true, force: true }); - } -} - -/** Transpile a Tailwind component to CJS, evaluate it, and server-render it. */ -function render(tsxSource: string, componentName: string): string { - const js = ts.transpileModule(tsxSource, { - compilerOptions: { - jsx: ts.JsxEmit.ReactJSX, - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.ES2022, - esModuleInterop: true, - }, - }).outputText; - - const nodeRequire = createRequire(import.meta.url); - const shimRequire = (id: string): unknown => { - if (id === "react") return nodeRequire("react"); - if (id === "react/jsx-runtime") return nodeRequire("react/jsx-runtime"); - throw new Error(`unexpected import: ${id}`); - }; - const mod = { exports: {} as Record }; - new Function("require", "exports", "module", js)(shimRequire, mod.exports, mod); - - const React = nodeRequire("react") as typeof import("react"); - const { renderToStaticMarkup } = nodeRequire( - "react-dom/server", - ) as typeof import("react-dom/server"); - const Component = (mod.exports[componentName] ?? mod.exports.default) as React.ComponentType; - return renderToStaticMarkup(React.createElement(Component)); -} +import { compile, render } from "./compile-harness.js"; function idmlSample() { const { document } = parseIdml(buildSampleIdml()); diff --git a/tests/fixtures/indesign/README.md b/tests/fixtures/indesign/README.md new file mode 100644 index 0000000..9630a9b --- /dev/null +++ b/tests/fixtures/indesign/README.md @@ -0,0 +1,14 @@ +# InDesign pipeline fixtures + +`sample.idml` is a small, valid IDML package (a zip of XML) used by the +InDesign-pipeline end-to-end smoke test. It contains one spread with a heading +text frame and a linked image, a master spread, swatches, fonts, and paragraph/ +character styles — enough to exercise parse → tokens → React generation. + +It is generated from the programmatic builder +`packages/pipeline/src/indesign/__tests__/idml-fixtures.ts` (`buildSampleIdml()`), +so it stays in sync with the unit-test fixtures. To regenerate after changing the +builder, write `buildSampleIdml()`'s bytes to this path. + +PDF fixtures are generated in-memory with `pdf-lib` in the test suite +(`packages/pipeline/src/pdf/__tests__/pdf-fixtures.ts`). diff --git a/tests/fixtures/indesign/sample.idml b/tests/fixtures/indesign/sample.idml new file mode 100644 index 0000000..27745e8 Binary files /dev/null and b/tests/fixtures/indesign/sample.idml differ