@tendrilapp/cli 0.1.40 → 0.1.41
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/SKILL.md +20 -4
- package/dist/tendril-mcp.js +15 -1
- package/dist/tendril.js +1863 -533
- package/package.json +1 -1
package/dist/tendril.js
CHANGED
|
@@ -1980,8 +1980,8 @@ var init_src = __esm({
|
|
|
1980
1980
|
function variableNameToPath(name) {
|
|
1981
1981
|
return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
|
|
1982
1982
|
}
|
|
1983
|
-
function tokenPathToCssVar(
|
|
1984
|
-
return `--${
|
|
1983
|
+
function tokenPathToCssVar(path54) {
|
|
1984
|
+
return `--${path54.join("-")}`;
|
|
1985
1985
|
}
|
|
1986
1986
|
function toDtcgToken(variable, defaultMode) {
|
|
1987
1987
|
const modes = Object.keys(variable.valuesByMode);
|
|
@@ -2025,11 +2025,11 @@ function toDtcgToken(variable, defaultMode) {
|
|
|
2025
2025
|
}
|
|
2026
2026
|
function mapVariablesToDtcg(variables, defaultMode = "light") {
|
|
2027
2027
|
const entries = variables.map((variable) => {
|
|
2028
|
-
const
|
|
2029
|
-
if (
|
|
2028
|
+
const path54 = variableNameToPath(variable.name);
|
|
2029
|
+
if (path54.length === 0) {
|
|
2030
2030
|
throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
|
|
2031
2031
|
}
|
|
2032
|
-
return { variable, path:
|
|
2032
|
+
return { variable, path: path54 };
|
|
2033
2033
|
});
|
|
2034
2034
|
const groupPrefixes = /* @__PURE__ */ new Set();
|
|
2035
2035
|
for (const e of entries) {
|
|
@@ -2050,21 +2050,21 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
|
|
|
2050
2050
|
}
|
|
2051
2051
|
const tokens = {};
|
|
2052
2052
|
const flat = [];
|
|
2053
|
-
for (const { variable, path:
|
|
2053
|
+
for (const { variable, path: path54 } of entries) {
|
|
2054
2054
|
const token = toDtcgToken(variable, defaultMode);
|
|
2055
2055
|
let group = tokens;
|
|
2056
|
-
for (const segment of
|
|
2056
|
+
for (const segment of path54.slice(0, -1)) {
|
|
2057
2057
|
const existing = group[segment];
|
|
2058
2058
|
group = existing ?? (group[segment] = {});
|
|
2059
2059
|
}
|
|
2060
|
-
const leaf =
|
|
2060
|
+
const leaf = path54[path54.length - 1];
|
|
2061
2061
|
if (group[leaf] !== void 0) {
|
|
2062
|
-
throw new DtcgMappingError("duplicate-path", `Duplicate token path "${
|
|
2062
|
+
throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path54.join(".")}" (variable ${variable.id})`);
|
|
2063
2063
|
}
|
|
2064
2064
|
group[leaf] = token;
|
|
2065
2065
|
flat.push({
|
|
2066
|
-
path:
|
|
2067
|
-
cssVar: tokenPathToCssVar(
|
|
2066
|
+
path: path54.join("."),
|
|
2067
|
+
cssVar: tokenPathToCssVar(path54),
|
|
2068
2068
|
type: token.$type,
|
|
2069
2069
|
value: token.$value
|
|
2070
2070
|
});
|
|
@@ -2253,9 +2253,9 @@ function boundId(value) {
|
|
|
2253
2253
|
return isObject(value) && typeof value["boundVariableId"] === "string" ? value["boundVariableId"] : void 0;
|
|
2254
2254
|
}
|
|
2255
2255
|
function resolveBinding(ctx, id) {
|
|
2256
|
-
const
|
|
2257
|
-
if (
|
|
2258
|
-
return
|
|
2256
|
+
const path54 = ctx.pathById.get(id);
|
|
2257
|
+
if (path54 === void 0) ctx.unresolved.add(id);
|
|
2258
|
+
return path54;
|
|
2259
2259
|
}
|
|
2260
2260
|
function parseVariantProps(name) {
|
|
2261
2261
|
if (!name.includes("=")) return void 0;
|
|
@@ -2290,8 +2290,8 @@ function walk(ctx, raw) {
|
|
|
2290
2290
|
if (!isObject(paint) || paint["visible"] === false) continue;
|
|
2291
2291
|
const id = boundId(paint);
|
|
2292
2292
|
if (id !== void 0) {
|
|
2293
|
-
const
|
|
2294
|
-
if (
|
|
2293
|
+
const path54 = resolveBinding(ctx, id);
|
|
2294
|
+
if (path54 !== void 0) tokens.add(path54);
|
|
2295
2295
|
} else if (typeof paint["color"] === "string") {
|
|
2296
2296
|
ctx.hardcoded.push({ node: name, property, value: paint["color"] });
|
|
2297
2297
|
}
|
|
@@ -2299,8 +2299,8 @@ function walk(ctx, raw) {
|
|
|
2299
2299
|
}
|
|
2300
2300
|
const radiusId = boundId(raw["cornerRadius"]);
|
|
2301
2301
|
if (radiusId !== void 0) {
|
|
2302
|
-
const
|
|
2303
|
-
if (
|
|
2302
|
+
const path54 = resolveBinding(ctx, radiusId);
|
|
2303
|
+
if (path54 !== void 0) tokens.add(path54);
|
|
2304
2304
|
} else if (typeof raw["cornerRadius"] === "number" && raw["cornerRadius"] !== 0) {
|
|
2305
2305
|
ctx.hardcoded.push({ node: name, property: "border-radius", value: `${raw["cornerRadius"]}px` });
|
|
2306
2306
|
}
|
|
@@ -2310,10 +2310,10 @@ function walk(ctx, raw) {
|
|
|
2310
2310
|
layout = { mode: layoutMode === "HORIZONTAL" ? "flex-row" : "flex-column" };
|
|
2311
2311
|
const gapId = boundId(raw["itemSpacing"]);
|
|
2312
2312
|
if (gapId !== void 0) {
|
|
2313
|
-
const
|
|
2314
|
-
if (
|
|
2315
|
-
layout.gap =
|
|
2316
|
-
tokens.add(
|
|
2313
|
+
const path54 = resolveBinding(ctx, gapId);
|
|
2314
|
+
if (path54 !== void 0) {
|
|
2315
|
+
layout.gap = path54;
|
|
2316
|
+
tokens.add(path54);
|
|
2317
2317
|
}
|
|
2318
2318
|
} else if (typeof raw["itemSpacing"] === "number" && raw["itemSpacing"] !== 0) {
|
|
2319
2319
|
ctx.hardcoded.push({ node: name, property: "gap", value: `${raw["itemSpacing"]}px` });
|
|
@@ -2322,10 +2322,10 @@ function walk(ctx, raw) {
|
|
|
2322
2322
|
for (const field of PADDING_FIELDS) {
|
|
2323
2323
|
const id = boundId(raw[field]);
|
|
2324
2324
|
if (id !== void 0) {
|
|
2325
|
-
const
|
|
2326
|
-
if (
|
|
2327
|
-
paddingPaths.push(
|
|
2328
|
-
tokens.add(
|
|
2325
|
+
const path54 = resolveBinding(ctx, id);
|
|
2326
|
+
if (path54 !== void 0) {
|
|
2327
|
+
paddingPaths.push(path54);
|
|
2328
|
+
tokens.add(path54);
|
|
2329
2329
|
}
|
|
2330
2330
|
} else if (typeof raw[field] === "number" && raw[field] !== 0) {
|
|
2331
2331
|
ctx.hardcoded.push({ node: name, property: field, value: `${raw[field]}px` });
|
|
@@ -2533,9 +2533,17 @@ var init_src3 = __esm({
|
|
|
2533
2533
|
});
|
|
2534
2534
|
|
|
2535
2535
|
// packages/cli/src/invocation.ts
|
|
2536
|
-
import { existsSync as existsSync5, realpathSync } from "node:fs";
|
|
2536
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "node:fs";
|
|
2537
2537
|
import path5 from "node:path";
|
|
2538
2538
|
import { fileURLToPath } from "node:url";
|
|
2539
|
+
function isDevTree(root) {
|
|
2540
|
+
try {
|
|
2541
|
+
const pkg = JSON.parse(readFileSync5(path5.join(root, "package.json"), "utf8"));
|
|
2542
|
+
return pkg.private === true && pkg.name === "@tendrilapp/cli";
|
|
2543
|
+
} catch {
|
|
2544
|
+
return false;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2539
2547
|
function findPathTendril(pathEnv, platform) {
|
|
2540
2548
|
const dirs = pathEnv.split(path5.delimiter).filter((d) => d !== "" && !/node_modules[\\/]\.bin/.test(d) && !/[\\/]_npx[\\/]/.test(d));
|
|
2541
2549
|
const names = platform === "win32" ? ["tendril.cmd", "tendril.bat"] : ["tendril"];
|
|
@@ -2557,11 +2565,17 @@ function packageRootOf(file) {
|
|
|
2557
2565
|
}
|
|
2558
2566
|
}
|
|
2559
2567
|
function resolveInvocation(pathEnv, platform, selfFile = fileURLToPath(import.meta.url)) {
|
|
2568
|
+
let self = null;
|
|
2569
|
+
try {
|
|
2570
|
+
self = packageRootOf(realpathSync(selfFile));
|
|
2571
|
+
} catch {
|
|
2572
|
+
self = null;
|
|
2573
|
+
}
|
|
2574
|
+
if (self !== null && isDevTree(self)) return DEV_INVOCATION;
|
|
2560
2575
|
const binary = findPathTendril(pathEnv, platform);
|
|
2561
2576
|
if (binary === null) return NPX_INVOCATION;
|
|
2562
2577
|
try {
|
|
2563
2578
|
const onPath = packageRootOf(realpathSync(binary));
|
|
2564
|
-
const self = packageRootOf(realpathSync(selfFile));
|
|
2565
2579
|
if (onPath === null || self === null) return NPX_INVOCATION;
|
|
2566
2580
|
return realpathSync(onPath) === realpathSync(self) ? "tendril" : NPX_INVOCATION;
|
|
2567
2581
|
} catch {
|
|
@@ -2579,11 +2593,12 @@ function quoteArg(value) {
|
|
|
2579
2593
|
if (/["`$]/.test(value)) throw new Error(`refusing to emit a shell argument containing a double quote, backtick or $: ${value}`);
|
|
2580
2594
|
return /[\s'*?[\]()&;|<>#~]/.test(value) ? `"${value}"` : value;
|
|
2581
2595
|
}
|
|
2582
|
-
var NPX_INVOCATION, cached;
|
|
2596
|
+
var NPX_INVOCATION, DEV_INVOCATION, cached;
|
|
2583
2597
|
var init_invocation = __esm({
|
|
2584
2598
|
"packages/cli/src/invocation.ts"() {
|
|
2585
2599
|
"use strict";
|
|
2586
2600
|
NPX_INVOCATION = "npx -y -p @tendrilapp/cli@latest tendril";
|
|
2601
|
+
DEV_INVOCATION = "pnpm --filter @tendrilapp/cli dev";
|
|
2587
2602
|
cached = null;
|
|
2588
2603
|
}
|
|
2589
2604
|
});
|
|
@@ -4350,7 +4365,7 @@ var init_font_collection = __esm({
|
|
|
4350
4365
|
});
|
|
4351
4366
|
|
|
4352
4367
|
// packages/verify/src/font-discovery.ts
|
|
4353
|
-
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as
|
|
4368
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
4354
4369
|
import os2 from "node:os";
|
|
4355
4370
|
import path12 from "node:path";
|
|
4356
4371
|
function weightFromSubfamily(subfamily) {
|
|
@@ -4398,7 +4413,7 @@ function faceAt(bytes, view, dirOffset, file, faceIndex) {
|
|
|
4398
4413
|
}
|
|
4399
4414
|
function facesInFile(file) {
|
|
4400
4415
|
try {
|
|
4401
|
-
const bytes = new Uint8Array(
|
|
4416
|
+
const bytes = new Uint8Array(readFileSync6(file));
|
|
4402
4417
|
if (bytes.length < 12) return [];
|
|
4403
4418
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
4404
4419
|
if (isCollection(bytes)) {
|
|
@@ -4490,7 +4505,7 @@ var init_font_discovery = __esm({
|
|
|
4490
4505
|
|
|
4491
4506
|
// packages/verify/src/font-resolve.ts
|
|
4492
4507
|
import { createHash as createHash2 } from "node:crypto";
|
|
4493
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as
|
|
4508
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4494
4509
|
import os3 from "node:os";
|
|
4495
4510
|
import path13 from "node:path";
|
|
4496
4511
|
function fontCacheDir() {
|
|
@@ -4569,7 +4584,7 @@ async function resolveFonts(family, weights, cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
4569
4584
|
}
|
|
4570
4585
|
}
|
|
4571
4586
|
const mPath = path13.join(cacheDir, "manifest.json");
|
|
4572
|
-
const prior = existsSync9(mPath) ? JSON.parse(
|
|
4587
|
+
const prior = existsSync9(mPath) ? JSON.parse(readFileSync7(mPath, "utf8")) : [];
|
|
4573
4588
|
const portable2 = resolved.map((m) => ({ ...m, file: path13.basename(m.file) }));
|
|
4574
4589
|
const merged = [...prior.filter((p) => !resolved.some((m) => m.family === p.family && m.weight === p.weight)), ...portable2];
|
|
4575
4590
|
if (resolved.length > 0) writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
|
|
@@ -4583,7 +4598,7 @@ function addLocalFont(family, weight, filePath, cacheDir = DEFAULT_FONT_CACHE, f
|
|
|
4583
4598
|
if (![".woff2", ".woff", ".ttf", ".otf", ".ttc"].includes(ext)) {
|
|
4584
4599
|
throw new Error(`unsupported font file "${ext}" \u2014 use .woff2 (preferred), .woff, .ttf, .otf or .ttc`);
|
|
4585
4600
|
}
|
|
4586
|
-
let bytes = new Uint8Array(
|
|
4601
|
+
let bytes = new Uint8Array(readFileSync7(src));
|
|
4587
4602
|
if (bytes.length === 0) throw new Error(`font file is empty: ${src}`);
|
|
4588
4603
|
let storedExt = ext;
|
|
4589
4604
|
if (isCollection(bytes)) {
|
|
@@ -4606,16 +4621,16 @@ ${shown}`
|
|
|
4606
4621
|
writeFileSync3(file, bytes);
|
|
4607
4622
|
const face = { family, weight, source: `${provenance}:${path13.basename(src)}`, sha256, file, license: "unknown" };
|
|
4608
4623
|
const mPath = path13.join(cacheDir, "manifest.json");
|
|
4609
|
-
const prior = existsSync9(mPath) ? JSON.parse(
|
|
4624
|
+
const prior = existsSync9(mPath) ? JSON.parse(readFileSync7(mPath, "utf8")) : [];
|
|
4610
4625
|
const merged = [...prior.filter((p) => !(p.family === family && p.weight === weight)), { ...face, file: path13.basename(file) }];
|
|
4611
4626
|
writeFileSync3(mPath, `${JSON.stringify(merged, null, 2)}
|
|
4612
4627
|
`);
|
|
4613
4628
|
return face;
|
|
4614
4629
|
}
|
|
4615
4630
|
function checkFontLock(lockPath, cacheDir = DEFAULT_FONT_CACHE) {
|
|
4616
|
-
const lock = JSON.parse(
|
|
4631
|
+
const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
|
|
4617
4632
|
const mPath = path13.join(cacheDir, "manifest.json");
|
|
4618
|
-
const manifest = existsSync9(mPath) ? JSON.parse(
|
|
4633
|
+
const manifest = existsSync9(mPath) ? JSON.parse(readFileSync7(mPath, "utf8")) : [];
|
|
4619
4634
|
return lock.map((l) => {
|
|
4620
4635
|
const m = manifest.find((x) => x.family === l.family && x.weight === l.weight);
|
|
4621
4636
|
if (m === void 0) return { family: l.family, weight: l.weight, status: "missing" };
|
|
@@ -4627,7 +4642,7 @@ function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
4627
4642
|
if (!existsSync9(mPath)) return [];
|
|
4628
4643
|
let entries;
|
|
4629
4644
|
try {
|
|
4630
|
-
entries = JSON.parse(
|
|
4645
|
+
entries = JSON.parse(readFileSync7(mPath, "utf8"));
|
|
4631
4646
|
} catch {
|
|
4632
4647
|
return [];
|
|
4633
4648
|
}
|
|
@@ -4642,7 +4657,7 @@ function resolvedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
4642
4657
|
}
|
|
4643
4658
|
function requiredFontsManifest(families, cacheDir = DEFAULT_FONT_CACHE) {
|
|
4644
4659
|
const mPath = path13.join(cacheDir, "manifest.json");
|
|
4645
|
-
const manifest = existsSync9(mPath) ? JSON.parse(
|
|
4660
|
+
const manifest = existsSync9(mPath) ? JSON.parse(readFileSync7(mPath, "utf8")) : [];
|
|
4646
4661
|
const wanted = new Set(families.map((f) => f.toLowerCase()));
|
|
4647
4662
|
return manifest.filter((f) => wanted.has(f.family.toLowerCase())).map((f) => ({
|
|
4648
4663
|
family: f.family,
|
|
@@ -4659,7 +4674,7 @@ function facesMissingLicense(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
4659
4674
|
if (!existsSync9(mPath)) return [];
|
|
4660
4675
|
let entries;
|
|
4661
4676
|
try {
|
|
4662
|
-
entries = JSON.parse(
|
|
4677
|
+
entries = JSON.parse(readFileSync7(mPath, "utf8"));
|
|
4663
4678
|
} catch {
|
|
4664
4679
|
return [];
|
|
4665
4680
|
}
|
|
@@ -4672,7 +4687,7 @@ function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
4672
4687
|
if (!existsSync9(mPath)) return [];
|
|
4673
4688
|
let entries;
|
|
4674
4689
|
try {
|
|
4675
|
-
entries = JSON.parse(
|
|
4690
|
+
entries = JSON.parse(readFileSync7(mPath, "utf8"));
|
|
4676
4691
|
} catch {
|
|
4677
4692
|
return [];
|
|
4678
4693
|
}
|
|
@@ -4681,7 +4696,7 @@ function verifiedFontFamilies(cacheDir = DEFAULT_FONT_CACHE) {
|
|
|
4681
4696
|
if (typeof e.family !== "string" || typeof e.weight !== "number" || typeof e.file !== "string") continue;
|
|
4682
4697
|
const file = path13.isAbsolute(e.file) && existsSync9(e.file) ? e.file : path13.resolve(cacheDir, path13.basename(e.file));
|
|
4683
4698
|
if (!existsSync9(file)) continue;
|
|
4684
|
-
if (createHash2("sha256").update(
|
|
4699
|
+
if (createHash2("sha256").update(readFileSync7(file)).digest("hex") !== e.sha256) continue;
|
|
4685
4700
|
const set = byFamily.get(e.family) ?? /* @__PURE__ */ new Set();
|
|
4686
4701
|
set.add(e.weight);
|
|
4687
4702
|
byFamily.set(e.family, set);
|
|
@@ -4703,7 +4718,7 @@ function addSystemFamily(family, opts = {}) {
|
|
|
4703
4718
|
const overwrote = [];
|
|
4704
4719
|
const cacheDir = opts.cacheDir ?? DEFAULT_FONT_CACHE;
|
|
4705
4720
|
const manifestFile = path13.join(cacheDir, "manifest.json");
|
|
4706
|
-
const prior = existsSync9(manifestFile) ? JSON.parse(
|
|
4721
|
+
const prior = existsSync9(manifestFile) ? JSON.parse(readFileSync7(manifestFile, "utf8")) : [];
|
|
4707
4722
|
const taken = /* @__PURE__ */ new Set();
|
|
4708
4723
|
for (const face of faces) {
|
|
4709
4724
|
const skip = (reason) => skipped.push({ subfamily: face.subfamily, weight: face.weight, reason });
|
|
@@ -4765,17 +4780,17 @@ var init_font_resolve = __esm({
|
|
|
4765
4780
|
|
|
4766
4781
|
// packages/verify/src/font-faces.ts
|
|
4767
4782
|
import { createHash as createHash3 } from "node:crypto";
|
|
4768
|
-
import { existsSync as existsSync10, readFileSync as
|
|
4783
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
|
|
4769
4784
|
import path14 from "node:path";
|
|
4770
4785
|
function injectedGroups(manifestPath2) {
|
|
4771
4786
|
if (!existsSync10(manifestPath2)) return { groups: [], shared: false };
|
|
4772
|
-
const claimed = JSON.parse(
|
|
4787
|
+
const claimed = JSON.parse(readFileSync8(manifestPath2, "utf8"));
|
|
4773
4788
|
const resolveFile = (f) => path14.isAbsolute(f) && existsSync10(f) ? f : path14.resolve(path14.dirname(manifestPath2), path14.basename(f));
|
|
4774
4789
|
const byFile = /* @__PURE__ */ new Map();
|
|
4775
4790
|
for (const f of claimed) {
|
|
4776
4791
|
const file = resolveFile(f.file);
|
|
4777
4792
|
if (!existsSync10(file)) continue;
|
|
4778
|
-
if (createHash3("sha256").update(
|
|
4793
|
+
if (createHash3("sha256").update(readFileSync8(file)).digest("hex") !== f.sha256) continue;
|
|
4779
4794
|
const k = `${f.family}:${f.file}`;
|
|
4780
4795
|
const e = byFile.get(k) ?? { family: f.family, weights: [], file };
|
|
4781
4796
|
e.weights.push(f.weight);
|
|
@@ -4788,7 +4803,7 @@ function fontFaceCss(manifestPath2 = path14.join(fontCacheDir(), "manifest.json"
|
|
|
4788
4803
|
const { groups, shared } = injectedGroups(manifestPath2);
|
|
4789
4804
|
return groups.map((e) => {
|
|
4790
4805
|
const weight = shared || e.weights.length > 1 ? `${SPAN[0]} ${SPAN[1]}` : String(e.weights[0]);
|
|
4791
|
-
return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${
|
|
4806
|
+
return `@font-face { font-family: '${e.family}'; font-weight: ${weight}; font-style: normal; src: url(data:font/woff2;base64,${readFileSync8(e.file).toString("base64")}) format('woff2'); }`;
|
|
4792
4807
|
}).join("\n");
|
|
4793
4808
|
}
|
|
4794
4809
|
function injectedFamilyWeights(manifestPath2 = path14.join(fontCacheDir(), "manifest.json")) {
|
|
@@ -4816,7 +4831,7 @@ var init_font_faces = __esm({
|
|
|
4816
4831
|
});
|
|
4817
4832
|
|
|
4818
4833
|
// packages/verify/src/admission.ts
|
|
4819
|
-
import { readFileSync as
|
|
4834
|
+
import { readFileSync as readFileSync9, readdirSync as readdirSync4, existsSync as existsSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4820
4835
|
import path15 from "node:path";
|
|
4821
4836
|
import { build as build2 } from "esbuild";
|
|
4822
4837
|
import postcss from "postcss";
|
|
@@ -4826,7 +4841,7 @@ function fontWeightsByFamily() {
|
|
|
4826
4841
|
const mPath = path15.join(fontCacheDir(), "manifest.json");
|
|
4827
4842
|
const out = /* @__PURE__ */ new Map();
|
|
4828
4843
|
if (!existsSync11(mPath)) return out;
|
|
4829
|
-
for (const f of JSON.parse(
|
|
4844
|
+
for (const f of JSON.parse(readFileSync9(mPath, "utf8")))
|
|
4830
4845
|
out.set(f.family, [...out.get(f.family) ?? [], f.weight]);
|
|
4831
4846
|
return out;
|
|
4832
4847
|
}
|
|
@@ -4846,7 +4861,7 @@ var init_admission = __esm({
|
|
|
4846
4861
|
});
|
|
4847
4862
|
|
|
4848
4863
|
// packages/verify/src/candidate-css.ts
|
|
4849
|
-
import { existsSync as existsSync12, readFileSync as
|
|
4864
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
4850
4865
|
import path16 from "node:path";
|
|
4851
4866
|
function candidateCss(bundleDir) {
|
|
4852
4867
|
const files = ["tokens.css", "styles.css"].map((f) => path16.join(bundleDir, f));
|
|
@@ -4865,7 +4880,7 @@ function candidateCss(bundleDir) {
|
|
|
4865
4880
|
}
|
|
4866
4881
|
files.push(path16.join(dir, "tokens.css"), path16.join(dir, "styles.css"));
|
|
4867
4882
|
}
|
|
4868
|
-
return files.filter((f) => existsSync12(f)).map((f) =>
|
|
4883
|
+
return files.filter((f) => existsSync12(f)).map((f) => readFileSync10(f, "utf8")).join("\n");
|
|
4869
4884
|
}
|
|
4870
4885
|
var init_candidate_css = __esm({
|
|
4871
4886
|
"packages/verify/src/candidate-css.ts"() {
|
|
@@ -5083,7 +5098,7 @@ __export(behavior_exports, {
|
|
|
5083
5098
|
compileMount: () => compileMount,
|
|
5084
5099
|
recordingIsDark: () => recordingIsDark
|
|
5085
5100
|
});
|
|
5086
|
-
import { existsSync as existsSync13, readFileSync as
|
|
5101
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
|
|
5087
5102
|
import path18 from "node:path";
|
|
5088
5103
|
import { build as build3 } from "esbuild";
|
|
5089
5104
|
import { chromium as chromium3 } from "playwright-core";
|
|
@@ -5159,8 +5174,8 @@ async function runSteps(page, spec, renderPose) {
|
|
|
5159
5174
|
if (renderPose === void 0) return { id: spec.id, pass: false, detail: "commitMatchesPose requires a pose renderer" };
|
|
5160
5175
|
await settle(page);
|
|
5161
5176
|
const idle = await shotRoot(page);
|
|
5162
|
-
for (const
|
|
5163
|
-
await page.waitForTimeout(
|
|
5177
|
+
for (const delay2 of [70, 130, 190]) {
|
|
5178
|
+
await page.waitForTimeout(delay2);
|
|
5164
5179
|
await freezeAnimations(page);
|
|
5165
5180
|
if (!(await shotRoot(page)).equals(idle)) {
|
|
5166
5181
|
return { id: spec.id, pass: false, detail: "unmeasurable: the component's pixels change at rest (free-running animation or a timer in YOUR candidate) \u2014 the committed state cannot be observed through it; this IS fixable from code: keep every scored pose reachable with all motion idle (freezable CSS animation, no JS-driven repaints)" };
|
|
@@ -5455,7 +5470,7 @@ function recordingIsDark(task) {
|
|
|
5455
5470
|
const f = path18.join(task.set, rep, "get_screenshot.json");
|
|
5456
5471
|
if (!existsSync13(f)) return false;
|
|
5457
5472
|
try {
|
|
5458
|
-
const env = JSON.parse(
|
|
5473
|
+
const env = JSON.parse(readFileSync11(f, "utf8")).content.find((c) => c.type === "image");
|
|
5459
5474
|
if (env?.data === void 0) return false;
|
|
5460
5475
|
const png = PNG2.sync.read(Buffer.from(env.data, "base64"));
|
|
5461
5476
|
let sum = 0;
|
|
@@ -6071,6 +6086,11 @@ var init_bundle = __esm({
|
|
|
6071
6086
|
rep: z9.string(),
|
|
6072
6087
|
similarity: z9.number(),
|
|
6073
6088
|
inkRecall: z9.number(),
|
|
6089
|
+
/** Pre-deadband measured values. Declared so the field SURVIVES the
|
|
6090
|
+
* schema round-trip — the emitter writes it (bundle-emit) and zod was
|
|
6091
|
+
* silently stripping it back out, so a reader could not tell a
|
|
6092
|
+
* rounded 0.970 from a measured one. */
|
|
6093
|
+
exact: z9.object({ similarity: z9.number(), inkRecall: z9.number() }).optional(),
|
|
6074
6094
|
status: ConfigStatusSchema
|
|
6075
6095
|
});
|
|
6076
6096
|
BehaviorClaimSchema = z9.object({
|
|
@@ -6092,7 +6112,18 @@ var init_bundle = __esm({
|
|
|
6092
6112
|
licenseNote: z9.string().optional(),
|
|
6093
6113
|
/** Content hash over the set manifest + rep envelopes (the identity
|
|
6094
6114
|
* verify compares against, not the path). */
|
|
6095
|
-
hash: z9.string()
|
|
6115
|
+
hash: z9.string(),
|
|
6116
|
+
/** KIT IDENTITY (PROV-1). The Figma file key and component-set node
|
|
6117
|
+
* the recording came from. Both are captured at plan time in the
|
|
6118
|
+
* recording set and were being dropped here, which left a published
|
|
6119
|
+
* bundle unable to say which kit it derives from — while the portal's
|
|
6120
|
+
* consent model is scoped to exactly that file key (ADR-018 §3), and
|
|
6121
|
+
* the right to publish attaches to a kit rather than to a folder
|
|
6122
|
+
* someone named. Optional: recordings planned before this field
|
|
6123
|
+
* existed carry neither, and a set can be a handed-in selection with
|
|
6124
|
+
* no component set at all. Absent means NOT RECORDED, never "no kit". */
|
|
6125
|
+
figmaFile: z9.string().optional(),
|
|
6126
|
+
componentSetNode: z9.string().optional()
|
|
6096
6127
|
}),
|
|
6097
6128
|
environment: z9.object({
|
|
6098
6129
|
chrome: z9.string(),
|
|
@@ -6141,6 +6172,198 @@ var init_bundle = __esm({
|
|
|
6141
6172
|
}
|
|
6142
6173
|
});
|
|
6143
6174
|
|
|
6175
|
+
// packages/metadata/src/published-surface.ts
|
|
6176
|
+
function classifyBundleSurface(files, opts) {
|
|
6177
|
+
const published = [];
|
|
6178
|
+
const excluded = [];
|
|
6179
|
+
const unknown = [];
|
|
6180
|
+
for (const raw of files) {
|
|
6181
|
+
const path54 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6182
|
+
const inEvidence = path54.startsWith(`${EVIDENCE_DIR}/`);
|
|
6183
|
+
const name = inEvidence ? path54.slice(EVIDENCE_DIR.length + 1) : path54;
|
|
6184
|
+
if (name.includes("/")) {
|
|
6185
|
+
unknown.push(path54);
|
|
6186
|
+
continue;
|
|
6187
|
+
}
|
|
6188
|
+
if (inEvidence) {
|
|
6189
|
+
if (name === "verify-report.json") published.push({ path: path54, role: "verify-report" });
|
|
6190
|
+
else if (name === "diff-legend.txt") published.push({ path: path54, role: "diff-legend" });
|
|
6191
|
+
else if (name === "inspect.html") published.push({ path: path54, role: "inspect-sheet" });
|
|
6192
|
+
else {
|
|
6193
|
+
const hit = EVIDENCE_PATTERNS.find((p) => p.re.test(name));
|
|
6194
|
+
if (hit !== void 0) published.push({ path: path54, role: hit.role });
|
|
6195
|
+
else unknown.push(path54);
|
|
6196
|
+
}
|
|
6197
|
+
continue;
|
|
6198
|
+
}
|
|
6199
|
+
if (name === opts.entry) published.push({ path: path54, role: "entry" });
|
|
6200
|
+
else if (name === "styles.css") published.push({ path: path54, role: "styles" });
|
|
6201
|
+
else if (name === "tokens.css") published.push({ path: path54, role: "tokens" });
|
|
6202
|
+
else if (name === "fonts.css") published.push({ path: path54, role: "fonts" });
|
|
6203
|
+
else if (name === "component.json") published.push({ path: path54, role: "manifest" });
|
|
6204
|
+
else {
|
|
6205
|
+
const skip = EXCLUDED.find((e) => e.test(name));
|
|
6206
|
+
if (skip !== void 0) excluded.push({ path: path54, reason: skip.reason });
|
|
6207
|
+
else unknown.push(path54);
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
const roles = new Set(published.map((p) => p.role));
|
|
6211
|
+
return { published, excluded, unknown, missingRequired: REQUIRED_ROLES.filter((r) => !roles.has(r)) };
|
|
6212
|
+
}
|
|
6213
|
+
function missingInspectCrops(sheetText, publishedPaths) {
|
|
6214
|
+
const held = new Set(publishedPaths);
|
|
6215
|
+
const missing = /* @__PURE__ */ new Set();
|
|
6216
|
+
for (const [, name] of sheetText.matchAll(/src="\.\/([^"]+)"/g)) {
|
|
6217
|
+
const path54 = `${EVIDENCE_DIR}/${name}`;
|
|
6218
|
+
if (!held.has(path54)) missing.add(path54);
|
|
6219
|
+
}
|
|
6220
|
+
return [...missing].sort();
|
|
6221
|
+
}
|
|
6222
|
+
var EVIDENCE_DIR, REQUIRED_ROLES, EXCLUDED, EVIDENCE_PATTERNS, SCORED_FILE_ROLES;
|
|
6223
|
+
var init_published_surface = __esm({
|
|
6224
|
+
"packages/metadata/src/published-surface.ts"() {
|
|
6225
|
+
"use strict";
|
|
6226
|
+
EVIDENCE_DIR = "verify-evidence";
|
|
6227
|
+
REQUIRED_ROLES = ["entry", "styles", "manifest", "verify-report", "evidence-triple"];
|
|
6228
|
+
EXCLUDED = [
|
|
6229
|
+
{ test: (n) => n === "score-history.jsonl", reason: "generation loop score history" },
|
|
6230
|
+
{ test: (n) => n === "loop-state.json", reason: "generation loop state" },
|
|
6231
|
+
{ test: (n) => n === "run-log.json", reason: "generation run log" },
|
|
6232
|
+
{ test: (n) => n.endsWith(".md"), reason: "brief / debrief / model response" },
|
|
6233
|
+
{ test: (n) => n === ".DS_Store", reason: "macOS directory metadata" }
|
|
6234
|
+
];
|
|
6235
|
+
EVIDENCE_PATTERNS = [
|
|
6236
|
+
{ re: /^.+-absent-\d+-(?:ref|render)\.png$/, role: "evidence-absent-crop" },
|
|
6237
|
+
{ re: /^.+-inspect-\d+-(?:ref|render)\.png$/, role: "evidence-zoom-crop" },
|
|
6238
|
+
{ re: /^.+-(?:ref|render|diff)\.png$/, role: "evidence-triple" }
|
|
6239
|
+
];
|
|
6240
|
+
SCORED_FILE_ROLES = /* @__PURE__ */ new Set([
|
|
6241
|
+
"entry",
|
|
6242
|
+
"styles",
|
|
6243
|
+
"tokens",
|
|
6244
|
+
"fonts",
|
|
6245
|
+
"manifest",
|
|
6246
|
+
"evidence-triple",
|
|
6247
|
+
"evidence-absent-crop",
|
|
6248
|
+
"diff-legend"
|
|
6249
|
+
]);
|
|
6250
|
+
}
|
|
6251
|
+
});
|
|
6252
|
+
|
|
6253
|
+
// packages/metadata/src/bundle-files.ts
|
|
6254
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
6255
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
6256
|
+
import path19 from "node:path";
|
|
6257
|
+
function bundleFiles(dir, prefix = "") {
|
|
6258
|
+
const out = [];
|
|
6259
|
+
for (const entry of readdirSync6(dir).sort()) {
|
|
6260
|
+
const full = path19.join(dir, entry);
|
|
6261
|
+
const rel = prefix === "" ? entry : `${prefix}/${entry}`;
|
|
6262
|
+
if (statSync3(full).isDirectory()) out.push(...bundleFiles(full, rel));
|
|
6263
|
+
else out.push(rel);
|
|
6264
|
+
}
|
|
6265
|
+
return out;
|
|
6266
|
+
}
|
|
6267
|
+
function digestScoredFiles(bundleDir, entry) {
|
|
6268
|
+
const surface = classifyBundleSurface(bundleFiles(bundleDir), { entry });
|
|
6269
|
+
const out = {};
|
|
6270
|
+
for (const file of surface.published) {
|
|
6271
|
+
if (!SCORED_FILE_ROLES.has(file.role)) continue;
|
|
6272
|
+
out[file.path] = createHash4("sha256").update(readFileSync12(path19.join(bundleDir, file.path))).digest("hex");
|
|
6273
|
+
}
|
|
6274
|
+
return out;
|
|
6275
|
+
}
|
|
6276
|
+
var init_bundle_files = __esm({
|
|
6277
|
+
"packages/metadata/src/bundle-files.ts"() {
|
|
6278
|
+
"use strict";
|
|
6279
|
+
init_published_surface();
|
|
6280
|
+
}
|
|
6281
|
+
});
|
|
6282
|
+
|
|
6283
|
+
// packages/metadata/src/verify-report.ts
|
|
6284
|
+
function verifyReportForTransport(report, bundleName) {
|
|
6285
|
+
const { dir: _dir, ...evidence } = report.evidence;
|
|
6286
|
+
const { eyeCheck: _eyeCheck, ...rest } = report;
|
|
6287
|
+
return { ...rest, bundle: bundleName, evidence };
|
|
6288
|
+
}
|
|
6289
|
+
function nonEmptyArray(value) {
|
|
6290
|
+
return Array.isArray(value) && value.length > 0;
|
|
6291
|
+
}
|
|
6292
|
+
function undisclosedTrustFacts(report) {
|
|
6293
|
+
if (report === null || typeof report !== "object" || Array.isArray(report)) return [];
|
|
6294
|
+
const r = report;
|
|
6295
|
+
const facts = [];
|
|
6296
|
+
if (r["recordingSetDrift"] !== void 0 && r["recordingSetDrift"] !== null) {
|
|
6297
|
+
facts.push({
|
|
6298
|
+
pointer: "$.recordingSetDrift",
|
|
6299
|
+
consequence: "the recording set that was scored is not the one the bundle stamps, so these scores describe a different design than the bundle claims"
|
|
6300
|
+
});
|
|
6301
|
+
}
|
|
6302
|
+
if (nonEmptyArray(r["substitutedFamilies"])) {
|
|
6303
|
+
facts.push({
|
|
6304
|
+
pointer: "$.substitutedFamilies",
|
|
6305
|
+
consequence: "a font family was substituted, so the scores measure a substitute face and no config can be certified under one"
|
|
6306
|
+
});
|
|
6307
|
+
}
|
|
6308
|
+
if (nonEmptyArray(r["certBlockedByAbsentInk"])) {
|
|
6309
|
+
facts.push({
|
|
6310
|
+
pointer: "$.certBlockedByAbsentInk",
|
|
6311
|
+
consequence: "certification was blocked by absent ink, which a verdict alone does not say"
|
|
6312
|
+
});
|
|
6313
|
+
}
|
|
6314
|
+
return facts;
|
|
6315
|
+
}
|
|
6316
|
+
function isSetHash(value) {
|
|
6317
|
+
return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
|
|
6318
|
+
}
|
|
6319
|
+
function readScoredFiles(report) {
|
|
6320
|
+
if (report === null || typeof report !== "object" || Array.isArray(report)) return void 0;
|
|
6321
|
+
const value = report["scoredFiles"];
|
|
6322
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
6323
|
+
const entries = Object.entries(value);
|
|
6324
|
+
if (entries.length === 0) return void 0;
|
|
6325
|
+
const out = {};
|
|
6326
|
+
for (const [path54, digest] of entries) {
|
|
6327
|
+
if (path54 === "" || path54.startsWith("/") || path54.includes("..")) return void 0;
|
|
6328
|
+
if (!isSetHash(digest)) return void 0;
|
|
6329
|
+
out[path54] = digest;
|
|
6330
|
+
}
|
|
6331
|
+
return out;
|
|
6332
|
+
}
|
|
6333
|
+
function compareScoredFiles(recorded, actual) {
|
|
6334
|
+
const missing = [];
|
|
6335
|
+
const unscored = [];
|
|
6336
|
+
const changed = [];
|
|
6337
|
+
for (const [path54, digest] of Object.entries(recorded)) {
|
|
6338
|
+
if (!(path54 in actual)) missing.push(path54);
|
|
6339
|
+
else if (actual[path54] !== digest) changed.push(path54);
|
|
6340
|
+
}
|
|
6341
|
+
for (const path54 of Object.keys(actual)) if (!(path54 in recorded)) unscored.push(path54);
|
|
6342
|
+
return { missing: missing.sort(), unscored: unscored.sort(), changed: changed.sort() };
|
|
6343
|
+
}
|
|
6344
|
+
function scoredRecordingSetHash(report) {
|
|
6345
|
+
if (report === null || typeof report !== "object" || Array.isArray(report)) return void 0;
|
|
6346
|
+
const r = report;
|
|
6347
|
+
const drift = r["recordingSetDrift"];
|
|
6348
|
+
if (drift !== null && typeof drift === "object" && !Array.isArray(drift)) {
|
|
6349
|
+
const current = drift["current"];
|
|
6350
|
+
if (isSetHash(current)) return current;
|
|
6351
|
+
}
|
|
6352
|
+
const manifest = r["bundleManifest"];
|
|
6353
|
+
if (manifest !== null && typeof manifest === "object" && !Array.isArray(manifest)) {
|
|
6354
|
+
const stamped = manifest["recordingSetHash"];
|
|
6355
|
+
if (isSetHash(stamped)) return stamped;
|
|
6356
|
+
}
|
|
6357
|
+
return void 0;
|
|
6358
|
+
}
|
|
6359
|
+
var VERIFY_REPORT_FILENAME;
|
|
6360
|
+
var init_verify_report = __esm({
|
|
6361
|
+
"packages/metadata/src/verify-report.ts"() {
|
|
6362
|
+
"use strict";
|
|
6363
|
+
VERIFY_REPORT_FILENAME = "verify-report.json";
|
|
6364
|
+
}
|
|
6365
|
+
});
|
|
6366
|
+
|
|
6144
6367
|
// packages/metadata/src/motion-css.ts
|
|
6145
6368
|
var MOTION_CSS_PATTERN, scannableCss, MOTION_TOKEN_NAMES;
|
|
6146
6369
|
var init_motion_css = __esm({
|
|
@@ -6366,14 +6589,17 @@ var init_src4 = __esm({
|
|
|
6366
6589
|
init_extract();
|
|
6367
6590
|
init_recording_set();
|
|
6368
6591
|
init_bundle();
|
|
6592
|
+
init_bundle_files();
|
|
6593
|
+
init_verify_report();
|
|
6594
|
+
init_published_surface();
|
|
6369
6595
|
init_motion_css();
|
|
6370
6596
|
init_profile();
|
|
6371
6597
|
}
|
|
6372
6598
|
});
|
|
6373
6599
|
|
|
6374
6600
|
// packages/verify/src/bundle-quality.ts
|
|
6375
|
-
import { readFileSync as
|
|
6376
|
-
import
|
|
6601
|
+
import { readFileSync as readFileSync13, readdirSync as readdirSync7, rmSync as rmSync2, writeFileSync as writeFileSync5, existsSync as existsSync14 } from "node:fs";
|
|
6602
|
+
import path20 from "node:path";
|
|
6377
6603
|
function unscopedReduceBlockFindings(file, css) {
|
|
6378
6604
|
const stripped = css.replace(/\/\*[\s\S]*?\*\//g, (c) => c.replace(/[^\n]/g, " "));
|
|
6379
6605
|
const media = /@media[^{]*prefers-reduced-motion\s*:\s*reduce[^{]*\{/.exec(stripped);
|
|
@@ -6431,16 +6657,16 @@ function recordedAssetFindings(entryFile, entrySource, recordedPaths) {
|
|
|
6431
6657
|
function recordedAssetPaths(setDir, reps) {
|
|
6432
6658
|
const map = /* @__PURE__ */ new Map();
|
|
6433
6659
|
for (const rep of reps) {
|
|
6434
|
-
const dir =
|
|
6660
|
+
const dir = path20.join(setDir, rep);
|
|
6435
6661
|
let files;
|
|
6436
6662
|
try {
|
|
6437
|
-
files =
|
|
6663
|
+
files = readdirSync7(dir).filter((f) => f.startsWith("asset-") && f.endsWith(".svg"));
|
|
6438
6664
|
} catch {
|
|
6439
6665
|
continue;
|
|
6440
6666
|
}
|
|
6441
6667
|
for (const f of files) {
|
|
6442
6668
|
try {
|
|
6443
|
-
const svg =
|
|
6669
|
+
const svg = readFileSync13(path20.join(dir, f), "utf8");
|
|
6444
6670
|
for (const m of svg.matchAll(/\sd="([^"]+)"/g)) {
|
|
6445
6671
|
const d = m[1];
|
|
6446
6672
|
const owners = map.get(d) ?? [];
|
|
@@ -6508,17 +6734,17 @@ function recordedTokenMapState(setDir, reps) {
|
|
|
6508
6734
|
const readMap = (file) => {
|
|
6509
6735
|
if (!existsSync14(file)) return void 0;
|
|
6510
6736
|
try {
|
|
6511
|
-
const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(
|
|
6737
|
+
const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync13(file, "utf8"))) || "{}");
|
|
6512
6738
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
6513
6739
|
} catch {
|
|
6514
6740
|
return {};
|
|
6515
6741
|
}
|
|
6516
6742
|
};
|
|
6517
|
-
const setLevel = readMap(
|
|
6743
|
+
const setLevel = readMap(path20.join(setDir, "get_variable_defs.json"));
|
|
6518
6744
|
if (setLevel !== void 0) return Object.keys(setLevel).length === 0 ? "empty" : "populated";
|
|
6519
6745
|
let recorded = false;
|
|
6520
6746
|
for (const rep of reps) {
|
|
6521
|
-
const m = readMap(
|
|
6747
|
+
const m = readMap(path20.join(setDir, rep, "get_variable_defs.json"));
|
|
6522
6748
|
if (m === void 0) continue;
|
|
6523
6749
|
recorded = true;
|
|
6524
6750
|
if (Object.keys(m).length > 0) return "populated";
|
|
@@ -6530,7 +6756,7 @@ function recordedTokensByValue(setDir, reps) {
|
|
|
6530
6756
|
const readMap = (file) => {
|
|
6531
6757
|
if (!existsSync14(file)) return void 0;
|
|
6532
6758
|
try {
|
|
6533
|
-
const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(
|
|
6759
|
+
const parsed = JSON.parse(envelopeFirstTextPart(JSON.parse(readFileSync13(file, "utf8"))) || "{}");
|
|
6534
6760
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
6535
6761
|
} catch {
|
|
6536
6762
|
return void 0;
|
|
@@ -6548,8 +6774,8 @@ function recordedTokensByValue(setDir, reps) {
|
|
|
6548
6774
|
byValue.set(key, candidates);
|
|
6549
6775
|
}
|
|
6550
6776
|
};
|
|
6551
|
-
absorb(readMap(
|
|
6552
|
-
for (const rep of reps) absorb(readMap(
|
|
6777
|
+
absorb(readMap(path20.join(setDir, "get_variable_defs.json")));
|
|
6778
|
+
for (const rep of reps) absorb(readMap(path20.join(setDir, rep, "get_variable_defs.json")));
|
|
6553
6779
|
return byValue;
|
|
6554
6780
|
}
|
|
6555
6781
|
function scannable(css) {
|
|
@@ -6658,11 +6884,11 @@ function conventionsFindings(entrySource, css, profile) {
|
|
|
6658
6884
|
}
|
|
6659
6885
|
async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, contract, profile) {
|
|
6660
6886
|
const findings = [];
|
|
6661
|
-
const entryPath =
|
|
6662
|
-
const cssPath =
|
|
6663
|
-
const tokensPath =
|
|
6664
|
-
const css = existsSync14(cssPath) ?
|
|
6665
|
-
const tokensCss = existsSync14(tokensPath) ?
|
|
6887
|
+
const entryPath = path20.join(bundleDir, entry);
|
|
6888
|
+
const cssPath = path20.join(bundleDir, "styles.css");
|
|
6889
|
+
const tokensPath = path20.join(bundleDir, "tokens.css");
|
|
6890
|
+
const css = existsSync14(cssPath) ? readFileSync13(cssPath, "utf8") : "";
|
|
6891
|
+
const tokensCss = existsSync14(tokensPath) ? readFileSync13(tokensPath, "utf8") : void 0;
|
|
6666
6892
|
findings.push(
|
|
6667
6893
|
...fontStackFindings(
|
|
6668
6894
|
[
|
|
@@ -6675,8 +6901,8 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
|
|
|
6675
6901
|
if (existsSync14(entryPath)) {
|
|
6676
6902
|
const workDir = newScratchDir("quality");
|
|
6677
6903
|
try {
|
|
6678
|
-
const tsxPath =
|
|
6679
|
-
writeFileSync5(tsxPath,
|
|
6904
|
+
const tsxPath = path20.join(workDir, entry);
|
|
6905
|
+
writeFileSync5(tsxPath, readFileSync13(entryPath, "utf8"));
|
|
6680
6906
|
for (const d of runTscStrict([tsxPath]).diagnostics) {
|
|
6681
6907
|
if (d.code === 2307 && /['"]\.\/composed\//.test(d.message)) continue;
|
|
6682
6908
|
findings.push({ kind: "tsc", file: entry, ...d.line === void 0 ? {} : { line: d.line }, message: `TS${d.code}: ${d.message}` });
|
|
@@ -6686,7 +6912,7 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
|
|
|
6686
6912
|
}
|
|
6687
6913
|
}
|
|
6688
6914
|
if (contract !== void 0 && existsSync14(entryPath)) {
|
|
6689
|
-
findings.push(...expertLensFindings(entry,
|
|
6915
|
+
findings.push(...expertLensFindings(entry, readFileSync13(entryPath, "utf8"), contract));
|
|
6690
6916
|
}
|
|
6691
6917
|
for (const sheet of [
|
|
6692
6918
|
{ file: "styles.css", text: css },
|
|
@@ -6697,7 +6923,7 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
|
|
|
6697
6923
|
findings.push(...placeholderSelectorFindings(sheet.file, sheet.text));
|
|
6698
6924
|
}
|
|
6699
6925
|
if (set !== void 0 && existsSync14(entryPath)) {
|
|
6700
|
-
findings.push(...recordedAssetFindings(entry,
|
|
6926
|
+
findings.push(...recordedAssetFindings(entry, readFileSync13(entryPath, "utf8"), recordedAssetPaths(set.dir, set.reps)));
|
|
6701
6927
|
}
|
|
6702
6928
|
if (css !== "") {
|
|
6703
6929
|
const mapState = set === void 0 ? void 0 : recordedTokenMapState(set.dir, set.reps);
|
|
@@ -6719,7 +6945,7 @@ async function checkBundleQuality(bundleDir, entry, set, fontManifest, motion, c
|
|
|
6719
6945
|
})
|
|
6720
6946
|
);
|
|
6721
6947
|
if (profile !== void 0 && existsSync14(entryPath)) {
|
|
6722
|
-
findings.push(...conventionsFindings(
|
|
6948
|
+
findings.push(...conventionsFindings(readFileSync13(entryPath, "utf8"), css, profile));
|
|
6723
6949
|
}
|
|
6724
6950
|
return { findings, tokensAbsent: tokensCss === void 0 && !/var\(\s*--/.test(css) };
|
|
6725
6951
|
}
|
|
@@ -6820,8 +7046,8 @@ var init_effect_geometry = __esm({
|
|
|
6820
7046
|
});
|
|
6821
7047
|
|
|
6822
7048
|
// packages/verify/src/bundle-score.ts
|
|
6823
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync3, readFileSync as
|
|
6824
|
-
import
|
|
7049
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7050
|
+
import path21 from "node:path";
|
|
6825
7051
|
import { build as build4 } from "esbuild";
|
|
6826
7052
|
import { chromium as chromium4 } from "playwright-core";
|
|
6827
7053
|
function getFontFaces2() {
|
|
@@ -6875,7 +7101,7 @@ function chooseSeat(cropAt, ref, padW, padH, backdrop, origin, extents) {
|
|
|
6875
7101
|
}
|
|
6876
7102
|
function metadataRoot(set, rep) {
|
|
6877
7103
|
try {
|
|
6878
|
-
const text = JSON.parse(
|
|
7104
|
+
const text = JSON.parse(readFileSync14(path21.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
6879
7105
|
return parseMetadataStructure(text);
|
|
6880
7106
|
} catch {
|
|
6881
7107
|
return void 0;
|
|
@@ -6930,19 +7156,19 @@ function smallSemanticNodes(set, rep, maxArea = 1024) {
|
|
|
6930
7156
|
});
|
|
6931
7157
|
}
|
|
6932
7158
|
function repMeta(set, rep) {
|
|
6933
|
-
const text = JSON.parse(
|
|
7159
|
+
const text = JSON.parse(readFileSync14(path21.join(set, rep, "get_metadata.json"), "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
6934
7160
|
const root = parseMetadataStructure(text);
|
|
6935
7161
|
return { w: Math.round(root.width ?? 100), h: Math.round(root.height ?? 40) };
|
|
6936
7162
|
}
|
|
6937
7163
|
function repRef(set, rep) {
|
|
6938
|
-
const env = JSON.parse(
|
|
7164
|
+
const env = JSON.parse(readFileSync14(path21.join(set, rep, "get_screenshot.json"), "utf8")).content.find((c) => c.type === "image");
|
|
6939
7165
|
return Uint8Array.from(Buffer.from(env?.data ?? "", "base64"));
|
|
6940
7166
|
}
|
|
6941
7167
|
function repEffectExtents(set, rep) {
|
|
6942
|
-
const file =
|
|
7168
|
+
const file = path21.join(set, rep, "get_design_context.json");
|
|
6943
7169
|
if (!existsSync15(file)) return void 0;
|
|
6944
7170
|
try {
|
|
6945
|
-
const text = JSON.parse(
|
|
7171
|
+
const text = JSON.parse(readFileSync14(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
6946
7172
|
const extents = shadowExtents(text);
|
|
6947
7173
|
return extents.top + extents.right + extents.bottom + extents.left > 0 ? extents : void 0;
|
|
6948
7174
|
} catch {
|
|
@@ -6952,17 +7178,17 @@ function repEffectExtents(set, rep) {
|
|
|
6952
7178
|
async function scoreBundleForTask(task, bundleDir, bar = BAR, opts = {}) {
|
|
6953
7179
|
if (opts.evidenceDir !== void 0) {
|
|
6954
7180
|
mkdirSync3(opts.evidenceDir, { recursive: true });
|
|
6955
|
-
writeFileSync6(
|
|
7181
|
+
writeFileSync6(path21.join(opts.evidenceDir, "diff-legend.txt"), DIFF_LEGEND_TEXT);
|
|
6956
7182
|
}
|
|
6957
7183
|
const CONFIGS2 = task.configs;
|
|
6958
7184
|
const timeoutMs = opts.mountDeadlineMs ?? ENGINE_MOUNT_DEADLINE_MS;
|
|
6959
|
-
const entryTsx =
|
|
7185
|
+
const entryTsx = path21.join(bundleDir, task.entry);
|
|
6960
7186
|
if (!existsSync15(entryTsx)) return CONFIGS2.map((c) => ({ rep: c.rep, similarity: 0, inkRecall: 0, exact: { similarity: 0, inkRecall: 0 }, pass: false, error: `${task.entry} missing` }));
|
|
6961
7187
|
const css = candidateCss(bundleDir);
|
|
6962
7188
|
const mountSrc = `
|
|
6963
7189
|
import { createElement } from "react";
|
|
6964
7190
|
import { createRoot } from "react-dom/client";
|
|
6965
|
-
import * as B from ${JSON.stringify(
|
|
7191
|
+
import * as B from ${JSON.stringify(path21.resolve(entryTsx))};
|
|
6966
7192
|
const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
|
|
6967
7193
|
const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
|
|
6968
7194
|
const root = document.getElementById("root");
|
|
@@ -7061,12 +7287,12 @@ body{margin:0;background:rgb(${backdrop.join(",")})}
|
|
|
7061
7287
|
return name === void 0 ? c : { ...c, name };
|
|
7062
7288
|
});
|
|
7063
7289
|
if (opts.evidenceDir !== void 0) {
|
|
7064
|
-
writeFileSync6(
|
|
7065
|
-
writeFileSync6(
|
|
7066
|
-
writeFileSync6(
|
|
7290
|
+
writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-render.png`), scored);
|
|
7291
|
+
writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-ref.png`), ref);
|
|
7292
|
+
writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-diff.png`), diffPng(scored, ref, [], backdrop));
|
|
7067
7293
|
for (const [i, c] of absent.slice(0, 3).entries()) {
|
|
7068
|
-
writeFileSync6(
|
|
7069
|
-
writeFileSync6(
|
|
7294
|
+
writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-ref.png`), zoomCrop(ref, c));
|
|
7295
|
+
writeFileSync6(path21.join(opts.evidenceDir, `${cfg.rep}-absent-${i}-render.png`), zoomCrop(scored, c));
|
|
7070
7296
|
}
|
|
7071
7297
|
}
|
|
7072
7298
|
return {
|
|
@@ -7313,7 +7539,7 @@ var init_parity = __esm({
|
|
|
7313
7539
|
// packages/verify/src/composition.ts
|
|
7314
7540
|
import { createRequire as createRequire2 } from "node:module";
|
|
7315
7541
|
import { existsSync as existsSync16 } from "node:fs";
|
|
7316
|
-
import
|
|
7542
|
+
import path22 from "node:path";
|
|
7317
7543
|
import { build as build6 } from "esbuild";
|
|
7318
7544
|
import { chromium as chromium7 } from "playwright-core";
|
|
7319
7545
|
function getFontFaces4() {
|
|
@@ -7321,9 +7547,9 @@ function getFontFaces4() {
|
|
|
7321
7547
|
return _fontFaces4;
|
|
7322
7548
|
}
|
|
7323
7549
|
async function compileInstrumentedMount(task, bundleDir, composedParts = []) {
|
|
7324
|
-
const entryTsx =
|
|
7550
|
+
const entryTsx = path22.join(bundleDir, task.entry);
|
|
7325
7551
|
if (!existsSync16(entryTsx)) return { error: `${task.entry} missing` };
|
|
7326
|
-
const requireFromVerify = createRequire2(
|
|
7552
|
+
const requireFromVerify = createRequire2(path22.join(VERIFY_PKG_DIR, "package.json"));
|
|
7327
7553
|
let realJsxPath;
|
|
7328
7554
|
try {
|
|
7329
7555
|
realJsxPath = requireFromVerify.resolve("react/jsx-runtime");
|
|
@@ -7334,8 +7560,8 @@ async function compileInstrumentedMount(task, bundleDir, composedParts = []) {
|
|
|
7334
7560
|
import { createElement } from "react";
|
|
7335
7561
|
import { createRoot } from "react-dom/client";
|
|
7336
7562
|
import { __registerParts } from "react/jsx-runtime";
|
|
7337
|
-
import * as B from ${JSON.stringify(
|
|
7338
|
-
${composedParts.map((p, i) => `import * as CP${i} from ${JSON.stringify(
|
|
7563
|
+
import * as B from ${JSON.stringify(path22.resolve(entryTsx))};
|
|
7564
|
+
${composedParts.map((p, i) => `import * as CP${i} from ${JSON.stringify(path22.resolve(bundleDir, p.modulePath))};`).join("\n")}
|
|
7339
7565
|
const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown>; partComponents: string[] } }).__cfg;
|
|
7340
7566
|
const pairs: Array<[unknown, string]> = [];
|
|
7341
7567
|
for (const name of cfg.partComponents) {
|
|
@@ -7391,7 +7617,7 @@ function expectedParts(task, roles, mainSlug) {
|
|
|
7391
7617
|
}
|
|
7392
7618
|
function interiorRegions(setDir, roles) {
|
|
7393
7619
|
const mains = roles.main;
|
|
7394
|
-
const withInterior = mains.filter((m) => existsSync16(
|
|
7620
|
+
const withInterior = mains.filter((m) => existsSync16(path22.join(setDir, m, "get_metadata_interior.json")));
|
|
7395
7621
|
if (withInterior.length === 0) {
|
|
7396
7622
|
return { unavailable: "no interior geometry recorded for any main (pre-obligation set) \u2014 re-record with `tendril record` to enable crop checks" };
|
|
7397
7623
|
}
|
|
@@ -7665,16 +7891,16 @@ export const jsxs = (t, p, k) => real.jsxs(t, inject(t, p), k);
|
|
|
7665
7891
|
|
|
7666
7892
|
// packages/verify/src/occlusion.ts
|
|
7667
7893
|
import { existsSync as existsSync17 } from "node:fs";
|
|
7668
|
-
import
|
|
7894
|
+
import path23 from "node:path";
|
|
7669
7895
|
import { build as build7 } from "esbuild";
|
|
7670
7896
|
import { chromium as chromium8 } from "playwright-core";
|
|
7671
7897
|
async function compileTwoUp(task, bundleDir) {
|
|
7672
|
-
const entryTsx =
|
|
7898
|
+
const entryTsx = path23.join(bundleDir, task.entry);
|
|
7673
7899
|
if (!existsSync17(entryTsx)) return { error: `${task.entry} missing` };
|
|
7674
7900
|
const src = `
|
|
7675
7901
|
import { createElement } from "react";
|
|
7676
7902
|
import { createRoot } from "react-dom/client";
|
|
7677
|
-
import * as B from ${JSON.stringify(
|
|
7903
|
+
import * as B from ${JSON.stringify(path23.resolve(entryTsx))};
|
|
7678
7904
|
const cfg = (window as unknown as { __cfg: { component: string; props: Record<string, unknown> } }).__cfg;
|
|
7679
7905
|
const C = (B as Record<string, unknown>)[cfg.component] as Parameters<typeof createElement>[0] | undefined;
|
|
7680
7906
|
for (const id of ["first", "second"]) {
|
|
@@ -7925,26 +8151,26 @@ var init_src5 = __esm({
|
|
|
7925
8151
|
});
|
|
7926
8152
|
|
|
7927
8153
|
// packages/cli/src/environment.ts
|
|
7928
|
-
import { existsSync as existsSync18, readFileSync as
|
|
7929
|
-
import
|
|
7930
|
-
import { createHash as
|
|
8154
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "node:fs";
|
|
8155
|
+
import path24 from "node:path";
|
|
8156
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
7931
8157
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7932
8158
|
function cliVersion() {
|
|
7933
8159
|
try {
|
|
7934
|
-
return JSON.parse(
|
|
8160
|
+
return JSON.parse(readFileSync16(path24.join(path24.dirname(fileURLToPath5(import.meta.url)), "..", "package.json"), "utf8")).version ?? "dev";
|
|
7935
8161
|
} catch {
|
|
7936
8162
|
return "dev";
|
|
7937
8163
|
}
|
|
7938
8164
|
}
|
|
7939
8165
|
function environmentStamp(taskFamilies) {
|
|
7940
|
-
const manifestPath2 =
|
|
8166
|
+
const manifestPath2 = path24.join(fontCacheDir(), "manifest.json");
|
|
7941
8167
|
let fontsHash = null;
|
|
7942
8168
|
if (existsSync18(manifestPath2)) {
|
|
7943
8169
|
try {
|
|
7944
|
-
const entries = JSON.parse(
|
|
8170
|
+
const entries = JSON.parse(readFileSync16(manifestPath2, "utf8"));
|
|
7945
8171
|
const scoped = taskFamilies === void 0 || taskFamilies === null ? entries : entries.filter((f) => taskFamilies.some((fam) => fam.toLowerCase() === f.family.toLowerCase()));
|
|
7946
8172
|
const faces = scoped.map((f) => `${f.family}:${f.weight}:${f.sha256}`).sort();
|
|
7947
|
-
fontsHash = faces.length === 0 ? null :
|
|
8173
|
+
fontsHash = faces.length === 0 ? null : createHash5("sha256").update(faces.join("\n")).digest("hex").slice(0, 16);
|
|
7948
8174
|
} catch {
|
|
7949
8175
|
fontsHash = null;
|
|
7950
8176
|
}
|
|
@@ -7985,8 +8211,8 @@ var init_describe = __esm({
|
|
|
7985
8211
|
});
|
|
7986
8212
|
|
|
7987
8213
|
// packages/cli/src/env.ts
|
|
7988
|
-
import { existsSync as existsSync19, readFileSync as
|
|
7989
|
-
import
|
|
8214
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "node:fs";
|
|
8215
|
+
import path25 from "node:path";
|
|
7990
8216
|
function parseEnv(content) {
|
|
7991
8217
|
const entries = /* @__PURE__ */ new Map();
|
|
7992
8218
|
for (const line of content.split("\n")) {
|
|
@@ -7998,9 +8224,9 @@ function parseEnv(content) {
|
|
|
7998
8224
|
function resolveCredential(name) {
|
|
7999
8225
|
const fromProcess = process.env[name];
|
|
8000
8226
|
if (fromProcess) return fromProcess;
|
|
8001
|
-
const envPath =
|
|
8227
|
+
const envPath = path25.resolve(process.env["INIT_CWD"] ?? process.cwd(), ".env");
|
|
8002
8228
|
if (!existsSync19(envPath)) return void 0;
|
|
8003
|
-
return parseEnv(
|
|
8229
|
+
return parseEnv(readFileSync17(envPath, "utf8")).get(name);
|
|
8004
8230
|
}
|
|
8005
8231
|
var init_env = __esm({
|
|
8006
8232
|
"packages/cli/src/env.ts"() {
|
|
@@ -8059,18 +8285,200 @@ var init_output = __esm({
|
|
|
8059
8285
|
}
|
|
8060
8286
|
});
|
|
8061
8287
|
|
|
8288
|
+
// packages/cli/src/publish-client.ts
|
|
8289
|
+
import { chmodSync, existsSync as existsSync20, mkdirSync as mkdirSync4, readFileSync as readFileSync18, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8290
|
+
import os4 from "node:os";
|
|
8291
|
+
import path26 from "node:path";
|
|
8292
|
+
function sessionPath() {
|
|
8293
|
+
return process.env["TENDRIL_SESSION_PATH"] ?? path26.join(os4.homedir(), ".tendril", "session.json");
|
|
8294
|
+
}
|
|
8295
|
+
function readStoredSession(file = sessionPath()) {
|
|
8296
|
+
if (!existsSync20(file)) return void 0;
|
|
8297
|
+
try {
|
|
8298
|
+
const parsed = JSON.parse(readFileSync18(file, "utf8"));
|
|
8299
|
+
if (typeof parsed.origin !== "string" || typeof parsed.token !== "string") return void 0;
|
|
8300
|
+
return { origin: parsed.origin, token: parsed.token };
|
|
8301
|
+
} catch {
|
|
8302
|
+
return void 0;
|
|
8303
|
+
}
|
|
8304
|
+
}
|
|
8305
|
+
function writeStoredSession(session, file = sessionPath()) {
|
|
8306
|
+
mkdirSync4(path26.dirname(file), { recursive: true });
|
|
8307
|
+
writeFileSync7(file, `${JSON.stringify(session, null, 2)}
|
|
8308
|
+
`, { mode: 384 });
|
|
8309
|
+
chmodSync(file, 384);
|
|
8310
|
+
}
|
|
8311
|
+
function clearStoredSession(file = sessionPath()) {
|
|
8312
|
+
if (existsSync20(file)) rmSync3(file);
|
|
8313
|
+
}
|
|
8314
|
+
function tokenFor(origin, file = sessionPath()) {
|
|
8315
|
+
const fromEnv = process.env["TENDRIL_TOKEN"];
|
|
8316
|
+
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
8317
|
+
const pinned = (process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
|
|
8318
|
+
if (pinned !== "" && pinned !== origin) return { ok: false, reason: "origin-mismatch", from: "env", boundTo: pinned };
|
|
8319
|
+
return { ok: true, token: fromEnv, from: "env" };
|
|
8320
|
+
}
|
|
8321
|
+
const stored = readStoredSession(file);
|
|
8322
|
+
if (stored === void 0) return { ok: false, reason: "none" };
|
|
8323
|
+
if (stored.origin !== origin) return { ok: false, reason: "origin-mismatch", from: "file", boundTo: stored.origin };
|
|
8324
|
+
return { ok: true, token: stored.token, from: "file" };
|
|
8325
|
+
}
|
|
8326
|
+
function isSecureOrigin(origin) {
|
|
8327
|
+
let url;
|
|
8328
|
+
try {
|
|
8329
|
+
url = new URL(origin);
|
|
8330
|
+
} catch {
|
|
8331
|
+
return false;
|
|
8332
|
+
}
|
|
8333
|
+
if (url.protocol === "https:") return true;
|
|
8334
|
+
return url.protocol === "http:" && (url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "[::1]");
|
|
8335
|
+
}
|
|
8336
|
+
function isRecord(value) {
|
|
8337
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8338
|
+
}
|
|
8339
|
+
function delay(ms) {
|
|
8340
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8341
|
+
}
|
|
8342
|
+
var InsecureOriginError, HttpPublishClient, REQUEST_TIMEOUT_MS, RETRIES, BACKOFF_MS;
|
|
8343
|
+
var init_publish_client = __esm({
|
|
8344
|
+
"packages/cli/src/publish-client.ts"() {
|
|
8345
|
+
"use strict";
|
|
8346
|
+
InsecureOriginError = class extends Error {
|
|
8347
|
+
constructor(origin) {
|
|
8348
|
+
super(`${origin} is not https, so a session token sent there would travel in the clear`);
|
|
8349
|
+
this.origin = origin;
|
|
8350
|
+
this.name = "InsecureOriginError";
|
|
8351
|
+
}
|
|
8352
|
+
origin;
|
|
8353
|
+
};
|
|
8354
|
+
HttpPublishClient = class {
|
|
8355
|
+
origin;
|
|
8356
|
+
token;
|
|
8357
|
+
send;
|
|
8358
|
+
constructor(options) {
|
|
8359
|
+
this.origin = options.origin.replace(/\/+$/, "");
|
|
8360
|
+
if (!isSecureOrigin(this.origin)) throw new InsecureOriginError(this.origin);
|
|
8361
|
+
this.token = options.token;
|
|
8362
|
+
this.send = options.fetch ?? globalThis.fetch;
|
|
8363
|
+
}
|
|
8364
|
+
acceptTerms(input) {
|
|
8365
|
+
return this.json("POST", "/api/consent", input);
|
|
8366
|
+
}
|
|
8367
|
+
begin(input) {
|
|
8368
|
+
return this.json("POST", "/api/publications", input, true);
|
|
8369
|
+
}
|
|
8370
|
+
async upload(input) {
|
|
8371
|
+
const encoded = input.relPath.split("/").map(encodeURIComponent).join("/");
|
|
8372
|
+
return this.request("PUT", `/api/publications/${encodeURIComponent(input.publicationId)}/objects/${encoded}`, {
|
|
8373
|
+
body: input.bytes,
|
|
8374
|
+
contentType: "application/octet-stream",
|
|
8375
|
+
retryable: true
|
|
8376
|
+
});
|
|
8377
|
+
}
|
|
8378
|
+
commit(input) {
|
|
8379
|
+
return this.json("POST", `/api/publications/${encodeURIComponent(input.publicationId)}/commit`, {}, true);
|
|
8380
|
+
}
|
|
8381
|
+
issueShareLink(input) {
|
|
8382
|
+
return this.json("POST", `/api/publications/${encodeURIComponent(input.publicationId)}/shares`, {
|
|
8383
|
+
expiresAt: input.expiresAt,
|
|
8384
|
+
recipientEmail: input.recipientEmail
|
|
8385
|
+
});
|
|
8386
|
+
}
|
|
8387
|
+
listShareLinks(input) {
|
|
8388
|
+
return this.request("GET", `/api/publications/${encodeURIComponent(input.publicationId)}/shares`, {
|
|
8389
|
+
body: "",
|
|
8390
|
+
contentType: "application/json",
|
|
8391
|
+
retryable: true
|
|
8392
|
+
});
|
|
8393
|
+
}
|
|
8394
|
+
revokeShareLink(input) {
|
|
8395
|
+
return this.request("DELETE", `/api/publications/${encodeURIComponent(input.publicationId)}/shares/${encodeURIComponent(input.shareLinkId)}`, {
|
|
8396
|
+
body: "",
|
|
8397
|
+
contentType: "application/json",
|
|
8398
|
+
retryable: true
|
|
8399
|
+
});
|
|
8400
|
+
}
|
|
8401
|
+
json(method, pathname, body, retryable = false) {
|
|
8402
|
+
return this.request(method, pathname, { body: JSON.stringify(body), contentType: "application/json", retryable });
|
|
8403
|
+
}
|
|
8404
|
+
async request(method, pathname, init) {
|
|
8405
|
+
let response;
|
|
8406
|
+
let lastError = "";
|
|
8407
|
+
const attempts = init.retryable === true ? RETRIES : 1;
|
|
8408
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
8409
|
+
try {
|
|
8410
|
+
response = await this.send(`${this.origin}${pathname}`, {
|
|
8411
|
+
method,
|
|
8412
|
+
headers: { authorization: `Bearer ${this.token}`, "content-type": init.contentType },
|
|
8413
|
+
// GET and DELETE carry nothing. `fetch` THROWS on a GET with a
|
|
8414
|
+
// body — even an empty string — so this is a hard requirement
|
|
8415
|
+
// rather than tidiness.
|
|
8416
|
+
...method === "GET" || method === "HEAD" || init.body === "" ? {} : { body: init.body },
|
|
8417
|
+
// A publish that hangs forever is worse than one that fails:
|
|
8418
|
+
// the user cannot tell it from slow, and there is nothing to
|
|
8419
|
+
// act on. Node's fetch has no default timeout at all.
|
|
8420
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
8421
|
+
});
|
|
8422
|
+
} catch (error) {
|
|
8423
|
+
lastError = error.message;
|
|
8424
|
+
if (attempt >= attempts) {
|
|
8425
|
+
return {
|
|
8426
|
+
ok: false,
|
|
8427
|
+
status: 0,
|
|
8428
|
+
refusal: `could not reach ${this.origin} after ${String(attempt)} ${attempt === 1 ? "try" : "tries"}: ${lastError}`
|
|
8429
|
+
};
|
|
8430
|
+
}
|
|
8431
|
+
await delay(BACKOFF_MS * attempt);
|
|
8432
|
+
continue;
|
|
8433
|
+
}
|
|
8434
|
+
if (response.status >= 500 && attempt < attempts) {
|
|
8435
|
+
await delay(BACKOFF_MS * attempt);
|
|
8436
|
+
continue;
|
|
8437
|
+
}
|
|
8438
|
+
break;
|
|
8439
|
+
}
|
|
8440
|
+
const text = await response.text();
|
|
8441
|
+
let parsed;
|
|
8442
|
+
try {
|
|
8443
|
+
parsed = JSON.parse(text);
|
|
8444
|
+
} catch {
|
|
8445
|
+
return {
|
|
8446
|
+
ok: false,
|
|
8447
|
+
status: response.status,
|
|
8448
|
+
refusal: `${this.origin} answered with ${String(response.status)} and something that is not JSON, so it is probably not a Tendril portal`
|
|
8449
|
+
};
|
|
8450
|
+
}
|
|
8451
|
+
const record = parsed ?? {};
|
|
8452
|
+
if (response.ok) return { ok: true, value: parsed };
|
|
8453
|
+
return {
|
|
8454
|
+
ok: false,
|
|
8455
|
+
status: response.status,
|
|
8456
|
+
refusal: typeof record["refusal"] === "string" ? record["refusal"] : `the portal refused with ${String(response.status)}`,
|
|
8457
|
+
...Array.isArray(record["detail"]) ? { detail: record["detail"] } : {},
|
|
8458
|
+
...Array.isArray(record["missing"]) ? { missing: record["missing"] } : {},
|
|
8459
|
+
...isRecord(record["needsConsent"]) ? { needsConsent: record["needsConsent"] } : {},
|
|
8460
|
+
...isRecord(record["needsConfirmation"]) ? { needsConfirmation: record["needsConfirmation"] } : {}
|
|
8461
|
+
};
|
|
8462
|
+
}
|
|
8463
|
+
};
|
|
8464
|
+
REQUEST_TIMEOUT_MS = 12e4;
|
|
8465
|
+
RETRIES = 3;
|
|
8466
|
+
BACKOFF_MS = 500;
|
|
8467
|
+
}
|
|
8468
|
+
});
|
|
8469
|
+
|
|
8062
8470
|
// packages/cli/src/entitlement.ts
|
|
8063
|
-
import { chmodSync, existsSync as
|
|
8471
|
+
import { chmodSync as chmodSync2, existsSync as existsSync21, mkdirSync as mkdirSync5, readFileSync as readFileSync19, writeFileSync as writeFileSync8 } from "node:fs";
|
|
8064
8472
|
import crypto from "node:crypto";
|
|
8065
|
-
import
|
|
8066
|
-
import
|
|
8473
|
+
import os5 from "node:os";
|
|
8474
|
+
import path27 from "node:path";
|
|
8067
8475
|
function entitlementPath() {
|
|
8068
|
-
return process.env["TENDRIL_ENTITLEMENT_PATH"] ??
|
|
8476
|
+
return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path27.join(os5.homedir(), ".tendril", "entitlement.json");
|
|
8069
8477
|
}
|
|
8070
8478
|
function readStoredEntitlement(file = entitlementPath()) {
|
|
8071
|
-
if (!
|
|
8479
|
+
if (!existsSync21(file)) return void 0;
|
|
8072
8480
|
try {
|
|
8073
|
-
const parsed = JSON.parse(
|
|
8481
|
+
const parsed = JSON.parse(readFileSync19(file, "utf8"));
|
|
8074
8482
|
if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
|
|
8075
8483
|
return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
|
|
8076
8484
|
} catch {
|
|
@@ -8078,10 +8486,10 @@ function readStoredEntitlement(file = entitlementPath()) {
|
|
|
8078
8486
|
}
|
|
8079
8487
|
}
|
|
8080
8488
|
function writeStoredEntitlement(stored, file = entitlementPath()) {
|
|
8081
|
-
|
|
8082
|
-
|
|
8489
|
+
mkdirSync5(path27.dirname(file), { recursive: true });
|
|
8490
|
+
writeFileSync8(file, `${JSON.stringify(stored, null, 2)}
|
|
8083
8491
|
`);
|
|
8084
|
-
|
|
8492
|
+
chmodSync2(file, 384);
|
|
8085
8493
|
}
|
|
8086
8494
|
function parseEntitlementToken(token) {
|
|
8087
8495
|
if (!token.startsWith(ENT_PREFIX)) return { error: "not a tendril entitlement token" };
|
|
@@ -8163,9 +8571,9 @@ var init_entitlement = __esm({
|
|
|
8163
8571
|
|
|
8164
8572
|
// packages/cli/src/commands/doctor.ts
|
|
8165
8573
|
import { spawnSync } from "node:child_process";
|
|
8166
|
-
import { existsSync as
|
|
8167
|
-
import
|
|
8168
|
-
import
|
|
8574
|
+
import { existsSync as existsSync22, readFileSync as readFileSync20, readdirSync as readdirSync8 } from "node:fs";
|
|
8575
|
+
import os6 from "node:os";
|
|
8576
|
+
import path28 from "node:path";
|
|
8169
8577
|
function withDeadline(work, ms) {
|
|
8170
8578
|
return Promise.race([
|
|
8171
8579
|
work,
|
|
@@ -8225,19 +8633,19 @@ async function runDoctorChecks(options) {
|
|
|
8225
8633
|
remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
|
|
8226
8634
|
});
|
|
8227
8635
|
}
|
|
8228
|
-
const fontManifest =
|
|
8636
|
+
const fontManifest = path28.join(fontCacheDir(), "manifest.json");
|
|
8229
8637
|
checks.push(
|
|
8230
|
-
|
|
8638
|
+
existsSync22(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync20(fontManifest, "utf8")).length} faces)` } : {
|
|
8231
8639
|
name: "font-cache",
|
|
8232
8640
|
ok: true,
|
|
8233
8641
|
detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
|
|
8234
8642
|
remediation: `Nothing to do now: \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` fetches exactly what a recording declares, and generate/verify name that command \u2014 with the set filled in \u2014 when they need it.`
|
|
8235
8643
|
}
|
|
8236
8644
|
);
|
|
8237
|
-
const pluginRoot =
|
|
8238
|
-
if (
|
|
8645
|
+
const pluginRoot = path28.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
|
|
8646
|
+
if (existsSync22(pluginRoot)) {
|
|
8239
8647
|
try {
|
|
8240
|
-
const versions =
|
|
8648
|
+
const versions = readdirSync8(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
|
|
8241
8649
|
const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
|
|
8242
8650
|
if (newest !== void 0) {
|
|
8243
8651
|
const skewed = versionIsNewer(newest, cliVersion());
|
|
@@ -8271,13 +8679,52 @@ async function runDoctorChecks(options) {
|
|
|
8271
8679
|
checks.push(
|
|
8272
8680
|
ent.ok ? ent.mode === "pre-launch" ? { name: "entitlement", ok: true, detail: "pre-launch build \u2014 entitlement gates present but unarmed (no public key shipped); record/generate run freely" } : { name: "entitlement", ok: true, detail: `active plan "${ent.claims.plan}" until ${new Date(ent.claims.exp).toISOString().slice(0, 10)}${ent.stale ? " (stale \u2014 renews at next opportunity)" : ""}` } : { name: "entitlement", ok: false, detail: ent.error, remediation: ent.remediation }
|
|
8273
8681
|
);
|
|
8682
|
+
checks.push(await portalSessionCheck(options.fetchImpl ?? fetch));
|
|
8274
8683
|
const figmaToken = resolveCredential("FIGMA_TOKEN");
|
|
8275
8684
|
checks.push({
|
|
8276
8685
|
name: "figma-pat",
|
|
8277
8686
|
ok: true,
|
|
8278
8687
|
detail: figmaToken ? "FIGMA_TOKEN configured but unused \u2014 recording runs over the Figma MCP transport" : "FIGMA_TOKEN not set \u2014 not needed; recording runs over the Figma MCP transport"
|
|
8279
8688
|
});
|
|
8280
|
-
return {
|
|
8689
|
+
return {
|
|
8690
|
+
ok: checks.filter((c) => c.name !== "figma-pat" && c.name !== "openrouter-key" && c.name !== "path-skew" && c.name !== "portal-session").every((c) => c.ok),
|
|
8691
|
+
checks
|
|
8692
|
+
};
|
|
8693
|
+
}
|
|
8694
|
+
async function portalSessionCheck(fetchImpl) {
|
|
8695
|
+
const stored = readStoredSession();
|
|
8696
|
+
const connectHint = `Run ${tendrilCommand("login")} and approve in your browser (agents: call the tendril_login tool, relay the link and code, then tendril_login_wait) \u2014 or mint a token under "Connect your CLI" in your portal library and use ${tendrilCommand("login --paste")}.`;
|
|
8697
|
+
if (stored === void 0) {
|
|
8698
|
+
return { name: "portal-session", ok: true, detail: `not signed in to a portal \u2014 publishing needs it, verification never does. ${connectHint}` };
|
|
8699
|
+
}
|
|
8700
|
+
let response;
|
|
8701
|
+
try {
|
|
8702
|
+
const ctl = new AbortController();
|
|
8703
|
+
const timer = setTimeout(() => ctl.abort(), 5e3);
|
|
8704
|
+
timer.unref?.();
|
|
8705
|
+
response = await fetchImpl(`${stored.origin}/api/whoami`, { headers: { authorization: `Bearer ${stored.token}` }, signal: ctl.signal });
|
|
8706
|
+
clearTimeout(timer);
|
|
8707
|
+
} catch {
|
|
8708
|
+
return { name: "portal-session", ok: true, detail: `${stored.origin} did not answer \u2014 the stored session could not be checked. Publishing will say so precisely if it is actually dead.` };
|
|
8709
|
+
}
|
|
8710
|
+
if (!response.ok) {
|
|
8711
|
+
return {
|
|
8712
|
+
name: "portal-session",
|
|
8713
|
+
ok: false,
|
|
8714
|
+
detail: `the session stored for ${stored.origin} no longer works there (${String(response.status)}) \u2014 it was revoked or expired`,
|
|
8715
|
+
remediation: connectHint
|
|
8716
|
+
};
|
|
8717
|
+
}
|
|
8718
|
+
const body = await response.json();
|
|
8719
|
+
const email = typeof body.email === "string" && body.email !== "" ? ` as ${body.email}` : "";
|
|
8720
|
+
const expiry = typeof body.sessionExpiresAt === "string" ? body.sessionExpiresAt : null;
|
|
8721
|
+
const daysLeft = expiry === null ? null : Math.floor((Date.parse(expiry) - Date.now()) / 864e5);
|
|
8722
|
+
return {
|
|
8723
|
+
name: "portal-session",
|
|
8724
|
+
ok: true,
|
|
8725
|
+
detail: `signed in to ${stored.origin}${email}${expiry === null ? "" : `, session good until ${expiry.slice(0, 10)}`}`,
|
|
8726
|
+
...daysLeft !== null && daysLeft <= 7 ? { remediation: `That is ${String(daysLeft)} day(s) away \u2014 run ${tendrilCommand("login")} soon for a fresh session.` } : {}
|
|
8727
|
+
};
|
|
8281
8728
|
}
|
|
8282
8729
|
function probeVersion(binary) {
|
|
8283
8730
|
const windowsShim = /\.(cmd|bat)$/i.test(binary);
|
|
@@ -8352,6 +8799,7 @@ var init_doctor = __esm({
|
|
|
8352
8799
|
init_environment();
|
|
8353
8800
|
init_invocation();
|
|
8354
8801
|
init_output();
|
|
8802
|
+
init_publish_client();
|
|
8355
8803
|
init_entitlement();
|
|
8356
8804
|
DEFAULT_MCP_URL = "http://127.0.0.1:3845/mcp";
|
|
8357
8805
|
DOCTOR_DESCRIPTION = {
|
|
@@ -9467,8 +9915,8 @@ var init_engine_curated = __esm({
|
|
|
9467
9915
|
});
|
|
9468
9916
|
|
|
9469
9917
|
// packages/generate/src/loop.ts
|
|
9470
|
-
import { existsSync as
|
|
9471
|
-
import
|
|
9918
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync22, renameSync, writeFileSync as writeFileSync10 } from "node:fs";
|
|
9919
|
+
import path31 from "node:path";
|
|
9472
9920
|
import { z as z13 } from "zod";
|
|
9473
9921
|
function objective(scores, behaviors) {
|
|
9474
9922
|
const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
|
|
@@ -9510,9 +9958,9 @@ ${absentLines.join("\n")}` : ""}
|
|
|
9510
9958
|
Fix the FAIL configs (region coordinates are in the recorded screenshot's frame; ink<1 means recorded foreground pixels your render does not cover). Reading the pair: ink credits a recorded pixel when ANY render ink lands within 1px of it, so ink=1 proves coverage \u2014 not presence, shape, or colour. A wrong-shaped mark over the right region, or a wrong-coloured one that is still ink, scores 1.000; and a recorded mark within ~30 channel-sum of the backdrop (#f6f6f6 on white is 27) is not ink at all, so it can be absent at ink 1.000 with no MISSING line. With ink=1 AND low sim, coverage held while pixels differ, so start with the TWO causes below \u2014 but never read ink=1 as "nothing is missing": confirm every faint or small recorded mark (hairlines, dividers, low-contrast controls) exists in your render. GEOMETRY: wrong position/size/radius; the diff shows shifted edges and bands. WRONG TEXT WEIGHT OR FACE: the diff shows a uniform haze over glyph runs; CSS binds the font FAMILY before the weight, a later family in the stack is never consulted for a weight the first one lacks, and font-synthesis: none rules out faux-bold \u2014 so a stack led by a family the kit holds at one weight renders every other weight in that face, silently. Check the font stack against the kit's cached faces before concluding geometry is at fault. LOW ink with high sim means recorded marks are absent or painted invisibly. Do not regress PASS configs. ${mode === "files" ? "Update the files in your candidate directory and re-run the score." : "Reply with the complete corrected files in the same FILE format."}`;
|
|
9511
9959
|
}
|
|
9512
9960
|
function archivePriorRun(outDir) {
|
|
9513
|
-
if (!
|
|
9961
|
+
if (!existsSync24(path31.join(outDir, "run-log.json")) && !existsSync24(path31.join(outDir, "loop-state.json"))) return void 0;
|
|
9514
9962
|
let n = 1;
|
|
9515
|
-
while (
|
|
9963
|
+
while (existsSync24(`${outDir}-prev-${n}`)) n += 1;
|
|
9516
9964
|
renameSync(outDir, `${outDir}-prev-${n}`);
|
|
9517
9965
|
return `${outDir}-prev-${n}`;
|
|
9518
9966
|
}
|
|
@@ -9521,14 +9969,14 @@ async function runEngineLoop(opts) {
|
|
|
9521
9969
|
const plateau = opts.plateau ?? 2;
|
|
9522
9970
|
const progress = opts.onProgress ?? (() => {
|
|
9523
9971
|
});
|
|
9524
|
-
const statePath =
|
|
9525
|
-
const resuming = opts.resume === true &&
|
|
9972
|
+
const statePath = path31.join(opts.outDir, "loop-state.json");
|
|
9973
|
+
const resuming = opts.resume === true && existsSync24(statePath);
|
|
9526
9974
|
if (!resuming) {
|
|
9527
9975
|
const archived = archivePriorRun(opts.outDir);
|
|
9528
9976
|
if (archived !== void 0) progress(`previous run archived to ${archived}`);
|
|
9529
9977
|
}
|
|
9530
|
-
|
|
9531
|
-
const scratch =
|
|
9978
|
+
mkdirSync7(opts.outDir, { recursive: true });
|
|
9979
|
+
const scratch = path31.join(opts.outDir, ".candidate");
|
|
9532
9980
|
let attempts = [];
|
|
9533
9981
|
let log = [];
|
|
9534
9982
|
let best;
|
|
@@ -9536,7 +9984,7 @@ async function runEngineLoop(opts) {
|
|
|
9536
9984
|
let nonAccepted = 0;
|
|
9537
9985
|
let stopReason = "max-iterations";
|
|
9538
9986
|
if (resuming) {
|
|
9539
|
-
const restored = LoopStateSchema.parse(JSON.parse(
|
|
9987
|
+
const restored = LoopStateSchema.parse(JSON.parse(readFileSync22(statePath, "utf8")));
|
|
9540
9988
|
attempts = restored.attempts;
|
|
9541
9989
|
log = restored.iterations;
|
|
9542
9990
|
spentUsd = restored.spentUsd;
|
|
@@ -9551,12 +9999,12 @@ async function runEngineLoop(opts) {
|
|
|
9551
9999
|
progress(`resumed: ${log.length} iteration(s), $${spentUsd.toFixed(3)} spent, best pass=${best?.objective[0] ?? 0}`);
|
|
9552
10000
|
}
|
|
9553
10001
|
const persist = () => {
|
|
9554
|
-
|
|
10002
|
+
writeFileSync10(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
|
|
9555
10003
|
`);
|
|
9556
10004
|
};
|
|
9557
10005
|
const writeCandidate = (files) => {
|
|
9558
|
-
|
|
9559
|
-
for (const [name, content] of Object.entries(files))
|
|
10006
|
+
mkdirSync7(scratch, { recursive: true });
|
|
10007
|
+
for (const [name, content] of Object.entries(files)) writeFileSync10(path31.join(scratch, name), content);
|
|
9560
10008
|
};
|
|
9561
10009
|
const scoreCandidate = async (candidate, iter, usd, modelMs) => {
|
|
9562
10010
|
writeCandidate(candidate.files);
|
|
@@ -9614,8 +10062,8 @@ async function runEngineLoop(opts) {
|
|
|
9614
10062
|
const usd = candidate.usage?.usd ?? 0;
|
|
9615
10063
|
spentUsd += usd;
|
|
9616
10064
|
if (candidate.raw !== void 0) {
|
|
9617
|
-
|
|
9618
|
-
|
|
10065
|
+
mkdirSync7(path31.join(opts.outDir, "responses"), { recursive: true });
|
|
10066
|
+
writeFileSync10(path31.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
|
|
9619
10067
|
}
|
|
9620
10068
|
if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
|
|
9621
10069
|
const finish = candidate.usage?.finishReason ?? "?";
|
|
@@ -9641,10 +10089,10 @@ async function runEngineLoop(opts) {
|
|
|
9641
10089
|
}
|
|
9642
10090
|
}
|
|
9643
10091
|
}
|
|
9644
|
-
if (best !== void 0) for (const [name, content] of Object.entries(best.files))
|
|
10092
|
+
if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync10(path31.join(opts.outDir, name), content);
|
|
9645
10093
|
const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
|
|
9646
|
-
|
|
9647
|
-
|
|
10094
|
+
writeFileSync10(
|
|
10095
|
+
path31.join(opts.outDir, "run-log.json"),
|
|
9648
10096
|
`${JSON.stringify(
|
|
9649
10097
|
{
|
|
9650
10098
|
...opts.meta,
|
|
@@ -9711,8 +10159,8 @@ var init_loop2 = __esm({
|
|
|
9711
10159
|
});
|
|
9712
10160
|
|
|
9713
10161
|
// packages/generate/src/brief.ts
|
|
9714
|
-
import { existsSync as
|
|
9715
|
-
import
|
|
10162
|
+
import { existsSync as existsSync25, readFileSync as readFileSync23 } from "node:fs";
|
|
10163
|
+
import path32 from "node:path";
|
|
9716
10164
|
import { PNG as PNG3 } from "pngjs";
|
|
9717
10165
|
function singleAxes2(name) {
|
|
9718
10166
|
const parsed = parseVariantAxes(name);
|
|
@@ -10152,15 +10600,15 @@ function authorBehaviors(api, extras = {}) {
|
|
|
10152
10600
|
};
|
|
10153
10601
|
}
|
|
10154
10602
|
function envelopeText(file) {
|
|
10155
|
-
return envelopeFirstTextPart(JSON.parse(
|
|
10603
|
+
return envelopeFirstTextPart(JSON.parse(readFileSync23(file, "utf8")));
|
|
10156
10604
|
}
|
|
10157
10605
|
function metadataText(file) {
|
|
10158
|
-
return envelopeTextContent(JSON.parse(
|
|
10606
|
+
return envelopeTextContent(JSON.parse(readFileSync23(file, "utf8")));
|
|
10159
10607
|
}
|
|
10160
10608
|
function dismissEvidence(setDir, repSlugs) {
|
|
10161
10609
|
for (const slug of repSlugs) {
|
|
10162
|
-
const f =
|
|
10163
|
-
if (!
|
|
10610
|
+
const f = path32.join(setDir, slug, "get_design_context.json");
|
|
10611
|
+
if (!existsSync25(f)) continue;
|
|
10164
10612
|
const text = envelopeText(f);
|
|
10165
10613
|
const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
|
|
10166
10614
|
if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
|
|
@@ -10187,9 +10635,9 @@ function dismissEvidence(setDir, repSlugs) {
|
|
|
10187
10635
|
const glyphIsTheComponent = (() => {
|
|
10188
10636
|
const slugToCheck = vis.visibleIn[0];
|
|
10189
10637
|
if (slugToCheck === void 0) return false;
|
|
10190
|
-
const metaFile =
|
|
10638
|
+
const metaFile = path32.join(setDir, slugToCheck, "get_metadata.json");
|
|
10191
10639
|
try {
|
|
10192
|
-
const root = parseMetadataStructure(envelopeTextContent(JSON.parse(
|
|
10640
|
+
const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync23(metaFile, "utf8"))));
|
|
10193
10641
|
if (root.children.length !== 1) return false;
|
|
10194
10642
|
const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
|
|
10195
10643
|
return contains(root.children[0]);
|
|
@@ -10209,10 +10657,10 @@ function dismissEvidence(setDir, repSlugs) {
|
|
|
10209
10657
|
return void 0;
|
|
10210
10658
|
}
|
|
10211
10659
|
function recordedReferencePng(setDir, slug) {
|
|
10212
|
-
const f =
|
|
10213
|
-
if (!
|
|
10660
|
+
const f = path32.join(setDir, slug, "get_screenshot.json");
|
|
10661
|
+
if (!existsSync25(f)) return void 0;
|
|
10214
10662
|
try {
|
|
10215
|
-
const env = JSON.parse(
|
|
10663
|
+
const env = JSON.parse(readFileSync23(f, "utf8")).content.find((c) => c.type === "image");
|
|
10216
10664
|
return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
|
|
10217
10665
|
} catch {
|
|
10218
10666
|
return void 0;
|
|
@@ -10253,7 +10701,7 @@ function recordedFontNeeds(setDir, opts = {}) {
|
|
|
10253
10701
|
for (const m of text.matchAll(/font-\['([^':\]]+)(?::([^'\]]+))?'/g)) famAdd(m[1], m[2] === void 0 ? void 0 : styleWeight(m[2]));
|
|
10254
10702
|
for (const m of text.matchAll(/family-name:var\([^,)]*,\s*'([^':\]]+)(?::([^'\]]+))?'/g)) famAdd(m[1], m[2] === void 0 ? void 0 : styleWeight(m[2]));
|
|
10255
10703
|
for (const m of text.matchAll(/\bfont-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)\b/g)) unpaired.add(STYLE_WEIGHTS2[m[1]]);
|
|
10256
|
-
for (const m of text.matchAll(/font-weight:\s*([1-9]
|
|
10704
|
+
for (const m of text.matchAll(/font-weight:\s*([1-9]\d{2})\b/g)) unpaired.add(Number(m[1]));
|
|
10257
10705
|
};
|
|
10258
10706
|
const fromDefs = (text) => {
|
|
10259
10707
|
let defs;
|
|
@@ -10271,15 +10719,34 @@ function recordedFontNeeds(setDir, opts = {}) {
|
|
|
10271
10719
|
}
|
|
10272
10720
|
if (/^[A-Za-z][A-Za-z0-9 ]{1,39}$/.test(v.trim())) famAdd(v.trim());
|
|
10273
10721
|
}
|
|
10722
|
+
fromFontCalls(defs);
|
|
10274
10723
|
};
|
|
10724
|
+
function fromFontCalls(defs) {
|
|
10725
|
+
for (const v of Object.values(defs)) {
|
|
10726
|
+
if (typeof v !== "string") continue;
|
|
10727
|
+
const call = /\bFont\(\s*family:\s*"?([^",]+)"?\s*,([^)]*)\)/.exec(v);
|
|
10728
|
+
if (call === null) continue;
|
|
10729
|
+
const rest = call[2];
|
|
10730
|
+
const explicit = /\bweight:\s*([1-9]\d{2})\b/.exec(rest);
|
|
10731
|
+
const styleRef = /\bstyle:\s*([^,)]+)/.exec(rest);
|
|
10732
|
+
let weight = explicit === null ? void 0 : Number(explicit[1]);
|
|
10733
|
+
if (weight === void 0 && styleRef !== null) {
|
|
10734
|
+
const ref = styleRef[1].trim().replace(/^"|"$/g, "");
|
|
10735
|
+
weight = styleWeight((typeof defs[ref] === "string" ? defs[ref] : ref).trim());
|
|
10736
|
+
}
|
|
10737
|
+
const famRef = call[1].trim();
|
|
10738
|
+
const family = typeof defs[famRef] === "string" ? defs[famRef].trim() : famRef;
|
|
10739
|
+
if (/^[A-Za-z][A-Za-z0-9 ]{1,39}$/.test(family)) famAdd(family, weight);
|
|
10740
|
+
}
|
|
10741
|
+
}
|
|
10275
10742
|
const manifest = loadManifest(setDir);
|
|
10276
|
-
const setDefs =
|
|
10277
|
-
if (
|
|
10743
|
+
const setDefs = path32.join(setDir, "get_variable_defs.json");
|
|
10744
|
+
if (existsSync25(setDefs)) fromDefs(envelopeText(setDefs));
|
|
10278
10745
|
for (const rep of manifest.reps) {
|
|
10279
|
-
const ctx =
|
|
10280
|
-
if (
|
|
10281
|
-
const defs =
|
|
10282
|
-
if (
|
|
10746
|
+
const ctx = path32.join(setDir, rep.slug, "get_design_context.json");
|
|
10747
|
+
if (existsSync25(ctx)) fromEmission(envelopeText(ctx));
|
|
10748
|
+
const defs = path32.join(setDir, rep.slug, "get_variable_defs.json");
|
|
10749
|
+
if (existsSync25(defs)) fromDefs(envelopeText(defs));
|
|
10283
10750
|
}
|
|
10284
10751
|
return [...byFamily.entries()].map(([family, paired]) => {
|
|
10285
10752
|
const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
|
|
@@ -10290,10 +10757,10 @@ function symbolFontGlyphCount(setDir, reps) {
|
|
|
10290
10757
|
const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
|
|
10291
10758
|
const glyphs = /* @__PURE__ */ new Set();
|
|
10292
10759
|
for (const rep of reps) {
|
|
10293
|
-
const file =
|
|
10294
|
-
if (!
|
|
10760
|
+
const file = path32.join(setDir, rep, "get_metadata.json");
|
|
10761
|
+
if (!existsSync25(file)) continue;
|
|
10295
10762
|
try {
|
|
10296
|
-
const text = JSON.parse(
|
|
10763
|
+
const text = JSON.parse(readFileSync23(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
|
|
10297
10764
|
for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
|
|
10298
10765
|
const name = decodeXmlEntities(m[1]).replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
|
|
10299
10766
|
if (PUA.test(name)) glyphs.add(name);
|
|
@@ -10319,8 +10786,8 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
10319
10786
|
const propRep = [];
|
|
10320
10787
|
const perRep = [];
|
|
10321
10788
|
for (const slug of repSlugs) {
|
|
10322
|
-
const f =
|
|
10323
|
-
if (!
|
|
10789
|
+
const f = path32.join(setDir, slug, "get_design_context.json");
|
|
10790
|
+
if (!existsSync25(f)) continue;
|
|
10324
10791
|
const code = envelopeText(f);
|
|
10325
10792
|
const props = /* @__PURE__ */ new Map();
|
|
10326
10793
|
for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
|
|
@@ -10346,8 +10813,8 @@ function recordedTextSlots(setDir, repSlugs) {
|
|
|
10346
10813
|
const axisValuesBySlug = /* @__PURE__ */ new Map();
|
|
10347
10814
|
const valuesByAxis = /* @__PURE__ */ new Map();
|
|
10348
10815
|
for (const slug of repSlugs) {
|
|
10349
|
-
const metaFile =
|
|
10350
|
-
if (!
|
|
10816
|
+
const metaFile = path32.join(setDir, slug, "get_metadata.json");
|
|
10817
|
+
if (!existsSync25(metaFile)) continue;
|
|
10351
10818
|
const name = symbolName(metadataText(metaFile));
|
|
10352
10819
|
if (name === void 0) continue;
|
|
10353
10820
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -10462,8 +10929,8 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
10462
10929
|
const poses = [];
|
|
10463
10930
|
const missing = [];
|
|
10464
10931
|
for (const rep of manifest.reps) {
|
|
10465
|
-
const metaFile =
|
|
10466
|
-
if (!
|
|
10932
|
+
const metaFile = path32.join(setDir, rep.slug, "get_metadata.json");
|
|
10933
|
+
if (!existsSync25(metaFile)) {
|
|
10467
10934
|
missing.push(rep.slug);
|
|
10468
10935
|
continue;
|
|
10469
10936
|
}
|
|
@@ -10477,8 +10944,8 @@ function authorTaskFromSet(setDir, opts = {}) {
|
|
|
10477
10944
|
if (missing.length > 0) {
|
|
10478
10945
|
throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
|
|
10479
10946
|
}
|
|
10480
|
-
const setMeta =
|
|
10481
|
-
const latticeNames = manifest.latticeNames ?? (
|
|
10947
|
+
const setMeta = path32.join(setDir, "get_metadata.json");
|
|
10948
|
+
const latticeNames = manifest.latticeNames ?? (existsSync25(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
|
|
10482
10949
|
const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
|
|
10483
10950
|
const recordedFonts = recordedFontFamilies(setDir);
|
|
10484
10951
|
const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
|
|
@@ -10679,17 +11146,17 @@ var init_brief = __esm({
|
|
|
10679
11146
|
});
|
|
10680
11147
|
|
|
10681
11148
|
// packages/generate/src/segments.ts
|
|
10682
|
-
import { existsSync as
|
|
10683
|
-
import
|
|
11149
|
+
import { existsSync as existsSync26, readFileSync as readFileSync24, readdirSync as readdirSync10 } from "node:fs";
|
|
11150
|
+
import path33 from "node:path";
|
|
10684
11151
|
function repText(set, rep, tool) {
|
|
10685
|
-
const env = JSON.parse(
|
|
11152
|
+
const env = JSON.parse(readFileSync24(path33.join(set, rep, `${tool}.json`), "utf8"));
|
|
10686
11153
|
return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
|
|
10687
11154
|
}
|
|
10688
11155
|
function refPngDims(set, rep) {
|
|
10689
|
-
const f =
|
|
10690
|
-
if (!
|
|
11156
|
+
const f = path33.join(set, rep, "get_screenshot.json");
|
|
11157
|
+
if (!existsSync26(f)) return void 0;
|
|
10691
11158
|
try {
|
|
10692
|
-
const env = JSON.parse(
|
|
11159
|
+
const env = JSON.parse(readFileSync24(f, "utf8")).content.find((c) => c.type === "image");
|
|
10693
11160
|
if (env?.data === void 0) return void 0;
|
|
10694
11161
|
const buf = Buffer.from(env.data, "base64");
|
|
10695
11162
|
if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return void 0;
|
|
@@ -10755,20 +11222,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
|
|
|
10755
11222
|
}
|
|
10756
11223
|
function buildSegments(task, mode = "fenced") {
|
|
10757
11224
|
const SET = task.set;
|
|
10758
|
-
let defsRecorded =
|
|
11225
|
+
let defsRecorded = existsSync26(path33.join(SET, "get_variable_defs.json"));
|
|
10759
11226
|
let rawDefs = {};
|
|
10760
|
-
if (
|
|
10761
|
-
const text = envelopeFirstTextPart(JSON.parse(
|
|
11227
|
+
if (existsSync26(path33.join(SET, "get_variable_defs.json"))) {
|
|
11228
|
+
const text = envelopeFirstTextPart(JSON.parse(readFileSync24(path33.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
|
|
10762
11229
|
try {
|
|
10763
11230
|
rawDefs = JSON.parse(text);
|
|
10764
11231
|
} catch {
|
|
10765
11232
|
}
|
|
10766
11233
|
} else {
|
|
10767
11234
|
for (const cfg of task.configs) {
|
|
10768
|
-
const f =
|
|
10769
|
-
if (!
|
|
11235
|
+
const f = path33.join(SET, cfg.rep, "get_variable_defs.json");
|
|
11236
|
+
if (!existsSync26(f)) continue;
|
|
10770
11237
|
defsRecorded = true;
|
|
10771
|
-
const text = envelopeFirstTextPart(JSON.parse(
|
|
11238
|
+
const text = envelopeFirstTextPart(JSON.parse(readFileSync24(f, "utf8"))) || "{}";
|
|
10772
11239
|
try {
|
|
10773
11240
|
for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
|
|
10774
11241
|
} catch {
|
|
@@ -10776,8 +11243,8 @@ function buildSegments(task, mode = "fenced") {
|
|
|
10776
11243
|
}
|
|
10777
11244
|
}
|
|
10778
11245
|
const emissionTexts = task.configs.map((cfg) => {
|
|
10779
|
-
const f =
|
|
10780
|
-
return
|
|
11246
|
+
const f = path33.join(SET, cfg.rep, "get_design_context.json");
|
|
11247
|
+
return existsSync26(f) ? envelopeFirstTextPart(JSON.parse(readFileSync24(f, "utf8"))) : "";
|
|
10781
11248
|
});
|
|
10782
11249
|
const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
|
|
10783
11250
|
const defs = JSON.stringify(map, null, 1);
|
|
@@ -10795,9 +11262,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
|
|
|
10795
11262
|
for (const cfg of task.configs) {
|
|
10796
11263
|
const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
|
|
10797
11264
|
const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
|
|
10798
|
-
const assets =
|
|
11265
|
+
const assets = readdirSync10(path33.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
|
|
10799
11266
|
\`\`\`svg
|
|
10800
|
-
${
|
|
11267
|
+
${readFileSync24(path33.join(SET, cfg.rep, f), "utf8")}
|
|
10801
11268
|
\`\`\``).join("\n");
|
|
10802
11269
|
const refNote = (() => {
|
|
10803
11270
|
const dims = refPngDims(SET, cfg.rep);
|
|
@@ -10833,7 +11300,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
|
|
|
10833
11300
|
} else {
|
|
10834
11301
|
parts.push(`
|
|
10835
11302
|
## Output format
|
|
10836
|
-
Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${
|
|
11303
|
+
Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path33.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
|
|
10837
11304
|
}
|
|
10838
11305
|
return parts.join("\n");
|
|
10839
11306
|
}
|
|
@@ -10900,9 +11367,9 @@ var init_adapter = __esm({
|
|
|
10900
11367
|
});
|
|
10901
11368
|
|
|
10902
11369
|
// packages/generate/src/bundle-emit.ts
|
|
10903
|
-
import { createHash as
|
|
10904
|
-
import { copyFileSync, existsSync as
|
|
10905
|
-
import
|
|
11370
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
11371
|
+
import { copyFileSync, existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync25, readdirSync as readdirSync11, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
11372
|
+
import path34 from "node:path";
|
|
10906
11373
|
function pinFromConfigs(configs) {
|
|
10907
11374
|
const domains = /* @__PURE__ */ new Map();
|
|
10908
11375
|
const kinds = /* @__PURE__ */ new Map();
|
|
@@ -10971,9 +11438,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
10971
11438
|
const notices = [];
|
|
10972
11439
|
const licenseTexts = /* @__PURE__ */ new Map();
|
|
10973
11440
|
for (const face of faces) {
|
|
10974
|
-
const src =
|
|
10975
|
-
const target = `./fonts/${
|
|
10976
|
-
const format = FONT_FORMATS[
|
|
11441
|
+
const src = path34.join(cacheDir, path34.basename(face.file));
|
|
11442
|
+
const target = `./fonts/${path34.basename(face.file)}`;
|
|
11443
|
+
const format = FONT_FORMATS[path34.extname(face.file).toLowerCase()] ?? "truetype";
|
|
10977
11444
|
const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
|
|
10978
11445
|
const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
|
|
10979
11446
|
const license = normalizeFontLicense(face.license);
|
|
@@ -11011,14 +11478,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11011
11478
|
`/* ${decl} */`
|
|
11012
11479
|
);
|
|
11013
11480
|
}
|
|
11014
|
-
} else if (
|
|
11015
|
-
|
|
11016
|
-
copyFileSync(src,
|
|
11481
|
+
} else if (existsSync27(src) && createHash6("sha256").update(readFileSync25(src)).digest("hex") === face.sha256) {
|
|
11482
|
+
mkdirSync8(path34.join(bundleDir, "fonts"), { recursive: true });
|
|
11483
|
+
copyFileSync(src, path34.join(bundleDir, "fonts", path34.basename(face.file)));
|
|
11017
11484
|
licenseTexts.set(terms.file, terms.text);
|
|
11018
11485
|
const upstream = upstreamAttribution(face);
|
|
11019
11486
|
notices.push(
|
|
11020
11487
|
"",
|
|
11021
|
-
`${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${
|
|
11488
|
+
`${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path34.basename(face.file)}`,
|
|
11022
11489
|
` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
|
|
11023
11490
|
` source: ${face.source}`,
|
|
11024
11491
|
` sha256: ${face.sha256}`,
|
|
@@ -11032,9 +11499,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11032
11499
|
}
|
|
11033
11500
|
if (lines.length === 0) return null;
|
|
11034
11501
|
if (notices.length > 0) {
|
|
11035
|
-
const fontsDir =
|
|
11036
|
-
for (const [file, text] of licenseTexts)
|
|
11037
|
-
|
|
11502
|
+
const fontsDir = path34.join(bundleDir, "fonts");
|
|
11503
|
+
for (const [file, text] of licenseTexts) writeFileSync11(path34.join(fontsDir, file), text);
|
|
11504
|
+
writeFileSync11(path34.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
|
|
11038
11505
|
`);
|
|
11039
11506
|
header.push(
|
|
11040
11507
|
"/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
|
|
@@ -11046,10 +11513,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
|
|
|
11046
11513
|
`;
|
|
11047
11514
|
}
|
|
11048
11515
|
function countLatticeSymbols(setDir) {
|
|
11049
|
-
const manifestFile =
|
|
11050
|
-
if (
|
|
11516
|
+
const manifestFile = path34.join(setDir, "recording-set.json");
|
|
11517
|
+
if (existsSync27(manifestFile)) {
|
|
11051
11518
|
try {
|
|
11052
|
-
const stored = JSON.parse(
|
|
11519
|
+
const stored = JSON.parse(readFileSync25(manifestFile, "utf8"));
|
|
11053
11520
|
if (stored.variantScope !== "component-set") return null;
|
|
11054
11521
|
const lattice = stored.latticeNames;
|
|
11055
11522
|
if (lattice !== void 0 && lattice.length > 0) return lattice.length;
|
|
@@ -11057,13 +11524,13 @@ function countLatticeSymbols(setDir) {
|
|
|
11057
11524
|
}
|
|
11058
11525
|
}
|
|
11059
11526
|
const files = [
|
|
11060
|
-
|
|
11061
|
-
...
|
|
11062
|
-
].filter((f) =>
|
|
11527
|
+
path34.join(setDir, "get_metadata.json"),
|
|
11528
|
+
...existsSync27(setDir) ? readdirSync11(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path34.join(setDir, f)) : []
|
|
11529
|
+
].filter((f) => existsSync27(f));
|
|
11063
11530
|
if (files.length === 0) return null;
|
|
11064
11531
|
let count = 0;
|
|
11065
11532
|
for (const f of files) {
|
|
11066
|
-
const text = envelopeTextContent(JSON.parse(
|
|
11533
|
+
const text = envelopeTextContent(JSON.parse(readFileSync25(f, "utf8")));
|
|
11067
11534
|
count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
|
|
11068
11535
|
}
|
|
11069
11536
|
return count > 0 ? count : null;
|
|
@@ -11071,23 +11538,23 @@ function countLatticeSymbols(setDir) {
|
|
|
11071
11538
|
function recordingSetHash(setDir, configs) {
|
|
11072
11539
|
const relPaths = [];
|
|
11073
11540
|
for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
|
|
11074
|
-
if (
|
|
11541
|
+
if (existsSync27(path34.join(setDir, name))) relPaths.push(name);
|
|
11075
11542
|
}
|
|
11076
11543
|
for (const cfg of configs) {
|
|
11077
11544
|
for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
|
|
11078
|
-
if (
|
|
11545
|
+
if (existsSync27(path34.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
|
|
11079
11546
|
}
|
|
11080
|
-
if (
|
|
11081
|
-
for (const asset of
|
|
11547
|
+
if (existsSync27(path34.join(setDir, cfg.rep))) {
|
|
11548
|
+
for (const asset of readdirSync11(path34.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
|
|
11082
11549
|
relPaths.push(`${cfg.rep}/${asset}`);
|
|
11083
11550
|
}
|
|
11084
11551
|
}
|
|
11085
11552
|
}
|
|
11086
11553
|
return hashRecordingSet(
|
|
11087
11554
|
relPaths,
|
|
11088
|
-
(p) => new Uint8Array(
|
|
11555
|
+
(p) => new Uint8Array(readFileSync25(path34.join(setDir, p))),
|
|
11089
11556
|
(chunks) => {
|
|
11090
|
-
const h =
|
|
11557
|
+
const h = createHash6("sha256");
|
|
11091
11558
|
for (const c of chunks) h.update(c);
|
|
11092
11559
|
return h.digest("hex");
|
|
11093
11560
|
}
|
|
@@ -11097,6 +11564,17 @@ function statusOf(s) {
|
|
|
11097
11564
|
const tier = tierOf(s, BARS.cert);
|
|
11098
11565
|
return tier === "certified" && (s.absentInk?.length ?? 0) > 0 ? "pass" : tier;
|
|
11099
11566
|
}
|
|
11567
|
+
function kitIdentity(setDir) {
|
|
11568
|
+
try {
|
|
11569
|
+
const m = loadManifest(setDir);
|
|
11570
|
+
return {
|
|
11571
|
+
...typeof m.figmaFile === "string" ? { figmaFile: m.figmaFile } : {},
|
|
11572
|
+
...typeof m.componentSetNode === "string" ? { componentSetNode: m.componentSetNode } : {}
|
|
11573
|
+
};
|
|
11574
|
+
} catch {
|
|
11575
|
+
return {};
|
|
11576
|
+
}
|
|
11577
|
+
}
|
|
11100
11578
|
function emitBundleV1(opts) {
|
|
11101
11579
|
const substituted = (opts.substitutedFamilies ?? []).length > 0;
|
|
11102
11580
|
const parityFailed = new Set(opts.behaviors.filter((b) => b.id.startsWith("parity:") && !b.pass).map((b) => b.id.slice("parity:".length)));
|
|
@@ -11115,8 +11593,8 @@ function emitBundleV1(opts) {
|
|
|
11115
11593
|
const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
|
|
11116
11594
|
const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
|
|
11117
11595
|
const contract = opts.behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
|
|
11118
|
-
const cssFiles = ["styles.css", "tokens.css"].map((f) =>
|
|
11119
|
-
const families = cssFontFamilies(cssFiles.map((f) =>
|
|
11596
|
+
const cssFiles = ["styles.css", "tokens.css"].map((f) => path34.join(opts.bundleDir, f)).filter((f) => existsSync27(f));
|
|
11597
|
+
const families = cssFontFamilies(cssFiles.map((f) => readFileSync25(f, "utf8")).join("\n"));
|
|
11120
11598
|
const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
|
|
11121
11599
|
family: f.family,
|
|
11122
11600
|
weight: f.weight,
|
|
@@ -11145,12 +11623,21 @@ function emitBundleV1(opts) {
|
|
|
11145
11623
|
// resolvable via verify's --set override).
|
|
11146
11624
|
path: (() => {
|
|
11147
11625
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
11148
|
-
const rel =
|
|
11626
|
+
const rel = path34.relative(base, opts.task.set);
|
|
11149
11627
|
return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
|
|
11150
11628
|
})(),
|
|
11151
11629
|
component: opts.componentName,
|
|
11152
11630
|
...opts.licenseNote !== void 0 ? { licenseNote: opts.licenseNote } : {},
|
|
11153
|
-
hash: recordingSetHash(opts.task.set, opts.task.configs)
|
|
11631
|
+
hash: recordingSetHash(opts.task.set, opts.task.configs),
|
|
11632
|
+
// PROV-1: kit identity, carried through from the recording set.
|
|
11633
|
+
// Captured at plan time and previously dropped here, which left
|
|
11634
|
+
// a published bundle unable to name the kit it derives from —
|
|
11635
|
+
// and the portal's consent is scoped to exactly this file key
|
|
11636
|
+
// (ADR-018 §3), because the right to publish attaches to a kit.
|
|
11637
|
+
// Read defensively: a set planned before these fields existed,
|
|
11638
|
+
// or one built from a handed-in selection, legitimately has
|
|
11639
|
+
// neither, and absent must read as NOT RECORDED.
|
|
11640
|
+
...kitIdentity(opts.task.set)
|
|
11154
11641
|
},
|
|
11155
11642
|
environment: { ...opts.environment, ...(opts.substitutedFamilies ?? []).length > 0 ? { substitutedFamilies: opts.substitutedFamilies } : {} },
|
|
11156
11643
|
coverage: { recordedConfigs: statuses.length, latticeConfigs: lattice },
|
|
@@ -11173,25 +11660,25 @@ function emitBundleV1(opts) {
|
|
|
11173
11660
|
})
|
|
11174
11661
|
};
|
|
11175
11662
|
const written = [];
|
|
11176
|
-
const manifestPath2 =
|
|
11177
|
-
|
|
11663
|
+
const manifestPath2 = path34.join(opts.bundleDir, "component.json");
|
|
11664
|
+
writeFileSync11(manifestPath2, `${JSON.stringify(manifest, null, 2)}
|
|
11178
11665
|
`);
|
|
11179
11666
|
written.push(manifestPath2);
|
|
11180
|
-
const stylesPath =
|
|
11181
|
-
if (
|
|
11667
|
+
const stylesPath = path34.join(opts.bundleDir, "styles.css");
|
|
11668
|
+
if (existsSync27(stylesPath)) {
|
|
11182
11669
|
const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
|
|
11183
|
-
const current =
|
|
11670
|
+
const current = readFileSync25(stylesPath, "utf8");
|
|
11184
11671
|
const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
|
|
11185
|
-
|
|
11672
|
+
writeFileSync11(stylesPath, `${comment}
|
|
11186
11673
|
${stripped}`);
|
|
11187
11674
|
written.push(stylesPath);
|
|
11188
11675
|
}
|
|
11189
|
-
const fontsCssPath =
|
|
11190
|
-
|
|
11191
|
-
|
|
11676
|
+
const fontsCssPath = path34.join(opts.bundleDir, "fonts.css");
|
|
11677
|
+
rmSync4(path34.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
|
|
11678
|
+
rmSync4(fontsCssPath, { force: true });
|
|
11192
11679
|
const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
|
|
11193
11680
|
if (fontsCss !== null) {
|
|
11194
|
-
|
|
11681
|
+
writeFileSync11(fontsCssPath, fontsCss);
|
|
11195
11682
|
written.push(fontsCssPath);
|
|
11196
11683
|
}
|
|
11197
11684
|
const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
|
|
@@ -11619,9 +12106,9 @@ DEALINGS IN THE FONT SOFTWARE.
|
|
|
11619
12106
|
});
|
|
11620
12107
|
|
|
11621
12108
|
// packages/generate/src/compose-pins.ts
|
|
11622
|
-
import { createHash as
|
|
11623
|
-
import { existsSync as
|
|
11624
|
-
import
|
|
12109
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12110
|
+
import { existsSync as existsSync28, readFileSync as readFileSync26, readdirSync as readdirSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
|
|
12111
|
+
import path35 from "node:path";
|
|
11625
12112
|
function bundleDirs(roots, depth = 4) {
|
|
11626
12113
|
const found = [];
|
|
11627
12114
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -11630,31 +12117,31 @@ function bundleDirs(roots, depth = 4) {
|
|
|
11630
12117
|
try {
|
|
11631
12118
|
key = realpathSync3(dir);
|
|
11632
12119
|
} catch {
|
|
11633
|
-
key =
|
|
12120
|
+
key = path35.resolve(dir);
|
|
11634
12121
|
}
|
|
11635
12122
|
if (seen.has(key)) return;
|
|
11636
12123
|
seen.add(key);
|
|
11637
|
-
if (
|
|
12124
|
+
if (existsSync28(path35.join(dir, "component.json"))) {
|
|
11638
12125
|
found.push(key);
|
|
11639
12126
|
return;
|
|
11640
12127
|
}
|
|
11641
12128
|
if (remaining === 0) return;
|
|
11642
12129
|
let entries;
|
|
11643
12130
|
try {
|
|
11644
|
-
entries =
|
|
12131
|
+
entries = readdirSync12(dir);
|
|
11645
12132
|
} catch {
|
|
11646
12133
|
return;
|
|
11647
12134
|
}
|
|
11648
12135
|
for (const e of entries) {
|
|
11649
12136
|
if (e === "node_modules" || e.startsWith(".")) continue;
|
|
11650
|
-
const full =
|
|
12137
|
+
const full = path35.join(dir, e);
|
|
11651
12138
|
try {
|
|
11652
|
-
if (
|
|
12139
|
+
if (statSync4(full).isDirectory()) walk2(full, remaining - 1);
|
|
11653
12140
|
} catch {
|
|
11654
12141
|
}
|
|
11655
12142
|
}
|
|
11656
12143
|
};
|
|
11657
|
-
for (const r of roots) walk2(
|
|
12144
|
+
for (const r of roots) walk2(path35.resolve(r), depth);
|
|
11658
12145
|
return found;
|
|
11659
12146
|
}
|
|
11660
12147
|
function composedPins(hostSet, libraryRoots) {
|
|
@@ -11673,7 +12160,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
11673
12160
|
let pinned = false;
|
|
11674
12161
|
const failures = [];
|
|
11675
12162
|
for (const rel of partnerRels) {
|
|
11676
|
-
const partnerSet =
|
|
12163
|
+
const partnerSet = path35.resolve(hostSet, rel);
|
|
11677
12164
|
let partnerTask;
|
|
11678
12165
|
let partnerManifest;
|
|
11679
12166
|
try {
|
|
@@ -11702,7 +12189,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
11702
12189
|
const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
|
|
11703
12190
|
const matches = candidates.filter((dir) => {
|
|
11704
12191
|
try {
|
|
11705
|
-
const parsed = readBundleManifest(
|
|
12192
|
+
const parsed = readBundleManifest(readFileSync26(path35.join(dir, "component.json"), "utf8"));
|
|
11706
12193
|
return parsed.manifest?.provenance.recordingSet.hash === wantHash;
|
|
11707
12194
|
} catch {
|
|
11708
12195
|
return false;
|
|
@@ -11715,13 +12202,13 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
11715
12202
|
continue;
|
|
11716
12203
|
}
|
|
11717
12204
|
if (matches.length > 1) {
|
|
11718
|
-
failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) =>
|
|
12205
|
+
failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path35.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
|
|
11719
12206
|
continue;
|
|
11720
12207
|
}
|
|
11721
12208
|
const bundleDir = matches[0];
|
|
11722
12209
|
let manifest;
|
|
11723
12210
|
try {
|
|
11724
|
-
manifest = readBundleManifest(
|
|
12211
|
+
manifest = readBundleManifest(readFileSync26(path35.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
11725
12212
|
} catch {
|
|
11726
12213
|
manifest = void 0;
|
|
11727
12214
|
}
|
|
@@ -11738,8 +12225,8 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
11738
12225
|
const moduleFiles = [];
|
|
11739
12226
|
let fileIssue;
|
|
11740
12227
|
for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
|
|
11741
|
-
const file =
|
|
11742
|
-
if (!
|
|
12228
|
+
const file = path35.join(bundleDir, name);
|
|
12229
|
+
if (!existsSync28(file)) {
|
|
11743
12230
|
if (name === manifest.entry || name === "styles.css") {
|
|
11744
12231
|
fileIssue = `${rel}: partner bundle is missing ${name}`;
|
|
11745
12232
|
break;
|
|
@@ -11748,7 +12235,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
11748
12235
|
}
|
|
11749
12236
|
let bytes;
|
|
11750
12237
|
try {
|
|
11751
|
-
bytes =
|
|
12238
|
+
bytes = readFileSync26(file);
|
|
11752
12239
|
} catch {
|
|
11753
12240
|
fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
|
|
11754
12241
|
break;
|
|
@@ -11757,7 +12244,7 @@ function composedPins(hostSet, libraryRoots) {
|
|
|
11757
12244
|
fileIssue = `${rel}: partner file ${name} exceeds the pin size cap (${bytes.byteLength} bytes)`;
|
|
11758
12245
|
break;
|
|
11759
12246
|
}
|
|
11760
|
-
moduleFiles.push({ name, content: bytes.toString("utf8"), sha256:
|
|
12247
|
+
moduleFiles.push({ name, content: bytes.toString("utf8"), sha256: createHash7("sha256").update(bytes).digest("hex") });
|
|
11761
12248
|
}
|
|
11762
12249
|
if (fileIssue !== void 0) {
|
|
11763
12250
|
failures.push(fileIssue);
|
|
@@ -11820,14 +12307,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
|
|
|
11820
12307
|
const checks = [];
|
|
11821
12308
|
let entrySource = "";
|
|
11822
12309
|
try {
|
|
11823
|
-
entrySource =
|
|
12310
|
+
entrySource = readFileSync26(path35.join(candidateDir, hostEntry), "utf8");
|
|
11824
12311
|
} catch {
|
|
11825
12312
|
}
|
|
11826
|
-
const candidateRoot =
|
|
12313
|
+
const candidateRoot = path35.resolve(candidateDir);
|
|
11827
12314
|
for (const pin of pins) {
|
|
11828
12315
|
const dir = composedModuleDir(pin.partnerName);
|
|
11829
|
-
const resolvedDir =
|
|
11830
|
-
if (!resolvedDir.startsWith(candidateRoot +
|
|
12316
|
+
const resolvedDir = path35.resolve(candidateDir, dir);
|
|
12317
|
+
if (!resolvedDir.startsWith(candidateRoot + path35.sep)) {
|
|
11831
12318
|
checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
|
|
11832
12319
|
checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
|
|
11833
12320
|
continue;
|
|
@@ -11838,12 +12325,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
|
|
|
11838
12325
|
wrong.push(`${f.name} refused (not a plain path segment)`);
|
|
11839
12326
|
continue;
|
|
11840
12327
|
}
|
|
11841
|
-
const target =
|
|
11842
|
-
if (!
|
|
12328
|
+
const target = path35.join(candidateDir, dir, f.name);
|
|
12329
|
+
if (!existsSync28(target)) {
|
|
11843
12330
|
wrong.push(`${f.name} missing`);
|
|
11844
12331
|
continue;
|
|
11845
12332
|
}
|
|
11846
|
-
const sha =
|
|
12333
|
+
const sha = createHash7("sha256").update(readFileSync26(target)).digest("hex");
|
|
11847
12334
|
if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
|
|
11848
12335
|
}
|
|
11849
12336
|
checks.push({
|
|
@@ -11868,10 +12355,10 @@ function rootClassesFor(emission, nodeId) {
|
|
|
11868
12355
|
}
|
|
11869
12356
|
function regionOverrides(hostSet, partnerSet, instances) {
|
|
11870
12357
|
const read = (setDir, rep) => {
|
|
11871
|
-
const f =
|
|
11872
|
-
if (!
|
|
12358
|
+
const f = path35.join(setDir, rep, "get_design_context.json");
|
|
12359
|
+
if (!existsSync28(f)) return void 0;
|
|
11873
12360
|
try {
|
|
11874
|
-
return envelopeFirstTextPart(JSON.parse(
|
|
12361
|
+
return envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8")));
|
|
11875
12362
|
} catch {
|
|
11876
12363
|
return void 0;
|
|
11877
12364
|
}
|
|
@@ -11901,7 +12388,7 @@ var init_compose_pins = __esm({
|
|
|
11901
12388
|
init_src4();
|
|
11902
12389
|
init_brief();
|
|
11903
12390
|
init_bundle_emit();
|
|
11904
|
-
composedModuleDir = (partnerName) =>
|
|
12391
|
+
composedModuleDir = (partnerName) => path35.posix.join("composed", partnerName);
|
|
11905
12392
|
safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
|
|
11906
12393
|
MAX_PINNED_FILE_BYTES = 1024 * 1024;
|
|
11907
12394
|
CONTEXT_LAYOUT_TOKENS = /* @__PURE__ */ new Set(["shrink-0", "grow", "flex-1", "basis-0", "self-stretch", "w-full", "h-full"]);
|
|
@@ -11909,8 +12396,8 @@ var init_compose_pins = __esm({
|
|
|
11909
12396
|
});
|
|
11910
12397
|
|
|
11911
12398
|
// packages/generate/src/motion.ts
|
|
11912
|
-
import { existsSync as
|
|
11913
|
-
import
|
|
12399
|
+
import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
|
|
12400
|
+
import path36 from "node:path";
|
|
11914
12401
|
function springProgress(u, bounce) {
|
|
11915
12402
|
const decay = Math.log(100);
|
|
11916
12403
|
if (bounce <= 0) {
|
|
@@ -11981,10 +12468,10 @@ function reportsNoMotion(text) {
|
|
|
11981
12468
|
});
|
|
11982
12469
|
}
|
|
11983
12470
|
function motionTruthFor(setDir) {
|
|
11984
|
-
const file =
|
|
11985
|
-
if (
|
|
12471
|
+
const file = path36.join(setDir, "get_motion_context.json");
|
|
12472
|
+
if (existsSync29(file) && usableEnvelope(file, "get_motion_context").ok) {
|
|
11986
12473
|
try {
|
|
11987
|
-
const text = envelopeTextContent(JSON.parse(
|
|
12474
|
+
const text = envelopeTextContent(JSON.parse(readFileSync27(file, "utf8")));
|
|
11988
12475
|
if (text.trim() === "") return { state: "recorded-empty" };
|
|
11989
12476
|
return reportsNoMotion(text) ? { state: "recorded-no-motion", text } : { state: "recorded", text };
|
|
11990
12477
|
} catch {
|
|
@@ -11997,21 +12484,21 @@ function motionTruthFor(setDir) {
|
|
|
11997
12484
|
}
|
|
11998
12485
|
}
|
|
11999
12486
|
function motionDisclosure(bundleDir, setDir) {
|
|
12000
|
-
const sheets = ["styles.css", "tokens.css"].map((f) =>
|
|
12001
|
-
const composedRoot =
|
|
12487
|
+
const sheets = ["styles.css", "tokens.css"].map((f) => path36.join(bundleDir, f));
|
|
12488
|
+
const composedRoot = path36.join(bundleDir, "composed");
|
|
12002
12489
|
try {
|
|
12003
|
-
for (const entry of
|
|
12004
|
-
const dir =
|
|
12490
|
+
for (const entry of readdirSync13(composedRoot).sort()) {
|
|
12491
|
+
const dir = path36.join(composedRoot, entry);
|
|
12005
12492
|
try {
|
|
12006
|
-
if (!
|
|
12493
|
+
if (!statSync5(dir).isDirectory()) continue;
|
|
12007
12494
|
} catch {
|
|
12008
12495
|
continue;
|
|
12009
12496
|
}
|
|
12010
|
-
sheets.push(
|
|
12497
|
+
sheets.push(path36.join(dir, "styles.css"), path36.join(dir, "tokens.css"));
|
|
12011
12498
|
}
|
|
12012
12499
|
} catch {
|
|
12013
12500
|
}
|
|
12014
|
-
const css = sheets.filter((f) =>
|
|
12501
|
+
const css = sheets.filter((f) => existsSync29(f)).map((f) => readFileSync27(f, "utf8")).join("\n");
|
|
12015
12502
|
if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
|
|
12016
12503
|
return {
|
|
12017
12504
|
present: true,
|
|
@@ -12378,12 +12865,12 @@ var init_components = __esm({
|
|
|
12378
12865
|
|
|
12379
12866
|
// packages/generate/src/codebase/walk.ts
|
|
12380
12867
|
import fs2 from "node:fs";
|
|
12381
|
-
import
|
|
12868
|
+
import path37 from "node:path";
|
|
12382
12869
|
function resolvedPathIsExcluded(real, roots) {
|
|
12383
|
-
if (isNeverRead(
|
|
12870
|
+
if (isNeverRead(path37.basename(real))) return true;
|
|
12384
12871
|
for (const root of roots) {
|
|
12385
|
-
if (real !== root && !real.startsWith(root +
|
|
12386
|
-
for (const segment of
|
|
12872
|
+
if (real !== root && !real.startsWith(root + path37.sep)) continue;
|
|
12873
|
+
for (const segment of path37.relative(root, real).split(path37.sep).slice(0, -1)) {
|
|
12387
12874
|
if (segment.startsWith(".") || EXCLUDED_DIRS.has(segment)) return true;
|
|
12388
12875
|
}
|
|
12389
12876
|
}
|
|
@@ -12397,7 +12884,7 @@ function containedRealpath(abs, roots) {
|
|
|
12397
12884
|
return null;
|
|
12398
12885
|
}
|
|
12399
12886
|
for (const root of roots) {
|
|
12400
|
-
if (real === root || real.startsWith(root +
|
|
12887
|
+
if (real === root || real.startsWith(root + path37.sep)) return real;
|
|
12401
12888
|
}
|
|
12402
12889
|
return null;
|
|
12403
12890
|
}
|
|
@@ -12430,7 +12917,7 @@ function walkRepo(roots, limits, accept) {
|
|
|
12430
12917
|
continue;
|
|
12431
12918
|
}
|
|
12432
12919
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
12433
|
-
const abs =
|
|
12920
|
+
const abs = path37.join(frame.dir, entry.name);
|
|
12434
12921
|
if (EXCLUDED_DIRS.has(entry.name)) continue;
|
|
12435
12922
|
if (isNeverRead(entry.name)) continue;
|
|
12436
12923
|
const real = containedRealpath(abs, realRoots);
|
|
@@ -12518,18 +13005,18 @@ var init_walk = __esm({
|
|
|
12518
13005
|
/^\.netrc$/i
|
|
12519
13006
|
];
|
|
12520
13007
|
isNeverRead = (basename) => NEVER_READ.some((re) => re.test(basename));
|
|
12521
|
-
toRel = (root, abs) =>
|
|
13008
|
+
toRel = (root, abs) => path37.relative(root, abs).split(path37.sep).join(path37.posix.sep);
|
|
12522
13009
|
}
|
|
12523
13010
|
});
|
|
12524
13011
|
|
|
12525
13012
|
// packages/generate/src/codebase/scan.ts
|
|
12526
13013
|
import crypto2 from "node:crypto";
|
|
12527
13014
|
import fs3 from "node:fs";
|
|
12528
|
-
import
|
|
13015
|
+
import path38 from "node:path";
|
|
12529
13016
|
import postcss3 from "postcss";
|
|
12530
13017
|
function scanCodebase(options) {
|
|
12531
13018
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
12532
|
-
const roots = options.roots.map((r) =>
|
|
13019
|
+
const roots = options.roots.map((r) => path38.resolve(r));
|
|
12533
13020
|
const walk2 = walkRepo(
|
|
12534
13021
|
roots,
|
|
12535
13022
|
{
|
|
@@ -12549,7 +13036,7 @@ function scanCodebase(options) {
|
|
|
12549
13036
|
let bytesRead = 0;
|
|
12550
13037
|
let filesRead = 0;
|
|
12551
13038
|
for (const file of walk2.files) {
|
|
12552
|
-
const base =
|
|
13039
|
+
const base = path38.posix.basename(file.rel);
|
|
12553
13040
|
configFiles.add(file.rel);
|
|
12554
13041
|
if (/^tailwind\.config\./.test(base) || file.rel === "babel.config.js") continue;
|
|
12555
13042
|
const text = readTextFile(file.abs);
|
|
@@ -12565,7 +13052,7 @@ function scanCodebase(options) {
|
|
|
12565
13052
|
}
|
|
12566
13053
|
const css = extractCssCustomProperties(cssFiles.filter((f) => !f.rel.includes("..")));
|
|
12567
13054
|
const components = scanComponents(componentFiles);
|
|
12568
|
-
const packages = manifests.filter((m) =>
|
|
13055
|
+
const packages = manifests.filter((m) => path38.posix.basename(m.rel) === "package.json");
|
|
12569
13056
|
const styling = detectStyling(configFiles, cssFiles, componentFiles, manifests);
|
|
12570
13057
|
const classNameStyle = representativeClassNames(cssFiles, css.unparsed.length);
|
|
12571
13058
|
const disclosures = buildDisclosures(
|
|
@@ -12608,13 +13095,13 @@ function scanCodebase(options) {
|
|
|
12608
13095
|
},
|
|
12609
13096
|
components: {
|
|
12610
13097
|
entries: components.entries.slice(0, PROFILE_LIMITS.maxComponents),
|
|
12611
|
-
fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(
|
|
13098
|
+
fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path38.posix.basename(f.rel)))),
|
|
12612
13099
|
directoryLayout: buildHistogram(componentFiles.map((f) => classifyDirectoryLayout(f.rel))),
|
|
12613
13100
|
exportStyle: buildHistogram(components.entries.map((e) => e.exportStyle)),
|
|
12614
13101
|
classNameStyle,
|
|
12615
13102
|
colocation: buildHistogram(collectColocation(componentFiles, cssFiles)),
|
|
12616
13103
|
barrelFiles: componentFiles.filter(
|
|
12617
|
-
(f) => /^index\.[tj]sx?$/.test(
|
|
13104
|
+
(f) => /^index\.[tj]sx?$/.test(path38.posix.basename(f.rel)) && isReExportOnly(f.text)
|
|
12618
13105
|
).length,
|
|
12619
13106
|
refForwarding: {
|
|
12620
13107
|
forwardRef: componentFiles.filter((f) => /\bforwardRef\s*[(<]/.test(f.text)).length,
|
|
@@ -12637,7 +13124,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
|
|
|
12637
13124
|
};
|
|
12638
13125
|
const deps = /* @__PURE__ */ new Map();
|
|
12639
13126
|
for (const manifest of manifests) {
|
|
12640
|
-
if (
|
|
13127
|
+
if (path38.posix.basename(manifest.rel) !== "package.json") continue;
|
|
12641
13128
|
try {
|
|
12642
13129
|
const parsed = JSON.parse(manifest.text);
|
|
12643
13130
|
for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
@@ -12649,7 +13136,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
|
|
|
12649
13136
|
}
|
|
12650
13137
|
}
|
|
12651
13138
|
for (const cfg of configFiles) {
|
|
12652
|
-
const base =
|
|
13139
|
+
const base = path38.posix.basename(cfg);
|
|
12653
13140
|
if (/^tailwind\.config\./.test(base)) add("tailwind-v3", "file", cfg);
|
|
12654
13141
|
if (base === "components.json") add("shadcn-style", "file", cfg);
|
|
12655
13142
|
}
|
|
@@ -12707,8 +13194,8 @@ function collectClassNames(cssFiles) {
|
|
|
12707
13194
|
return [...distinct].sort().map(classifyClassName);
|
|
12708
13195
|
}
|
|
12709
13196
|
function classifyDirectoryLayout(rel) {
|
|
12710
|
-
const base =
|
|
12711
|
-
const dir =
|
|
13197
|
+
const base = path38.posix.basename(rel).replace(/\.[^.]+$/, "");
|
|
13198
|
+
const dir = path38.posix.basename(path38.posix.dirname(rel));
|
|
12712
13199
|
if (base === "index") return "component-dir";
|
|
12713
13200
|
if (base === dir) return "component-dir";
|
|
12714
13201
|
return "flat-file";
|
|
@@ -12732,7 +13219,7 @@ function detectFormatting(manifests, componentFiles) {
|
|
|
12732
13219
|
(a, b) => a.rel.split("/").length - b.rel.split("/").length || a.rel.localeCompare(b.rel)
|
|
12733
13220
|
);
|
|
12734
13221
|
for (const manifest of byDepth) {
|
|
12735
|
-
const base =
|
|
13222
|
+
const base = path38.posix.basename(manifest.rel);
|
|
12736
13223
|
if (!/^\.prettierrc/.test(base) && base !== "package.json") continue;
|
|
12737
13224
|
try {
|
|
12738
13225
|
const parsed = JSON.parse(manifest.text);
|
|
@@ -12750,7 +13237,7 @@ function detectFormatting(manifests, componentFiles) {
|
|
|
12750
13237
|
}
|
|
12751
13238
|
}
|
|
12752
13239
|
for (const manifest of byDepth) {
|
|
12753
|
-
if (
|
|
13240
|
+
if (path38.posix.basename(manifest.rel) !== ".editorconfig") continue;
|
|
12754
13241
|
const style = /indent_style\s*=\s*(tab|space)/.exec(manifest.text)?.[1];
|
|
12755
13242
|
const width = /indent_size\s*=\s*(\d+)/.exec(manifest.text)?.[1];
|
|
12756
13243
|
if (style || width) {
|
|
@@ -12821,12 +13308,12 @@ function buildDisclosures(detected, css, cappedOut, unrepresentativeClassNames)
|
|
|
12821
13308
|
return out;
|
|
12822
13309
|
}
|
|
12823
13310
|
function outPathIsGitIgnored(outPath) {
|
|
12824
|
-
const dir =
|
|
13311
|
+
const dir = path38.dirname(outPath);
|
|
12825
13312
|
try {
|
|
12826
|
-
const ignoreFile =
|
|
13313
|
+
const ignoreFile = path38.join(path38.dirname(dir), ".gitignore");
|
|
12827
13314
|
if (!fs3.existsSync(ignoreFile)) return false;
|
|
12828
13315
|
const patterns = fs3.readFileSync(ignoreFile, "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
12829
|
-
const base =
|
|
13316
|
+
const base = path38.basename(dir);
|
|
12830
13317
|
return patterns.some((p) => p === base || p === `${base}/` || p === `/${base}` || p === `/${base}/`);
|
|
12831
13318
|
} catch {
|
|
12832
13319
|
return false;
|
|
@@ -12973,8 +13460,8 @@ __export(profile_exports, {
|
|
|
12973
13460
|
PROFILE_DESCRIPTION: () => PROFILE_DESCRIPTION,
|
|
12974
13461
|
runProfile: () => runProfile
|
|
12975
13462
|
});
|
|
12976
|
-
import { closeSync, constants, existsSync as
|
|
12977
|
-
import
|
|
13463
|
+
import { closeSync, constants, existsSync as existsSync30, mkdirSync as mkdirSync9, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync12 } from "node:fs";
|
|
13464
|
+
import path39 from "node:path";
|
|
12978
13465
|
function escapesScanRoot(outPath, scanRoot) {
|
|
12979
13466
|
const resolveExisting = (target) => {
|
|
12980
13467
|
let cursor = target;
|
|
@@ -12982,23 +13469,23 @@ function escapesScanRoot(outPath, scanRoot) {
|
|
|
12982
13469
|
try {
|
|
12983
13470
|
return realpathSync4(cursor);
|
|
12984
13471
|
} catch {
|
|
12985
|
-
const parent =
|
|
13472
|
+
const parent = path39.dirname(cursor);
|
|
12986
13473
|
if (parent === cursor) return cursor;
|
|
12987
13474
|
cursor = parent;
|
|
12988
13475
|
}
|
|
12989
13476
|
}
|
|
12990
13477
|
};
|
|
12991
13478
|
const root = resolveExisting(scanRoot);
|
|
12992
|
-
const dir = resolveExisting(
|
|
12993
|
-
return dir !== root && !dir.startsWith(root +
|
|
13479
|
+
const dir = resolveExisting(path39.dirname(outPath));
|
|
13480
|
+
return dir !== root && !dir.startsWith(root + path39.sep);
|
|
12994
13481
|
}
|
|
12995
13482
|
function runProfile(options) {
|
|
12996
13483
|
if (options.describe) {
|
|
12997
13484
|
printDescription(PROFILE_DESCRIPTION);
|
|
12998
13485
|
return;
|
|
12999
13486
|
}
|
|
13000
|
-
const dir =
|
|
13001
|
-
if (!
|
|
13487
|
+
const dir = path39.resolve(options.dir ?? ".");
|
|
13488
|
+
if (!existsSync30(dir)) {
|
|
13002
13489
|
fail(options, ExitCode.InputValidation, {
|
|
13003
13490
|
error: `no such directory: ${dir}`,
|
|
13004
13491
|
code: "profile_dir_missing",
|
|
@@ -13006,7 +13493,7 @@ function runProfile(options) {
|
|
|
13006
13493
|
});
|
|
13007
13494
|
}
|
|
13008
13495
|
const profile = scanCodebase({ roots: [dir], ...options.now ? { now: options.now } : {} });
|
|
13009
|
-
const outPath =
|
|
13496
|
+
const outPath = path39.resolve(options.out ?? path39.join(dir, "tendril-out", "codebase-profile.json"));
|
|
13010
13497
|
if (!options.dryRun) {
|
|
13011
13498
|
if (options.out === void 0 && escapesScanRoot(outPath, dir)) {
|
|
13012
13499
|
fail(options, ExitCode.InputValidation, {
|
|
@@ -13015,10 +13502,10 @@ function runProfile(options) {
|
|
|
13015
13502
|
remediation: `\`tendril-out\` in that project is a symlink pointing outside it, so writing the profile there could overwrite an unrelated file. Remove the symlink, or choose an explicit destination: \`${tendrilCommand(`profile --dir ${quoteArg(dir)} --out ./codebase-profile.json`)}\`.`
|
|
13016
13503
|
});
|
|
13017
13504
|
}
|
|
13018
|
-
|
|
13505
|
+
mkdirSync9(path39.dirname(outPath), { recursive: true });
|
|
13019
13506
|
const handle = openSync(outPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 420);
|
|
13020
13507
|
try {
|
|
13021
|
-
|
|
13508
|
+
writeFileSync12(handle, `${JSON.stringify(profile, null, 2)}
|
|
13022
13509
|
`, "utf8");
|
|
13023
13510
|
} finally {
|
|
13024
13511
|
closeSync(handle);
|
|
@@ -13080,7 +13567,7 @@ Written to ${outPath}
|
|
|
13080
13567
|
`);
|
|
13081
13568
|
if (!ignored) {
|
|
13082
13569
|
process.stdout.write(
|
|
13083
|
-
` NOTE: ${
|
|
13570
|
+
` NOTE: ${path39.basename(path39.dirname(outPath))}/ is not gitignored here \u2014 add it to .gitignore, or this profile will show up in your next commit.
|
|
13084
13571
|
`
|
|
13085
13572
|
);
|
|
13086
13573
|
}
|
|
@@ -13219,11 +13706,11 @@ __export(compose_exports, {
|
|
|
13219
13706
|
compositionPairsFor: () => compositionPairsFor,
|
|
13220
13707
|
runCompose: () => runCompose
|
|
13221
13708
|
});
|
|
13222
|
-
import { createHash as
|
|
13223
|
-
import { existsSync as
|
|
13224
|
-
import
|
|
13709
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
13710
|
+
import { existsSync as existsSync31, readFileSync as readFileSync28 } from "node:fs";
|
|
13711
|
+
import path40 from "node:path";
|
|
13225
13712
|
function compositionPairsFor(hostSet, roots) {
|
|
13226
|
-
const scanRoots = [.../* @__PURE__ */ new Set([...roots,
|
|
13713
|
+
const scanRoots = [.../* @__PURE__ */ new Set([...roots, path40.dirname(hostSet)])];
|
|
13227
13714
|
const edges = composeReport(buildComposeIndex(scanRoots));
|
|
13228
13715
|
const pairs = substitutionPairs(edges, hostSet);
|
|
13229
13716
|
const { raw } = readManifestFile(hostSet);
|
|
@@ -13255,7 +13742,7 @@ function substitutionPairs(edges, hostSet) {
|
|
|
13255
13742
|
});
|
|
13256
13743
|
}
|
|
13257
13744
|
const pair = pairs.get(key);
|
|
13258
|
-
const poseDisplay = e.pose.reps.map((r) => `${
|
|
13745
|
+
const poseDisplay = e.pose.reps.map((r) => `${path40.basename(r.dir)}:${r.slug}`).join(", ");
|
|
13259
13746
|
pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
|
|
13260
13747
|
for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
|
|
13261
13748
|
}
|
|
@@ -13267,7 +13754,7 @@ function runCompose(flags) {
|
|
|
13267
13754
|
return;
|
|
13268
13755
|
}
|
|
13269
13756
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
13270
|
-
const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) =>
|
|
13757
|
+
const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path40.resolve(base, d)) : [base];
|
|
13271
13758
|
if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
|
|
13272
13759
|
fail(flags, ExitCode.InputValidation, {
|
|
13273
13760
|
error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
|
|
@@ -13276,7 +13763,7 @@ function runCompose(flags) {
|
|
|
13276
13763
|
});
|
|
13277
13764
|
}
|
|
13278
13765
|
if (flags.set !== void 0) {
|
|
13279
|
-
runComposeConfirm(flags,
|
|
13766
|
+
runComposeConfirm(flags, path40.resolve(base, flags.set), roots);
|
|
13280
13767
|
return;
|
|
13281
13768
|
}
|
|
13282
13769
|
const index = buildComposeIndex(roots);
|
|
@@ -13294,7 +13781,7 @@ function runCompose(flags) {
|
|
|
13294
13781
|
}
|
|
13295
13782
|
let lastHost = "";
|
|
13296
13783
|
for (const e of edges) {
|
|
13297
|
-
const host = `${
|
|
13784
|
+
const host = `${path40.basename(e.hostSet)}`;
|
|
13298
13785
|
if (host !== lastHost) {
|
|
13299
13786
|
process.stdout.write(`
|
|
13300
13787
|
${host}
|
|
@@ -13302,7 +13789,7 @@ ${host}
|
|
|
13302
13789
|
lastHost = host;
|
|
13303
13790
|
}
|
|
13304
13791
|
const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
|
|
13305
|
-
const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${
|
|
13792
|
+
const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path40.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
|
|
13306
13793
|
process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
|
|
13307
13794
|
`);
|
|
13308
13795
|
for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
|
|
@@ -13314,14 +13801,14 @@ ${NOTE}
|
|
|
13314
13801
|
});
|
|
13315
13802
|
}
|
|
13316
13803
|
function runComposeConfirm(flags, hostSet, roots) {
|
|
13317
|
-
if (!
|
|
13804
|
+
if (!existsSync31(path40.join(hostSet, "recording-set.json"))) {
|
|
13318
13805
|
fail(flags, ExitCode.InputValidation, {
|
|
13319
13806
|
error: `no recording-set.json in ${hostSet}`,
|
|
13320
13807
|
code: "no-recording-set",
|
|
13321
13808
|
remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
|
|
13322
13809
|
});
|
|
13323
13810
|
}
|
|
13324
|
-
const scanRoots = [.../* @__PURE__ */ new Set([...roots,
|
|
13811
|
+
const scanRoots = [.../* @__PURE__ */ new Set([...roots, path40.dirname(hostSet)])];
|
|
13325
13812
|
const index = buildComposeIndex(scanRoots);
|
|
13326
13813
|
const edges = composeReport(index);
|
|
13327
13814
|
const pairs = substitutionPairs(edges, hostSet);
|
|
@@ -13404,7 +13891,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
|
|
|
13404
13891
|
// full recording-set hash join lands with pin authoring, where
|
|
13405
13892
|
// task configs exist.)
|
|
13406
13893
|
manifestSha256: Object.fromEntries(
|
|
13407
|
-
p.partnerDirs.map((d) => [
|
|
13894
|
+
p.partnerDirs.map((d) => [path40.relative(hostSet, d), createHash8("sha256").update(readFileSync28(path40.join(d, "recording-set.json"))).digest("hex")])
|
|
13408
13895
|
)
|
|
13409
13896
|
},
|
|
13410
13897
|
// Schema shape ONLY (review: the display-side poseDisplay field
|
|
@@ -13483,10 +13970,10 @@ __export(record_exports, {
|
|
|
13483
13970
|
runRecordPlan: () => runRecordPlan,
|
|
13484
13971
|
runRecordStatus: () => runRecordStatus
|
|
13485
13972
|
});
|
|
13486
|
-
import { existsSync as
|
|
13487
|
-
import
|
|
13488
|
-
import
|
|
13489
|
-
import { writeFileSync as
|
|
13973
|
+
import { existsSync as existsSync32, mkdtempSync as mkdtempSync2, readFileSync as readFileSync29, readdirSync as readdirSync14 } from "node:fs";
|
|
13974
|
+
import os7 from "node:os";
|
|
13975
|
+
import path41 from "node:path";
|
|
13976
|
+
import { writeFileSync as writeFileSync13 } from "node:fs";
|
|
13490
13977
|
function recordsInteractionState(reports) {
|
|
13491
13978
|
const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
|
|
13492
13979
|
return reports.some((r) => evident(r.axis) || r.domain.some(evident));
|
|
@@ -13508,7 +13995,7 @@ function interactionDisclosure(component, reports) {
|
|
|
13508
13995
|
};
|
|
13509
13996
|
}
|
|
13510
13997
|
function symbolsFromMetadataEnvelope(file, sourceFrame) {
|
|
13511
|
-
const env = JSON.parse(
|
|
13998
|
+
const env = JSON.parse(readFileSync29(file, "utf8"));
|
|
13512
13999
|
const text = env.content.map((c) => c.text ?? "").join("\n");
|
|
13513
14000
|
const symbols = [];
|
|
13514
14001
|
const walk2 = (node, ancestor) => {
|
|
@@ -13566,8 +14053,8 @@ function runRecordPlan(opts) {
|
|
|
13566
14053
|
if (rawFile !== void 0) {
|
|
13567
14054
|
try {
|
|
13568
14055
|
const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
|
|
13569
|
-
const tmp =
|
|
13570
|
-
|
|
14056
|
+
const tmp = path41.join(mkdtempSync2(path41.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
|
|
14057
|
+
writeFileSync13(tmp, JSON.stringify(envelope));
|
|
13571
14058
|
metadataEntries.push({ file: tmp });
|
|
13572
14059
|
} catch (err) {
|
|
13573
14060
|
fail(opts, ExitCode.InputValidation, {
|
|
@@ -13588,7 +14075,7 @@ function runRecordPlan(opts) {
|
|
|
13588
14075
|
let metadataTruncated = false;
|
|
13589
14076
|
for (const { file, frame } of metadataEntries) {
|
|
13590
14077
|
try {
|
|
13591
|
-
const parsed = symbolsFromMetadataEnvelope(
|
|
14078
|
+
const parsed = symbolsFromMetadataEnvelope(path41.resolve(file), frame);
|
|
13592
14079
|
symbols.push(...parsed.symbols);
|
|
13593
14080
|
if (parsed.truncated) metadataTruncated = true;
|
|
13594
14081
|
} catch (err) {
|
|
@@ -13622,7 +14109,7 @@ function runRecordPlan(opts) {
|
|
|
13622
14109
|
if (symbols.length === 0) {
|
|
13623
14110
|
const leads = metadataEntries.flatMap(({ file }) => {
|
|
13624
14111
|
try {
|
|
13625
|
-
const env = JSON.parse(
|
|
14112
|
+
const env = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
|
|
13626
14113
|
return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
|
|
13627
14114
|
} catch {
|
|
13628
14115
|
return [];
|
|
@@ -13715,7 +14202,7 @@ function runRecordPlan(opts) {
|
|
|
13715
14202
|
// a CLI flag and the MCP surface has no parameter for it.
|
|
13716
14203
|
decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
|
|
13717
14204
|
text: "Record a reduced pose set (anchor + one-factor sweeps + conflict crosses) instead of the full variant matrix. This buys calls with coverage \u2014 sampling is blind to multi-axis interactions and the report discloses the poses it skipped \u2014 so only the user may accept that trade. There is no tool parameter for it; the user runs it themselves in their own terminal, before any pose is recorded.",
|
|
13718
|
-
userRuns: [`rm ${quoteArg(
|
|
14205
|
+
userRuns: [`rm ${quoteArg(path41.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
|
|
13719
14206
|
},
|
|
13720
14207
|
{
|
|
13721
14208
|
id: "larger-allowance",
|
|
@@ -13913,7 +14400,7 @@ function runRecordNext(opts) {
|
|
|
13913
14400
|
const progress = payload["progress"];
|
|
13914
14401
|
process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
|
|
13915
14402
|
\u2192 ${payload["note"]}
|
|
13916
|
-
\u2192 then: ${tendrilCommand(`record ingest --set ${
|
|
14403
|
+
\u2192 then: ${tendrilCommand(`record ingest --set ${path41.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
|
|
13917
14404
|
`);
|
|
13918
14405
|
});
|
|
13919
14406
|
}
|
|
@@ -13987,7 +14474,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
13987
14474
|
const skipped = [];
|
|
13988
14475
|
const failed = [];
|
|
13989
14476
|
for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
|
|
13990
|
-
if (
|
|
14477
|
+
if (existsSync32(path41.join(setDir, rep, name))) {
|
|
13991
14478
|
skipped.push(name);
|
|
13992
14479
|
continue;
|
|
13993
14480
|
}
|
|
@@ -14009,16 +14496,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
|
|
|
14009
14496
|
}
|
|
14010
14497
|
function rawEnvelopeFromFile(file, parts) {
|
|
14011
14498
|
if (parts) {
|
|
14012
|
-
const blocks = JSON.parse(
|
|
14499
|
+
const blocks = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
|
|
14013
14500
|
if (!Array.isArray(blocks) || blocks.length === 0 || blocks.some((p) => typeof p !== "string")) throw new Error("parts file must be a non-empty JSON array of strings");
|
|
14014
14501
|
return { content: blocks.map((text) => ({ type: "text", text })) };
|
|
14015
14502
|
}
|
|
14016
|
-
return { content: [{ type: "text", text:
|
|
14503
|
+
return { content: [{ type: "text", text: readFileSync29(path41.resolve(file), "utf8") }] };
|
|
14017
14504
|
}
|
|
14018
14505
|
async function runRecordIngest(opts) {
|
|
14019
14506
|
let payload;
|
|
14020
14507
|
try {
|
|
14021
|
-
payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(
|
|
14508
|
+
payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync29(path41.resolve(opts.file), "utf8"));
|
|
14022
14509
|
} catch (err) {
|
|
14023
14510
|
fail(opts, ExitCode.InputValidation, {
|
|
14024
14511
|
error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -14030,7 +14517,7 @@ async function runRecordIngest(opts) {
|
|
|
14030
14517
|
fail(opts, ExitCode.InputValidation, {
|
|
14031
14518
|
error: "--raw is for text tool responses; screenshots are binary",
|
|
14032
14519
|
code: "envelope-invalid",
|
|
14033
|
-
remediation: `Use \`${tendrilCommand(`record fetch --set ${
|
|
14520
|
+
remediation: `Use \`${tendrilCommand(`record fetch --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
|
|
14034
14521
|
});
|
|
14035
14522
|
}
|
|
14036
14523
|
if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
|
|
@@ -14050,7 +14537,7 @@ async function runRecordIngest(opts) {
|
|
|
14050
14537
|
remediation: REINGEST_GUIDANCE
|
|
14051
14538
|
});
|
|
14052
14539
|
}
|
|
14053
|
-
|
|
14540
|
+
writeFileSync13(path41.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
|
|
14054
14541
|
`);
|
|
14055
14542
|
emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14056
14543
|
process.stdout.write("set-level get_variable_defs ingested\n");
|
|
@@ -14074,7 +14561,7 @@ async function runRecordIngest(opts) {
|
|
|
14074
14561
|
remediation: REINGEST_GUIDANCE
|
|
14075
14562
|
});
|
|
14076
14563
|
}
|
|
14077
|
-
|
|
14564
|
+
writeFileSync13(path41.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
|
|
14078
14565
|
`);
|
|
14079
14566
|
emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
|
|
14080
14567
|
process.stdout.write("set-level get_motion_context ingested\n");
|
|
@@ -14090,7 +14577,7 @@ async function runRecordIngest(opts) {
|
|
|
14090
14577
|
if (assets !== void 0) {
|
|
14091
14578
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14092
14579
|
`);
|
|
14093
|
-
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${
|
|
14580
|
+
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
|
|
14094
14581
|
`);
|
|
14095
14582
|
}
|
|
14096
14583
|
});
|
|
@@ -14163,15 +14650,15 @@ async function runRecordIngestRep(opts) {
|
|
|
14163
14650
|
if (assets !== void 0) {
|
|
14164
14651
|
for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
|
|
14165
14652
|
`);
|
|
14166
|
-
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${
|
|
14653
|
+
for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
|
|
14167
14654
|
`);
|
|
14168
14655
|
}
|
|
14169
14656
|
});
|
|
14170
14657
|
}
|
|
14171
14658
|
function runRecordAsset(opts) {
|
|
14172
14659
|
if (opts.dir !== void 0) {
|
|
14173
|
-
const dir =
|
|
14174
|
-
const names =
|
|
14660
|
+
const dir = path41.resolve(opts.dir);
|
|
14661
|
+
const names = readdirSync14(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
|
|
14175
14662
|
if (names.length === 0) {
|
|
14176
14663
|
fail(opts, ExitCode.InputValidation, {
|
|
14177
14664
|
error: `no asset-*.<ext> files found in ${dir}`,
|
|
@@ -14182,7 +14669,7 @@ function runRecordAsset(opts) {
|
|
|
14182
14669
|
const ingested = [];
|
|
14183
14670
|
try {
|
|
14184
14671
|
for (const name of names) {
|
|
14185
|
-
ingestAsset(opts.setDir, opts.rep, name,
|
|
14672
|
+
ingestAsset(opts.setDir, opts.rep, name, readFileSync29(path41.join(dir, name)));
|
|
14186
14673
|
ingested.push(name);
|
|
14187
14674
|
}
|
|
14188
14675
|
} catch (err) {
|
|
@@ -14202,11 +14689,11 @@ function runRecordAsset(opts) {
|
|
|
14202
14689
|
fail(opts, ExitCode.InputValidation, {
|
|
14203
14690
|
error: "pass --name and --file for a single asset, or --dir for a batch",
|
|
14204
14691
|
code: "asset-rejected",
|
|
14205
|
-
remediation: tendrilCommand(`record asset --set ${
|
|
14692
|
+
remediation: tendrilCommand(`record asset --set ${path41.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
|
|
14206
14693
|
});
|
|
14207
14694
|
}
|
|
14208
14695
|
try {
|
|
14209
|
-
ingestAsset(opts.setDir, opts.rep, opts.name,
|
|
14696
|
+
ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync29(path41.resolve(opts.file)));
|
|
14210
14697
|
emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
|
|
14211
14698
|
process.stdout.write(`ingested ${opts.rep}/${opts.name}
|
|
14212
14699
|
`);
|
|
@@ -14223,8 +14710,8 @@ function runRecordStatus(opts) {
|
|
|
14223
14710
|
const status = sessionStatus(opts.setDir);
|
|
14224
14711
|
const composition = (() => {
|
|
14225
14712
|
try {
|
|
14226
|
-
const setDir =
|
|
14227
|
-
const { open, standing, invalid } = compositionPairsFor(setDir, [
|
|
14713
|
+
const setDir = path41.resolve(opts.setDir);
|
|
14714
|
+
const { open, standing, invalid } = compositionPairsFor(setDir, [path41.dirname(setDir)]);
|
|
14228
14715
|
return {
|
|
14229
14716
|
openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
|
|
14230
14717
|
confirmed: standing.filter((s) => s.status === "confirmed").length,
|
|
@@ -14245,7 +14732,7 @@ function runRecordStatus(opts) {
|
|
|
14245
14732
|
}
|
|
14246
14733
|
}
|
|
14247
14734
|
process.stdout.write(
|
|
14248
|
-
status.motion.recorded ? motionTruthFor(
|
|
14735
|
+
status.motion.recorded ? motionTruthFor(path41.resolve(opts.setDir)).state === "recorded-no-motion" ? "MOTION set-level motion context recorded \u2014 the response reports NO motion data (no keyframe tracks, no snippets); briefs prescribe default doctrine and say so. This is the instrument's answer, not proof the design has no transitions\n" : status.motion.asked ? "MOTION set-level motion context recorded\n" : "MOTION set-level motion context recorded (ingested onto a set that predates the obligation \u2014 briefs will quote it as recorded truth)\n" : status.motion.asked ? status.motion.invalid !== void 0 ? `MOTION set-level motion file is UNUSABLE (${status.motion.invalid}) \u2014 re-record it via \`record next\`
|
|
14249
14736
|
` : "MOTION set-level motion context not yet recorded \u2014 `record next` names the call once the reps and token map are done\n" : "MOTION never asked \u2014 this set predates the motion-capture obligation (fresh plans record it; briefs prescribe default motion doctrine only)\n"
|
|
14250
14737
|
);
|
|
14251
14738
|
if ("unavailable" in composition) {
|
|
@@ -14253,7 +14740,7 @@ function runRecordStatus(opts) {
|
|
|
14253
14740
|
`);
|
|
14254
14741
|
} else if (composition.openPairs.length > 0) {
|
|
14255
14742
|
process.stdout.write(
|
|
14256
|
-
`COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${
|
|
14743
|
+
`COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${path41.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
|
|
14257
14744
|
`
|
|
14258
14745
|
);
|
|
14259
14746
|
} else if (composition.confirmed > 0) {
|
|
@@ -14293,7 +14780,7 @@ function narrowedRoles(derived, override) {
|
|
|
14293
14780
|
function rolesFromFile(opts, file, derived) {
|
|
14294
14781
|
let json;
|
|
14295
14782
|
try {
|
|
14296
|
-
json = JSON.parse(
|
|
14783
|
+
json = JSON.parse(readFileSync29(path41.resolve(file), "utf8"));
|
|
14297
14784
|
} catch (err) {
|
|
14298
14785
|
fail(opts, ExitCode.InputValidation, {
|
|
14299
14786
|
error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
@@ -14331,11 +14818,11 @@ function rolesFromFile(opts, file, derived) {
|
|
|
14331
14818
|
};
|
|
14332
14819
|
}
|
|
14333
14820
|
function runRecordFinish(opts) {
|
|
14334
|
-
if (!
|
|
14821
|
+
if (!existsSync32(path41.join(opts.setDir, "recording-set.json"))) {
|
|
14335
14822
|
fail(opts, ExitCode.InputValidation, {
|
|
14336
14823
|
error: `no recording-set.json in ${opts.setDir}`,
|
|
14337
14824
|
code: "no-recording-set",
|
|
14338
|
-
remediation: `Run \`${tendrilCommand(`record plan --set ${
|
|
14825
|
+
remediation: `Run \`${tendrilCommand(`record plan --set ${path41.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
|
|
14339
14826
|
});
|
|
14340
14827
|
}
|
|
14341
14828
|
const { manifest, raw } = readManifestFile(opts.setDir);
|
|
@@ -14363,17 +14850,17 @@ function runRecordFinish(opts) {
|
|
|
14363
14850
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
14364
14851
|
error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
|
|
14365
14852
|
code: "roles-confirmation-not-interactive",
|
|
14366
|
-
remediation: `A human runs \`${tendrilCommand(`record finish --set ${
|
|
14853
|
+
remediation: `A human runs \`${tendrilCommand(`record finish --set ${path41.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
|
|
14367
14854
|
});
|
|
14368
14855
|
}
|
|
14369
14856
|
const merged = { ...raw, roles };
|
|
14370
|
-
const { issues } = validateRecordingSet(merged, (rel) =>
|
|
14857
|
+
const { issues } = validateRecordingSet(merged, (rel) => existsSync32(path41.join(opts.setDir, rel)));
|
|
14371
14858
|
const errors = issues.filter((i) => i.severity === "error");
|
|
14372
14859
|
if (errors.length > 0) {
|
|
14373
14860
|
fail(opts, ExitCode.InputValidation, {
|
|
14374
14861
|
error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
|
|
14375
14862
|
code: "recording-set-invalid",
|
|
14376
|
-
remediation: `Fix the set (\`${tendrilCommand(`record status --set ${
|
|
14863
|
+
remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path41.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
|
|
14377
14864
|
});
|
|
14378
14865
|
}
|
|
14379
14866
|
for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
|
|
@@ -14423,9 +14910,9 @@ var init_record = __esm({
|
|
|
14423
14910
|
});
|
|
14424
14911
|
|
|
14425
14912
|
// packages/cli/src/font-guidance.ts
|
|
14426
|
-
import
|
|
14913
|
+
import path42 from "node:path";
|
|
14427
14914
|
function fontsUnprovenRemediation(setDir) {
|
|
14428
|
-
const set = setDir === void 0 ? void 0 :
|
|
14915
|
+
const set = setDir === void 0 ? void 0 : path42.resolve(setDir);
|
|
14429
14916
|
if (set !== void 0) {
|
|
14430
14917
|
try {
|
|
14431
14918
|
const needs = recordedFontNeeds(set);
|
|
@@ -14500,8 +14987,8 @@ __export(fonts_exports, {
|
|
|
14500
14987
|
runFontsResolveSet: () => runFontsResolveSet,
|
|
14501
14988
|
runFontsStatus: () => runFontsStatus
|
|
14502
14989
|
});
|
|
14503
|
-
import { existsSync as
|
|
14504
|
-
import
|
|
14990
|
+
import { existsSync as existsSync33, readFileSync as readFileSync30 } from "node:fs";
|
|
14991
|
+
import path43 from "node:path";
|
|
14505
14992
|
async function runFontsResolve(opts) {
|
|
14506
14993
|
const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
|
|
14507
14994
|
const queryRefusal = systemFaceRefusal(opts.family.trim());
|
|
@@ -14522,7 +15009,7 @@ async function runFontsResolve(opts) {
|
|
|
14522
15009
|
}
|
|
14523
15010
|
}
|
|
14524
15011
|
async function runFontsResolveSet(opts) {
|
|
14525
|
-
const setDir =
|
|
15012
|
+
const setDir = path43.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
|
|
14526
15013
|
let needs = [];
|
|
14527
15014
|
try {
|
|
14528
15015
|
needs = recordedFontNeeds(setDir);
|
|
@@ -14573,7 +15060,10 @@ async function runFontsResolveSet(opts) {
|
|
|
14573
15060
|
}
|
|
14574
15061
|
}
|
|
14575
15062
|
const refusals = [...new Set(failures.map((f) => f.family))].map((family) => ({ family, reason: systemFaceRefusal(family.trim()) })).filter((r) => r.reason !== void 0);
|
|
14576
|
-
|
|
15063
|
+
const withAFace = new Set([...cached2, ...resolved, ...relicensed].map((f) => f.family.toLowerCase()));
|
|
15064
|
+
const familyMisses = failures.filter((f) => !withAFace.has(f.family.toLowerCase()));
|
|
15065
|
+
const weightMisses = failures.filter((f) => withAFace.has(f.family.toLowerCase()));
|
|
15066
|
+
emitData(opts, { set: setDir, needs, cached: cached2, resolved, relicensed, unlicensed, failures, familyMisses, weightMisses, ...refusals.length > 0 ? { refusals } : {} }, () => {
|
|
14577
15067
|
process.stdout.write(`set declares: ${needs.map((n) => `${n.family} (${n.weights.join(", ")})`).join(" \xB7 ")}
|
|
14578
15068
|
`);
|
|
14579
15069
|
for (const f of cached2) process.stdout.write(`cached ${f.family} ${f.weight}
|
|
@@ -14599,22 +15089,31 @@ async function runFontsResolveSet(opts) {
|
|
|
14599
15089
|
if (byteDrift.length > 0) {
|
|
14600
15090
|
warn(opts, `${byteDrift.map((f) => `${f.family} ${f.weight}`).join(", ")}: the foundry now serves different bytes than the cache held \u2014 the faces were re-fetched to record their licence, so re-pin any font lock deliberately`);
|
|
14601
15091
|
}
|
|
14602
|
-
if (
|
|
14603
|
-
|
|
15092
|
+
if (weightMisses.length > 0) {
|
|
15093
|
+
const byFamily = [...new Set(weightMisses.map((f) => f.family))].map(
|
|
15094
|
+
(fam) => `"${fam}" ${weightMisses.filter((f) => f.family === fam).map((f) => f.weight).join(", ")}`
|
|
15095
|
+
);
|
|
15096
|
+
warn(
|
|
15097
|
+
opts,
|
|
15098
|
+
`weight instance(s) unavailable: ${byFamily.join("; ")} \u2014 the FAMILY is provisioned, so the mount renders these at the nearest cached weight. Verify reports that as an advisory and never gates on it, so this run is not a failure. If the exact instance matters, register a file you licence: ${tendrilCommand('fonts add "<Family>" <weight> <file>')}.`
|
|
15099
|
+
);
|
|
15100
|
+
}
|
|
15101
|
+
if (familyMisses.length > 0) {
|
|
15102
|
+
warn(opts, `${familyMisses.length} face(s) unresolved with NO cached face for the family \u2014 verification will refuse to certify under substitution`);
|
|
14604
15103
|
process.exitCode = ExitCode.FontsUnproven;
|
|
14605
15104
|
}
|
|
14606
15105
|
}
|
|
14607
15106
|
function runFontsStatus(opts) {
|
|
14608
|
-
const manifestPath2 =
|
|
14609
|
-
if (!
|
|
15107
|
+
const manifestPath2 = path43.join(opts.cacheDir, "manifest.json");
|
|
15108
|
+
if (!existsSync33(manifestPath2)) {
|
|
14610
15109
|
fail(opts, ExitCode.FontsUnproven, {
|
|
14611
15110
|
error: `no font cache at ${opts.cacheDir}`,
|
|
14612
15111
|
code: "fonts-unresolved",
|
|
14613
15112
|
remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
|
|
14614
15113
|
});
|
|
14615
15114
|
}
|
|
14616
|
-
const faces = JSON.parse(
|
|
14617
|
-
const lockVerdicts = opts.lock !== void 0 ? checkFontLock(
|
|
15115
|
+
const faces = JSON.parse(readFileSync30(manifestPath2, "utf8"));
|
|
15116
|
+
const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path43.resolve(opts.lock), opts.cacheDir) : null;
|
|
14618
15117
|
emitData(opts, { faces, lockVerdicts }, () => {
|
|
14619
15118
|
for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
|
|
14620
15119
|
`);
|
|
@@ -14658,13 +15157,13 @@ function familyMismatch(family, declared) {
|
|
|
14658
15157
|
}
|
|
14659
15158
|
function runFontsAdd(opts) {
|
|
14660
15159
|
if (opts.set !== void 0) {
|
|
14661
|
-
const declared = taskFontFamilies(
|
|
15160
|
+
const declared = taskFontFamilies(path43.resolve(opts.set)) ?? [];
|
|
14662
15161
|
const mismatch = familyMismatch(opts.family, declared);
|
|
14663
15162
|
if (mismatch !== void 0) {
|
|
14664
15163
|
fail(opts, ExitCode.InputValidation, {
|
|
14665
15164
|
error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. Adding it under this name would cache a face the mount never matches, and scoring would keep refusing for the family that is still missing.`,
|
|
14666
15165
|
code: "font-family-not-declared",
|
|
14667
|
-
remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${
|
|
15166
|
+
remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path43.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
|
|
14668
15167
|
});
|
|
14669
15168
|
}
|
|
14670
15169
|
} else {
|
|
@@ -14723,13 +15222,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
|
|
|
14723
15222
|
}
|
|
14724
15223
|
function runFontsAddSystem(opts) {
|
|
14725
15224
|
if (opts.set !== void 0) {
|
|
14726
|
-
const declared = taskFontFamilies(
|
|
15225
|
+
const declared = taskFontFamilies(path43.resolve(opts.set)) ?? [];
|
|
14727
15226
|
const mismatch = familyMismatch(opts.family, declared);
|
|
14728
15227
|
if (mismatch !== void 0) {
|
|
14729
15228
|
fail(opts, ExitCode.InputValidation, {
|
|
14730
15229
|
error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. A face cached under a name the mount never matches leaves scoring refusing for the family that is still missing.`,
|
|
14731
15230
|
code: "font-family-not-declared",
|
|
14732
|
-
remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(
|
|
15231
|
+
remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path43.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
|
|
14733
15232
|
});
|
|
14734
15233
|
}
|
|
14735
15234
|
} else {
|
|
@@ -14779,12 +15278,12 @@ var init_fonts = __esm({
|
|
|
14779
15278
|
});
|
|
14780
15279
|
|
|
14781
15280
|
// packages/cli/src/profile-input.ts
|
|
14782
|
-
import { existsSync as
|
|
14783
|
-
import
|
|
15281
|
+
import { existsSync as existsSync34, readFileSync as readFileSync31 } from "node:fs";
|
|
15282
|
+
import path44 from "node:path";
|
|
14784
15283
|
function loadCodebaseProfile(flags, profilePath) {
|
|
14785
15284
|
if (profilePath === void 0) return null;
|
|
14786
|
-
const abs =
|
|
14787
|
-
if (!
|
|
15285
|
+
const abs = path44.resolve(profilePath);
|
|
15286
|
+
if (!existsSync34(abs)) {
|
|
14788
15287
|
fail(flags, ExitCode.InputValidation, {
|
|
14789
15288
|
error: `no profile at ${abs}`,
|
|
14790
15289
|
code: "profile_missing",
|
|
@@ -14792,7 +15291,7 @@ function loadCodebaseProfile(flags, profilePath) {
|
|
|
14792
15291
|
});
|
|
14793
15292
|
}
|
|
14794
15293
|
try {
|
|
14795
|
-
return readCodebaseProfile(
|
|
15294
|
+
return readCodebaseProfile(readFileSync31(abs, "utf8"));
|
|
14796
15295
|
} catch (error) {
|
|
14797
15296
|
fail(flags, ExitCode.InputValidation, {
|
|
14798
15297
|
error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -14834,8 +15333,8 @@ __export(verify_exports, {
|
|
|
14834
15333
|
runVerify: () => runVerify,
|
|
14835
15334
|
verdictCaveatsFor: () => verdictCaveatsFor
|
|
14836
15335
|
});
|
|
14837
|
-
import { existsSync as
|
|
14838
|
-
import
|
|
15336
|
+
import { existsSync as existsSync35, readFileSync as readFileSync32, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
|
|
15337
|
+
import path45 from "node:path";
|
|
14839
15338
|
function interactionCoverage(behaviors) {
|
|
14840
15339
|
const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
|
|
14841
15340
|
const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
|
|
@@ -14912,7 +15411,6 @@ function operabilityLine(state) {
|
|
|
14912
15411
|
return void 0;
|
|
14913
15412
|
}
|
|
14914
15413
|
function foldConfigStatus(s, failDemotions, substitutedFamilies) {
|
|
14915
|
-
const { exact: _exact, ...reported } = s;
|
|
14916
15414
|
let status = tierOf(s, BARS2.cert);
|
|
14917
15415
|
const certDemote = [];
|
|
14918
15416
|
let absentInkDemoted = false;
|
|
@@ -14935,7 +15433,7 @@ function foldConfigStatus(s, failDemotions, substitutedFamilies) {
|
|
|
14935
15433
|
status = "pass";
|
|
14936
15434
|
certDemote.push(`substituted fonts: cache lacks ${substitutedFamilies.map((f) => `"${f}"`).join(", ")} \u2014 certification never measures a substitute face`);
|
|
14937
15435
|
}
|
|
14938
|
-
const base = certDemote.length > 0 ? { ...
|
|
15436
|
+
const base = certDemote.length > 0 ? { ...s, status, demotedBy: certDemote } : { ...s, status };
|
|
14939
15437
|
return {
|
|
14940
15438
|
row: failDemotions === void 0 ? base : { ...base, status: "fail", demotedBy: [...certDemote, ...failDemotions] },
|
|
14941
15439
|
absentInkDemoted
|
|
@@ -15077,10 +15575,26 @@ function compositionReport(input) {
|
|
|
15077
15575
|
function eyeCheck(bundleDir) {
|
|
15078
15576
|
return {
|
|
15079
15577
|
command: tendrilCommand(`inspect "${bundleDir}"`),
|
|
15080
|
-
sheetPath:
|
|
15578
|
+
sheetPath: path45.join(bundleDir, "verify-evidence", "inspect.html"),
|
|
15081
15579
|
note: "magnified recorded-vs-rendered crops of every small node; scores cannot see shape. The command WRITES/refreshes the sheet at sheetPath \u2014 re-run it after every verify; an existing sheet may show stale crops."
|
|
15082
15580
|
};
|
|
15083
15581
|
}
|
|
15582
|
+
function evidenceArtifacts(evidenceDir, reps) {
|
|
15583
|
+
const named = (name) => existsSync35(path45.join(evidenceDir, name)) ? name : null;
|
|
15584
|
+
return {
|
|
15585
|
+
legend: named("diff-legend.txt"),
|
|
15586
|
+
configs: reps.map((rep) => {
|
|
15587
|
+
const absentInk = [];
|
|
15588
|
+
for (let i = 0; ; i += 1) {
|
|
15589
|
+
const ref = named(`${rep}-absent-${i}-ref.png`);
|
|
15590
|
+
const render = named(`${rep}-absent-${i}-render.png`);
|
|
15591
|
+
if (ref === null || render === null) break;
|
|
15592
|
+
absentInk.push({ ref, render });
|
|
15593
|
+
}
|
|
15594
|
+
return { rep, render: named(`${rep}-render.png`), ref: named(`${rep}-ref.png`), diff: named(`${rep}-diff.png`), absentInk };
|
|
15595
|
+
})
|
|
15596
|
+
};
|
|
15597
|
+
}
|
|
15084
15598
|
function failureTally(t) {
|
|
15085
15599
|
return `${t.configs.length} config(s), ${t.behaviors.length} behavior(s), ${t.structural.length + t.crops.length} composition check(s), ${t.occlusion.length} occlusion check(s) below the ${t.bar} bar${t.evidenceUnverified ? "; PLUS the interaction-evidence instrument gate failed (operability could not be verified \u2014 see FAIL interaction-evidence above)" : ""} \u2014 bundle written, verdict honest (Q4)`;
|
|
15086
15600
|
}
|
|
@@ -15114,7 +15628,7 @@ function taskFromManifest(opts, manifest, setDir) {
|
|
|
15114
15628
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15115
15629
|
const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
15116
15630
|
const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
|
|
15117
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15631
|
+
const registry = Object.values(TASKS).find((t) => path45.resolve(t.set) === path45.resolve(setDir));
|
|
15118
15632
|
const authored = (() => {
|
|
15119
15633
|
if (registry !== void 0) return void 0;
|
|
15120
15634
|
try {
|
|
@@ -15174,19 +15688,19 @@ function verdictCaveatsFor(input) {
|
|
|
15174
15688
|
async function runVerify(opts) {
|
|
15175
15689
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15176
15690
|
let recordingSetDrift;
|
|
15177
|
-
const setOverride = opts.set !== void 0 ?
|
|
15178
|
-
opts = { ...opts, bundleDir:
|
|
15179
|
-
if (!
|
|
15691
|
+
const setOverride = opts.set !== void 0 ? path45.resolve(callerCwd, opts.set) : void 0;
|
|
15692
|
+
opts = { ...opts, bundleDir: path45.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
|
|
15693
|
+
if (!existsSync35(opts.bundleDir)) {
|
|
15180
15694
|
fail(opts, ExitCode.InputValidation, {
|
|
15181
15695
|
error: `bundle directory not found: ${opts.bundleDir}`,
|
|
15182
15696
|
code: "bundle-missing",
|
|
15183
15697
|
remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
|
|
15184
15698
|
});
|
|
15185
15699
|
}
|
|
15186
|
-
const manifestPath2 =
|
|
15700
|
+
const manifestPath2 = path45.join(opts.bundleDir, "component.json");
|
|
15187
15701
|
let manifest;
|
|
15188
|
-
if (
|
|
15189
|
-
const { manifest: parsed, issues } = readBundleManifest(
|
|
15702
|
+
if (existsSync35(manifestPath2)) {
|
|
15703
|
+
const { manifest: parsed, issues } = readBundleManifest(readFileSync32(manifestPath2, "utf8"));
|
|
15190
15704
|
if (issues.length > 0) {
|
|
15191
15705
|
fail(opts, ExitCode.InputValidation, {
|
|
15192
15706
|
error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -15217,21 +15731,21 @@ async function runVerify(opts) {
|
|
|
15217
15731
|
task = registry;
|
|
15218
15732
|
} else if (manifest !== void 0) {
|
|
15219
15733
|
const resolveSetDir = (p) => {
|
|
15220
|
-
if (
|
|
15221
|
-
const fromRepo =
|
|
15222
|
-
if (
|
|
15223
|
-
return
|
|
15734
|
+
if (path45.isAbsolute(p)) return p;
|
|
15735
|
+
const fromRepo = path45.resolve(REPO_ROOT, p);
|
|
15736
|
+
if (existsSync35(fromRepo)) return fromRepo;
|
|
15737
|
+
return path45.resolve(callerCwd, p);
|
|
15224
15738
|
};
|
|
15225
15739
|
const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
|
|
15226
|
-
if (!
|
|
15740
|
+
if (!existsSync35(path45.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path45.resolve(t.set) === path45.resolve(setDir))) {
|
|
15227
15741
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
15228
15742
|
error: `recording set not found or unmanifested: ${setDir}`,
|
|
15229
15743
|
code: "recording-set-missing",
|
|
15230
15744
|
remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
|
|
15231
15745
|
});
|
|
15232
15746
|
}
|
|
15233
|
-
const registry = Object.values(TASKS).find((t) =>
|
|
15234
|
-
if (registry !== void 0 && !
|
|
15747
|
+
const registry = Object.values(TASKS).find((t) => path45.resolve(t.set) === path45.resolve(setDir));
|
|
15748
|
+
if (registry !== void 0 && !existsSync35(path45.join(setDir, "recording-set.json"))) {
|
|
15235
15749
|
const recordedSlugs = registry.configs.map((c) => c.rep);
|
|
15236
15750
|
const adapterSlugs = Object.keys(manifest.propAdapter);
|
|
15237
15751
|
unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
|
|
@@ -15263,9 +15777,9 @@ async function runVerify(opts) {
|
|
|
15263
15777
|
recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
|
|
15264
15778
|
}
|
|
15265
15779
|
for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
|
|
15266
|
-
const p =
|
|
15267
|
-
if (!
|
|
15268
|
-
const issues = checkBundleSourceFile(name, new Uint8Array(
|
|
15780
|
+
const p = path45.join(opts.bundleDir, name);
|
|
15781
|
+
if (!existsSync35(p)) continue;
|
|
15782
|
+
const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync32(p)));
|
|
15269
15783
|
if (issues.length > 0) {
|
|
15270
15784
|
fail(opts, ExitCode.InputValidation, {
|
|
15271
15785
|
error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
|
|
@@ -15303,7 +15817,7 @@ async function runVerify(opts) {
|
|
|
15303
15817
|
warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
|
|
15304
15818
|
}
|
|
15305
15819
|
const missing = task.configs.filter(
|
|
15306
|
-
(c) => !
|
|
15820
|
+
(c) => !existsSync35(path45.join(task.set, c.rep, "get_screenshot.json")) || !existsSync35(path45.join(task.set, c.rep, "get_metadata.json"))
|
|
15307
15821
|
);
|
|
15308
15822
|
if (missing.length > 0) {
|
|
15309
15823
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -15313,7 +15827,8 @@ async function runVerify(opts) {
|
|
|
15313
15827
|
});
|
|
15314
15828
|
}
|
|
15315
15829
|
const bar = BARS2[opts.bar];
|
|
15316
|
-
const evidenceDir =
|
|
15830
|
+
const evidenceDir = path45.join(opts.bundleDir, "verify-evidence");
|
|
15831
|
+
rmSync5(evidenceDir, { recursive: true, force: true });
|
|
15317
15832
|
const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
15318
15833
|
const quality = await checkBundleQuality(
|
|
15319
15834
|
opts.bundleDir,
|
|
@@ -15330,7 +15845,7 @@ async function runVerify(opts) {
|
|
|
15330
15845
|
// ASKED, never "follows every convention".
|
|
15331
15846
|
loadCodebaseProfile(opts, opts.profile) ?? void 0
|
|
15332
15847
|
);
|
|
15333
|
-
const bundleCss = ["tokens.css", "styles.css"].map((f) =>
|
|
15848
|
+
const bundleCss = ["tokens.css", "styles.css"].map((f) => path45.join(opts.bundleDir, f)).filter((f) => existsSync35(f)).map((f) => readFileSync32(f, "utf8")).join("\n");
|
|
15334
15849
|
const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
|
|
15335
15850
|
const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs);
|
|
15336
15851
|
const framing = checkAdapterFraming(
|
|
@@ -15346,10 +15861,10 @@ async function runVerify(opts) {
|
|
|
15346
15861
|
warn(opts, `compositions extension REJECTED (${crossComposition.malformed}) \u2014 the cross-bundle backstop did NOT run over it; repair the manifest entry and re-verify. This is an instrument failure, not a clean bill.`);
|
|
15347
15862
|
}
|
|
15348
15863
|
const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15349
|
-
const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ?
|
|
15864
|
+
const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path45.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
|
|
15350
15865
|
const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
|
|
15351
15866
|
const renderExpectations = verifyPins.pins.map((pin) => ({
|
|
15352
|
-
modulePath:
|
|
15867
|
+
modulePath: path45.join(composedModuleDir(pin.partnerName), pin.entryComponent),
|
|
15353
15868
|
component: pin.entryComponent,
|
|
15354
15869
|
stampName: `${pin.pairKey}:${pin.entryComponent}`,
|
|
15355
15870
|
hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
|
|
@@ -15407,8 +15922,10 @@ async function runVerify(opts) {
|
|
|
15407
15922
|
}
|
|
15408
15923
|
})();
|
|
15409
15924
|
const compositionBlock = compositionReport({ availability, structural, crops, regions: regionsOut, composedPairs: composedPairs.size });
|
|
15925
|
+
const scoredFiles = digestScoredFiles(opts.bundleDir, manifest !== void 0 ? manifest.entry : task.entry);
|
|
15410
15926
|
const report = {
|
|
15411
15927
|
bundle: opts.bundleDir,
|
|
15928
|
+
scoredFiles,
|
|
15412
15929
|
...opts.task !== void 0 ? { task: opts.task } : {},
|
|
15413
15930
|
...manifest !== void 0 ? { bundleManifest: { name: manifest.name, bundleVersion: manifest.bundleVersion, recordingSetHash: manifest.provenance.recordingSet.hash } } : {},
|
|
15414
15931
|
targetBar: opts.bar,
|
|
@@ -15473,7 +15990,7 @@ async function runVerify(opts) {
|
|
|
15473
15990
|
} : {},
|
|
15474
15991
|
configs: statuses,
|
|
15475
15992
|
behaviors,
|
|
15476
|
-
evidence: { dir: evidenceDir,
|
|
15993
|
+
evidence: { dir: evidenceDir, ...evidenceArtifacts(evidenceDir, statuses.map((s) => s.rep)) },
|
|
15477
15994
|
composition: compositionBlock,
|
|
15478
15995
|
verdict: ok ? "verified" : "verification-failed",
|
|
15479
15996
|
// Run-23 R5: the one-word verdict printed beside "coverage
|
|
@@ -15634,7 +16151,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
15634
16151
|
}
|
|
15635
16152
|
process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
|
|
15636
16153
|
`);
|
|
15637
|
-
const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) =>
|
|
16154
|
+
const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path45.join(opts.bundleDir, f)).filter((f) => existsSync35(f)).map((f) => readFileSync32(f, "utf8")).join("\n")));
|
|
15638
16155
|
if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
|
|
15639
16156
|
process.stdout.write(`fonts: scored with Tendril-cache faces \u2014 a consuming app must provision the same families (the bundle ships fonts.css when faces are shippable; sha-pinned list in component.json requiredFonts)
|
|
15640
16157
|
`);
|
|
@@ -15686,8 +16203,27 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
|
|
|
15686
16203
|
);
|
|
15687
16204
|
process.exitCode = ExitCode.VerificationFailed;
|
|
15688
16205
|
}
|
|
16206
|
+
persistReport(opts, report, evidenceDir);
|
|
15689
16207
|
}
|
|
15690
|
-
|
|
16208
|
+
function persistReport(opts, report, evidenceDir) {
|
|
16209
|
+
if (!existsSync35(evidenceDir)) return;
|
|
16210
|
+
const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
|
|
16211
|
+
const withExit = {
|
|
16212
|
+
...report,
|
|
16213
|
+
rulerExit,
|
|
16214
|
+
...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
|
|
16215
|
+
};
|
|
16216
|
+
try {
|
|
16217
|
+
writeFileSync14(
|
|
16218
|
+
path45.join(evidenceDir, VERIFY_REPORT_FILENAME),
|
|
16219
|
+
`${JSON.stringify(verifyReportForTransport(withExit, path45.basename(opts.bundleDir)), null, 2)}
|
|
16220
|
+
`
|
|
16221
|
+
);
|
|
16222
|
+
} catch (e) {
|
|
16223
|
+
warn(opts, `could not write ${VERIFY_REPORT_FILENAME} (${e instanceof Error ? e.message : String(e)}) \u2014 the verdict above stands, but this run left no persisted report beside its evidence.`);
|
|
16224
|
+
}
|
|
16225
|
+
}
|
|
16226
|
+
var BARS2, NO_INTERACTIVE_POSES, NO_ROLE_MANIFEST, ROLES_NOT_RESOLVED, UNSTAMPED_ROLES, COMPOSE_ON_GENERATE_ARMED, NO_OVERLAY_DECLARED, EXIT_REFUSALS;
|
|
15691
16227
|
var init_verify = __esm({
|
|
15692
16228
|
"packages/cli/src/commands/verify.ts"() {
|
|
15693
16229
|
"use strict";
|
|
@@ -15717,6 +16253,10 @@ var init_verify = __esm({
|
|
|
15717
16253
|
UNSTAMPED_ROLES = "unstamped";
|
|
15718
16254
|
COMPOSE_ON_GENERATE_ARMED = false;
|
|
15719
16255
|
NO_OVERLAY_DECLARED = "no overlay declared";
|
|
16256
|
+
EXIT_REFUSALS = {
|
|
16257
|
+
[ExitCode.VerificationFailed]: "the ruler refused this run: one or more checks fell below the requested bar",
|
|
16258
|
+
[ExitCode.FontsUnproven]: "the ruler refused this run: required font faces were not provisioned, so scores measure a substitute"
|
|
16259
|
+
};
|
|
15720
16260
|
}
|
|
15721
16261
|
});
|
|
15722
16262
|
|
|
@@ -15726,11 +16266,11 @@ __export(engine_exports, {
|
|
|
15726
16266
|
runEngineBrief: () => runEngineBrief,
|
|
15727
16267
|
runEngineScore: () => runEngineScore
|
|
15728
16268
|
});
|
|
15729
|
-
import { appendFileSync, existsSync as
|
|
15730
|
-
import
|
|
16269
|
+
import { appendFileSync, existsSync as existsSync36, mkdirSync as mkdirSync10, readFileSync as readFileSync33, writeFileSync as writeFileSync15 } from "node:fs";
|
|
16270
|
+
import path46 from "node:path";
|
|
15731
16271
|
function resolveEngineTask(opts, callerCwd) {
|
|
15732
|
-
const asPath =
|
|
15733
|
-
const isSet =
|
|
16272
|
+
const asPath = path46.resolve(callerCwd, opts.taskOrSet);
|
|
16273
|
+
const isSet = existsSync36(path46.join(asPath, "recording-set.json"));
|
|
15734
16274
|
const registry = TASKS[opts.taskOrSet];
|
|
15735
16275
|
if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
|
|
15736
16276
|
if (isSet) {
|
|
@@ -15739,7 +16279,7 @@ function resolveEngineTask(opts, callerCwd) {
|
|
|
15739
16279
|
for (const d of authored.disclosures) warn(opts, d);
|
|
15740
16280
|
return {
|
|
15741
16281
|
task: authored.task,
|
|
15742
|
-
name:
|
|
16282
|
+
name: path46.basename(asPath),
|
|
15743
16283
|
ref: asPath,
|
|
15744
16284
|
disclosures: authored.disclosures,
|
|
15745
16285
|
interactionEvidence: authored.api.interactionEvidence,
|
|
@@ -15767,13 +16307,13 @@ function runEngineBrief(opts) {
|
|
|
15767
16307
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15768
16308
|
const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
|
|
15769
16309
|
const bar = BARS3[opts.bar];
|
|
15770
|
-
if (
|
|
16310
|
+
if (existsSync36(path46.join(task.set, "recording-set.json"))) {
|
|
15771
16311
|
try {
|
|
15772
|
-
const { open } = compositionPairsFor(
|
|
16312
|
+
const { open } = compositionPairsFor(path46.resolve(task.set), [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
|
|
15773
16313
|
if (open.length > 0) {
|
|
15774
16314
|
const componentNames = [...new Set(open.map((p) => p.displayName))];
|
|
15775
16315
|
disclosures.push(
|
|
15776
|
-
`COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${
|
|
16316
|
+
`COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${path46.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
|
|
15777
16317
|
);
|
|
15778
16318
|
}
|
|
15779
16319
|
} catch (err) {
|
|
@@ -15790,9 +16330,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
15790
16330
|
const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
|
|
15791
16331
|
const segments = buildSegments(task, "files");
|
|
15792
16332
|
let notRecorded;
|
|
15793
|
-
const manifestPath2 =
|
|
15794
|
-
if (
|
|
15795
|
-
notRecorded = JSON.parse(
|
|
16333
|
+
const manifestPath2 = path46.join(task.set, "recording-set.json");
|
|
16334
|
+
if (existsSync36(manifestPath2)) {
|
|
16335
|
+
notRecorded = JSON.parse(readFileSync33(manifestPath2, "utf8")).notRecorded;
|
|
15796
16336
|
}
|
|
15797
16337
|
const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
|
|
15798
16338
|
|
|
@@ -15800,7 +16340,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
|
|
|
15800
16340
|
DO NOT INVENT PIXELS FOR THESE POSES. Implement them ONLY as the composition of recorded per-axis truth (the custom-property composition rule: one paint axis sets variables, the other consumes) and list every such pose in your report as UNRECORDED-COMPOSED. Recording them is the real fix: re-plan the set (full matrix is the default) and record the missing poses.
|
|
15801
16341
|
${notRecorded}` : "";
|
|
15802
16342
|
let fontProvisioning;
|
|
15803
|
-
if (
|
|
16343
|
+
if (existsSync36(manifestPath2)) {
|
|
15804
16344
|
const missingFams = unprovisionedFamilies(task.set);
|
|
15805
16345
|
const unprovided = unprovisionedFaces(task.set);
|
|
15806
16346
|
const weightOnly = missingFams.length === 0;
|
|
@@ -15822,7 +16362,7 @@ ${notRecorded}` : "";
|
|
|
15822
16362
|
};
|
|
15823
16363
|
}
|
|
15824
16364
|
}
|
|
15825
|
-
const pinsResult = composedPins(task.set, [opts.library !== void 0 ?
|
|
16365
|
+
const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
|
|
15826
16366
|
const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
|
|
15827
16367
|
const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
|
|
15828
16368
|
|
|
@@ -15858,10 +16398,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
|
|
|
15858
16398
|
|
|
15859
16399
|
=== TASK PAYLOAD (recorded truth, verbatim) ===
|
|
15860
16400
|
${segments}`;
|
|
15861
|
-
const payloadFile =
|
|
15862
|
-
const candidateDirSuggestion =
|
|
15863
|
-
|
|
15864
|
-
|
|
16401
|
+
const payloadFile = path46.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
|
|
16402
|
+
const candidateDirSuggestion = path46.resolve(callerCwd, `tendril-out/${name}-candidate`);
|
|
16403
|
+
mkdirSync10(path46.dirname(payloadFile), { recursive: true });
|
|
16404
|
+
writeFileSync15(payloadFile, payload);
|
|
15865
16405
|
emitData(
|
|
15866
16406
|
opts,
|
|
15867
16407
|
{
|
|
@@ -15907,7 +16447,7 @@ ${segments}`;
|
|
|
15907
16447
|
// command must search the same bundle roots the pins came
|
|
15908
16448
|
// from, or the oracle and the brief describe different worlds.
|
|
15909
16449
|
`Run \`${tendrilCommand(
|
|
15910
|
-
`engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(
|
|
16450
|
+
`engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path46.resolve(callerCwd, opts.library))}` : ""} --json`
|
|
15911
16451
|
)}\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model. If you wrote the bundle elsewhere, pass that directory instead.`,
|
|
15912
16452
|
"Apply the returned feedback and re-score. Stop when all checks pass. Two consecutive non-improving scores mean INSPECT the verify-evidence diffs before deciding \u2014 stop only when inspection yields no fix hypothesis (a measured run was byte-identical twice, then went 27/27 after reading the diffs).",
|
|
15913
16453
|
"Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
|
|
@@ -15922,8 +16462,8 @@ ${segments}`;
|
|
|
15922
16462
|
);
|
|
15923
16463
|
}
|
|
15924
16464
|
function appendScoreHistory(candidateDir, entry) {
|
|
15925
|
-
const file =
|
|
15926
|
-
const starts =
|
|
16465
|
+
const file = path46.join(candidateDir, "score-history.jsonl");
|
|
16466
|
+
const starts = existsSync36(file) ? readFileSync33(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
|
|
15927
16467
|
const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
|
|
15928
16468
|
appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
|
|
15929
16469
|
`);
|
|
@@ -15931,9 +16471,9 @@ function appendScoreHistory(candidateDir, entry) {
|
|
|
15931
16471
|
async function runEngineScore(opts) {
|
|
15932
16472
|
requireEntitlement(opts);
|
|
15933
16473
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
15934
|
-
const candidateDir =
|
|
16474
|
+
const candidateDir = path46.resolve(callerCwd, opts.candidateDir);
|
|
15935
16475
|
const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
|
|
15936
|
-
if (!
|
|
16476
|
+
if (!existsSync36(candidateDir)) {
|
|
15937
16477
|
fail(opts, ExitCode.InputValidation, {
|
|
15938
16478
|
error: `candidate directory not found: ${candidateDir}`,
|
|
15939
16479
|
code: "candidate-missing",
|
|
@@ -15958,10 +16498,10 @@ async function runEngineScore(opts) {
|
|
|
15958
16498
|
for (const g of missingWeights(task.set)) {
|
|
15959
16499
|
warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
|
|
15960
16500
|
}
|
|
15961
|
-
if (opts.rebind !== true &&
|
|
16501
|
+
if (opts.rebind !== true && existsSync36(path46.join(candidateDir, "component.json"))) {
|
|
15962
16502
|
const prior = (() => {
|
|
15963
16503
|
try {
|
|
15964
|
-
const read = readBundleManifest(
|
|
16504
|
+
const read = readBundleManifest(readFileSync33(path46.join(candidateDir, "component.json"), "utf8"));
|
|
15965
16505
|
return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
|
|
15966
16506
|
} catch {
|
|
15967
16507
|
return { unreadable: true };
|
|
@@ -15983,12 +16523,12 @@ async function runEngineScore(opts) {
|
|
|
15983
16523
|
}
|
|
15984
16524
|
}
|
|
15985
16525
|
const bar = BARS3[opts.bar];
|
|
15986
|
-
const evidenceDir =
|
|
16526
|
+
const evidenceDir = path46.join(candidateDir, "verify-evidence");
|
|
15987
16527
|
const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
|
|
15988
16528
|
const parity = await checkHoverParity(task, candidateDir, task.configs);
|
|
15989
16529
|
const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
|
|
15990
16530
|
const occlusionFailures = occlusionRows.filter((o) => !o.pass);
|
|
15991
|
-
const scorePins = composedPins(task.set, [opts.library !== void 0 ?
|
|
16531
|
+
const scorePins = composedPins(task.set, [opts.library !== void 0 ? path46.resolve(callerCwd, opts.library) : callerCwd]);
|
|
15992
16532
|
const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
|
|
15993
16533
|
const behaviors = [...await checkBehaviors(task, candidateDir), ...parity, ...composition];
|
|
15994
16534
|
const parityCoverage = parity.length > 0 ? `${parity.filter((p) => p.pass).length}/${parity.length} hover-forced configs` : "not applicable (no hover-forced configs in this set)";
|
|
@@ -16206,11 +16746,11 @@ var codeconnect_exports = {};
|
|
|
16206
16746
|
__export(codeconnect_exports, {
|
|
16207
16747
|
runCodeConnect: () => runCodeConnect
|
|
16208
16748
|
});
|
|
16209
|
-
import { existsSync as
|
|
16210
|
-
import
|
|
16749
|
+
import { existsSync as existsSync37, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "node:fs";
|
|
16750
|
+
import path47 from "node:path";
|
|
16211
16751
|
function runCodeConnect(opts) {
|
|
16212
16752
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
16213
|
-
const bundleDir =
|
|
16753
|
+
const bundleDir = path47.resolve(callerCwd, opts.bundleDir);
|
|
16214
16754
|
let url;
|
|
16215
16755
|
try {
|
|
16216
16756
|
url = new URL(opts.figmaUrl);
|
|
@@ -16226,7 +16766,7 @@ function runCodeConnect(opts) {
|
|
|
16226
16766
|
}
|
|
16227
16767
|
let manifest;
|
|
16228
16768
|
try {
|
|
16229
|
-
const read = readBundleManifest(
|
|
16769
|
+
const read = readBundleManifest(readFileSync34(path47.join(bundleDir, "component.json"), "utf8"));
|
|
16230
16770
|
if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
|
|
16231
16771
|
manifest = read.manifest;
|
|
16232
16772
|
} catch (err) {
|
|
@@ -16236,8 +16776,8 @@ function runCodeConnect(opts) {
|
|
|
16236
16776
|
remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
|
|
16237
16777
|
});
|
|
16238
16778
|
}
|
|
16239
|
-
const setDir =
|
|
16240
|
-
if (!
|
|
16779
|
+
const setDir = path47.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
|
|
16780
|
+
if (!existsSync37(path47.join(setDir, "recording-set.json"))) {
|
|
16241
16781
|
fail(opts, ExitCode.InputValidation, {
|
|
16242
16782
|
error: `recording set not found at ${setDir}`,
|
|
16243
16783
|
code: "codeconnect-no-set",
|
|
@@ -16258,10 +16798,10 @@ function runCodeConnect(opts) {
|
|
|
16258
16798
|
const component = api.component;
|
|
16259
16799
|
const recManifest = loadManifest(setDir);
|
|
16260
16800
|
const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
|
|
16261
|
-
const meta =
|
|
16262
|
-
if (!
|
|
16801
|
+
const meta = path47.join(setDir, r.slug, "get_metadata.json");
|
|
16802
|
+
if (!existsSync37(meta)) return void 0;
|
|
16263
16803
|
try {
|
|
16264
|
-
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(
|
|
16804
|
+
return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync34(meta, "utf8"))))?.[1];
|
|
16265
16805
|
} catch {
|
|
16266
16806
|
return void 0;
|
|
16267
16807
|
}
|
|
@@ -16326,7 +16866,7 @@ function runCodeConnect(opts) {
|
|
|
16326
16866
|
axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
|
|
16327
16867
|
fragmentVars.push(varName);
|
|
16328
16868
|
}
|
|
16329
|
-
const entryRel =
|
|
16869
|
+
const entryRel = path47.relative(callerCwd, path47.join(bundleDir, manifest.entry));
|
|
16330
16870
|
const trust = manifest.trustStatement.split("\n")[0] ?? "";
|
|
16331
16871
|
const lines = [
|
|
16332
16872
|
`// url=${opts.figmaUrl}`,
|
|
@@ -16350,8 +16890,8 @@ function runCodeConnect(opts) {
|
|
|
16350
16890
|
`}`,
|
|
16351
16891
|
``
|
|
16352
16892
|
].join("\n");
|
|
16353
|
-
const outFile =
|
|
16354
|
-
|
|
16893
|
+
const outFile = path47.resolve(callerCwd, opts.out ?? path47.join(bundleDir, `${component}.figma.ts`));
|
|
16894
|
+
writeFileSync16(outFile, lines);
|
|
16355
16895
|
emitData(
|
|
16356
16896
|
opts,
|
|
16357
16897
|
{
|
|
@@ -16389,18 +16929,18 @@ var init_codeconnect = __esm({
|
|
|
16389
16929
|
});
|
|
16390
16930
|
|
|
16391
16931
|
// packages/mcp/src/server.ts
|
|
16392
|
-
import { createHash as
|
|
16393
|
-
import { existsSync as
|
|
16394
|
-
import
|
|
16395
|
-
import
|
|
16932
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
16933
|
+
import { existsSync as existsSync38, mkdtempSync as mkdtempSync3, readFileSync as readFileSync35, readdirSync as readdirSync15, writeFileSync as writeFileSync17 } from "node:fs";
|
|
16934
|
+
import os8 from "node:os";
|
|
16935
|
+
import path48 from "node:path";
|
|
16396
16936
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
16397
16937
|
import { z as z14 } from "zod";
|
|
16398
16938
|
function sourceHash() {
|
|
16399
|
-
const dir =
|
|
16400
|
-
const h =
|
|
16401
|
-
for (const f of
|
|
16939
|
+
const dir = path48.dirname(fileURLToPath6(import.meta.url));
|
|
16940
|
+
const h = createHash9("sha256");
|
|
16941
|
+
for (const f of readdirSync15(dir).filter((n) => n.endsWith(".ts")).sort()) {
|
|
16402
16942
|
h.update(f);
|
|
16403
|
-
h.update(
|
|
16943
|
+
h.update(readFileSync35(path48.join(dir, f)));
|
|
16404
16944
|
}
|
|
16405
16945
|
return h.digest("hex").slice(0, 16);
|
|
16406
16946
|
}
|
|
@@ -16408,10 +16948,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
|
|
|
16408
16948
|
var init_server = __esm({
|
|
16409
16949
|
"packages/mcp/src/server.ts"() {
|
|
16410
16950
|
"use strict";
|
|
16411
|
-
REPO_ROOT3 =
|
|
16412
|
-
CLI_BIN =
|
|
16413
|
-
BUNDLED_CLI =
|
|
16414
|
-
CLI_SPAWN =
|
|
16951
|
+
REPO_ROOT3 = path48.resolve(path48.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
|
|
16952
|
+
CLI_BIN = path48.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
|
|
16953
|
+
BUNDLED_CLI = path48.join(path48.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
|
|
16954
|
+
CLI_SPAWN = existsSync38(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
|
|
16415
16955
|
str = (d) => z14.string().describe(d);
|
|
16416
16956
|
optStr = (d) => z14.string().optional().describe(d);
|
|
16417
16957
|
TOOLS = [
|
|
@@ -16442,13 +16982,13 @@ var init_server = __esm({
|
|
|
16442
16982
|
const single = i["metadata"];
|
|
16443
16983
|
const parts = i["metadataParts"];
|
|
16444
16984
|
if (single !== void 0 || parts !== void 0) {
|
|
16445
|
-
const tmp =
|
|
16985
|
+
const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
16446
16986
|
if (single !== void 0) {
|
|
16447
|
-
|
|
16987
|
+
writeFileSync17(tmp, single);
|
|
16448
16988
|
argvOut.push("--metadata-raw-file", tmp);
|
|
16449
16989
|
} else {
|
|
16450
16990
|
if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
|
|
16451
|
-
|
|
16991
|
+
writeFileSync17(tmp, JSON.stringify(parts));
|
|
16452
16992
|
argvOut.push("--metadata-raw-parts-file", tmp);
|
|
16453
16993
|
}
|
|
16454
16994
|
}
|
|
@@ -16480,16 +17020,30 @@ var init_server = __esm({
|
|
|
16480
17020
|
{
|
|
16481
17021
|
name: "tendril_doctor",
|
|
16482
17022
|
annotations: { readOnlyHint: true },
|
|
16483
|
-
description: "Machine readiness + version status in one shot: installed vs latest version (with publish date and update remediation), browser identity, font-cache state, Figma desktop MCP reachability. Use to self-diagnose before recording/scoring, or whenever versions are in question. Exit 1 = something not ready; the report says exactly what and how to fix it.",
|
|
17023
|
+
description: "Machine readiness + version status in one shot: installed vs latest version (with publish date and update remediation), browser identity, font-cache state, Figma desktop MCP reachability, and whether this machine holds a LIVE portal session (checked against the portal itself; informational \u2014 publishing needs it, verification never does; when absent, offer tendril_login). Use to self-diagnose before recording/scoring, or whenever versions are in question. Exit 1 = something not ready; the report says exactly what and how to fix it.",
|
|
16484
17024
|
schema: z14.object({}),
|
|
16485
17025
|
argv: () => ["doctor"]
|
|
16486
17026
|
},
|
|
16487
17027
|
{
|
|
16488
|
-
name: "
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
17028
|
+
name: "tendril_login",
|
|
17029
|
+
description: "Connect this machine to the user's Tendril portal account WITHOUT the user touching a terminal \u2014 phase one of the browser-approve sign-in. Starts the handshake, opens their browser to the approve page, and returns a verification link plus a short code. RELAY BOTH to the user verbatim: they click Approve on the page, and they must check the page shows EXACTLY this code (that check is the phishing defence \u2014 an attacker can send someone else's approve link). Then call tendril_login_wait to finish. Offer this whenever tendril_doctor reports no portal session and the user wants to publish or share; publishing needs it, verification never does.",
|
|
17030
|
+
schema: z14.object({
|
|
17031
|
+
portal: optStr("portal origin override for self-hosted portals (defaults to https://app.trytendril.com)")
|
|
17032
|
+
}),
|
|
17033
|
+
argv: (i) => ["login", "--device-start", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
|
|
17034
|
+
},
|
|
17035
|
+
{
|
|
17036
|
+
name: "tendril_login_wait",
|
|
17037
|
+
description: "Phase two of the browser-approve sign-in: waits (up to ten minutes, with live progress) for the user to click Approve on the page tendril_login opened, then stores the session on this machine. Call it right after relaying the link and code. A denial, a lapse, and success each come back as their own sentence; confirm the result to the user, and on success they are ready to publish.",
|
|
17038
|
+
schema: z14.object({}),
|
|
17039
|
+
argv: () => ["login", "--device-wait"]
|
|
17040
|
+
},
|
|
17041
|
+
{
|
|
17042
|
+
name: "tendril_record_next",
|
|
17043
|
+
annotations: { readOnlyHint: true },
|
|
17044
|
+
description: "Get the next pending recording instruction (which Figma MCP tool to call for which node, and how to save it). RARELY NEEDED: every ingest/fetch response already carries `next` \u2014 use this only to resume an interrupted session. The full queue is known from plan, so independent reps may be recorded in any order (and in parallel).",
|
|
17045
|
+
schema: z14.object({ setDir: str("recording set directory") }),
|
|
17046
|
+
argv: (i) => ["record", "next", "--set", i["setDir"]]
|
|
16493
17047
|
},
|
|
16494
17048
|
{
|
|
16495
17049
|
name: "tendril_record_fetch",
|
|
@@ -16525,14 +17079,14 @@ var init_server = __esm({
|
|
|
16525
17079
|
const bridge = (label, single, parts) => {
|
|
16526
17080
|
if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
|
|
16527
17081
|
if (single === void 0 && parts === void 0) return;
|
|
16528
|
-
const tmp =
|
|
17082
|
+
const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
16529
17083
|
if (single !== void 0) {
|
|
16530
|
-
|
|
17084
|
+
writeFileSync17(tmp, single);
|
|
16531
17085
|
argvOut.push(`--${label}-file`, tmp);
|
|
16532
17086
|
} else {
|
|
16533
17087
|
const blocks = parts;
|
|
16534
17088
|
if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
|
|
16535
|
-
|
|
17089
|
+
writeFileSync17(tmp, JSON.stringify(blocks));
|
|
16536
17090
|
argvOut.push(`--${label}-parts-file`, tmp);
|
|
16537
17091
|
}
|
|
16538
17092
|
};
|
|
@@ -16573,12 +17127,12 @@ var init_server = __esm({
|
|
|
16573
17127
|
const file = i["file"];
|
|
16574
17128
|
if ([text, texts, file].filter((x) => x !== void 0).length !== 1) throw new Error("pass exactly one of `text` (single-block response), `texts` (multi-block response), or `file` (a saved envelope)");
|
|
16575
17129
|
if (file !== void 0) return [...base, "--file", file];
|
|
16576
|
-
const tmp =
|
|
17130
|
+
const tmp = path48.join(mkdtempSync3(path48.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
|
|
16577
17131
|
if (text !== void 0) {
|
|
16578
|
-
|
|
17132
|
+
writeFileSync17(tmp, text);
|
|
16579
17133
|
return [...base, "--file", tmp, "--raw"];
|
|
16580
17134
|
}
|
|
16581
|
-
|
|
17135
|
+
writeFileSync17(tmp, JSON.stringify(texts));
|
|
16582
17136
|
return [...base, "--file", tmp, "--raw-parts"];
|
|
16583
17137
|
}
|
|
16584
17138
|
},
|
|
@@ -16735,13 +17289,13 @@ __export(permissions_exports, {
|
|
|
16735
17289
|
runPermissions: () => runPermissions,
|
|
16736
17290
|
writeSelection: () => writeSelection
|
|
16737
17291
|
});
|
|
16738
|
-
import { existsSync as
|
|
16739
|
-
import
|
|
16740
|
-
import
|
|
17292
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync11, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
|
|
17293
|
+
import os9 from "node:os";
|
|
17294
|
+
import path49 from "node:path";
|
|
16741
17295
|
function mergeAllowlist(file, entries, denyEntries = []) {
|
|
16742
17296
|
let settings = {};
|
|
16743
|
-
if (
|
|
16744
|
-
settings = JSON.parse(
|
|
17297
|
+
if (existsSync39(file) && readFileSync36(file, "utf8").trim() !== "") {
|
|
17298
|
+
settings = JSON.parse(readFileSync36(file, "utf8"));
|
|
16745
17299
|
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
|
|
16746
17300
|
}
|
|
16747
17301
|
const permissions = settings["permissions"] ??= {};
|
|
@@ -16761,8 +17315,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
|
|
|
16761
17315
|
}
|
|
16762
17316
|
if (added.length > 0 || denyAdded.length > 0) {
|
|
16763
17317
|
allow.push(...added);
|
|
16764
|
-
|
|
16765
|
-
|
|
17318
|
+
mkdirSync11(path49.dirname(file), { recursive: true });
|
|
17319
|
+
writeFileSync18(file, `${JSON.stringify(settings, null, 2)}
|
|
16766
17320
|
`);
|
|
16767
17321
|
}
|
|
16768
17322
|
return { added, alreadyPresent, denyAdded };
|
|
@@ -16826,7 +17380,7 @@ async function runPermissions(flags) {
|
|
|
16826
17380
|
}
|
|
16827
17381
|
if (flags.write) {
|
|
16828
17382
|
const base = process.env["INIT_CWD"] ?? process.cwd();
|
|
16829
|
-
const file = flags.user ?
|
|
17383
|
+
const file = flags.user ? path49.join(os9.homedir(), ".claude", "settings.json") : path49.join(base, ".claude", "settings.local.json");
|
|
16830
17384
|
const { entries, denyEntries } = writeSelection(result, flags.user === true);
|
|
16831
17385
|
if (flags.dryRun) {
|
|
16832
17386
|
emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
|
|
@@ -16975,24 +17529,51 @@ __export(inspect_exports, {
|
|
|
16975
17529
|
INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
|
|
16976
17530
|
runInspect: () => runInspect
|
|
16977
17531
|
});
|
|
16978
|
-
import { existsSync as
|
|
16979
|
-
import
|
|
17532
|
+
import { existsSync as existsSync40, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
|
|
17533
|
+
import path50 from "node:path";
|
|
17534
|
+
function readVerifyReport(evidenceDir) {
|
|
17535
|
+
const p = path50.join(evidenceDir, VERIFY_REPORT_FILENAME);
|
|
17536
|
+
if (!existsSync40(p)) return void 0;
|
|
17537
|
+
try {
|
|
17538
|
+
return JSON.parse(readFileSync37(p, "utf8"));
|
|
17539
|
+
} catch {
|
|
17540
|
+
return void 0;
|
|
17541
|
+
}
|
|
17542
|
+
}
|
|
17543
|
+
function verdictBlock(report) {
|
|
17544
|
+
if (report === void 0) {
|
|
17545
|
+
return `<div class="verdict none"><h2>No verdict on file</h2><p>This evidence carries no <code>${VERIFY_REPORT_FILENAME}</code>, so these images are unlabelled pixels \u2014 nothing here says what passed. Re-run <code>tendril verify</code> to write one.</p></div>`;
|
|
17546
|
+
}
|
|
17547
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
17548
|
+
for (const c of report.configs) buckets.set(c.status, (buckets.get(c.status) ?? 0) + 1);
|
|
17549
|
+
const split = [...buckets.entries()].map(([k, n]) => `<span class="tier t-${esc(k)}">${n} ${esc(k)}</span>`).join("");
|
|
17550
|
+
const failed = report.behaviors.filter((b) => !b.pass);
|
|
17551
|
+
return `<div class="verdict ${report.verdict === "verified" ? "ok" : "bad"}"><h2>${esc(report.verdict)} <small>at bar \u201C${esc(report.targetBar)}\u201D</small></h2><p class="tiers">${split}<span class="tier">${report.behaviors.length - failed.length}/${report.behaviors.length} behaviors</span></p>` + (failed.length > 0 ? `<ul class="fails">${failed.map((b) => `<li><code>${esc(b.id)}</code>${b.detail !== void 0 ? ` \u2014 ${esc(b.detail)}` : ""}</li>`).join("")}</ul>` : "") + (report.verdictCaveats.length > 0 ? `<h3>What was never asked</h3><ul class="caveats">${report.verdictCaveats.map((c) => `<li>${esc(c)}</li>`).join("")}</ul>` : `<p class="caveats">No caveats: every question this bar asks was answered.</p>`) + `</div>`;
|
|
17552
|
+
}
|
|
17553
|
+
function scoreLine(report, rep) {
|
|
17554
|
+
const c = report?.configs.find((x) => x.rep === rep);
|
|
17555
|
+
if (c === void 0) return "";
|
|
17556
|
+
const ex = c.exact !== void 0 ? ` <span class="exact">measured ${c.exact.similarity.toFixed(6)} / ${c.exact.inkRecall.toFixed(6)}</span>` : "";
|
|
17557
|
+
const why = Array.isArray(c.demotedBy) && c.demotedBy.length > 0 ? `<span class="demoted">demoted: ${esc(c.demotedBy.join("; "))}</span>` : "";
|
|
17558
|
+
const err = typeof c.error === "string" ? `<span class="demoted">${esc(c.error)}</span>` : "";
|
|
17559
|
+
return `<p class="scores"><span class="tier t-${esc(c.status)}">${esc(c.status)}</span> sim ${c.similarity} \xB7 ink ${c.inkRecall}${ex} ${why} ${err}</p>`;
|
|
17560
|
+
}
|
|
16980
17561
|
async function runInspect(opts) {
|
|
16981
17562
|
if (opts.describe) {
|
|
16982
17563
|
printDescription(INSPECT_DESCRIPTION);
|
|
16983
17564
|
return;
|
|
16984
17565
|
}
|
|
16985
|
-
const bundleDir =
|
|
16986
|
-
const evidenceDir =
|
|
16987
|
-
const manifestPath2 =
|
|
16988
|
-
if (!
|
|
17566
|
+
const bundleDir = path50.resolve(opts.bundleDir);
|
|
17567
|
+
const evidenceDir = path50.join(bundleDir, "verify-evidence");
|
|
17568
|
+
const manifestPath2 = path50.join(bundleDir, "component.json");
|
|
17569
|
+
if (!existsSync40(evidenceDir) || !existsSync40(manifestPath2)) {
|
|
16989
17570
|
fail(opts, ExitCode.InputValidation, {
|
|
16990
|
-
error: `nothing to inspect in ${bundleDir} \u2014 ${
|
|
17571
|
+
error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync40(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
|
|
16991
17572
|
code: "no-evidence",
|
|
16992
17573
|
remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
|
|
16993
17574
|
});
|
|
16994
17575
|
}
|
|
16995
|
-
const { manifest } = readBundleManifest(
|
|
17576
|
+
const { manifest } = readBundleManifest(readFileSync37(manifestPath2, "utf8"));
|
|
16996
17577
|
if (manifest === void 0) {
|
|
16997
17578
|
fail(opts, ExitCode.InputValidation, {
|
|
16998
17579
|
error: "component.json did not parse as a bundle manifest",
|
|
@@ -17000,8 +17581,9 @@ async function runInspect(opts) {
|
|
|
17000
17581
|
remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
|
|
17001
17582
|
});
|
|
17002
17583
|
}
|
|
17003
|
-
const setDir =
|
|
17004
|
-
const
|
|
17584
|
+
const setDir = path50.resolve(opts.set ?? manifest.provenance.recordingSet.path);
|
|
17585
|
+
const report = readVerifyReport(evidenceDir);
|
|
17586
|
+
const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync40(path50.join(evidenceDir, `${rep}-ref.png`)) && existsSync40(path50.join(evidenceDir, `${rep}-render.png`)));
|
|
17005
17587
|
if (reps.length === 0) {
|
|
17006
17588
|
fail(opts, ExitCode.InputValidation, {
|
|
17007
17589
|
error: "verify-evidence holds no ref/render pairs for this bundle's configs",
|
|
@@ -17012,15 +17594,15 @@ async function runInspect(opts) {
|
|
|
17012
17594
|
let crops = 0;
|
|
17013
17595
|
const sections = [];
|
|
17014
17596
|
for (const rep of reps) {
|
|
17015
|
-
const ref = new Uint8Array(
|
|
17016
|
-
const render = new Uint8Array(
|
|
17597
|
+
const ref = new Uint8Array(readFileSync37(path50.join(evidenceDir, `${rep}-ref.png`)));
|
|
17598
|
+
const render = new Uint8Array(readFileSync37(path50.join(evidenceDir, `${rep}-render.png`)));
|
|
17017
17599
|
const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
|
|
17018
17600
|
const cells = [];
|
|
17019
17601
|
for (const [i, n] of nodes.entries()) {
|
|
17020
17602
|
const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
|
|
17021
17603
|
try {
|
|
17022
|
-
|
|
17023
|
-
|
|
17604
|
+
writeFileSync19(path50.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
|
|
17605
|
+
writeFileSync19(path50.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
|
|
17024
17606
|
} catch {
|
|
17025
17607
|
continue;
|
|
17026
17608
|
}
|
|
@@ -17030,11 +17612,15 @@ async function runInspect(opts) {
|
|
|
17030
17612
|
);
|
|
17031
17613
|
}
|
|
17032
17614
|
sections.push(
|
|
17033
|
-
`<section><h2>${esc(rep)}</h2
|
|
17615
|
+
`<section><h2>${esc(rep)}</h2>` + scoreLine(report, rep) + `<div class="full"><span><em>recorded</em><img src="./${rep}-ref.png"></span><span><em>rendered</em><img src="./${rep}-render.png"></span><span><em>diff</em><img src="./${rep}-diff.png"></span></div>` + (cells.length > 0 ? `<div class="grid">${cells.join("")}</div>` : `<p class="none">no small recorded nodes in this config's sweep</p>`) + `</section>`
|
|
17034
17616
|
);
|
|
17035
17617
|
}
|
|
17036
|
-
const
|
|
17037
|
-
|
|
17618
|
+
for (const c of report?.configs ?? []) {
|
|
17619
|
+
if (reps.includes(c.rep)) continue;
|
|
17620
|
+
sections.push(`<section class="missing"><h2>${esc(c.rep)}</h2>${scoreLine(report, c.rep)}<p class="none">No evidence images for this config \u2014 it was scored, but nothing was captured to look at.</p></section>`);
|
|
17621
|
+
}
|
|
17622
|
+
const sheet = path50.join(evidenceDir, "inspect.html");
|
|
17623
|
+
writeFileSync19(
|
|
17038
17624
|
sheet,
|
|
17039
17625
|
`<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
|
|
17040
17626
|
body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
|
|
@@ -17043,11 +17629,23 @@ h1{font-size:20px} h2{font-size:16px;border-top:1px solid #ddd;padding-top:16px}
|
|
|
17043
17629
|
.full img{max-width:400px;border:1px solid #ccc} .pair img{border:1px solid #ccc;image-rendering:pixelated}
|
|
17044
17630
|
figure{margin:0 0 16px} figcaption{margin-bottom:4px} em{display:block;color:#666;font-style:normal;font-size:12px}
|
|
17045
17631
|
.grid{display:flex;flex-wrap:wrap;gap:20px;margin-top:12px} .none{color:#666}
|
|
17046
|
-
|
|
17047
|
-
|
|
17632
|
+
.verdict{border:1px solid #ddd;border-left-width:5px;padding:12px 16px;margin:16px 0;background:#fafafa}
|
|
17633
|
+
.verdict.ok{border-left-color:#1a7f37} .verdict.bad{border-left-color:#b35900} .verdict.none{border-left-color:#999}
|
|
17634
|
+
.verdict h2{border:0;padding:0;margin:0 0 8px} .verdict h3{font-size:13px;margin:12px 0 4px;color:#444}
|
|
17635
|
+
.verdict small{font-weight:400;color:#666}
|
|
17636
|
+
.tiers{margin:0;display:flex;gap:8px;flex-wrap:wrap}
|
|
17637
|
+
.tier{display:inline-block;padding:1px 8px;border:1px solid #ccc;border-radius:10px;font-size:12px;background:#fff}
|
|
17638
|
+
.t-certified{border-color:#1a7f37;color:#1a7f37} .t-pass{border-color:#8a6d00;color:#8a6d00} .t-fail{border-color:#b3261e;color:#b3261e}
|
|
17639
|
+
.caveats,.fails{margin:4px 0 0;padding-left:20px;color:#444} .caveats li,.fails li{margin:2px 0}
|
|
17640
|
+
.scores{margin:4px 0 10px;color:#333} .exact{color:#666;font-size:12px} .demoted{color:#b3261e;font-size:12px;margin-left:8px}
|
|
17641
|
+
.missing{opacity:.85} .legend{white-space:pre-wrap;font:12px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;background:#fafafa;border:1px solid #ddd;padding:12px;overflow-x:auto}
|
|
17642
|
+
</style><h1>${esc(manifest.name)} \u2014 verdict and evidence</h1>
|
|
17643
|
+
${verdictBlock(report)}
|
|
17644
|
+
<p>Below, every crop is a recorded node small enough that global metrics weight it as a rounding error.
|
|
17048
17645
|
Scan the pairs: anything present on the left and missing/invisible on the right is a defect,
|
|
17049
17646
|
whatever the scores said. Verdicts come from <code>tendril verify</code> \u2014 this sheet only shows.</p>
|
|
17050
17647
|
${sections.join("\n")}
|
|
17648
|
+
<section><h2>Diff colours</h2><div class="legend">${esc(DIFF_LEGEND_TEXT)}</div></section>
|
|
17051
17649
|
`
|
|
17052
17650
|
);
|
|
17053
17651
|
emitData(opts, { sheet, configs: reps.length, crops }, () => {
|
|
@@ -17087,6 +17685,694 @@ var init_inspect = __esm({
|
|
|
17087
17685
|
}
|
|
17088
17686
|
});
|
|
17089
17687
|
|
|
17688
|
+
// packages/cli/src/commands/login.ts
|
|
17689
|
+
var login_exports = {};
|
|
17690
|
+
__export(login_exports, {
|
|
17691
|
+
DEFAULT_PORTAL_ORIGIN: () => DEFAULT_PORTAL_ORIGIN,
|
|
17692
|
+
runLogin: () => runLogin,
|
|
17693
|
+
runLogout: () => runLogout
|
|
17694
|
+
});
|
|
17695
|
+
import { spawn } from "node:child_process";
|
|
17696
|
+
import { existsSync as existsSync41, mkdirSync as mkdirSync12, readFileSync as readFileSync38, rmSync as rmSync6, writeFileSync as writeFileSync20 } from "node:fs";
|
|
17697
|
+
import path51 from "node:path";
|
|
17698
|
+
import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
|
|
17699
|
+
async function runLogin(opts, deps) {
|
|
17700
|
+
const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
|
|
17701
|
+
if (!isSecureOrigin(origin)) {
|
|
17702
|
+
fail(opts, ExitCode.InputValidation, {
|
|
17703
|
+
error: `${origin} is not https, so a session token sent there would travel in the clear`,
|
|
17704
|
+
code: "portal-not-https",
|
|
17705
|
+
remediation: "Use the https:// address of your portal. Only 127.0.0.1 and localhost are exempt, for local development."
|
|
17706
|
+
});
|
|
17707
|
+
}
|
|
17708
|
+
if (opts.deviceStart === true) {
|
|
17709
|
+
await deviceStartPhase(opts, origin, deps ?? realDeps());
|
|
17710
|
+
return;
|
|
17711
|
+
}
|
|
17712
|
+
if (opts.deviceWait === true) {
|
|
17713
|
+
await deviceWaitPhase(opts, deps ?? realDeps());
|
|
17714
|
+
return;
|
|
17715
|
+
}
|
|
17716
|
+
if (process.stdin.isTTY === true && opts.paste !== true) {
|
|
17717
|
+
await deviceLogin(opts, origin, deps ?? realDeps());
|
|
17718
|
+
return;
|
|
17719
|
+
}
|
|
17720
|
+
await pasteLogin(opts, origin);
|
|
17721
|
+
}
|
|
17722
|
+
async function deviceLogin(opts, origin, deps) {
|
|
17723
|
+
const started = await startHandshake(opts, origin, deps);
|
|
17724
|
+
process.stderr.write(`
|
|
17725
|
+
Open this link to approve the sign-in:
|
|
17726
|
+
|
|
17727
|
+
${started.verificationUrl}
|
|
17728
|
+
|
|
17729
|
+
`);
|
|
17730
|
+
process.stderr.write(` The page must show this code: ${started.userCode}
|
|
17731
|
+
`);
|
|
17732
|
+
process.stderr.write(` Approve it only if this terminal is yours.
|
|
17733
|
+
|
|
17734
|
+
`);
|
|
17735
|
+
deps.openBrowser(started.verificationUrl);
|
|
17736
|
+
process.stderr.write(` Waiting for the browser (about ten minutes before this code lapses) `);
|
|
17737
|
+
const outcome = await waitForDecision(opts, origin, deps, started, () => process.stderr.write("."));
|
|
17738
|
+
process.stderr.write("\n");
|
|
17739
|
+
settleDecision(opts, origin, outcome);
|
|
17740
|
+
}
|
|
17741
|
+
async function waitForDecision(opts, origin, deps, started, onTick) {
|
|
17742
|
+
const interval = Math.max(1, started.intervalSeconds);
|
|
17743
|
+
const attempts = Math.ceil(660 / interval);
|
|
17744
|
+
for (let n = 0; n < attempts; n += 1) {
|
|
17745
|
+
await deps.sleep(interval);
|
|
17746
|
+
const state = await pollHandshake(opts, origin, deps, started.deviceCode);
|
|
17747
|
+
if (state.status === "pending") {
|
|
17748
|
+
onTick();
|
|
17749
|
+
continue;
|
|
17750
|
+
}
|
|
17751
|
+
if (state.status === "approved") return state;
|
|
17752
|
+
return { status: state.status };
|
|
17753
|
+
}
|
|
17754
|
+
return { status: "gave-up" };
|
|
17755
|
+
}
|
|
17756
|
+
function settleDecision(opts, origin, outcome) {
|
|
17757
|
+
switch (outcome.status) {
|
|
17758
|
+
case "approved":
|
|
17759
|
+
writeStoredSession({ origin, token: outcome.token });
|
|
17760
|
+
emitData(opts, { origin, storedAt: sessionPath(), via: "device" }, () => {
|
|
17761
|
+
process.stdout.write(`signed in to ${origin}
|
|
17762
|
+
`);
|
|
17763
|
+
process.stdout.write(` token stored at ${sessionPath()} (readable only by you)
|
|
17764
|
+
`);
|
|
17765
|
+
});
|
|
17766
|
+
return;
|
|
17767
|
+
case "denied":
|
|
17768
|
+
fail(opts, ExitCode.General, {
|
|
17769
|
+
error: "the sign-in was denied in the browser",
|
|
17770
|
+
code: "login-denied",
|
|
17771
|
+
remediation: `If that was not you saying no, run ${tendrilCommand("login")} again and approve the fresh code.`
|
|
17772
|
+
});
|
|
17773
|
+
break;
|
|
17774
|
+
case "expired":
|
|
17775
|
+
case "gave-up":
|
|
17776
|
+
fail(opts, ExitCode.General, {
|
|
17777
|
+
error: outcome.status === "expired" ? "the sign-in code lapsed before anyone approved it \u2014 they live ten minutes" : "gave up waiting for the browser approval",
|
|
17778
|
+
code: "login-expired",
|
|
17779
|
+
remediation: `Run ${tendrilCommand("login")} again for a fresh code.`
|
|
17780
|
+
});
|
|
17781
|
+
break;
|
|
17782
|
+
default:
|
|
17783
|
+
fail(opts, ExitCode.General, {
|
|
17784
|
+
error: "the portal no longer recognises this sign-in attempt",
|
|
17785
|
+
code: "login-unknown",
|
|
17786
|
+
remediation: `Run ${tendrilCommand("login")} again. If this repeats, the approval may be racing another terminal \u2014 approve only one at a time.`
|
|
17787
|
+
});
|
|
17788
|
+
}
|
|
17789
|
+
}
|
|
17790
|
+
function pendingLoginPath() {
|
|
17791
|
+
return path51.join(path51.dirname(sessionPath()), "pending-login.json");
|
|
17792
|
+
}
|
|
17793
|
+
async function deviceStartPhase(opts, origin, deps) {
|
|
17794
|
+
const started = await startHandshake(opts, origin, deps);
|
|
17795
|
+
const file = pendingLoginPath();
|
|
17796
|
+
mkdirSync12(path51.dirname(file), { recursive: true });
|
|
17797
|
+
writeFileSync20(file, `${JSON.stringify({ origin, ...started }, null, 2)}
|
|
17798
|
+
`, { mode: 384 });
|
|
17799
|
+
deps.openBrowser(started.verificationUrl);
|
|
17800
|
+
emitData(
|
|
17801
|
+
opts,
|
|
17802
|
+
{
|
|
17803
|
+
verificationUrl: started.verificationUrl,
|
|
17804
|
+
userCode: started.userCode,
|
|
17805
|
+
expiresAt: started.expiresAt,
|
|
17806
|
+
origin,
|
|
17807
|
+
next: "Show the user the link and the code \u2014 they approve in the browser, and must check the page shows this exact code. Then run login --device-wait (the tendril_login_wait tool) to finish."
|
|
17808
|
+
},
|
|
17809
|
+
() => {
|
|
17810
|
+
process.stdout.write(`open ${started.verificationUrl}
|
|
17811
|
+
`);
|
|
17812
|
+
process.stdout.write(` the page must show: ${started.userCode}
|
|
17813
|
+
`);
|
|
17814
|
+
process.stdout.write(` then: ${tendrilCommand("login --device-wait")}
|
|
17815
|
+
`);
|
|
17816
|
+
}
|
|
17817
|
+
);
|
|
17818
|
+
}
|
|
17819
|
+
async function deviceWaitPhase(opts, deps) {
|
|
17820
|
+
const file = pendingLoginPath();
|
|
17821
|
+
let pending;
|
|
17822
|
+
if (existsSync41(file)) {
|
|
17823
|
+
try {
|
|
17824
|
+
const parsed = JSON.parse(readFileSync38(file, "utf8"));
|
|
17825
|
+
if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
|
|
17826
|
+
pending = parsed;
|
|
17827
|
+
}
|
|
17828
|
+
} catch {
|
|
17829
|
+
}
|
|
17830
|
+
}
|
|
17831
|
+
if (pending === void 0) {
|
|
17832
|
+
fail(opts, ExitCode.InputValidation, {
|
|
17833
|
+
error: "there is no sign-in waiting to finish",
|
|
17834
|
+
code: "no-pending-login",
|
|
17835
|
+
remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
|
|
17836
|
+
});
|
|
17837
|
+
}
|
|
17838
|
+
const done = () => rmSync6(file, { force: true });
|
|
17839
|
+
const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
|
|
17840
|
+
let ticks = 0;
|
|
17841
|
+
const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
|
|
17842
|
+
ticks += 1;
|
|
17843
|
+
emitProgress(ticks, total, "waiting for the browser approval");
|
|
17844
|
+
});
|
|
17845
|
+
done();
|
|
17846
|
+
settleDecision(opts, pending.origin, outcome);
|
|
17847
|
+
}
|
|
17848
|
+
async function startHandshake(opts, origin, deps) {
|
|
17849
|
+
let response;
|
|
17850
|
+
try {
|
|
17851
|
+
response = await deps.fetch(`${origin}/api/device`, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
|
|
17852
|
+
} catch (error) {
|
|
17853
|
+
return unreachable(opts, origin, error.message);
|
|
17854
|
+
}
|
|
17855
|
+
if (!response.ok) return unreachable(opts, origin, `the portal answered ${String(response.status)}`);
|
|
17856
|
+
const body = await response.json();
|
|
17857
|
+
if (typeof body.deviceCode !== "string" || typeof body.userCode !== "string" || typeof body.verificationUrl !== "string" || typeof body.intervalSeconds !== "number" || typeof body.expiresAt !== "string") {
|
|
17858
|
+
return unreachable(opts, origin, "the portal's answer was not a device handshake");
|
|
17859
|
+
}
|
|
17860
|
+
return body;
|
|
17861
|
+
}
|
|
17862
|
+
async function pollHandshake(opts, origin, deps, deviceCode) {
|
|
17863
|
+
let response;
|
|
17864
|
+
try {
|
|
17865
|
+
response = await deps.fetch(`${origin}/api/device/token`, {
|
|
17866
|
+
method: "POST",
|
|
17867
|
+
headers: { "content-type": "application/json" },
|
|
17868
|
+
body: JSON.stringify({ deviceCode })
|
|
17869
|
+
});
|
|
17870
|
+
} catch {
|
|
17871
|
+
return { status: "pending" };
|
|
17872
|
+
}
|
|
17873
|
+
if (!response.ok) return { status: "pending" };
|
|
17874
|
+
const body = await response.json();
|
|
17875
|
+
if (body.status === "approved" && typeof body.token === "string") return { status: "approved", token: body.token };
|
|
17876
|
+
if (body.status === "denied" || body.status === "expired" || body.status === "unknown") return { status: body.status };
|
|
17877
|
+
return { status: "pending" };
|
|
17878
|
+
}
|
|
17879
|
+
function unreachable(opts, origin, detail) {
|
|
17880
|
+
fail(opts, ExitCode.General, {
|
|
17881
|
+
error: `could not start a sign-in with ${origin}: ${detail}`,
|
|
17882
|
+
code: "portal-unreachable",
|
|
17883
|
+
remediation: "Check your connection and try again. If your portal is self-hosted, confirm the --to URL."
|
|
17884
|
+
});
|
|
17885
|
+
}
|
|
17886
|
+
function realDeps() {
|
|
17887
|
+
return {
|
|
17888
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
17889
|
+
sleep: (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1e3)),
|
|
17890
|
+
openBrowser: (url) => {
|
|
17891
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
17892
|
+
try {
|
|
17893
|
+
spawn(cmd, args, { stdio: "ignore", detached: true }).on("error", () => void 0).unref();
|
|
17894
|
+
} catch {
|
|
17895
|
+
}
|
|
17896
|
+
}
|
|
17897
|
+
};
|
|
17898
|
+
}
|
|
17899
|
+
async function pasteLogin(opts, origin) {
|
|
17900
|
+
const token = await readToken(opts);
|
|
17901
|
+
if (token === void 0 || token.trim() === "") {
|
|
17902
|
+
fail(opts, ExitCode.InputValidation, {
|
|
17903
|
+
error: "no token was given",
|
|
17904
|
+
code: "no-token",
|
|
17905
|
+
remediation: `Paste the token when prompted, or pipe it: \`echo "<token>" | ${tendrilCommand(`login --to ${origin}`)}\`.`
|
|
17906
|
+
});
|
|
17907
|
+
}
|
|
17908
|
+
const trimmed = token.trim();
|
|
17909
|
+
if (!/^[A-Za-z0-9_-]{16,512}$/.test(trimmed)) {
|
|
17910
|
+
fail(opts, ExitCode.InputValidation, {
|
|
17911
|
+
error: "that does not look like a Tendril session token",
|
|
17912
|
+
code: "malformed-token",
|
|
17913
|
+
remediation: "Tokens are a single line of letters, digits, hyphens and underscores. Check for a stray space or a truncated paste."
|
|
17914
|
+
});
|
|
17915
|
+
}
|
|
17916
|
+
writeStoredSession({ origin, token: trimmed });
|
|
17917
|
+
emitData(opts, { origin, storedAt: sessionPath(), via: "paste" }, () => {
|
|
17918
|
+
process.stdout.write(`signed in to ${origin}
|
|
17919
|
+
`);
|
|
17920
|
+
process.stdout.write(` token stored at ${sessionPath()} (readable only by you)
|
|
17921
|
+
`);
|
|
17922
|
+
});
|
|
17923
|
+
}
|
|
17924
|
+
async function readToken(opts) {
|
|
17925
|
+
if (process.stdin.isTTY === true) {
|
|
17926
|
+
const entered = await password2({ message: "Paste your Tendril portal token (it will not be shown)" });
|
|
17927
|
+
if (isCancel3(entered)) {
|
|
17928
|
+
fail(opts, ExitCode.General, { error: "cancelled", code: "cancelled", remediation: "Run it again when you have the token." });
|
|
17929
|
+
}
|
|
17930
|
+
return entered;
|
|
17931
|
+
}
|
|
17932
|
+
const chunks = [];
|
|
17933
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
17934
|
+
return Buffer.concat(chunks).toString("utf8").split("\n")[0];
|
|
17935
|
+
}
|
|
17936
|
+
function runLogout(opts) {
|
|
17937
|
+
const stored = readStoredSession();
|
|
17938
|
+
if (stored === void 0) {
|
|
17939
|
+
emitData(opts, { signedOut: true, wasSignedIn: false }, () => process.stdout.write("you were not signed in\n"));
|
|
17940
|
+
return;
|
|
17941
|
+
}
|
|
17942
|
+
clearStoredSession();
|
|
17943
|
+
emitData(opts, { signedOut: true, wasSignedIn: true, origin: stored.origin }, () => {
|
|
17944
|
+
process.stdout.write(`this machine has forgotten its session for ${stored.origin}
|
|
17945
|
+
`);
|
|
17946
|
+
process.stdout.write(" the session itself is still valid \u2014 sign out everywhere from the portal to end it\n");
|
|
17947
|
+
});
|
|
17948
|
+
}
|
|
17949
|
+
var DEFAULT_PORTAL_ORIGIN;
|
|
17950
|
+
var init_login = __esm({
|
|
17951
|
+
"packages/cli/src/commands/login.ts"() {
|
|
17952
|
+
"use strict";
|
|
17953
|
+
init_src3();
|
|
17954
|
+
init_invocation();
|
|
17955
|
+
init_output();
|
|
17956
|
+
init_publish_client();
|
|
17957
|
+
DEFAULT_PORTAL_ORIGIN = "https://app.trytendril.com";
|
|
17958
|
+
}
|
|
17959
|
+
});
|
|
17960
|
+
|
|
17961
|
+
// packages/cli/src/commands/share.ts
|
|
17962
|
+
var share_exports = {};
|
|
17963
|
+
__export(share_exports, {
|
|
17964
|
+
runShare: () => runShare
|
|
17965
|
+
});
|
|
17966
|
+
async function runShare(opts) {
|
|
17967
|
+
const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
|
|
17968
|
+
if (origin === "") {
|
|
17969
|
+
fail(opts, ExitCode.InputValidation, {
|
|
17970
|
+
error: "no portal to share from",
|
|
17971
|
+
code: "no-portal-configured",
|
|
17972
|
+
remediation: `Pass \`--to <url>\` or set TENDRIL_PORTAL_URL.`
|
|
17973
|
+
});
|
|
17974
|
+
}
|
|
17975
|
+
if (!isSecureOrigin(origin)) {
|
|
17976
|
+
fail(opts, ExitCode.InputValidation, {
|
|
17977
|
+
error: `${origin} is not https, so a session token sent there would travel in the clear`,
|
|
17978
|
+
code: "portal-not-https",
|
|
17979
|
+
remediation: "Use the https:// address of your portal. Only 127.0.0.1 and localhost are exempt, for local development."
|
|
17980
|
+
});
|
|
17981
|
+
}
|
|
17982
|
+
const found = tokenFor(origin);
|
|
17983
|
+
if (!found.ok) {
|
|
17984
|
+
fail(opts, ExitCode.Auth, {
|
|
17985
|
+
error: found.reason === "origin-mismatch" ? `the session available here belongs to ${found.boundTo}, not ${origin}` : `no session for ${origin}`,
|
|
17986
|
+
code: found.reason === "origin-mismatch" ? "session-belongs-to-another-portal" : "not-signed-in",
|
|
17987
|
+
remediation: `Run \`${tendrilCommand(`login --to ${origin}`)}\`.`
|
|
17988
|
+
});
|
|
17989
|
+
}
|
|
17990
|
+
const client = new HttpPublishClient({ origin, token: found.token });
|
|
17991
|
+
if (opts.list === true) {
|
|
17992
|
+
const listed = await client.listShareLinks({ publicationId: opts.publicationId });
|
|
17993
|
+
if (!listed.ok) refuse(opts, listed);
|
|
17994
|
+
emitData(opts, listed.value, () => {
|
|
17995
|
+
if (listed.value.links.length === 0) {
|
|
17996
|
+
process.stdout.write("no links have been issued for this publication\n");
|
|
17997
|
+
return;
|
|
17998
|
+
}
|
|
17999
|
+
process.stdout.write(`${String(listed.value.links.length)} link(s) for this publication:
|
|
18000
|
+
`);
|
|
18001
|
+
for (const link of listed.value.links) {
|
|
18002
|
+
const ended = link.revoked_at !== null ? ` REVOKED ${link.revoked_at}` : link.expires_at !== null ? ` expires ${link.expires_at}` : " open-ended";
|
|
18003
|
+
process.stdout.write(` ${link.id}${ended}${link.recipient_email === null ? "" : ` \u2014 for ${link.recipient_email}`}
|
|
18004
|
+
`);
|
|
18005
|
+
}
|
|
18006
|
+
process.stdout.write(" (the links themselves are not stored \u2014 only their digests, so a lost link is a new link)\n");
|
|
18007
|
+
});
|
|
18008
|
+
return;
|
|
18009
|
+
}
|
|
18010
|
+
if (opts.revoke !== void 0) {
|
|
18011
|
+
const revoked = await client.revokeShareLink({ publicationId: opts.publicationId, shareLinkId: opts.revoke });
|
|
18012
|
+
if (!revoked.ok) refuse(opts, revoked);
|
|
18013
|
+
emitData(opts, revoked.value, () => {
|
|
18014
|
+
process.stdout.write(`revoked ${opts.revoke ?? ""}
|
|
18015
|
+
`);
|
|
18016
|
+
process.stdout.write(" anyone holding that link now gets told it was revoked, not that it never existed\n");
|
|
18017
|
+
});
|
|
18018
|
+
return;
|
|
18019
|
+
}
|
|
18020
|
+
const expiresAt = resolveExpiry(opts);
|
|
18021
|
+
const issued = await client.issueShareLink({
|
|
18022
|
+
publicationId: opts.publicationId,
|
|
18023
|
+
expiresAt,
|
|
18024
|
+
recipientEmail: opts.recipient ?? null
|
|
18025
|
+
});
|
|
18026
|
+
if (!issued.ok) refuse(opts, issued);
|
|
18027
|
+
emitData(opts, issued.value, () => {
|
|
18028
|
+
process.stdout.write(`${issued.value.url}
|
|
18029
|
+
|
|
18030
|
+
`);
|
|
18031
|
+
process.stdout.write(" Anyone with this link can read this one publication and its evidence.\n");
|
|
18032
|
+
process.stdout.write(" It is not tied to a person and needs no account.\n");
|
|
18033
|
+
process.stdout.write(
|
|
18034
|
+
issued.value.expiresAt === null ? " It does not expire. Revoke it with --revoke when you are done.\n" : ` It stops working at ${issued.value.expiresAt}.
|
|
18035
|
+
`
|
|
18036
|
+
);
|
|
18037
|
+
process.stdout.write(" This is the only time the link is shown \u2014 only its digest is stored.\n");
|
|
18038
|
+
});
|
|
18039
|
+
}
|
|
18040
|
+
function resolveExpiry(opts) {
|
|
18041
|
+
if (opts.expires === void 0) return null;
|
|
18042
|
+
const days = Number(opts.expires);
|
|
18043
|
+
if (Number.isInteger(days) && days > 0) {
|
|
18044
|
+
return new Date(Date.now() + days * 24 * 60 * 60 * 1e3).toISOString().replace(/\.\d{3}Z$/, (m) => m);
|
|
18045
|
+
}
|
|
18046
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(opts.expires)) return opts.expires;
|
|
18047
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18048
|
+
error: `--expires takes a number of days (e.g. 7) or an ISO-8601 UTC timestamp with milliseconds \u2014 got ${JSON.stringify(opts.expires)}`,
|
|
18049
|
+
code: "bad-expiry",
|
|
18050
|
+
remediation: "Try `--expires 7` for a week, or omit it for a link that does not expire."
|
|
18051
|
+
});
|
|
18052
|
+
}
|
|
18053
|
+
function refuse(opts, sent) {
|
|
18054
|
+
fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
|
|
18055
|
+
error: sent.refusal,
|
|
18056
|
+
code: "share-refused",
|
|
18057
|
+
remediation: sent.status === 404 ? "Check the publication id \u2014 only its owner can share it." : `Fix what is named above and run \`${tendrilCommand(`share ${opts.publicationId}`)}\` again.`
|
|
18058
|
+
});
|
|
18059
|
+
}
|
|
18060
|
+
var init_share = __esm({
|
|
18061
|
+
"packages/cli/src/commands/share.ts"() {
|
|
18062
|
+
"use strict";
|
|
18063
|
+
init_src3();
|
|
18064
|
+
init_invocation();
|
|
18065
|
+
init_output();
|
|
18066
|
+
init_publish_client();
|
|
18067
|
+
}
|
|
18068
|
+
});
|
|
18069
|
+
|
|
18070
|
+
// packages/cli/src/commands/publish.ts
|
|
18071
|
+
var publish_exports = {};
|
|
18072
|
+
__export(publish_exports, {
|
|
18073
|
+
runPublish: () => runPublish
|
|
18074
|
+
});
|
|
18075
|
+
import { existsSync as existsSync42, readFileSync as readFileSync39 } from "node:fs";
|
|
18076
|
+
import path52 from "node:path";
|
|
18077
|
+
async function runPublish(opts) {
|
|
18078
|
+
const bundleDir = path52.resolve(opts.bundleDir);
|
|
18079
|
+
const bundle = readBundle(opts, bundleDir);
|
|
18080
|
+
const report = bundle.report;
|
|
18081
|
+
if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
|
|
18082
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18083
|
+
error: `${VERIFY_REPORT_FILENAME} does not state the ruler's exit code, and an absent exit code is not a passing one`,
|
|
18084
|
+
code: "report-has-no-exit-code",
|
|
18085
|
+
remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` with a current CLI \u2014 this report predates the field.`
|
|
18086
|
+
});
|
|
18087
|
+
}
|
|
18088
|
+
if (report["rulerExit"] !== 0) {
|
|
18089
|
+
const why = typeof report["rulerRefusal"] === "string" ? ` \u2014 ${report["rulerRefusal"]}` : "";
|
|
18090
|
+
fail(opts, ExitCode.VerificationFailed, {
|
|
18091
|
+
error: `the ruler refused this run (exit ${String(report["rulerExit"])}), so it has nothing to publish${why}`,
|
|
18092
|
+
code: "run-refused-by-ruler",
|
|
18093
|
+
remediation: `Fix what the report names, re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` until it passes, then publish. A declined verdict never becomes a page.`
|
|
18094
|
+
});
|
|
18095
|
+
}
|
|
18096
|
+
const undisclosed = undisclosedTrustFacts(report);
|
|
18097
|
+
if (undisclosed.length > 0) {
|
|
18098
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18099
|
+
error: `this report carries a disclosure a published page cannot yet show, and a verdict shown without it is worse than no page: ${undisclosed.map((f) => `${f.pointer} \u2014 ${f.consequence}`).join("; ")}`,
|
|
18100
|
+
code: "report-carries-an-unrenderable-disclosure",
|
|
18101
|
+
remediation: "Resolve what the disclosure names \u2014 re-record so the set matches the bundle's stamp, or resolve the substituted font families \u2014 then re-verify and publish the clean run."
|
|
18102
|
+
});
|
|
18103
|
+
}
|
|
18104
|
+
if (scoredRecordingSetHash(report) === void 0) {
|
|
18105
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18106
|
+
error: "this report does not name the recording set these scores were measured against",
|
|
18107
|
+
code: "report-names-no-recording-set",
|
|
18108
|
+
remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` with a current CLI \u2014 the set identity rides the report.`
|
|
18109
|
+
});
|
|
18110
|
+
}
|
|
18111
|
+
const componentName = (opts.name ?? bundle.manifest.name).trim();
|
|
18112
|
+
const surface = classifyBundleSurface(bundle.files, { entry: bundle.manifest.entry });
|
|
18113
|
+
if (surface.unknown.length > 0) {
|
|
18114
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18115
|
+
error: `this bundle carries files a portal does not recognise, and publishing guesses at nothing: ${surface.unknown.join(", ")}`,
|
|
18116
|
+
code: "bundle-carries-unrecognised-files",
|
|
18117
|
+
remediation: `Remove them from ${opts.bundleDir}, or re-emit the bundle. Publishing them would ship something nobody reviewed; dropping them silently would ship evidence with a hole in it.`
|
|
18118
|
+
});
|
|
18119
|
+
}
|
|
18120
|
+
const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
|
|
18121
|
+
if (sheetEntry !== void 0) {
|
|
18122
|
+
const missingCrops = missingInspectCrops(
|
|
18123
|
+
readFileSync39(path52.join(bundleDir, sheetEntry.path), "utf8"),
|
|
18124
|
+
surface.published.map((p) => p.path)
|
|
18125
|
+
);
|
|
18126
|
+
if (missingCrops.length > 0) {
|
|
18127
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18128
|
+
error: `this bundle's inspect sheet references images it does not carry, and half a sheet reads as a broken component: ${missingCrops.join(", ")}`,
|
|
18129
|
+
code: "inspect-sheet-without-its-crops",
|
|
18130
|
+
remediation: `Re-run \`${tendrilCommand(`inspect ${opts.bundleDir}`)}\` to rebuild the sheet and its crops together, or delete the sheet and publish without it.`
|
|
18131
|
+
});
|
|
18132
|
+
}
|
|
18133
|
+
}
|
|
18134
|
+
if (surface.missingRequired.length > 0) {
|
|
18135
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18136
|
+
error: `this bundle is missing what a publishable bundle cannot be without: ${surface.missingRequired.join(", ")}`,
|
|
18137
|
+
code: "bundle-missing-required-role",
|
|
18138
|
+
remediation: `Run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` \u2014 the evidence a page shows is written by that run. Required: ${REQUIRED_ROLES.join(", ")}.`
|
|
18139
|
+
});
|
|
18140
|
+
}
|
|
18141
|
+
const recorded = readScoredFiles(report);
|
|
18142
|
+
if (recorded === void 0) {
|
|
18143
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18144
|
+
error: `${VERIFY_REPORT_FILENAME} does not record the files this run was scored beside, so nothing ties this bundle's bytes to its verdict`,
|
|
18145
|
+
code: "report-records-no-file-digests",
|
|
18146
|
+
remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` with a current CLI \u2014 this report predates the field.`
|
|
18147
|
+
});
|
|
18148
|
+
} else {
|
|
18149
|
+
const delta = compareScoredFiles(recorded, digestScoredFiles(bundleDir, bundle.manifest.entry));
|
|
18150
|
+
const disagreements = [
|
|
18151
|
+
...delta.changed.map((p) => `${p} (bytes differ from the scored run)`),
|
|
18152
|
+
...delta.missing.map((p) => `${p} (scored, no longer in the bundle)`),
|
|
18153
|
+
...delta.unscored.map((p) => `${p} (in the bundle, never scored)`)
|
|
18154
|
+
];
|
|
18155
|
+
if (disagreements.length > 0) {
|
|
18156
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18157
|
+
error: `this bundle is not the one the ruler scored: ${disagreements.join("; ")}`,
|
|
18158
|
+
code: "bundle-disagrees-with-scored-files",
|
|
18159
|
+
remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` so the verdict describes these bytes. A page must never show code the ruler never saw.`
|
|
18160
|
+
});
|
|
18161
|
+
}
|
|
18162
|
+
}
|
|
18163
|
+
const figmaFile = bundle.manifest.provenance.recordingSet.figmaFile ?? null;
|
|
18164
|
+
if (opts.dryRun) {
|
|
18165
|
+
emitData(
|
|
18166
|
+
opts,
|
|
18167
|
+
{
|
|
18168
|
+
bundleDir,
|
|
18169
|
+
componentName,
|
|
18170
|
+
figmaFile,
|
|
18171
|
+
entry: bundle.manifest.entry,
|
|
18172
|
+
publishes: surface.published,
|
|
18173
|
+
excluded: surface.excluded,
|
|
18174
|
+
rulerVersion: report["environment"]?.["ruler"] ?? null,
|
|
18175
|
+
wouldPublish: true
|
|
18176
|
+
},
|
|
18177
|
+
() => {
|
|
18178
|
+
process.stdout.write(`${componentName} would publish ${String(surface.published.length)} files:
|
|
18179
|
+
`);
|
|
18180
|
+
for (const entry of surface.published) process.stdout.write(` ${entry.path} (${entry.role})
|
|
18181
|
+
`);
|
|
18182
|
+
for (const left of surface.excluded) process.stdout.write(` \u2014 leaving ${left.path} behind (${left.reason})
|
|
18183
|
+
`);
|
|
18184
|
+
}
|
|
18185
|
+
);
|
|
18186
|
+
return;
|
|
18187
|
+
}
|
|
18188
|
+
const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
|
|
18189
|
+
const client = opts.client ?? httpClient(opts, origin);
|
|
18190
|
+
if (opts.confirmPublish === true && process.stdin.isTTY !== true) {
|
|
18191
|
+
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18192
|
+
error: "--confirm-publish requires an interactive terminal \u2014 putting a component on the internet is a human-only decision",
|
|
18193
|
+
code: "publish-confirmation-not-interactive",
|
|
18194
|
+
remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Agents: show this to your operator instead of confirming it.`
|
|
18195
|
+
});
|
|
18196
|
+
}
|
|
18197
|
+
if (opts.acceptTerms === true) {
|
|
18198
|
+
const accepted = await client.acceptTerms({ figmaFile });
|
|
18199
|
+
if (!accepted.ok) refuse2(opts, accepted, "accept-terms-refused");
|
|
18200
|
+
}
|
|
18201
|
+
const opened = await client.begin({
|
|
18202
|
+
componentName,
|
|
18203
|
+
figmaFile,
|
|
18204
|
+
entry: bundle.manifest.entry,
|
|
18205
|
+
files: bundle.files,
|
|
18206
|
+
report: bundle.reportText,
|
|
18207
|
+
confirmed: opts.confirmPublish === true
|
|
18208
|
+
});
|
|
18209
|
+
if (!opened.ok) {
|
|
18210
|
+
if (opened.needsConsent !== void 0) {
|
|
18211
|
+
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18212
|
+
error: `publishing this design system needs an acceptance of the publishing terms (version ${opened.needsConsent.termsVersion}) first`,
|
|
18213
|
+
code: "publishing-terms-not-accepted",
|
|
18214
|
+
remediation: `Read the terms, then run \`${tendrilCommand(`publish ${opts.bundleDir} --accept-terms --confirm-publish`)}\`. The acceptance covers this design system only.`
|
|
18215
|
+
});
|
|
18216
|
+
}
|
|
18217
|
+
if (opened.needsConfirmation !== void 0) {
|
|
18218
|
+
fail(opts, ExitCode.ConfirmationRequired, {
|
|
18219
|
+
error: `publishing ${JSON.stringify(opened.needsConfirmation.componentName)} for the first time is a decision a person makes, not a build step`,
|
|
18220
|
+
code: "first-publish-unconfirmed",
|
|
18221
|
+
remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Later publishes of this component need no flag.`
|
|
18222
|
+
});
|
|
18223
|
+
}
|
|
18224
|
+
refuse2(opts, opened, "publish-refused");
|
|
18225
|
+
}
|
|
18226
|
+
const uploaded = [];
|
|
18227
|
+
for (const object of opened.value.plan.objects) {
|
|
18228
|
+
const file = path52.join(bundleDir, object.relPath);
|
|
18229
|
+
if (!existsSync42(file)) {
|
|
18230
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18231
|
+
error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
|
|
18232
|
+
code: "planned-file-missing",
|
|
18233
|
+
remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` and publish again \u2014 a publication is refused rather than going live with a hole in its evidence.`
|
|
18234
|
+
});
|
|
18235
|
+
}
|
|
18236
|
+
const sent = await client.upload({
|
|
18237
|
+
publicationId: opened.value.publicationId,
|
|
18238
|
+
relPath: object.relPath,
|
|
18239
|
+
bytes: new Uint8Array(readFileSync39(file))
|
|
18240
|
+
});
|
|
18241
|
+
if (!sent.ok) refuse2(opts, sent, "upload-refused");
|
|
18242
|
+
uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
|
|
18243
|
+
}
|
|
18244
|
+
const committed = await client.commit({ publicationId: opened.value.publicationId });
|
|
18245
|
+
if (!committed.ok) {
|
|
18246
|
+
if (committed.missing !== void 0 && committed.missing.length > 0) {
|
|
18247
|
+
fail(opts, ExitCode.General, {
|
|
18248
|
+
error: `this publication is missing objects the bundle declared, and a verdict beside missing evidence is worse than no page: ${committed.missing.join(", ")}`,
|
|
18249
|
+
code: "publication-incomplete",
|
|
18250
|
+
remediation: `Run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication and re-sends only what is missing.`
|
|
18251
|
+
});
|
|
18252
|
+
}
|
|
18253
|
+
refuse2(opts, committed, "commit-refused");
|
|
18254
|
+
}
|
|
18255
|
+
emitData(
|
|
18256
|
+
opts,
|
|
18257
|
+
{
|
|
18258
|
+
publicationId: committed.value.publicationId,
|
|
18259
|
+
url: committed.value.url,
|
|
18260
|
+
componentName,
|
|
18261
|
+
figmaFile: opened.value.figmaFile,
|
|
18262
|
+
rulerVersion: opened.value.rulerVersion,
|
|
18263
|
+
resumed: opened.value.resumed === true,
|
|
18264
|
+
files: uploaded
|
|
18265
|
+
},
|
|
18266
|
+
() => {
|
|
18267
|
+
const reused = uploaded.filter((u) => u.deduplicated).length;
|
|
18268
|
+
if (opened.value.resumed === true) process.stdout.write(`resumed the unfinished publish of ${componentName}
|
|
18269
|
+
`);
|
|
18270
|
+
process.stdout.write(`published ${componentName} \u2014 ${String(uploaded.length)} files`);
|
|
18271
|
+
process.stdout.write(reused > 0 ? ` (${String(reused)} you already had)
|
|
18272
|
+
` : "\n");
|
|
18273
|
+
process.stdout.write(` ${committed.value.url}
|
|
18274
|
+
`);
|
|
18275
|
+
process.stdout.write(` verdict as scored by ruler ${opened.value.rulerVersion}
|
|
18276
|
+
`);
|
|
18277
|
+
}
|
|
18278
|
+
);
|
|
18279
|
+
}
|
|
18280
|
+
function readBundle(opts, bundleDir) {
|
|
18281
|
+
const manifestPath2 = path52.join(bundleDir, "component.json");
|
|
18282
|
+
const reportPath = path52.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
|
|
18283
|
+
if (!existsSync42(manifestPath2)) {
|
|
18284
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18285
|
+
error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
|
|
18286
|
+
code: "not-a-bundle",
|
|
18287
|
+
remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
|
|
18288
|
+
});
|
|
18289
|
+
}
|
|
18290
|
+
if (!existsSync42(reportPath)) {
|
|
18291
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18292
|
+
error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
|
|
18293
|
+
code: "bundle-not-verified",
|
|
18294
|
+
remediation: `Run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` first. Verification is free and needs no account; publishing without it would put a page up with no verdict on it.`
|
|
18295
|
+
});
|
|
18296
|
+
}
|
|
18297
|
+
const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
|
|
18298
|
+
if (manifest === void 0) {
|
|
18299
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18300
|
+
error: "component.json did not parse as a bundle manifest",
|
|
18301
|
+
code: "no-mount-contract",
|
|
18302
|
+
remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
|
|
18303
|
+
});
|
|
18304
|
+
}
|
|
18305
|
+
const reportText = readFileSync39(reportPath, "utf8");
|
|
18306
|
+
let report;
|
|
18307
|
+
try {
|
|
18308
|
+
report = JSON.parse(reportText);
|
|
18309
|
+
} catch (error) {
|
|
18310
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18311
|
+
error: `${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME} is not parseable JSON: ${error.message}`,
|
|
18312
|
+
code: "report-unparseable",
|
|
18313
|
+
remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` \u2014 that file is written by the ruler and should never be edited by hand.`
|
|
18314
|
+
});
|
|
18315
|
+
}
|
|
18316
|
+
if (report === null || typeof report !== "object" || Array.isArray(report)) {
|
|
18317
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18318
|
+
error: `${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME} parses, but a verify report is a JSON object and this is not one`,
|
|
18319
|
+
code: "report-not-an-object",
|
|
18320
|
+
remediation: `Re-run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\`.`
|
|
18321
|
+
});
|
|
18322
|
+
}
|
|
18323
|
+
return { manifest, report, reportText, files: bundleFiles(bundleDir) };
|
|
18324
|
+
}
|
|
18325
|
+
function httpClient(opts, origin) {
|
|
18326
|
+
if (origin === "") {
|
|
18327
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18328
|
+
error: "no portal to publish to",
|
|
18329
|
+
code: "no-portal-configured",
|
|
18330
|
+
remediation: `Pass \`--to <url>\` or set TENDRIL_PORTAL_URL. \`${tendrilCommand(`publish ${opts.bundleDir} --dry-run`)}\` shows exactly what would be published without needing one.`
|
|
18331
|
+
});
|
|
18332
|
+
}
|
|
18333
|
+
if (!isSecureOrigin(origin)) {
|
|
18334
|
+
fail(opts, ExitCode.InputValidation, {
|
|
18335
|
+
error: `${origin} is not https, so a session token sent there would travel in the clear`,
|
|
18336
|
+
code: "portal-not-https",
|
|
18337
|
+
remediation: "Use the https:// address of your portal. Only 127.0.0.1 and localhost are exempt, for local development."
|
|
18338
|
+
});
|
|
18339
|
+
}
|
|
18340
|
+
const found = tokenFor(origin);
|
|
18341
|
+
if (!found.ok && found.reason === "origin-mismatch") {
|
|
18342
|
+
fail(opts, ExitCode.Auth, {
|
|
18343
|
+
error: `the session available here belongs to ${found.boundTo}, and this would publish to ${origin}`,
|
|
18344
|
+
code: "session-belongs-to-another-portal",
|
|
18345
|
+
remediation: found.from === "env" ? `TENDRIL_TOKEN is pinned to TENDRIL_PORTAL_URL (${found.boundTo}). Publish to that portal, or set both to ${origin} together \u2014 a token is a credential for one host.` : `Sign in to ${origin}. The stored session is for ${found.boundTo}, and sending it elsewhere would hand that host a live credential.`
|
|
18346
|
+
});
|
|
18347
|
+
}
|
|
18348
|
+
if (!found.ok) {
|
|
18349
|
+
fail(opts, ExitCode.Auth, {
|
|
18350
|
+
error: `no session for ${origin}`,
|
|
18351
|
+
code: "not-signed-in",
|
|
18352
|
+
remediation: `Run \`${tendrilCommand(`login --to ${origin}`)}\` and paste the token your portal operator gave you (or set TENDRIL_TOKEN and TENDRIL_PORTAL_URL together for this shell). Verification stays free and account-less; only publishing needs an account.`
|
|
18353
|
+
});
|
|
18354
|
+
}
|
|
18355
|
+
return new HttpPublishClient({ origin, token: found.token });
|
|
18356
|
+
}
|
|
18357
|
+
function refuse2(opts, sent, code) {
|
|
18358
|
+
const detail = sent.detail === void 0 || sent.detail.length === 0 ? "" : `: ${sent.detail.join(", ")}`;
|
|
18359
|
+
fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
|
|
18360
|
+
error: `${sent.refusal}${detail}`,
|
|
18361
|
+
code,
|
|
18362
|
+
remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${(opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "")}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 a retry rejoins this same unfinished publication.` : `Fix what is named above and run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one.`
|
|
18363
|
+
});
|
|
18364
|
+
}
|
|
18365
|
+
var init_publish = __esm({
|
|
18366
|
+
"packages/cli/src/commands/publish.ts"() {
|
|
18367
|
+
"use strict";
|
|
18368
|
+
init_src3();
|
|
18369
|
+
init_src4();
|
|
18370
|
+
init_invocation();
|
|
18371
|
+
init_output();
|
|
18372
|
+
init_publish_client();
|
|
18373
|
+
}
|
|
18374
|
+
});
|
|
18375
|
+
|
|
17090
18376
|
// packages/cli/src/commands/generate-route.ts
|
|
17091
18377
|
var generate_route_exports = {};
|
|
17092
18378
|
__export(generate_route_exports, {
|
|
@@ -17110,18 +18396,18 @@ var generate_recorded_exports = {};
|
|
|
17110
18396
|
__export(generate_recorded_exports, {
|
|
17111
18397
|
runGenerateRecorded: () => runGenerateRecorded
|
|
17112
18398
|
});
|
|
17113
|
-
import { confirm as confirm3, isCancel as
|
|
17114
|
-
import { existsSync as
|
|
17115
|
-
import
|
|
18399
|
+
import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
|
|
18400
|
+
import { existsSync as existsSync43, readFileSync as readFileSync40 } from "node:fs";
|
|
18401
|
+
import path53 from "node:path";
|
|
17116
18402
|
async function runGenerateRecorded(opts) {
|
|
17117
18403
|
const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
17118
|
-
const outDirAbs =
|
|
17119
|
-
const recordedAsPath =
|
|
18404
|
+
const outDirAbs = path53.resolve(callerCwd, opts.out);
|
|
18405
|
+
const recordedAsPath = path53.resolve(callerCwd, opts.recorded);
|
|
17120
18406
|
let task;
|
|
17121
18407
|
let taskName;
|
|
17122
18408
|
let authoredApi;
|
|
17123
18409
|
let composition;
|
|
17124
|
-
const isSet =
|
|
18410
|
+
const isSet = existsSync43(path53.join(recordedAsPath, "recording-set.json"));
|
|
17125
18411
|
const registry = TASKS[opts.recorded];
|
|
17126
18412
|
if (registry !== void 0 && !isSet) {
|
|
17127
18413
|
task = registry;
|
|
@@ -17130,7 +18416,7 @@ async function runGenerateRecorded(opts) {
|
|
|
17130
18416
|
try {
|
|
17131
18417
|
const authored = authorTaskFromSet(recordedAsPath);
|
|
17132
18418
|
task = authored.task;
|
|
17133
|
-
taskName =
|
|
18419
|
+
taskName = path53.basename(recordedAsPath);
|
|
17134
18420
|
authoredApi = authored.api;
|
|
17135
18421
|
const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
|
|
17136
18422
|
if (roles.success) composition = roles.data;
|
|
@@ -17164,7 +18450,7 @@ async function runGenerateRecorded(opts) {
|
|
|
17164
18450
|
warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
|
|
17165
18451
|
}
|
|
17166
18452
|
const missing = task.configs.filter(
|
|
17167
|
-
(c) => !
|
|
18453
|
+
(c) => !existsSync43(path53.join(task.set, c.rep, "get_screenshot.json")) || !existsSync43(path53.join(task.set, c.rep, "get_metadata.json")) || !existsSync43(path53.join(task.set, c.rep, "get_design_context.json"))
|
|
17168
18454
|
);
|
|
17169
18455
|
if (missing.length > 0) {
|
|
17170
18456
|
fail(opts, ExitCode.RecordingIncomplete, {
|
|
@@ -17234,8 +18520,8 @@ async function runGenerateRecorded(opts) {
|
|
|
17234
18520
|
` : `${line}
|
|
17235
18521
|
`);
|
|
17236
18522
|
if (opts.dryRun) {
|
|
17237
|
-
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite:
|
|
17238
|
-
process.stdout.write(`dry-run: nothing sent, nothing written (would write ${
|
|
18523
|
+
emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path53.join(outDirAbs, taskName) }, () => {
|
|
18524
|
+
process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path53.join(outDirAbs, taskName)})
|
|
17239
18525
|
`);
|
|
17240
18526
|
});
|
|
17241
18527
|
return;
|
|
@@ -17250,7 +18536,7 @@ async function runGenerateRecorded(opts) {
|
|
|
17250
18536
|
});
|
|
17251
18537
|
}
|
|
17252
18538
|
const accepted = await confirm3({ message: `Proceed? (cap $${opts.capUsd.toFixed(2)}, ${opts.maxIterations} iterations max)` });
|
|
17253
|
-
if (
|
|
18539
|
+
if (isCancel4(accepted) || accepted !== true) {
|
|
17254
18540
|
fail(opts, ExitCode.ConfirmationRequired, {
|
|
17255
18541
|
error: "Run was not confirmed.",
|
|
17256
18542
|
code: "CONFIRMATION_DECLINED",
|
|
@@ -17258,10 +18544,10 @@ async function runGenerateRecorded(opts) {
|
|
|
17258
18544
|
});
|
|
17259
18545
|
}
|
|
17260
18546
|
}
|
|
17261
|
-
const bundleDir =
|
|
17262
|
-
if (
|
|
18547
|
+
const bundleDir = path53.join(outDirAbs, taskName);
|
|
18548
|
+
if (existsSync43(path53.join(bundleDir, "component.json"))) {
|
|
17263
18549
|
try {
|
|
17264
|
-
const prior = readBundleManifest(
|
|
18550
|
+
const prior = readBundleManifest(readFileSync40(path53.join(bundleDir, "component.json"), "utf8")).manifest;
|
|
17265
18551
|
if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
|
|
17266
18552
|
fail(opts, ExitCode.InputValidation, {
|
|
17267
18553
|
error: `${bundleDir} already holds a bundle bound to recording set "${prior.provenance.recordingSet.path}" \u2014 generating here against a different set would silently rewrite its verification identity`,
|
|
@@ -17428,7 +18714,7 @@ init_invocation();
|
|
|
17428
18714
|
init_output();
|
|
17429
18715
|
import { intro, isCancel, outro, password } from "@clack/prompts";
|
|
17430
18716
|
import fs from "node:fs";
|
|
17431
|
-
import
|
|
18717
|
+
import path29 from "node:path";
|
|
17432
18718
|
var INIT_DESCRIPTION = {
|
|
17433
18719
|
name: "init",
|
|
17434
18720
|
summary: "Configure the OpenRouter credential in .env, and optionally a Figma token (idempotent).",
|
|
@@ -17470,7 +18756,7 @@ async function runInit(flags) {
|
|
|
17470
18756
|
printDescription(INIT_DESCRIPTION);
|
|
17471
18757
|
return;
|
|
17472
18758
|
}
|
|
17473
|
-
const envPath =
|
|
18759
|
+
const envPath = path29.resolve(process.cwd(), ".env");
|
|
17474
18760
|
const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
|
|
17475
18761
|
let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
|
|
17476
18762
|
let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
|
|
@@ -17491,7 +18777,7 @@ async function runInit(flags) {
|
|
|
17491
18777
|
if (openrouterKey) next.set(ENV_KEYS.openrouter, openrouterKey);
|
|
17492
18778
|
if (figmaToken) next.set(ENV_KEYS.figma, figmaToken);
|
|
17493
18779
|
const changed = [...next].some(([key, value]) => existing.get(key) !== value);
|
|
17494
|
-
const gitignorePath =
|
|
18780
|
+
const gitignorePath = path29.resolve(process.cwd(), ".gitignore");
|
|
17495
18781
|
const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
|
|
17496
18782
|
const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
|
|
17497
18783
|
if (flags.dryRun) {
|
|
@@ -17547,14 +18833,14 @@ init_invocation();
|
|
|
17547
18833
|
init_output();
|
|
17548
18834
|
init_entitlement();
|
|
17549
18835
|
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
17550
|
-
import { readFileSync as
|
|
18836
|
+
import { readFileSync as readFileSync21, readdirSync as readdirSync9, existsSync as existsSync23 } from "node:fs";
|
|
17551
18837
|
|
|
17552
18838
|
// packages/cli/src/pipeline.ts
|
|
17553
18839
|
init_src2();
|
|
17554
18840
|
init_src5();
|
|
17555
18841
|
init_src4();
|
|
17556
|
-
import { mkdirSync as
|
|
17557
|
-
import
|
|
18842
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
18843
|
+
import path30 from "node:path";
|
|
17558
18844
|
|
|
17559
18845
|
// packages/cli/src/assets-module.ts
|
|
17560
18846
|
init_src();
|
|
@@ -17890,8 +19176,8 @@ async function runGenerationPipeline(input) {
|
|
|
17890
19176
|
});
|
|
17891
19177
|
const written = [];
|
|
17892
19178
|
if (!input.dryRun) {
|
|
17893
|
-
const dir =
|
|
17894
|
-
|
|
19179
|
+
const dir = path30.resolve(input.outDir, semantics.componentName);
|
|
19180
|
+
mkdirSync6(dir, { recursive: true });
|
|
17895
19181
|
const files = {
|
|
17896
19182
|
// Bundle-local tokens: THE emission the component's CSS resolves
|
|
17897
19183
|
// against — the same artifact the verify harness injects. A preview
|
|
@@ -17914,14 +19200,14 @@ async function runGenerationPipeline(input) {
|
|
|
17914
19200
|
`
|
|
17915
19201
|
};
|
|
17916
19202
|
for (const [name, content] of Object.entries(files)) {
|
|
17917
|
-
const filePath =
|
|
17918
|
-
|
|
19203
|
+
const filePath = path30.join(dir, name);
|
|
19204
|
+
writeFileSync9(filePath, content);
|
|
17919
19205
|
written.push(filePath);
|
|
17920
19206
|
}
|
|
17921
19207
|
for (const artifact of emitTokenArtifacts(input.mapping)) {
|
|
17922
|
-
const filePath =
|
|
17923
|
-
|
|
17924
|
-
|
|
19208
|
+
const filePath = path30.resolve(input.outDir, artifact.path);
|
|
19209
|
+
mkdirSync6(path30.dirname(filePath), { recursive: true });
|
|
19210
|
+
writeFileSync9(filePath, artifact.content);
|
|
17925
19211
|
written.push(filePath);
|
|
17926
19212
|
}
|
|
17927
19213
|
}
|
|
@@ -17979,7 +19265,7 @@ var GENERATE_DESCRIPTION = {
|
|
|
17979
19265
|
function resolveProvidedSource(flags, contextFile) {
|
|
17980
19266
|
let raw;
|
|
17981
19267
|
try {
|
|
17982
|
-
raw =
|
|
19268
|
+
raw = readFileSync21(contextFile, "utf8");
|
|
17983
19269
|
} catch {
|
|
17984
19270
|
fail(flags, ExitCode.InputValidation, {
|
|
17985
19271
|
error: `Cannot read context file "${contextFile}".`,
|
|
@@ -18099,11 +19385,11 @@ token mapping (${mapping.flat.length} variables):
|
|
|
18099
19385
|
let initialCode;
|
|
18100
19386
|
let initialSemantics;
|
|
18101
19387
|
try {
|
|
18102
|
-
if (
|
|
18103
|
-
for (const entry of
|
|
19388
|
+
if (existsSync23(flags.out)) {
|
|
19389
|
+
for (const entry of readdirSync9(flags.out)) {
|
|
18104
19390
|
const cjPath = `${flags.out}/${entry}/component.json`;
|
|
18105
|
-
if (!
|
|
18106
|
-
const cj = JSON.parse(
|
|
19391
|
+
if (!existsSync23(cjPath)) continue;
|
|
19392
|
+
const cj = JSON.parse(readFileSync21(cjPath, "utf8"));
|
|
18107
19393
|
if (cj.name !== void 0 && Array.isArray(cj.props)) {
|
|
18108
19394
|
previousApi = JSON.stringify({
|
|
18109
19395
|
componentName: cj.name,
|
|
@@ -18111,14 +19397,14 @@ token mapping (${mapping.flat.length} variables):
|
|
|
18111
19397
|
});
|
|
18112
19398
|
const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
|
|
18113
19399
|
const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
|
|
18114
|
-
if (flags.refine &&
|
|
19400
|
+
if (flags.refine && existsSync23(tsxPath) && existsSync23(cssPath)) {
|
|
18115
19401
|
initialCode = {
|
|
18116
|
-
tsx:
|
|
18117
|
-
css:
|
|
19402
|
+
tsx: readFileSync21(tsxPath, "utf8"),
|
|
19403
|
+
css: readFileSync21(cssPath, "utf8")
|
|
18118
19404
|
};
|
|
18119
19405
|
const semPath = `${flags.out}/${entry}/semantics.json`;
|
|
18120
|
-
if (
|
|
18121
|
-
initialSemantics = JSON.parse(
|
|
19406
|
+
if (existsSync23(semPath)) {
|
|
19407
|
+
initialSemantics = JSON.parse(readFileSync21(semPath, "utf8"));
|
|
18122
19408
|
}
|
|
18123
19409
|
}
|
|
18124
19410
|
break;
|
|
@@ -18513,6 +19799,50 @@ function buildProgram() {
|
|
|
18513
19799
|
...local["maxArea"] !== void 0 ? { maxArea: Number(local["maxArea"]) } : {}
|
|
18514
19800
|
});
|
|
18515
19801
|
});
|
|
19802
|
+
program.command("login").description("Sign this machine in to the Tendril portal (approve in your browser), so `publish` and `share` can reach it. Verification never needs this.").option("--to <url>", "the portal to sign in to (defaults to the hosted portal; or set TENDRIL_PORTAL_URL)").option("--paste", "paste a token minted on the portal's Connect page instead of approving in the browser").option("--device-start", "agents: start the browser approval and return the link and code immediately").option("--device-wait", "agents: wait for the browser approval started by --device-start").action(async (_o, cmd) => {
|
|
19803
|
+
const flags = globalFlags(cmd.parent);
|
|
19804
|
+
const local = cmd.opts();
|
|
19805
|
+
const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
19806
|
+
await runLogin2({
|
|
19807
|
+
...flags,
|
|
19808
|
+
...local["to"] !== void 0 ? { to: local["to"] } : {},
|
|
19809
|
+
...local["paste"] !== void 0 ? { paste: local["paste"] } : {},
|
|
19810
|
+
...local["deviceStart"] !== void 0 ? { deviceStart: local["deviceStart"] } : {},
|
|
19811
|
+
...local["deviceWait"] !== void 0 ? { deviceWait: local["deviceWait"] } : {}
|
|
19812
|
+
});
|
|
19813
|
+
});
|
|
19814
|
+
program.command("logout").description("Forget this machine's stored portal session. Does not end the session itself.").action(async (_o, cmd) => {
|
|
19815
|
+
const flags = globalFlags(cmd.parent);
|
|
19816
|
+
const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
19817
|
+
runLogout2(flags);
|
|
19818
|
+
});
|
|
19819
|
+
program.command("share").description("Create a link that opens one publication's verdict and evidence for someone with no account. Anyone holding the link can read it.").argument("<publicationId>", "the publication to share, as `publish` printed it").option("--to <url>", "the portal (or set TENDRIL_PORTAL_URL)").option("--expires <days|timestamp>", "a number of days, or an ISO-8601 UTC timestamp; omit for a link that does not expire").option("--recipient <email>", "recorded so you can see who a link was for; never used to authenticate").option("--list", "show the links outstanding for this publication").option("--revoke <shareLinkId>", "stop a link working").action(async (publicationId, _o, cmd) => {
|
|
19820
|
+
const flags = globalFlags(cmd.parent);
|
|
19821
|
+
const local = cmd.opts();
|
|
19822
|
+
const { runShare: runShare2 } = await Promise.resolve().then(() => (init_share(), share_exports));
|
|
19823
|
+
await runShare2({
|
|
19824
|
+
...flags,
|
|
19825
|
+
publicationId,
|
|
19826
|
+
...local["to"] !== void 0 ? { to: local["to"] } : {},
|
|
19827
|
+
...local["expires"] !== void 0 ? { expires: String(local["expires"]) } : {},
|
|
19828
|
+
...local["recipient"] !== void 0 ? { recipient: local["recipient"] } : {},
|
|
19829
|
+
...local["list"] !== void 0 ? { list: local["list"] } : {},
|
|
19830
|
+
...local["revoke"] !== void 0 ? { revoke: local["revoke"] } : {}
|
|
19831
|
+
});
|
|
19832
|
+
});
|
|
19833
|
+
program.command("publish").description("Put a verified bundle and its evidence on a page, at a URL you can send to someone. Refuses a run the ruler declined.").argument("<bundleDir>", "bundle directory that has already been verified").option("--to <url>", "the portal to publish to (or set TENDRIL_PORTAL_URL)").option("--name <name>", "library label for this component (default: the bundle manifest's own name)").option("--accept-terms", "accept the current publishing terms for this design system as part of this run").option("--confirm-publish", "you, a person, saying this component may be published \u2014 required the first time, and refused without an interactive terminal").action(async (bundleDir, _o, cmd) => {
|
|
19834
|
+
const flags = globalFlags(cmd.parent);
|
|
19835
|
+
const local = cmd.opts();
|
|
19836
|
+
const { runPublish: runPublish2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
|
|
19837
|
+
await runPublish2({
|
|
19838
|
+
...flags,
|
|
19839
|
+
bundleDir,
|
|
19840
|
+
...local["to"] !== void 0 ? { to: local["to"] } : {},
|
|
19841
|
+
...local["name"] !== void 0 ? { name: local["name"] } : {},
|
|
19842
|
+
...local["acceptTerms"] !== void 0 ? { acceptTerms: local["acceptTerms"] } : {},
|
|
19843
|
+
...local["confirmPublish"] !== void 0 ? { confirmPublish: local["confirmPublish"] } : {}
|
|
19844
|
+
});
|
|
19845
|
+
});
|
|
18516
19846
|
program.command("verify").description("Recompute verification for a generated bundle against its recording set (per-config status, behaviors, honest exit codes).").argument("<bundleDir>", "bundle directory to verify").option("--task <name>", "legacy task adapter for bundles WITHOUT component.json (bundle v1 carries its own prop manifest)").option("--set <dir>", "recording set directory (overrides the bundle's provenance path)").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--library <dir>", "workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: current directory)").option("--profile <file>", "a `tendril profile` artifact; reports whether the bundle followed your codebase conventions (never gates the verdict)").action(async (bundleDir, _opts, cmd) => {
|
|
18517
19847
|
const flags = globalFlags(cmd);
|
|
18518
19848
|
const local = cmd.opts();
|