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.
Files changed (62) hide show
  1. package/README.md +201 -0
  2. package/bin/inkhouse.mjs +171 -0
  3. package/code.js +11802 -0
  4. package/manifest.json +30 -0
  5. package/package.json +45 -0
  6. package/scanner/blob-placement-regression.ts +132 -0
  7. package/scanner/class-collector.ts +69 -0
  8. package/scanner/cli.ts +336 -0
  9. package/scanner/component-scanner.ts +2876 -0
  10. package/scanner/css-patch-regression.ts +112 -0
  11. package/scanner/css-token-reader-regression.ts +92 -0
  12. package/scanner/css-token-reader.ts +477 -0
  13. package/scanner/font-style-resolver-regression.ts +32 -0
  14. package/scanner/index.ts +9 -0
  15. package/scanner/radial-gradient-regression.ts +53 -0
  16. package/scanner/style-map.ts +145 -0
  17. package/scanner/tailwind-parser.ts +644 -0
  18. package/scanner/transform-math-regression.ts +42 -0
  19. package/scanner/types.ts +298 -0
  20. package/src/blob-placement.ts +111 -0
  21. package/src/change-detection.ts +204 -0
  22. package/src/class-utils.ts +105 -0
  23. package/src/clip-path-decorative.ts +194 -0
  24. package/src/color-resolver.ts +98 -0
  25. package/src/colors.ts +196 -0
  26. package/src/component-defs.ts +54 -0
  27. package/src/component-gen.ts +561 -0
  28. package/src/component-lookup.ts +82 -0
  29. package/src/config.ts +115 -0
  30. package/src/design-system.ts +59 -0
  31. package/src/dev-server.ts +173 -0
  32. package/src/figma-globals.d.ts +3 -0
  33. package/src/font-style-resolver.ts +171 -0
  34. package/src/github.ts +1465 -0
  35. package/src/icon-builder.ts +607 -0
  36. package/src/image-cache.ts +22 -0
  37. package/src/inline-text.ts +271 -0
  38. package/src/layout-parser.ts +667 -0
  39. package/src/layout-utils.ts +155 -0
  40. package/src/main.ts +687 -0
  41. package/src/node-ir.ts +595 -0
  42. package/src/pack-provider.ts +148 -0
  43. package/src/packs.ts +126 -0
  44. package/src/radial-gradient.ts +84 -0
  45. package/src/render-context.ts +138 -0
  46. package/src/responsive-analyzer.ts +139 -0
  47. package/src/state-analyzer.ts +143 -0
  48. package/src/story-builder.ts +1706 -0
  49. package/src/story-layout.ts +38 -0
  50. package/src/tailwind.ts +2379 -0
  51. package/src/text-builder.ts +116 -0
  52. package/src/text-line.ts +42 -0
  53. package/src/token-source.ts +43 -0
  54. package/src/tokens.ts +717 -0
  55. package/src/transform-math.ts +44 -0
  56. package/src/ui-builder.ts +1996 -0
  57. package/src/utility-resolver.ts +125 -0
  58. package/src/variables.ts +1042 -0
  59. package/src/width-solver.ts +466 -0
  60. package/templates/patch-tokens-route.ts +165 -0
  61. package/templates/scan-components-route.ts +57 -0
  62. 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
@@ -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
+ }