@koda-sl/baker-cli 0.223.0 → 0.224.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/README.md +6 -3
- package/dist/{chunk-CMPAHYLB.js → chunk-EKLAHWSF.js} +4 -4
- package/dist/chunk-EKLAHWSF.js.map +1 -0
- package/dist/cli.js +147 -52
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-CMPAHYLB.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-EKLAHWSF.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -21272,24 +21272,28 @@ function renderFontFaceCss(faces) {
|
|
|
21272
21272
|
}
|
|
21273
21273
|
var FONT_PROVIDERS = ["google", "fontsource"];
|
|
21274
21274
|
var FONTSOURCE_API = "https://api.fontsource.org/v1";
|
|
21275
|
+
var FONT_STYLES = ["normal", "italic"];
|
|
21276
|
+
var ITAL_AXIS = { normal: 0, italic: 1 };
|
|
21275
21277
|
function fontsourceId(family) {
|
|
21276
21278
|
return family.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
21277
21279
|
}
|
|
21278
|
-
function fontsourceFaces(meta, weights, subsets) {
|
|
21280
|
+
function fontsourceFaces(meta, weights, subsets, styles = ["normal"]) {
|
|
21279
21281
|
const faces = [];
|
|
21280
21282
|
for (const subset of subsets.filter((s) => meta.subsets.includes(s))) {
|
|
21281
21283
|
for (const weight of weights.filter((w) => meta.weights.includes(w))) {
|
|
21282
|
-
const
|
|
21283
|
-
|
|
21284
|
-
|
|
21285
|
-
|
|
21286
|
-
|
|
21287
|
-
|
|
21288
|
-
|
|
21289
|
-
|
|
21290
|
-
|
|
21291
|
-
|
|
21292
|
-
|
|
21284
|
+
for (const style of styles.filter((style2) => meta.styles.includes(style2))) {
|
|
21285
|
+
const url = meta.variants[String(weight)]?.[style]?.[subset]?.url?.woff2;
|
|
21286
|
+
if (!url) continue;
|
|
21287
|
+
faces.push({
|
|
21288
|
+
subset,
|
|
21289
|
+
family: meta.family,
|
|
21290
|
+
style,
|
|
21291
|
+
weight,
|
|
21292
|
+
url,
|
|
21293
|
+
unicodeRange: meta.unicodeRange?.[subset] ?? "",
|
|
21294
|
+
provider: "fontsource"
|
|
21295
|
+
});
|
|
21296
|
+
}
|
|
21293
21297
|
}
|
|
21294
21298
|
}
|
|
21295
21299
|
return faces;
|
|
@@ -21319,29 +21323,30 @@ function asFontsourceMeta(body) {
|
|
|
21319
21323
|
const ok = typeof meta.family === "string" && Array.isArray(meta.weights) && Array.isArray(meta.styles) && Array.isArray(meta.subsets) && typeof meta.variants === "object" && meta.variants !== null;
|
|
21320
21324
|
return ok ? meta : null;
|
|
21321
21325
|
}
|
|
21322
|
-
async function
|
|
21323
|
-
const
|
|
21324
|
-
|
|
21325
|
-
);
|
|
21326
|
-
if (probes.some((probe) => probe.got.status === "unreachable")) return null;
|
|
21327
|
-
return probes.filter((probe) => probe.got.status === "served").map((probe) => probe.weight);
|
|
21326
|
+
async function probeGoogleFaces(family, lookup) {
|
|
21327
|
+
const got = await lookup(googleFontCssUrl(family, STANDARD_WEIGHTS, FONT_STYLES));
|
|
21328
|
+
if (got.status === "unreachable") return null;
|
|
21329
|
+
return got.status === "served" ? parseGoogleFontFaces(got.css) : [];
|
|
21328
21330
|
}
|
|
21329
|
-
async function resolveFromGoogle(family, weights, subsets, deps) {
|
|
21330
|
-
const google = await deps.lookupGoogleCss(googleFontCssUrl(family, weights));
|
|
21331
|
+
async function resolveFromGoogle(family, weights, subsets, styles, deps) {
|
|
21332
|
+
const google = await deps.lookupGoogleCss(googleFontCssUrl(family, weights, FONT_STYLES));
|
|
21331
21333
|
if (google.status === "unreachable") return { ok: false, reason: "unreachable", provider: "google" };
|
|
21332
21334
|
if (google.status === "absent") {
|
|
21333
|
-
const served2 = await
|
|
21335
|
+
const served2 = await probeGoogleFaces(family, deps.lookupGoogleCss);
|
|
21334
21336
|
if (served2 === null) return { ok: false, reason: "unreachable", provider: "google" };
|
|
21335
|
-
|
|
21337
|
+
const available = [...new Set(served2.map((face) => face.weight))].sort((a, b) => a - b);
|
|
21338
|
+
return available.length > 0 ? { ok: false, reason: "weight", provider: "google", available } : null;
|
|
21336
21339
|
}
|
|
21337
21340
|
const served = parseGoogleFontFaces(google.css);
|
|
21338
21341
|
if (served.length === 0) return null;
|
|
21339
|
-
const
|
|
21340
|
-
|
|
21342
|
+
const availableStyles = [...new Set(served.map((face) => face.style))].sort();
|
|
21343
|
+
const inSubset = served.filter((face) => subsets.includes(face.subset));
|
|
21344
|
+
const faces = inSubset.filter((face) => styles.includes(face.style)).map((face) => ({ ...face, provider: "google" }));
|
|
21345
|
+
if (faces.length > 0) return { ok: true, provider: "google", faces, availableStyles };
|
|
21341
21346
|
return { ok: false, reason: "subset", provider: "google", available: [...new Set(served.map((f) => f.subset))] };
|
|
21342
21347
|
}
|
|
21343
|
-
async function resolveBrandFaces(family, weights, subsets, deps) {
|
|
21344
|
-
const google = await resolveFromGoogle(family, weights, subsets, deps);
|
|
21348
|
+
async function resolveBrandFaces(family, weights, subsets, deps, styles = ["normal"]) {
|
|
21349
|
+
const google = await resolveFromGoogle(family, weights, subsets, styles, deps);
|
|
21345
21350
|
if (google) return google;
|
|
21346
21351
|
let body;
|
|
21347
21352
|
try {
|
|
@@ -21351,12 +21356,12 @@ async function resolveBrandFaces(family, weights, subsets, deps) {
|
|
|
21351
21356
|
}
|
|
21352
21357
|
const meta = asFontsourceMeta(body);
|
|
21353
21358
|
if (meta) {
|
|
21354
|
-
const faces = fontsourceFaces(meta, weights, subsets);
|
|
21359
|
+
const faces = fontsourceFaces(meta, weights, subsets, styles);
|
|
21355
21360
|
const ambiguous = undisambiguatedSubsets(faces);
|
|
21356
21361
|
if (ambiguous.length > 0) {
|
|
21357
21362
|
return { ok: false, reason: "ambiguous-subsets", provider: "fontsource", available: meta.subsets };
|
|
21358
21363
|
}
|
|
21359
|
-
if (faces.length > 0) return { ok: true, provider: "fontsource", faces };
|
|
21364
|
+
if (faces.length > 0) return { ok: true, provider: "fontsource", faces, availableStyles: [...meta.styles].sort() };
|
|
21360
21365
|
if (!subsets.some((subset) => meta.subsets.includes(subset))) {
|
|
21361
21366
|
return { ok: false, reason: "subset", provider: "fontsource", available: meta.subsets };
|
|
21362
21367
|
}
|
|
@@ -21453,18 +21458,28 @@ function resolveFetchWeights(flag, declared) {
|
|
|
21453
21458
|
}
|
|
21454
21459
|
return declared?.length ? declared : [400];
|
|
21455
21460
|
}
|
|
21456
|
-
|
|
21461
|
+
var DEFAULT_SUBSETS = ["latin"];
|
|
21462
|
+
function resolveFetchSubsets(flag, selfHosted) {
|
|
21463
|
+
const asked = String(flag ?? "").split(",").map((subset) => subset.trim()).filter(Boolean);
|
|
21464
|
+
const named = flag ? [...new Set(asked)] : selfHosted;
|
|
21465
|
+
return named.length > 0 ? named : [...DEFAULT_SUBSETS];
|
|
21466
|
+
}
|
|
21467
|
+
function resolveFetchStyles(italic, selfHosted) {
|
|
21468
|
+
return italic || selfHosted.includes("italic") ? ["normal", "italic"] : ["normal"];
|
|
21469
|
+
}
|
|
21470
|
+
function googleFontCssUrl(family, weights, styles = ["normal"]) {
|
|
21457
21471
|
const name = family.trim().split(/\s+/).map(encodeURIComponent).join("+");
|
|
21458
|
-
const
|
|
21459
|
-
|
|
21472
|
+
const sorted = [...new Set(weights)].sort((a, b) => a - b);
|
|
21473
|
+
if (sorted.length === 0) return `${GOOGLE_CSS2}?family=${name}&display=swap`;
|
|
21474
|
+
const wanted = FONT_STYLES.filter((style) => styles.includes(style));
|
|
21475
|
+
const spec = wanted.length > 1 ? `${name}:ital,wght@${wanted.flatMap((style) => sorted.map((w) => `${ITAL_AXIS[style]},${w}`)).join(";")}` : `${name}:wght@${sorted.join(";")}`;
|
|
21460
21476
|
return `${GOOGLE_CSS2}?family=${spec}&display=swap`;
|
|
21461
21477
|
}
|
|
21462
21478
|
var STANDARD_WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
|
21463
21479
|
async function probeServedWeights(family, fetchCss) {
|
|
21464
|
-
const
|
|
21465
|
-
|
|
21466
|
-
);
|
|
21467
|
-
return probes.filter((w) => w !== null);
|
|
21480
|
+
const css = await fetchCss(googleFontCssUrl(family, STANDARD_WEIGHTS));
|
|
21481
|
+
if (css === null) return [];
|
|
21482
|
+
return [...new Set(parseGoogleFontFaces(css).map((face) => face.weight))].sort((a, b) => a - b);
|
|
21468
21483
|
}
|
|
21469
21484
|
async function checkFontRequests(requests, fetchCss) {
|
|
21470
21485
|
const results = [];
|
|
@@ -21546,6 +21561,34 @@ function selfHostedWeights(css, family) {
|
|
|
21546
21561
|
}
|
|
21547
21562
|
return [...weights].sort((a, b) => a - b);
|
|
21548
21563
|
}
|
|
21564
|
+
function fontFileSubset(href) {
|
|
21565
|
+
const file = posix.basename((href.split("?")[0] ?? "").split("#")[0] ?? "");
|
|
21566
|
+
return /^[A-Za-z0-9]+-[1-9]00(?:italic)?-([\w-]+)\.woff2$/.exec(file)?.[1] ?? null;
|
|
21567
|
+
}
|
|
21568
|
+
function selfHostedSubsets(css, family) {
|
|
21569
|
+
const target = normalizeFamily(family);
|
|
21570
|
+
const subsets = /* @__PURE__ */ new Set();
|
|
21571
|
+
for (const rule of stripCssComments(css).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
|
|
21572
|
+
const body = rule[1] ?? "";
|
|
21573
|
+
const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
|
|
21574
|
+
if (!declared || normalizeFamily(declared) !== target) continue;
|
|
21575
|
+
for (const url of body.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/gi)) {
|
|
21576
|
+
const subset = fontFileSubset(url[1] ?? "");
|
|
21577
|
+
if (subset) subsets.add(subset);
|
|
21578
|
+
}
|
|
21579
|
+
}
|
|
21580
|
+
return [...subsets].sort();
|
|
21581
|
+
}
|
|
21582
|
+
function selfHostedStyles(css, family) {
|
|
21583
|
+
const target = normalizeFamily(family);
|
|
21584
|
+
const styles = /* @__PURE__ */ new Set();
|
|
21585
|
+
for (const rule of stripCssComments(css).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
|
|
21586
|
+
const body = rule[1] ?? "";
|
|
21587
|
+
const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
|
|
21588
|
+
if (declared && normalizeFamily(declared) === target) styles.add(faceStyle(body));
|
|
21589
|
+
}
|
|
21590
|
+
return [...styles].sort();
|
|
21591
|
+
}
|
|
21549
21592
|
function fontsourceFamilies(css) {
|
|
21550
21593
|
const out = /* @__PURE__ */ new Set();
|
|
21551
21594
|
for (const imported of stripCssComments(css).matchAll(/@fontsource(-variable)?\/([\w-]+)/gi)) {
|
|
@@ -21566,21 +21609,32 @@ function unresolvedBrandFamilies(css) {
|
|
|
21566
21609
|
for (const request of googleFontRequests(stripCssComments(css))) loaded.add(normalizeFamily(request.family));
|
|
21567
21610
|
return declared.filter((family) => !loaded.has(family)).map((family) => asAuthored(css, family));
|
|
21568
21611
|
}
|
|
21569
|
-
function stripFontFaceBlocks(source, family) {
|
|
21612
|
+
function stripFontFaceBlocks(source, family, styles) {
|
|
21570
21613
|
const target = normalizeFamily(family);
|
|
21571
21614
|
const ranges = [];
|
|
21572
|
-
for (const rule of stripCssComments(source).matchAll(/@font-face\s*\{[^}]
|
|
21573
|
-
const
|
|
21574
|
-
|
|
21575
|
-
|
|
21576
|
-
|
|
21615
|
+
for (const rule of stripCssComments(source).matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
|
|
21616
|
+
const body = rule[1] ?? "";
|
|
21617
|
+
const declared = body.match(/font-family\s*:\s*([^;]+)/i)?.[1];
|
|
21618
|
+
if (!declared || normalizeFamily(declared) !== target || rule.index === void 0) continue;
|
|
21619
|
+
if (styles && !styles.has(faceStyle(body))) continue;
|
|
21620
|
+
ranges.push([rule.index, rule.index + rule[0].length]);
|
|
21577
21621
|
}
|
|
21578
21622
|
let out = source;
|
|
21579
21623
|
for (const [start, end] of ranges.reverse()) out = `${out.slice(0, start)}${out.slice(end)}`;
|
|
21580
21624
|
return out.replace(/\n{3,}/g, "\n\n");
|
|
21581
21625
|
}
|
|
21626
|
+
function faceStyle(body) {
|
|
21627
|
+
return body.match(/font-style\s*:\s*([\w-]+)/i)?.[1]?.trim().toLowerCase() ?? "normal";
|
|
21628
|
+
}
|
|
21629
|
+
function declaredStyles(block) {
|
|
21630
|
+
const styles = /* @__PURE__ */ new Set();
|
|
21631
|
+
for (const rule of stripCssComments(block).matchAll(/@font-face\s*\{([^}]*)\}/gi))
|
|
21632
|
+
styles.add(faceStyle(rule[1] ?? ""));
|
|
21633
|
+
return styles;
|
|
21634
|
+
}
|
|
21582
21635
|
function upsertFontFaceCss(css, family, block) {
|
|
21583
|
-
const
|
|
21636
|
+
const styles = declaredStyles(block);
|
|
21637
|
+
const out = stripFontFaceBlocks(css, family, styles.size > 0 ? styles : void 0);
|
|
21584
21638
|
const themeAt = stripCssComments(out).search(/@theme\b/);
|
|
21585
21639
|
if (themeAt < 0) return `${out.trimEnd()}
|
|
21586
21640
|
|
|
@@ -21600,7 +21654,7 @@ ${tail}`;
|
|
|
21600
21654
|
// src/commands/brand/fonts.ts
|
|
21601
21655
|
var GLOBAL_CSS = path2.join("src", "styles", "global.css");
|
|
21602
21656
|
var FONTS_DIR = path2.join("src", "brand", "fonts");
|
|
21603
|
-
var
|
|
21657
|
+
var DEFAULT_SUBSETS_LABEL = DEFAULT_SUBSETS.join(",");
|
|
21604
21658
|
var BROWSER_UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36";
|
|
21605
21659
|
registerSchema({
|
|
21606
21660
|
command: "brand.fonts.check",
|
|
@@ -21624,7 +21678,12 @@ registerSchema({
|
|
|
21624
21678
|
},
|
|
21625
21679
|
subsets: {
|
|
21626
21680
|
type: "string",
|
|
21627
|
-
description: `Comma-separated unicode subsets (default: ${
|
|
21681
|
+
description: `Comma-separated unicode subsets (default: the subsets global.css already self-hosts, else ${DEFAULT_SUBSETS_LABEL})`,
|
|
21682
|
+
required: false
|
|
21683
|
+
},
|
|
21684
|
+
italic: {
|
|
21685
|
+
type: "boolean",
|
|
21686
|
+
description: "Also self-host the family's italic faces. On automatically when global.css already loads one, so a re-fetch never strands it.",
|
|
21628
21687
|
required: false
|
|
21629
21688
|
}
|
|
21630
21689
|
}
|
|
@@ -21669,15 +21728,34 @@ var WIRING_HINT = {
|
|
|
21669
21728
|
};
|
|
21670
21729
|
var PROVIDER_DEPS = { lookupGoogleCss, fetchJson };
|
|
21671
21730
|
var PROVIDER_LABEL = { google: "Google Fonts", fontsource: "Fontsource" };
|
|
21731
|
+
function italicHints(family, wantedStyles, availableStyles, downloadedStyles, provider) {
|
|
21732
|
+
const wanted = wantedStyles.includes("italic");
|
|
21733
|
+
const available = availableStyles.includes("italic");
|
|
21734
|
+
if (wanted && !downloadedStyles.includes("italic")) {
|
|
21735
|
+
return [
|
|
21736
|
+
`NO ITALIC: ${PROVIDER_LABEL[provider]} does not ship an italic for "${family}", so none was written. Drop italic from src/brand/BRAND.md, or the browser will slant the upright face instead.`
|
|
21737
|
+
];
|
|
21738
|
+
}
|
|
21739
|
+
if (!wanted && available) {
|
|
21740
|
+
return [
|
|
21741
|
+
`${PROVIDER_LABEL[provider]} also ships an italic for "${family}", which was NOT downloaded. If the brand uses italic anywhere, re-run with \`--italic\` \u2014 otherwise the browser slants the upright face, which is not the same shapes.`
|
|
21742
|
+
];
|
|
21743
|
+
}
|
|
21744
|
+
return [];
|
|
21745
|
+
}
|
|
21672
21746
|
function fetchHints({
|
|
21673
21747
|
family,
|
|
21674
21748
|
downloadedWeights,
|
|
21749
|
+
downloadedStyles,
|
|
21675
21750
|
incomplete,
|
|
21676
21751
|
wiring,
|
|
21677
|
-
provider
|
|
21752
|
+
provider,
|
|
21753
|
+
wantedStyles,
|
|
21754
|
+
availableStyles
|
|
21678
21755
|
}) {
|
|
21679
21756
|
return [
|
|
21680
21757
|
WIRING_HINT[wiring](family),
|
|
21758
|
+
...italicHints(family, wantedStyles, availableStyles, downloadedStyles, provider),
|
|
21681
21759
|
`Record the exact weights (${downloadedWeights.join(", ")}) in src/brand/BRAND.md \u2014 a weight listed there but not downloaded renders as a synthesized fallback.`,
|
|
21682
21760
|
...incomplete.length > 0 ? [
|
|
21683
21761
|
// The remedy is provider-specific: only a Google family has an @import
|
|
@@ -21838,7 +21916,14 @@ var fontsFetchCommand = defineCommand93({
|
|
|
21838
21916
|
type: "string",
|
|
21839
21917
|
description: "Comma-separated weights (default: the weights global.css already requests or self-hosts, else 400)"
|
|
21840
21918
|
},
|
|
21841
|
-
subsets: {
|
|
21919
|
+
subsets: {
|
|
21920
|
+
type: "string",
|
|
21921
|
+
description: `Comma-separated unicode subsets (default: the subsets global.css already self-hosts, else ${DEFAULT_SUBSETS_LABEL})`
|
|
21922
|
+
},
|
|
21923
|
+
italic: {
|
|
21924
|
+
type: "boolean",
|
|
21925
|
+
description: "Also self-host the family's italic faces (default: on when global.css already loads one, off otherwise)"
|
|
21926
|
+
}
|
|
21842
21927
|
},
|
|
21843
21928
|
async run({ args }) {
|
|
21844
21929
|
const family = String(args.family).trim();
|
|
@@ -21850,10 +21935,9 @@ var fontsFetchCommand = defineCommand93({
|
|
|
21850
21935
|
if (weights.length === 0) {
|
|
21851
21936
|
fail("INVALID_WEIGHTS", `"${args.weights}" has no usable weight \u2014 pass whole hundreds, e.g. --weights 400,700.`);
|
|
21852
21937
|
}
|
|
21853
|
-
const wanted = new Set(
|
|
21854
|
-
|
|
21855
|
-
);
|
|
21856
|
-
const resolved = await resolveBrandFaces(family, weights, [...wanted], PROVIDER_DEPS);
|
|
21938
|
+
const wanted = new Set(resolveFetchSubsets(args.subsets, selfHostedSubsets(css, family)));
|
|
21939
|
+
const styles = resolveFetchStyles(args.italic, selfHostedStyles(css, family));
|
|
21940
|
+
const resolved = await resolveBrandFaces(family, weights, [...wanted], PROVIDER_DEPS, styles);
|
|
21857
21941
|
if (!resolved.ok) return failResolution(family, weights, resolved);
|
|
21858
21942
|
const faces = resolved.faces;
|
|
21859
21943
|
const fontsDir = path2.resolve(process.cwd(), FONTS_DIR);
|
|
@@ -21877,6 +21961,7 @@ var fontsFetchCommand = defineCommand93({
|
|
|
21877
21961
|
}
|
|
21878
21962
|
const fontFace = renderFontFaceCss(downloaded);
|
|
21879
21963
|
const downloadedWeights = [...new Set(downloaded.map((f) => f.weight))].sort((a, b) => a - b);
|
|
21964
|
+
const downloadedStyles = [...new Set(downloaded.map((f) => f.style))].sort();
|
|
21880
21965
|
const incomplete = weights.filter((w) => !downloadedWeights.includes(w));
|
|
21881
21966
|
const wiring = await wireFontFaceIntoStylesheet(css, family, fontFace);
|
|
21882
21967
|
writeJson({
|
|
@@ -21885,11 +21970,21 @@ var fontsFetchCommand = defineCommand93({
|
|
|
21885
21970
|
family,
|
|
21886
21971
|
provider: resolved.provider,
|
|
21887
21972
|
weights: downloadedWeights,
|
|
21973
|
+
styles: downloadedStyles,
|
|
21888
21974
|
files: written,
|
|
21889
21975
|
fontFace,
|
|
21890
21976
|
stylesheet: wiring
|
|
21891
21977
|
},
|
|
21892
|
-
hints: fetchHints({
|
|
21978
|
+
hints: fetchHints({
|
|
21979
|
+
family,
|
|
21980
|
+
downloadedWeights,
|
|
21981
|
+
downloadedStyles,
|
|
21982
|
+
incomplete,
|
|
21983
|
+
wiring,
|
|
21984
|
+
provider: resolved.provider,
|
|
21985
|
+
wantedStyles: styles,
|
|
21986
|
+
availableStyles: resolved.availableStyles
|
|
21987
|
+
})
|
|
21893
21988
|
});
|
|
21894
21989
|
}
|
|
21895
21990
|
});
|