inkbridge 0.1.0-beta.24 → 0.1.0-beta.25

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 CHANGED
@@ -41,10 +41,17 @@ pnpm exec inkbridge setup
41
41
  - Creates `inkbridge.config.json` in your project root (auto-detects component paths from `.storybook/main.*`)
42
42
  - Creates the scanner API route at `src/app/api/inkbridge/scan-components/route.ts`
43
43
  - Creates the token patch API route at `src/app/api/inkbridge/patch-tokens/route.ts`
44
- - Adds `inkbridge:dev` and `inkbridge:scan` scripts to your `package.json`
45
44
  - Adds `headers()` to your `next.config.*` exposing CORS on `/api/inkbridge/:path*` so the Figma plugin iframe can reach those routes (scoped — your other routes are untouched)
46
45
  - Appends `.inkbridge/` to your `.gitignore` so the scanner-output JSON doesn't show up in every diff
47
46
 
47
+ No `package.json` scripts are added by default — the plugin works through the API routes via your normal `pnpm dev`. If you're developing the plugin source itself and want the maintainer local-link scripts, pass `--dev`:
48
+
49
+ ```bash
50
+ pnpm exec inkbridge setup --dev
51
+ ```
52
+
53
+ That adds five maintainer scripts to `package.json`: `inkbridge:plugin:which`, `inkbridge:plugin:use-beta`, `inkbridge:plugin:use-local`, `inkbridge:dev:local`, `inkbridge:scan:local`.
54
+
48
55
  Inkbridge does not modify your ESLint config or any other tooling. The recommended `react/forbid-dom-props` rule for inline styles is suggested in "Recommended lint rules" below, but never written by setup.
49
56
 
50
57
  ### 2. Load the plugin in Figma Desktop
@@ -58,7 +65,7 @@ Figma remembers this path — you only do this once.
58
65
  ### 3. Start your dev server
59
66
 
60
67
  ```bash
61
- pnpm inkbridge:dev
68
+ pnpm dev
62
69
  ```
63
70
 
64
71
  The plugin auto-discovers your server on ports `4000`, `3000`, and `5173`.
@@ -71,7 +78,7 @@ The plugin auto-discovers your server on ports `4000`, `3000`, and `5173`.
71
78
 
72
79
  ### Generate Design System Page
73
80
 
74
- 1. Start dev server: `pnpm inkbridge:dev`
81
+ 1. Start dev server: `pnpm dev`
75
82
  2. Open any Figma file
76
83
  3. **Plugins → Development → Inkbridge → Generate Design System Page**
77
84
  4. The plugin scans your Storybook stories and builds a "Design System" page with token tables, themed columns, grouped component sections, and responsive/state preview blocks where relevant
@@ -88,11 +95,15 @@ When a story instance uses non-default style/behavior props (for example `classN
88
95
 
89
96
  ### Manual scan
90
97
 
98
+ Scans happen automatically every time you click **Generate Design System Page** — the plugin calls `GET /api/inkbridge/scan-components` on your dev server and reads the response live. There is no manual step in the normal workflow.
99
+
100
+ For debugging scanner output, you can invoke the scanner CLI directly:
101
+
91
102
  ```bash
92
- pnpm inkbridge:scan
103
+ pnpm tsx node_modules/inkbridge/scanner/cli.ts
93
104
  ```
94
105
 
95
- Writes `.inkbridge/component-definitions.json`. Useful for debugging scanner output.
106
+ That writes `.inkbridge/component-definitions.json` to disk. (If you ran `inkbridge setup --dev`, the `inkbridge:scan:local` script gives you the sibling-repo variant.)
96
107
 
97
108
  ### Push to Code
98
109
 
@@ -238,13 +249,13 @@ Full reference at [inkbridge.ink/docs/responsive-previews](https://inkbridge.ink
238
249
 
239
250
  The plugin cannot connect to `localhost:4000`, `:3000`, or `:5173`.
240
251
 
241
- - Make sure `pnpm inkbridge:dev` is running
252
+ - Make sure `pnpm dev` is running
242
253
  - Check the terminal for errors in the Next.js server
243
254
  - Make sure you're using **Figma Desktop** — the browser version blocks localhost
244
255
 
245
256
  ### "Create Pull Request" does nothing / no PR opens
246
257
 
247
- - Restart your dev server (`pnpm inkbridge:dev`) so `/api/inkbridge/patch-tokens` is available
258
+ - Restart your dev server (`pnpm dev`) so `/api/inkbridge/patch-tokens` is available
248
259
  - Re-import the plugin from `node_modules/inkbridge/manifest.json` after updating
249
260
  - Re-open the plugin and retry **Push Tokens to Code**
250
261
 
package/bin/inkbridge.mjs CHANGED
@@ -165,6 +165,13 @@ async function setup() {
165
165
  // next.config — those are partial-edit territory and force-rewriting
166
166
  // them would clobber consumer state.
167
167
  const force = process.argv.includes("--force");
168
+ // `--dev` opts the consumer into maintainer/dev-loop scripts (local-link
169
+ // mode, version probes, `:use-beta` / `:use-local` switches). The default
170
+ // setup writes no package.json scripts at all — the plugin works
171
+ // end-to-end via the scan + patch API routes alone. Use `--dev` only when
172
+ // you're working on the plugin source itself (sibling `../inkbridge/`
173
+ // checkout) and need the local-link toggle.
174
+ const devMode = process.argv.includes("--dev");
168
175
  const scanRouteDest = join(PROJECT_ROOT, "src/app/api/inkbridge/scan-components/route.ts");
169
176
  const scanRouteSrc = join(PACKAGE_DIR, "templates/scan-components-route.ts");
170
177
  const patchRouteDest = join(PROJECT_ROOT, "src/app/api/inkbridge/patch-tokens/route.ts");
@@ -173,7 +180,7 @@ async function setup() {
173
180
  const inkbridgeCfgPath = join(PROJECT_ROOT, "inkbridge.config.json");
174
181
 
175
182
  console.log("");
176
- console.log(` inkbridge setup${dryRun ? " — dry run (no files will be written)" : ""}${force ? " — force (overwrite existing inkbridge files)" : ""}`);
183
+ console.log(` inkbridge setup${dryRun ? " — dry run (no files will be written)" : ""}${force ? " — force (overwrite existing inkbridge files)" : ""}${devMode ? " — dev (install maintainer scripts)" : ""}`);
177
184
  console.log("");
178
185
 
179
186
  const env = detectEnvironment(PROJECT_ROOT);
@@ -208,10 +215,21 @@ async function setup() {
208
215
  if (existsSync(pkgPath)) {
209
216
  try { pkg = JSON.parse(readFileSync(pkgPath, "utf8")); } catch (_e) {}
210
217
  }
211
- const scriptsToAdd = {
212
- "inkbridge:dev": "next dev",
213
- "inkbridge:scan": "tsx node_modules/inkbridge/scanner/cli.ts",
214
- };
218
+ // Default consumer setup writes no package.json scripts — the plugin
219
+ // works via the scan + patch API routes alone. `--dev` adds the
220
+ // maintainer-only loop (local-link toggle, version probe, `:use-beta` /
221
+ // `:use-local` switches). Anyone working on the plugin source itself
222
+ // (sibling `../inkbridge/` checkout) wants these; ordinary consumers
223
+ // should not see them in their package.json.
224
+ const scriptsToAdd = devMode
225
+ ? {
226
+ "inkbridge:dev:local": "INKBRIDGE_LOCAL=1 next dev",
227
+ "inkbridge:scan:local": "tsx ../inkbridge/tools/figma-plugin/scanner/cli.ts",
228
+ "inkbridge:plugin:which": "node -e \"const p=require('./package.json'); console.log('inkbridge =>', (p.devDependencies&&p.devDependencies.inkbridge)||(p.dependencies&&p.dependencies.inkbridge)||'not-set')\"",
229
+ "inkbridge:plugin:use-beta": "pnpm add -D inkbridge@beta",
230
+ "inkbridge:plugin:use-local": "pnpm add -D inkbridge@file:../inkbridge/tools/figma-plugin",
231
+ }
232
+ : {};
215
233
  const pkgScriptAdditions = pkg
216
234
  ? Object.entries(scriptsToAdd).filter(([k]) => !(pkg.scripts && pkg.scripts[k]))
217
235
  : [];
@@ -338,8 +356,16 @@ function printPostSetupHint() {
338
356
  console.log(` ${manifestPath}`);
339
357
  console.log("");
340
358
  console.log(" Start developing:");
341
- console.log(" pnpm inkbridge:dev (Next.js dev server for the scan + token patch routes)");
342
- console.log(" pnpm inkbridge:scan (manually re-scan components)");
359
+ console.log(" pnpm dev (your project's Next.js dev server also serves the scan + token patch routes)");
360
+ if (devMode) {
361
+ console.log("");
362
+ console.log(" Maintainer scripts installed (--dev):");
363
+ console.log(" pnpm inkbridge:plugin:which Print the installed inkbridge version");
364
+ console.log(" pnpm inkbridge:plugin:use-beta Switch back to the npm beta tag");
365
+ console.log(" pnpm inkbridge:plugin:use-local Switch to file:../inkbridge/tools/figma-plugin");
366
+ console.log(" pnpm inkbridge:dev:local Next.js dev with INKBRIDGE_LOCAL=1 (sibling repo scanner)");
367
+ console.log(" pnpm inkbridge:scan:local Manually re-scan via sibling repo's scanner");
368
+ }
343
369
  console.log("");
344
370
  }
345
371
 
@@ -435,8 +461,11 @@ switch (command) {
435
461
  }
436
462
  default:
437
463
  console.log("Usage:");
438
- console.log(" inkbridge setup Wire up scanner/token-patch routes + scripts (run once after install)");
439
- console.log(" inkbridge path Print the manifest.json path for Figma Desktop");
440
- console.log(" inkbridge skill install Install the AI development skill (maintainers only)");
464
+ console.log(" inkbridge setup Wire up scanner/token-patch routes (run once after install)");
465
+ console.log(" --dev Also install maintainer-only scripts (local-link, version probe, use-beta/use-local)");
466
+ console.log(" --force Overwrite the route templates + inkbridge.config.json from this plugin version");
467
+ console.log(" --dry-run Print the plan without writing files");
468
+ console.log(" inkbridge path Print the manifest.json path for Figma Desktop");
469
+ console.log(" inkbridge skill install Install the AI development skill (maintainers only)");
441
470
  break;
442
471
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inkbridge",
3
- "version": "0.1.0-beta.24",
3
+ "version": "0.1.0-beta.25",
4
4
  "description": "Generate a Figma design system from your Storybook stories — Tailwind React components rendered as native frames, design tokens, component states, and round-trip code sync.",
5
5
  "type": "module",
6
6
  "bin": {
package/scanner/cli.ts CHANGED
@@ -141,10 +141,9 @@ async function main() {
141
141
  // wrote in their own files — no leaked Tailwind defaults like
142
142
  // `serif: ui-serif, Georgia, Cambria, …` or the lone Tailwind
143
143
  // `spacing: 0.25rem` baseline value.
144
- const finalOutput: typeof output & {
145
- tokens?: ReturnType<typeof readTokenSourceMap>;
146
- consumerTokens?: ReturnType<typeof readConsumerOwnedTokenMap>;
147
- } = output;
144
+ //
145
+ // Both fields are declared optional on `ComponentDefinitions`, so they
146
+ // assign straight onto `output` — no inline intersection type needed.
148
147
  if (CLI_INCLUDE_TOKENS) {
149
148
  const baseOpts = {
150
149
  projectRoot: process.cwd(),
@@ -152,8 +151,8 @@ async function main() {
152
151
  cssTokenPath: CLI_CSS_TOKEN_PATH,
153
152
  dtcgTokenPath: CLI_DTCG_TOKEN_PATH,
154
153
  };
155
- finalOutput.tokens = readTokenSourceMap(baseOpts);
156
- finalOutput.consumerTokens = readConsumerOwnedTokenMap(baseOpts);
154
+ output.tokens = readTokenSourceMap(baseOpts);
155
+ output.consumerTokens = readConsumerOwnedTokenMap(baseOpts);
157
156
  }
158
157
 
159
158
  // Write to JSON file. Default matches the consumer-side path used by
@@ -172,7 +171,7 @@ async function main() {
172
171
  // programmatically by the plugin runtime — the indentation cost was
173
172
  // ~30% of the file size for no functional benefit. If you need to
174
173
  // hand-inspect it, pipe through `jq` (`jq . component-definitions.json`).
175
- fs.writeFileSync(outputPath, JSON.stringify(finalOutput));
174
+ fs.writeFileSync(outputPath, JSON.stringify(output));
176
175
 
177
176
  const totalStories = analyses.reduce((sum, a) => sum + (a.stories?.length || 0), 0);
178
177
 
package/scanner/types.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  * will use to generate design system components.
6
6
  */
7
7
 
8
+ import type { ScannedTokenMap } from '../src/tokens/token-source';
9
+
8
10
  // ============================================================================
9
11
  // Component Analysis Types
10
12
  // ============================================================================
@@ -289,6 +291,21 @@ export interface ComponentDefinitions {
289
291
 
290
292
  /** Tailwind class -> CSS declaration map */
291
293
  styleMap?: Record<string, StyleRuleEntry[]>;
294
+
295
+ /**
296
+ * Token maps, emitted only when the scanner runs with `--include-tokens`.
297
+ *
298
+ * `tokens` — full merged map (Tailwind/tw-animate-css defaults pulled in via
299
+ * `@import "tailwindcss"` + consumer overrides). Drives the runtime token
300
+ * resolver so utilities like `text-sm` keep resolving when unoverridden.
301
+ *
302
+ * `consumerTokens` — consumer-only scan (bare `@import`s skipped). Drives the
303
+ * Design Tokens panel so it shows only what the consumer wrote, not leaked
304
+ * Tailwind defaults. See `scanner/cli.ts` and the "two scans" section in
305
+ * `.ai/architecture.md`.
306
+ */
307
+ tokens?: ScannedTokenMap;
308
+ consumerTokens?: ScannedTokenMap;
292
309
  }
293
310
 
294
311
  export interface StyleRuleEntry {
@@ -155,6 +155,15 @@ function patchCssVariables(cssText: string, updatesByTheme: ThemeUpdateMap): str
155
155
  }
156
156
 
157
157
  export async function POST(request: Request): Promise<NextResponse> {
158
+ // Production guard. The Figma plugin only ever calls this on localhost
159
+ // during design work. Exposing the route on a deployed app gains
160
+ // nothing but adds a DoS vector (postcss-parses arbitrary `cssText`,
161
+ // CORS `*`, no auth). Returning 404 (rather than 403) makes the route
162
+ // indistinguishable from a missing endpoint — no signal to attackers
163
+ // that something is here.
164
+ if (process.env.NODE_ENV === 'production') {
165
+ return new NextResponse(null, { status: 404 });
166
+ }
158
167
  try {
159
168
  const payload = (await request.json()) as PatchPayload;
160
169
  const cssText = typeof payload.cssText === 'string' ? payload.cssText : '';
@@ -180,5 +189,8 @@ export async function POST(request: Request): Promise<NextResponse> {
180
189
  }
181
190
 
182
191
  export async function OPTIONS(): Promise<NextResponse> {
192
+ if (process.env.NODE_ENV === 'production') {
193
+ return new NextResponse(null, { status: 404 });
194
+ }
183
195
  return new NextResponse(null, { headers: CORS });
184
196
  }
@@ -46,6 +46,15 @@ const CORS = {
46
46
  type ScannerOutput = Record<string, unknown>;
47
47
 
48
48
  export async function GET(request: Request): Promise<NextResponse> {
49
+ // Production guard. The Figma plugin only ever calls this on localhost
50
+ // during design work, so exposing the route on a deployed app gains
51
+ // nothing but adds a DoS vector (every call spawns `tsx` running the
52
+ // scanner CLI — CPU-heavy, no rate limit, CORS `*`). Returning 404
53
+ // (rather than 403) makes the route indistinguishable from a missing
54
+ // endpoint — no signal to attackers that something is here.
55
+ if (process.env.NODE_ENV === 'production') {
56
+ return new NextResponse(null, { status: 404 });
57
+ }
49
58
  try {
50
59
  const url = new URL(request.url);
51
60
  const tokenSourceMode = url.searchParams.get('tokenSourceMode') || 'auto';
@@ -82,5 +91,8 @@ export async function GET(request: Request): Promise<NextResponse> {
82
91
  }
83
92
 
84
93
  export async function OPTIONS(): Promise<NextResponse> {
94
+ if (process.env.NODE_ENV === 'production') {
95
+ return new NextResponse(null, { status: 404 });
96
+ }
85
97
  return new NextResponse(null, { headers: CORS });
86
98
  }