border-customs 0.4.1 → 0.5.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 +65 -0
- package/dist/assets/package-lock.json +371 -3
- package/dist/index.js +356 -85
- package/package.json +10 -3
- package/plugin/border-command.md +17 -0
- package/plugin/border.ts +239 -0
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 fileURLToPath6, pathToFileURL } from "node:url";
|
|
13033
13033
|
|
|
13034
13034
|
// src/cli.ts
|
|
13035
13035
|
import { parseArgs } from "node:util";
|
|
@@ -37940,12 +37940,12 @@ function packOnce(o) {
|
|
|
37940
37940
|
throw new EngineRunError(`npm pack reported '${report.filename}' but no tarball landed in ${o.distDir}`, null);
|
|
37941
37941
|
}
|
|
37942
37942
|
const data = readFileSync3(tarballAbs);
|
|
37943
|
-
const
|
|
37943
|
+
const sha2564 = createHash3("sha256").update(data).digest("hex");
|
|
37944
37944
|
const sha1 = createHash3("sha1").update(data).digest("hex");
|
|
37945
37945
|
if (sha1 !== report.shasum || data.byteLength !== report.size) {
|
|
37946
37946
|
throw new EngineRunError(`npm pack report disagrees with the tarball bytes for ${report.filename} \u2014 corrupted or swapped artifact, failing closed`, null);
|
|
37947
37947
|
}
|
|
37948
|
-
return { filename: report.filename, sha256:
|
|
37948
|
+
return { filename: report.filename, sha256: sha2564, bytes: data.byteLength, tarballAbs };
|
|
37949
37949
|
}
|
|
37950
37950
|
function publintFinding(o, tarballAbs, identity) {
|
|
37951
37951
|
const candidates = o.publintBinPath !== void 0 ? [o.publintBinPath] : [
|
|
@@ -38950,7 +38950,7 @@ async function runNpmArtifactStage(o) {
|
|
|
38950
38950
|
const stateDir = join10(repoDir, BORDER_STATE_DIR);
|
|
38951
38951
|
const distDir = join10(stateDir, "dist");
|
|
38952
38952
|
mkdirSync3(distDir, { recursive: true });
|
|
38953
|
-
const { filename, sha256:
|
|
38953
|
+
const { filename, sha256: sha2564, bytes, tarballAbs } = packOnce({
|
|
38954
38954
|
repoDir,
|
|
38955
38955
|
distDir,
|
|
38956
38956
|
...o.env !== void 0 ? { env: o.env } : {},
|
|
@@ -38958,7 +38958,7 @@ async function runNpmArtifactStage(o) {
|
|
|
38958
38958
|
...o.npmTimeoutMs !== void 0 ? { npmTimeoutMs: o.npmTimeoutMs } : {}
|
|
38959
38959
|
});
|
|
38960
38960
|
const findings = [];
|
|
38961
|
-
const extractDir = join10(stateDir, "tmp",
|
|
38961
|
+
const extractDir = join10(stateDir, "tmp", sha2564.slice(0, 8));
|
|
38962
38962
|
rmSync3(extractDir, { recursive: true, force: true });
|
|
38963
38963
|
mkdirSync3(extractDir, { recursive: true });
|
|
38964
38964
|
try {
|
|
@@ -39063,7 +39063,7 @@ async function runNpmArtifactStage(o) {
|
|
|
39063
39063
|
if (publint !== null) findings.push(publint);
|
|
39064
39064
|
return {
|
|
39065
39065
|
findings,
|
|
39066
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${filename}`, sha256:
|
|
39066
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${filename}`, sha256: sha2564, bytes }
|
|
39067
39067
|
};
|
|
39068
39068
|
}
|
|
39069
39069
|
|
|
@@ -39707,6 +39707,7 @@ import { spawnSync as spawnSync7 } from "node:child_process";
|
|
|
39707
39707
|
import { createHash as createHash7 } from "node:crypto";
|
|
39708
39708
|
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync as readdirSync4, rmSync as rmSync5 } from "node:fs";
|
|
39709
39709
|
import { join as join13 } from "node:path";
|
|
39710
|
+
import { gunzipSync as gunzipSync2 } from "node:zlib";
|
|
39710
39711
|
|
|
39711
39712
|
// src/artifacts/rubygems.ts
|
|
39712
39713
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -40144,9 +40145,9 @@ async function runRubygemsArtifactStage(o) {
|
|
|
40144
40145
|
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);
|
|
40145
40146
|
}
|
|
40146
40147
|
const bytes = readFileSync7(dist);
|
|
40147
|
-
const
|
|
40148
|
+
const sha2564 = createHash6("sha256").update(bytes).digest("hex");
|
|
40148
40149
|
const findings = [];
|
|
40149
|
-
const extractDir = join12(stateDir, "tmp",
|
|
40150
|
+
const extractDir = join12(stateDir, "tmp", sha2564.slice(0, 8));
|
|
40150
40151
|
rmSync4(extractDir, { recursive: true, force: true });
|
|
40151
40152
|
mkdirSync4(extractDir, { recursive: true });
|
|
40152
40153
|
const dataDir = `${extractDir}-data`;
|
|
@@ -40224,7 +40225,7 @@ async function runRubygemsArtifactStage(o) {
|
|
|
40224
40225
|
}
|
|
40225
40226
|
return {
|
|
40226
40227
|
findings,
|
|
40227
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256:
|
|
40228
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256: sha2564, bytes: bytes.length }
|
|
40228
40229
|
};
|
|
40229
40230
|
}
|
|
40230
40231
|
|
|
@@ -40235,6 +40236,64 @@ function artifactTargets() {
|
|
|
40235
40236
|
function sha256File(path4) {
|
|
40236
40237
|
return createHash7("sha256").update(readFileSync8(path4)).digest("hex");
|
|
40237
40238
|
}
|
|
40239
|
+
function octalField(block, start, end) {
|
|
40240
|
+
const text = block.subarray(start, end).toString("utf8").replace(/[\0 ].*$/, "");
|
|
40241
|
+
if (text === "") return 0;
|
|
40242
|
+
if (!/^[0-7]+$/.test(text)) return null;
|
|
40243
|
+
return Number.parseInt(text, 8);
|
|
40244
|
+
}
|
|
40245
|
+
function tarEntries(buf) {
|
|
40246
|
+
const magic = buf.subarray(257, 263).toString("latin1");
|
|
40247
|
+
if (!magic.startsWith("ustar")) return null;
|
|
40248
|
+
const out = [];
|
|
40249
|
+
let off = 0;
|
|
40250
|
+
while (off + 512 <= buf.length) {
|
|
40251
|
+
const header = buf.subarray(off, off + 512);
|
|
40252
|
+
if (header.every((b) => b === 0)) return out;
|
|
40253
|
+
const size = octalField(header, 124, 136);
|
|
40254
|
+
if (size === null) return null;
|
|
40255
|
+
const mode = octalField(header, 100, 108);
|
|
40256
|
+
if (mode === null) return null;
|
|
40257
|
+
const typeFlag = header[156] ?? 48;
|
|
40258
|
+
const body = off + 512;
|
|
40259
|
+
if (body + size > buf.length) return null;
|
|
40260
|
+
const name = header.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
|
|
40261
|
+
const linkName = header.subarray(157, 257).toString("utf8").replace(/\0.*$/, "");
|
|
40262
|
+
if (typeFlag !== 120 && typeFlag !== 103 && typeFlag !== 76 && typeFlag !== 75) {
|
|
40263
|
+
out.push({ name, member: { content: buf.subarray(body, body + size), typeFlag, mode, linkName } });
|
|
40264
|
+
}
|
|
40265
|
+
off = body + Math.ceil(size / 512) * 512;
|
|
40266
|
+
}
|
|
40267
|
+
return out;
|
|
40268
|
+
}
|
|
40269
|
+
var GEM_METADATA_STAMP_RE = /^(rubygems_version|gem_version|date):/;
|
|
40270
|
+
var GEM_LEGACY_EMPTY_RE = /^(autorequire|description|email|homepage|post_install_message|signing_key):[ ]*$/;
|
|
40271
|
+
function sha256Hex(buf) {
|
|
40272
|
+
return createHash7("sha256").update(buf).digest("hex");
|
|
40273
|
+
}
|
|
40274
|
+
function gemContentDigest(bytes) {
|
|
40275
|
+
const outer = tarEntries(bytes);
|
|
40276
|
+
if (outer === null) return null;
|
|
40277
|
+
const meta3 = outer.find((e) => e.name === "metadata.gz")?.member;
|
|
40278
|
+
const data = outer.find((e) => e.name === "data.tar.gz")?.member;
|
|
40279
|
+
if (meta3 === void 0 || data === void 0) return null;
|
|
40280
|
+
let metadata;
|
|
40281
|
+
let inner;
|
|
40282
|
+
try {
|
|
40283
|
+
metadata = gunzipSync2(meta3.content).toString("utf8");
|
|
40284
|
+
inner = gunzipSync2(data.content);
|
|
40285
|
+
} catch {
|
|
40286
|
+
return null;
|
|
40287
|
+
}
|
|
40288
|
+
const innerEntries = tarEntries(inner);
|
|
40289
|
+
if (innerEntries === null) return null;
|
|
40290
|
+
const chain = createHash7("sha256");
|
|
40291
|
+
for (const { name, member: m } of innerEntries) {
|
|
40292
|
+
chain.update(name).update("\0").update(String(m.typeFlag)).update("\0").update(String(m.mode & 73)).update("\0").update(m.linkName).update("\0").update(sha256Hex(m.content)).update("\0");
|
|
40293
|
+
}
|
|
40294
|
+
const metaStripped = metadata.split("\n").filter((line) => !GEM_METADATA_STAMP_RE.test(line) && !GEM_LEGACY_EMPTY_RE.test(line)).join("\n");
|
|
40295
|
+
return createHash7("sha256").update("border-gem-content-v1\0").update(chain.digest("hex")).update("\0").update(metaStripped).digest("hex");
|
|
40296
|
+
}
|
|
40238
40297
|
var packSeq = 0;
|
|
40239
40298
|
function packNpmArtifacts(repoDir, o = {}) {
|
|
40240
40299
|
packSeq += 1;
|
|
@@ -40292,7 +40351,10 @@ function packRubygemsArtifacts(repoDir, o = {}) {
|
|
|
40292
40351
|
if (r.error !== void 0 || r.status !== 0) return null;
|
|
40293
40352
|
const files = readdirSync4(dest).filter((f) => f.endsWith(".gem")).sort();
|
|
40294
40353
|
if (files.length === 0) return null;
|
|
40295
|
-
return files.map((f) =>
|
|
40354
|
+
return files.map((f) => {
|
|
40355
|
+
const raw = readFileSync8(join13(dest, f));
|
|
40356
|
+
return { file: f, sha256: sha256Hex(raw), contentDigest: gemContentDigest(raw) };
|
|
40357
|
+
});
|
|
40296
40358
|
} finally {
|
|
40297
40359
|
rmSync5(dest, { recursive: true, force: true });
|
|
40298
40360
|
}
|
|
@@ -40305,11 +40367,30 @@ var REPACKERS = {
|
|
|
40305
40367
|
function repackChannels() {
|
|
40306
40368
|
return publishChannels().filter((c) => c.freshness === "repack").map((c) => c.id);
|
|
40307
40369
|
}
|
|
40308
|
-
function
|
|
40309
|
-
|
|
40370
|
+
function gemParity(entry, repoDir) {
|
|
40371
|
+
if (entry.contentDigest !== void 0) return entry.contentDigest;
|
|
40372
|
+
try {
|
|
40373
|
+
return gemContentDigest(readFileSync8(join13(repoDir, entry.file)));
|
|
40374
|
+
} catch {
|
|
40375
|
+
return null;
|
|
40376
|
+
}
|
|
40377
|
+
}
|
|
40378
|
+
function sameArtifacts(a, b, extensions, repoDir) {
|
|
40379
|
+
const digests = (xs) => {
|
|
40380
|
+
const out = [];
|
|
40381
|
+
for (const x of xs) {
|
|
40382
|
+
const base = x.file.slice(x.file.lastIndexOf("/") + 1);
|
|
40383
|
+
const ext = extensions.find((e) => base.endsWith(e));
|
|
40384
|
+
if (ext === void 0) continue;
|
|
40385
|
+
const parity = ext === ".gem" ? gemParity(x, repoDir) : x.sha256;
|
|
40386
|
+
if (parity === null) return null;
|
|
40387
|
+
out.push(`${base}\0${parity}`);
|
|
40388
|
+
}
|
|
40389
|
+
return out.sort();
|
|
40390
|
+
};
|
|
40310
40391
|
const ka = digests(a);
|
|
40311
40392
|
const kb = digests(b);
|
|
40312
|
-
return ka.length === kb.length && ka.every((x, i) => x === kb[i]);
|
|
40393
|
+
return ka !== null && kb !== null && ka.length === kb.length && ka.every((x, i) => x === kb[i]);
|
|
40313
40394
|
}
|
|
40314
40395
|
function verifyArtifactFreshness(record2, ctx, repoDir, o = {}) {
|
|
40315
40396
|
if (!record2.effectiveTargets.some((t) => artifactTargets().includes(t))) return true;
|
|
@@ -40331,7 +40412,8 @@ function verifyArtifactFreshness(record2, ctx, repoDir, o = {}) {
|
|
|
40331
40412
|
needed.flatMap((id) => {
|
|
40332
40413
|
const channel = publishChannels().find((c) => c.id === id);
|
|
40333
40414
|
return channel?.artifactExtensions ?? [];
|
|
40334
|
-
})
|
|
40415
|
+
}),
|
|
40416
|
+
repoDir
|
|
40335
40417
|
);
|
|
40336
40418
|
}
|
|
40337
40419
|
|
|
@@ -40809,17 +40891,17 @@ function requirePassedRecord(kind, i) {
|
|
|
40809
40891
|
function rehashRecordedArtifacts(kind, extensions, record2, repoDir) {
|
|
40810
40892
|
const conventions = extensions;
|
|
40811
40893
|
const entries = (record2.artifacts ?? []).filter((a) => conventions.some((ext) => a.file.endsWith(ext)));
|
|
40812
|
-
const
|
|
40894
|
+
const fail4 = (detail) => ({ ok: false, message: `artifact changed since check \u2014 ${detail}` });
|
|
40813
40895
|
if (entries.length === 0) {
|
|
40814
|
-
return
|
|
40896
|
+
return fail4(`the PASSED check recorded no ${kind} artifacts (${conventions.join("/")}) to re-verify`);
|
|
40815
40897
|
}
|
|
40816
40898
|
const files = [];
|
|
40817
40899
|
for (const a of entries) {
|
|
40818
40900
|
const rel = join17(DIST_DIR, basename4(a.file));
|
|
40819
40901
|
const abs = join17(repoDir, rel);
|
|
40820
|
-
if (!existsSync11(abs)) return
|
|
40902
|
+
if (!existsSync11(abs)) return fail4(`${rel} is missing from the working tree`);
|
|
40821
40903
|
const digest2 = createHash10("sha256").update(readFileSync10(abs)).digest("hex");
|
|
40822
|
-
if (digest2 !== a.sha256) return
|
|
40904
|
+
if (digest2 !== a.sha256) return fail4(`${rel} no longer hashes to the digest recorded by the check`);
|
|
40823
40905
|
files.push(rel);
|
|
40824
40906
|
}
|
|
40825
40907
|
return { ok: true, files: files.sort() };
|
|
@@ -41278,7 +41360,7 @@ function npmExposureCoords(cfg, read) {
|
|
|
41278
41360
|
return { name, version: version2 };
|
|
41279
41361
|
}
|
|
41280
41362
|
function classifyNpmVersionView(o) {
|
|
41281
|
-
const
|
|
41363
|
+
const fail4 = (why) => {
|
|
41282
41364
|
throw new EngineRunError(
|
|
41283
41365
|
`npm registry probe inconclusive (${why}); refusing to assume the version is absent \u2014 registry unreachable or returned unexpected output, push blocked until the probe succeeds`,
|
|
41284
41366
|
o.status
|
|
@@ -41286,19 +41368,19 @@ function classifyNpmVersionView(o) {
|
|
|
41286
41368
|
};
|
|
41287
41369
|
if (o.status === 0) {
|
|
41288
41370
|
const text = o.stdout.trim();
|
|
41289
|
-
if (text.length === 0) return
|
|
41371
|
+
if (text.length === 0) return fail4("exit 0 with empty stdout");
|
|
41290
41372
|
let body;
|
|
41291
41373
|
try {
|
|
41292
41374
|
body = JSON.parse(text);
|
|
41293
41375
|
} catch {
|
|
41294
|
-
return
|
|
41376
|
+
return fail4("exit 0 with malformed JSON body");
|
|
41295
41377
|
}
|
|
41296
|
-
if (typeof body !== "object" || body === null) return
|
|
41378
|
+
if (typeof body !== "object" || body === null) return fail4("exit 0 with non-object JSON body");
|
|
41297
41379
|
return "present";
|
|
41298
41380
|
}
|
|
41299
41381
|
if (/code E404\b/.test(o.stderr) || /\b404 Not Found\b/.test(o.stderr)) return "absent";
|
|
41300
41382
|
const m = /code (E[A-Z0-9_]+|ERR_[A-Z0-9_]+)/.exec(o.stderr);
|
|
41301
|
-
return
|
|
41383
|
+
return fail4(m === null ? `exit ${String(o.status)} without a 404 marker` : `npm error ${m[1]}`);
|
|
41302
41384
|
}
|
|
41303
41385
|
function npmNameClaimed(o) {
|
|
41304
41386
|
if (o.status === 0) return true;
|
|
@@ -41705,9 +41787,9 @@ async function runCargoArtifactStage(o) {
|
|
|
41705
41787
|
const dist = join18(distDir, crateName);
|
|
41706
41788
|
renameSync(packed, dist);
|
|
41707
41789
|
const bytes = readFileSync11(dist);
|
|
41708
|
-
const
|
|
41790
|
+
const sha2564 = createHash11("sha256").update(bytes).digest("hex");
|
|
41709
41791
|
const findings = [];
|
|
41710
|
-
const extractDir = join18(stateDir, "tmp",
|
|
41792
|
+
const extractDir = join18(stateDir, "tmp", sha2564.slice(0, 8));
|
|
41711
41793
|
rmSync8(extractDir, { recursive: true, force: true });
|
|
41712
41794
|
mkdirSync8(extractDir, { recursive: true });
|
|
41713
41795
|
try {
|
|
@@ -41760,7 +41842,7 @@ async function runCargoArtifactStage(o) {
|
|
|
41760
41842
|
}
|
|
41761
41843
|
return {
|
|
41762
41844
|
findings,
|
|
41763
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${crateName}`, sha256:
|
|
41845
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${crateName}`, sha256: sha2564, bytes: bytes.length }
|
|
41764
41846
|
};
|
|
41765
41847
|
} finally {
|
|
41766
41848
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
@@ -43618,6 +43700,193 @@ async function runLlmRequest(ctx) {
|
|
|
43618
43700
|
return runLlmRequestCore(ctx);
|
|
43619
43701
|
}
|
|
43620
43702
|
|
|
43703
|
+
// src/commands/opencode.ts
|
|
43704
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
43705
|
+
import { existsSync as existsSync17, lstatSync, mkdirSync as mkdirSync11, readFileSync as readFileSync16, rmSync as rmSync12, writeFileSync as writeFileSync7 } from "node:fs";
|
|
43706
|
+
import os3 from "node:os";
|
|
43707
|
+
import { dirname as dirname12, join as join26 } from "node:path";
|
|
43708
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
43709
|
+
var PLUGIN_MARKER = "// border-opencode-plugin v";
|
|
43710
|
+
var COMMAND_MARKER = "<!-- border-opencode-command -->";
|
|
43711
|
+
function opencodeConfigRoot(env2) {
|
|
43712
|
+
const xdg = env2["XDG_CONFIG_HOME"] ?? "";
|
|
43713
|
+
if (xdg.length > 0) return xdg;
|
|
43714
|
+
const home = env2["HOME"] ?? "";
|
|
43715
|
+
return join26(home.length > 0 ? home : os3.homedir(), ".config");
|
|
43716
|
+
}
|
|
43717
|
+
function resolvePluginAsset(moduleUrl, fileName) {
|
|
43718
|
+
const here = dirname12(fileURLToPath5(moduleUrl));
|
|
43719
|
+
const candidates = [
|
|
43720
|
+
join26(here, "..", "plugin", fileName),
|
|
43721
|
+
// dist mode (bundled): <root>/plugin/<f>
|
|
43722
|
+
join26(here, "..", "..", "plugin", fileName)
|
|
43723
|
+
// src mode: <root>/plugin/<f>
|
|
43724
|
+
];
|
|
43725
|
+
return candidates.find((p) => existsSync17(p)) ?? candidates[1];
|
|
43726
|
+
}
|
|
43727
|
+
function targetsFor(env2) {
|
|
43728
|
+
const root = join26(opencodeConfigRoot(env2), "opencode");
|
|
43729
|
+
return [
|
|
43730
|
+
{ fileName: "border.ts", destination: join26(root, "plugins", "border.ts"), marker: PLUGIN_MARKER },
|
|
43731
|
+
{ fileName: "border-command.md", destination: join26(root, "commands", "border.md"), marker: COMMAND_MARKER }
|
|
43732
|
+
];
|
|
43733
|
+
}
|
|
43734
|
+
function sha2563(bytes) {
|
|
43735
|
+
return createHash15("sha256").update(bytes).digest("hex");
|
|
43736
|
+
}
|
|
43737
|
+
function firstLine(bytes) {
|
|
43738
|
+
const text = bytes.toString("utf8");
|
|
43739
|
+
const newline = text.indexOf("\n");
|
|
43740
|
+
return newline < 0 ? text : text.slice(0, newline);
|
|
43741
|
+
}
|
|
43742
|
+
function carriesMarker(bytes, marker) {
|
|
43743
|
+
return firstLine(bytes).startsWith(marker);
|
|
43744
|
+
}
|
|
43745
|
+
function fail(ctx, message) {
|
|
43746
|
+
ctx.stderr(`border: opencode: ${message}`);
|
|
43747
|
+
return EXIT_ERROR;
|
|
43748
|
+
}
|
|
43749
|
+
function readPackagedAsset(ctx, fileName) {
|
|
43750
|
+
const source = resolvePluginAsset(import.meta.url, fileName);
|
|
43751
|
+
try {
|
|
43752
|
+
return readFileSync16(source);
|
|
43753
|
+
} catch {
|
|
43754
|
+
ctx.stderr(`border: opencode: packaged asset is missing (${source})`);
|
|
43755
|
+
ctx.stderr("border: opencode: the border install looks broken \u2014 `npm i -g border-customs` or a fresh checkout should carry plugin/.");
|
|
43756
|
+
return null;
|
|
43757
|
+
}
|
|
43758
|
+
}
|
|
43759
|
+
function readTarget(destination) {
|
|
43760
|
+
try {
|
|
43761
|
+
return readFileSync16(destination);
|
|
43762
|
+
} catch {
|
|
43763
|
+
return null;
|
|
43764
|
+
}
|
|
43765
|
+
}
|
|
43766
|
+
function requirePlainDestination(ctx, destination) {
|
|
43767
|
+
let shape;
|
|
43768
|
+
try {
|
|
43769
|
+
shape = lstatSync(destination);
|
|
43770
|
+
} catch {
|
|
43771
|
+
return null;
|
|
43772
|
+
}
|
|
43773
|
+
if (!shape.isFile() || shape.isSymbolicLink()) {
|
|
43774
|
+
return fail(
|
|
43775
|
+
ctx,
|
|
43776
|
+
`refusing to touch ${destination} \u2014 the path exists but is not a plain regular file (directories and symlinks are never followed, entered, or destroyed)`
|
|
43777
|
+
);
|
|
43778
|
+
}
|
|
43779
|
+
return null;
|
|
43780
|
+
}
|
|
43781
|
+
function classify(target, current, packaged) {
|
|
43782
|
+
if (current === null) return "absent";
|
|
43783
|
+
if (!carriesMarker(current, target.marker)) return "foreign";
|
|
43784
|
+
return current.equals(packaged) ? "up-to-date" : "outdated";
|
|
43785
|
+
}
|
|
43786
|
+
function requireNoForeign(ctx, targets) {
|
|
43787
|
+
for (const target of targets) {
|
|
43788
|
+
const shape = requirePlainDestination(ctx, target.destination);
|
|
43789
|
+
if (shape !== null) return shape;
|
|
43790
|
+
const current = readTarget(target.destination);
|
|
43791
|
+
if (current !== null && !carriesMarker(current, target.marker)) {
|
|
43792
|
+
return fail(
|
|
43793
|
+
ctx,
|
|
43794
|
+
`refusing to touch ${target.destination} \u2014 the file exists but is not ours (its first line lacks the marker '${target.marker}'); move it aside manually, nothing was written anywhere`
|
|
43795
|
+
);
|
|
43796
|
+
}
|
|
43797
|
+
}
|
|
43798
|
+
return null;
|
|
43799
|
+
}
|
|
43800
|
+
function prepareTargets(ctx, targets) {
|
|
43801
|
+
const prepared = [];
|
|
43802
|
+
for (const target of targets) {
|
|
43803
|
+
const packaged = readPackagedAsset(ctx, target.fileName);
|
|
43804
|
+
if (packaged === null) return null;
|
|
43805
|
+
prepared.push({ target, packaged });
|
|
43806
|
+
}
|
|
43807
|
+
return prepared;
|
|
43808
|
+
}
|
|
43809
|
+
function opencodeInstall(ctx) {
|
|
43810
|
+
const targets = targetsFor(ctx.env);
|
|
43811
|
+
const prepared = prepareTargets(ctx, targets);
|
|
43812
|
+
if (prepared === null) return EXIT_ERROR;
|
|
43813
|
+
const blocked = requireNoForeign(ctx, targets);
|
|
43814
|
+
if (blocked !== null) return blocked;
|
|
43815
|
+
for (const { target, packaged } of prepared) {
|
|
43816
|
+
const current = readTarget(target.destination);
|
|
43817
|
+
const state = classify(target, current, packaged);
|
|
43818
|
+
if (state === "foreign") {
|
|
43819
|
+
return fail(
|
|
43820
|
+
ctx,
|
|
43821
|
+
`refusing to touch ${target.destination} \u2014 the file exists but is not ours (its first line lacks the marker '${target.marker}'); move it aside manually, nothing was written anywhere`
|
|
43822
|
+
);
|
|
43823
|
+
}
|
|
43824
|
+
if (state === "up-to-date") {
|
|
43825
|
+
ctx.stdout(`${target.destination}: up to date`);
|
|
43826
|
+
continue;
|
|
43827
|
+
}
|
|
43828
|
+
mkdirSync11(dirname12(target.destination), { recursive: true });
|
|
43829
|
+
writeFileSync7(target.destination, packaged);
|
|
43830
|
+
ctx.stdout(`${target.destination}: ${state === "outdated" ? "updated" : "installed"}`);
|
|
43831
|
+
}
|
|
43832
|
+
ctx.stdout("note: restart opencode to load the plugin \u2014 tools and commands are scanned at startup.");
|
|
43833
|
+
return EXIT_PASS;
|
|
43834
|
+
}
|
|
43835
|
+
function opencodeStatus(ctx) {
|
|
43836
|
+
const targets = targetsFor(ctx.env);
|
|
43837
|
+
const prepared = prepareTargets(ctx, targets);
|
|
43838
|
+
if (prepared === null) return EXIT_ERROR;
|
|
43839
|
+
for (const { target, packaged } of prepared) {
|
|
43840
|
+
const current = readTarget(target.destination);
|
|
43841
|
+
const state = classify(target, current, packaged);
|
|
43842
|
+
const installedSha = current === null ? "-" : sha2563(current);
|
|
43843
|
+
ctx.stdout(`${target.destination}: ${state} installed=${installedSha} packaged=${sha2563(packaged)}`);
|
|
43844
|
+
}
|
|
43845
|
+
return EXIT_PASS;
|
|
43846
|
+
}
|
|
43847
|
+
function opencodeUninstall(ctx) {
|
|
43848
|
+
const targets = targetsFor(ctx.env);
|
|
43849
|
+
const blocked = requireNoForeign(ctx, targets);
|
|
43850
|
+
if (blocked !== null) return blocked;
|
|
43851
|
+
for (const target of targets) {
|
|
43852
|
+
const current = readTarget(target.destination);
|
|
43853
|
+
if (current === null) {
|
|
43854
|
+
ctx.stdout(`${target.destination}: absent (nothing to remove)`);
|
|
43855
|
+
continue;
|
|
43856
|
+
}
|
|
43857
|
+
if (!carriesMarker(current, target.marker)) {
|
|
43858
|
+
return fail(
|
|
43859
|
+
ctx,
|
|
43860
|
+
`refusing to remove ${target.destination} \u2014 the file is not ours (its first line lacks the marker '${target.marker}'); nothing was removed anywhere`
|
|
43861
|
+
);
|
|
43862
|
+
}
|
|
43863
|
+
rmSync12(target.destination);
|
|
43864
|
+
ctx.stdout(`${target.destination}: removed`);
|
|
43865
|
+
}
|
|
43866
|
+
ctx.stdout("note: restart opencode for the tool and command to disappear.");
|
|
43867
|
+
return EXIT_PASS;
|
|
43868
|
+
}
|
|
43869
|
+
function runOpencode(ctx) {
|
|
43870
|
+
const [sub, ...rest] = ctx.positionals;
|
|
43871
|
+
if (rest.length > 0) {
|
|
43872
|
+
throw new UnknownArgError(
|
|
43873
|
+
`opencode ${sub ?? "<none>"}: unexpected argument '${rest[0] ?? ""}' \u2014 usage: border opencode install | status | uninstall`
|
|
43874
|
+
);
|
|
43875
|
+
}
|
|
43876
|
+
switch (sub) {
|
|
43877
|
+
case "install":
|
|
43878
|
+
return opencodeInstall(ctx);
|
|
43879
|
+
case "status":
|
|
43880
|
+
return opencodeStatus(ctx);
|
|
43881
|
+
case "uninstall":
|
|
43882
|
+
return opencodeUninstall(ctx);
|
|
43883
|
+
default:
|
|
43884
|
+
throw new UnknownArgError(
|
|
43885
|
+
`opencode: unknown subcommand '${sub ?? "<none>"}' \u2014 usage: border opencode install | status | uninstall`
|
|
43886
|
+
);
|
|
43887
|
+
}
|
|
43888
|
+
}
|
|
43889
|
+
|
|
43621
43890
|
// src/push/git.ts
|
|
43622
43891
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
43623
43892
|
var DIVERGED_MESSAGE = "history diverged; border never force-pushes; resolve manually";
|
|
@@ -43883,17 +44152,17 @@ async function executeYesPush(ctx, loaded) {
|
|
|
43883
44152
|
}
|
|
43884
44153
|
|
|
43885
44154
|
// src/roundtrip/orchestrate.ts
|
|
43886
|
-
import { mkdtempSync as mkdtempSync5, rmSync as
|
|
44155
|
+
import { mkdtempSync as mkdtempSync5, rmSync as rmSync13, writeFileSync as writeFileSync8 } from "node:fs";
|
|
43887
44156
|
import { randomBytes } from "node:crypto";
|
|
43888
44157
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
43889
|
-
import { join as
|
|
44158
|
+
import { join as join27 } from "node:path";
|
|
43890
44159
|
|
|
43891
44160
|
// src/scan/fetch.ts
|
|
43892
|
-
import { createHash as
|
|
44161
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
43893
44162
|
import { basename as basename5 } from "node:path";
|
|
43894
44163
|
var SCAN_MAX_ARTIFACT_BYTES = 200 * 1024 * 1024;
|
|
43895
44164
|
var SCAN_REQUEST_TIMEOUT_MS = 6e4;
|
|
43896
|
-
var SCAN_USER_AGENT = "border-customs/0.
|
|
44165
|
+
var SCAN_USER_AGENT = "border-customs/0.5.0 (+https://github.com/sumteclab/border; border scan)";
|
|
43897
44166
|
var CRATES_DOWNLOAD_HOST = "https://static.crates.io";
|
|
43898
44167
|
var defaultScanFetcher = async (url2, init) => {
|
|
43899
44168
|
if (!/^https?:\/\//.test(url2)) throw new EngineRunError(`border scan refuses non-http(s) URL: ${url2}`, null);
|
|
@@ -43918,19 +44187,19 @@ var defaultScanFetcher = async (url2, init) => {
|
|
|
43918
44187
|
}
|
|
43919
44188
|
};
|
|
43920
44189
|
};
|
|
43921
|
-
var
|
|
44190
|
+
var fail2 = (why) => {
|
|
43922
44191
|
throw new EngineRunError(`border scan could not fetch the artifact (${why}) \u2014 refusing to scan a partial or absent download (fail-closed)`, null);
|
|
43923
44192
|
};
|
|
43924
44193
|
async function readCapped(res, url2, cap = SCAN_MAX_ARTIFACT_BYTES) {
|
|
43925
|
-
if (res.status !== 200)
|
|
44194
|
+
if (res.status !== 200) fail2(`HTTP ${String(res.status)} for ${url2}`);
|
|
43926
44195
|
if (res.contentLength !== null && res.contentLength > cap) {
|
|
43927
|
-
|
|
44196
|
+
fail2(`${url2} announces ${String(res.contentLength)} bytes, over the 200 MiB scan size cap`);
|
|
43928
44197
|
}
|
|
43929
44198
|
const parts = [];
|
|
43930
44199
|
let total = 0;
|
|
43931
44200
|
for await (const chunk of res.chunks()) {
|
|
43932
44201
|
total += chunk.byteLength;
|
|
43933
|
-
if (total > cap)
|
|
44202
|
+
if (total > cap) fail2(`${url2} streams past the 200 MiB scan size cap after ${String(total)} bytes`);
|
|
43934
44203
|
parts.push(Buffer.from(chunk));
|
|
43935
44204
|
}
|
|
43936
44205
|
return Buffer.concat(parts);
|
|
@@ -43944,7 +44213,7 @@ async function fetchJson(fetcher, url2, timeoutMs) {
|
|
|
43944
44213
|
try {
|
|
43945
44214
|
return JSON.parse(body.toString("utf8"));
|
|
43946
44215
|
} catch {
|
|
43947
|
-
|
|
44216
|
+
fail2(`metadata response from ${url2} is not valid JSON`);
|
|
43948
44217
|
}
|
|
43949
44218
|
}
|
|
43950
44219
|
var isRecord2 = (v) => typeof v === "object" && v !== null;
|
|
@@ -43980,11 +44249,11 @@ async function fetchArtifact(spec, o) {
|
|
|
43980
44249
|
const meta3 = await fetchJson(o.fetcher, npmMetadataUrl(spec.name, spec.version), timeoutMs);
|
|
43981
44250
|
const dist = isRecord2(meta3) && isRecord2(meta3["dist"]) ? meta3["dist"] : {};
|
|
43982
44251
|
const tarball = dist["tarball"];
|
|
43983
|
-
if (typeof tarball !== "string" || tarball.length === 0)
|
|
43984
|
-
if (!/^https?:\/\//.test(tarball))
|
|
44252
|
+
if (typeof tarball !== "string" || tarball.length === 0) fail2(`npm metadata for ${label} has no dist.tarball`);
|
|
44253
|
+
if (!/^https?:\/\//.test(tarball)) fail2(`npm dist.tarball for ${label} is not an http(s) URL`);
|
|
43985
44254
|
const bytes = await fetchBytes(o.fetcher, tarball, timeoutMs);
|
|
43986
44255
|
const filename = safeArtifactName(tarball, `${spec.name.replace("@", "").replace("/", "-")}-${spec.version}.tgz`);
|
|
43987
|
-
if (!filename.endsWith(".tgz") && !filename.endsWith(".tar.gz"))
|
|
44256
|
+
if (!filename.endsWith(".tgz") && !filename.endsWith(".tar.gz")) fail2(`npm tarball ${filename} is not a .tgz/.tar.gz`);
|
|
43988
44257
|
return { bytes, filename };
|
|
43989
44258
|
}
|
|
43990
44259
|
case "pypi": {
|
|
@@ -43994,11 +44263,11 @@ async function fetchArtifact(spec, o) {
|
|
|
43994
44263
|
const sdists = files.filter((f) => f["packagetype"] === "sdist");
|
|
43995
44264
|
const sdist = sdists.find((f) => typeof f["filename"] === "string" && f["filename"].endsWith(".tar.gz"));
|
|
43996
44265
|
if (sdist === void 0) {
|
|
43997
|
-
|
|
44266
|
+
fail2(`no .tar.gz sdist published for ${label} \u2014 wheels are pre-built artifacts and out of scan scope (setup code lives in the sdist)`);
|
|
43998
44267
|
}
|
|
43999
44268
|
const url2 = sdist["url"];
|
|
44000
44269
|
const name = sdist["filename"];
|
|
44001
|
-
if (typeof url2 !== "string" || typeof name !== "string")
|
|
44270
|
+
if (typeof url2 !== "string" || typeof name !== "string") fail2(`PyPI sdist entry for ${label} is malformed`);
|
|
44002
44271
|
const bytes = await fetchBytes(o.fetcher, url2, timeoutMs);
|
|
44003
44272
|
return { bytes, filename: safeArtifactName(url2, name) };
|
|
44004
44273
|
}
|
|
@@ -44018,8 +44287,8 @@ async function fetchArtifact(spec, o) {
|
|
|
44018
44287
|
}
|
|
44019
44288
|
}
|
|
44020
44289
|
}
|
|
44021
|
-
function
|
|
44022
|
-
return
|
|
44290
|
+
function sha256Hex2(data) {
|
|
44291
|
+
return createHash16("sha256").update(data).digest("hex");
|
|
44023
44292
|
}
|
|
44024
44293
|
|
|
44025
44294
|
// src/scan/spec.ts
|
|
@@ -44062,8 +44331,8 @@ function parseScanSpec(raw) {
|
|
|
44062
44331
|
}
|
|
44063
44332
|
|
|
44064
44333
|
// src/roundtrip/localArtifact.ts
|
|
44065
|
-
import { createReadStream, existsSync as
|
|
44066
|
-
import { createHash as
|
|
44334
|
+
import { createReadStream, existsSync as existsSync18, statSync as statSync2 } from "node:fs";
|
|
44335
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
44067
44336
|
import { basename as basename6, resolve as resolve15 } from "node:path";
|
|
44068
44337
|
var EXT_TABLE = /* @__PURE__ */ new Map([
|
|
44069
44338
|
[".whl", "pypi"],
|
|
@@ -44111,7 +44380,7 @@ var GRAMMAR_HINT = {
|
|
|
44111
44380
|
};
|
|
44112
44381
|
function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
|
|
44113
44382
|
const abs = resolve15(cwd, raw);
|
|
44114
|
-
if (
|
|
44383
|
+
if (existsSync18(abs)) {
|
|
44115
44384
|
const st = statSync2(abs);
|
|
44116
44385
|
if (st.isDirectory()) {
|
|
44117
44386
|
bad3(`'${raw}' resolves to a directory (${abs}) \u2014 local mode needs a single artifact FILE, not a directory`);
|
|
@@ -44146,7 +44415,7 @@ function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
|
|
|
44146
44415
|
return null;
|
|
44147
44416
|
}
|
|
44148
44417
|
async function readLocalArtifact(absPath, cap = SCAN_MAX_ARTIFACT_BYTES) {
|
|
44149
|
-
const hash2 =
|
|
44418
|
+
const hash2 = createHash17("sha256");
|
|
44150
44419
|
const parts = [];
|
|
44151
44420
|
let total = 0;
|
|
44152
44421
|
for await (const chunk of createReadStream(absPath)) {
|
|
@@ -44372,7 +44641,7 @@ var CLAIM_SCANNER_PY = [
|
|
|
44372
44641
|
].join("\n");
|
|
44373
44642
|
|
|
44374
44643
|
// src/roundtrip/manifest.ts
|
|
44375
|
-
import { createHash as
|
|
44644
|
+
import { createHash as createHash18 } from "node:crypto";
|
|
44376
44645
|
var RT_ENGINE = "roundtrip";
|
|
44377
44646
|
var RT_RESIDUE_RULE = "roundtrip-residue-file";
|
|
44378
44647
|
var RT_MODIFY_RULE = "roundtrip-modified-file";
|
|
@@ -44423,7 +44692,7 @@ var PERSISTENCE_PATTERNS = [
|
|
|
44423
44692
|
function isPersistenceSurface(path4) {
|
|
44424
44693
|
return PERSISTENCE_PATTERNS.some((re) => re.test(path4));
|
|
44425
44694
|
}
|
|
44426
|
-
var digestOf = (what) =>
|
|
44695
|
+
var digestOf = (what) => createHash18("sha256").update(what).digest("hex");
|
|
44427
44696
|
function classifyResidue(label, diff, attribution = null) {
|
|
44428
44697
|
const findings = [];
|
|
44429
44698
|
for (const e of diff.added) {
|
|
@@ -44470,7 +44739,7 @@ function classifyResidue(label, diff, attribution = null) {
|
|
|
44470
44739
|
|
|
44471
44740
|
// src/roundtrip/orchestrate.ts
|
|
44472
44741
|
function containerNameFor(label, rand = randomBytes(3).toString("hex")) {
|
|
44473
|
-
return `rt-${
|
|
44742
|
+
return `rt-${sha256Hex2(label).slice(0, 8)}-${rand}`;
|
|
44474
44743
|
}
|
|
44475
44744
|
var shArgv = (name) => {
|
|
44476
44745
|
if (!/^[A-Za-z0-9@._+/-]+$/.test(name)) {
|
|
@@ -44591,7 +44860,7 @@ function runPipeline2(exec, profile, spec, label, hostPath, container, closure,
|
|
|
44591
44860
|
return {
|
|
44592
44861
|
findings: classifyResidue(label, diffManifests(m1, m3), attribution),
|
|
44593
44862
|
installDelta,
|
|
44594
|
-
baselineDigest:
|
|
44863
|
+
baselineDigest: sha256Hex2([...m1.keys()].join("\n"))
|
|
44595
44864
|
};
|
|
44596
44865
|
} finally {
|
|
44597
44866
|
try {
|
|
@@ -44625,25 +44894,25 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
44625
44894
|
ctx.stderr("roundtrip: docker unavailable \u2014 cannot verify");
|
|
44626
44895
|
return EXIT_ERROR;
|
|
44627
44896
|
}
|
|
44628
|
-
const baseDir = mkdtempSync5(
|
|
44897
|
+
const baseDir = mkdtempSync5(join27(tmpdir3(), "border-roundtrip-"));
|
|
44629
44898
|
try {
|
|
44630
44899
|
const localRead = local !== null ? await readLocalArtifact(local.absPath) : null;
|
|
44631
44900
|
const bytes = localRead?.bytes ?? (await fetchArtifact(spec, { fetcher: deps.fetcher ?? defaultScanFetcher })).bytes;
|
|
44632
|
-
const artifactSha256 = localRead !== null ? localRead.sha256 :
|
|
44633
|
-
const hostPath =
|
|
44634
|
-
|
|
44901
|
+
const artifactSha256 = localRead !== null ? localRead.sha256 : sha256Hex2(bytes);
|
|
44902
|
+
const hostPath = join27(baseDir, "artifact");
|
|
44903
|
+
writeFileSync8(hostPath, bytes);
|
|
44635
44904
|
const container = containerNameFor(label);
|
|
44636
44905
|
const closure = spec.ecosystem === "pypi" ? resolvePipClosure(exec, profile, hostPath, label, spec) : null;
|
|
44637
44906
|
const { findings, installDelta, baselineDigest } = runPipeline2(exec, profile, spec, label, hostPath, container, closure, (msg) => ctx.stderr(msg));
|
|
44638
44907
|
const verdict = computeVerdict(findings);
|
|
44639
44908
|
const report = {
|
|
44640
44909
|
schemaVersion: 1,
|
|
44641
|
-
key:
|
|
44910
|
+
key: sha256Hex2(`roundtrip:${label}:${baselineDigest}:${verdict}`),
|
|
44642
44911
|
head: baselineDigest.slice(0, 40),
|
|
44643
44912
|
dirty: false,
|
|
44644
44913
|
exposureSet: [label],
|
|
44645
44914
|
refSet: [],
|
|
44646
|
-
rulesHash:
|
|
44915
|
+
rulesHash: sha256Hex2(`${profile.image}|${profile.install(profile.artifactPath, spec).join(" ")}|${profile.uninstall(spec).join(" ")}`),
|
|
44647
44916
|
verdict,
|
|
44648
44917
|
counts: countFindings(findings),
|
|
44649
44918
|
findings,
|
|
@@ -44675,7 +44944,7 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
44675
44944
|
}
|
|
44676
44945
|
return exitCodeFromVerdict(verdict);
|
|
44677
44946
|
} finally {
|
|
44678
|
-
|
|
44947
|
+
rmSync13(baseDir, { recursive: true, force: true });
|
|
44679
44948
|
}
|
|
44680
44949
|
}
|
|
44681
44950
|
function parsesRegistrySpec(raw) {
|
|
@@ -44733,7 +45002,7 @@ var runRoundtrip = (ctx) => runRoundtripCore(ctx);
|
|
|
44733
45002
|
// src/scan/scan.ts
|
|
44734
45003
|
import { mkdtempSync as mkdtempSync6 } from "node:fs";
|
|
44735
45004
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
44736
|
-
import { join as
|
|
45005
|
+
import { join as join29 } from "node:path";
|
|
44737
45006
|
|
|
44738
45007
|
// src/scan/config.ts
|
|
44739
45008
|
function buildScanConfigYaml(eco) {
|
|
@@ -44760,10 +45029,10 @@ function synthesizeScanConfig(eco) {
|
|
|
44760
45029
|
}
|
|
44761
45030
|
|
|
44762
45031
|
// src/scan/materialize.ts
|
|
44763
|
-
import { existsSync as
|
|
44764
|
-
import { join as
|
|
45032
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, readdirSync as readdirSync10, readFileSync as readFileSync17, rmSync as rmSync14, writeFileSync as writeFileSync9 } from "node:fs";
|
|
45033
|
+
import { join as join28 } from "node:path";
|
|
44765
45034
|
var CRATES_ENVELOPE_FILES = ["Cargo.toml.orig", ".cargo-ok", ".cargo_vcs_info.json"];
|
|
44766
|
-
var
|
|
45035
|
+
var fail3 = (why) => {
|
|
44767
45036
|
throw new Error(`border scan cannot materialize the artifact (${why}) \u2014 refusing to stage an unusable tree (fail-closed)`);
|
|
44768
45037
|
};
|
|
44769
45038
|
var oneLine = (err) => (err instanceof Error ? err.message : String(err)).replace(/\s+/g, " ").trim();
|
|
@@ -44771,20 +45040,20 @@ function extractFlat(archive, destDir) {
|
|
|
44771
45040
|
try {
|
|
44772
45041
|
extractArchive(archive, destDir);
|
|
44773
45042
|
} catch (err) {
|
|
44774
|
-
|
|
45043
|
+
fail3(`extraction of ${archive} failed: ${oneLine(err)}`);
|
|
44775
45044
|
}
|
|
44776
45045
|
}
|
|
44777
45046
|
function unwrapSingleDir(dir) {
|
|
44778
45047
|
const entries = readdirSync10(dir, { withFileTypes: true });
|
|
44779
45048
|
const dirs = entries.filter((e) => e.isDirectory());
|
|
44780
45049
|
if (entries.length === 1 && dirs.length === 1 && dirs[0] !== void 0) {
|
|
44781
|
-
return
|
|
45050
|
+
return join28(dir, dirs[0].name);
|
|
44782
45051
|
}
|
|
44783
45052
|
return dir;
|
|
44784
45053
|
}
|
|
44785
45054
|
function requireManifest(repoDir, ecosystem, candidates) {
|
|
44786
|
-
if (!candidates.some((rel) =>
|
|
44787
|
-
|
|
45055
|
+
if (!candidates.some((rel) => existsSync19(join28(repoDir, rel)))) {
|
|
45056
|
+
fail3(`${ecosystem} package tree has none of [${candidates.join(", ")}] at its root`);
|
|
44788
45057
|
}
|
|
44789
45058
|
}
|
|
44790
45059
|
function materializePackage(o) {
|
|
@@ -44793,19 +45062,19 @@ function materializePackage(o) {
|
|
|
44793
45062
|
`);
|
|
44794
45063
|
});
|
|
44795
45064
|
const safe = safeArtifactName(o.filename, `${o.ecosystem}-artifact`);
|
|
44796
|
-
const archive =
|
|
44797
|
-
|
|
44798
|
-
const extractDir =
|
|
44799
|
-
|
|
45065
|
+
const archive = join28(o.baseDir, safe);
|
|
45066
|
+
writeFileSync9(archive, o.bytes);
|
|
45067
|
+
const extractDir = join28(o.baseDir, "extract");
|
|
45068
|
+
mkdirSync12(extractDir, { recursive: true });
|
|
44800
45069
|
extractFlat(archive, extractDir);
|
|
44801
45070
|
let repoDir = unwrapSingleDir(extractDir);
|
|
44802
45071
|
if (o.ecosystem === "rubygems") {
|
|
44803
45072
|
const outer = repoDir;
|
|
44804
45073
|
const dataCandidates = findNativeMissArchives(outer).filter((f) => f.endsWith("data.tar.gz"));
|
|
44805
|
-
const inner = dataCandidates[0] ??
|
|
44806
|
-
if (!
|
|
44807
|
-
const gemTree =
|
|
44808
|
-
|
|
45074
|
+
const inner = dataCandidates[0] ?? join28(outer, "data.tar.gz");
|
|
45075
|
+
if (!existsSync19(inner)) fail3(".gem archive contains no data.tar.gz");
|
|
45076
|
+
const gemTree = join28(o.baseDir, "gem-tree");
|
|
45077
|
+
mkdirSync12(gemTree, { recursive: true });
|
|
44809
45078
|
extractFlat(inner, gemTree);
|
|
44810
45079
|
repoDir = unwrapSingleDir(gemTree);
|
|
44811
45080
|
} else if (o.ecosystem === "npm") {
|
|
@@ -44815,11 +45084,11 @@ function materializePackage(o) {
|
|
|
44815
45084
|
} else {
|
|
44816
45085
|
requireManifest(repoDir, o.ecosystem, ["Cargo.toml"]);
|
|
44817
45086
|
for (const file2 of CRATES_ENVELOPE_FILES) {
|
|
44818
|
-
const path4 =
|
|
44819
|
-
if (!
|
|
44820
|
-
const original =
|
|
44821
|
-
|
|
44822
|
-
note(`border scan: crates envelope normalized: ${file2} sha256=${
|
|
45087
|
+
const path4 = join28(repoDir, file2);
|
|
45088
|
+
if (!existsSync19(path4)) continue;
|
|
45089
|
+
const original = readFileSync17(path4);
|
|
45090
|
+
rmSync14(path4, { force: true });
|
|
45091
|
+
note(`border scan: crates envelope normalized: ${file2} sha256=${sha256Hex2(original)}`);
|
|
44823
45092
|
}
|
|
44824
45093
|
}
|
|
44825
45094
|
const env2 = o.env;
|
|
@@ -44845,7 +45114,7 @@ async function runScanCore(ctx, deps = {}) {
|
|
|
44845
45114
|
}
|
|
44846
45115
|
const cfg = synthesizeScanConfig(spec.ecosystem);
|
|
44847
45116
|
const fetcher = deps.fetcher ?? defaultScanFetcher;
|
|
44848
|
-
const baseDir = mkdtempSync6(
|
|
45117
|
+
const baseDir = mkdtempSync6(join29(tmpdir4(), "border-scan-"));
|
|
44849
45118
|
try {
|
|
44850
45119
|
const { bytes, filename } = await fetchArtifact(spec, { fetcher });
|
|
44851
45120
|
const env2 = { ...ctx.env };
|
|
@@ -44856,12 +45125,12 @@ async function runScanCore(ctx, deps = {}) {
|
|
|
44856
45125
|
const verdict = computeVerdict(findings);
|
|
44857
45126
|
const report = {
|
|
44858
45127
|
schemaVersion: 1,
|
|
44859
|
-
key:
|
|
45128
|
+
key: sha256Hex2(`scan:${spec.ecosystem}:${spec.name}@${spec.version}:${head}`),
|
|
44860
45129
|
head,
|
|
44861
45130
|
dirty: false,
|
|
44862
45131
|
exposureSet: [`${spec.ecosystem}:${spec.name}@${spec.version}`],
|
|
44863
45132
|
refSet: [],
|
|
44864
|
-
rulesHash:
|
|
45133
|
+
rulesHash: sha256Hex2(buildScanConfigYaml(spec.ecosystem)),
|
|
44865
45134
|
verdict,
|
|
44866
45135
|
counts: countFindings(findings),
|
|
44867
45136
|
findings,
|
|
@@ -44970,11 +45239,12 @@ var handlers = {
|
|
|
44970
45239
|
"llm-request": runLlmRequest,
|
|
44971
45240
|
"llm-ingest": runLlmIngest,
|
|
44972
45241
|
scan: runScan2,
|
|
44973
|
-
roundtrip: runRoundtrip
|
|
45242
|
+
roundtrip: runRoundtrip,
|
|
45243
|
+
opencode: runOpencode
|
|
44974
45244
|
};
|
|
44975
45245
|
|
|
44976
45246
|
// src/cli/types.ts
|
|
44977
|
-
var SUBCOMMANDS = ["check", "push", "status", "llm-request", "llm-ingest", "scan", "roundtrip"];
|
|
45247
|
+
var SUBCOMMANDS = ["check", "push", "status", "llm-request", "llm-ingest", "scan", "roundtrip", "opencode"];
|
|
44978
45248
|
|
|
44979
45249
|
// src/cli.ts
|
|
44980
45250
|
var VALID_TARGETS = targetIds();
|
|
@@ -44992,6 +45262,7 @@ function usage() {
|
|
|
44992
45262
|
" llm-ingest record provenance captured by the harness",
|
|
44993
45263
|
" scan inspect a third-party package ([ecosystem:]name@version) for residue",
|
|
44994
45264
|
" roundtrip install+uninstall a package (registry spec or local artifact file) in a throwaway container, report uninstall residue",
|
|
45265
|
+
" opencode install|status|uninstall the official opencode plugin adapter (files under $XDG_CONFIG_HOME/opencode)",
|
|
44995
45266
|
"",
|
|
44996
45267
|
"flags:",
|
|
44997
45268
|
" --config <path> config file (default: ./border.yaml, then git-remote fallback)",
|
|
@@ -45006,7 +45277,7 @@ function usage() {
|
|
|
45006
45277
|
"",
|
|
45007
45278
|
"exit codes: 0 pass (MEDIUM/INFO/LOW allowed) | 1 gate-blocked (CRITICAL/HIGH) or partial push | 2 config/tool error",
|
|
45008
45279
|
"",
|
|
45009
|
-
"subcommands: check, push, status, llm-request, llm-ingest, scan, roundtrip"
|
|
45280
|
+
"subcommands: check, push, status, llm-request, llm-ingest, scan, roundtrip, opencode"
|
|
45010
45281
|
].join("\n");
|
|
45011
45282
|
}
|
|
45012
45283
|
function isSubcommand(value) {
|
|
@@ -45130,7 +45401,7 @@ function isMainModule({ argv1, moduleUrl, realpath }) {
|
|
|
45130
45401
|
return p;
|
|
45131
45402
|
}
|
|
45132
45403
|
};
|
|
45133
|
-
return pathToFileURL(canonical(argv1)).href === pathToFileURL(canonical(
|
|
45404
|
+
return pathToFileURL(canonical(argv1)).href === pathToFileURL(canonical(fileURLToPath6(moduleUrl))).href;
|
|
45134
45405
|
}
|
|
45135
45406
|
if (isMainModule({ argv1: process.argv[1], moduleUrl: import.meta.url, realpath: realpathSync.native })) {
|
|
45136
45407
|
const outcome = run(process.argv.slice(2));
|