@loucompanion/forge-bridge 0.1.3-dev.9d8f2e7e5241 → 0.1.3-dev.a58d7cd1092f
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 +83 -0
- package/dist/bridge/bridge.base.d.ts +4 -1
- package/dist/bridge/bridge.service.d.ts +55 -3
- package/dist/bridge/bridge.service.js +834 -87
- package/dist/bridge/bridge.types.d.ts +89 -1
- package/dist/bridge/runner-host-capabilities.d.ts +15 -0
- package/dist/bridge/runner-host-capabilities.js +93 -0
- package/dist/cleanup/cleanup.service.d.ts +14 -2
- package/dist/cleanup/cleanup.service.js +496 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.js +37 -3
- package/dist/cli/cli.service.d.ts +33 -2
- package/dist/cli/cli.service.js +209 -27
- package/dist/cli/local-config.d.ts +5 -0
- package/dist/cli/local-config.js +45 -1
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
- package/dist/runtime/attempt-process-supervisor.js +386 -0
- package/dist/runtime/docker-engine-probe.d.ts +83 -0
- package/dist/runtime/docker-engine-probe.js +300 -0
- package/dist/runtime/generated-output-reset.d.ts +10 -0
- package/dist/runtime/generated-output-reset.js +103 -0
- package/dist/runtime/host-listener-inspector.d.ts +46 -0
- package/dist/runtime/host-listener-inspector.js +349 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +47 -0
- package/dist/runtime/host-runtime-contract.js +700 -0
- package/dist/runtime/host-runtime-lease.d.ts +197 -0
- package/dist/runtime/host-runtime-lease.js +2203 -0
- package/dist/runtime/host-runtime-promotion.d.ts +23 -0
- package/dist/runtime/host-runtime-promotion.js +159 -0
- package/dist/runtime/memory-capacity.d.ts +68 -0
- package/dist/runtime/memory-capacity.js +121 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +284 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +231 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +158 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/preflight.d.ts +45 -1
- package/dist/sandbox/preflight.js +299 -34
- package/dist/sandbox/process.js +21 -2
- package/dist/sandbox/repository-lock.js +41 -3
- package/dist/sandbox/runtime.d.ts +66 -3
- package/dist/sandbox/runtime.js +670 -105
- package/dist/sandbox/sandbox.types.d.ts +10 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.js +1 -1
- package/dist/sandbox/worker.js +2 -5
- package/dist/sandbox/workspace.service.d.ts +1 -0
- package/dist/sandbox/workspace.service.js +28 -5
- package/dist/session/managed-attempt-tools.d.ts +21 -0
- package/dist/session/managed-attempt-tools.js +912 -0
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +105 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +335 -0
- package/dist/session/phase-completion.artifacts.d.ts +17 -0
- package/dist/session/phase-completion.artifacts.js +233 -24
- package/dist/session/phase-completion.d.ts +12 -2
- package/dist/session/phase-completion.helpers.d.ts +5 -0
- package/dist/session/phase-completion.helpers.js +51 -10
- package/dist/session/phase-completion.js +14 -3
- package/dist/session/phase-completion.prompts.d.ts +3 -3
- package/dist/session/phase-completion.prompts.js +24 -8
- package/dist/session/phase-completion.runtime.d.ts +13 -1
- package/dist/session/phase-completion.runtime.js +189 -8
- package/dist/session/phase-completion.schemas.js +117 -4
- package/dist/session/phase-completion.validation.d.ts +4 -1
- package/dist/session/phase-completion.validation.js +153 -22
- package/dist/session/pre-publication-validation.d.ts +46 -0
- package/dist/session/pre-publication-validation.js +208 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
- package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
- package/dist/session/provider-cli/provider-cli.deterministic.js +47 -1
- package/dist/session/provider-cli/provider-cli.process.d.ts +25 -0
- package/dist/session/provider-cli/provider-cli.process.js +463 -34
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +4 -1
- package/dist/session/provider-cli/provider-cli.prompts.js +108 -25
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
- package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +22 -2
- package/dist/session/provider-cli/provider-cli.service.js +529 -100
- package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
- package/dist/session/provider-cli/provider-output-state.js +91 -16
- package/dist/session/provider-cli/provider-usage.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.js +28 -5
- package/dist/session/session-redaction.d.ts +3 -0
- package/dist/session/session-redaction.js +38 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +522 -41
- package/dist/session/session.types.d.ts +72 -0
- package/dist/session/session.types.js +74 -3
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +21 -1
- package/dist/shared/process/dotnet-toolchain.d.ts +14 -0
- package/dist/shared/process/dotnet-toolchain.js +179 -0
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +78 -0
- package/dist/shared/process/host-process-supervisor.d.ts +59 -0
- package/dist/shared/process/host-process-supervisor.js +330 -0
- package/dist/shared/process/process.d.ts +1 -0
- package/dist/shared/process/process.js +1 -0
- package/dist/shared/redaction/redaction.js +12 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +43 -4
- package/dist/worktree/dependency-warmer.d.ts +28 -1
- package/dist/worktree/dependency-warmer.js +308 -9
- package/dist/worktree/index.d.ts +2 -1
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +13 -2
- package/dist/worktree/worktree.service.js +238 -47
- package/dist/worktree/worktree.types.d.ts +5 -1
- package/package.json +5 -3
- package/runtime/windows-job-supervisor.ps1 +444 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { type DotNetToolchainSelection } from "../shared/process/dotnet-toolchain.js";
|
|
1
2
|
import type { BridgeService } from "../bridge/bridge.base.js";
|
|
2
3
|
import type { BridgeServiceConfig } from "../bridge/bridge.types.js";
|
|
4
|
+
import type { CleanupMode } from "../cleanup/cleanup.types.js";
|
|
5
|
+
import type { CleanupService } from "../cleanup/cleanup.base.js";
|
|
3
6
|
import type { SessionService } from "../session/session.base.js";
|
|
4
7
|
import type { CliIo, CliService } from "./cli.base.js";
|
|
8
|
+
type DotNetToolchainConfigurator = (repoRoots: readonly string[]) => Promise<DotNetToolchainSelection | null>;
|
|
5
9
|
export declare class ForgeBridgeCliService implements CliService {
|
|
6
10
|
private readonly sessions;
|
|
7
11
|
private readonly bridgeFactory;
|
|
8
12
|
private readonly pairRunner;
|
|
9
|
-
|
|
13
|
+
private readonly cleanupFactory;
|
|
14
|
+
private readonly dotNetToolchainConfigurator;
|
|
15
|
+
constructor(sessions?: SessionService, bridgeFactory?: (config: BridgeServiceConfig) => BridgeService, pairRunner?: PairRunnerClient, cleanupFactory?: CleanupServiceFactory, dotNetToolchainConfigurator?: DotNetToolchainConfigurator);
|
|
10
16
|
run(argv: readonly string[], io?: CliIo): Promise<number>;
|
|
11
17
|
}
|
|
12
18
|
export declare function parseArgs(argv: readonly string[]): {
|
|
@@ -34,6 +40,14 @@ export declare function parseArgs(argv: readonly string[]): {
|
|
|
34
40
|
readonly ok: true;
|
|
35
41
|
readonly command: "remove-repo";
|
|
36
42
|
readonly label: string;
|
|
43
|
+
} | {
|
|
44
|
+
readonly ok: true;
|
|
45
|
+
readonly command: "server-show";
|
|
46
|
+
readonly config: BridgeServiceConfig;
|
|
47
|
+
} | {
|
|
48
|
+
readonly ok: true;
|
|
49
|
+
readonly command: "server-set";
|
|
50
|
+
readonly serverUrl: string;
|
|
37
51
|
} | {
|
|
38
52
|
readonly ok: true;
|
|
39
53
|
readonly command: "status";
|
|
@@ -54,9 +68,26 @@ export declare function parseArgs(argv: readonly string[]): {
|
|
|
54
68
|
readonly command: "sandbox-doctor";
|
|
55
69
|
readonly repoPath: string;
|
|
56
70
|
readonly requirePinnedBase: boolean;
|
|
71
|
+
} | {
|
|
72
|
+
readonly ok: true;
|
|
73
|
+
readonly command: "sandbox-smoke";
|
|
74
|
+
readonly repoPath: string;
|
|
75
|
+
readonly reportPath?: string;
|
|
76
|
+
readonly requirePinnedBase: boolean;
|
|
77
|
+
readonly probeLoopbackPort: boolean;
|
|
57
78
|
} | {
|
|
58
79
|
readonly ok: true;
|
|
59
80
|
readonly command: "sandbox-reap";
|
|
81
|
+
} | {
|
|
82
|
+
readonly ok: true;
|
|
83
|
+
readonly command: "sandbox-on";
|
|
84
|
+
readonly allowOpenPublic: boolean;
|
|
85
|
+
} | {
|
|
86
|
+
readonly ok: true;
|
|
87
|
+
readonly command: "sandbox-off";
|
|
88
|
+
} | {
|
|
89
|
+
readonly ok: true;
|
|
90
|
+
readonly command: "sandbox-status";
|
|
60
91
|
} | {
|
|
61
92
|
readonly ok: false;
|
|
62
93
|
readonly error: string;
|
|
@@ -78,5 +109,5 @@ interface PairRunnerClaim {
|
|
|
78
109
|
type PairRunnerClient = (args: PairArgs) => Promise<PairRunnerClaim>;
|
|
79
110
|
export declare function parseCodexModelCatalog(raw: string): string[];
|
|
80
111
|
export declare function parseCursorModelList(raw: string): string[];
|
|
81
|
-
type
|
|
112
|
+
type CleanupServiceFactory = (stateRoot: string) => CleanupService;
|
|
82
113
|
export {};
|
package/dist/cli/cli.service.js
CHANGED
|
@@ -6,15 +6,19 @@ import { createInterface } from "node:readline/promises";
|
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { resolveCursorAgentDirectLaunch } from "../shared/cursor-agent/cursor-agent.launch.js";
|
|
8
8
|
import { buildWindowsCmdLaunch } from "../shared/process/windows-launch.js";
|
|
9
|
+
import { configureDotNetToolchain } from "../shared/process/dotnet-toolchain.js";
|
|
9
10
|
import { supportsDraftImageAttachmentsFor } from "../shared/provider-capabilities.js";
|
|
10
|
-
import {
|
|
11
|
+
import { redactText } from "../shared/redaction/redaction.js";
|
|
11
12
|
import { defaultBridgeServiceConfig, ForgeBridgeService } from "../bridge/bridge.service.js";
|
|
12
13
|
import { forgeBridgeVersion } from "../version.js";
|
|
13
|
-
import {
|
|
14
|
+
import { ManagedWorktreeCleanupService } from "../cleanup/cleanup.service.js";
|
|
15
|
+
import { addRepoMapping, applyLocalConfigDefaults, providerStatusesFor, readLocalConfigSync, removeRepoMapping, resolveLocalConfigPath, setSandboxPolicy, setServerUrl, writeLocalConfig } from "./local-config.js";
|
|
14
16
|
import { BridgeSessionService } from "../session/session.service.js";
|
|
17
|
+
import { redactRunnerDispatchEvent, redactRunOnceResultPacket } from "../session/session-redaction.js";
|
|
15
18
|
import { validateDockerfilePolicy, validateSandboxContract } from "../sandbox/contract.js";
|
|
16
19
|
import { DockerPreflight } from "../sandbox/preflight.js";
|
|
17
20
|
import { DockerSandboxRuntime } from "../sandbox/runtime.js";
|
|
21
|
+
import { DockerSandboxSmoke } from "../sandbox/smoke.js";
|
|
18
22
|
import { parseCompletionMcpArgs, runCompletionMcpServer } from "../session/phase-completion.js";
|
|
19
23
|
import { failureResult, parseRunOnceDispatchPacket } from "../session/session.types.js";
|
|
20
24
|
import { commandHelpText, helpText } from "./cli.help.js";
|
|
@@ -24,10 +28,14 @@ export class ForgeBridgeCliService {
|
|
|
24
28
|
sessions;
|
|
25
29
|
bridgeFactory;
|
|
26
30
|
pairRunner;
|
|
27
|
-
|
|
31
|
+
cleanupFactory;
|
|
32
|
+
dotNetToolchainConfigurator;
|
|
33
|
+
constructor(sessions = new BridgeSessionService(), bridgeFactory = (config) => new ForgeBridgeService(config), pairRunner = claimBridgeRunner, cleanupFactory = stateRoot => new ManagedWorktreeCleanupService(stateRoot), dotNetToolchainConfigurator = configureDotNetToolchain) {
|
|
28
34
|
this.sessions = sessions;
|
|
29
35
|
this.bridgeFactory = bridgeFactory;
|
|
30
36
|
this.pairRunner = pairRunner;
|
|
37
|
+
this.cleanupFactory = cleanupFactory;
|
|
38
|
+
this.dotNetToolchainConfigurator = dotNetToolchainConfigurator;
|
|
31
39
|
}
|
|
32
40
|
async run(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
33
41
|
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
@@ -62,11 +70,29 @@ export class ForgeBridgeCliService {
|
|
|
62
70
|
io.stderr.write(`forge-bridge connect requires ${missing[0]}.\n`);
|
|
63
71
|
return 2;
|
|
64
72
|
}
|
|
73
|
+
let dotNetToolchain;
|
|
74
|
+
try {
|
|
75
|
+
dotNetToolchain = await this.dotNetToolchainConfigurator([
|
|
76
|
+
...config.repoMappings.map((mapping) => mapping.localPath?.trim()),
|
|
77
|
+
config.defaultRepoRoot.trim(),
|
|
78
|
+
].filter((repoRoot) => Boolean(repoRoot)));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
if (dotNetToolchain) {
|
|
85
|
+
io.stdout.write(`Using .NET SDK ${dotNetToolchain.sdkVersion}: ${dotNetToolchain.executablePath}\n`);
|
|
86
|
+
}
|
|
65
87
|
const controller = new AbortController();
|
|
66
88
|
const stop = () => controller.abort("signal received");
|
|
67
89
|
process.once("SIGINT", stop);
|
|
68
90
|
process.once("SIGTERM", stop);
|
|
69
91
|
try {
|
|
92
|
+
if (config.cleanupWorktrees && config.repoMappings.some(mapping => mapping.localPath?.trim())) {
|
|
93
|
+
io.stdout.write("Cleaning stale managed worktrees before accepting dispatches.\n");
|
|
94
|
+
await cleanupRepos(config, "stale", true, this.cleanupFactory(config.sandboxStateRoot), io);
|
|
95
|
+
}
|
|
70
96
|
io.stdout.write(connectStartupText(config));
|
|
71
97
|
await this.bridgeFactory(config).run(controller.signal);
|
|
72
98
|
return 0;
|
|
@@ -122,12 +148,38 @@ export class ForgeBridgeCliService {
|
|
|
122
148
|
return 2;
|
|
123
149
|
}
|
|
124
150
|
}
|
|
151
|
+
if (args.command === "server-show") {
|
|
152
|
+
const saved = readLocalConfigSync().serverUrl ?? "not configured";
|
|
153
|
+
io.stdout.write(`Saved server: ${saved}\n`);
|
|
154
|
+
if (process.env.FORGE_URL?.trim()) {
|
|
155
|
+
io.stdout.write(`Effective server: ${process.env.FORGE_URL.trim()} (FORGE_URL override)\n`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
io.stdout.write(`Effective server: ${args.config.serverUrl || "not configured"}\n`);
|
|
159
|
+
}
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
if (args.command === "server-set") {
|
|
163
|
+
try {
|
|
164
|
+
const config = await setServerUrl(args.serverUrl);
|
|
165
|
+
io.stdout.write(`Saved Forge server: ${config.serverUrl}\n`);
|
|
166
|
+
io.stdout.write("Existing runner credentials were retained. Re-run `forge-bridge pair --server <url>` if this is a different Forge deployment.\n");
|
|
167
|
+
if (process.env.FORGE_URL?.trim()) {
|
|
168
|
+
io.stdout.write(`Warning: FORGE_URL is set to ${process.env.FORGE_URL.trim()} and overrides the saved server.\n`);
|
|
169
|
+
}
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
174
|
+
return 2;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
125
177
|
if (args.command === "status") {
|
|
126
178
|
await writeStatus(args.config, args.checkUpdates, io);
|
|
127
179
|
return 0;
|
|
128
180
|
}
|
|
129
181
|
if (args.command === "cleanup") {
|
|
130
|
-
return await cleanupRepos(args.config, args.mode, args.force, io);
|
|
182
|
+
return await cleanupRepos(args.config, args.mode, args.force, this.cleanupFactory(args.config.sandboxStateRoot), io);
|
|
131
183
|
}
|
|
132
184
|
if (args.command === "sandbox-init") {
|
|
133
185
|
return await initializeSandbox(args.repoPath, io);
|
|
@@ -135,9 +187,28 @@ export class ForgeBridgeCliService {
|
|
|
135
187
|
if (args.command === "sandbox-doctor") {
|
|
136
188
|
return await diagnoseSandbox(args.repoPath, args.requirePinnedBase, io);
|
|
137
189
|
}
|
|
190
|
+
if (args.command === "sandbox-smoke") {
|
|
191
|
+
return await smokeSandbox(args.repoPath, args.reportPath, args.requirePinnedBase, args.probeLoopbackPort, io);
|
|
192
|
+
}
|
|
138
193
|
if (args.command === "sandbox-reap") {
|
|
139
194
|
return await reapSandboxes(io);
|
|
140
195
|
}
|
|
196
|
+
if (args.command === "sandbox-on") {
|
|
197
|
+
const saved = await setSandboxPolicy(true, args.allowOpenPublic);
|
|
198
|
+
io.stdout.write("Saved local Docker sandbox policy. Restart the bridge to apply it.\n");
|
|
199
|
+
writeSandboxPolicyStatus(saved, io);
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
if (args.command === "sandbox-off") {
|
|
203
|
+
const saved = await setSandboxPolicy(false, false);
|
|
204
|
+
io.stdout.write("Disabled local Docker sandbox placement. Restart the bridge to apply it.\n");
|
|
205
|
+
writeSandboxPolicyStatus(saved, io);
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
if (args.command === "sandbox-status") {
|
|
209
|
+
writeSandboxPolicyStatus(readLocalConfigSync(), io);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
141
212
|
if (args.command === "mcp") {
|
|
142
213
|
const parsed = parseCompletionMcpArgs(args.argv);
|
|
143
214
|
if (!parsed.ok) {
|
|
@@ -156,12 +227,12 @@ export class ForgeBridgeCliService {
|
|
|
156
227
|
emit: async (event) => {
|
|
157
228
|
const line = {
|
|
158
229
|
type: "runner_event",
|
|
159
|
-
event:
|
|
230
|
+
event: redactRunnerDispatchEvent(event, redactionSecrets)
|
|
160
231
|
};
|
|
161
232
|
io.stdout.write(`${JSON.stringify(line)}\n`);
|
|
162
233
|
}
|
|
163
234
|
});
|
|
164
|
-
await writeJsonAtomic(args.resultPath,
|
|
235
|
+
await writeJsonAtomic(args.resultPath, redactRunOnceResultPacket(result, redactionSecrets));
|
|
165
236
|
return result.exitCode;
|
|
166
237
|
}
|
|
167
238
|
catch (error) {
|
|
@@ -247,6 +318,15 @@ export function parseArgs(argv) {
|
|
|
247
318
|
}
|
|
248
319
|
return { ok: true, command: "remove-repo", label: argv[1] };
|
|
249
320
|
}
|
|
321
|
+
if (argv[0] === "server") {
|
|
322
|
+
if (argv.length === 1 || (argv[1] === "show" && argv.length === 2)) {
|
|
323
|
+
return { ok: true, command: "server-show", config: configuredBridgeServiceConfig() };
|
|
324
|
+
}
|
|
325
|
+
if (argv[1] === "set" && argv[2] && argv.length === 3) {
|
|
326
|
+
return { ok: true, command: "server-set", serverUrl: argv[2] };
|
|
327
|
+
}
|
|
328
|
+
return { ok: false, error: "Usage: forge-bridge server <show|set <url>>" };
|
|
329
|
+
}
|
|
250
330
|
if (argv[0] === "status") {
|
|
251
331
|
let checkUpdates = false;
|
|
252
332
|
for (let index = 1; index < argv.length; index += 1) {
|
|
@@ -301,8 +381,26 @@ export function parseArgs(argv) {
|
|
|
301
381
|
}
|
|
302
382
|
function parseSandboxArgs(argv) {
|
|
303
383
|
const action = argv[1];
|
|
384
|
+
if (action === "on") {
|
|
385
|
+
let allowOpenPublic = false;
|
|
386
|
+
for (let index = 2; index < argv.length; index += 1) {
|
|
387
|
+
if (argv[index] === "--allow-open-public") {
|
|
388
|
+
allowOpenPublic = true;
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
return { ok: false, error: `Unknown sandbox argument: ${argv[index]}` };
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return { ok: true, command: "sandbox-on", allowOpenPublic };
|
|
395
|
+
}
|
|
396
|
+
if (action === "off" && argv.length === 2)
|
|
397
|
+
return { ok: true, command: "sandbox-off" };
|
|
398
|
+
if (action === "status" && argv.length === 2)
|
|
399
|
+
return { ok: true, command: "sandbox-status" };
|
|
304
400
|
let repoPath = process.cwd();
|
|
401
|
+
let reportPath;
|
|
305
402
|
let requirePinnedBase = false;
|
|
403
|
+
let probeLoopbackPort = false;
|
|
306
404
|
for (let index = 2; index < argv.length; index += 1) {
|
|
307
405
|
const arg = argv[index];
|
|
308
406
|
if (arg === "--repo") {
|
|
@@ -310,9 +408,17 @@ function parseSandboxArgs(argv) {
|
|
|
310
408
|
if (!repoPath)
|
|
311
409
|
return { ok: false, error: "--repo requires a path." };
|
|
312
410
|
}
|
|
313
|
-
else if (arg === "--
|
|
411
|
+
else if (arg === "--report" && action === "smoke") {
|
|
412
|
+
reportPath = argv[++index];
|
|
413
|
+
if (!reportPath)
|
|
414
|
+
return { ok: false, error: "--report requires a path." };
|
|
415
|
+
}
|
|
416
|
+
else if (arg === "--require-pinned-base" && (action === "doctor" || action === "smoke")) {
|
|
314
417
|
requirePinnedBase = true;
|
|
315
418
|
}
|
|
419
|
+
else if (arg === "--probe-loopback-port" && action === "smoke") {
|
|
420
|
+
probeLoopbackPort = true;
|
|
421
|
+
}
|
|
316
422
|
else {
|
|
317
423
|
return { ok: false, error: `Unknown sandbox argument: ${arg}` };
|
|
318
424
|
}
|
|
@@ -321,9 +427,18 @@ function parseSandboxArgs(argv) {
|
|
|
321
427
|
return { ok: true, command: "sandbox-init", repoPath: path.resolve(repoPath) };
|
|
322
428
|
if (action === "doctor")
|
|
323
429
|
return { ok: true, command: "sandbox-doctor", repoPath: path.resolve(repoPath), requirePinnedBase };
|
|
430
|
+
if (action === "smoke")
|
|
431
|
+
return {
|
|
432
|
+
ok: true,
|
|
433
|
+
command: "sandbox-smoke",
|
|
434
|
+
repoPath: path.resolve(repoPath),
|
|
435
|
+
reportPath: reportPath ? path.resolve(reportPath) : undefined,
|
|
436
|
+
requirePinnedBase,
|
|
437
|
+
probeLoopbackPort,
|
|
438
|
+
};
|
|
324
439
|
if (action === "reap" && argv.length === 2)
|
|
325
440
|
return { ok: true, command: "sandbox-reap" };
|
|
326
|
-
return { ok: false, error: "Usage: forge-bridge sandbox <init|doctor|reap> [--repo <path>] [--require-pinned-base]" };
|
|
441
|
+
return { ok: false, error: "Usage: forge-bridge sandbox <on|off|status|init|doctor|smoke|reap> [--allow-open-public] [--repo <path>] [--report <path>] [--require-pinned-base] [--probe-loopback-port]" };
|
|
327
442
|
}
|
|
328
443
|
const sandboxContractTemplate = `schemaVersion: 1
|
|
329
444
|
workingDirectory: /workspace
|
|
@@ -389,6 +504,10 @@ async function diagnoseSandbox(repoPath, requirePinnedBase, io) {
|
|
|
389
504
|
diskHighWatermarkPercent: config.diskHighWatermarkPercent,
|
|
390
505
|
diskLowWatermarkPercent: config.diskLowWatermarkPercent,
|
|
391
506
|
minimumWorkspaceBytes: config.minimumWorkspaceBytes,
|
|
507
|
+
hostMemoryReserveBytes: config.sandboxHostMemoryReserveBytes,
|
|
508
|
+
defaultWorkerMemoryLimitBytes: config.defaultSandboxWorkerMemoryLimitBytes,
|
|
509
|
+
defaultServiceMemoryLimitBytes: config.defaultSandboxServiceMemoryLimitBytes,
|
|
510
|
+
runtimeOverheadBytes: config.sandboxRuntimeOverheadBytes,
|
|
392
511
|
requireRootless: config.requireRootlessDocker
|
|
393
512
|
});
|
|
394
513
|
try {
|
|
@@ -409,6 +528,42 @@ async function diagnoseSandbox(repoPath, requirePinnedBase, io) {
|
|
|
409
528
|
return 1;
|
|
410
529
|
}
|
|
411
530
|
}
|
|
531
|
+
async function smokeSandbox(repoPath, reportPath, requirePinnedBase, probeLoopbackPort, io) {
|
|
532
|
+
try {
|
|
533
|
+
const result = await new DockerSandboxSmoke(defaultBridgeServiceConfig()).run({
|
|
534
|
+
repoPath,
|
|
535
|
+
reportPath,
|
|
536
|
+
requirePinnedBase,
|
|
537
|
+
probeLoopbackPort,
|
|
538
|
+
onProgress: (stage) => io.stdout.write(`Smoke: ${stage}...\n`),
|
|
539
|
+
});
|
|
540
|
+
const { report } = result;
|
|
541
|
+
if (report.repository.contractDigest)
|
|
542
|
+
io.stdout.write(`Contract: valid (${report.repository.contractDigest})\n`);
|
|
543
|
+
for (const warning of report.repository.policyWarnings)
|
|
544
|
+
io.stdout.write(`Warning: ${warning}\n`);
|
|
545
|
+
if (report.docker.runtimeVersion) {
|
|
546
|
+
io.stdout.write(`Docker: healthy (${report.docker.runtimeVersion}; capacity ${report.docker.capacity})\n`);
|
|
547
|
+
}
|
|
548
|
+
io.stdout.write(`Attempt: ${report.dispatchAttemptId}\n`);
|
|
549
|
+
io.stdout.write(`Resource lease: ${report.resourceLeaseId}\n`);
|
|
550
|
+
if (report.publishedPortProbe) {
|
|
551
|
+
io.stdout.write(`Loopback port: ${report.publishedPortProbe.hostIp}:${report.publishedPortProbe.hostPort} -> ${report.publishedPortProbe.containerPort}/tcp\n`);
|
|
552
|
+
}
|
|
553
|
+
io.stdout.write(`Cleanup: ${report.cleanup.verified ? "verified (zero exact-label resources)" : "failed"}\n`);
|
|
554
|
+
io.stdout.write(`Report: ${result.reportPath}\n`);
|
|
555
|
+
if (report.status === "passed") {
|
|
556
|
+
io.stdout.write("Sandbox smoke: PASS\n");
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
io.stderr.write(`Sandbox smoke: FAIL (${report.failure?.code ?? "sandbox_smoke_failed"} at ${report.failure?.stage ?? "unknown"}): ${report.failure?.message ?? "unknown failure"}\n`);
|
|
560
|
+
return 1;
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
io.stderr.write(`Sandbox smoke could not write its report: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
564
|
+
return 1;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
412
567
|
async function reapSandboxes(io) {
|
|
413
568
|
const config = defaultBridgeServiceConfig();
|
|
414
569
|
if (!/^[0-9a-f-]{32,36}$/iu.test(config.runnerId)) {
|
|
@@ -423,6 +578,10 @@ async function reapSandboxes(io) {
|
|
|
423
578
|
diskHighWatermarkPercent: config.diskHighWatermarkPercent,
|
|
424
579
|
diskLowWatermarkPercent: config.diskLowWatermarkPercent,
|
|
425
580
|
minimumWorkspaceBytes: config.minimumWorkspaceBytes,
|
|
581
|
+
hostMemoryReserveBytes: config.sandboxHostMemoryReserveBytes,
|
|
582
|
+
defaultWorkerMemoryLimitBytes: config.defaultSandboxWorkerMemoryLimitBytes,
|
|
583
|
+
defaultServiceMemoryLimitBytes: config.defaultSandboxServiceMemoryLimitBytes,
|
|
584
|
+
runtimeOverheadBytes: config.sandboxRuntimeOverheadBytes,
|
|
426
585
|
requireRootless: config.requireRootlessDocker
|
|
427
586
|
});
|
|
428
587
|
try {
|
|
@@ -576,7 +735,7 @@ function parsePairArgs(argv) {
|
|
|
576
735
|
name: name?.trim() || defaultName,
|
|
577
736
|
fingerprint: fingerprint?.trim() || `local-bridge:${os.hostname()}:${user}`,
|
|
578
737
|
repoRoot: repoRoot?.trim() ? path.resolve(repoRoot) : undefined,
|
|
579
|
-
cleanupWorktrees: cleanupWorktrees ??
|
|
738
|
+
cleanupWorktrees: cleanupWorktrees ?? defaultConfig.cleanupWorktrees,
|
|
580
739
|
cliProviders: cliProviders.length > 0 ? cliProviders : localConfig.cliProviders && localConfig.cliProviders.length > 0 ? [...localConfig.cliProviders] : ["codex"],
|
|
581
740
|
projectLabels: projectLabels.length > 0 ? projectLabels : localConfig.projectLabels ? [...localConfig.projectLabels] : []
|
|
582
741
|
};
|
|
@@ -731,11 +890,28 @@ function connectStartupText(config) {
|
|
|
731
890
|
` repo folder: ${config.defaultRepoRoot || "not configured"}`,
|
|
732
891
|
` providers: ${formatList(config.cliProviders)}`,
|
|
733
892
|
` cleanup worktrees: ${config.cleanupWorktrees ? "enabled" : "disabled"}`,
|
|
893
|
+
` Docker Desktop auto-recovery: ${config.dockerDesktopRecoveryEnabled ? "enabled" : "disabled"}`,
|
|
894
|
+
` open public sandbox networking: ${config.allowOpenPublicSandbox ? "enabled" : "disabled"}`,
|
|
734
895
|
"",
|
|
735
896
|
"Press Ctrl+C to stop.",
|
|
736
897
|
"",
|
|
737
898
|
].join("\n");
|
|
738
899
|
}
|
|
900
|
+
function writeSandboxPolicyStatus(local, io) {
|
|
901
|
+
const effective = configuredBridgeServiceConfig({}, local);
|
|
902
|
+
io.stdout.write(`Config: ${resolveLocalConfigPath()}\n`);
|
|
903
|
+
io.stdout.write(`Docker sandbox placement: ${effective.dockerEnabled ? "on" : "off"}\n`);
|
|
904
|
+
io.stdout.write(`Open public sandbox networking: ${effective.allowOpenPublicSandbox ? "allowed" : "denied"}\n`);
|
|
905
|
+
if (process.env.FORGE_DOCKER_SANDBOX !== undefined) {
|
|
906
|
+
io.stdout.write("Docker setting source: FORGE_DOCKER_SANDBOX override\n");
|
|
907
|
+
}
|
|
908
|
+
if (process.env.FORGE_ALLOW_OPEN_PUBLIC_SANDBOX !== undefined) {
|
|
909
|
+
io.stdout.write("Open public setting source: FORGE_ALLOW_OPEN_PUBLIC_SANDBOX override\n");
|
|
910
|
+
}
|
|
911
|
+
if (effective.allowOpenPublicSandbox) {
|
|
912
|
+
io.stdout.write("Warning: open_public permits unrestricted outbound network access from developer sandboxes.\n");
|
|
913
|
+
}
|
|
914
|
+
}
|
|
739
915
|
function formatList(values) {
|
|
740
916
|
return values.length > 0 ? values.join(", ") : "none";
|
|
741
917
|
}
|
|
@@ -772,7 +948,7 @@ async function writeStatus(config, checkUpdates, io) {
|
|
|
772
948
|
io.stdout.write("Update check: no update channel configured.\n");
|
|
773
949
|
}
|
|
774
950
|
}
|
|
775
|
-
async function cleanupRepos(config, mode, force, io) {
|
|
951
|
+
async function cleanupRepos(config, mode, force, cleanup, io) {
|
|
776
952
|
const repos = config.repoMappings.filter((mapping) => mapping.localPath?.trim());
|
|
777
953
|
if (repos.length === 0) {
|
|
778
954
|
io.stdout.write("No repos configured.\n");
|
|
@@ -781,20 +957,28 @@ async function cleanupRepos(config, mode, force, io) {
|
|
|
781
957
|
let failures = 0;
|
|
782
958
|
for (const repo of repos) {
|
|
783
959
|
const repoPath = path.resolve(repo.localPath);
|
|
784
|
-
const worktreeRoot = path.join(path.dirname(repoPath), ".forge-worktrees", path.basename(repoPath));
|
|
785
|
-
if (!force) {
|
|
786
|
-
const action = mode === "all" ? "remove" : "prune";
|
|
787
|
-
io.stdout.write(`[dry-run] Would ${action} ${worktreeRoot}\n`);
|
|
788
|
-
continue;
|
|
789
|
-
}
|
|
790
960
|
try {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
961
|
+
const result = await cleanup.run({
|
|
962
|
+
repoRoot: repoPath,
|
|
963
|
+
allowedRepoRoot: config.defaultRepoRoot || undefined,
|
|
964
|
+
mode,
|
|
965
|
+
force,
|
|
966
|
+
});
|
|
967
|
+
if (!force) {
|
|
968
|
+
const action = mode === "all"
|
|
969
|
+
? "remove"
|
|
970
|
+
: mode === "stale"
|
|
971
|
+
? `remove ${result.candidateWorktrees} stale managed ${result.candidateWorktrees === 1 ? "worktree" : "worktrees"} from`
|
|
972
|
+
: "prune";
|
|
973
|
+
io.stdout.write(`[dry-run] Would ${action} ${result.worktreeRoot}\n`);
|
|
974
|
+
}
|
|
975
|
+
else if (mode === "all") {
|
|
976
|
+
io.stdout.write(`Removed ${result.worktreeRoot}\n`);
|
|
977
|
+
}
|
|
978
|
+
else if (mode === "stale") {
|
|
979
|
+
io.stdout.write(`Removed ${result.removedWorktrees} stale managed ${result.removedWorktrees === 1 ? "worktree" : "worktrees"} from ${result.worktreeRoot}\n`);
|
|
795
980
|
}
|
|
796
981
|
else {
|
|
797
|
-
await runGit(repoPath, ["worktree", "prune"]);
|
|
798
982
|
io.stdout.write(`Pruned stale worktree metadata for ${repoPath}\n`);
|
|
799
983
|
}
|
|
800
984
|
}
|
|
@@ -943,8 +1127,8 @@ function uniqueModels(models) {
|
|
|
943
1127
|
function isRecord(value) {
|
|
944
1128
|
return typeof value === "object" && value !== null;
|
|
945
1129
|
}
|
|
946
|
-
async function withDetectedCliProviders(config) {
|
|
947
|
-
const detected = await detectCliProviders(
|
|
1130
|
+
async function withDetectedCliProviders(config, candidates = config.cliProviders) {
|
|
1131
|
+
const detected = await detectCliProviders(uniqueProviders(candidates));
|
|
948
1132
|
const found = detected.filter((result) => result.found);
|
|
949
1133
|
if (found.length === 0) {
|
|
950
1134
|
return config;
|
|
@@ -968,6 +1152,7 @@ async function withDetectedCliProviders(config) {
|
|
|
968
1152
|
};
|
|
969
1153
|
}
|
|
970
1154
|
async function withDetectedPairCliProviders(args) {
|
|
1155
|
+
const configured = configuredProviderCandidates(args.cliProviders);
|
|
971
1156
|
const detectedConfig = await withDetectedCliProviders({
|
|
972
1157
|
...defaultBridgeServiceConfig({
|
|
973
1158
|
serverUrl: args.serverUrl,
|
|
@@ -977,7 +1162,7 @@ async function withDetectedPairCliProviders(args) {
|
|
|
977
1162
|
cleanupWorktrees: args.cleanupWorktrees,
|
|
978
1163
|
defaultRepoRoot: args.repoRoot ?? defaultBridgeServiceConfig().defaultRepoRoot
|
|
979
1164
|
})
|
|
980
|
-
});
|
|
1165
|
+
}, configured);
|
|
981
1166
|
return {
|
|
982
1167
|
...args,
|
|
983
1168
|
cliProviders: detectedConfig.cliProviders
|
|
@@ -992,6 +1177,3 @@ function uniqueProviders(providers) {
|
|
|
992
1177
|
.filter((provider) => provider.length > 0)
|
|
993
1178
|
.filter((provider, index, all) => all.findIndex((candidate) => candidate.toLowerCase() === provider.toLowerCase()) === index);
|
|
994
1179
|
}
|
|
995
|
-
async function runGit(cwd, args) {
|
|
996
|
-
await execFileAsync("git", args, { cwd, timeout: 10_000 });
|
|
997
|
-
}
|
|
@@ -7,6 +7,9 @@ export interface BridgeLocalConfig {
|
|
|
7
7
|
readonly deviceName?: string;
|
|
8
8
|
readonly defaultRepoRoot?: string;
|
|
9
9
|
readonly cleanupWorktrees?: boolean;
|
|
10
|
+
readonly dockerEnabled?: boolean;
|
|
11
|
+
readonly dockerDesktopRecoveryEnabled?: boolean;
|
|
12
|
+
readonly allowOpenPublicSandbox?: boolean;
|
|
10
13
|
readonly cliProviders?: readonly string[];
|
|
11
14
|
readonly providerStatuses?: Readonly<Record<string, BridgeProviderStatus>>;
|
|
12
15
|
readonly projectLabels?: readonly string[];
|
|
@@ -34,4 +37,6 @@ export declare function removeRepoMapping(label: string): Promise<{
|
|
|
34
37
|
readonly config: BridgeLocalConfig;
|
|
35
38
|
readonly removed: boolean;
|
|
36
39
|
}>;
|
|
40
|
+
export declare function setServerUrl(serverUrl: string): Promise<BridgeLocalConfig>;
|
|
41
|
+
export declare function setSandboxPolicy(dockerEnabled: boolean, allowOpenPublicSandbox: boolean): Promise<BridgeLocalConfig>;
|
|
37
42
|
export declare function providerStatusesFor(providers: readonly string[]): Readonly<Record<string, BridgeProviderStatus>>;
|
package/dist/cli/local-config.js
CHANGED
|
@@ -45,6 +45,15 @@ export function applyLocalConfigDefaults(config, local) {
|
|
|
45
45
|
deviceName: process.env.FORGE_DEVICE_NAME ?? local.deviceName ?? config.deviceName,
|
|
46
46
|
defaultRepoRoot: process.env.FORGE_REPO_ROOT ?? local.defaultRepoRoot ?? config.defaultRepoRoot,
|
|
47
47
|
cleanupWorktrees: local.cleanupWorktrees ?? config.cleanupWorktrees,
|
|
48
|
+
dockerEnabled: process.env.FORGE_DOCKER_SANDBOX === undefined
|
|
49
|
+
? local.dockerEnabled ?? config.dockerEnabled
|
|
50
|
+
: config.dockerEnabled,
|
|
51
|
+
dockerDesktopRecoveryEnabled: process.env.FORGE_DOCKER_DESKTOP_AUTO_RECOVERY === undefined
|
|
52
|
+
? local.dockerDesktopRecoveryEnabled ?? config.dockerDesktopRecoveryEnabled
|
|
53
|
+
: config.dockerDesktopRecoveryEnabled,
|
|
54
|
+
allowOpenPublicSandbox: process.env.FORGE_ALLOW_OPEN_PUBLIC_SANDBOX === undefined
|
|
55
|
+
? local.allowOpenPublicSandbox ?? config.allowOpenPublicSandbox
|
|
56
|
+
: config.allowOpenPublicSandbox,
|
|
48
57
|
cliProviders,
|
|
49
58
|
providerStatuses: normalizeProviderStatuses(local.providerStatuses) ?? providerStatusesFor(cliProviders),
|
|
50
59
|
projectLabels: local.projectLabels ?? config.projectLabels,
|
|
@@ -88,6 +97,19 @@ export async function removeRepoMapping(label) {
|
|
|
88
97
|
await writeLocalConfig(next);
|
|
89
98
|
return { config: next, removed: after.length !== before.length };
|
|
90
99
|
}
|
|
100
|
+
export async function setServerUrl(serverUrl) {
|
|
101
|
+
const normalized = normalizeServerUrl(serverUrl);
|
|
102
|
+
const current = await readLocalConfig();
|
|
103
|
+
const next = normalizeLocalConfig({ ...current, serverUrl: normalized });
|
|
104
|
+
await writeLocalConfig(next);
|
|
105
|
+
return next;
|
|
106
|
+
}
|
|
107
|
+
export async function setSandboxPolicy(dockerEnabled, allowOpenPublicSandbox) {
|
|
108
|
+
const current = await readLocalConfig();
|
|
109
|
+
const next = normalizeLocalConfig({ ...current, dockerEnabled, allowOpenPublicSandbox });
|
|
110
|
+
await writeLocalConfig(next);
|
|
111
|
+
return next;
|
|
112
|
+
}
|
|
91
113
|
export function providerStatusesFor(providers) {
|
|
92
114
|
return Object.fromEntries(providers.map((provider) => [provider, { signedIn: true, version: "", models: configuredModelsFor(provider) }]));
|
|
93
115
|
}
|
|
@@ -106,6 +128,9 @@ function normalizeLocalConfig(config) {
|
|
|
106
128
|
deviceName: cleanString(config.deviceName),
|
|
107
129
|
defaultRepoRoot: cleanString(config.defaultRepoRoot),
|
|
108
130
|
cleanupWorktrees: config.cleanupWorktrees,
|
|
131
|
+
dockerEnabled: config.dockerEnabled,
|
|
132
|
+
dockerDesktopRecoveryEnabled: config.dockerDesktopRecoveryEnabled,
|
|
133
|
+
allowOpenPublicSandbox: config.allowOpenPublicSandbox,
|
|
109
134
|
cliProviders: cleanList(config.cliProviders),
|
|
110
135
|
providerStatuses: normalizeProviderStatuses(config.providerStatuses),
|
|
111
136
|
projectLabels: cleanList(config.projectLabels),
|
|
@@ -200,10 +225,11 @@ function normalizeQaAppLaunchTarget(value) {
|
|
|
200
225
|
command: cleanString(value.command),
|
|
201
226
|
working_directory: cleanString(value.working_directory),
|
|
202
227
|
ready_url: cleanString(value.ready_url),
|
|
228
|
+
managed_runtime_port: cleanString(value.managed_runtime_port),
|
|
203
229
|
routes,
|
|
204
230
|
notes: cleanString(value.notes)
|
|
205
231
|
};
|
|
206
|
-
return normalized.url || normalized.command || normalized.ready_url ||
|
|
232
|
+
return normalized.url || normalized.command || normalized.ready_url || normalized.managed_runtime_port
|
|
207
233
|
? normalized
|
|
208
234
|
: undefined;
|
|
209
235
|
}
|
|
@@ -211,6 +237,24 @@ function cleanString(value) {
|
|
|
211
237
|
const clean = value?.trim();
|
|
212
238
|
return clean ? clean : undefined;
|
|
213
239
|
}
|
|
240
|
+
function normalizeServerUrl(value) {
|
|
241
|
+
const clean = value.trim();
|
|
242
|
+
let parsed;
|
|
243
|
+
try {
|
|
244
|
+
parsed = new URL(clean);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
throw new Error(`Invalid Forge server URL: ${value}`);
|
|
248
|
+
}
|
|
249
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
250
|
+
throw new Error("Forge server URL must use http or https.");
|
|
251
|
+
}
|
|
252
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
253
|
+
throw new Error("Forge server URL cannot include credentials, a query string, or a fragment.");
|
|
254
|
+
}
|
|
255
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/u, "") || "/";
|
|
256
|
+
return parsed.toString().replace(/\/$/u, "");
|
|
257
|
+
}
|
|
214
258
|
function cleanList(values) {
|
|
215
259
|
const clean = [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
|
|
216
260
|
return clean.length > 0 ? clean : undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface AspNetRuntimePrebuildPlan {
|
|
2
|
+
readonly fingerprint: string;
|
|
3
|
+
readonly receiptPath: string;
|
|
4
|
+
readonly command: string;
|
|
5
|
+
readonly args: readonly string[];
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
8
|
+
readonly projectPath: string;
|
|
9
|
+
readonly worktreePath: string;
|
|
10
|
+
readonly inputCommitSha: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AspNetRuntimePrebuildResult {
|
|
13
|
+
readonly cacheHit: boolean;
|
|
14
|
+
readonly receiptPath: string;
|
|
15
|
+
readonly fingerprint: string;
|
|
16
|
+
}
|
|
17
|
+
export interface AspNetRuntimeBuildExecutionResult {
|
|
18
|
+
readonly exitCode: number;
|
|
19
|
+
readonly stdoutTail: string;
|
|
20
|
+
readonly stderrTail: string;
|
|
21
|
+
readonly stdoutLogPath: string;
|
|
22
|
+
readonly stderrLogPath: string;
|
|
23
|
+
}
|
|
24
|
+
export type AspNetRuntimeBuildExecutor = (plan: AspNetRuntimePrebuildPlan, signal?: AbortSignal) => Promise<AspNetRuntimeBuildExecutionResult>;
|
|
25
|
+
export declare function createAspNetRuntimePrebuildPlan(options: {
|
|
26
|
+
readonly command: string;
|
|
27
|
+
readonly args: readonly string[];
|
|
28
|
+
readonly cwd: string;
|
|
29
|
+
readonly worktreePath: string;
|
|
30
|
+
readonly environment?: Readonly<Record<string, string>>;
|
|
31
|
+
readonly inputCommitSha: string;
|
|
32
|
+
readonly runtimeContractFingerprint: string;
|
|
33
|
+
}): AspNetRuntimePrebuildPlan | null;
|
|
34
|
+
export declare function ensureAspNetRuntimePrebuilt(plan: AspNetRuntimePrebuildPlan, execute: AspNetRuntimeBuildExecutor, signal?: AbortSignal): Promise<AspNetRuntimePrebuildResult>;
|
|
35
|
+
export declare function withAspNetPrebuiltRunArguments(command: string, args: readonly string[]): readonly string[];
|
|
36
|
+
export declare function resolveWorktreeCommitSha(worktreePath: string, supplied?: string | null): Promise<string>;
|