v12.4: designlang pack — one polished design-system bundle#71
Merged
Manavarya09 merged 1 commit intomainfrom May 5, 2026
Merged
v12.4: designlang pack — one polished design-system bundle#71Manavarya09 merged 1 commit intomainfrom
Manavarya09 merged 1 commit intomainfrom
Conversation
Closes #59. `designlang pack <url>` runs the full extraction once and writes every emitter output into a single, signed, layered directory. One artifact a designer or dev can clone, drop into a project, or zip up and send to a client. Layout: <host>-design-system/ ├── README.md bespoke, references source + grade + at-a-glance ├── LICENSE.txt provenance + usage guidance ├── tokens/ DTCG + Tailwind + CSS vars + Figma vars + motion + theme.js ├── components/ typed React stubs (anatomy.tsx) ├── storybook/ runnable Storybook project ├── starter/ minimal HTML starter wired to tokens/variables.css ├── prompts/ v0 / Lovable / Cursor / Claude Artifacts + named recipes/*.md └── extras/ voice.json + prompt-pack.md rollup Pure composition — zero new deps, zero new analysis. Reuses formatDtcg, formatTokens, formatTailwind, formatCssVars, formatFigma, formatMotionTokens, formatReactTheme, formatAnatomyStubs, formatStorybook, buildPromptPack, generateClone (opt-in via --with-clone). Internals: - `toText()` normaliser handles the inconsistent return shapes across formatters (some return objects, some return JSON strings already). - Recipes now write as `<name>.md` files, not array indices. - 7 new tests cover directory shape, valid JSON token outputs, README content, starter wiring, recipe filenames, and a regression test for the double-stringify bug. 357/357 tests pass. Verified live on stripe.com: 40 files, 232KB, all token JSON parses cleanly.
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| try { return new URL(url).hostname; } catch { return String(url || ''); } | ||
| } | ||
|
|
||
| function exists(p) { |
There was a problem hiding this comment.
Pull request overview
Adds a new “pack” workflow to produce a single on-disk design-system bundle (tokens + components + Storybook + starter + prompts) from one extraction, and wires it into the CLI and docs for the v12.4.0 release.
Changes:
- Introduces
src/pack.jswithbuildPack(design, opts)to write a standardized pack directory structure. - Adds
designlang pack <url>CLI command to run extraction then build the pack (with optional--with-cloneand--open). - Updates docs/changelog/version and adds tests covering pack output shape and key file contents.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/formatters.test.js | Adds test coverage for buildPack() output layout, token JSON validity, README/starter content, and regression behavior. |
| src/pack.js | New pack builder that writes README/LICENSE, tokens, components, storybook project, starter, prompts, and extras. |
| README.md | Documents the new pack command in quick-start + command list. |
| package.json | Bumps package version to 12.4.0. |
| CHANGELOG.md | Adds 12.4.0 release notes describing the new pack feature. |
| bin/design-extract.js | Adds pack subcommand implementation and output messaging (including --open). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * @param {boolean} [opts.withClone=true] — include the Next.js starter | ||
| * @returns {object} { dir, files: string[] } | ||
| */ | ||
| export function buildPack(design, opts = {}) { |
Comment on lines
+322
to
+333
| // starter/ — minimal HTML by default; full Next.js when --with-clone | ||
| const starterDir = join(outDir, 'starter'); | ||
| if (opts.withClone) { | ||
| try { | ||
| generateClone(design, starterDir); | ||
| written.push(starterDir); | ||
| } catch (err) { | ||
| // Clone is best-effort — fall back to HTML starter if it errors. | ||
| mkdirSync(starterDir, { recursive: true }); | ||
| writeFile(join(starterDir, 'index.html'), buildStarter(design)); | ||
| written.push(join(starterDir, 'index.html')); | ||
| } |
Comment on lines
+185
to
+227
| const hostName = host(design.meta?.url); | ||
| const families = (design.typography?.families || []).map(f => (typeof f === 'string' ? f : f.name)).filter(Boolean); | ||
| const display = families[0] || 'system-ui'; | ||
| const heading = (design.voice?.sampleHeadings || [])[0] || `Built from ${hostName}`; | ||
| return `<!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <title>${hostName} starter</title> | ||
| <link rel="stylesheet" href="../tokens/variables.css"> | ||
| <style> | ||
| body { | ||
| margin: 0; | ||
| font-family: ${JSON.stringify(display)}, -apple-system, BlinkMacSystemFont, sans-serif; | ||
| background: var(--color-background, #fff); | ||
| color: var(--color-text, #111); | ||
| line-height: 1.5; | ||
| } | ||
| main { max-width: 840px; margin: 0 auto; padding: 64px 32px; } | ||
| h1 { font-size: clamp(40px, 6vw, 72px); line-height: 1.05; margin: 0 0 18px; } | ||
| p { font-size: 18px; max-width: 56ch; color: var(--color-text-secondary, #555); } | ||
| .cta { | ||
| display: inline-block; | ||
| padding: 14px 28px; | ||
| margin-top: 28px; | ||
| background: var(--color-primary, #0a0a0a); | ||
| color: var(--color-on-primary, #fff); | ||
| border-radius: var(--radius-md, 8px); | ||
| text-decoration: none; | ||
| font-weight: 600; | ||
| } | ||
| .cta:hover { opacity: 0.9; } | ||
| .meta { margin-top: 64px; font-size: 12px; color: #888; } | ||
| .meta a { color: inherit; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <main> | ||
| <h1>${heading}</h1> | ||
| <p>This starter is wired to the tokens in <code>tokens/variables.css</code>. Edit the variables and watch this page change. Drop in your own components and use the same tokens to keep the visual language consistent.</p> | ||
| <a class="cta" href="#">Get started</a> | ||
| <p class="meta">Generated by <a href="https://designlang.app">designlang</a>. Re-pack with <code>npx designlang pack ${hostName}</code>.</p> | ||
| </main> |
Comment on lines
+354
to
+360
| for (const recipe of pack.recipes) { | ||
| // Each recipe = { name, content }; sanitise name to a safe filename. | ||
| const safeName = String(recipe.name || 'recipe').replace(/[^a-z0-9-_]+/gi, '-').toLowerCase(); | ||
| const p = join(recipesDir, `${safeName}.md`); | ||
| writeFile(p, recipe.content); | ||
| written.push(p); | ||
| } |
Comment on lines
+45
to
+54
|
|
||
| function nameFromUrl(url) { | ||
| try { | ||
| const u = new URL(url); | ||
| return u.hostname.replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '').toLowerCase(); | ||
| } catch { | ||
| return 'design-system'; | ||
| } | ||
| } | ||
|
|
Comment on lines
+59
to
+61
| function exists(p) { | ||
| try { statSync(p); return true; } catch { return false; } | ||
| } |
| **Pack — one command, one polished design-system bundle.** | ||
|
|
||
| \`designlang pack <url>\` runs the full extraction once and writes every | ||
| emitter output into a single, signed, layered directory. One artifact a |
Comment on lines
+9
to
+10
| designer or dev can clone, drop into a project, or zip up and send to a | ||
| client. Closes [#59](https://github.com/Manavarya09/design-extract/issues/59). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #59.
`designlang pack ` runs the full extraction once and writes every emitter output into a single, signed, layered directory. One artifact a designer or dev can clone, drop into a project, or zip up and send to a client.
```bash
npx designlang pack stripe.com
→ ./stripe-com-design-system/ (40 files, 232KB)
```
Layout
```
-design-system/
├── README.md bespoke, references source + grade + at-a-glance
├── LICENSE.txt provenance + usage guidance
├── tokens/ DTCG + Tailwind + CSS vars + Figma vars + motion + theme.js
├── components/ typed React stubs (anatomy.tsx)
├── storybook/ runnable Storybook project
├── starter/ minimal HTML starter wired to tokens/variables.css
├── prompts/ v0 / Lovable / Cursor / Claude Artifacts + named recipes/*.md
└── extras/ voice.json + prompt-pack.md rollup
```
What's in the change
Test plan
Why this exists
The 17+ loose files designlang already emits are the right pieces, but asking a user to zip them themselves is friction. `pack` is the same artifacts as one polished, downloadable, cite-able bundle. Closes the gap between "extract" and "I can hand this to a teammate."
🤖 Generated with Claude Code