@loucompanion/forge-bridge 0.1.3-dev.c28767e94230 → 0.1.3-dev.d46a8e73d03d

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.
@@ -556,13 +556,14 @@ export class DockerSandboxRuntime {
556
556
  const helperImageDigest = (await this.docker(["image", "inspect", helperTag, "--format", "{{.Id}}"], 15_000)).stdout.trim();
557
557
  if (!/^sha256:[0-9a-f]{64}$/i.test(helperImageDigest))
558
558
  throw new Error("sandbox_build_failed: helper image did not resolve to an immutable digest.");
559
+ const projectContextRef = ociLayoutContextRef(path.relative(overlayRoot, projectOciRoot));
559
560
  await this.runBuildStage("agent", key, buildxBuildArgs(builder, path.join(metadataRoot, "agent.json"), [
560
561
  "--pull=false",
561
- "--build-context", `${projectContextName}=${ociLayoutContextRef(projectOciRoot)}`,
562
+ "--build-context", `${projectContextName}=${projectContextRef}`,
562
563
  ...imageLabelArgs(project, key, "agent"),
563
564
  "--tag", overlayTag,
564
- overlayRoot
565
- ]), sink, signal);
565
+ "."
566
+ ]), sink, signal, { cwd: overlayRoot });
566
567
  const digest = (await this.docker(["image", "inspect", overlayTag, "--format", "{{.Id}}"], 15_000)).stdout.trim();
567
568
  if (!/^sha256:[0-9a-f]{64}$/i.test(digest))
568
569
  throw new Error("sandbox_build_failed: overlay image did not resolve to an immutable digest.");
@@ -602,10 +603,11 @@ export class DockerSandboxRuntime {
602
603
  }));
603
604
  return { imageDigest: digest, helperImageDigest };
604
605
  }
605
- async runBuildStage(stage, key, args, sink, signal) {
606
+ async runBuildStage(stage, key, args, sink, signal, options = {}) {
606
607
  const result = await runSandboxProcess({
607
608
  command: this.config.dockerExecutable,
608
609
  args: this.dockerArgs(args),
610
+ cwd: options.cwd,
609
611
  timeoutMs: 30 * 60_000,
610
612
  maxOutputBytes: 2 * 1024 * 1024,
611
613
  terminateOnOutputLimit: true,
@@ -658,10 +660,10 @@ export class DockerSandboxRuntime {
658
660
  }
659
661
  async initializeVolumes(helperImage, resources, includeCredentials, signal) {
660
662
  const cacheMounts = Object.entries(resources.cacheVolumes).flatMap(([name, volume]) => ["--volume", `${volume}:/cache/${name}`]);
661
- const credentialSetup = includeCredentials ? "f.chownSync('/credentials',10001,10001);f.chmodSync('/credentials',0o700);" : "";
662
- const script = `const f=require('fs');f.mkdirSync('/control/input',{recursive:true});f.mkdirSync('/control/output',{recursive:true});for(const p of ['/control','/control/input','/control/output','/workspace'])f.chmodSync(p,0o777);${credentialSetup}for(const p of f.readdirSync('/cache').map(n=>'/cache/'+n)){f.chownSync(p,10001,10001);f.chmodSync(p,0o700)}`;
663
+ const credentialSetup = includeCredentials ? "f.chmodSync('/credentials',0o700);" : "";
664
+ const script = `const f=require('fs');f.mkdirSync('/control/input',{recursive:true});f.mkdirSync('/control/output',{recursive:true});for(const p of ['/control','/control/input','/control/output','/workspace'])f.chmodSync(p,0o777);${credentialSetup}for(const p of f.readdirSync('/cache').map(n=>'/cache/'+n)){f.chmodSync(p,0o700);f.chownSync(p,10001,10001)}`;
663
665
  const credentialMount = includeCredentials ? ["--volume", `${resources.credentialsVolume}:/credentials`] : [];
664
- await this.docker(["run", "--rm", "--network", "none", "--read-only", "--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--pids-limit", "64", "--memory", "128m", "--memory-swap", "128m", "--cpus", "0.25", "--log-driver", "none", "--user", "0", "--entrypoint", "node", "--volume", `${resources.workspaceVolume}:/workspace`, "--volume", `${resources.controlVolume}:/control`, ...credentialMount, ...cacheMounts, "--tmpfs", "/cache:rw,nosuid,nodev,noexec,size=1m", helperImage, "-e", script], 60_000, signal);
666
+ await this.docker(["run", "--rm", "--network", "none", "--read-only", "--cap-drop", "ALL", "--cap-add", "CHOWN", "--security-opt", "no-new-privileges", "--pids-limit", "64", "--memory", "128m", "--memory-swap", "128m", "--cpus", "0.25", "--log-driver", "none", "--user", "0", "--entrypoint", "node", "--volume", `${resources.workspaceVolume}:/workspace`, "--volume", `${resources.controlVolume}:/control`, ...credentialMount, ...cacheMounts, "--tmpfs", "/cache:rw,nosuid,nodev,noexec,size=1m", helperImage, "-e", script], 60_000, signal);
665
667
  }
666
668
  async inspectContainerState(containerId) {
667
669
  const result = await this.docker(["container", "inspect", containerId, "--format", "{{json .State}}"], 15_000, undefined, 256 * 1024);
@@ -692,10 +694,10 @@ export class DockerSandboxRuntime {
692
694
  if (!providerAuth) {
693
695
  return;
694
696
  }
695
- const script = "let s='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>s+=c);process.stdin.on('end',()=>{const f=require('fs'),p=require('path'),root='/credentials';for(const e of f.readdirSync(root))f.rmSync(p.join(root,e),{recursive:true,force:true});const packet=JSON.parse(s);for(const file of packet.files){if(!/^[a-z0-9_.-]+$/i.test(file.name))throw new Error('invalid credential file');const target=p.resolve(root,file.name);if(!target.startsWith(root+'/'))throw new Error('invalid credential file');const body=Buffer.from(file.contentBase64,'base64');f.writeFileSync(target,body,{mode:0o600});f.chownSync(target,10001,10001)}f.chownSync(root,10001,10001);f.chmodSync(root,0o700)})";
697
+ const script = "let s='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>s+=c);process.stdin.on('end',()=>{const f=require('fs'),p=require('path'),root='/credentials';for(const e of f.readdirSync(root))f.rmSync(p.join(root,e),{recursive:true,force:true});const packet=JSON.parse(s);for(const file of packet.files){if(!/^[a-z0-9_.-]+$/i.test(file.name))throw new Error('invalid credential file');const target=p.resolve(root,file.name);if(!target.startsWith(root+'/'))throw new Error('invalid credential file');const body=Buffer.from(file.contentBase64,'base64');f.writeFileSync(target,body,{mode:0o600});f.chmodSync(target,0o600);f.chownSync(target,10001,10001)}f.chmodSync(root,0o700);f.chownSync(root,10001,10001)})";
696
698
  await requireSandboxProcess({
697
699
  command: this.config.dockerExecutable,
698
- args: this.dockerArgs(["run", "--rm", "--interactive", "--network", "none", "--read-only", "--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--pids-limit", "64", "--memory", "128m", "--memory-swap", "128m", "--cpus", "0.25", "--log-driver", "none", "--user", "0", "--entrypoint", "node", "--volume", `${resources.credentialsVolume}:/credentials`, helperImage, "-e", script]),
700
+ args: this.dockerArgs(["run", "--rm", "--interactive", "--network", "none", "--read-only", "--cap-drop", "ALL", "--cap-add", "CHOWN", "--security-opt", "no-new-privileges", "--pids-limit", "64", "--memory", "128m", "--memory-swap", "128m", "--cpus", "0.25", "--log-driver", "none", "--user", "0", "--entrypoint", "node", "--volume", `${resources.credentialsVolume}:/credentials`, helperImage, "-e", script]),
699
701
  input: JSON.stringify({ files: providerAuth.files }),
700
702
  timeoutMs: 60_000,
701
703
  maxOutputBytes: 64 * 1024,
@@ -1453,11 +1455,15 @@ export async function extractOciLayoutArchive(archivePath, destination, signal)
1453
1455
  await fs.rm(archivePath, { force: true }).catch(() => undefined);
1454
1456
  }
1455
1457
  export function ociLayoutContextRef(layoutPath) {
1456
- const absolute = /^[A-Za-z]:[\\/]/u.test(layoutPath) ? layoutPath : path.resolve(layoutPath);
1457
- const normalized = absolute.replace(/\\/gu, "/");
1458
+ if (/^[A-Za-z]:[\\/]/u.test(layoutPath)) {
1459
+ throw new Error("sandbox_build_failed: Windows absolute OCI layout paths are not BuildKit-safe; use a relative path from the build working directory.");
1460
+ }
1461
+ const normalized = layoutPath.replace(/\\/gu, "/").replace(/^\.\//u, "");
1458
1462
  // BuildKit's oci-layout resolver expects a raw filesystem path here; it does not percent-decode
1459
- // URI path escapes such as %20 before opening index.json.
1460
- return normalized.startsWith("/") ? `oci-layout://${normalized}` : `oci-layout:///${normalized}`;
1463
+ // URI path escapes such as %20 before opening index.json. On Windows, absolute drive paths
1464
+ // such as D:/state/project-oci are parsed as OCI layout ref/tag syntax, so callers should
1465
+ // run buildx from the layout parent and pass a relative path (for example project-oci).
1466
+ return `oci-layout://${normalized}`;
1461
1467
  }
1462
1468
  export function formatOciExtractionFailure(result) {
1463
1469
  const output = result.stderr.trim() || result.stdout.trim() || "no tar output";
@@ -111,7 +111,7 @@ function parseSandboxResourceRequest(value) {
111
111
  }
112
112
  function parseSandboxNetworkPolicy(value) {
113
113
  if (value === undefined || value === null)
114
- return { mode: "public_allowlist", allowedHosts: [] };
114
+ return { mode: "offline", allowedHosts: [] };
115
115
  const policy = asObject(value, "sandbox.networkPolicy");
116
116
  const mode = requiredString(policy, "mode");
117
117
  if (mode !== "offline" && mode !== "public_allowlist" && mode !== "open_public") {
@@ -128,6 +128,9 @@ function parseSandboxNetworkPolicy(value) {
128
128
  if (mode !== "public_allowlist" && allowedHosts.length > 0) {
129
129
  throw new Error("sandbox.networkPolicy.allowedHosts requires public_allowlist mode.");
130
130
  }
131
+ if (mode === "public_allowlist" && allowedHosts.length === 0) {
132
+ return { mode: "offline", allowedHosts: [] };
133
+ }
131
134
  return { mode, allowedHosts };
132
135
  }
133
136
  export function failureResult(summary, events = []) {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const forgeBridgeVersion = "0.1.3-dev.c28767e94230";
1
+ export declare const forgeBridgeVersion = "0.1.3-dev.d46a8e73d03d";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const forgeBridgeVersion = "0.1.3-dev.c28767e94230";
1
+ export const forgeBridgeVersion = "0.1.3-dev.d46a8e73d03d";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loucompanion/forge-bridge",
3
- "version": "0.1.3-dev.c28767e94230",
3
+ "version": "0.1.3-dev.d46a8e73d03d",
4
4
  "description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",