inkhouse 0.1.0-beta.0
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/README.md +201 -0
- package/bin/inkhouse.mjs +171 -0
- package/code.js +11802 -0
- package/manifest.json +30 -0
- package/package.json +45 -0
- package/scanner/blob-placement-regression.ts +132 -0
- package/scanner/class-collector.ts +69 -0
- package/scanner/cli.ts +336 -0
- package/scanner/component-scanner.ts +2876 -0
- package/scanner/css-patch-regression.ts +112 -0
- package/scanner/css-token-reader-regression.ts +92 -0
- package/scanner/css-token-reader.ts +477 -0
- package/scanner/font-style-resolver-regression.ts +32 -0
- package/scanner/index.ts +9 -0
- package/scanner/radial-gradient-regression.ts +53 -0
- package/scanner/style-map.ts +145 -0
- package/scanner/tailwind-parser.ts +644 -0
- package/scanner/transform-math-regression.ts +42 -0
- package/scanner/types.ts +298 -0
- package/src/blob-placement.ts +111 -0
- package/src/change-detection.ts +204 -0
- package/src/class-utils.ts +105 -0
- package/src/clip-path-decorative.ts +194 -0
- package/src/color-resolver.ts +98 -0
- package/src/colors.ts +196 -0
- package/src/component-defs.ts +54 -0
- package/src/component-gen.ts +561 -0
- package/src/component-lookup.ts +82 -0
- package/src/config.ts +115 -0
- package/src/design-system.ts +59 -0
- package/src/dev-server.ts +173 -0
- package/src/figma-globals.d.ts +3 -0
- package/src/font-style-resolver.ts +171 -0
- package/src/github.ts +1465 -0
- package/src/icon-builder.ts +607 -0
- package/src/image-cache.ts +22 -0
- package/src/inline-text.ts +271 -0
- package/src/layout-parser.ts +667 -0
- package/src/layout-utils.ts +155 -0
- package/src/main.ts +687 -0
- package/src/node-ir.ts +595 -0
- package/src/pack-provider.ts +148 -0
- package/src/packs.ts +126 -0
- package/src/radial-gradient.ts +84 -0
- package/src/render-context.ts +138 -0
- package/src/responsive-analyzer.ts +139 -0
- package/src/state-analyzer.ts +143 -0
- package/src/story-builder.ts +1706 -0
- package/src/story-layout.ts +38 -0
- package/src/tailwind.ts +2379 -0
- package/src/text-builder.ts +116 -0
- package/src/text-line.ts +42 -0
- package/src/token-source.ts +43 -0
- package/src/tokens.ts +717 -0
- package/src/transform-math.ts +44 -0
- package/src/ui-builder.ts +1996 -0
- package/src/utility-resolver.ts +125 -0
- package/src/variables.ts +1042 -0
- package/src/width-solver.ts +466 -0
- package/templates/patch-tokens-route.ts +165 -0
- package/templates/scan-components-route.ts +57 -0
- package/ui.html +1222 -0
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Inkhouse
|
|
2
|
+
|
|
3
|
+
Generates native Figma frames from your Tailwind React components and syncs design tokens back to your codebase via GitHub PRs.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
| Feature | Description |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Generate Design System Page** | Scans your Storybook stories and builds a pixel-accurate Figma page from your Tailwind classes |
|
|
10
|
+
| **Push to Code** | Creates a GitHub PR from Figma token changes. Source can be CSS-first (`auto`/`css`) or DTCG (`dtcg`). |
|
|
11
|
+
| **Detect & Sync Changes** | Detects class-level changes in Figma frames and proposes them as a GitHub PR |
|
|
12
|
+
| **Dev Mode codegen** | Shows Tailwind class names for any selected node in Figma Dev Mode |
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- **Figma Desktop** (not the browser — the plugin needs localhost access)
|
|
19
|
+
- **Node.js 18+** and **pnpm**
|
|
20
|
+
- A project with Next.js, Tailwind CSS, and Storybook stories (`.stories.tsx`)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### 1. Install the package
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add -D inkhouse
|
|
30
|
+
pnpm exec inkhouse setup
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`setup` does three things automatically:
|
|
34
|
+
- Creates `inkhouse.config.json` in your project root (auto-detects component paths from `.storybook/main.ts`)
|
|
35
|
+
- Creates the scanner API route at `src/app/api/figma/scan-components/route.ts`
|
|
36
|
+
- Creates the token patch API route at `src/app/api/figma/patch-tokens/route.ts`
|
|
37
|
+
- Adds `figma:dev` and `figma:scan` scripts to your `package.json`
|
|
38
|
+
|
|
39
|
+
### 2. Load the plugin in Figma Desktop
|
|
40
|
+
|
|
41
|
+
1. Open Figma Desktop
|
|
42
|
+
2. Go to **Plugins → Development → Import plugin from manifest...**
|
|
43
|
+
3. Select `node_modules/inkhouse/manifest.json` from your project root
|
|
44
|
+
|
|
45
|
+
Figma remembers this path — you only do this once.
|
|
46
|
+
|
|
47
|
+
### 3. Start your dev server
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm figma:dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The plugin auto-discovers your server on ports `4000`, `3000`, and `5173`.
|
|
54
|
+
|
|
55
|
+
> **The plugin will not work without the dev server running.**
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
### Generate Design System Page
|
|
62
|
+
|
|
63
|
+
1. Start dev server: `pnpm figma:dev`
|
|
64
|
+
2. Open any Figma file
|
|
65
|
+
3. **Plugins → Development → Inkhouse → Generate Design System Page**
|
|
66
|
+
4. The plugin scans your Storybook stories and builds a "Design System" page
|
|
67
|
+
|
|
68
|
+
Component data is always scanned live on every run — never stale. Re-run at any time to pick up changes.
|
|
69
|
+
|
|
70
|
+
### Manual scan
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pnpm figma:scan
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Writes `.inkhouse/component-definitions.json`. Useful for debugging scanner output.
|
|
77
|
+
|
|
78
|
+
### Push to Code (Pro)
|
|
79
|
+
|
|
80
|
+
Requires a Pro license and GitHub configuration.
|
|
81
|
+
|
|
82
|
+
1. **Plugins → Development → Inkhouse → Settings**
|
|
83
|
+
2. Fill in:
|
|
84
|
+
- **GitHub owner** — your org or username (e.g. `acme-corp`)
|
|
85
|
+
- **Repository** — repo name (e.g. `my-app`)
|
|
86
|
+
- **Base branch** — default `main`
|
|
87
|
+
- **Token Source Mode** — `auto` (default), `css`, or `dtcg`
|
|
88
|
+
- **CSS Token Path** — optional override (e.g. `src/app/tokens.css`)
|
|
89
|
+
- **DTCG Token Path** — used in `dtcg` mode and as fallback in `auto`
|
|
90
|
+
- **Also update DTCG on Push to Code** — optional; includes DTCG artifact in CSS-first pushes
|
|
91
|
+
- **Allow New Tokens from Figma** — optional; permits creating new token keys on push (off by default)
|
|
92
|
+
- **New Token Prefixes** — optional comma-separated allowlist (for example `chart-, brand-`)
|
|
93
|
+
- **GitHub token** — a [fine-grained PAT](https://github.com/settings/tokens?type=beta) with `Contents` and `Pull requests` read & write
|
|
94
|
+
- **License key** — from your Inkhouse account
|
|
95
|
+
3. **Save Settings**
|
|
96
|
+
4. **Plugins → Development → Inkhouse → Push to Code**
|
|
97
|
+
5. Enter a commit message and click **Push Tokens to Code** (it auto-detects token changes and opens the PR step)
|
|
98
|
+
6. Review the detected changes and click **Create Pull Request**
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
Edit `inkhouse.config.json` in your project root:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"componentPaths": ["src/components", "src/features"],
|
|
109
|
+
"exclude": ["ui", "index"],
|
|
110
|
+
"onlyWithStories": true
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Field | Default | Description |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `componentPaths` | auto-detected | Directories to scan for components |
|
|
117
|
+
| `exclude` | `[]` | Directory or file names to skip |
|
|
118
|
+
| `onlyWithStories` | `true` | Only include components with a `.stories.tsx` file |
|
|
119
|
+
|
|
120
|
+
The scanner re-reads this file on every scan — no server restart needed.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Troubleshooting
|
|
125
|
+
|
|
126
|
+
### "Dev server not reachable"
|
|
127
|
+
|
|
128
|
+
The plugin cannot connect to `localhost:4000`, `:3000`, or `:5173`.
|
|
129
|
+
|
|
130
|
+
- Make sure `pnpm figma:dev` is running
|
|
131
|
+
- Check the terminal for errors in the Next.js server
|
|
132
|
+
- Make sure you're using **Figma Desktop** — the browser version blocks localhost
|
|
133
|
+
|
|
134
|
+
### "Create Pull Request" does nothing / no PR opens
|
|
135
|
+
|
|
136
|
+
- Restart your dev server (`pnpm figma:dev`) so `/api/figma/patch-tokens` is available
|
|
137
|
+
- Re-import the plugin from `node_modules/inkhouse/manifest.json` after updating
|
|
138
|
+
- Re-open the plugin and retry **Push Tokens to Code**
|
|
139
|
+
|
|
140
|
+
### Theme rename/new theme only commits one changed token
|
|
141
|
+
|
|
142
|
+
- Ensure your project route `src/app/api/figma/patch-tokens/route.ts` is up to date:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pnpm exec inkhouse setup
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- Restart the dev server after route/template updates
|
|
149
|
+
- Expected behavior:
|
|
150
|
+
- stale theme blocks are removed (for example `secondary`)
|
|
151
|
+
- new/missing theme blocks are written as full blocks (for example full `client2`), not sparse one-line patches
|
|
152
|
+
|
|
153
|
+
### "Incompatible scanner contract"
|
|
154
|
+
|
|
155
|
+
The plugin and scanner are out of sync. Update to the latest version:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pnpm update inkhouse
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Then close and re-open the plugin in Figma.
|
|
162
|
+
|
|
163
|
+
### Design system page looks wrong after code changes
|
|
164
|
+
|
|
165
|
+
Re-run **Generate Design System Page** — it always reflects the current state of your components.
|
|
166
|
+
|
|
167
|
+
### Font weight/style looks different in Figma
|
|
168
|
+
|
|
169
|
+
The plugin uses a nearest-weight fallback strategy. Make sure the target font family is installed/enabled in Figma.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Updating
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pnpm update inkhouse
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Component data is always scanned live — you only need to update when a new `inkhouse` version ships rendering improvements.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Architecture
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Figma plugin (sandboxed)
|
|
187
|
+
└── code.js Main logic — cannot make network requests directly
|
|
188
|
+
└── ui.html Hidden iframe — relays all network requests to code.js
|
|
189
|
+
|
|
190
|
+
code.js → ui.html → localhost:4000/api/figma/scan-components
|
|
191
|
+
→ localhost:4000/api/figma/patch-tokens
|
|
192
|
+
→ https://api.github.com
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
All network I/O is relayed through the UI iframe via `postMessage` due to the Figma sandbox restriction.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
package/bin/inkhouse.mjs
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { copyFile, mkdir, readFile, writeFile } from "fs/promises";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { dirname, join, resolve } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const PACKAGE_DIR = join(__dirname, "..");
|
|
9
|
+
|
|
10
|
+
// When running via postinstall, INIT_CWD is the consumer project root.
|
|
11
|
+
// Fall back to process.cwd() for direct invocations.
|
|
12
|
+
const PROJECT_ROOT = process.env.INIT_CWD || process.cwd();
|
|
13
|
+
|
|
14
|
+
const [, , command = "help"] = process.argv;
|
|
15
|
+
|
|
16
|
+
function detectStorybookPaths(root) {
|
|
17
|
+
const candidates = [
|
|
18
|
+
".storybook/main.ts",
|
|
19
|
+
".storybook/main.js",
|
|
20
|
+
".storybook/main.mjs",
|
|
21
|
+
".storybook/main.cjs",
|
|
22
|
+
];
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
const fullPath = join(root, candidate);
|
|
25
|
+
if (!existsSync(fullPath)) continue;
|
|
26
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
27
|
+
const storiesMatch = content.match(/stories\s*:\s*\[([^\]]+)\]/s);
|
|
28
|
+
if (!storiesMatch) continue;
|
|
29
|
+
const globs = [...storiesMatch[1].matchAll(/["'`]([^"'`]+)["'`]/g)].map(m => m[1]);
|
|
30
|
+
const dirs = new Set();
|
|
31
|
+
for (const glob of globs) {
|
|
32
|
+
const normalized = glob.replace(/^\.\.\//, "");
|
|
33
|
+
const first = normalized.split("/")[0];
|
|
34
|
+
if (first && !first.includes("*") && !first.includes("{")) dirs.add(first);
|
|
35
|
+
}
|
|
36
|
+
if (dirs.size > 0) return [...dirs];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// postinstall — called automatically after `pnpm add -D inkhouse`
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
async function postinstall() {
|
|
45
|
+
const pkg = JSON.parse(await readFile(join(PACKAGE_DIR, "package.json"), "utf8"));
|
|
46
|
+
const manifestPath = join(PROJECT_ROOT, "node_modules/inkhouse/manifest.json");
|
|
47
|
+
|
|
48
|
+
console.log("");
|
|
49
|
+
console.log(` ✓ inkhouse v${pkg.version} installed`);
|
|
50
|
+
console.log("");
|
|
51
|
+
console.log(" Next: run setup to wire up the scanner endpoint and scripts:");
|
|
52
|
+
console.log(" pnpm exec inkhouse setup");
|
|
53
|
+
console.log("");
|
|
54
|
+
console.log(" Then load the plugin in Figma Desktop:");
|
|
55
|
+
console.log(" Plugins → Development → Import plugin from manifest");
|
|
56
|
+
console.log(` ${manifestPath}`);
|
|
57
|
+
console.log("");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// setup — patches package.json + creates scanner route
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
async function setup() {
|
|
64
|
+
const scanRouteDest = join(PROJECT_ROOT, "src/app/api/figma/scan-components/route.ts");
|
|
65
|
+
const scanRouteSrc = join(PACKAGE_DIR, "templates/scan-components-route.ts");
|
|
66
|
+
const patchRouteDest = join(PROJECT_ROOT, "src/app/api/figma/patch-tokens/route.ts");
|
|
67
|
+
const patchRouteSrc = join(PACKAGE_DIR, "templates/patch-tokens-route.ts");
|
|
68
|
+
const pkgPath = join(PROJECT_ROOT, "package.json");
|
|
69
|
+
|
|
70
|
+
// 1. Create inkhouse.config.json
|
|
71
|
+
const inkhouseCfgPath = join(PROJECT_ROOT, "inkhouse.config.json");
|
|
72
|
+
if (existsSync(inkhouseCfgPath)) {
|
|
73
|
+
console.log(" ~ inkhouse.config.json already exists, skipping");
|
|
74
|
+
} else {
|
|
75
|
+
const detectedPaths = detectStorybookPaths(PROJECT_ROOT);
|
|
76
|
+
const componentPaths = detectedPaths.length > 0 ? detectedPaths : ["src"];
|
|
77
|
+
const cfg = {
|
|
78
|
+
componentPaths,
|
|
79
|
+
exclude: [],
|
|
80
|
+
onlyWithStories: true,
|
|
81
|
+
};
|
|
82
|
+
await writeFile(inkhouseCfgPath, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
83
|
+
if (detectedPaths.length > 0) {
|
|
84
|
+
console.log(` ✓ created inkhouse.config.json (detected paths: ${detectedPaths.join(", ")})`);
|
|
85
|
+
} else {
|
|
86
|
+
console.log(" ✓ created inkhouse.config.json (defaulting to src/ — edit to customise)");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 3. Copy scanner route
|
|
91
|
+
if (existsSync(scanRouteDest)) {
|
|
92
|
+
console.log(" ~ scanner route already exists, skipping: src/app/api/figma/scan-components/route.ts");
|
|
93
|
+
} else {
|
|
94
|
+
await mkdir(dirname(scanRouteDest), { recursive: true });
|
|
95
|
+
await copyFile(scanRouteSrc, scanRouteDest);
|
|
96
|
+
console.log(" ✓ created src/app/api/figma/scan-components/route.ts");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (existsSync(patchRouteDest)) {
|
|
100
|
+
console.log(" ~ token patch route already exists, skipping: src/app/api/figma/patch-tokens/route.ts");
|
|
101
|
+
} else {
|
|
102
|
+
await mkdir(dirname(patchRouteDest), { recursive: true });
|
|
103
|
+
await copyFile(patchRouteSrc, patchRouteDest);
|
|
104
|
+
console.log(" ✓ created src/app/api/figma/patch-tokens/route.ts");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 4. Patch package.json scripts
|
|
108
|
+
if (!existsSync(pkgPath)) {
|
|
109
|
+
console.warn(" ! package.json not found at project root — skipping script injection");
|
|
110
|
+
} else {
|
|
111
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
112
|
+
pkg.scripts = pkg.scripts || {};
|
|
113
|
+
const toAdd = {
|
|
114
|
+
"figma:dev": "next dev",
|
|
115
|
+
"figma:scan": "tsx node_modules/inkhouse/scanner/cli.ts",
|
|
116
|
+
};
|
|
117
|
+
const added = [];
|
|
118
|
+
for (const [k, v] of Object.entries(toAdd)) {
|
|
119
|
+
if (!pkg.scripts[k]) {
|
|
120
|
+
pkg.scripts[k] = v;
|
|
121
|
+
added.push(k);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (added.length > 0) {
|
|
125
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
126
|
+
for (const k of added) console.log(` ✓ added script: ${k}`);
|
|
127
|
+
} else {
|
|
128
|
+
console.log(" ~ scripts already present, skipping");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 5. Print manifest path
|
|
133
|
+
const manifestPath = resolve(PROJECT_ROOT, "node_modules/inkhouse/manifest.json");
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log(" Setup complete. Load the plugin in Figma Desktop:");
|
|
136
|
+
console.log(" Plugins → Development → Import plugin from manifest");
|
|
137
|
+
console.log(` ${manifestPath}`);
|
|
138
|
+
console.log("");
|
|
139
|
+
console.log(" Start developing:");
|
|
140
|
+
console.log(" pnpm figma:dev (starts Next.js dev server for scan + token patch routes)");
|
|
141
|
+
console.log(" pnpm figma:scan (manually re-scan components)");
|
|
142
|
+
console.log("");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// path — print manifest path (useful reference)
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
function printPath() {
|
|
149
|
+
const manifestPath = resolve(PROJECT_ROOT, "node_modules/inkhouse/manifest.json");
|
|
150
|
+
console.log(manifestPath);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// dispatch
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
switch (command) {
|
|
157
|
+
case "postinstall":
|
|
158
|
+
await postinstall();
|
|
159
|
+
break;
|
|
160
|
+
case "setup":
|
|
161
|
+
await setup();
|
|
162
|
+
break;
|
|
163
|
+
case "path":
|
|
164
|
+
printPath();
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
console.log("Usage:");
|
|
168
|
+
console.log(" inkhouse setup Wire up scanner/token-patch routes + scripts (run once after install)");
|
|
169
|
+
console.log(" inkhouse path Print the manifest.json path for Figma Desktop");
|
|
170
|
+
break;
|
|
171
|
+
}
|