border-customs 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -4
- package/dist/assets/package-lock.json +2 -2
- package/dist/index.js +594 -82
- package/index.ts +7 -0
- package/package.json +3 -2
- package/plugin/border-command.md +1 -1
- package/plugin/border.ts +171 -59
package/dist/index.js
CHANGED
|
@@ -13029,7 +13029,7 @@ var require_micromatch = __commonJS({
|
|
|
13029
13029
|
|
|
13030
13030
|
// src/index.ts
|
|
13031
13031
|
import { realpathSync } from "node:fs";
|
|
13032
|
-
import { fileURLToPath as
|
|
13032
|
+
import { fileURLToPath as fileURLToPath7, pathToFileURL } from "node:url";
|
|
13033
13033
|
|
|
13034
13034
|
// src/cli.ts
|
|
13035
13035
|
import { parseArgs } from "node:util";
|
|
@@ -38093,12 +38093,12 @@ function packOnce(o) {
|
|
|
38093
38093
|
throw new EngineRunError(`npm pack reported '${report.filename}' but no tarball landed in ${o.distDir}`, null);
|
|
38094
38094
|
}
|
|
38095
38095
|
const data = readFileSync3(tarballAbs);
|
|
38096
|
-
const
|
|
38096
|
+
const sha2563 = createHash3("sha256").update(data).digest("hex");
|
|
38097
38097
|
const sha1 = createHash3("sha1").update(data).digest("hex");
|
|
38098
38098
|
if (sha1 !== report.shasum || data.byteLength !== report.size) {
|
|
38099
38099
|
throw new EngineRunError(`npm pack report disagrees with the tarball bytes for ${report.filename} \u2014 corrupted or swapped artifact, failing closed`, null);
|
|
38100
38100
|
}
|
|
38101
|
-
return { filename: report.filename, sha256:
|
|
38101
|
+
return { filename: report.filename, sha256: sha2563, bytes: data.byteLength, tarballAbs };
|
|
38102
38102
|
}
|
|
38103
38103
|
function publintFinding(o, tarballAbs, identity) {
|
|
38104
38104
|
const candidates = o.publintBinPath !== void 0 ? [o.publintBinPath] : [
|
|
@@ -39141,7 +39141,7 @@ async function runNpmArtifactStage(o) {
|
|
|
39141
39141
|
const stateDir = join10(repoDir, BORDER_STATE_DIR);
|
|
39142
39142
|
const distDir = join10(stateDir, "dist");
|
|
39143
39143
|
mkdirSync3(distDir, { recursive: true });
|
|
39144
|
-
const { filename, sha256:
|
|
39144
|
+
const { filename, sha256: sha2563, bytes, tarballAbs } = packOnce({
|
|
39145
39145
|
repoDir,
|
|
39146
39146
|
distDir,
|
|
39147
39147
|
...o.env !== void 0 ? { env: o.env } : {},
|
|
@@ -39149,7 +39149,7 @@ async function runNpmArtifactStage(o) {
|
|
|
39149
39149
|
...o.npmTimeoutMs !== void 0 ? { npmTimeoutMs: o.npmTimeoutMs } : {}
|
|
39150
39150
|
});
|
|
39151
39151
|
const findings = [];
|
|
39152
|
-
const extractDir = join10(stateDir, "tmp",
|
|
39152
|
+
const extractDir = join10(stateDir, "tmp", sha2563.slice(0, 8));
|
|
39153
39153
|
rmSync3(extractDir, { recursive: true, force: true });
|
|
39154
39154
|
mkdirSync3(extractDir, { recursive: true });
|
|
39155
39155
|
try {
|
|
@@ -39254,7 +39254,7 @@ async function runNpmArtifactStage(o) {
|
|
|
39254
39254
|
if (publint !== null) findings.push(publint);
|
|
39255
39255
|
return {
|
|
39256
39256
|
findings,
|
|
39257
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${filename}`, sha256:
|
|
39257
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${filename}`, sha256: sha2563, bytes }
|
|
39258
39258
|
};
|
|
39259
39259
|
}
|
|
39260
39260
|
|
|
@@ -40354,9 +40354,9 @@ async function runRubygemsArtifactStage(o) {
|
|
|
40354
40354
|
throw new EngineRunError(`gem build produced no ${gemName} in ${relative3(repoDir, distDir)} \u2014 failing closed (was the gemspec's literal name/version re-read?)`, null);
|
|
40355
40355
|
}
|
|
40356
40356
|
const bytes = readFileSync7(dist);
|
|
40357
|
-
const
|
|
40357
|
+
const sha2563 = createHash6("sha256").update(bytes).digest("hex");
|
|
40358
40358
|
const findings = [];
|
|
40359
|
-
const extractDir = join12(stateDir, "tmp",
|
|
40359
|
+
const extractDir = join12(stateDir, "tmp", sha2563.slice(0, 8));
|
|
40360
40360
|
rmSync4(extractDir, { recursive: true, force: true });
|
|
40361
40361
|
mkdirSync4(extractDir, { recursive: true });
|
|
40362
40362
|
const dataDir = `${extractDir}-data`;
|
|
@@ -40434,7 +40434,7 @@ async function runRubygemsArtifactStage(o) {
|
|
|
40434
40434
|
}
|
|
40435
40435
|
return {
|
|
40436
40436
|
findings,
|
|
40437
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256:
|
|
40437
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256: sha2563, bytes: bytes.length }
|
|
40438
40438
|
};
|
|
40439
40439
|
}
|
|
40440
40440
|
|
|
@@ -41996,9 +41996,9 @@ async function runCargoArtifactStage(o) {
|
|
|
41996
41996
|
const dist = join18(distDir, crateName);
|
|
41997
41997
|
renameSync(packed, dist);
|
|
41998
41998
|
const bytes = readFileSync11(dist);
|
|
41999
|
-
const
|
|
41999
|
+
const sha2563 = createHash11("sha256").update(bytes).digest("hex");
|
|
42000
42000
|
const findings = [];
|
|
42001
|
-
const extractDir = join18(stateDir, "tmp",
|
|
42001
|
+
const extractDir = join18(stateDir, "tmp", sha2563.slice(0, 8));
|
|
42002
42002
|
rmSync8(extractDir, { recursive: true, force: true });
|
|
42003
42003
|
mkdirSync8(extractDir, { recursive: true });
|
|
42004
42004
|
try {
|
|
@@ -42051,7 +42051,7 @@ async function runCargoArtifactStage(o) {
|
|
|
42051
42051
|
}
|
|
42052
42052
|
return {
|
|
42053
42053
|
findings,
|
|
42054
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${crateName}`, sha256:
|
|
42054
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${crateName}`, sha256: sha2563, bytes: bytes.length }
|
|
42055
42055
|
};
|
|
42056
42056
|
} finally {
|
|
42057
42057
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
@@ -44482,43 +44482,551 @@ async function runLlmRequest(ctx) {
|
|
|
44482
44482
|
}
|
|
44483
44483
|
|
|
44484
44484
|
// src/commands/opencode.ts
|
|
44485
|
+
import { existsSync as existsSync22, lstatSync, mkdirSync as mkdirSync12, readFileSync as readFileSync20, rmSync as rmSync13, writeFileSync as writeFileSync8 } from "node:fs";
|
|
44486
|
+
import { dirname as dirname16, join as join31 } from "node:path";
|
|
44487
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
44488
|
+
|
|
44489
|
+
// src/opencode/inspect.ts
|
|
44490
|
+
import { existsSync as existsSync21, readFileSync as readFileSync19 } from "node:fs";
|
|
44491
|
+
import { join as join30 } from "node:path";
|
|
44492
|
+
|
|
44493
|
+
// src/opencode/jsonconfig.ts
|
|
44494
|
+
import { existsSync as existsSync19, readdirSync as readdirSync10, readFileSync as readFileSync18 } from "node:fs";
|
|
44495
|
+
import { join as join29 } from "node:path";
|
|
44496
|
+
|
|
44497
|
+
// src/opencode/contract.ts
|
|
44485
44498
|
import { createHash as createHash17 } from "node:crypto";
|
|
44486
|
-
import { existsSync as existsSync18
|
|
44499
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
44487
44500
|
import os3 from "node:os";
|
|
44488
44501
|
import { dirname as dirname15, join as join28 } from "node:path";
|
|
44489
44502
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
44490
44503
|
var PLUGIN_MARKER = "// border-opencode-plugin v";
|
|
44491
44504
|
var COMMAND_MARKER = "<!-- border-opencode-command -->";
|
|
44505
|
+
var BORDER_PACKAGE = "border-customs";
|
|
44506
|
+
var DEFAULT_REGISTRY_BASE = "https://registry.npmjs.org";
|
|
44507
|
+
var REGISTRY_TIMEOUT_MS2 = 15e3;
|
|
44508
|
+
function sha256Hex3(bytes) {
|
|
44509
|
+
return createHash17("sha256").update(bytes).digest("hex");
|
|
44510
|
+
}
|
|
44511
|
+
function firstLine(bytes) {
|
|
44512
|
+
const text = bytes.toString("utf8");
|
|
44513
|
+
const newline = text.indexOf("\n");
|
|
44514
|
+
return newline < 0 ? text : text.slice(0, newline);
|
|
44515
|
+
}
|
|
44492
44516
|
function opencodeConfigRoot(env2) {
|
|
44493
44517
|
const xdg = env2["XDG_CONFIG_HOME"] ?? "";
|
|
44494
44518
|
if (xdg.length > 0) return xdg;
|
|
44495
44519
|
const home = env2["HOME"] ?? "";
|
|
44496
44520
|
return join28(home.length > 0 ? home : os3.homedir(), ".config");
|
|
44497
44521
|
}
|
|
44498
|
-
function
|
|
44522
|
+
function opencodeCacheRoot(env2) {
|
|
44523
|
+
const xdg = env2["XDG_CACHE_HOME"] ?? "";
|
|
44524
|
+
const base = xdg.length > 0 ? xdg : join28(env2["HOME"] ?? os3.homedir(), ".cache");
|
|
44525
|
+
return join28(base, "opencode", "packages");
|
|
44526
|
+
}
|
|
44527
|
+
function routeAPluginPath(env2) {
|
|
44528
|
+
return join28(opencodeConfigRoot(env2), "opencode", "plugins", "border.ts");
|
|
44529
|
+
}
|
|
44530
|
+
function packagedAssetPath(moduleUrl, fileName) {
|
|
44499
44531
|
const here = dirname15(fileURLToPath5(moduleUrl));
|
|
44500
44532
|
const candidates = [
|
|
44501
44533
|
join28(here, "..", "plugin", fileName),
|
|
44502
|
-
// dist
|
|
44534
|
+
// dist (bundled) mode: <root>/plugin/<f>
|
|
44503
44535
|
join28(here, "..", "..", "plugin", fileName)
|
|
44504
44536
|
// src mode: <root>/plugin/<f>
|
|
44505
44537
|
];
|
|
44506
44538
|
return candidates.find((p) => existsSync18(p)) ?? candidates[1];
|
|
44507
44539
|
}
|
|
44508
|
-
function
|
|
44509
|
-
const
|
|
44510
|
-
|
|
44511
|
-
|
|
44512
|
-
|
|
44540
|
+
function packagedDistEntry(moduleUrl) {
|
|
44541
|
+
const here = dirname15(fileURLToPath5(moduleUrl));
|
|
44542
|
+
const candidates = [
|
|
44543
|
+
join28(here, "index.js"),
|
|
44544
|
+
// dist (bundled) mode: <root>/dist/index.js
|
|
44545
|
+
join28(here, "..", "dist", "index.js"),
|
|
44546
|
+
// src/opencode mode one level under root
|
|
44547
|
+
join28(here, "..", "..", "dist", "index.js")
|
|
44548
|
+
// src mode: <root>/dist/index.js
|
|
44513
44549
|
];
|
|
44550
|
+
return candidates.find((p) => existsSync18(p)) ?? candidates[2];
|
|
44514
44551
|
}
|
|
44515
|
-
|
|
44516
|
-
|
|
44552
|
+
|
|
44553
|
+
// src/opencode/jsonconfig.ts
|
|
44554
|
+
function stripJsoncComments(text) {
|
|
44555
|
+
let out = "";
|
|
44556
|
+
let inString = false;
|
|
44557
|
+
let inLine = false;
|
|
44558
|
+
let inBlock = false;
|
|
44559
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
44560
|
+
const ch = text[i];
|
|
44561
|
+
const next = text[i + 1] ?? "";
|
|
44562
|
+
if (inLine) {
|
|
44563
|
+
if (ch === "\n") {
|
|
44564
|
+
inLine = false;
|
|
44565
|
+
out += ch;
|
|
44566
|
+
}
|
|
44567
|
+
continue;
|
|
44568
|
+
}
|
|
44569
|
+
if (inBlock) {
|
|
44570
|
+
if (ch === "*" && next === "/") {
|
|
44571
|
+
inBlock = false;
|
|
44572
|
+
i += 1;
|
|
44573
|
+
out += " ";
|
|
44574
|
+
}
|
|
44575
|
+
continue;
|
|
44576
|
+
}
|
|
44577
|
+
if (inString) {
|
|
44578
|
+
if (ch === "\\") {
|
|
44579
|
+
out += ch + next;
|
|
44580
|
+
i += 1;
|
|
44581
|
+
continue;
|
|
44582
|
+
}
|
|
44583
|
+
if (ch === '"') inString = false;
|
|
44584
|
+
out += ch;
|
|
44585
|
+
continue;
|
|
44586
|
+
}
|
|
44587
|
+
if (ch === '"') {
|
|
44588
|
+
inString = true;
|
|
44589
|
+
out += ch;
|
|
44590
|
+
continue;
|
|
44591
|
+
}
|
|
44592
|
+
if (ch === "/" && next === "/") {
|
|
44593
|
+
inLine = true;
|
|
44594
|
+
i += 1;
|
|
44595
|
+
continue;
|
|
44596
|
+
}
|
|
44597
|
+
if (ch === "/" && next === "*") {
|
|
44598
|
+
inBlock = true;
|
|
44599
|
+
i += 1;
|
|
44600
|
+
continue;
|
|
44601
|
+
}
|
|
44602
|
+
out += ch;
|
|
44603
|
+
}
|
|
44604
|
+
return out;
|
|
44517
44605
|
}
|
|
44518
|
-
function
|
|
44519
|
-
|
|
44520
|
-
|
|
44521
|
-
|
|
44606
|
+
function parseJsonc(text) {
|
|
44607
|
+
return JSON.parse(stripJsoncComments(text).replace(/,(\s*[}\]])/g, "$1"));
|
|
44608
|
+
}
|
|
44609
|
+
var isRecord2 = (v) => typeof v === "object" && v !== null;
|
|
44610
|
+
function splitNpmSpec(entry) {
|
|
44611
|
+
const at = entry.lastIndexOf("@");
|
|
44612
|
+
if (at <= 0) return { name: entry, spec: null };
|
|
44613
|
+
return { name: entry.slice(0, at), spec: entry.slice(at + 1) };
|
|
44614
|
+
}
|
|
44615
|
+
function readPluginDeclarations(path4, pkg) {
|
|
44616
|
+
const none = { path: path4, status: "absent", specs: [] };
|
|
44617
|
+
let text;
|
|
44618
|
+
try {
|
|
44619
|
+
if (!existsSync19(path4)) return none;
|
|
44620
|
+
text = readFileSync18(path4, "utf8");
|
|
44621
|
+
} catch {
|
|
44622
|
+
return { path: path4, status: "error", specs: [] };
|
|
44623
|
+
}
|
|
44624
|
+
let parsed;
|
|
44625
|
+
try {
|
|
44626
|
+
parsed = parseJsonc(text);
|
|
44627
|
+
} catch {
|
|
44628
|
+
return { path: path4, status: "error", specs: [] };
|
|
44629
|
+
}
|
|
44630
|
+
if (!isRecord2(parsed)) return { path: path4, status: "error", specs: [] };
|
|
44631
|
+
const plugin = Array.isArray(parsed["plugin"]) ? parsed["plugin"] : [];
|
|
44632
|
+
const specs = plugin.filter((e) => typeof e === "string" && splitNpmSpec(e).name === pkg);
|
|
44633
|
+
return { path: path4, status: "ok", specs };
|
|
44634
|
+
}
|
|
44635
|
+
function listCacheDirs(cacheRoot, pkg) {
|
|
44636
|
+
let names;
|
|
44637
|
+
try {
|
|
44638
|
+
names = readdirSync10(cacheRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith(`${pkg}@`)).map((e) => e.name).sort();
|
|
44639
|
+
} catch {
|
|
44640
|
+
return [];
|
|
44641
|
+
}
|
|
44642
|
+
return names.map((name) => {
|
|
44643
|
+
const pkgRoot = join29(cacheRoot, name, "node_modules", pkg);
|
|
44644
|
+
let packageVersion = null;
|
|
44645
|
+
try {
|
|
44646
|
+
const meta3 = parseJsonc(readFileSync18(join29(pkgRoot, "package.json"), "utf8"));
|
|
44647
|
+
if (isRecord2(meta3) && typeof meta3["version"] === "string") packageVersion = meta3["version"];
|
|
44648
|
+
} catch {
|
|
44649
|
+
packageVersion = null;
|
|
44650
|
+
}
|
|
44651
|
+
let markerLine = null;
|
|
44652
|
+
try {
|
|
44653
|
+
markerLine = firstLine(readFileSync18(join29(pkgRoot, "plugin", "border.ts")));
|
|
44654
|
+
} catch {
|
|
44655
|
+
markerLine = null;
|
|
44656
|
+
}
|
|
44657
|
+
const markerVersion = markerLine !== null && markerLine.startsWith(PLUGIN_MARKER) ? markerLine.slice(PLUGIN_MARKER.length).trim() : null;
|
|
44658
|
+
return { dir: join29(cacheRoot, name), tag: name.slice(pkg.length + 1), packageVersion, markerVersion, markerLine };
|
|
44659
|
+
});
|
|
44660
|
+
}
|
|
44661
|
+
|
|
44662
|
+
// src/opencode/aspects/cacheVersion.ts
|
|
44663
|
+
var isExactVersion = (spec) => /^\d+\.\d+\.\d+([-+].*)?$/.test(spec);
|
|
44664
|
+
function verifyCacheVersion(facts) {
|
|
44665
|
+
const id = "cache-version";
|
|
44666
|
+
const { cacheDirs, configs, cacheRoot, pkg } = facts;
|
|
44667
|
+
const errored = configs.filter((c) => c.status === "error").map((c) => c.path);
|
|
44668
|
+
const declared = configs.some((c) => c.specs.length > 0);
|
|
44669
|
+
const where = configs.map((c) => `${c.path} [${c.status}]`).join(", ");
|
|
44670
|
+
if (cacheDirs.length === 0) {
|
|
44671
|
+
if (errored.length > 0) {
|
|
44672
|
+
return { id, verdict: "CANNOT", detail: `no cache directory under ${cacheRoot} and the configuration is unreadable (${errored.join(", ")}) \u2014 cannot tell whether a Route-B load is claimed` };
|
|
44673
|
+
}
|
|
44674
|
+
if (declared) {
|
|
44675
|
+
return { id, verdict: "CANNOT", detail: `NOT-CACHED: ${pkg} is declared (${configs.flatMap((c) => [...c.specs]).join(", ")}) but no cache directory exists under ${cacheRoot} \u2014 the load claim has no bytes behind it` };
|
|
44676
|
+
}
|
|
44677
|
+
return { id, verdict: "PASS", detail: `no ${pkg} cache directory under ${cacheRoot} and none declared (${where}) \u2014 Route B not in use, nothing to be stale` };
|
|
44678
|
+
}
|
|
44679
|
+
const pins = new Set(
|
|
44680
|
+
configs.flatMap((c) => [...c.specs]).map((s) => splitNpmSpec(s).spec).filter((s) => s !== null && isExactVersion(s))
|
|
44681
|
+
);
|
|
44682
|
+
const problems = [];
|
|
44683
|
+
for (const d of cacheDirs) {
|
|
44684
|
+
if (d.packageVersion === null) {
|
|
44685
|
+
problems.push(`${d.dir}: package.json missing or unreadable`);
|
|
44686
|
+
}
|
|
44687
|
+
if (d.markerLine === null) {
|
|
44688
|
+
problems.push(`${d.dir}: plugin/border.ts missing or unreadable in the cached package`);
|
|
44689
|
+
} else if (d.markerVersion === null) {
|
|
44690
|
+
problems.push(`${d.dir}: plugin/border.ts first line lacks the marker '${PLUGIN_MARKER}'`);
|
|
44691
|
+
} else if (d.packageVersion !== null && d.markerVersion !== d.packageVersion) {
|
|
44692
|
+
problems.push(`${d.dir}: marker says ${d.markerVersion}, package.json says ${d.packageVersion}`);
|
|
44693
|
+
}
|
|
44694
|
+
if (pins.has(d.tag) && d.packageVersion !== null && d.packageVersion !== d.tag) {
|
|
44695
|
+
problems.push(`${d.dir}: cached under exact spec '${d.tag}' but the package reports ${d.packageVersion}`);
|
|
44696
|
+
}
|
|
44697
|
+
}
|
|
44698
|
+
if (problems.length > 0) {
|
|
44699
|
+
return { id, verdict: "FAIL", detail: problems.join("; ") };
|
|
44700
|
+
}
|
|
44701
|
+
return {
|
|
44702
|
+
id,
|
|
44703
|
+
verdict: "PASS",
|
|
44704
|
+
detail: `cache self-consistent: ${cacheDirs.map((d) => `${d.tag}->${d.packageVersion ?? "?"}`).join(", ")}`
|
|
44705
|
+
};
|
|
44706
|
+
}
|
|
44707
|
+
|
|
44708
|
+
// src/opencode/aspects/dualRoute.ts
|
|
44709
|
+
function verifyDualRoute(facts) {
|
|
44710
|
+
const id = "dual-route";
|
|
44711
|
+
const { configs, routeAInstalled, routeAPlugin, pkg } = facts;
|
|
44712
|
+
const errored = configs.filter((c) => c.status === "error").map((c) => c.path);
|
|
44713
|
+
if (errored.length > 0) {
|
|
44714
|
+
return { id, verdict: "CANNOT", detail: `configuration unreadable (${errored.join(", ")}) \u2014 Route B presence cannot be counted` };
|
|
44715
|
+
}
|
|
44716
|
+
const declared = configs.flatMap((c) => [...c.specs]);
|
|
44717
|
+
const hasB = declared.length > 0;
|
|
44718
|
+
if (routeAInstalled && hasB) {
|
|
44719
|
+
return {
|
|
44720
|
+
id,
|
|
44721
|
+
verdict: "FAIL",
|
|
44722
|
+
detail: `both routes registered: Route A drop at ${routeAPlugin} AND Route B declaration ${declared.map((d) => `'${d}'`).join(", ")} \u2014 pick exactly one route (the dual-registration window blueprint \xA77 forbids)`
|
|
44723
|
+
};
|
|
44724
|
+
}
|
|
44725
|
+
if (routeAInstalled) {
|
|
44726
|
+
return { id, verdict: "PASS", detail: `Route A only: drop at ${routeAPlugin}, no ${pkg} config declaration` };
|
|
44727
|
+
}
|
|
44728
|
+
if (hasB) {
|
|
44729
|
+
return { id, verdict: "PASS", detail: `Route B only: ${declared.map((d) => `'${d}'`).join(", ")}, no file drop at ${routeAPlugin}` };
|
|
44730
|
+
}
|
|
44731
|
+
return { id, verdict: "PASS", detail: "neither route registered \u2014 no border plugin load claimed on the V1 surface" };
|
|
44732
|
+
}
|
|
44733
|
+
|
|
44734
|
+
// src/opencode/aspects/identity.ts
|
|
44735
|
+
import { execFile } from "node:child_process";
|
|
44736
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
44737
|
+
var HANDSHAKE_ARGV = ["--help"];
|
|
44738
|
+
var HANDSHAKE_BANNER_PREFIX = "border";
|
|
44739
|
+
var HANDSHAKE_USAGE_NEEDLE = "usage: border";
|
|
44740
|
+
var HANDSHAKE_TIMEOUT_MS = 2e4;
|
|
44741
|
+
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
44742
|
+
var defaultInspectRunner = (target, argv) => new Promise((resolve20) => {
|
|
44743
|
+
execFile(
|
|
44744
|
+
target.bin,
|
|
44745
|
+
[...target.prefix, ...argv],
|
|
44746
|
+
{ timeout: HANDSHAKE_TIMEOUT_MS, maxBuffer: MAX_OUTPUT_BYTES, shell: false },
|
|
44747
|
+
(error61, stdout, stderr) => {
|
|
44748
|
+
const out = stdout.slice(0, MAX_OUTPUT_BYTES);
|
|
44749
|
+
const err = stderr.slice(0, MAX_OUTPUT_BYTES);
|
|
44750
|
+
if (error61 === null) {
|
|
44751
|
+
resolve20({ status: 0, stdout: out, stderr: err });
|
|
44752
|
+
return;
|
|
44753
|
+
}
|
|
44754
|
+
const failure = error61;
|
|
44755
|
+
if (failure.code === "ENOENT" || failure.code === "EACCES" || failure.code === "ENOEXEC") {
|
|
44756
|
+
resolve20({ status: null, stdout: out, stderr: err });
|
|
44757
|
+
return;
|
|
44758
|
+
}
|
|
44759
|
+
const status = typeof failure.code === "number" ? failure.code : failure.killed === true || failure.signal != null ? 124 : -1;
|
|
44760
|
+
resolve20({ status, stdout: out, stderr: err });
|
|
44761
|
+
}
|
|
44762
|
+
);
|
|
44763
|
+
});
|
|
44764
|
+
function passesHandshake(outcome) {
|
|
44765
|
+
if (outcome.status !== 0) return false;
|
|
44766
|
+
const firstLine2 = (outcome.stdout.match(/^\s*(.*)/) ?? ["", ""])[1] ?? "";
|
|
44767
|
+
return firstLine2.startsWith(HANDSHAKE_BANNER_PREFIX) && outcome.stdout.includes(HANDSHAKE_USAGE_NEEDLE);
|
|
44768
|
+
}
|
|
44769
|
+
function identityCandidates(env2, distEntry) {
|
|
44770
|
+
const configured = env2["BORDER_BIN"];
|
|
44771
|
+
if (configured !== void 0 && configured.length > 0) {
|
|
44772
|
+
return [{ bin: configured, prefix: [], label: `BORDER_BIN=${configured}` }];
|
|
44773
|
+
}
|
|
44774
|
+
const candidates = [];
|
|
44775
|
+
if (existsSync20(distEntry)) {
|
|
44776
|
+
candidates.push({ bin: distEntry, prefix: [], label: "packaged dist/index.js (shebang exec)" });
|
|
44777
|
+
candidates.push({ bin: "node", prefix: [distEntry], label: "packaged dist/index.js under PATH node" });
|
|
44778
|
+
}
|
|
44779
|
+
candidates.push({ bin: "border", prefix: [], label: "border on PATH" });
|
|
44780
|
+
return candidates;
|
|
44781
|
+
}
|
|
44782
|
+
async function verifyIdentity(deps) {
|
|
44783
|
+
const id = "identity-handshake";
|
|
44784
|
+
const runner = deps.runner ?? defaultInspectRunner;
|
|
44785
|
+
const candidates = identityCandidates(deps.env, deps.distEntry);
|
|
44786
|
+
const reasons = [];
|
|
44787
|
+
let anySpawned = false;
|
|
44788
|
+
for (const candidate of candidates) {
|
|
44789
|
+
const probe = await runner(candidate, HANDSHAKE_ARGV);
|
|
44790
|
+
if (passesHandshake(probe)) {
|
|
44791
|
+
return { id, verdict: "PASS", detail: `${candidate.label} passed the identity handshake` };
|
|
44792
|
+
}
|
|
44793
|
+
if (probe.status !== null) {
|
|
44794
|
+
anySpawned = true;
|
|
44795
|
+
reasons.push(`${candidate.label}: spawned but --help did not answer as border (impostor?)`);
|
|
44796
|
+
} else {
|
|
44797
|
+
reasons.push(`${candidate.label}: unspawnable (ENOENT/EACCES/ENOEXEC)`);
|
|
44798
|
+
}
|
|
44799
|
+
}
|
|
44800
|
+
if (anySpawned) {
|
|
44801
|
+
return { id, verdict: "FAIL", detail: `a border binary was reachable but failed the identity handshake \u2014 impersonation shape: ${reasons.join("; ")}` };
|
|
44802
|
+
}
|
|
44803
|
+
return { id, verdict: "CANNOT", detail: `no border binary reachable \u2014 tried: ${reasons.join("; ")}` };
|
|
44804
|
+
}
|
|
44805
|
+
|
|
44806
|
+
// src/opencode/aspects/routeA.ts
|
|
44807
|
+
function verifyRouteAParity(facts) {
|
|
44808
|
+
const id = "route-a-parity";
|
|
44809
|
+
const { routeAPlugin, routeAInstalled } = facts;
|
|
44810
|
+
if (!routeAInstalled) {
|
|
44811
|
+
return { id, verdict: "PASS", detail: `${routeAPlugin}: absent \u2014 Route A not installed, nothing to be stale` };
|
|
44812
|
+
}
|
|
44813
|
+
if (facts.packagedPlugin === null) {
|
|
44814
|
+
return { id, verdict: "CANNOT", detail: "the packaged plugin asset is unreadable \u2014 the border install itself looks broken, parity cannot be judged" };
|
|
44815
|
+
}
|
|
44816
|
+
const installed = facts.routeABytes;
|
|
44817
|
+
if (installed === null) {
|
|
44818
|
+
return { id, verdict: "CANNOT", detail: `${routeAPlugin}: present but unreadable \u2014 parity cannot be judged` };
|
|
44819
|
+
}
|
|
44820
|
+
const installedSha = sha256Hex3(installed);
|
|
44821
|
+
const packagedSha = sha256Hex3(facts.packagedPlugin);
|
|
44822
|
+
if (installedSha === packagedSha) {
|
|
44823
|
+
return { id, verdict: "PASS", detail: `${routeAPlugin}: up-to-date installed=${installedSha} packaged=${packagedSha}` };
|
|
44824
|
+
}
|
|
44825
|
+
const ours = firstLine(installed).startsWith(PLUGIN_MARKER);
|
|
44826
|
+
const shape = ours ? "outdated (marker ours, bytes differ)" : "foreign (first line lacks the marker)";
|
|
44827
|
+
return { id, verdict: "FAIL", detail: `${routeAPlugin}: ${shape} installed=${installedSha} packaged=${packagedSha}` };
|
|
44828
|
+
}
|
|
44829
|
+
|
|
44830
|
+
// src/opencode/aspects/scope.ts
|
|
44831
|
+
var SCOPE_ID = "scope";
|
|
44832
|
+
var SCOPE_NAME = "v1-load-surface";
|
|
44833
|
+
var SCOPE_UNCOVERED = [
|
|
44834
|
+
"V2 project-local .opencode/plugins/",
|
|
44835
|
+
"V2 npm-prefix/node_modules installs",
|
|
44836
|
+
"V2 lazy activation state"
|
|
44837
|
+
];
|
|
44838
|
+
function verifyScope() {
|
|
44839
|
+
return {
|
|
44840
|
+
id: SCOPE_ID,
|
|
44841
|
+
verdict: "PASS",
|
|
44842
|
+
detail: `verdict scope = ${SCOPE_NAME}; NOT covered (declared blind, separate ledger item): ${SCOPE_UNCOVERED.join(", ")}`
|
|
44843
|
+
};
|
|
44844
|
+
}
|
|
44845
|
+
|
|
44846
|
+
// src/opencode/registry.ts
|
|
44847
|
+
var isRecord3 = (v) => typeof v === "object" && v !== null;
|
|
44848
|
+
var defaultRegistryFetcher = async (url2, init) => {
|
|
44849
|
+
if (!/^https?:\/\//.test(url2)) throw new Error(`border opencode inspect refuses non-http(s) URL: ${url2}`);
|
|
44850
|
+
const res = await fetch(url2, {
|
|
44851
|
+
redirect: "follow",
|
|
44852
|
+
signal: init.signal,
|
|
44853
|
+
headers: { "user-agent": "border-customs (+https://github.com/TachikomaGundam/Border-Customs; border opencode inspect)" }
|
|
44854
|
+
});
|
|
44855
|
+
const raw = res.headers.get("content-length");
|
|
44856
|
+
const parsed = raw === null ? Number.NaN : Number(raw);
|
|
44857
|
+
return {
|
|
44858
|
+
status: res.status,
|
|
44859
|
+
contentLength: Number.isFinite(parsed) && parsed >= 0 ? parsed : null,
|
|
44860
|
+
chunks: () => {
|
|
44861
|
+
if (res.body === null) {
|
|
44862
|
+
return {
|
|
44863
|
+
async *[Symbol.asyncIterator]() {
|
|
44864
|
+
}
|
|
44865
|
+
};
|
|
44866
|
+
}
|
|
44867
|
+
return res.body;
|
|
44868
|
+
}
|
|
44869
|
+
};
|
|
44870
|
+
};
|
|
44871
|
+
function npmTagUrl(pkg, tag, base = DEFAULT_REGISTRY_BASE) {
|
|
44872
|
+
const enc = pkg.startsWith("@") ? pkg.replace("/", "%2F") : encodeURIComponent(pkg);
|
|
44873
|
+
return `${base}/${enc}/${encodeURIComponent(tag)}`;
|
|
44874
|
+
}
|
|
44875
|
+
var REGISTRY_BODY_CAP = 8 * 1024 * 1024;
|
|
44876
|
+
async function fetchRegistryVersion(fetcher, url2, timeoutMs) {
|
|
44877
|
+
const res = await fetcher(url2, { signal: AbortSignal.timeout(timeoutMs) });
|
|
44878
|
+
if (res.status !== 200) throw new Error(`HTTP ${String(res.status)} from ${url2}`);
|
|
44879
|
+
const parts = [];
|
|
44880
|
+
let total = 0;
|
|
44881
|
+
for await (const chunk of res.chunks()) {
|
|
44882
|
+
total += chunk.byteLength;
|
|
44883
|
+
if (total > REGISTRY_BODY_CAP) throw new Error(`${url2} exceeds the ${String(REGISTRY_BODY_CAP)} B metadata cap`);
|
|
44884
|
+
parts.push(Buffer.from(chunk));
|
|
44885
|
+
}
|
|
44886
|
+
let parsed;
|
|
44887
|
+
try {
|
|
44888
|
+
parsed = JSON.parse(Buffer.concat(parts).toString("utf8"));
|
|
44889
|
+
} catch {
|
|
44890
|
+
throw new Error(`metadata response from ${url2} is not valid JSON`);
|
|
44891
|
+
}
|
|
44892
|
+
if (!isRecord3(parsed) || typeof parsed["version"] !== "string" || parsed["version"].length === 0) {
|
|
44893
|
+
throw new Error(`metadata response from ${url2} carries no "version" string`);
|
|
44894
|
+
}
|
|
44895
|
+
return parsed["version"];
|
|
44896
|
+
}
|
|
44897
|
+
|
|
44898
|
+
// src/opencode/aspects/specFreshness.ts
|
|
44899
|
+
var isExactVersion2 = (spec) => /^\d+\.\d+\.\d+([-+].*)?$/.test(spec);
|
|
44900
|
+
async function verifySpecFreshness(facts, deps = {}) {
|
|
44901
|
+
const id = "spec-freshness";
|
|
44902
|
+
const { configs, cacheDirs, pkg } = facts;
|
|
44903
|
+
const errored = configs.filter((c) => c.status === "error").map((c) => c.path);
|
|
44904
|
+
if (errored.length > 0) {
|
|
44905
|
+
return { id, verdict: "CANNOT", detail: `configuration unreadable (${errored.join(", ")}) \u2014 the expected version cannot be determined` };
|
|
44906
|
+
}
|
|
44907
|
+
const specs = [...new Set(configs.flatMap((c) => [...c.specs]).map((s) => splitNpmSpec(s).spec).filter((s) => s !== null))];
|
|
44908
|
+
if (specs.length === 0) {
|
|
44909
|
+
return { id, verdict: "PASS", detail: `no ${pkg} spec declared in opencode config \u2014 nothing to be fresh` };
|
|
44910
|
+
}
|
|
44911
|
+
const expected = /* @__PURE__ */ new Map();
|
|
44912
|
+
const fetcher = deps.fetcher ?? defaultRegistryFetcher;
|
|
44913
|
+
const timeoutMs = deps.timeoutMs ?? REGISTRY_TIMEOUT_MS2;
|
|
44914
|
+
for (const spec of specs) {
|
|
44915
|
+
if (isExactVersion2(spec)) {
|
|
44916
|
+
expected.set(spec, spec);
|
|
44917
|
+
continue;
|
|
44918
|
+
}
|
|
44919
|
+
try {
|
|
44920
|
+
expected.set(spec, await fetchRegistryVersion(fetcher, npmTagUrl(pkg, spec, deps.registryBase), timeoutMs));
|
|
44921
|
+
} catch (err) {
|
|
44922
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44923
|
+
return { id, verdict: "CANNOT", detail: `registry unreachable for spec '${pkg}@${spec}': ${message} \u2014 border never reads silence as fresh` };
|
|
44924
|
+
}
|
|
44925
|
+
}
|
|
44926
|
+
if (cacheDirs.length === 0) {
|
|
44927
|
+
return { id, verdict: "PASS", detail: `nothing cached to compare (see cache-version); spec resolves to ${[...new Set(expected.values())].join(", ")}` };
|
|
44928
|
+
}
|
|
44929
|
+
const wanted = new Set(expected.values());
|
|
44930
|
+
const judged = cacheDirs.filter((d) => d.packageVersion !== null);
|
|
44931
|
+
const stale = judged.filter((d) => !wanted.has(d.packageVersion));
|
|
44932
|
+
if (judged.length > 0 && stale.length > 0) {
|
|
44933
|
+
return {
|
|
44934
|
+
id,
|
|
44935
|
+
verdict: "FAIL",
|
|
44936
|
+
detail: `cache holds ${stale.map((d) => `${d.tag}->${d.packageVersion}`).join(",")}, spec expects ${[...wanted].join(",")}`
|
|
44937
|
+
};
|
|
44938
|
+
}
|
|
44939
|
+
return { id, verdict: "PASS", detail: `cache matches spec: ${[...wanted].join(",")}` };
|
|
44940
|
+
}
|
|
44941
|
+
|
|
44942
|
+
// src/opencode/inspect.ts
|
|
44943
|
+
function aggregateVerdict(aspects) {
|
|
44944
|
+
if (aspects.some((a) => a.verdict === "FAIL")) return "FAIL";
|
|
44945
|
+
if (aspects.some((a) => a.verdict === "CANNOT")) return "CANNOT";
|
|
44946
|
+
return "PASS";
|
|
44947
|
+
}
|
|
44948
|
+
function inspectExit(verdict) {
|
|
44949
|
+
switch (verdict) {
|
|
44950
|
+
case "FAIL":
|
|
44951
|
+
return EXIT_BLOCKED;
|
|
44952
|
+
case "CANNOT":
|
|
44953
|
+
return EXIT_ERROR;
|
|
44954
|
+
case "PASS":
|
|
44955
|
+
return EXIT_PASS;
|
|
44956
|
+
}
|
|
44957
|
+
}
|
|
44958
|
+
function readPackagedPlugin(path4) {
|
|
44959
|
+
try {
|
|
44960
|
+
return readFileSync19(path4);
|
|
44961
|
+
} catch {
|
|
44962
|
+
return null;
|
|
44963
|
+
}
|
|
44964
|
+
}
|
|
44965
|
+
function gatherFacts(env2, cwd, deps) {
|
|
44966
|
+
const pkg = deps.package ?? BORDER_PACKAGE;
|
|
44967
|
+
const pluginDrop = routeAPluginPath(env2);
|
|
44968
|
+
const cacheRoot = deps.cacheRoot ?? opencodeCacheRoot(env2);
|
|
44969
|
+
const globalPath = deps.globalConfigPath ?? join30(opencodeConfigRoot(env2), "opencode", "opencode.jsonc");
|
|
44970
|
+
const routeAInstalled = existsSync21(pluginDrop);
|
|
44971
|
+
return {
|
|
44972
|
+
pkg,
|
|
44973
|
+
env: env2,
|
|
44974
|
+
cacheRoot,
|
|
44975
|
+
cacheDirs: listCacheDirs(cacheRoot, pkg),
|
|
44976
|
+
configs: [readPluginDeclarations(globalPath, pkg), readPluginDeclarations(join30(cwd, "opencode.jsonc"), pkg)],
|
|
44977
|
+
packagedPlugin: readPackagedPlugin(packagedAssetPath(import.meta.url, "border.ts")),
|
|
44978
|
+
routeAPlugin: pluginDrop,
|
|
44979
|
+
routeAInstalled,
|
|
44980
|
+
routeABytes: routeAInstalled ? readPackagedPlugin(pluginDrop) : null
|
|
44981
|
+
};
|
|
44982
|
+
}
|
|
44983
|
+
async function runInspectCore(ctx, deps = {}) {
|
|
44984
|
+
const facts = gatherFacts(ctx.env, ctx.cwd, deps);
|
|
44985
|
+
const aspects = [
|
|
44986
|
+
verifyCacheVersion(facts),
|
|
44987
|
+
await verifySpecFreshness(facts, {
|
|
44988
|
+
...deps.registryFetcher !== void 0 ? { fetcher: deps.registryFetcher } : {},
|
|
44989
|
+
...deps.registryBase !== void 0 ? { registryBase: deps.registryBase } : {}
|
|
44990
|
+
}),
|
|
44991
|
+
await verifyIdentity({
|
|
44992
|
+
...deps.runner !== void 0 ? { runner: deps.runner } : {},
|
|
44993
|
+
distEntry: deps.distEntry ?? packagedDistEntry(import.meta.url),
|
|
44994
|
+
env: facts.env
|
|
44995
|
+
}),
|
|
44996
|
+
verifyRouteAParity(facts),
|
|
44997
|
+
verifyDualRoute(facts),
|
|
44998
|
+
verifyScope()
|
|
44999
|
+
];
|
|
45000
|
+
const verdict = aggregateVerdict(aspects);
|
|
45001
|
+
const report = { schemaVersion: 1, scope: "v1", verdict, aspects };
|
|
45002
|
+
if (ctx.flags.json) {
|
|
45003
|
+
ctx.stdout(JSON.stringify(report));
|
|
45004
|
+
} else {
|
|
45005
|
+
ctx.stdout(`border opencode inspect [${report.scope}] ${verdict}`);
|
|
45006
|
+
for (const a of aspects) {
|
|
45007
|
+
ctx.stdout(` ${a.verdict.padEnd(6)} ${a.id}: ${a.detail}`);
|
|
45008
|
+
}
|
|
45009
|
+
}
|
|
45010
|
+
return inspectExit(verdict);
|
|
45011
|
+
}
|
|
45012
|
+
|
|
45013
|
+
// src/commands/opencode.ts
|
|
45014
|
+
function resolvePluginAsset(moduleUrl, fileName) {
|
|
45015
|
+
const here = dirname16(fileURLToPath6(moduleUrl));
|
|
45016
|
+
const candidates = [
|
|
45017
|
+
join31(here, "..", "plugin", fileName),
|
|
45018
|
+
// dist mode (bundled): <root>/plugin/<f>
|
|
45019
|
+
join31(here, "..", "..", "plugin", fileName)
|
|
45020
|
+
// src mode: <root>/plugin/<f>
|
|
45021
|
+
];
|
|
45022
|
+
return candidates.find((p) => existsSync22(p)) ?? candidates[1];
|
|
45023
|
+
}
|
|
45024
|
+
function targetsFor(env2) {
|
|
45025
|
+
const root = join31(opencodeConfigRoot(env2), "opencode");
|
|
45026
|
+
return [
|
|
45027
|
+
{ fileName: "border.ts", destination: join31(root, "plugins", "border.ts"), marker: PLUGIN_MARKER },
|
|
45028
|
+
{ fileName: "border-command.md", destination: join31(root, "commands", "border.md"), marker: COMMAND_MARKER }
|
|
45029
|
+
];
|
|
44522
45030
|
}
|
|
44523
45031
|
function carriesMarker(bytes, marker) {
|
|
44524
45032
|
return firstLine(bytes).startsWith(marker);
|
|
@@ -44530,7 +45038,7 @@ function fail(ctx, message) {
|
|
|
44530
45038
|
function readPackagedAsset(ctx, fileName) {
|
|
44531
45039
|
const source = resolvePluginAsset(import.meta.url, fileName);
|
|
44532
45040
|
try {
|
|
44533
|
-
return
|
|
45041
|
+
return readFileSync20(source);
|
|
44534
45042
|
} catch {
|
|
44535
45043
|
ctx.stderr(`border: opencode: packaged asset is missing (${source})`);
|
|
44536
45044
|
ctx.stderr("border: opencode: the border install looks broken \u2014 `npm i -g border-customs` or a fresh checkout should carry plugin/.");
|
|
@@ -44539,7 +45047,7 @@ function readPackagedAsset(ctx, fileName) {
|
|
|
44539
45047
|
}
|
|
44540
45048
|
function readTarget(destination) {
|
|
44541
45049
|
try {
|
|
44542
|
-
return
|
|
45050
|
+
return readFileSync20(destination);
|
|
44543
45051
|
} catch {
|
|
44544
45052
|
return null;
|
|
44545
45053
|
}
|
|
@@ -44606,7 +45114,7 @@ function opencodeInstall(ctx) {
|
|
|
44606
45114
|
ctx.stdout(`${target.destination}: up to date`);
|
|
44607
45115
|
continue;
|
|
44608
45116
|
}
|
|
44609
|
-
mkdirSync12(
|
|
45117
|
+
mkdirSync12(dirname16(target.destination), { recursive: true });
|
|
44610
45118
|
writeFileSync8(target.destination, packaged);
|
|
44611
45119
|
ctx.stdout(`${target.destination}: ${state === "outdated" ? "updated" : "installed"}`);
|
|
44612
45120
|
}
|
|
@@ -44620,8 +45128,8 @@ function opencodeStatus(ctx) {
|
|
|
44620
45128
|
for (const { target, packaged } of prepared) {
|
|
44621
45129
|
const current = readTarget(target.destination);
|
|
44622
45130
|
const state = classify(target, current, packaged);
|
|
44623
|
-
const installedSha = current === null ? "-" :
|
|
44624
|
-
ctx.stdout(`${target.destination}: ${state} installed=${installedSha} packaged=${
|
|
45131
|
+
const installedSha = current === null ? "-" : sha256Hex3(current);
|
|
45132
|
+
ctx.stdout(`${target.destination}: ${state} installed=${installedSha} packaged=${sha256Hex3(packaged)}`);
|
|
44625
45133
|
}
|
|
44626
45134
|
return EXIT_PASS;
|
|
44627
45135
|
}
|
|
@@ -44648,23 +45156,27 @@ function opencodeUninstall(ctx) {
|
|
|
44648
45156
|
return EXIT_PASS;
|
|
44649
45157
|
}
|
|
44650
45158
|
function runOpencode(ctx) {
|
|
44651
|
-
const [sub, ...
|
|
44652
|
-
|
|
44653
|
-
|
|
44654
|
-
`opencode ${sub ?? "<none>"}: unexpected argument '${rest[0] ?? ""}' \u2014 usage: border opencode install | status | uninstall`
|
|
44655
|
-
);
|
|
44656
|
-
}
|
|
45159
|
+
const [sub, ...restAll] = ctx.positionals;
|
|
45160
|
+
const rest = restAll.filter((arg) => arg !== "--json");
|
|
45161
|
+
const USAGE = "usage: border opencode install | status | uninstall | inspect";
|
|
44657
45162
|
switch (sub) {
|
|
44658
45163
|
case "install":
|
|
44659
|
-
return opencodeInstall(ctx);
|
|
44660
45164
|
case "status":
|
|
44661
|
-
return opencodeStatus(ctx);
|
|
44662
45165
|
case "uninstall":
|
|
44663
|
-
|
|
45166
|
+
case "inspect": {
|
|
45167
|
+
if (rest.length > 0) {
|
|
45168
|
+
throw new UnknownArgError(`opencode ${sub}: unexpected argument '${rest[0] ?? ""}' \u2014 ${USAGE}`);
|
|
45169
|
+
}
|
|
45170
|
+
const table2 = {
|
|
45171
|
+
install: opencodeInstall,
|
|
45172
|
+
status: opencodeStatus,
|
|
45173
|
+
uninstall: opencodeUninstall,
|
|
45174
|
+
inspect: runInspectCore
|
|
45175
|
+
};
|
|
45176
|
+
return table2[sub](ctx);
|
|
45177
|
+
}
|
|
44664
45178
|
default:
|
|
44665
|
-
throw new UnknownArgError(
|
|
44666
|
-
`opencode: unknown subcommand '${sub ?? "<none>"}' \u2014 usage: border opencode install | status | uninstall`
|
|
44667
|
-
);
|
|
45179
|
+
throw new UnknownArgError(`opencode: unknown subcommand '${sub ?? "<none>"}' \u2014 ${USAGE}`);
|
|
44668
45180
|
}
|
|
44669
45181
|
}
|
|
44670
45182
|
|
|
@@ -44768,7 +45280,7 @@ function confirmRemoteBranch(i) {
|
|
|
44768
45280
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
44769
45281
|
import { mkdtempSync as mkdtempSync6, rmSync as rmSync14 } from "node:fs";
|
|
44770
45282
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
44771
|
-
import { dirname as
|
|
45283
|
+
import { dirname as dirname17, join as join32, resolve as resolve18 } from "node:path";
|
|
44772
45284
|
var LANDINGS_REF_PREFIX = "refs/border-landings/";
|
|
44773
45285
|
async function runLandingVerification(o) {
|
|
44774
45286
|
if (o.legs.length === 0) return null;
|
|
@@ -44818,7 +45330,7 @@ function gitPlain(repoDir, env2, args) {
|
|
|
44818
45330
|
for (const [name, value] of Object.entries(env2)) {
|
|
44819
45331
|
if (!name.startsWith("GIT_")) clean[name] = value;
|
|
44820
45332
|
}
|
|
44821
|
-
clean.GIT_CEILING_DIRECTORIES =
|
|
45333
|
+
clean.GIT_CEILING_DIRECTORIES = dirname17(resolve18(repoDir));
|
|
44822
45334
|
clean.GIT_OPTIONAL_LOCKS = "0";
|
|
44823
45335
|
clean.GIT_TERMINAL_PROMPT = "0";
|
|
44824
45336
|
const r = spawnSync18("git", ["-C", resolve18(repoDir), ...args], { encoding: "utf8", env: clean, timeout: 55e3, maxBuffer: 32 * 1024 * 1024 });
|
|
@@ -44843,7 +45355,7 @@ async function defaultScanRemoteTip(o, remote, branch, remoteSha) {
|
|
|
44843
45355
|
ownedTempRef = landingRef;
|
|
44844
45356
|
rev = git.run(["rev-parse", landingRef]).trim();
|
|
44845
45357
|
}
|
|
44846
|
-
const treeRoot = mkdtempSync6(
|
|
45358
|
+
const treeRoot = mkdtempSync6(join32(tmpdir4(), "border-landing-tree-"));
|
|
44847
45359
|
try {
|
|
44848
45360
|
return await scanTipTree(git, treeRoot, rev, o.env);
|
|
44849
45361
|
} finally {
|
|
@@ -45052,14 +45564,14 @@ async function executeYesPush(ctx, loaded) {
|
|
|
45052
45564
|
import { mkdtempSync as mkdtempSync7, rmSync as rmSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
45053
45565
|
import { randomBytes } from "node:crypto";
|
|
45054
45566
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
45055
|
-
import { join as
|
|
45567
|
+
import { join as join33 } from "node:path";
|
|
45056
45568
|
|
|
45057
45569
|
// src/scan/fetch.ts
|
|
45058
45570
|
import { createHash as createHash18 } from "node:crypto";
|
|
45059
45571
|
import { basename as basename5 } from "node:path";
|
|
45060
45572
|
var SCAN_MAX_ARTIFACT_BYTES = 200 * 1024 * 1024;
|
|
45061
45573
|
var SCAN_REQUEST_TIMEOUT_MS = 6e4;
|
|
45062
|
-
var SCAN_USER_AGENT = "border-customs/0.
|
|
45574
|
+
var SCAN_USER_AGENT = "border-customs/0.8.0 (+https://github.com/sumteclab/border; border scan)";
|
|
45063
45575
|
var CRATES_DOWNLOAD_HOST = "https://static.crates.io";
|
|
45064
45576
|
var defaultScanFetcher = async (url2, init) => {
|
|
45065
45577
|
if (!/^https?:\/\//.test(url2)) throw new EngineRunError(`border scan refuses non-http(s) URL: ${url2}`, null);
|
|
@@ -45113,7 +45625,7 @@ async function fetchJson(fetcher, url2, timeoutMs) {
|
|
|
45113
45625
|
fail2(`metadata response from ${url2} is not valid JSON`);
|
|
45114
45626
|
}
|
|
45115
45627
|
}
|
|
45116
|
-
var
|
|
45628
|
+
var isRecord4 = (v) => typeof v === "object" && v !== null;
|
|
45117
45629
|
function channelBaseUrl(eco) {
|
|
45118
45630
|
const ch = publishChannels().find((c) => c.id === eco);
|
|
45119
45631
|
if (ch === void 0 || ch.defaultUrl.length === 0) {
|
|
@@ -45144,7 +45656,7 @@ async function fetchArtifact(spec, o) {
|
|
|
45144
45656
|
switch (spec.ecosystem) {
|
|
45145
45657
|
case "npm": {
|
|
45146
45658
|
const meta3 = await fetchJson(o.fetcher, npmMetadataUrl(spec.name, spec.version), timeoutMs);
|
|
45147
|
-
const dist =
|
|
45659
|
+
const dist = isRecord4(meta3) && isRecord4(meta3["dist"]) ? meta3["dist"] : {};
|
|
45148
45660
|
const tarball = dist["tarball"];
|
|
45149
45661
|
if (typeof tarball !== "string" || tarball.length === 0) fail2(`npm metadata for ${label} has no dist.tarball`);
|
|
45150
45662
|
if (!/^https?:\/\//.test(tarball)) fail2(`npm dist.tarball for ${label} is not an http(s) URL`);
|
|
@@ -45155,8 +45667,8 @@ async function fetchArtifact(spec, o) {
|
|
|
45155
45667
|
}
|
|
45156
45668
|
case "pypi": {
|
|
45157
45669
|
const meta3 = await fetchJson(o.fetcher, pypiMetadataUrl(spec.name, spec.version), timeoutMs);
|
|
45158
|
-
const urls =
|
|
45159
|
-
const files = urls.filter(
|
|
45670
|
+
const urls = isRecord4(meta3) && Array.isArray(meta3["urls"]) ? meta3["urls"] : [];
|
|
45671
|
+
const files = urls.filter(isRecord4);
|
|
45160
45672
|
const sdists = files.filter((f) => f["packagetype"] === "sdist");
|
|
45161
45673
|
const sdist = sdists.find((f) => typeof f["filename"] === "string" && f["filename"].endsWith(".tar.gz"));
|
|
45162
45674
|
if (sdist === void 0) {
|
|
@@ -45184,7 +45696,7 @@ async function fetchArtifact(spec, o) {
|
|
|
45184
45696
|
}
|
|
45185
45697
|
}
|
|
45186
45698
|
}
|
|
45187
|
-
function
|
|
45699
|
+
function sha256Hex4(data) {
|
|
45188
45700
|
return createHash18("sha256").update(data).digest("hex");
|
|
45189
45701
|
}
|
|
45190
45702
|
|
|
@@ -45228,7 +45740,7 @@ function parseScanSpec(raw) {
|
|
|
45228
45740
|
}
|
|
45229
45741
|
|
|
45230
45742
|
// src/roundtrip/localArtifact.ts
|
|
45231
|
-
import { createReadStream, existsSync as
|
|
45743
|
+
import { createReadStream, existsSync as existsSync23, statSync as statSync3 } from "node:fs";
|
|
45232
45744
|
import { createHash as createHash19 } from "node:crypto";
|
|
45233
45745
|
import { basename as basename6, resolve as resolve19 } from "node:path";
|
|
45234
45746
|
var EXT_TABLE = /* @__PURE__ */ new Map([
|
|
@@ -45277,7 +45789,7 @@ var GRAMMAR_HINT = {
|
|
|
45277
45789
|
};
|
|
45278
45790
|
function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
|
|
45279
45791
|
const abs = resolve19(cwd, raw);
|
|
45280
|
-
if (
|
|
45792
|
+
if (existsSync23(abs)) {
|
|
45281
45793
|
const st = statSync3(abs);
|
|
45282
45794
|
if (st.isDirectory()) {
|
|
45283
45795
|
bad3(`'${raw}' resolves to a directory (${abs}) \u2014 local mode needs a single artifact FILE, not a directory`);
|
|
@@ -45636,7 +46148,7 @@ function classifyResidue(label, diff, attribution = null) {
|
|
|
45636
46148
|
|
|
45637
46149
|
// src/roundtrip/orchestrate.ts
|
|
45638
46150
|
function containerNameFor(label, rand = randomBytes(3).toString("hex")) {
|
|
45639
|
-
return `rt-${
|
|
46151
|
+
return `rt-${sha256Hex4(label).slice(0, 8)}-${rand}`;
|
|
45640
46152
|
}
|
|
45641
46153
|
var shArgv = (name) => {
|
|
45642
46154
|
if (!/^[A-Za-z0-9@._+/-]+$/.test(name)) {
|
|
@@ -45757,7 +46269,7 @@ function runPipeline2(exec, profile, spec, label, hostPath, container, closure,
|
|
|
45757
46269
|
return {
|
|
45758
46270
|
findings: classifyResidue(label, diffManifests(m1, m3), attribution),
|
|
45759
46271
|
installDelta,
|
|
45760
|
-
baselineDigest:
|
|
46272
|
+
baselineDigest: sha256Hex4([...m1.keys()].join("\n"))
|
|
45761
46273
|
};
|
|
45762
46274
|
} finally {
|
|
45763
46275
|
try {
|
|
@@ -45791,12 +46303,12 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
45791
46303
|
ctx.stderr("roundtrip: docker unavailable \u2014 cannot verify");
|
|
45792
46304
|
return EXIT_ERROR;
|
|
45793
46305
|
}
|
|
45794
|
-
const baseDir = mkdtempSync7(
|
|
46306
|
+
const baseDir = mkdtempSync7(join33(tmpdir5(), "border-roundtrip-"));
|
|
45795
46307
|
try {
|
|
45796
46308
|
const localRead = local !== null ? await readLocalArtifact(local.absPath) : null;
|
|
45797
46309
|
const bytes = localRead?.bytes ?? (await fetchArtifact(spec, { fetcher: deps.fetcher ?? defaultScanFetcher })).bytes;
|
|
45798
|
-
const artifactSha256 = localRead !== null ? localRead.sha256 :
|
|
45799
|
-
const hostPath =
|
|
46310
|
+
const artifactSha256 = localRead !== null ? localRead.sha256 : sha256Hex4(bytes);
|
|
46311
|
+
const hostPath = join33(baseDir, "artifact");
|
|
45800
46312
|
writeFileSync9(hostPath, bytes);
|
|
45801
46313
|
const container = containerNameFor(label);
|
|
45802
46314
|
const closure = spec.ecosystem === "pypi" ? resolvePipClosure(exec, profile, hostPath, label, spec) : null;
|
|
@@ -45804,12 +46316,12 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
45804
46316
|
const verdict = computeVerdict(findings);
|
|
45805
46317
|
const report = {
|
|
45806
46318
|
schemaVersion: 1,
|
|
45807
|
-
key:
|
|
46319
|
+
key: sha256Hex4(`roundtrip:${label}:${baselineDigest}:${verdict}`),
|
|
45808
46320
|
head: baselineDigest.slice(0, 40),
|
|
45809
46321
|
dirty: false,
|
|
45810
46322
|
exposureSet: [label],
|
|
45811
46323
|
refSet: [],
|
|
45812
|
-
rulesHash:
|
|
46324
|
+
rulesHash: sha256Hex4(`${profile.image}|${profile.install(profile.artifactPath, spec).join(" ")}|${profile.uninstall(spec).join(" ")}`),
|
|
45813
46325
|
verdict,
|
|
45814
46326
|
counts: countFindings(findings),
|
|
45815
46327
|
findings,
|
|
@@ -45899,7 +46411,7 @@ var runRoundtrip = (ctx) => runRoundtripCore(ctx);
|
|
|
45899
46411
|
// src/scan/scan.ts
|
|
45900
46412
|
import { mkdtempSync as mkdtempSync8 } from "node:fs";
|
|
45901
46413
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
45902
|
-
import { join as
|
|
46414
|
+
import { join as join35 } from "node:path";
|
|
45903
46415
|
|
|
45904
46416
|
// src/scan/config.ts
|
|
45905
46417
|
function buildScanConfigYaml(eco) {
|
|
@@ -45926,8 +46438,8 @@ function synthesizeScanConfig(eco) {
|
|
|
45926
46438
|
}
|
|
45927
46439
|
|
|
45928
46440
|
// src/scan/materialize.ts
|
|
45929
|
-
import { existsSync as
|
|
45930
|
-
import { join as
|
|
46441
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync13, readdirSync as readdirSync11, readFileSync as readFileSync21, rmSync as rmSync16, writeFileSync as writeFileSync10 } from "node:fs";
|
|
46442
|
+
import { join as join34 } from "node:path";
|
|
45931
46443
|
var CRATES_ENVELOPE_FILES = ["Cargo.toml.orig", ".cargo-ok", ".cargo_vcs_info.json"];
|
|
45932
46444
|
var fail3 = (why) => {
|
|
45933
46445
|
throw new Error(`border scan cannot materialize the artifact (${why}) \u2014 refusing to stage an unusable tree (fail-closed)`);
|
|
@@ -45941,15 +46453,15 @@ function extractFlat(archive, destDir) {
|
|
|
45941
46453
|
}
|
|
45942
46454
|
}
|
|
45943
46455
|
function unwrapSingleDir(dir) {
|
|
45944
|
-
const entries =
|
|
46456
|
+
const entries = readdirSync11(dir, { withFileTypes: true });
|
|
45945
46457
|
const dirs = entries.filter((e) => e.isDirectory());
|
|
45946
46458
|
if (entries.length === 1 && dirs.length === 1 && dirs[0] !== void 0) {
|
|
45947
|
-
return
|
|
46459
|
+
return join34(dir, dirs[0].name);
|
|
45948
46460
|
}
|
|
45949
46461
|
return dir;
|
|
45950
46462
|
}
|
|
45951
46463
|
function requireManifest(repoDir, ecosystem, candidates) {
|
|
45952
|
-
if (!candidates.some((rel) =>
|
|
46464
|
+
if (!candidates.some((rel) => existsSync24(join34(repoDir, rel)))) {
|
|
45953
46465
|
fail3(`${ecosystem} package tree has none of [${candidates.join(", ")}] at its root`);
|
|
45954
46466
|
}
|
|
45955
46467
|
}
|
|
@@ -45959,18 +46471,18 @@ function materializePackage(o) {
|
|
|
45959
46471
|
`);
|
|
45960
46472
|
});
|
|
45961
46473
|
const safe = safeArtifactName(o.filename, `${o.ecosystem}-artifact`);
|
|
45962
|
-
const archive =
|
|
46474
|
+
const archive = join34(o.baseDir, safe);
|
|
45963
46475
|
writeFileSync10(archive, o.bytes);
|
|
45964
|
-
const extractDir =
|
|
46476
|
+
const extractDir = join34(o.baseDir, "extract");
|
|
45965
46477
|
mkdirSync13(extractDir, { recursive: true });
|
|
45966
46478
|
extractFlat(archive, extractDir);
|
|
45967
46479
|
let repoDir = unwrapSingleDir(extractDir);
|
|
45968
46480
|
if (o.ecosystem === "rubygems") {
|
|
45969
46481
|
const outer = repoDir;
|
|
45970
46482
|
const dataCandidates = findNativeMissArchives(outer).filter((f) => f.endsWith("data.tar.gz"));
|
|
45971
|
-
const inner = dataCandidates[0] ??
|
|
45972
|
-
if (!
|
|
45973
|
-
const gemTree =
|
|
46483
|
+
const inner = dataCandidates[0] ?? join34(outer, "data.tar.gz");
|
|
46484
|
+
if (!existsSync24(inner)) fail3(".gem archive contains no data.tar.gz");
|
|
46485
|
+
const gemTree = join34(o.baseDir, "gem-tree");
|
|
45974
46486
|
mkdirSync13(gemTree, { recursive: true });
|
|
45975
46487
|
extractFlat(inner, gemTree);
|
|
45976
46488
|
repoDir = unwrapSingleDir(gemTree);
|
|
@@ -45981,11 +46493,11 @@ function materializePackage(o) {
|
|
|
45981
46493
|
} else {
|
|
45982
46494
|
requireManifest(repoDir, o.ecosystem, ["Cargo.toml"]);
|
|
45983
46495
|
for (const file2 of CRATES_ENVELOPE_FILES) {
|
|
45984
|
-
const path4 =
|
|
45985
|
-
if (!
|
|
45986
|
-
const original =
|
|
46496
|
+
const path4 = join34(repoDir, file2);
|
|
46497
|
+
if (!existsSync24(path4)) continue;
|
|
46498
|
+
const original = readFileSync21(path4);
|
|
45987
46499
|
rmSync16(path4, { force: true });
|
|
45988
|
-
note(`border scan: crates envelope normalized: ${file2} sha256=${
|
|
46500
|
+
note(`border scan: crates envelope normalized: ${file2} sha256=${sha256Hex4(original)}`);
|
|
45989
46501
|
}
|
|
45990
46502
|
}
|
|
45991
46503
|
const env2 = o.env;
|
|
@@ -46011,7 +46523,7 @@ async function runScanCore(ctx, deps = {}) {
|
|
|
46011
46523
|
}
|
|
46012
46524
|
const cfg = synthesizeScanConfig(spec.ecosystem);
|
|
46013
46525
|
const fetcher = deps.fetcher ?? defaultScanFetcher;
|
|
46014
|
-
const baseDir = mkdtempSync8(
|
|
46526
|
+
const baseDir = mkdtempSync8(join35(tmpdir6(), "border-scan-"));
|
|
46015
46527
|
try {
|
|
46016
46528
|
const { bytes, filename } = await fetchArtifact(spec, { fetcher });
|
|
46017
46529
|
const env2 = { ...ctx.env };
|
|
@@ -46022,12 +46534,12 @@ async function runScanCore(ctx, deps = {}) {
|
|
|
46022
46534
|
const verdict = computeVerdict(findings);
|
|
46023
46535
|
const report = {
|
|
46024
46536
|
schemaVersion: 1,
|
|
46025
|
-
key:
|
|
46537
|
+
key: sha256Hex4(`scan:${spec.ecosystem}:${spec.name}@${spec.version}:${head}`),
|
|
46026
46538
|
head,
|
|
46027
46539
|
dirty: false,
|
|
46028
46540
|
exposureSet: [`${spec.ecosystem}:${spec.name}@${spec.version}`],
|
|
46029
46541
|
refSet: [],
|
|
46030
|
-
rulesHash:
|
|
46542
|
+
rulesHash: sha256Hex4(buildScanConfigYaml(spec.ecosystem)),
|
|
46031
46543
|
verdict,
|
|
46032
46544
|
counts: countFindings(findings),
|
|
46033
46545
|
findings,
|
|
@@ -46168,7 +46680,7 @@ function usage() {
|
|
|
46168
46680
|
" llm-ingest record provenance captured by the harness",
|
|
46169
46681
|
" scan inspect a third-party package ([ecosystem:]name@version) for residue",
|
|
46170
46682
|
" roundtrip install+uninstall a package (registry spec or local artifact file) in a throwaway container, report uninstall residue",
|
|
46171
|
-
" opencode install|status|uninstall the official opencode plugin adapter (files under $XDG_CONFIG_HOME/opencode)",
|
|
46683
|
+
" opencode install|status|uninstall|inspect the official opencode plugin adapter (files under $XDG_CONFIG_HOME/opencode; inspect = read-only V1 load-surface audit)",
|
|
46172
46684
|
" exfil read-only exfil-lens scan of <ref|url> [ref...]: tip tree + tag notes (--deep adds reachable-history blobs + commit messages)",
|
|
46173
46685
|
"",
|
|
46174
46686
|
"flags:",
|
|
@@ -46311,7 +46823,7 @@ function isMainModule({ argv1, moduleUrl, realpath }) {
|
|
|
46311
46823
|
return p;
|
|
46312
46824
|
}
|
|
46313
46825
|
};
|
|
46314
|
-
return pathToFileURL(canonical(argv1)).href === pathToFileURL(canonical(
|
|
46826
|
+
return pathToFileURL(canonical(argv1)).href === pathToFileURL(canonical(fileURLToPath7(moduleUrl))).href;
|
|
46315
46827
|
}
|
|
46316
46828
|
if (isMainModule({ argv1: process.argv[1], moduleUrl: import.meta.url, realpath: realpathSync.native })) {
|
|
46317
46829
|
const outcome = run(process.argv.slice(2));
|