brandrail 0.3.1 → 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 +856 -103
- 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) {
|
|
@@ -501,15 +984,15 @@ function importedComponents(code, components) {
|
|
|
501
984
|
}
|
|
502
985
|
return out;
|
|
503
986
|
}
|
|
504
|
-
function skipBalanced(code,
|
|
505
|
-
const open = code[
|
|
987
|
+
function skipBalanced(code, i2) {
|
|
988
|
+
const open = code[i2];
|
|
506
989
|
if (open === '"' || open === "'") {
|
|
507
|
-
const end = code.indexOf(open,
|
|
990
|
+
const end = code.indexOf(open, i2 + 1);
|
|
508
991
|
return end === -1 ? code.length : end + 1;
|
|
509
992
|
}
|
|
510
993
|
let depth = 0;
|
|
511
994
|
let quote = null;
|
|
512
|
-
for (let j =
|
|
995
|
+
for (let j = i2; j < code.length; j++) {
|
|
513
996
|
const c = code[j];
|
|
514
997
|
if (quote) {
|
|
515
998
|
if (c === "\\") j++;
|
|
@@ -525,27 +1008,27 @@ function skipBalanced(code, i) {
|
|
|
525
1008
|
function parseAttributes(code, start) {
|
|
526
1009
|
const attrs = [];
|
|
527
1010
|
let spread = false;
|
|
528
|
-
let
|
|
529
|
-
while (
|
|
530
|
-
while (/\s/.test(code[
|
|
531
|
-
const c = code[
|
|
1011
|
+
let i2 = start;
|
|
1012
|
+
while (i2 < code.length) {
|
|
1013
|
+
while (/\s/.test(code[i2] ?? "")) i2++;
|
|
1014
|
+
const c = code[i2];
|
|
532
1015
|
if (c === void 0) return null;
|
|
533
1016
|
if (c === ">") return { attrs, spread, selfClosing: false };
|
|
534
|
-
if (c === "/" && code[
|
|
1017
|
+
if (c === "/" && code[i2 + 1] === ">") return { attrs, spread, selfClosing: true };
|
|
535
1018
|
if (c === "{") {
|
|
536
1019
|
spread = true;
|
|
537
|
-
|
|
1020
|
+
i2 = skipBalanced(code, i2);
|
|
538
1021
|
continue;
|
|
539
1022
|
}
|
|
540
|
-
const name = /^[A-Za-z_$][\w$:.-]*/.exec(code.slice(
|
|
1023
|
+
const name = /^[A-Za-z_$][\w$:.-]*/.exec(code.slice(i2, i2 + 80))?.[0];
|
|
541
1024
|
if (!name) return null;
|
|
542
|
-
attrs.push({ name, offset:
|
|
543
|
-
|
|
544
|
-
while (/\s/.test(code[
|
|
545
|
-
if (code[
|
|
546
|
-
|
|
547
|
-
while (/\s/.test(code[
|
|
548
|
-
|
|
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);
|
|
549
1032
|
}
|
|
550
1033
|
}
|
|
551
1034
|
return null;
|
|
@@ -626,36 +1109,36 @@ function stripComments(code, kind) {
|
|
|
626
1109
|
return out.replace(/\/\*[\s\S]*?\*\//g, blank).replace(/(^|[^:])\/\/[^\n]*/g, (m, p1) => p1 + blank(m.slice(p1.length)));
|
|
627
1110
|
}
|
|
628
1111
|
const chars = out.split("");
|
|
629
|
-
let
|
|
1112
|
+
let i2 = 0;
|
|
630
1113
|
let quote = null;
|
|
631
|
-
while (
|
|
632
|
-
const c = chars[
|
|
633
|
-
const n = chars[
|
|
1114
|
+
while (i2 < chars.length) {
|
|
1115
|
+
const c = chars[i2];
|
|
1116
|
+
const n = chars[i2 + 1];
|
|
634
1117
|
if (quote) {
|
|
635
|
-
if (c === "\\")
|
|
1118
|
+
if (c === "\\") i2 += 2;
|
|
636
1119
|
else {
|
|
637
1120
|
if (c === quote) quote = null;
|
|
638
|
-
|
|
1121
|
+
i2++;
|
|
639
1122
|
}
|
|
640
1123
|
continue;
|
|
641
1124
|
}
|
|
642
1125
|
if (c === '"' || c === "'" || c === "`") {
|
|
643
1126
|
quote = c;
|
|
644
|
-
|
|
1127
|
+
i2++;
|
|
645
1128
|
continue;
|
|
646
1129
|
}
|
|
647
1130
|
if (c === "/" && n === "*") {
|
|
648
|
-
const end = out.indexOf("*/",
|
|
1131
|
+
const end = out.indexOf("*/", i2 + 2);
|
|
649
1132
|
const stop = end === -1 ? chars.length : end + 2;
|
|
650
|
-
for (let j =
|
|
651
|
-
|
|
1133
|
+
for (let j = i2; j < stop; j++) if (chars[j] !== "\n") chars[j] = " ";
|
|
1134
|
+
i2 = stop;
|
|
652
1135
|
continue;
|
|
653
1136
|
}
|
|
654
|
-
if (c === "/" && n === "/" && chars[
|
|
655
|
-
while (
|
|
1137
|
+
if (c === "/" && n === "/" && chars[i2 - 1] !== ":" && kind === "jsx") {
|
|
1138
|
+
while (i2 < chars.length && chars[i2] !== "\n") chars[i2++] = " ";
|
|
656
1139
|
continue;
|
|
657
1140
|
}
|
|
658
|
-
|
|
1141
|
+
i2++;
|
|
659
1142
|
}
|
|
660
1143
|
return chars.join("");
|
|
661
1144
|
}
|
|
@@ -664,7 +1147,7 @@ var LineIndex = class {
|
|
|
664
1147
|
text;
|
|
665
1148
|
constructor(text) {
|
|
666
1149
|
this.text = text;
|
|
667
|
-
for (let
|
|
1150
|
+
for (let i2 = 0; i2 < text.length; i2++) if (text.charCodeAt(i2) === 10) this.starts.push(i2 + 1);
|
|
668
1151
|
}
|
|
669
1152
|
position(offset) {
|
|
670
1153
|
let lo = 0;
|
|
@@ -687,19 +1170,19 @@ var LineIndex = class {
|
|
|
687
1170
|
};
|
|
688
1171
|
function globToRegExp(glob) {
|
|
689
1172
|
let re = "";
|
|
690
|
-
for (let
|
|
691
|
-
const c = glob[
|
|
1173
|
+
for (let i2 = 0; i2 < glob.length; i2++) {
|
|
1174
|
+
const c = glob[i2];
|
|
692
1175
|
if (c === "*") {
|
|
693
|
-
if (glob[
|
|
694
|
-
const slash = glob[
|
|
1176
|
+
if (glob[i2 + 1] === "*") {
|
|
1177
|
+
const slash = glob[i2 + 2] === "/";
|
|
695
1178
|
re += slash ? "(?:.*/)?" : ".*";
|
|
696
|
-
|
|
1179
|
+
i2 += slash ? 2 : 1;
|
|
697
1180
|
} else re += "[^/]*";
|
|
698
1181
|
} else if (c === "?") re += "[^/]";
|
|
699
1182
|
else if (c === "{") {
|
|
700
|
-
const end = glob.indexOf("}",
|
|
701
|
-
re += `(?:${glob.slice(
|
|
702
|
-
|
|
1183
|
+
const end = glob.indexOf("}", i2);
|
|
1184
|
+
re += `(?:${glob.slice(i2 + 1, end).split(",").map((s) => s.replace(/[.+^$()|[\]\\]/g, "\\$&")).join("|")})`;
|
|
1185
|
+
i2 = end;
|
|
703
1186
|
} else re += c.replace(/[.+^$()|[\]\\]/g, "\\$&");
|
|
704
1187
|
}
|
|
705
1188
|
return new RegExp(`^${re}$`);
|
|
@@ -713,7 +1196,7 @@ function makeIgnore(globs) {
|
|
|
713
1196
|
}
|
|
714
1197
|
|
|
715
1198
|
// ../engine/src/check.ts
|
|
716
|
-
var ENGINE_VERSION = "0.3.
|
|
1199
|
+
var ENGINE_VERSION = "0.3.2";
|
|
717
1200
|
var WEIGHT = { error: 4, warning: 1.5, info: 0.25 };
|
|
718
1201
|
function computeScore(counts, lines) {
|
|
719
1202
|
const penalty = counts.error * WEIGHT.error + counts.warning * WEIGHT.warning + counts.info * WEIGHT.info;
|
|
@@ -789,12 +1272,12 @@ var ansi = (on) => {
|
|
|
789
1272
|
return { red: w("31"), yellow: w("33"), cyan: w("36"), green: w("32"), dim: w("2"), bold: w("1") };
|
|
790
1273
|
};
|
|
791
1274
|
var rendered = (r) => r.violations.length > 0 && r.violations.every((v) => v.line === 0);
|
|
792
|
-
function formatPretty(report, { color = true, max = 200 } = {}) {
|
|
1275
|
+
function formatPretty(report, { color = true, max: max2 = 200 } = {}) {
|
|
793
1276
|
const c = ansi(color);
|
|
794
1277
|
const sev = { error: c.red, warning: c.yellow, info: c.cyan };
|
|
795
1278
|
const lines = [];
|
|
796
1279
|
let lastFile = "";
|
|
797
|
-
for (const v of report.violations.slice(0,
|
|
1280
|
+
for (const v of report.violations.slice(0, max2)) {
|
|
798
1281
|
if (v.file !== lastFile) {
|
|
799
1282
|
lines.push("", c.bold(v.file));
|
|
800
1283
|
lastFile = v.file;
|
|
@@ -804,7 +1287,7 @@ function formatPretty(report, { color = true, max = 200 } = {}) {
|
|
|
804
1287
|
if (v.suggestion) lines.push(` ${" ".repeat(16)} ${c.green("\u2192")} ${v.suggestion}`);
|
|
805
1288
|
if (rendered2 && v.snippet) lines.push(` ${" ".repeat(16)} ${c.dim(`on ${v.snippet}`)}`);
|
|
806
1289
|
}
|
|
807
|
-
if (report.violations.length >
|
|
1290
|
+
if (report.violations.length > max2) lines.push("", c.dim(`\u2026 and ${report.violations.length - max2} more`));
|
|
808
1291
|
const s = report.score;
|
|
809
1292
|
const scoreStr = s >= 80 ? c.green(String(s)) : s >= 65 ? c.yellow(String(s)) : c.red(String(s));
|
|
810
1293
|
lines.push(
|
|
@@ -816,9 +1299,9 @@ function formatPretty(report, { color = true, max = 200 } = {}) {
|
|
|
816
1299
|
}
|
|
817
1300
|
var esc = (s) => s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
818
1301
|
var escProp = (s) => esc(s).replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
819
|
-
function formatGithubAnnotations(report,
|
|
1302
|
+
function formatGithubAnnotations(report, max2 = 50) {
|
|
820
1303
|
const level = { error: "error", warning: "warning", info: "notice" };
|
|
821
|
-
return report.violations.slice(0,
|
|
1304
|
+
return report.violations.slice(0, max2).map(
|
|
822
1305
|
(v) => `::${level[v.severity]} file=${escProp(v.file)},line=${v.line},col=${v.column},title=${escProp(`Brandrail: ${title(v.ruleId)}`)}::${esc(
|
|
823
1306
|
v.suggestion ? `${v.message}. ${v.suggestion}` : v.message
|
|
824
1307
|
)}`
|
|
@@ -931,6 +1414,17 @@ function fontWeight(v) {
|
|
|
931
1414
|
return WEIGHTS[slug(v)] ?? (/^\d{3}$/.test(v.trim()) ? v.trim() : v);
|
|
932
1415
|
}
|
|
933
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
|
+
}
|
|
934
1428
|
if (v && typeof v === "object" && "r" in v) {
|
|
935
1429
|
const c = v;
|
|
936
1430
|
const hex2 = toHex({ r: c.r * 255, g: c.g * 255, b: c.b * 255, a: 1 });
|
|
@@ -948,7 +1442,14 @@ function colorValue(v) {
|
|
|
948
1442
|
const hex = toHex(rgb);
|
|
949
1443
|
return rgb.a < 1 ? `${hex}${Math.round(rgb.a * 255).toString(16).padStart(2, "0")}` : hex;
|
|
950
1444
|
}
|
|
951
|
-
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
|
+
};
|
|
952
1453
|
function classify(name, kind, value, opts = {}) {
|
|
953
1454
|
const parts = segs(name);
|
|
954
1455
|
const hay = slug(`${opts.context ?? ""} ${name}`);
|
|
@@ -980,7 +1481,7 @@ function classify(name, kind, value, opts = {}) {
|
|
|
980
1481
|
return null;
|
|
981
1482
|
}
|
|
982
1483
|
const v = dim(value);
|
|
983
|
-
const has = (...scope) => scope.some((
|
|
1484
|
+
const has = (...scope) => scope.some((x2) => scopes.includes(x2));
|
|
984
1485
|
const nm = (...prefixes) => stripPrefix(last, ...prefixes) || last;
|
|
985
1486
|
if (has("CORNER_RADIUS") || /radius|(^|-)rad(-|$)|corner|rounded/.test(hay)) return { path: `radius.${nm("radius", "rad", "corner", "rounded")}`, type: "dimension", value: v };
|
|
986
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 };
|
|
@@ -990,7 +1491,7 @@ function classify(name, kind, value, opts = {}) {
|
|
|
990
1491
|
if (/icon/.test(last)) return { path: `size.icon.${nm("icon")}`, type: "dimension", value: v };
|
|
991
1492
|
if (has("GAP") || /spac|gap|padding|margin|inset|(^|-)space(-|$)/.test(hay)) return { path: `space.${nm("spacing", "space", "gap")}`, type: "dimension", value: v };
|
|
992
1493
|
if (has("STROKE_FLOAT") || /stroke|border-width/.test(hay)) return { path: `border.width.${nm("border-width", "stroke")}`, type: "dimension", value: v };
|
|
993
|
-
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 };
|
|
994
1495
|
return { path: ["number", ...parts].join("."), type: "number", value: String(value) };
|
|
995
1496
|
}
|
|
996
1497
|
function rawLeaves(node, prefix, out, studio, inherited) {
|
|
@@ -1051,8 +1552,8 @@ function flattenTokens(doc) {
|
|
|
1051
1552
|
if (!v) continue;
|
|
1052
1553
|
const d = r.dark !== void 0 ? colorValue(r.dark) : null;
|
|
1053
1554
|
out.push({ path: r.path, type: "color", value: v, ...d ? { dark: d } : {}, ...r.description ? { description: r.description } : {} });
|
|
1054
|
-
} else if (typeof r.value === "string" || typeof r.value === "number") {
|
|
1055
|
-
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";
|
|
1056
1557
|
out.push({ path: r.path, type, value: type === "dimension" ? dim(r.value) : String(r.value), ...r.description ? { description: r.description } : {} });
|
|
1057
1558
|
}
|
|
1058
1559
|
}
|
|
@@ -1111,8 +1612,8 @@ function buildDtcg(tokens) {
|
|
|
1111
1612
|
for (const t of tokens) {
|
|
1112
1613
|
const parts = t.path.split(".");
|
|
1113
1614
|
let node = tree;
|
|
1114
|
-
parts.forEach((p,
|
|
1115
|
-
if (
|
|
1615
|
+
parts.forEach((p, i2) => {
|
|
1616
|
+
if (i2 === parts.length - 1) {
|
|
1116
1617
|
node[p] = {
|
|
1117
1618
|
$type: t.type,
|
|
1118
1619
|
$value: t.value,
|
|
@@ -1209,7 +1710,7 @@ function fromFigmaVariables(meta, opts = {}) {
|
|
|
1209
1710
|
if (!c) continue;
|
|
1210
1711
|
let darkValue;
|
|
1211
1712
|
if (c.type === "color") {
|
|
1212
|
-
const hasDarkSomewhere = Object.values(cols).some((
|
|
1713
|
+
const hasDarkSomewhere = Object.values(cols).some((x2) => x2.modes.some((m) => DARK.test(m.name)));
|
|
1213
1714
|
if (hasDarkSomewhere) {
|
|
1214
1715
|
const d = colorValue(resolve4(v, "dark"));
|
|
1215
1716
|
if (d && d !== c.value) darkValue = d;
|
|
@@ -1243,7 +1744,7 @@ function fromFigmaStyles(styles, opts = {}) {
|
|
|
1243
1744
|
}
|
|
1244
1745
|
}
|
|
1245
1746
|
const fams = [...families.entries()].sort((a, b) => b[1] - a[1]);
|
|
1246
|
-
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 }));
|
|
1247
1748
|
const notes = skipped ? [`${skipped} fill style${skipped === 1 ? "" : "s"} (gradients or images) were skipped.`] : [];
|
|
1248
1749
|
return finish("figma-styles", out, notes, opts);
|
|
1249
1750
|
}
|
|
@@ -1261,7 +1762,7 @@ function fromModeObject(obj, opts = {}) {
|
|
|
1261
1762
|
const leafSets = modes.map(([name, v]) => {
|
|
1262
1763
|
const l = [];
|
|
1263
1764
|
plainLeaves(v, [], l);
|
|
1264
|
-
return { name, leaves: l, keys: new Set(l.map((
|
|
1765
|
+
return { name, leaves: l, keys: new Set(l.map((x2) => x2.key.join("/"))) };
|
|
1265
1766
|
});
|
|
1266
1767
|
const groups = [];
|
|
1267
1768
|
for (const m of leafSets) {
|
|
@@ -1310,7 +1811,7 @@ function fromModeObject(obj, opts = {}) {
|
|
|
1310
1811
|
if (!c) continue;
|
|
1311
1812
|
let d;
|
|
1312
1813
|
if (c.type === "color") {
|
|
1313
|
-
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;
|
|
1314
1815
|
const dv = colorValue(deref(darkLeaf ? darkLeaf.value : l.value, "dark"));
|
|
1315
1816
|
if (dv && dv !== c.value) d = dv;
|
|
1316
1817
|
}
|
|
@@ -1320,12 +1821,125 @@ function fromModeObject(obj, opts = {}) {
|
|
|
1320
1821
|
for (const p of pending) out.push({ ...p.t, referenced: referenced.has(p.key) });
|
|
1321
1822
|
return finish("mode-object", out, notes, opts);
|
|
1322
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
|
+
}
|
|
1323
1935
|
function importTokens(input, opts = {}) {
|
|
1324
1936
|
if (!input || typeof input !== "object") throw new Error("Expected a JSON object.");
|
|
1325
1937
|
const o = input;
|
|
1938
|
+
if (Array.isArray(o.$files)) return importTokenFiles(o.$files, opts);
|
|
1326
1939
|
const meta = o.meta ?? o;
|
|
1327
1940
|
if (meta.variables && meta.variableCollections) return fromFigmaVariables(meta, opts);
|
|
1328
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);
|
|
1329
1943
|
if (hasDollarValue(o) || hasStudioValue(o)) {
|
|
1330
1944
|
const studio = !hasDollarValue(o);
|
|
1331
1945
|
const flat = flattenTokens(o);
|
|
@@ -1416,7 +2030,7 @@ function generateInstructions(config, opts = {}) {
|
|
|
1416
2030
|
const radiusVars = named(/^radius\./);
|
|
1417
2031
|
const sizeVars = named(/^font\.size\./);
|
|
1418
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(", ")}.` : ""}`);
|
|
1419
|
-
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(", ")}.` : ""}`);
|
|
1420
2034
|
if (sizeVars.length) lines.push(`- Font sizes come from the type scale: ${sizeVars.join(", ")}. Matching line heights are \`--font-line-height-*\`.`);
|
|
1421
2035
|
if (r.fonts.length) lines.push(`- Fonts: use ${r.fontVars.map((v) => `\`var(${v})\``).join(" or ")}; never set another font-family.`);
|
|
1422
2036
|
lines.push("");
|
|
@@ -1589,9 +2203,9 @@ function collectRenderedNodes(maxNodes = 3e3) {
|
|
|
1589
2203
|
}
|
|
1590
2204
|
var near = (scale, v, tol = 0.5) => scale.some((s) => Math.abs(s - v) <= tol);
|
|
1591
2205
|
var neighbours2 = (scale, v) => {
|
|
1592
|
-
const lo = [...scale].reverse().find((
|
|
1593
|
-
const hi = scale.find((
|
|
1594
|
-
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 ");
|
|
1595
2209
|
};
|
|
1596
2210
|
function closestToken(config, rgb) {
|
|
1597
2211
|
const lab = rgbToLab(rgb);
|
|
@@ -1657,17 +2271,17 @@ function checkRendered(pages, configInput, opts = {}) {
|
|
|
1657
2271
|
const bg = n.effectiveBackground ? parseColor(n.effectiveBackground) : null;
|
|
1658
2272
|
if (fg && bg && fg.a > 0.05) {
|
|
1659
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;
|
|
1660
|
-
const
|
|
2274
|
+
const ratio2 = contrastRatio(blended, bg);
|
|
1661
2275
|
const large = n.fontSize >= 24 || n.fontSize >= 18.66 && n.fontWeight >= 700;
|
|
1662
2276
|
const min = large ? 3 : 4.5;
|
|
1663
|
-
if (
|
|
2277
|
+
if (ratio2 < min) {
|
|
1664
2278
|
const pair = `${colorLabel(fg)} on ${toHex(bg)}`;
|
|
1665
2279
|
add(
|
|
1666
2280
|
`contrast:${pair}:${large}`,
|
|
1667
2281
|
{
|
|
1668
2282
|
ruleId: "text-contrast",
|
|
1669
2283
|
severity: "error",
|
|
1670
|
-
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"})`,
|
|
1671
2285
|
suggestion: "Use the Brand Kit's text colours on its background colours, e.g. fg.default on canvas.default",
|
|
1672
2286
|
sample: n.text
|
|
1673
2287
|
},
|
|
@@ -1847,15 +2461,15 @@ var px2 = (v) => {
|
|
|
1847
2461
|
const m = v.trim().match(/^(-?\d*\.?\d+)(px|rem)?$/);
|
|
1848
2462
|
return m ? Number.parseFloat(m[1]) * (m[2] === "rem" ? 16 : 1) : null;
|
|
1849
2463
|
};
|
|
1850
|
-
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]);
|
|
1851
2465
|
function luminance(hex) {
|
|
1852
2466
|
const n = hex.replace("#", "").slice(0, 6);
|
|
1853
|
-
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);
|
|
1854
2468
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
1855
2469
|
}
|
|
1856
2470
|
var contrast = (a, b) => {
|
|
1857
|
-
const [
|
|
1858
|
-
return (
|
|
2471
|
+
const [x2, y] = [luminance(a), luminance(b)].sort((p, q) => q - p);
|
|
2472
|
+
return (x2 + 0.05) / (y + 0.05);
|
|
1859
2473
|
};
|
|
1860
2474
|
function onColor(bg, candidates) {
|
|
1861
2475
|
return candidates.filter((c) => HEX.test(c)).sort((a, b) => contrast(b, bg) - contrast(a, bg))[0] ?? "#ffffff";
|
|
@@ -1895,7 +2509,7 @@ function scaleVars(s) {
|
|
|
1895
2509
|
function colorVars(colors, dark, font) {
|
|
1896
2510
|
const r = pickColorRoles(colors, dark);
|
|
1897
2511
|
const find = (re) => {
|
|
1898
|
-
const c = colors.find((
|
|
2512
|
+
const c = colors.find((x2) => re.test(x2.path));
|
|
1899
2513
|
return c ? dark ? c.dark ?? c.value : c.value : void 0;
|
|
1900
2514
|
};
|
|
1901
2515
|
const success = find(/success/);
|
|
@@ -1945,8 +2559,126 @@ function kitBridgeCss(tokens, opts = {}) {
|
|
|
1945
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");
|
|
1946
2560
|
}
|
|
1947
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
|
+
|
|
1948
2680
|
// src/api.ts
|
|
1949
|
-
var DEFAULT_API = "https://
|
|
2681
|
+
var DEFAULT_API = "https://www.brandrail.com.au";
|
|
1950
2682
|
function apiBase() {
|
|
1951
2683
|
return (process.env.BRANDRAIL_API_URL ?? DEFAULT_API).replace(/\/$/, "");
|
|
1952
2684
|
}
|
|
@@ -2057,7 +2789,7 @@ function parseChangedLines(diff) {
|
|
|
2057
2789
|
const start = Number(h[1]);
|
|
2058
2790
|
const count = h[2] === void 0 ? 1 : Number(h[2]);
|
|
2059
2791
|
const set = out.get(file);
|
|
2060
|
-
for (let
|
|
2792
|
+
for (let i2 = 0; i2 < count; i2++) set.add(start + i2);
|
|
2061
2793
|
}
|
|
2062
2794
|
}
|
|
2063
2795
|
return out;
|
|
@@ -2070,13 +2802,13 @@ function changedLines(cwd, base) {
|
|
|
2070
2802
|
|
|
2071
2803
|
// src/scan.ts
|
|
2072
2804
|
import { existsSync as existsSync2 } from "node:fs";
|
|
2073
|
-
import { createRequire } from "node:module";
|
|
2805
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
2074
2806
|
import { join as join2, resolve as resolve2 } from "node:path";
|
|
2075
2807
|
function loadPlaywright(cwd) {
|
|
2076
2808
|
for (const base of [join2(cwd, "package.json"), import.meta.url]) {
|
|
2077
2809
|
for (const name of ["playwright", "playwright-core"]) {
|
|
2078
2810
|
try {
|
|
2079
|
-
return
|
|
2811
|
+
return createRequire2(base)(name);
|
|
2080
2812
|
} catch {
|
|
2081
2813
|
}
|
|
2082
2814
|
}
|
|
@@ -2132,7 +2864,7 @@ Usage
|
|
|
2132
2864
|
brandrail check [paths...] Scan files and print violations
|
|
2133
2865
|
brandrail init Create brandrail.config.json and AI rule files
|
|
2134
2866
|
brandrail scan <url> Check a running page (localhost, staging, behind a login) in a real browser
|
|
2135
|
-
brandrail import <
|
|
2867
|
+
brandrail import <files\u2026> Load tokens into the config: DTCG, Tokens Studio, Figma variables JSON, or Figma's Export modes ZIP
|
|
2136
2868
|
brandrail rules List the rules
|
|
2137
2869
|
|
|
2138
2870
|
Options for check
|
|
@@ -2171,16 +2903,16 @@ function parseArgs(argv) {
|
|
|
2171
2903
|
const [cmd = "help", ...rest] = argv;
|
|
2172
2904
|
const positional = [];
|
|
2173
2905
|
const flags = {};
|
|
2174
|
-
for (let
|
|
2175
|
-
const a = rest[
|
|
2906
|
+
for (let i2 = 0; i2 < rest.length; i2++) {
|
|
2907
|
+
const a = rest[i2];
|
|
2176
2908
|
if (a.startsWith("--no-")) flags[a.slice(5)] = false;
|
|
2177
2909
|
else if (a.startsWith("--")) {
|
|
2178
2910
|
const [k, v] = a.slice(2).split("=", 2);
|
|
2179
|
-
const next = rest[
|
|
2911
|
+
const next = rest[i2 + 1];
|
|
2180
2912
|
if (v !== void 0) flags[k] = v;
|
|
2181
2913
|
else if (next !== void 0 && !next.startsWith("-") && ["config", "format", "fail-below", "changed", "pages", "storage-state", "wait", "width", "tokens-css"].includes(k)) {
|
|
2182
2914
|
flags[k] = next;
|
|
2183
|
-
|
|
2915
|
+
i2++;
|
|
2184
2916
|
} else flags[k] = true;
|
|
2185
2917
|
} else positional.push(a);
|
|
2186
2918
|
}
|
|
@@ -2315,7 +3047,7 @@ function writeMcpConfigs(root) {
|
|
|
2315
3047
|
for (const [k, v] of Object.entries(ours)) {
|
|
2316
3048
|
if (Array.isArray(v)) {
|
|
2317
3049
|
const prev = Array.isArray(existing[k]) ? existing[k] : [];
|
|
2318
|
-
merged[k] = [...prev.filter((
|
|
3050
|
+
merged[k] = [...prev.filter((x2) => !v.some((y) => y.id === x2.id)), ...v];
|
|
2319
3051
|
} else merged[k] = { ...existing[k] ?? {}, ...v };
|
|
2320
3052
|
}
|
|
2321
3053
|
} catch {
|
|
@@ -2327,7 +3059,7 @@ function writeMcpConfigs(root) {
|
|
|
2327
3059
|
`);
|
|
2328
3060
|
log(`\u2714 added brandrail to ${c.file} (${c.name})`);
|
|
2329
3061
|
}
|
|
2330
|
-
for (const c of mcpClientConfigs(url).filter((
|
|
3062
|
+
for (const c of mcpClientConfigs(url).filter((x2) => !x2.projectFile)) {
|
|
2331
3063
|
log(`
|
|
2332
3064
|
${c.name}: ${c.command ? `run
|
|
2333
3065
|
${c.command}
|
|
@@ -2374,7 +3106,7 @@ async function cmdScan(cwd, positional, flags) {
|
|
|
2374
3106
|
const url = positional[0];
|
|
2375
3107
|
if (!url) throw new Error("Usage: brandrail scan <url> [--pages 3] [--storage-state auth.json] [--upload]");
|
|
2376
3108
|
const { config, source } = await loadConfig(cwd, flags);
|
|
2377
|
-
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;
|
|
2378
3110
|
log(`Scanning ${url} \u2026`);
|
|
2379
3111
|
const { report, pages } = await scanPages(
|
|
2380
3112
|
cwd,
|
|
@@ -2423,11 +3155,31 @@ Brand score ${report.score} is below ${threshold}.`);
|
|
|
2423
3155
|
}
|
|
2424
3156
|
return 0;
|
|
2425
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
|
+
}
|
|
2426
3177
|
function cmdImport(cwd, positional, flags) {
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
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`;
|
|
2431
3183
|
const path = (typeof flags.config === "string" ? resolve3(cwd, flags.config) : findConfig(cwd)) ?? join3(cwd, CONFIG_FILE);
|
|
2432
3184
|
const config = existsSync3(path) ? readConfig(path) : { ...starterConfig, tokens: {} };
|
|
2433
3185
|
const next = flags.replace ? res.tokens : mergeTokens(config.tokens, res.tokens);
|
|
@@ -2436,6 +3188,7 @@ function cmdImport(cwd, positional, flags) {
|
|
|
2436
3188
|
`);
|
|
2437
3189
|
log(`\u2714 ${res.count} tokens from ${file} (${res.format})${res.primitives ? `, ${res.primitives} palette primitives left out (--primitives to keep)` : ""}`);
|
|
2438
3190
|
for (const n of res.notes) log(` \xB7 ${n}`);
|
|
3191
|
+
for (const i2 of auditBrandKit(next)) log(` ${i2.severity === "error" ? "\u2716" : "!"} ${i2.message}`);
|
|
2439
3192
|
log(`\u2714 ${relative2(cwd, path) || CONFIG_FILE}: ${d.changed.length} changed, ${d.added.length} added, ${d.removed.length} removed`);
|
|
2440
3193
|
log("\nNext: npx brandrail init (to refresh AI rule files), then npx brandrail check");
|
|
2441
3194
|
return 0;
|
|
@@ -2471,8 +3224,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2471
3224
|
}
|
|
2472
3225
|
main().then(
|
|
2473
3226
|
(code) => process.exit(code),
|
|
2474
|
-
(
|
|
2475
|
-
log(`brandrail: ${
|
|
3227
|
+
(err2) => {
|
|
3228
|
+
log(`brandrail: ${err2.message}`);
|
|
2476
3229
|
process.exit(2);
|
|
2477
3230
|
}
|
|
2478
3231
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brandrail",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Keep AI-generated UI on-brand: checks code and live pages against your design system, and wires your Brand Kit into Claude Code, Cursor, Copilot and Codex.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-system",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"tailwind",
|
|
15
15
|
"brand"
|
|
16
16
|
],
|
|
17
|
-
"homepage": "https://
|
|
17
|
+
"homepage": "https://www.brandrail.com.au",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
20
|
"url": "git+https://github.com/ThiBaptistella/brandrail.git",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22",
|
|
40
40
|
"esbuild": "^0.28.2",
|
|
41
|
+
"fflate": "0.8.3",
|
|
41
42
|
"playwright-core": "1.63.0",
|
|
42
43
|
"typescript": "5.9.3",
|
|
43
44
|
"@brandrail/engine": "0.1.0"
|