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.
- package/dist/brandrail.mjs +957 -88
- package/package.json +3 -2
package/dist/brandrail.mjs
CHANGED
|
@@ -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 = (
|
|
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,
|
|
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,
|
|
37
|
-
if ([r2, g2,
|
|
38
|
-
return { r: clamp(r2), g: clamp(g2), b: clamp(
|
|
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
|
|
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(
|
|
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((
|
|
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((
|
|
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,
|
|
237
|
-
const start = Math.max(code.lastIndexOf("\n", offset - 1) + 1, offset -
|
|
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((
|
|
248
|
-
const upper = scale.find((
|
|
249
|
-
const opts = [...new Set([lower, upper].filter((
|
|
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
|
|
1112
|
+
let i2 = 0;
|
|
521
1113
|
let quote = null;
|
|
522
|
-
while (
|
|
523
|
-
const c = chars[
|
|
524
|
-
const n = chars[
|
|
1114
|
+
while (i2 < chars.length) {
|
|
1115
|
+
const c = chars[i2];
|
|
1116
|
+
const n = chars[i2 + 1];
|
|
525
1117
|
if (quote) {
|
|
526
|
-
if (c === "\\")
|
|
1118
|
+
if (c === "\\") i2 += 2;
|
|
527
1119
|
else {
|
|
528
1120
|
if (c === quote) quote = null;
|
|
529
|
-
|
|
1121
|
+
i2++;
|
|
530
1122
|
}
|
|
531
1123
|
continue;
|
|
532
1124
|
}
|
|
533
1125
|
if (c === '"' || c === "'" || c === "`") {
|
|
534
1126
|
quote = c;
|
|
535
|
-
|
|
1127
|
+
i2++;
|
|
536
1128
|
continue;
|
|
537
1129
|
}
|
|
538
1130
|
if (c === "/" && n === "*") {
|
|
539
|
-
const end = out.indexOf("*/",
|
|
1131
|
+
const end = out.indexOf("*/", i2 + 2);
|
|
540
1132
|
const stop = end === -1 ? chars.length : end + 2;
|
|
541
|
-
for (let j =
|
|
542
|
-
|
|
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[
|
|
546
|
-
while (
|
|
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
|
-
|
|
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
|
|
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
|
|
582
|
-
const c = glob[
|
|
1173
|
+
for (let i2 = 0; i2 < glob.length; i2++) {
|
|
1174
|
+
const c = glob[i2];
|
|
583
1175
|
if (c === "*") {
|
|
584
|
-
if (glob[
|
|
585
|
-
const slash = glob[
|
|
1176
|
+
if (glob[i2 + 1] === "*") {
|
|
1177
|
+
const slash = glob[i2 + 2] === "/";
|
|
586
1178
|
re += slash ? "(?:.*/)?" : ".*";
|
|
587
|
-
|
|
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("}",
|
|
592
|
-
re += `(?:${glob.slice(
|
|
593
|
-
|
|
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.
|
|
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,
|
|
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 >
|
|
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,
|
|
1302
|
+
function formatGithubAnnotations(report, max2 = 50) {
|
|
711
1303
|
const level = { error: "error", warning: "warning", info: "notice" };
|
|
712
|
-
return report.violations.slice(0,
|
|
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) =>
|
|
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((
|
|
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((
|
|
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,
|
|
1004
|
-
if (
|
|
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((
|
|
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],
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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((
|
|
1477
|
-
const hi = scale.find((
|
|
1478
|
-
return [...new Set([lo, hi].filter((
|
|
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
|
|
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 (
|
|
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 ${
|
|
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,
|
|
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((
|
|
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 [
|
|
1742
|
-
return (
|
|
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((
|
|
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://
|
|
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
|
|
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
|
|
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 <
|
|
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
|
|
2059
|
-
const a = rest[
|
|
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[
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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,
|
|
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
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
const res =
|
|
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
|
-
(
|
|
2359
|
-
log(`brandrail: ${
|
|
3227
|
+
(err2) => {
|
|
3228
|
+
log(`brandrail: ${err2.message}`);
|
|
2360
3229
|
process.exit(2);
|
|
2361
3230
|
}
|
|
2362
3231
|
);
|