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 +18 -7
- package/bin/inkbridge.mjs +39 -10
- package/package.json +1 -1
- package/scanner/cli.ts +6 -7
- package/scanner/types.ts +17 -0
- package/templates/patch-tokens-route.ts +12 -0
- package/templates/scan-components-route.ts +12 -0
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
|
|
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
|
|
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
|
|
103
|
+
pnpm tsx node_modules/inkbridge/scanner/cli.ts
|
|
93
104
|
```
|
|
94
105
|
|
|
95
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
342
|
-
|
|
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
|
|
439
|
-
console.log("
|
|
440
|
-
console.log("
|
|
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.
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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(
|
|
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
|
}
|