brandrail 0.2.0 → 0.3.0
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 +182 -13
- package/package.json +8 -9
package/dist/brandrail.mjs
CHANGED
|
@@ -604,7 +604,7 @@ function makeIgnore(globs) {
|
|
|
604
604
|
}
|
|
605
605
|
|
|
606
606
|
// ../engine/src/check.ts
|
|
607
|
-
var ENGINE_VERSION = "0.
|
|
607
|
+
var ENGINE_VERSION = "0.3.0";
|
|
608
608
|
var WEIGHT = { error: 4, warning: 1.5, info: 0.25 };
|
|
609
609
|
function computeScore(counts, lines) {
|
|
610
610
|
const penalty = counts.error * WEIGHT.error + counts.warning * WEIGHT.warning + counts.info * WEIGHT.info;
|
|
@@ -1252,6 +1252,7 @@ function mergeTokens(base, incoming) {
|
|
|
1252
1252
|
}
|
|
1253
1253
|
|
|
1254
1254
|
// ../engine/src/instructions.ts
|
|
1255
|
+
var KIT_PACKAGE = "@brandrail/kit";
|
|
1255
1256
|
var RULES_START = "<!-- brandrail:start -->";
|
|
1256
1257
|
var RULES_END = "<!-- brandrail:end -->";
|
|
1257
1258
|
function generateInstructions(config, opts = {}) {
|
|
@@ -1263,9 +1264,15 @@ function generateInstructions(config, opts = {}) {
|
|
|
1263
1264
|
`All UI in this repo must follow the Brand Kit${r.tokenVersion ? ` (v${r.tokenVersion})` : ""}. These rules are checked on every pull request by Brandrail.`
|
|
1264
1265
|
);
|
|
1265
1266
|
lines.push("");
|
|
1266
|
-
|
|
1267
|
+
const usesKit = r.components.some((c) => c.importPath === KIT_PACKAGE);
|
|
1268
|
+
if (opts.tokensCss || usesKit) {
|
|
1267
1269
|
lines.push("## Setup");
|
|
1268
|
-
|
|
1270
|
+
if (opts.tokensCss) {
|
|
1271
|
+
lines.push(`- \`${opts.tokensCss}\` defines every CSS variable below (light and dark). Import it once at the app entry, before other styles. Don't edit it: it's generated from the Brand Kit.`);
|
|
1272
|
+
}
|
|
1273
|
+
if (usesKit) {
|
|
1274
|
+
lines.push(`- Import \`${KIT_PACKAGE}/styles.css\` once at the app entry. Pass \`theme="brandrail"\` to kit components, and \`colorMode={ColorMode.DARK}\` (from \`${KIT_PACKAGE}\`) for dark mode. The Brand Kit styles them through the CSS variables; don't override their colours, spacing or fonts with your own CSS.`);
|
|
1275
|
+
}
|
|
1269
1276
|
lines.push("");
|
|
1270
1277
|
}
|
|
1271
1278
|
if (r.components.length) {
|
|
@@ -1387,7 +1394,7 @@ var RULE_FILES = [
|
|
|
1387
1394
|
// ../engine/src/rendered.ts
|
|
1388
1395
|
function collectRenderedNodes(maxNodes = 3e3) {
|
|
1389
1396
|
const SKIP = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEMPLATE", "IFRAME", "HEAD", "META", "LINK", "BR", "WBR", "SOURCE", "TRACK"]);
|
|
1390
|
-
const
|
|
1397
|
+
const px3 = (v) => {
|
|
1391
1398
|
const n = parseFloat(v);
|
|
1392
1399
|
return Number.isFinite(n) ? Math.round(n * 100) / 100 : 0;
|
|
1393
1400
|
};
|
|
@@ -1435,8 +1442,8 @@ function collectRenderedNodes(maxNodes = 3e3) {
|
|
|
1435
1442
|
const r = el.getBoundingClientRect();
|
|
1436
1443
|
if (r.width < 1 || r.height < 1) continue;
|
|
1437
1444
|
const text = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent ?? "").join(" ").replace(/\s+/g, " ").trim();
|
|
1438
|
-
const borderW = Math.max(
|
|
1439
|
-
const borderSide =
|
|
1445
|
+
const borderW = Math.max(px3(cs.borderTopWidth), px3(cs.borderRightWidth), px3(cs.borderBottomWidth), px3(cs.borderLeftWidth));
|
|
1446
|
+
const borderSide = px3(cs.borderTopWidth) > 0 ? cs.borderTopColor : px3(cs.borderBottomWidth) > 0 ? cs.borderBottomColor : px3(cs.borderLeftWidth) > 0 ? cs.borderLeftColor : cs.borderRightColor;
|
|
1440
1447
|
nodes.push({
|
|
1441
1448
|
selector: selectorOf(el),
|
|
1442
1449
|
tag: el.tagName.toLowerCase(),
|
|
@@ -1446,12 +1453,12 @@ function collectRenderedNodes(maxNodes = 3e3) {
|
|
|
1446
1453
|
borderColor: borderW > 0 && cs.borderTopStyle !== "none" ? borderSide : void 0,
|
|
1447
1454
|
effectiveBackground: effectiveBg(el),
|
|
1448
1455
|
fontFamily: cs.fontFamily,
|
|
1449
|
-
fontSize:
|
|
1456
|
+
fontSize: px3(cs.fontSize),
|
|
1450
1457
|
fontWeight: parseInt(cs.fontWeight, 10) || 400,
|
|
1451
|
-
radius: Math.max(
|
|
1458
|
+
radius: Math.max(px3(cs.borderTopLeftRadius), px3(cs.borderTopRightRadius), px3(cs.borderBottomLeftRadius), px3(cs.borderBottomRightRadius)),
|
|
1452
1459
|
minSide: Math.round(Math.min(r.width, r.height)),
|
|
1453
|
-
padding: [
|
|
1454
|
-
gap: cs.display.includes("flex") || cs.display.includes("grid") ? [
|
|
1460
|
+
padding: [px3(cs.paddingTop), px3(cs.paddingRight), px3(cs.paddingBottom), px3(cs.paddingLeft)],
|
|
1461
|
+
gap: cs.display.includes("flex") || cs.display.includes("grid") ? [px3(cs.rowGap), px3(cs.columnGap)] : []
|
|
1455
1462
|
});
|
|
1456
1463
|
}
|
|
1457
1464
|
const links = Array.from(document.querySelectorAll("a[href]")).map((a) => a.href).filter((h) => {
|
|
@@ -1667,6 +1674,161 @@ function generateTokensCss(tokens, opts = {}) {
|
|
|
1667
1674
|
`;
|
|
1668
1675
|
}
|
|
1669
1676
|
|
|
1677
|
+
// ../engine/src/kit-bridge.ts
|
|
1678
|
+
var ROLE_PATTERNS = {
|
|
1679
|
+
canvas: [/canvas\.default$/, /^color\.(bg|background)(\.default)?$/, /surface(\.1|\.default)?$/, /canvas$/, /(bg|background)/],
|
|
1680
|
+
subtle: [/canvas\.subtle$/, /(bg|background)\.subtle$/, /surface\.2$/, /fill\.1$/, /subtle$/],
|
|
1681
|
+
fg: [/fg\.default$/, /text\.(default|primary|dark-primary)$/, /^color\.(fg|text|foreground)$/, /fg|text/],
|
|
1682
|
+
muted: [/fg\.muted$/, /text\.(muted|secondary|dark-secondary)$/, /muted|secondary/],
|
|
1683
|
+
border: [/border\.default$/, /^color\.(base\.)?border$/, /border/],
|
|
1684
|
+
primary: [/action\.primary$/, /^color\.(brand\.)?primary$/, /brand(-active)?$/, /primary/, /brand/],
|
|
1685
|
+
accent: [/accent(\.default)?$/, /link/, /info/, /accent/],
|
|
1686
|
+
danger: [/danger(\.default|-active)?$/, /error/, /danger/]
|
|
1687
|
+
};
|
|
1688
|
+
var ROLE_FALLBACK = {
|
|
1689
|
+
canvas: ["#ffffff", "#0d1117"],
|
|
1690
|
+
subtle: ["#f6f8fa", "#151b23"],
|
|
1691
|
+
fg: ["#1f2328", "#f0f6fc"],
|
|
1692
|
+
muted: ["#59636e", "#9198a1"],
|
|
1693
|
+
border: ["#d1d9e0", "#3d444d"],
|
|
1694
|
+
primary: ["#1f883d", "#238636"],
|
|
1695
|
+
accent: ["#0969da", "#4493f8"],
|
|
1696
|
+
danger: ["#cf222e", "#f85149"]
|
|
1697
|
+
};
|
|
1698
|
+
var HEX = /^#(?:[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
1699
|
+
function pickColorRoles(colors, dark = false) {
|
|
1700
|
+
const out = {};
|
|
1701
|
+
for (const role of Object.keys(ROLE_PATTERNS)) {
|
|
1702
|
+
let hit;
|
|
1703
|
+
for (const re of ROLE_PATTERNS[role]) {
|
|
1704
|
+
hit = colors.find((c) => re.test(c.path) && HEX.test(c.value));
|
|
1705
|
+
if (hit) break;
|
|
1706
|
+
}
|
|
1707
|
+
const fb = ROLE_FALLBACK[role][dark ? 1 : 0];
|
|
1708
|
+
if (!hit) out[role] = { value: fb };
|
|
1709
|
+
else if (!dark) out[role] = { value: hit.value, path: hit.path };
|
|
1710
|
+
else if (hit.dark) out[role] = { value: hit.dark, path: hit.path };
|
|
1711
|
+
else out[role] = role === "primary" || role === "accent" || role === "danger" ? { value: hit.value, path: hit.path } : { value: fb, path: hit.path };
|
|
1712
|
+
}
|
|
1713
|
+
return out;
|
|
1714
|
+
}
|
|
1715
|
+
var KIT_SPACING = { 3: 4, 4: 6, 5: 8, 6: 10, 7: 12, 8: 16, 9: 20, 10: 24, 11: 28, 12: 32, 13: 36, 14: 40, 15: 44, 16: 48, 17: 56, 18: 64 };
|
|
1716
|
+
var KIT_RADIUS = { 1: 2, 2: 4, 3: 6, 4: 8, 5: 10, 6: 12, 7: 16, 8: 22, 9: 28 };
|
|
1717
|
+
var KIT_RADIUS_NAME = { 2: "sm", 3: "md", 6: "lg" };
|
|
1718
|
+
var KIT_TYPE = [
|
|
1719
|
+
["Xs", 12, 18],
|
|
1720
|
+
["Sm", 14, 22],
|
|
1721
|
+
["Base", 16, 24],
|
|
1722
|
+
["Md", 16, 24],
|
|
1723
|
+
["Lg", 18, 28],
|
|
1724
|
+
["Xl", 20, 32],
|
|
1725
|
+
["2xl", 24, 38],
|
|
1726
|
+
["3xl", 30, 42],
|
|
1727
|
+
["4xl", 36, 52]
|
|
1728
|
+
];
|
|
1729
|
+
var KIT_THEMES = ["brandrail", "themeA", "themeB"];
|
|
1730
|
+
var px2 = (v) => {
|
|
1731
|
+
const m = v.trim().match(/^(-?\d*\.?\d+)(px|rem)?$/);
|
|
1732
|
+
return m ? Number.parseFloat(m[1]) * (m[2] === "rem" ? 16 : 1) : null;
|
|
1733
|
+
};
|
|
1734
|
+
var nearest2 = (values, v) => values.reduce((best, x) => Math.abs(x - v) < Math.abs(best - v) ? x : best, values[0]);
|
|
1735
|
+
function luminance(hex) {
|
|
1736
|
+
const n = hex.replace("#", "").slice(0, 6);
|
|
1737
|
+
const [r, g, b] = [0, 2, 4].map((i) => parseInt(n.slice(i, i + 2), 16) / 255).map((c) => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
|
|
1738
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
1739
|
+
}
|
|
1740
|
+
var contrast = (a, b) => {
|
|
1741
|
+
const [x, y] = [luminance(a), luminance(b)].sort((p, q) => q - p);
|
|
1742
|
+
return (x + 0.05) / (y + 0.05);
|
|
1743
|
+
};
|
|
1744
|
+
function onColor(bg, candidates) {
|
|
1745
|
+
return candidates.filter((c) => HEX.test(c)).sort((a, b) => contrast(b, bg) - contrast(a, bg))[0] ?? "#ffffff";
|
|
1746
|
+
}
|
|
1747
|
+
function scalesOf(flat) {
|
|
1748
|
+
const leaf = (p) => p.split(".").pop();
|
|
1749
|
+
const spacing = flat.filter((t) => /^(space|spacing)\./.test(t.path) && !/\.unit$/.test(t.path)).map((t) => px2(t.value)).filter((v) => v !== null && v > 0 && v < 999);
|
|
1750
|
+
const unit = flat.find((t) => /^space\.unit$/.test(t.path));
|
|
1751
|
+
if (!spacing.length && unit && px2(unit.value)) for (const m of [0.5, 1, 1.5, 2, 3, 4, 5, 6]) spacing.push(px2(unit.value) * m);
|
|
1752
|
+
const radii = flat.filter((t) => /^radius\./.test(t.path)).map((t) => ({ name: leaf(t.path), value: px2(t.value) ?? -1 })).filter((r) => r.value > 0 && r.value < 999);
|
|
1753
|
+
const lineHeights = new Map(flat.filter((t) => /^font\.line-height\./.test(t.path)).map((t) => [leaf(t.path), px2(t.value) ?? void 0]));
|
|
1754
|
+
const type = flat.filter((t) => /^font\.size\./.test(t.path)).map((t) => ({ name: leaf(t.path), size: px2(t.value) ?? 0, lineHeight: lineHeights.get(leaf(t.path)) })).filter((t) => t.size > 0);
|
|
1755
|
+
const font = flat.find((t) => t.type === "fontFamily" || /^font\.family\./.test(t.path))?.value;
|
|
1756
|
+
return { spacing, radii, type, font };
|
|
1757
|
+
}
|
|
1758
|
+
function scaleVars(s) {
|
|
1759
|
+
const out = [];
|
|
1760
|
+
if (s.spacing.length) for (const [n, v] of Object.entries(KIT_SPACING)) out.push(`--spacing${n}: ${nearest2(s.spacing, v)}px;`);
|
|
1761
|
+
if (s.radii.length) {
|
|
1762
|
+
const values = s.radii.map((r) => r.value);
|
|
1763
|
+
for (const [n, v] of Object.entries(KIT_RADIUS)) {
|
|
1764
|
+
const named = s.radii.find((r) => r.name === KIT_RADIUS_NAME[Number(n)]);
|
|
1765
|
+
out.push(`--defaultRadius-rad${n}: ${named ? named.value : nearest2(values, v)}px;`);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
if (s.type.length) {
|
|
1769
|
+
const sizes = s.type.map((t) => t.size);
|
|
1770
|
+
for (const [name, size, lh] of KIT_TYPE) {
|
|
1771
|
+
const step = s.type.find((t) => t.name.toLowerCase() === name.toLowerCase()) ?? s.type.find((t) => t.size === nearest2(sizes, size));
|
|
1772
|
+
out.push(`--fontA-fontSize${name}: ${step.size}px;`, `--fontA-lineHeight${name}: ${step.lineHeight ?? Math.round(step.size * lh / size)}px;`);
|
|
1773
|
+
}
|
|
1774
|
+
const base = s.type.find((t) => t.name === "base") ?? s.type.find((t) => t.size === nearest2(sizes, 14));
|
|
1775
|
+
out.push(`--fontSizeBase: ${base.size}px;`);
|
|
1776
|
+
}
|
|
1777
|
+
return out;
|
|
1778
|
+
}
|
|
1779
|
+
function colorVars(colors, dark, font) {
|
|
1780
|
+
const r = pickColorRoles(colors, dark);
|
|
1781
|
+
const find = (re) => {
|
|
1782
|
+
const c = colors.find((x) => re.test(x.path));
|
|
1783
|
+
return c ? dark ? c.dark ?? c.value : c.value : void 0;
|
|
1784
|
+
};
|
|
1785
|
+
const success = find(/success/);
|
|
1786
|
+
const warning = find(/warning|attention/);
|
|
1787
|
+
const mix = (a, b, pct) => `color-mix(in srgb, ${a} ${pct}%, ${b})`;
|
|
1788
|
+
const v = {
|
|
1789
|
+
baseSurface1: r.canvas.value,
|
|
1790
|
+
baseSurface2: r.subtle.value,
|
|
1791
|
+
baseSurface3: mix(r.subtle.value, r.border.value, 70),
|
|
1792
|
+
baseFill1: r.subtle.value,
|
|
1793
|
+
baseFill2: mix(r.subtle.value, r.border.value, 50),
|
|
1794
|
+
baseFill3: r.border.value,
|
|
1795
|
+
baseFill4: mix(r.muted.value, r.border.value, 60),
|
|
1796
|
+
baseFill5: r.muted.value,
|
|
1797
|
+
baseBorder: r.border.value,
|
|
1798
|
+
baseDisabled: mix(r.fg.value, "transparent", 28),
|
|
1799
|
+
textDarkPrimary: r.fg.value,
|
|
1800
|
+
textDarkSecondary: r.muted.value,
|
|
1801
|
+
textDarkTertiary: mix(r.muted.value, r.canvas.value, 75),
|
|
1802
|
+
textLightPrimary: onColor(r.primary.value, [pickColorRoles(colors, false).canvas.value, pickColorRoles(colors, false).fg.value]),
|
|
1803
|
+
stateBrandActive: r.primary.value,
|
|
1804
|
+
stateBrandHover: mix(r.primary.value, "black", 88),
|
|
1805
|
+
stateBrandSelected: mix(r.primary.value, "black", 75),
|
|
1806
|
+
stateDangerActive: r.danger.value,
|
|
1807
|
+
stateDangerHover: mix(r.danger.value, "black", 85),
|
|
1808
|
+
stateDangerSelected: mix(r.danger.value, r.canvas.value, 14),
|
|
1809
|
+
stateHighlightActive: r.accent.value,
|
|
1810
|
+
stateHighlightHover: mix(r.accent.value, r.canvas.value, 85),
|
|
1811
|
+
stateHighlightSelected: mix(r.accent.value, r.canvas.value, 14)
|
|
1812
|
+
};
|
|
1813
|
+
if (success) Object.assign(v, { stateSuccessActive: success, stateSuccessHover: mix(success, "black", 88), stateSuccessSelected: mix(success, r.canvas.value, 14) });
|
|
1814
|
+
if (warning) Object.assign(v, { stateWarningActive: warning, stateWarningHover: mix(warning, "black", 88), stateWarningSelected: mix(warning, r.canvas.value, 14) });
|
|
1815
|
+
if (font) Object.assign(v, { "fontA-fontFamily": font, "fontB-fontFamily": font });
|
|
1816
|
+
return Object.entries(v).map(([k, val]) => `--${k}: ${val};`);
|
|
1817
|
+
}
|
|
1818
|
+
function kitBridgeCss(tokens, opts = {}) {
|
|
1819
|
+
const flat = flattenTokens(tokens);
|
|
1820
|
+
const colors = flat.filter((t) => t.type === "color").map((t) => ({ path: t.path, value: t.value, dark: t.dark }));
|
|
1821
|
+
const sc = scalesOf(flat);
|
|
1822
|
+
const scales = scaleVars(sc);
|
|
1823
|
+
const block = (sel2, lines) => lines.length ? `${sel2} {
|
|
1824
|
+
${lines.join("\n ")}
|
|
1825
|
+
}` : "";
|
|
1826
|
+
const themes = `:is(${KIT_THEMES.map((t) => `.${t}`).join(", ")})`;
|
|
1827
|
+
const s = opts.scope;
|
|
1828
|
+
const sel = s ? { scale: s, light: `${s}, ${s} .light`, dark: `${s} .dark, ${s}.dark` } : { scale: "html:root", light: `html:root, html:root ${themes}.light`, dark: `html:root ${themes}.dark` };
|
|
1829
|
+
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
|
+
}
|
|
1831
|
+
|
|
1670
1832
|
// src/api.ts
|
|
1671
1833
|
var DEFAULT_API = "https://web-phi-nine-77.vercel.app";
|
|
1672
1834
|
function apiBase() {
|
|
@@ -1979,7 +2141,14 @@ function tokensCssPath(root, config, flags) {
|
|
|
1979
2141
|
function writeTokensCss(root, rel, config, org) {
|
|
1980
2142
|
const abs = join3(root, rel);
|
|
1981
2143
|
mkdirSync(dirname2(abs), { recursive: true });
|
|
1982
|
-
|
|
2144
|
+
let css = generateTokensCss(config.tokens, { version: config.tokenVersion, org });
|
|
2145
|
+
const usesKit = (config.components ?? []).some((c) => c.importPath === "@brandrail/kit");
|
|
2146
|
+
if (usesKit) css += `
|
|
2147
|
+
/* @brandrail/kit follows the Brand Kit: colours, font, spacing, radius and type scale. */
|
|
2148
|
+
${kitBridgeCss(config.tokens)}
|
|
2149
|
+
`;
|
|
2150
|
+
writeFileSync(abs, css);
|
|
2151
|
+
return usesKit;
|
|
1983
2152
|
}
|
|
1984
2153
|
function writeRuleFiles(root, config, org, tokensCss) {
|
|
1985
2154
|
const md = generateInstructions(config, { org, tokensCss });
|
|
@@ -2077,8 +2246,8 @@ async function cmdInit(cwd, flags) {
|
|
|
2077
2246
|
}
|
|
2078
2247
|
const css = flags["tokens-css"] === false ? void 0 : tokensCssPath(cwd, config, flags);
|
|
2079
2248
|
if (css) {
|
|
2080
|
-
writeTokensCss(cwd, css, config, org);
|
|
2081
|
-
log(`\u2714 wrote ${css} (the Brand Kit as CSS variables \u2014 import it once at your app entry)`);
|
|
2249
|
+
const kit = writeTokensCss(cwd, css, config, org);
|
|
2250
|
+
log(`\u2714 wrote ${css} (the Brand Kit as CSS variables${kit ? ", and @brandrail/kit themed from it" : ""} \u2014 import it once at your app entry)`);
|
|
2082
2251
|
}
|
|
2083
2252
|
if (flags.rules !== false) for (const f of writeRuleFiles(cwd, config, org, css)) log(`\u2714 updated ${f}`);
|
|
2084
2253
|
if (flags.mcp) writeMcpConfigs(cwd);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brandrail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
@@ -35,16 +35,15 @@
|
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/brandrail.mjs --external:playwright --external:playwright-core --banner:js='#!/usr/bin/env node' && chmod +x dist/brandrail.mjs",
|
|
40
|
-
"typecheck": "tsc --noEmit",
|
|
41
|
-
"prepublishOnly": "pnpm run build"
|
|
42
|
-
},
|
|
43
38
|
"devDependencies": {
|
|
44
|
-
"@brandrail/engine": "workspace:*",
|
|
45
39
|
"@types/node": "^22",
|
|
46
40
|
"esbuild": "^0.28.2",
|
|
47
41
|
"playwright-core": "1.63.0",
|
|
48
|
-
"typescript": "5.9.3"
|
|
42
|
+
"typescript": "5.9.3",
|
|
43
|
+
"@brandrail/engine": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/brandrail.mjs --external:playwright --external:playwright-core --banner:js='#!/usr/bin/env node' && chmod +x dist/brandrail.mjs",
|
|
47
|
+
"typecheck": "tsc --noEmit"
|
|
49
48
|
}
|
|
50
|
-
}
|
|
49
|
+
}
|