brandrail 0.3.0 → 0.3.2

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 (2) hide show
  1. package/dist/brandrail.mjs +957 -88
  2. package/package.json +3 -2
@@ -1,8 +1,491 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
5
- import { dirname as dirname2, join as join3, relative as relative2, resolve as resolve3 } from "node:path";
4
+ import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2, writeFileSync } from "node:fs";
5
+ import { basename, dirname as dirname2, join as join3, relative as relative2, resolve as resolve3 } from "node:path";
6
+
7
+ // ../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
8
+ import { createRequire } from "module";
9
+ var require2 = createRequire("/");
10
+ var _a;
11
+ var Worker;
12
+ var isMarkedAsUntransferable;
13
+ try {
14
+ _a = require2("worker_threads"), Worker = _a.Worker, isMarkedAsUntransferable = _a.isMarkedAsUntransferable;
15
+ } catch (e) {
16
+ }
17
+ var u8 = Uint8Array;
18
+ var u16 = Uint16Array;
19
+ var i32 = Int32Array;
20
+ var fleb = new u8([
21
+ 0,
22
+ 0,
23
+ 0,
24
+ 0,
25
+ 0,
26
+ 0,
27
+ 0,
28
+ 0,
29
+ 1,
30
+ 1,
31
+ 1,
32
+ 1,
33
+ 2,
34
+ 2,
35
+ 2,
36
+ 2,
37
+ 3,
38
+ 3,
39
+ 3,
40
+ 3,
41
+ 4,
42
+ 4,
43
+ 4,
44
+ 4,
45
+ 5,
46
+ 5,
47
+ 5,
48
+ 5,
49
+ 0,
50
+ /* unused */
51
+ 0,
52
+ 0,
53
+ /* impossible */
54
+ 0
55
+ ]);
56
+ var fdeb = new u8([
57
+ 0,
58
+ 0,
59
+ 0,
60
+ 0,
61
+ 1,
62
+ 1,
63
+ 2,
64
+ 2,
65
+ 3,
66
+ 3,
67
+ 4,
68
+ 4,
69
+ 5,
70
+ 5,
71
+ 6,
72
+ 6,
73
+ 7,
74
+ 7,
75
+ 8,
76
+ 8,
77
+ 9,
78
+ 9,
79
+ 10,
80
+ 10,
81
+ 11,
82
+ 11,
83
+ 12,
84
+ 12,
85
+ 13,
86
+ 13,
87
+ /* unused */
88
+ 0,
89
+ 0
90
+ ]);
91
+ var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
92
+ var freb = function(eb, start) {
93
+ var b = new u16(31);
94
+ for (var i2 = 0; i2 < 31; ++i2) {
95
+ b[i2] = start += 1 << eb[i2 - 1];
96
+ }
97
+ var r = new i32(b[30]);
98
+ for (var i2 = 1; i2 < 30; ++i2) {
99
+ for (var j = b[i2]; j < b[i2 + 1]; ++j) {
100
+ r[j] = j - b[i2] << 5 | i2;
101
+ }
102
+ }
103
+ return { b, r };
104
+ };
105
+ var _a = freb(fleb, 2);
106
+ var fl = _a.b;
107
+ var revfl = _a.r;
108
+ fl[28] = 258, revfl[258] = 28;
109
+ var _b = freb(fdeb, 0);
110
+ var fd = _b.b;
111
+ var revfd = _b.r;
112
+ var rev = new u16(32768);
113
+ for (i = 0; i < 32768; ++i) {
114
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
115
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
116
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
117
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
118
+ }
119
+ var x;
120
+ var i;
121
+ var hMap = (function(cd, mb, r) {
122
+ var s = cd.length;
123
+ var i2 = 0;
124
+ var l = new u16(mb);
125
+ for (; i2 < s; ++i2) {
126
+ if (cd[i2])
127
+ ++l[cd[i2] - 1];
128
+ }
129
+ var le = new u16(mb);
130
+ for (i2 = 1; i2 < mb; ++i2) {
131
+ le[i2] = le[i2 - 1] + l[i2 - 1] << 1;
132
+ }
133
+ var co;
134
+ if (r) {
135
+ co = new u16(1 << mb);
136
+ var rvb = 15 - mb;
137
+ for (i2 = 0; i2 < s; ++i2) {
138
+ if (cd[i2]) {
139
+ var sv = i2 << 4 | cd[i2];
140
+ var r_1 = mb - cd[i2];
141
+ var v = le[cd[i2] - 1]++ << r_1;
142
+ for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
143
+ co[rev[v] >> rvb] = sv;
144
+ }
145
+ }
146
+ }
147
+ } else {
148
+ co = new u16(s);
149
+ for (i2 = 0; i2 < s; ++i2) {
150
+ if (cd[i2]) {
151
+ co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2];
152
+ }
153
+ }
154
+ }
155
+ return co;
156
+ });
157
+ var flt = new u8(288);
158
+ for (i = 0; i < 144; ++i)
159
+ flt[i] = 8;
160
+ var i;
161
+ for (i = 144; i < 256; ++i)
162
+ flt[i] = 9;
163
+ var i;
164
+ for (i = 256; i < 280; ++i)
165
+ flt[i] = 7;
166
+ var i;
167
+ for (i = 280; i < 288; ++i)
168
+ flt[i] = 8;
169
+ var i;
170
+ var fdt = new u8(32);
171
+ for (i = 0; i < 32; ++i)
172
+ fdt[i] = 5;
173
+ var i;
174
+ var flrm = /* @__PURE__ */ hMap(flt, 9, 1);
175
+ var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
176
+ var max = function(a) {
177
+ var m = a[0];
178
+ for (var i2 = 1; i2 < a.length; ++i2) {
179
+ if (a[i2] > m)
180
+ m = a[i2];
181
+ }
182
+ return m;
183
+ };
184
+ var bits = function(d, p, m) {
185
+ var o = p / 8 | 0;
186
+ return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
187
+ };
188
+ var bits16 = function(d, p) {
189
+ var o = p / 8 | 0;
190
+ return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
191
+ };
192
+ var shft = function(p) {
193
+ return (p + 7) / 8 | 0;
194
+ };
195
+ var slc = function(v, s, e) {
196
+ if (s == null || s < 0)
197
+ s = 0;
198
+ if (e == null || e > v.length)
199
+ e = v.length;
200
+ return new u8(v.subarray(s, e));
201
+ };
202
+ var ec = [
203
+ "unexpected EOF",
204
+ "invalid block type",
205
+ "invalid length/literal",
206
+ "invalid distance",
207
+ "stream finished",
208
+ "no stream handler",
209
+ ,
210
+ // determined by compression function
211
+ "no callback",
212
+ "invalid UTF-8 data",
213
+ "extra field too long",
214
+ "date not in range 1980-2099",
215
+ "filename too long",
216
+ "stream finishing",
217
+ "invalid zip data"
218
+ // determined by unknown compression method
219
+ ];
220
+ var err = function(ind, msg, nt) {
221
+ var e = new Error(msg || ec[ind]);
222
+ e.code = ind;
223
+ if (Error.captureStackTrace)
224
+ Error.captureStackTrace(e, err);
225
+ if (!nt)
226
+ throw e;
227
+ return e;
228
+ };
229
+ var inflt = function(dat, st, buf, dict) {
230
+ var sl = dat.length, dl = dict ? dict.length : 0;
231
+ if (!sl || st.f && !st.l)
232
+ return buf || new u8(0);
233
+ var noBuf = !buf;
234
+ var resize = noBuf || st.i != 2;
235
+ var noSt = st.i;
236
+ if (noBuf)
237
+ buf = new u8(sl * 3);
238
+ var cbuf = function(l2) {
239
+ var bl = buf.length;
240
+ if (l2 > bl) {
241
+ var nbuf = new u8(Math.max(bl * 2, l2));
242
+ nbuf.set(buf);
243
+ buf = nbuf;
244
+ }
245
+ };
246
+ var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
247
+ var tbts = sl * 8;
248
+ do {
249
+ if (!lm) {
250
+ final = bits(dat, pos, 1);
251
+ var type = bits(dat, pos + 1, 3);
252
+ pos += 3;
253
+ if (!type) {
254
+ var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
255
+ if (t > sl) {
256
+ if (noSt)
257
+ err(0);
258
+ break;
259
+ }
260
+ if (resize)
261
+ cbuf(bt + l);
262
+ buf.set(dat.subarray(s, t), bt);
263
+ st.b = bt += l, st.p = pos = t * 8, st.f = final;
264
+ continue;
265
+ } else if (type == 1)
266
+ lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
267
+ else if (type == 2) {
268
+ var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
269
+ var tl = hLit + bits(dat, pos + 5, 31) + 1;
270
+ pos += 14;
271
+ var ldt = new u8(tl);
272
+ var clt = new u8(19);
273
+ for (var i2 = 0; i2 < hcLen; ++i2) {
274
+ clt[clim[i2]] = bits(dat, pos + i2 * 3, 7);
275
+ }
276
+ pos += hcLen * 3;
277
+ var clb = max(clt), clbmsk = (1 << clb) - 1;
278
+ var clm = hMap(clt, clb, 1);
279
+ for (var i2 = 0; i2 < tl; ) {
280
+ var r = clm[bits(dat, pos, clbmsk)];
281
+ pos += r & 15;
282
+ var s = r >> 4;
283
+ if (s < 16) {
284
+ ldt[i2++] = s;
285
+ } else {
286
+ var c = 0, n = 0;
287
+ if (s == 16)
288
+ n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i2 - 1];
289
+ else if (s == 17)
290
+ n = 3 + bits(dat, pos, 7), pos += 3;
291
+ else if (s == 18)
292
+ n = 11 + bits(dat, pos, 127), pos += 7;
293
+ while (n--)
294
+ ldt[i2++] = c;
295
+ }
296
+ }
297
+ var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
298
+ lbt = max(lt);
299
+ dbt = max(dt);
300
+ lm = hMap(lt, lbt, 1);
301
+ dm = hMap(dt, dbt, 1);
302
+ } else
303
+ err(1);
304
+ if (pos > tbts) {
305
+ if (noSt)
306
+ err(0);
307
+ break;
308
+ }
309
+ }
310
+ if (resize)
311
+ cbuf(bt + 131072);
312
+ var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
313
+ var lpos = pos;
314
+ for (; ; lpos = pos) {
315
+ var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
316
+ pos += c & 15;
317
+ if (pos > tbts) {
318
+ if (noSt)
319
+ err(0);
320
+ break;
321
+ }
322
+ if (!c)
323
+ err(2);
324
+ if (sym < 256)
325
+ buf[bt++] = sym;
326
+ else if (sym == 256) {
327
+ lpos = pos, lm = null;
328
+ break;
329
+ } else {
330
+ var add = sym - 254;
331
+ if (sym > 264) {
332
+ var i2 = sym - 257, b = fleb[i2];
333
+ add = bits(dat, pos, (1 << b) - 1) + fl[i2];
334
+ pos += b;
335
+ }
336
+ var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
337
+ if (!d)
338
+ err(3);
339
+ pos += d & 15;
340
+ var dt = fd[dsym];
341
+ if (dsym > 3) {
342
+ var b = fdeb[dsym];
343
+ dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
344
+ }
345
+ if (pos > tbts) {
346
+ if (noSt)
347
+ err(0);
348
+ break;
349
+ }
350
+ if (resize)
351
+ cbuf(bt + 131072);
352
+ var end = bt + add;
353
+ if (bt < dt) {
354
+ var shift = dl - dt, dend = Math.min(dt, end);
355
+ if (shift + bt < 0)
356
+ err(3);
357
+ for (; bt < dend; ++bt)
358
+ buf[bt] = dict[shift + bt];
359
+ }
360
+ for (; bt < end; ++bt)
361
+ buf[bt] = buf[bt - dt];
362
+ }
363
+ }
364
+ st.l = lm, st.p = lpos, st.b = bt, st.f = final;
365
+ if (lm)
366
+ final = 1, st.m = lbt, st.d = dm, st.n = dbt;
367
+ } while (!final);
368
+ return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
369
+ };
370
+ var et = /* @__PURE__ */ new u8(0);
371
+ var b2 = function(d, b) {
372
+ return d[b] | d[b + 1] << 8;
373
+ };
374
+ var b4 = function(d, b) {
375
+ return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
376
+ };
377
+ var b8 = function(d, b) {
378
+ return b4(d, b) + b4(d, b + 4) * 4294967296;
379
+ };
380
+ function inflateSync(data, opts) {
381
+ return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
382
+ }
383
+ var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
384
+ var tds = 0;
385
+ try {
386
+ td.decode(et, { stream: true });
387
+ tds = 1;
388
+ } catch (e) {
389
+ }
390
+ var dutf8 = function(d) {
391
+ for (var r = "", i2 = 0; ; ) {
392
+ var c = d[i2++];
393
+ var eb = (c > 127) + (c > 223) + (c > 239);
394
+ if (i2 + eb > d.length)
395
+ return { s: r, r: slc(d, i2 - 1) };
396
+ if (!eb)
397
+ r += String.fromCharCode(c);
398
+ else if (eb == 3) {
399
+ c = ((c & 15) << 18 | (d[i2++] & 63) << 12 | (d[i2++] & 63) << 6 | d[i2++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023);
400
+ } else if (eb & 1)
401
+ r += String.fromCharCode((c & 31) << 6 | d[i2++] & 63);
402
+ else
403
+ r += String.fromCharCode((c & 15) << 12 | (d[i2++] & 63) << 6 | d[i2++] & 63);
404
+ }
405
+ };
406
+ function strFromU8(dat, latin1) {
407
+ if (latin1) {
408
+ var r = "";
409
+ for (var i2 = 0; i2 < dat.length; i2 += 16384)
410
+ r += String.fromCharCode.apply(null, dat.subarray(i2, i2 + 16384));
411
+ return r;
412
+ } else if (td) {
413
+ return td.decode(dat);
414
+ } else {
415
+ var _a2 = dutf8(dat), s = _a2.s, r = _a2.r;
416
+ if (r.length)
417
+ err(8);
418
+ return s;
419
+ }
420
+ }
421
+ var slzh = function(d, b) {
422
+ return b + 30 + b2(d, b + 26) + b2(d, b + 28);
423
+ };
424
+ var zh = function(d, b, z) {
425
+ var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
426
+ var _a2 = z64hs(d, es, efl, z, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
427
+ return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
428
+ };
429
+ var z64hs = function(d, b, l, z, sc, su, off) {
430
+ var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
431
+ var nf = nsc + nsu + noff;
432
+ if (z && nf) {
433
+ for (; b + 4 < e; b += 4 + b2(d, b + 2)) {
434
+ if (b2(d, b) == 1) {
435
+ return [
436
+ nsc ? b8(d, b + 4 + 8 * nsu) : sc,
437
+ nsu ? b8(d, b + 4) : su,
438
+ noff ? b8(d, b + 4 + 8 * (nsu + nsc)) : off,
439
+ 1
440
+ ];
441
+ }
442
+ }
443
+ if (z < 2)
444
+ err(13);
445
+ }
446
+ return [sc, su, off, 0];
447
+ };
448
+ function unzipSync(data, opts) {
449
+ var files = {};
450
+ var e = data.length - 22;
451
+ for (; b4(data, e) != 101010256; --e) {
452
+ if (!e || data.length - e > 65558)
453
+ err(13);
454
+ }
455
+ ;
456
+ var c = b2(data, e + 8);
457
+ if (!c)
458
+ return {};
459
+ var o = b4(data, e + 16);
460
+ var z = b4(data, e - 20) == 117853008;
461
+ if (z) {
462
+ var ze = b4(data, e - 12);
463
+ z = b4(data, ze) == 101075792;
464
+ if (z) {
465
+ c = b4(data, ze + 32);
466
+ o = b4(data, ze + 48);
467
+ }
468
+ }
469
+ var fltr = opts && opts.filter;
470
+ for (var i2 = 0; i2 < c; ++i2) {
471
+ var _a2 = zh(data, o, z), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
472
+ o = no;
473
+ if (!fltr || fltr({
474
+ name: fn,
475
+ size: sc,
476
+ originalSize: su,
477
+ compression: c_2
478
+ })) {
479
+ if (!c_2)
480
+ files[fn] = slc(data, b, b + sc);
481
+ else if (c_2 == 8)
482
+ files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
483
+ else
484
+ err(14, "unknown compression type " + c_2);
485
+ }
486
+ }
487
+ return files;
488
+ }
6
489
 
7
490
  // ../engine/src/color.ts
8
491
  var clamp = (n, lo = 0, hi = 255) => Math.min(hi, Math.max(lo, n));
@@ -11,7 +494,7 @@ function parseHex(hex) {
11
494
  if (!/^[0-9a-f]+$/.test(h)) return null;
12
495
  if (h.length === 3 || h.length === 4) h = h.split("").map((c) => c + c).join("");
13
496
  if (h.length !== 6 && h.length !== 8) return null;
14
- const n = (i) => parseInt(h.slice(i, i + 2), 16);
497
+ const n = (i2) => parseInt(h.slice(i2, i2 + 2), 16);
15
498
  return { r: n(0), g: n(2), b: n(4), a: h.length === 8 ? n(6) / 255 : 1 };
16
499
  }
17
500
  function hslToRgb(h, s, l) {
@@ -30,12 +513,12 @@ function parseColor(input) {
30
513
  if (!m) return null;
31
514
  const parts = m[2].split(/[\s,/]+/).filter(Boolean);
32
515
  if (parts.length < 3) return null;
33
- const num = (p, max) => p.endsWith("%") ? parseFloat(p) / 100 * max : parseFloat(p);
516
+ const num = (p, max2) => p.endsWith("%") ? parseFloat(p) / 100 * max2 : parseFloat(p);
34
517
  const alpha = parts[3] !== void 0 ? num(parts[3], 1) : 1;
35
518
  if (m[1].startsWith("rgb")) {
36
- const [r2, g2, b2] = parts.slice(0, 3).map((p) => num(p, 255));
37
- if ([r2, g2, b2].some((v) => Number.isNaN(v))) return null;
38
- return { r: clamp(r2), g: clamp(g2), b: clamp(b2), a: alpha };
519
+ const [r2, g2, b3] = parts.slice(0, 3).map((p) => num(p, 255));
520
+ if ([r2, g2, b3].some((v) => Number.isNaN(v))) return null;
521
+ return { r: clamp(r2), g: clamp(g2), b: clamp(b3), a: alpha };
39
522
  }
40
523
  const [h, sat, l] = [parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2])];
41
524
  if ([h, sat, l].some((v) => Number.isNaN(v))) return null;
@@ -51,11 +534,11 @@ function rgbToLab({ r, g, b }) {
51
534
  return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
52
535
  };
53
536
  const [R, G, B] = [lin(r), lin(g), lin(b)];
54
- const x = (R * 0.4124 + G * 0.3576 + B * 0.1805) / 0.95047;
537
+ const x2 = (R * 0.4124 + G * 0.3576 + B * 0.1805) / 0.95047;
55
538
  const y = R * 0.2126 + G * 0.7152 + B * 0.0722;
56
539
  const z = (R * 0.0193 + G * 0.1192 + B * 0.9505) / 1.08883;
57
540
  const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
58
- return { l: 116 * f(y) - 16, a: 500 * (f(x) - f(y)), b: 200 * (f(y) - f(z)) };
541
+ return { l: 116 * f(y) - 16, a: 500 * (f(x2) - f(y)), b: 200 * (f(y) - f(z)) };
59
542
  }
60
543
  function deltaE(a, b) {
61
544
  return Math.sqrt((a.l - b.l) ** 2 + (a.a - b.a) ** 2 + (a.b - b.b) ** 2);
@@ -68,7 +551,7 @@ function contrastRatio(a, b) {
68
551
  });
69
552
  return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
70
553
  };
71
- const [l1, l2] = [lum(a), lum(b)].sort((x, y) => y - x);
554
+ const [l1, l2] = [lum(a), lum(b)].sort((x2, y) => y - x2);
72
555
  return (l1 + 0.05) / (l2 + 0.05);
73
556
  }
74
557
 
@@ -165,7 +648,7 @@ function normalizeFontFamily(value) {
165
648
  function isGenericFont(family) {
166
649
  return GENERIC_FONTS.has(family) || family.startsWith("var(");
167
650
  }
168
- var uniqSorted = (xs) => [...new Set(xs.map((x) => Math.round(x * 100) / 100))].sort((a, b) => a - b);
651
+ var uniqSorted = (xs) => [...new Set(xs.map((x2) => Math.round(x2 * 100) / 100))].sort((a, b) => a - b);
169
652
  function resolveConfig(config) {
170
653
  const all = leaves(config.tokens);
171
654
  const colors = [];
@@ -233,8 +716,8 @@ function* matches(re, text) {
233
716
  if (m[0].length === 0) g.lastIndex++;
234
717
  }
235
718
  }
236
- var lineBefore = (code, offset, max = 80) => {
237
- const start = Math.max(code.lastIndexOf("\n", offset - 1) + 1, offset - max);
719
+ var lineBefore = (code, offset, max2 = 80) => {
720
+ const start = Math.max(code.lastIndexOf("\n", offset - 1) + 1, offset - max2);
238
721
  return code.slice(start, offset);
239
722
  };
240
723
  var lineOf = (code, offset) => {
@@ -244,9 +727,9 @@ var lineOf = (code, offset) => {
244
727
  };
245
728
  var fmt = (n) => `${Math.round(n * 100) / 100}px`;
246
729
  function neighbours(scale, v) {
247
- const lower = [...scale].reverse().find((x) => x <= v);
248
- const upper = scale.find((x) => x >= v);
249
- const opts = [...new Set([lower, upper].filter((x) => x !== void 0))];
730
+ const lower = [...scale].reverse().find((x2) => x2 <= v);
731
+ const upper = scale.find((x2) => x2 >= v);
732
+ const opts = [...new Set([lower, upper].filter((x2) => x2 !== void 0))];
250
733
  return opts.map(fmt).join(" or ");
251
734
  }
252
735
  function pxValues(value) {
@@ -484,6 +967,114 @@ var imgAlt = {
484
967
  return out;
485
968
  }
486
969
  };
970
+ var NATIVE_ATTRS = new Set(
971
+ "id className style title role tabIndex hidden lang dir name value defaultValue defaultChecked checked disabled required readOnly autoFocus autoComplete placeholder type min max step maxLength minLength pattern form accept multiple href target rel download children".split(" ")
972
+ );
973
+ var ALWAYS_OK = /* @__PURE__ */ new Set(["key", "ref"]);
974
+ function importedComponents(code, components) {
975
+ const out = /* @__PURE__ */ new Map();
976
+ for (const m of matches(/\bimport\s+(?:type\s+)?\{([^}]*)\}\s*from\s*(['"])([^'"]+)\2/g, code)) {
977
+ const from = m[3];
978
+ for (const part of m[1].split(",")) {
979
+ const [orig, alias] = part.trim().replace(/^type\s+/, "").split(/\s+as\s+/).map((s) => s.trim());
980
+ if (!orig) continue;
981
+ const comp = components.find((c) => c.name === orig && c.importPath === from && c.props?.length);
982
+ if (comp) out.set(alias || orig, comp);
983
+ }
984
+ }
985
+ return out;
986
+ }
987
+ function skipBalanced(code, i2) {
988
+ const open = code[i2];
989
+ if (open === '"' || open === "'") {
990
+ const end = code.indexOf(open, i2 + 1);
991
+ return end === -1 ? code.length : end + 1;
992
+ }
993
+ let depth = 0;
994
+ let quote = null;
995
+ for (let j = i2; j < code.length; j++) {
996
+ const c = code[j];
997
+ if (quote) {
998
+ if (c === "\\") j++;
999
+ else if (c === quote) quote = null;
1000
+ continue;
1001
+ }
1002
+ if (c === '"' || c === "'" || c === "`") quote = c;
1003
+ else if (c === "{") depth++;
1004
+ else if (c === "}" && --depth === 0) return j + 1;
1005
+ }
1006
+ return code.length;
1007
+ }
1008
+ function parseAttributes(code, start) {
1009
+ const attrs = [];
1010
+ let spread = false;
1011
+ let i2 = start;
1012
+ while (i2 < code.length) {
1013
+ while (/\s/.test(code[i2] ?? "")) i2++;
1014
+ const c = code[i2];
1015
+ if (c === void 0) return null;
1016
+ if (c === ">") return { attrs, spread, selfClosing: false };
1017
+ if (c === "/" && code[i2 + 1] === ">") return { attrs, spread, selfClosing: true };
1018
+ if (c === "{") {
1019
+ spread = true;
1020
+ i2 = skipBalanced(code, i2);
1021
+ continue;
1022
+ }
1023
+ const name = /^[A-Za-z_$][\w$:.-]*/.exec(code.slice(i2, i2 + 80))?.[0];
1024
+ if (!name) return null;
1025
+ attrs.push({ name, offset: i2 });
1026
+ i2 += name.length;
1027
+ while (/\s/.test(code[i2] ?? "")) i2++;
1028
+ if (code[i2] === "=") {
1029
+ i2++;
1030
+ while (/\s/.test(code[i2] ?? "")) i2++;
1031
+ i2 = skipBalanced(code, i2);
1032
+ }
1033
+ }
1034
+ return null;
1035
+ }
1036
+ var componentProps = {
1037
+ id: "component-props",
1038
+ title: "Unknown or missing prop",
1039
+ description: "Design system components only accept their documented props, and required props must be passed.",
1040
+ defaultSeverity: "error",
1041
+ kinds: ["jsx"],
1042
+ check({ code, config }) {
1043
+ if (!config.components.some((c) => c.props?.length)) return [];
1044
+ const imported = importedComponents(code, config.components);
1045
+ if (imported.size === 0) return [];
1046
+ const out = [];
1047
+ const names = [...imported.keys()].map((n) => n.replace(/[$]/g, "\\$")).join("|");
1048
+ for (const m of matches(new RegExp(`<(${names})(?=[\\s/>])`, "g"), code)) {
1049
+ if (/[\w)\]]\s*$/.test(code.slice(Math.max(0, m.index - 20), m.index))) continue;
1050
+ const comp = imported.get(m[1]);
1051
+ const parsed = parseAttributes(code, m.index + m[0].length);
1052
+ if (!parsed) continue;
1053
+ const own = new Map(comp.props.map((p) => [p.name, p]));
1054
+ const list = comp.props.map((p) => p.required ? `${p.name} (required)` : p.name).join(", ");
1055
+ for (const a of parsed.attrs) {
1056
+ if (own.has(a.name) || ALWAYS_OK.has(a.name) || a.name.includes("-")) continue;
1057
+ if (comp.htmlAttributes && (NATIVE_ATTRS.has(a.name) || /^on[A-Z]/.test(a.name) || /^aria/.test(a.name))) continue;
1058
+ const sibling = config.components.find(
1059
+ (c) => c !== comp && c.importPath === comp.importPath && (c.name.startsWith(comp.name) || comp.name.startsWith(c.name)) && c.props?.some((p) => p.name === a.name)
1060
+ );
1061
+ out.push({
1062
+ offset: a.offset,
1063
+ message: `<${comp.name}> has no "${a.name}" prop`,
1064
+ suggestion: sibling ? `Use <${sibling.name}>, which has "${a.name}", or remove it. ${comp.name} props: ${list}` : `Remove it. ${comp.name} props: ${list}`
1065
+ });
1066
+ }
1067
+ if (!parsed.spread) {
1068
+ const passed = new Set(parsed.attrs.map((a) => a.name));
1069
+ for (const p of comp.props) {
1070
+ if (!p.required || passed.has(p.name) || p.name === "children" && !parsed.selfClosing) continue;
1071
+ out.push({ offset: m.index, message: `<${comp.name}> is missing the required "${p.name}" prop`, suggestion: `Add ${p.name}${p.type ? ` (${p.type})` : ""}` });
1072
+ }
1073
+ }
1074
+ }
1075
+ return out;
1076
+ }
1077
+ };
487
1078
  var RULES = [
488
1079
  noHardcodedColor,
489
1080
  noRawPaletteClass,
@@ -492,7 +1083,8 @@ var RULES = [
492
1083
  spacingScale,
493
1084
  fontFamily,
494
1085
  radiusScale,
495
- imgAlt
1086
+ imgAlt,
1087
+ componentProps
496
1088
  ];
497
1089
  var RULE_IDS = RULES.map((r) => r.id);
498
1090
 
@@ -517,36 +1109,36 @@ function stripComments(code, kind) {
517
1109
  return out.replace(/\/\*[\s\S]*?\*\//g, blank).replace(/(^|[^:])\/\/[^\n]*/g, (m, p1) => p1 + blank(m.slice(p1.length)));
518
1110
  }
519
1111
  const chars = out.split("");
520
- let i = 0;
1112
+ let i2 = 0;
521
1113
  let quote = null;
522
- while (i < chars.length) {
523
- const c = chars[i];
524
- const n = chars[i + 1];
1114
+ while (i2 < chars.length) {
1115
+ const c = chars[i2];
1116
+ const n = chars[i2 + 1];
525
1117
  if (quote) {
526
- if (c === "\\") i += 2;
1118
+ if (c === "\\") i2 += 2;
527
1119
  else {
528
1120
  if (c === quote) quote = null;
529
- i++;
1121
+ i2++;
530
1122
  }
531
1123
  continue;
532
1124
  }
533
1125
  if (c === '"' || c === "'" || c === "`") {
534
1126
  quote = c;
535
- i++;
1127
+ i2++;
536
1128
  continue;
537
1129
  }
538
1130
  if (c === "/" && n === "*") {
539
- const end = out.indexOf("*/", i + 2);
1131
+ const end = out.indexOf("*/", i2 + 2);
540
1132
  const stop = end === -1 ? chars.length : end + 2;
541
- for (let j = i; j < stop; j++) if (chars[j] !== "\n") chars[j] = " ";
542
- i = stop;
1133
+ for (let j = i2; j < stop; j++) if (chars[j] !== "\n") chars[j] = " ";
1134
+ i2 = stop;
543
1135
  continue;
544
1136
  }
545
- if (c === "/" && n === "/" && chars[i - 1] !== ":" && kind === "jsx") {
546
- while (i < chars.length && chars[i] !== "\n") chars[i++] = " ";
1137
+ if (c === "/" && n === "/" && chars[i2 - 1] !== ":" && kind === "jsx") {
1138
+ while (i2 < chars.length && chars[i2] !== "\n") chars[i2++] = " ";
547
1139
  continue;
548
1140
  }
549
- i++;
1141
+ i2++;
550
1142
  }
551
1143
  return chars.join("");
552
1144
  }
@@ -555,7 +1147,7 @@ var LineIndex = class {
555
1147
  text;
556
1148
  constructor(text) {
557
1149
  this.text = text;
558
- for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) this.starts.push(i + 1);
1150
+ for (let i2 = 0; i2 < text.length; i2++) if (text.charCodeAt(i2) === 10) this.starts.push(i2 + 1);
559
1151
  }
560
1152
  position(offset) {
561
1153
  let lo = 0;
@@ -578,19 +1170,19 @@ var LineIndex = class {
578
1170
  };
579
1171
  function globToRegExp(glob) {
580
1172
  let re = "";
581
- for (let i = 0; i < glob.length; i++) {
582
- const c = glob[i];
1173
+ for (let i2 = 0; i2 < glob.length; i2++) {
1174
+ const c = glob[i2];
583
1175
  if (c === "*") {
584
- if (glob[i + 1] === "*") {
585
- const slash = glob[i + 2] === "/";
1176
+ if (glob[i2 + 1] === "*") {
1177
+ const slash = glob[i2 + 2] === "/";
586
1178
  re += slash ? "(?:.*/)?" : ".*";
587
- i += slash ? 2 : 1;
1179
+ i2 += slash ? 2 : 1;
588
1180
  } else re += "[^/]*";
589
1181
  } else if (c === "?") re += "[^/]";
590
1182
  else if (c === "{") {
591
- const end = glob.indexOf("}", i);
592
- re += `(?:${glob.slice(i + 1, end).split(",").map((s) => s.replace(/[.+^$()|[\]\\]/g, "\\$&")).join("|")})`;
593
- i = end;
1183
+ const end = glob.indexOf("}", i2);
1184
+ re += `(?:${glob.slice(i2 + 1, end).split(",").map((s) => s.replace(/[.+^$()|[\]\\]/g, "\\$&")).join("|")})`;
1185
+ i2 = end;
594
1186
  } else re += c.replace(/[.+^$()|[\]\\]/g, "\\$&");
595
1187
  }
596
1188
  return new RegExp(`^${re}$`);
@@ -604,7 +1196,7 @@ function makeIgnore(globs) {
604
1196
  }
605
1197
 
606
1198
  // ../engine/src/check.ts
607
- var ENGINE_VERSION = "0.3.0";
1199
+ var ENGINE_VERSION = "0.3.2";
608
1200
  var WEIGHT = { error: 4, warning: 1.5, info: 0.25 };
609
1201
  function computeScore(counts, lines) {
610
1202
  const penalty = counts.error * WEIGHT.error + counts.warning * WEIGHT.warning + counts.info * WEIGHT.info;
@@ -680,12 +1272,12 @@ var ansi = (on) => {
680
1272
  return { red: w("31"), yellow: w("33"), cyan: w("36"), green: w("32"), dim: w("2"), bold: w("1") };
681
1273
  };
682
1274
  var rendered = (r) => r.violations.length > 0 && r.violations.every((v) => v.line === 0);
683
- function formatPretty(report, { color = true, max = 200 } = {}) {
1275
+ function formatPretty(report, { color = true, max: max2 = 200 } = {}) {
684
1276
  const c = ansi(color);
685
1277
  const sev = { error: c.red, warning: c.yellow, info: c.cyan };
686
1278
  const lines = [];
687
1279
  let lastFile = "";
688
- for (const v of report.violations.slice(0, max)) {
1280
+ for (const v of report.violations.slice(0, max2)) {
689
1281
  if (v.file !== lastFile) {
690
1282
  lines.push("", c.bold(v.file));
691
1283
  lastFile = v.file;
@@ -695,7 +1287,7 @@ function formatPretty(report, { color = true, max = 200 } = {}) {
695
1287
  if (v.suggestion) lines.push(` ${" ".repeat(16)} ${c.green("\u2192")} ${v.suggestion}`);
696
1288
  if (rendered2 && v.snippet) lines.push(` ${" ".repeat(16)} ${c.dim(`on ${v.snippet}`)}`);
697
1289
  }
698
- if (report.violations.length > max) lines.push("", c.dim(`\u2026 and ${report.violations.length - max} more`));
1290
+ if (report.violations.length > max2) lines.push("", c.dim(`\u2026 and ${report.violations.length - max2} more`));
699
1291
  const s = report.score;
700
1292
  const scoreStr = s >= 80 ? c.green(String(s)) : s >= 65 ? c.yellow(String(s)) : c.red(String(s));
701
1293
  lines.push(
@@ -707,9 +1299,9 @@ function formatPretty(report, { color = true, max = 200 } = {}) {
707
1299
  }
708
1300
  var esc = (s) => s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
709
1301
  var escProp = (s) => esc(s).replace(/:/g, "%3A").replace(/,/g, "%2C");
710
- function formatGithubAnnotations(report, max = 50) {
1302
+ function formatGithubAnnotations(report, max2 = 50) {
711
1303
  const level = { error: "error", warning: "warning", info: "notice" };
712
- return report.violations.slice(0, max).map(
1304
+ return report.violations.slice(0, max2).map(
713
1305
  (v) => `::${level[v.severity]} file=${escProp(v.file)},line=${v.line},col=${v.column},title=${escProp(`Brandrail: ${title(v.ruleId)}`)}::${esc(
714
1306
  v.suggestion ? `${v.message}. ${v.suggestion}` : v.message
715
1307
  )}`
@@ -765,7 +1357,9 @@ var starterConfig = {
765
1357
  subtle: { $type: "color", $value: "#f6f8fa", $description: "Headers, wells" }
766
1358
  },
767
1359
  border: { default: { $type: "color", $value: "#d1d9e0", $description: "Borders and dividers" } },
768
- danger: { default: { $type: "color", $value: "#cf222e", $description: "Destructive actions, errors" } }
1360
+ danger: { default: { $type: "color", $value: "#cf222e", $description: "Destructive actions, errors" } },
1361
+ success: { default: { $type: "color", $value: "#1a7f37", $description: "Success messages, completed states" } },
1362
+ attention: { default: { $type: "color", $value: "#9a6700", $description: "Warnings, needs attention" } }
769
1363
  },
770
1364
  font: {
771
1365
  family: { sans: { $type: "fontFamily", $value: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif" } },
@@ -820,6 +1414,17 @@ function fontWeight(v) {
820
1414
  return WEIGHTS[slug(v)] ?? (/^\d{3}$/.test(v.trim()) ? v.trim() : v);
821
1415
  }
822
1416
  function colorValue(v) {
1417
+ if (v && typeof v === "object" && ("components" in v || "hex" in v)) {
1418
+ const c = v;
1419
+ const alpha = typeof c.alpha === "number" ? c.alpha : 1;
1420
+ const withAlpha = (hex2) => alpha < 1 ? `${hex2.slice(0, 7)}${Math.round(alpha * 255).toString(16).padStart(2, "0")}` : hex2.slice(0, 7);
1421
+ if (typeof c.hex === "string" && /^#[0-9a-f]{6}/i.test(c.hex)) return withAlpha(c.hex.toLowerCase());
1422
+ const comps = c.components?.map((x2) => typeof x2 === "number" ? x2 : 0);
1423
+ if (comps?.length === 3 && (!c.colorSpace || /srgb|display-p3/i.test(c.colorSpace))) {
1424
+ return withAlpha(toHex({ r: comps[0] * 255, g: comps[1] * 255, b: comps[2] * 255, a: 1 }));
1425
+ }
1426
+ return null;
1427
+ }
823
1428
  if (v && typeof v === "object" && "r" in v) {
824
1429
  const c = v;
825
1430
  const hex2 = toHex({ r: c.r * 255, g: c.g * 255, b: c.b * 255, a: 1 });
@@ -837,7 +1442,14 @@ function colorValue(v) {
837
1442
  const hex = toHex(rgb);
838
1443
  return rgb.a < 1 ? `${hex}${Math.round(rgb.a * 255).toString(16).padStart(2, "0")}` : hex;
839
1444
  }
840
- var dim = (v) => typeof v === "number" ? `${Math.round(v * 100) / 100}px` : String(v).trim();
1445
+ var dim = (v) => {
1446
+ if (v && typeof v === "object" && "value" in v) {
1447
+ const d = v;
1448
+ const n = typeof d.value === "number" ? d.value : parseFloat(d.value);
1449
+ return d.unit === "rem" ? `${Math.round(n * 16 * 100) / 100}px` : `${Math.round(n * 100) / 100}px`;
1450
+ }
1451
+ return typeof v === "number" ? `${Math.round(v * 100) / 100}px` : String(v).trim();
1452
+ };
841
1453
  function classify(name, kind, value, opts = {}) {
842
1454
  const parts = segs(name);
843
1455
  const hay = slug(`${opts.context ?? ""} ${name}`);
@@ -869,7 +1481,7 @@ function classify(name, kind, value, opts = {}) {
869
1481
  return null;
870
1482
  }
871
1483
  const v = dim(value);
872
- const has = (...scope) => scope.some((x) => scopes.includes(x));
1484
+ const has = (...scope) => scope.some((x2) => scopes.includes(x2));
873
1485
  const nm = (...prefixes) => stripPrefix(last, ...prefixes) || last;
874
1486
  if (has("CORNER_RADIUS") || /radius|(^|-)rad(-|$)|corner|rounded/.test(hay)) return { path: `radius.${nm("radius", "rad", "corner", "rounded")}`, type: "dimension", value: v };
875
1487
  if (has("FONT_SIZE") || /font-size|text-size/.test(hay)) return { path: `font.size.${nm("font-size", "text-size", "size")}`, type: "dimension", value: v };
@@ -879,7 +1491,7 @@ function classify(name, kind, value, opts = {}) {
879
1491
  if (/icon/.test(last)) return { path: `size.icon.${nm("icon")}`, type: "dimension", value: v };
880
1492
  if (has("GAP") || /spac|gap|padding|margin|inset|(^|-)space(-|$)/.test(hay)) return { path: `space.${nm("spacing", "space", "gap")}`, type: "dimension", value: v };
881
1493
  if (has("STROKE_FLOAT") || /stroke|border-width/.test(hay)) return { path: `border.width.${nm("border-width", "stroke")}`, type: "dimension", value: v };
882
- if (has("WIDTH_HEIGHT") || /size|width|height/.test(hay)) return { path: ["size", ...rest.filter((x) => x !== "size"), last].join("."), type: "dimension", value: v };
1494
+ if (has("WIDTH_HEIGHT") || /size|width|height/.test(hay)) return { path: ["size", ...rest.filter((x2) => x2 !== "size"), last].join("."), type: "dimension", value: v };
883
1495
  return { path: ["number", ...parts].join("."), type: "number", value: String(value) };
884
1496
  }
885
1497
  function rawLeaves(node, prefix, out, studio, inherited) {
@@ -940,8 +1552,8 @@ function flattenTokens(doc) {
940
1552
  if (!v) continue;
941
1553
  const d = r.dark !== void 0 ? colorValue(r.dark) : null;
942
1554
  out.push({ path: r.path, type: "color", value: v, ...d ? { dark: d } : {}, ...r.description ? { description: r.description } : {} });
943
- } else if (typeof r.value === "string" || typeof r.value === "number") {
944
- const type = t === "fontfamily" || t === "fontfamilies" ? "fontFamily" : t === "fontweight" || t === "fontweights" ? "fontWeight" : TYPE_KIND[t] === "number" ? "dimension" : t === "number" ? "number" : /^font\.family\./.test(r.path) ? "fontFamily" : typeof r.value === "number" ? "number" : /px$|rem$/.test(String(r.value)) ? "dimension" : "string";
1555
+ } else if (typeof r.value === "string" || typeof r.value === "number" || r.value && typeof r.value === "object" && "unit" in r.value) {
1556
+ const type = typeof r.value === "object" ? "dimension" : t === "fontfamily" || t === "fontfamilies" ? "fontFamily" : t === "fontweight" || t === "fontweights" ? "fontWeight" : TYPE_KIND[t] === "number" ? "dimension" : t === "number" ? "number" : /^font\.family\./.test(r.path) ? "fontFamily" : typeof r.value === "number" ? "number" : /px$|rem$/.test(String(r.value)) ? "dimension" : "string";
945
1557
  out.push({ path: r.path, type, value: type === "dimension" ? dim(r.value) : String(r.value), ...r.description ? { description: r.description } : {} });
946
1558
  }
947
1559
  }
@@ -1000,8 +1612,8 @@ function buildDtcg(tokens) {
1000
1612
  for (const t of tokens) {
1001
1613
  const parts = t.path.split(".");
1002
1614
  let node = tree;
1003
- parts.forEach((p, i) => {
1004
- if (i === parts.length - 1) {
1615
+ parts.forEach((p, i2) => {
1616
+ if (i2 === parts.length - 1) {
1005
1617
  node[p] = {
1006
1618
  $type: t.type,
1007
1619
  $value: t.value,
@@ -1098,7 +1710,7 @@ function fromFigmaVariables(meta, opts = {}) {
1098
1710
  if (!c) continue;
1099
1711
  let darkValue;
1100
1712
  if (c.type === "color") {
1101
- const hasDarkSomewhere = Object.values(cols).some((x) => x.modes.some((m) => DARK.test(m.name)));
1713
+ const hasDarkSomewhere = Object.values(cols).some((x2) => x2.modes.some((m) => DARK.test(m.name)));
1102
1714
  if (hasDarkSomewhere) {
1103
1715
  const d = colorValue(resolve4(v, "dark"));
1104
1716
  if (d && d !== c.value) darkValue = d;
@@ -1132,7 +1744,7 @@ function fromFigmaStyles(styles, opts = {}) {
1132
1744
  }
1133
1745
  }
1134
1746
  const fams = [...families.entries()].sort((a, b) => b[1] - a[1]);
1135
- fams.forEach(([f], i) => out.push({ path: `font.family.${i === 0 ? "sans" : /mono|code/i.test(f) ? "mono" : slug(f)}`, type: "fontFamily", value: f }));
1747
+ fams.forEach(([f], i2) => out.push({ path: `font.family.${i2 === 0 ? "sans" : /mono|code/i.test(f) ? "mono" : slug(f)}`, type: "fontFamily", value: f }));
1136
1748
  const notes = skipped ? [`${skipped} fill style${skipped === 1 ? "" : "s"} (gradients or images) were skipped.`] : [];
1137
1749
  return finish("figma-styles", out, notes, opts);
1138
1750
  }
@@ -1150,7 +1762,7 @@ function fromModeObject(obj, opts = {}) {
1150
1762
  const leafSets = modes.map(([name, v]) => {
1151
1763
  const l = [];
1152
1764
  plainLeaves(v, [], l);
1153
- return { name, leaves: l, keys: new Set(l.map((x) => x.key.join("/"))) };
1765
+ return { name, leaves: l, keys: new Set(l.map((x2) => x2.key.join("/"))) };
1154
1766
  });
1155
1767
  const groups = [];
1156
1768
  for (const m of leafSets) {
@@ -1199,7 +1811,7 @@ function fromModeObject(obj, opts = {}) {
1199
1811
  if (!c) continue;
1200
1812
  let d;
1201
1813
  if (c.type === "color") {
1202
- const darkLeaf = dark && dark !== def ? dark.leaves.find((x) => x.key.join("/") === l.key.join("/")) : void 0;
1814
+ const darkLeaf = dark && dark !== def ? dark.leaves.find((x2) => x2.key.join("/") === l.key.join("/")) : void 0;
1203
1815
  const dv = colorValue(deref(darkLeaf ? darkLeaf.value : l.value, "dark"));
1204
1816
  if (dv && dv !== c.value) d = dv;
1205
1817
  }
@@ -1209,12 +1821,125 @@ function fromModeObject(obj, opts = {}) {
1209
1821
  for (const p of pending) out.push({ ...p.t, referenced: referenced.has(p.key) });
1210
1822
  return finish("mode-object", out, notes, opts);
1211
1823
  }
1824
+ var figmaExt = (node, key) => {
1825
+ const ext = node.$extensions;
1826
+ if (!ext) return void 0;
1827
+ if (`com.figma.${key}` in ext) return ext[`com.figma.${key}`];
1828
+ const nested = ext["com.figma"];
1829
+ return nested?.[key];
1830
+ };
1831
+ function fileLeaves(node, prefix, out, inherited) {
1832
+ if (!node || typeof node !== "object" || Array.isArray(node)) return;
1833
+ const o = node;
1834
+ const groupType = o.$type ?? inherited;
1835
+ if ("$value" in o) {
1836
+ const alias = figmaExt(o, "aliasData");
1837
+ const scopes = figmaExt(o, "scopes");
1838
+ out.push({
1839
+ path: prefix,
1840
+ type: o.$type ?? groupType,
1841
+ value: o.$value,
1842
+ description: o.$description,
1843
+ scopes: Array.isArray(scopes) ? scopes : void 0,
1844
+ aliasTarget: alias?.targetVariableName?.replace(/\//g, "."),
1845
+ hidden: figmaExt(o, "hiddenFromPublishing") === true || void 0
1846
+ });
1847
+ return;
1848
+ }
1849
+ for (const [k, v] of Object.entries(o)) {
1850
+ if (k.startsWith("$")) continue;
1851
+ fileLeaves(v, prefix ? `${prefix}.${k}` : k, out, groupType);
1852
+ }
1853
+ }
1854
+ var hasFigmaExt = (node, depth = 0) => {
1855
+ if (!node || typeof node !== "object" || depth > 12) return false;
1856
+ const ext = node.$extensions;
1857
+ if (ext && Object.keys(ext).some((k) => k.startsWith("com.figma"))) return true;
1858
+ return Object.values(node).some((v) => hasFigmaExt(v, depth + 1));
1859
+ };
1860
+ function modeOf(file) {
1861
+ const root = file.data ?? {};
1862
+ const parts = file.name.replace(/\\/g, "/").split("/").filter(Boolean);
1863
+ const base = (parts.pop() ?? "tokens").replace(/(\.tokens)?\.json$/i, "");
1864
+ const named = figmaExt(root, "modeName");
1865
+ const split = base.match(/^(.+?)[\s._-]+(light|dark|night|day|default|mode[\s_-]*\d+)$/i);
1866
+ return {
1867
+ collection: parts.join("/") || (split ? split[1] : ""),
1868
+ mode: typeof named === "string" ? named : split ? split[2] : base
1869
+ };
1870
+ }
1871
+ function importTokenFiles(files, opts = {}) {
1872
+ const parsed = files.filter((f) => f.data && typeof f.data === "object");
1873
+ if (!parsed.length) throw new Error("No JSON token files found.");
1874
+ const figma = parsed.some((f) => hasFigmaExt(f.data));
1875
+ if (parsed.length === 1 && !figma) return importTokens(parsed[0].data, opts);
1876
+ const notes = [];
1877
+ const withMode = parsed.map((f) => ({ ...f, ...modeOf(f) }));
1878
+ const byCollection = /* @__PURE__ */ new Map();
1879
+ for (const f of withMode) byCollection.set(f.collection, [...byCollection.get(f.collection) ?? [], f]);
1880
+ const light = /* @__PURE__ */ new Map();
1881
+ const darkOverrides = /* @__PURE__ */ new Map();
1882
+ for (const [collection, list] of byCollection) {
1883
+ const dark = list.find((f) => DARK.test(f.mode));
1884
+ const def = list.find((f) => LIGHT.test(f.mode) && !DARK.test(f.mode)) ?? list.find((f) => !DARK.test(f.mode)) ?? list[0];
1885
+ if (list.length > 1) {
1886
+ const label = collection ? `\u201C${collection}\u201D` : "The files";
1887
+ notes.push(`${label}: modes ${list.map((f) => f.mode).join(", ")}; used \u201C${def.mode}\u201D${dark && dark !== def ? ` (and \u201C${dark.mode}\u201D as dark)` : ""}.`);
1888
+ }
1889
+ const read = (f, into) => {
1890
+ const leaves2 = [];
1891
+ fileLeaves(f.data, "", leaves2);
1892
+ for (const l of leaves2) into.set(l.path, { path: l.path, type: l.type, value: l.value, description: l.description, leaf: l });
1893
+ };
1894
+ read(def, light);
1895
+ if (dark && dark !== def) read(dark, darkOverrides);
1896
+ }
1897
+ const lightRaws = resolveRefs([...light.values()]);
1898
+ const darkRaws = resolveRefs([...light.values()].map((r) => darkOverrides.get(r.path) ?? r));
1899
+ const darkByPath = new Map(darkRaws.map((r) => [r.path, r.value]));
1900
+ const referenced = /* @__PURE__ */ new Set();
1901
+ for (const r of [...light.values(), ...darkOverrides.values()]) {
1902
+ if (r.leaf.aliasTarget) referenced.add(r.leaf.aliasTarget);
1903
+ const m = typeof r.value === "string" ? r.value.trim().match(REF) : null;
1904
+ if (m) referenced.add(m[1]);
1905
+ }
1906
+ const out = [];
1907
+ let hidden = 0;
1908
+ for (const r of lightRaws) {
1909
+ const leaf = light.get(r.path).leaf;
1910
+ if (leaf.hidden) {
1911
+ hidden++;
1912
+ continue;
1913
+ }
1914
+ const t = (r.type ?? "").toLowerCase();
1915
+ const kind = t === "color" ? "color" : t === "boolean" ? "boolean" : t === "string" || t === "fontfamily" || t === "fontweight" ? "string" : typeof r.value === "string" && !/^-?[\d.]+(px|rem)?$/.test(r.value) && !colorValue(r.value) ? "string" : colorValue(r.value) && t !== "number" && t !== "dimension" ? "color" : "number";
1916
+ let tok;
1917
+ if (figma) {
1918
+ const value = kind === "number" && r.value && typeof r.value === "object" ? parseFloat(dim(r.value)) : r.value;
1919
+ tok = classify(r.path.replace(/\./g, "/"), kind, value, { scopes: leaf.scopes?.filter((x2) => x2 !== "ALL_SCOPES") });
1920
+ } else {
1921
+ const flat = flattenTokens({ x: { $type: r.type, $value: r.value } })[0];
1922
+ tok = flat ? { path: flat.type === "color" && !r.path.startsWith("color.") ? `color.${r.path}` : r.path, type: flat.type, value: flat.value } : null;
1923
+ }
1924
+ if (!tok) continue;
1925
+ let darkValue;
1926
+ if (tok.type === "color" && darkOverrides.size) {
1927
+ const d = colorValue(darkByPath.get(r.path));
1928
+ if (d && d !== tok.value) darkValue = d;
1929
+ }
1930
+ out.push({ ...tok, ...darkValue ? { dark: darkValue } : {}, ...r.description ? { description: r.description } : {}, referenced: referenced.has(r.path) });
1931
+ }
1932
+ if (hidden) notes.push(`${hidden} variable${hidden === 1 ? "" : "s"} hidden from publishing were skipped.`);
1933
+ return finish(figma ? "figma-export" : "dtcg", out, notes, opts);
1934
+ }
1212
1935
  function importTokens(input, opts = {}) {
1213
1936
  if (!input || typeof input !== "object") throw new Error("Expected a JSON object.");
1214
1937
  const o = input;
1938
+ if (Array.isArray(o.$files)) return importTokenFiles(o.$files, opts);
1215
1939
  const meta = o.meta ?? o;
1216
1940
  if (meta.variables && meta.variableCollections) return fromFigmaVariables(meta, opts);
1217
1941
  if (Array.isArray(o.styles) && o.styles.every((s) => s && "styleType" in s)) return fromFigmaStyles(o.styles, opts);
1942
+ if (hasDollarValue(o) && hasFigmaExt(o)) return importTokenFiles([{ name: "tokens.json", data: o }], opts);
1218
1943
  if (hasDollarValue(o) || hasStudioValue(o)) {
1219
1944
  const studio = !hasDollarValue(o);
1220
1945
  const flat = flattenTokens(o);
@@ -1280,7 +2005,12 @@ function generateInstructions(config, opts = {}) {
1280
2005
  lines.push("Always use these components. Never re-create them with raw HTML elements:");
1281
2006
  for (const c of r.components) {
1282
2007
  const tags = [...r.replaces].filter(([, v]) => v === c).map(([t]) => `<${t}>`);
1283
- lines.push(`- \`${c.name}\` from \`${c.importPath}\`${tags.length ? ` (instead of ${tags.join(", ")})` : ""}`);
2008
+ const props = c.props?.length ? ` \u2014 props: ${c.props.map((p) => p.required ? `${p.name}*` : p.name).join(", ")}` : "";
2009
+ lines.push(`- \`${c.name}\` from \`${c.importPath}\`${tags.length ? ` (instead of ${tags.join(", ")})` : ""}${props}`);
2010
+ }
2011
+ if (r.components.some((c) => c.props?.length)) {
2012
+ lines.push("");
2013
+ lines.push("Pass only the props listed for each component (* = required). Any other prop is a type error and fails the check: if you need something a component doesn't take, look for a related component (often one with a longer name, like AvatarLabel next to Avatar) or compose it with design tokens.");
1284
2014
  }
1285
2015
  lines.push("");
1286
2016
  }
@@ -1300,7 +2030,7 @@ function generateInstructions(config, opts = {}) {
1300
2030
  const radiusVars = named(/^radius\./);
1301
2031
  const sizeVars = named(/^font\.size\./);
1302
2032
  lines.push(`- Spacing (padding, margin, gap) must be one of: ${r.spacing.filter((s) => s > 2).map((s) => `${s}px`).join(", ")}.${spaceVars.length ? ` Prefer the variables: ${spaceVars.join(", ")}.` : ""}`);
1303
- lines.push(`- Corner radius must be one of: ${r.radii.filter((x) => x > 0 && x < 999).map((x) => `${x}px`).join(", ")} (or fully rounded).${radiusVars.length ? ` Variables: ${radiusVars.join(", ")}.` : ""}`);
2033
+ lines.push(`- Corner radius must be one of: ${r.radii.filter((x2) => x2 > 0 && x2 < 999).map((x2) => `${x2}px`).join(", ")} (or fully rounded).${radiusVars.length ? ` Variables: ${radiusVars.join(", ")}.` : ""}`);
1304
2034
  if (sizeVars.length) lines.push(`- Font sizes come from the type scale: ${sizeVars.join(", ")}. Matching line heights are \`--font-line-height-*\`.`);
1305
2035
  if (r.fonts.length) lines.push(`- Fonts: use ${r.fontVars.map((v) => `\`var(${v})\``).join(" or ")}; never set another font-family.`);
1306
2036
  lines.push("");
@@ -1473,9 +2203,9 @@ function collectRenderedNodes(maxNodes = 3e3) {
1473
2203
  }
1474
2204
  var near = (scale, v, tol = 0.5) => scale.some((s) => Math.abs(s - v) <= tol);
1475
2205
  var neighbours2 = (scale, v) => {
1476
- const lo = [...scale].reverse().find((x) => x <= v);
1477
- const hi = scale.find((x) => x >= v);
1478
- return [...new Set([lo, hi].filter((x) => x !== void 0))].map((x) => `${x}px`).join(" or ");
2206
+ const lo = [...scale].reverse().find((x2) => x2 <= v);
2207
+ const hi = scale.find((x2) => x2 >= v);
2208
+ return [...new Set([lo, hi].filter((x2) => x2 !== void 0))].map((x2) => `${x2}px`).join(" or ");
1479
2209
  };
1480
2210
  function closestToken(config, rgb) {
1481
2211
  const lab = rgbToLab(rgb);
@@ -1541,17 +2271,17 @@ function checkRendered(pages, configInput, opts = {}) {
1541
2271
  const bg = n.effectiveBackground ? parseColor(n.effectiveBackground) : null;
1542
2272
  if (fg && bg && fg.a > 0.05) {
1543
2273
  const blended = fg.a < 1 ? { r: fg.r * fg.a + bg.r * (1 - fg.a), g: fg.g * fg.a + bg.g * (1 - fg.a), b: fg.b * fg.a + bg.b * (1 - fg.a), a: 1 } : fg;
1544
- const ratio = contrastRatio(blended, bg);
2274
+ const ratio2 = contrastRatio(blended, bg);
1545
2275
  const large = n.fontSize >= 24 || n.fontSize >= 18.66 && n.fontWeight >= 700;
1546
2276
  const min = large ? 3 : 4.5;
1547
- if (ratio < min) {
2277
+ if (ratio2 < min) {
1548
2278
  const pair = `${colorLabel(fg)} on ${toHex(bg)}`;
1549
2279
  add(
1550
2280
  `contrast:${pair}:${large}`,
1551
2281
  {
1552
2282
  ruleId: "text-contrast",
1553
2283
  severity: "error",
1554
- message: (c) => `${pair} is ${ratio.toFixed(2)}:1, below ${min}:1${large ? " for large text" : ""} (${c} element${c === 1 ? "" : "s"})`,
2284
+ message: (c) => `${pair} is ${ratio2.toFixed(2)}:1, below ${min}:1${large ? " for large text" : ""} (${c} element${c === 1 ? "" : "s"})`,
1555
2285
  suggestion: "Use the Brand Kit's text colours on its background colours, e.g. fg.default on canvas.default",
1556
2286
  sample: n.text
1557
2287
  },
@@ -1731,15 +2461,15 @@ var px2 = (v) => {
1731
2461
  const m = v.trim().match(/^(-?\d*\.?\d+)(px|rem)?$/);
1732
2462
  return m ? Number.parseFloat(m[1]) * (m[2] === "rem" ? 16 : 1) : null;
1733
2463
  };
1734
- var nearest2 = (values, v) => values.reduce((best, x) => Math.abs(x - v) < Math.abs(best - v) ? x : best, values[0]);
2464
+ var nearest2 = (values, v) => values.reduce((best, x2) => Math.abs(x2 - v) < Math.abs(best - v) ? x2 : best, values[0]);
1735
2465
  function luminance(hex) {
1736
2466
  const n = hex.replace("#", "").slice(0, 6);
1737
- const [r, g, b] = [0, 2, 4].map((i) => parseInt(n.slice(i, i + 2), 16) / 255).map((c) => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
2467
+ const [r, g, b] = [0, 2, 4].map((i2) => parseInt(n.slice(i2, i2 + 2), 16) / 255).map((c) => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
1738
2468
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
1739
2469
  }
1740
2470
  var contrast = (a, b) => {
1741
- const [x, y] = [luminance(a), luminance(b)].sort((p, q) => q - p);
1742
- return (x + 0.05) / (y + 0.05);
2471
+ const [x2, y] = [luminance(a), luminance(b)].sort((p, q) => q - p);
2472
+ return (x2 + 0.05) / (y + 0.05);
1743
2473
  };
1744
2474
  function onColor(bg, candidates) {
1745
2475
  return candidates.filter((c) => HEX.test(c)).sort((a, b) => contrast(b, bg) - contrast(a, bg))[0] ?? "#ffffff";
@@ -1779,7 +2509,7 @@ function scaleVars(s) {
1779
2509
  function colorVars(colors, dark, font) {
1780
2510
  const r = pickColorRoles(colors, dark);
1781
2511
  const find = (re) => {
1782
- const c = colors.find((x) => re.test(x.path));
2512
+ const c = colors.find((x2) => re.test(x2.path));
1783
2513
  return c ? dark ? c.dark ?? c.value : c.value : void 0;
1784
2514
  };
1785
2515
  const success = find(/success/);
@@ -1829,8 +2559,126 @@ function kitBridgeCss(tokens, opts = {}) {
1829
2559
  return [block(sel.scale, scales), block(sel.light, colorVars(colors, false, sc.font)), block(sel.dark, colorVars(colors, true, sc.font))].filter(Boolean).join("\n");
1830
2560
  }
1831
2561
 
2562
+ // ../engine/src/brand-kit-audit.ts
2563
+ var ratio = (a, b) => {
2564
+ const x2 = parseColor(a);
2565
+ const y = parseColor(b);
2566
+ return x2 && y ? Math.round(contrastRatio(x2, y) * 100) / 100 : 21;
2567
+ };
2568
+ var r1 = (n) => `${Math.round(n * 10) / 10}:1`;
2569
+ var STATUS = [
2570
+ { key: "success", re: /success|positive/, label: "success", usedBy: "success notifications, checks and toggles" },
2571
+ { key: "warning", re: /warning|attention|caution/, label: "warning", usedBy: "warning notifications and states" },
2572
+ { key: "danger", re: /danger|error|critical|negative/, label: "danger / error", usedBy: "errors, destructive buttons and invalid fields" }
2573
+ ];
2574
+ function auditBrandKit(tokens) {
2575
+ const colors = flattenTokens(tokens).filter((t) => t.type === "color").map((t) => ({ path: t.path, value: t.value, dark: t.dark }));
2576
+ const issues = [];
2577
+ if (!colors.length) return issues;
2578
+ const modes = colors.some((c) => c.dark) ? ["light", "dark"] : ["light"];
2579
+ const light = pickColorRoles(colors, false);
2580
+ for (const mode of modes) {
2581
+ const dark = mode === "dark";
2582
+ const r = pickColorRoles(colors, dark);
2583
+ const where = modes.length > 1 ? ` (${mode} mode)` : "";
2584
+ const paths = (...xs) => xs.filter((x2) => !!x2);
2585
+ const text = ratio(r.fg.value, r.canvas.value);
2586
+ if (text < 4.5) {
2587
+ issues.push({
2588
+ severity: "error",
2589
+ code: "text-contrast",
2590
+ mode,
2591
+ paths: paths(r.fg.path, r.canvas.path),
2592
+ message: `Body text on the page background is ${r1(text)}${where}. It needs at least 4.5:1 to be readable.`
2593
+ });
2594
+ }
2595
+ const muted = ratio(r.muted.value, r.canvas.value);
2596
+ if (muted < 4.5) {
2597
+ issues.push({
2598
+ severity: muted < 3 ? "error" : "warning",
2599
+ code: "muted-contrast",
2600
+ mode,
2601
+ paths: paths(r.muted.path, r.canvas.path),
2602
+ message: `Secondary text on the page background is ${r1(muted)}${where}. Captions and help text need 4.5:1; darken ${r.muted.path ?? "the muted colour"}.`
2603
+ });
2604
+ }
2605
+ for (const [role, what] of [
2606
+ ["primary", "primary buttons"],
2607
+ ["danger", "destructive buttons"]
2608
+ ]) {
2609
+ if (!r[role].path) continue;
2610
+ const fill = r[role].value;
2611
+ const best = Math.max(ratio(light.canvas.value, fill), ratio(light.fg.value, fill));
2612
+ if (best < 4.5) {
2613
+ issues.push({
2614
+ severity: best < 3 ? "error" : "warning",
2615
+ code: "on-fill-contrast",
2616
+ mode,
2617
+ paths: [r[role].path],
2618
+ message: `Labels on ${what} are at most ${r1(best)}${where}: neither your background nor your text colour reads well on ${fill}. Use a darker or lighter ${r[role].path} (4.5:1 for text).`
2619
+ });
2620
+ }
2621
+ }
2622
+ for (const [role, what] of [
2623
+ ["primary", "ghost buttons, selected items and focus rings"],
2624
+ ["accent", "links and highlights"]
2625
+ ]) {
2626
+ if (!r[role].path) continue;
2627
+ const vs = ratio(r[role].value, r.canvas.value);
2628
+ if (vs < 4.5) {
2629
+ issues.push({
2630
+ severity: vs < 3 ? "error" : "warning",
2631
+ code: "status-contrast",
2632
+ mode,
2633
+ paths: [r[role].path],
2634
+ message: `${r[role].path} (${r[role].value}) is ${r1(vs)} on the page background${where}. Components use it as text and outlines for ${what}, which need 4.5:1 for text and 3:1 for icons and borders. Darken it, or add a darker variant for text.`
2635
+ });
2636
+ }
2637
+ }
2638
+ for (const s of STATUS) {
2639
+ const c = colors.find((x2) => s.re.test(x2.path));
2640
+ if (!c) continue;
2641
+ const value = dark ? c.dark ?? c.value : c.value;
2642
+ const vs = ratio(value, r.canvas.value);
2643
+ if (vs < 3) {
2644
+ issues.push({
2645
+ severity: "warning",
2646
+ code: "status-contrast",
2647
+ mode,
2648
+ paths: [c.path],
2649
+ message: `${c.path} (${value}) is ${r1(vs)} on the page background${where}: too light for text or icons (3:1 for icons, 4.5:1 for text). It's fine as a fill behind dark text; add a darker variant for text.`
2650
+ });
2651
+ }
2652
+ }
2653
+ }
2654
+ if (!light.primary.path) {
2655
+ issues.push({
2656
+ severity: "warning",
2657
+ code: "missing-color",
2658
+ paths: [],
2659
+ message: "No primary colour (e.g. color.action.primary). Buttons and links fall back to the kit's default green."
2660
+ });
2661
+ }
2662
+ for (const s of STATUS) {
2663
+ if (colors.some((x2) => s.re.test(x2.path))) continue;
2664
+ issues.push({
2665
+ severity: "warning",
2666
+ code: "missing-color",
2667
+ paths: [],
2668
+ message: `No ${s.label} colour (e.g. color.status.${s.key}). It's used for ${s.usedBy}, which fall back to the kit's own colour instead of your brand.`
2669
+ });
2670
+ }
2671
+ const seen = /* @__PURE__ */ new Set();
2672
+ return issues.filter((i2) => {
2673
+ const key = `${i2.code}:${i2.paths.join(",")}:${i2.message.replace(/ \((light|dark) mode\)/, "").replace(/[\d.]+:1/g, "")}`;
2674
+ if (seen.has(key)) return false;
2675
+ seen.add(key);
2676
+ return true;
2677
+ }).sort((a, b) => a.severity === b.severity ? 0 : a.severity === "error" ? -1 : 1);
2678
+ }
2679
+
1832
2680
  // src/api.ts
1833
- var DEFAULT_API = "https://web-phi-nine-77.vercel.app";
2681
+ var DEFAULT_API = "https://www.brandrail.com.au";
1834
2682
  function apiBase() {
1835
2683
  return (process.env.BRANDRAIL_API_URL ?? DEFAULT_API).replace(/\/$/, "");
1836
2684
  }
@@ -1941,7 +2789,7 @@ function parseChangedLines(diff) {
1941
2789
  const start = Number(h[1]);
1942
2790
  const count = h[2] === void 0 ? 1 : Number(h[2]);
1943
2791
  const set = out.get(file);
1944
- for (let i = 0; i < count; i++) set.add(start + i);
2792
+ for (let i2 = 0; i2 < count; i2++) set.add(start + i2);
1945
2793
  }
1946
2794
  }
1947
2795
  return out;
@@ -1954,13 +2802,13 @@ function changedLines(cwd, base) {
1954
2802
 
1955
2803
  // src/scan.ts
1956
2804
  import { existsSync as existsSync2 } from "node:fs";
1957
- import { createRequire } from "node:module";
2805
+ import { createRequire as createRequire2 } from "node:module";
1958
2806
  import { join as join2, resolve as resolve2 } from "node:path";
1959
2807
  function loadPlaywright(cwd) {
1960
2808
  for (const base of [join2(cwd, "package.json"), import.meta.url]) {
1961
2809
  for (const name of ["playwright", "playwright-core"]) {
1962
2810
  try {
1963
- return createRequire(base)(name);
2811
+ return createRequire2(base)(name);
1964
2812
  } catch {
1965
2813
  }
1966
2814
  }
@@ -2016,7 +2864,7 @@ Usage
2016
2864
  brandrail check [paths...] Scan files and print violations
2017
2865
  brandrail init Create brandrail.config.json and AI rule files
2018
2866
  brandrail scan <url> Check a running page (localhost, staging, behind a login) in a real browser
2019
- brandrail import <file> Load tokens (DTCG, Tokens Studio, Figma variables JSON) into the config
2867
+ brandrail import <files\u2026> Load tokens into the config: DTCG, Tokens Studio, Figma variables JSON, or Figma's Export modes ZIP
2020
2868
  brandrail rules List the rules
2021
2869
 
2022
2870
  Options for check
@@ -2055,16 +2903,16 @@ function parseArgs(argv) {
2055
2903
  const [cmd = "help", ...rest] = argv;
2056
2904
  const positional = [];
2057
2905
  const flags = {};
2058
- for (let i = 0; i < rest.length; i++) {
2059
- const a = rest[i];
2906
+ for (let i2 = 0; i2 < rest.length; i2++) {
2907
+ const a = rest[i2];
2060
2908
  if (a.startsWith("--no-")) flags[a.slice(5)] = false;
2061
2909
  else if (a.startsWith("--")) {
2062
2910
  const [k, v] = a.slice(2).split("=", 2);
2063
- const next = rest[i + 1];
2911
+ const next = rest[i2 + 1];
2064
2912
  if (v !== void 0) flags[k] = v;
2065
2913
  else if (next !== void 0 && !next.startsWith("-") && ["config", "format", "fail-below", "changed", "pages", "storage-state", "wait", "width", "tokens-css"].includes(k)) {
2066
2914
  flags[k] = next;
2067
- i++;
2915
+ i2++;
2068
2916
  } else flags[k] = true;
2069
2917
  } else positional.push(a);
2070
2918
  }
@@ -2199,7 +3047,7 @@ function writeMcpConfigs(root) {
2199
3047
  for (const [k, v] of Object.entries(ours)) {
2200
3048
  if (Array.isArray(v)) {
2201
3049
  const prev = Array.isArray(existing[k]) ? existing[k] : [];
2202
- merged[k] = [...prev.filter((x) => !v.some((y) => y.id === x.id)), ...v];
3050
+ merged[k] = [...prev.filter((x2) => !v.some((y) => y.id === x2.id)), ...v];
2203
3051
  } else merged[k] = { ...existing[k] ?? {}, ...v };
2204
3052
  }
2205
3053
  } catch {
@@ -2211,7 +3059,7 @@ function writeMcpConfigs(root) {
2211
3059
  `);
2212
3060
  log(`\u2714 added brandrail to ${c.file} (${c.name})`);
2213
3061
  }
2214
- for (const c of mcpClientConfigs(url).filter((x) => !x.projectFile)) {
3062
+ for (const c of mcpClientConfigs(url).filter((x2) => !x2.projectFile)) {
2215
3063
  log(`
2216
3064
  ${c.name}: ${c.command ? `run
2217
3065
  ${c.command}
@@ -2258,7 +3106,7 @@ async function cmdScan(cwd, positional, flags) {
2258
3106
  const url = positional[0];
2259
3107
  if (!url) throw new Error("Usage: brandrail scan <url> [--pages 3] [--storage-state auth.json] [--upload]");
2260
3108
  const { config, source } = await loadConfig(cwd, flags);
2261
- const num = (k, d, max) => typeof flags[k] === "string" ? Math.min(Math.max(Number(flags[k]) || d, 0), max) : d;
3109
+ const num = (k, d, max2) => typeof flags[k] === "string" ? Math.min(Math.max(Number(flags[k]) || d, 0), max2) : d;
2262
3110
  log(`Scanning ${url} \u2026`);
2263
3111
  const { report, pages } = await scanPages(
2264
3112
  cwd,
@@ -2307,11 +3155,31 @@ Brand score ${report.score} is below ${threshold}.`);
2307
3155
  }
2308
3156
  return 0;
2309
3157
  }
3158
+ function readTokenInputs(cwd, inputs) {
3159
+ const files = [];
3160
+ const add = (name, text) => files.push({ name, data: JSON.parse(text) });
3161
+ const walk = (abs, rel) => {
3162
+ if (statSync2(abs).isDirectory()) {
3163
+ for (const e of readdirSync2(abs)) if (!e.startsWith(".")) walk(join3(abs, e), rel ? `${rel}/${e}` : e);
3164
+ } else if (/\.zip$/i.test(abs)) {
3165
+ const entries = unzipSync(new Uint8Array(readFileSync2(abs)), { filter: (e) => /\.json$/i.test(e.name) && !/(^|\/)(__MACOSX|\.)/.test(e.name) });
3166
+ for (const [name, bytes] of Object.entries(entries)) add(name, strFromU8(bytes));
3167
+ } else if (/\.json$/i.test(abs)) {
3168
+ add(rel || basename(abs), readFileSync2(abs, "utf8"));
3169
+ }
3170
+ };
3171
+ for (const input of inputs) {
3172
+ const abs = resolve3(cwd, input);
3173
+ walk(abs, statSync2(abs).isDirectory() ? "" : basename(abs));
3174
+ }
3175
+ return files;
3176
+ }
2310
3177
  function cmdImport(cwd, positional, flags) {
2311
- const file = positional[0];
2312
- if (!file) throw new Error("Usage: brandrail import <tokens.json> [--replace] [--primitives]");
2313
- const raw = JSON.parse(readFileSync2(resolve3(cwd, file), "utf8"));
2314
- const res = importTokens(raw, { includePrimitives: !!flags.primitives });
3178
+ if (!positional.length) throw new Error("Usage: brandrail import <tokens.json | folder | export.zip> [more files\u2026] [--replace] [--primitives]");
3179
+ const files = readTokenInputs(cwd, positional);
3180
+ if (!files.length) throw new Error("No .json token files found.");
3181
+ const res = importTokenFiles(files, { includePrimitives: !!flags.primitives });
3182
+ const file = files.length === 1 ? positional[0] : `${files.length} files`;
2315
3183
  const path = (typeof flags.config === "string" ? resolve3(cwd, flags.config) : findConfig(cwd)) ?? join3(cwd, CONFIG_FILE);
2316
3184
  const config = existsSync3(path) ? readConfig(path) : { ...starterConfig, tokens: {} };
2317
3185
  const next = flags.replace ? res.tokens : mergeTokens(config.tokens, res.tokens);
@@ -2320,6 +3188,7 @@ function cmdImport(cwd, positional, flags) {
2320
3188
  `);
2321
3189
  log(`\u2714 ${res.count} tokens from ${file} (${res.format})${res.primitives ? `, ${res.primitives} palette primitives left out (--primitives to keep)` : ""}`);
2322
3190
  for (const n of res.notes) log(` \xB7 ${n}`);
3191
+ for (const i2 of auditBrandKit(next)) log(` ${i2.severity === "error" ? "\u2716" : "!"} ${i2.message}`);
2323
3192
  log(`\u2714 ${relative2(cwd, path) || CONFIG_FILE}: ${d.changed.length} changed, ${d.added.length} added, ${d.removed.length} removed`);
2324
3193
  log("\nNext: npx brandrail init (to refresh AI rule files), then npx brandrail check");
2325
3194
  return 0;
@@ -2355,8 +3224,8 @@ async function main(argv = process.argv.slice(2)) {
2355
3224
  }
2356
3225
  main().then(
2357
3226
  (code) => process.exit(code),
2358
- (err) => {
2359
- log(`brandrail: ${err.message}`);
3227
+ (err2) => {
3228
+ log(`brandrail: ${err2.message}`);
2360
3229
  process.exit(2);
2361
3230
  }
2362
3231
  );