@loucompanion/forge-bridge 0.1.3-dev.d1ca67076aa9 → 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.
- package/README.md +44 -0
- package/dist/bridge/bridge.base.d.ts +5 -1
- package/dist/bridge/bridge.service.d.ts +40 -2
- package/dist/bridge/bridge.service.js +580 -16
- package/dist/bridge/bridge.types.d.ts +114 -1
- package/dist/cli/cli.help.js +12 -0
- package/dist/cli/cli.service.d.ts +12 -0
- package/dist/cli/cli.service.js +171 -1
- package/dist/cli/local-config.d.ts +2 -0
- package/dist/cli/local-config.js +6 -2
- package/dist/sandbox/command-runner.d.ts +4 -0
- package/dist/sandbox/command-runner.js +47 -0
- package/dist/sandbox/contract.d.ts +5 -0
- package/dist/sandbox/contract.js +276 -0
- package/dist/sandbox/event-budget.d.ts +11 -0
- package/dist/sandbox/event-budget.js +27 -0
- package/dist/sandbox/image-policy.d.ts +35 -0
- package/dist/sandbox/image-policy.js +247 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/lease-watchdog.d.ts +18 -0
- package/dist/sandbox/lease-watchdog.js +67 -0
- package/dist/sandbox/preflight.d.ts +40 -0
- package/dist/sandbox/preflight.js +256 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +124 -0
- package/dist/sandbox/provider-auth/codex.d.ts +26 -0
- package/dist/sandbox/provider-auth/codex.js +212 -0
- package/dist/sandbox/repository-lock.d.ts +5 -0
- package/dist/sandbox/repository-lock.js +83 -0
- package/dist/sandbox/runtime.d.ts +234 -0
- package/dist/sandbox/runtime.js +1773 -0
- package/dist/sandbox/sandbox.types.d.ts +83 -0
- package/dist/sandbox/sandbox.types.js +1 -0
- package/dist/sandbox/worker-supervisor.d.ts +13 -0
- package/dist/sandbox/worker-supervisor.js +36 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +228 -0
- package/dist/sandbox/workspace.service.d.ts +12 -0
- package/dist/sandbox/workspace.service.js +49 -0
- package/dist/session/phase-completion.artifacts.js +23 -2
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +20 -9
- package/dist/session/provider-cli/provider-cli.process.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.process.js +23 -1
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +12 -1
- package/dist/session/provider-cli/provider-cli.prompts.js +119 -2
- package/dist/session/provider-cli/provider-cli.service.d.ts +4 -1
- package/dist/session/provider-cli/provider-cli.service.js +161 -43
- package/dist/session/provider-cli/provider-cli.types.d.ts +1 -0
- package/dist/session/session.service.js +34 -24
- package/dist/session/session.types.d.ts +52 -1
- package/dist/session/session.types.js +159 -3
- package/dist/shared/process/windows-launch.d.ts +1 -1
- package/dist/shared/process/windows-launch.js +6 -3
- package/dist/shared/provider-capabilities.d.ts +1 -0
- package/dist/shared/provider-capabilities.js +3 -0
- package/dist/shared/redaction/redaction.js +4 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/dependency-warmer.d.ts +11 -0
- package/dist/worktree/dependency-warmer.js +45 -1
- package/package.json +4 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface RunOnceDispatchPacket {
|
|
2
|
-
readonly schemaVersion: 1;
|
|
2
|
+
readonly schemaVersion: 1 | 2;
|
|
3
3
|
readonly userId: string;
|
|
4
4
|
readonly workItem: DispatchWorkItem;
|
|
5
5
|
readonly repo: DispatchRepo;
|
|
@@ -8,8 +8,10 @@ export interface RunOnceDispatchPacket {
|
|
|
8
8
|
readonly lease: DispatchLease;
|
|
9
9
|
readonly execution: DispatchExecution;
|
|
10
10
|
readonly serverLocal: DispatchServerLocalConfig;
|
|
11
|
+
readonly sandbox?: DispatchSandboxContext | null;
|
|
11
12
|
readonly pullRequest?: DispatchPullRequestContext | null;
|
|
12
13
|
readonly apiKeySecretGrant?: ApiKeySecretGrant | null;
|
|
14
|
+
readonly apiKeySecretGrantHandle?: ApiKeySecretGrantHandle | null;
|
|
13
15
|
readonly artifacts: readonly DispatchResolvedArtifact[];
|
|
14
16
|
readonly warmPathOverrides?: readonly string[];
|
|
15
17
|
readonly qaBrowser?: DispatchQaBrowserContext | null;
|
|
@@ -22,12 +24,56 @@ export interface RunOnceDispatchPacket {
|
|
|
22
24
|
readonly designerRefs?: DispatchDesignerRefHandoffContext | null;
|
|
23
25
|
readonly draft?: DispatchDraftPrompt | null;
|
|
24
26
|
}
|
|
27
|
+
export interface DispatchSandboxContext {
|
|
28
|
+
readonly dispatchAttemptId: string;
|
|
29
|
+
readonly executionLeaseId: string;
|
|
30
|
+
readonly runnerInstanceId: string;
|
|
31
|
+
readonly executionMode: "host" | "docker";
|
|
32
|
+
readonly executionPolicy: "host_allowed" | "sandbox_preferred" | "sandbox_required";
|
|
33
|
+
readonly networkPolicy: DispatchSandboxNetworkPolicy;
|
|
34
|
+
readonly requestedResources: DispatchSandboxResourceRequest;
|
|
35
|
+
readonly runtime?: string | null;
|
|
36
|
+
readonly sandboxConfigCommitSha?: string | null;
|
|
37
|
+
readonly inputCommitSha?: string | null;
|
|
38
|
+
readonly contractDigest?: string | null;
|
|
39
|
+
readonly imageDigest?: string | null;
|
|
40
|
+
readonly publicationMode: "bridge_push" | "shared_filesystem";
|
|
41
|
+
readonly publishGrantHandle?: DispatchPublishGrantHandle | null;
|
|
42
|
+
}
|
|
43
|
+
export interface DispatchPublishGrantHandle {
|
|
44
|
+
readonly grantId: string;
|
|
45
|
+
readonly branch: string;
|
|
46
|
+
readonly expiresAt: string;
|
|
47
|
+
}
|
|
48
|
+
export interface DispatchSandboxResourceRequest {
|
|
49
|
+
readonly sandboxSlots: number;
|
|
50
|
+
readonly cpuMillis: number;
|
|
51
|
+
readonly memoryBytes: number;
|
|
52
|
+
readonly workspaceBytes: number;
|
|
53
|
+
readonly pids: number;
|
|
54
|
+
readonly buildSlots: number;
|
|
55
|
+
}
|
|
56
|
+
export interface DispatchSandboxNetworkPolicy {
|
|
57
|
+
readonly mode: "offline" | "public_allowlist" | "open_public";
|
|
58
|
+
readonly allowedHosts: readonly string[];
|
|
59
|
+
}
|
|
25
60
|
export interface DispatchDraftPrompt {
|
|
26
61
|
readonly kind: string;
|
|
27
62
|
readonly systemPrompt: string;
|
|
28
63
|
readonly userPrompt: string;
|
|
29
64
|
readonly projectName?: string | null;
|
|
30
65
|
readonly repoName?: string | null;
|
|
66
|
+
readonly attachments?: readonly DispatchDraftAttachment[];
|
|
67
|
+
}
|
|
68
|
+
export interface DispatchDraftAttachment {
|
|
69
|
+
readonly index: number;
|
|
70
|
+
readonly fileName: string;
|
|
71
|
+
readonly contentType: string;
|
|
72
|
+
readonly kind: string;
|
|
73
|
+
readonly byteCount: number;
|
|
74
|
+
readonly summary: string;
|
|
75
|
+
readonly textContent?: string | null;
|
|
76
|
+
readonly contentBase64?: string | null;
|
|
31
77
|
}
|
|
32
78
|
export interface DispatchQaBrowserContext {
|
|
33
79
|
readonly required: boolean;
|
|
@@ -268,6 +314,11 @@ export interface ApiKeySecretGrant {
|
|
|
268
314
|
readonly secret: string;
|
|
269
315
|
readonly expiresAt: string;
|
|
270
316
|
}
|
|
317
|
+
export interface ApiKeySecretGrantHandle {
|
|
318
|
+
readonly grantId: string;
|
|
319
|
+
readonly provider: string;
|
|
320
|
+
readonly expiresAt: string;
|
|
321
|
+
}
|
|
271
322
|
export interface DispatchServerLocalConfig {
|
|
272
323
|
readonly repoRoot: string;
|
|
273
324
|
readonly cleanupWorktrees: boolean;
|
|
@@ -2,11 +2,11 @@ import path from "node:path";
|
|
|
2
2
|
export function parseRunOnceDispatchPacket(value) {
|
|
3
3
|
const packet = asObject(value, "dispatch packet");
|
|
4
4
|
const schemaVersion = requiredNumber(packet, "schemaVersion");
|
|
5
|
-
if (schemaVersion !== 1) {
|
|
5
|
+
if (schemaVersion !== 1 && schemaVersion !== 2) {
|
|
6
6
|
throw new Error(`Unsupported dispatch schemaVersion: ${schemaVersion}`);
|
|
7
7
|
}
|
|
8
8
|
return {
|
|
9
|
-
schemaVersion
|
|
9
|
+
schemaVersion,
|
|
10
10
|
userId: requiredString(packet, "userId"),
|
|
11
11
|
workItem: parseWorkItem(packet.workItem),
|
|
12
12
|
repo: parseRepo(packet.repo),
|
|
@@ -15,8 +15,10 @@ export function parseRunOnceDispatchPacket(value) {
|
|
|
15
15
|
lease: parseLease(packet.lease),
|
|
16
16
|
execution: parseExecution(packet.execution),
|
|
17
17
|
serverLocal: parseServerLocal(packet.serverLocal),
|
|
18
|
+
sandbox: parseSandbox(packet.sandbox, schemaVersion),
|
|
18
19
|
pullRequest: parsePullRequest(packet.pullRequest),
|
|
19
20
|
apiKeySecretGrant: parseApiKeySecretGrant(packet.apiKeySecretGrant),
|
|
21
|
+
apiKeySecretGrantHandle: parseApiKeySecretGrantHandle(packet.apiKeySecretGrantHandle),
|
|
20
22
|
artifacts: parseArtifacts(packet.artifacts),
|
|
21
23
|
warmPathOverrides: parseWarmPathOverrides(packet.warmPathOverrides),
|
|
22
24
|
qaBrowser: parseQaBrowser(packet.qaBrowser),
|
|
@@ -30,6 +32,107 @@ export function parseRunOnceDispatchPacket(value) {
|
|
|
30
32
|
draft: parseDraftPrompt(packet.draft)
|
|
31
33
|
};
|
|
32
34
|
}
|
|
35
|
+
function parseSandbox(value, schemaVersion) {
|
|
36
|
+
if (value === undefined || value === null) {
|
|
37
|
+
if (schemaVersion === 2) {
|
|
38
|
+
throw new Error("sandbox is required for dispatch schemaVersion 2.");
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const sandbox = asObject(value, "sandbox");
|
|
43
|
+
const executionMode = requiredString(sandbox, "executionMode");
|
|
44
|
+
if (executionMode !== "host" && executionMode !== "docker") {
|
|
45
|
+
throw new Error("sandbox.executionMode must be host or docker.");
|
|
46
|
+
}
|
|
47
|
+
const executionPolicy = requiredString(sandbox, "executionPolicy");
|
|
48
|
+
if (executionPolicy !== "host_allowed" && executionPolicy !== "sandbox_preferred" && executionPolicy !== "sandbox_required") {
|
|
49
|
+
throw new Error("sandbox.executionPolicy is invalid.");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
dispatchAttemptId: requiredString(sandbox, "dispatchAttemptId"),
|
|
53
|
+
executionLeaseId: requiredString(sandbox, "executionLeaseId"),
|
|
54
|
+
runnerInstanceId: requiredString(sandbox, "runnerInstanceId"),
|
|
55
|
+
executionMode,
|
|
56
|
+
executionPolicy,
|
|
57
|
+
networkPolicy: parseSandboxNetworkPolicy(sandbox.networkPolicy),
|
|
58
|
+
requestedResources: parseSandboxResourceRequest(sandbox.requestedResources),
|
|
59
|
+
runtime: optionalString(sandbox, "runtime"),
|
|
60
|
+
sandboxConfigCommitSha: optionalString(sandbox, "sandboxConfigCommitSha"),
|
|
61
|
+
inputCommitSha: optionalString(sandbox, "inputCommitSha"),
|
|
62
|
+
contractDigest: optionalString(sandbox, "contractDigest"),
|
|
63
|
+
imageDigest: optionalString(sandbox, "imageDigest"),
|
|
64
|
+
publicationMode: parsePublicationMode(sandbox.publicationMode),
|
|
65
|
+
publishGrantHandle: parsePublishGrantHandle(sandbox.publishGrantHandle)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function parsePublicationMode(value) {
|
|
69
|
+
if (value === undefined || value === null)
|
|
70
|
+
return "shared_filesystem";
|
|
71
|
+
if (value !== "bridge_push" && value !== "shared_filesystem")
|
|
72
|
+
throw new Error("sandbox.publicationMode is invalid.");
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
function parsePublishGrantHandle(value) {
|
|
76
|
+
if (value === undefined || value === null)
|
|
77
|
+
return null;
|
|
78
|
+
const handle = asObject(value, "sandbox.publishGrantHandle");
|
|
79
|
+
const grantId = requiredString(handle, "grantId");
|
|
80
|
+
const branch = requiredString(handle, "branch");
|
|
81
|
+
const expiresAt = requiredString(handle, "expiresAt");
|
|
82
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(grantId)) {
|
|
83
|
+
throw new Error("sandbox.publishGrantHandle.grantId is invalid.");
|
|
84
|
+
}
|
|
85
|
+
if (branch.length > 240)
|
|
86
|
+
throw new Error("sandbox.publishGrantHandle.branch is too long.");
|
|
87
|
+
if (!Number.isFinite(Date.parse(expiresAt)))
|
|
88
|
+
throw new Error("sandbox.publishGrantHandle.expiresAt is invalid.");
|
|
89
|
+
return { grantId, branch, expiresAt };
|
|
90
|
+
}
|
|
91
|
+
function parseSandboxResourceRequest(value) {
|
|
92
|
+
if (value === undefined || value === null) {
|
|
93
|
+
return {
|
|
94
|
+
sandboxSlots: 1,
|
|
95
|
+
cpuMillis: 2000,
|
|
96
|
+
memoryBytes: 2 * 1024 ** 3,
|
|
97
|
+
workspaceBytes: 10 * 1024 ** 3,
|
|
98
|
+
pids: 512,
|
|
99
|
+
buildSlots: 1
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const resources = asObject(value, "sandbox.requestedResources");
|
|
103
|
+
return {
|
|
104
|
+
sandboxSlots: boundedInteger(resources, "sandboxSlots", 1, 4),
|
|
105
|
+
cpuMillis: boundedInteger(resources, "cpuMillis", 100, 64_000),
|
|
106
|
+
memoryBytes: boundedInteger(resources, "memoryBytes", 128 * 1024 ** 2, 512 * 1024 ** 3),
|
|
107
|
+
workspaceBytes: boundedInteger(resources, "workspaceBytes", 1024 ** 3, 10 * 1024 ** 4),
|
|
108
|
+
pids: boundedInteger(resources, "pids", 32, 65_536),
|
|
109
|
+
buildSlots: boundedInteger(resources, "buildSlots", 0, 4)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function parseSandboxNetworkPolicy(value) {
|
|
113
|
+
if (value === undefined || value === null)
|
|
114
|
+
return { mode: "offline", allowedHosts: [] };
|
|
115
|
+
const policy = asObject(value, "sandbox.networkPolicy");
|
|
116
|
+
const mode = requiredString(policy, "mode");
|
|
117
|
+
if (mode !== "offline" && mode !== "public_allowlist" && mode !== "open_public") {
|
|
118
|
+
throw new Error("sandbox.networkPolicy.mode is invalid.");
|
|
119
|
+
}
|
|
120
|
+
const rawHosts = policy.allowedHosts ?? [];
|
|
121
|
+
if (!Array.isArray(rawHosts) || rawHosts.some((host) => typeof host !== "string")) {
|
|
122
|
+
throw new Error("sandbox.networkPolicy.allowedHosts must be a string array.");
|
|
123
|
+
}
|
|
124
|
+
const allowedHosts = [...new Set(rawHosts.map(host => host.trim().toLowerCase()).filter(Boolean))];
|
|
125
|
+
if (allowedHosts.length > 128 || allowedHosts.some(host => !/^(?:\*\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(host))) {
|
|
126
|
+
throw new Error("sandbox.networkPolicy.allowedHosts contains an invalid hostname.");
|
|
127
|
+
}
|
|
128
|
+
if (mode !== "public_allowlist" && allowedHosts.length > 0) {
|
|
129
|
+
throw new Error("sandbox.networkPolicy.allowedHosts requires public_allowlist mode.");
|
|
130
|
+
}
|
|
131
|
+
if (mode === "public_allowlist" && allowedHosts.length === 0) {
|
|
132
|
+
return { mode: "offline", allowedHosts: [] };
|
|
133
|
+
}
|
|
134
|
+
return { mode, allowedHosts };
|
|
135
|
+
}
|
|
33
136
|
export function failureResult(summary, events = []) {
|
|
34
137
|
return {
|
|
35
138
|
schemaVersion: 1,
|
|
@@ -121,9 +224,45 @@ function parseDraftPrompt(value) {
|
|
|
121
224
|
return {
|
|
122
225
|
kind: requiredString(draft, "kind"),
|
|
123
226
|
systemPrompt: requiredString(draft, "systemPrompt"),
|
|
124
|
-
userPrompt: requiredString(draft, "userPrompt")
|
|
227
|
+
userPrompt: requiredString(draft, "userPrompt"),
|
|
228
|
+
projectName: optionalString(draft, "projectName"),
|
|
229
|
+
repoName: optionalString(draft, "repoName"),
|
|
230
|
+
attachments: parseDraftAttachments(draft.attachments)
|
|
125
231
|
};
|
|
126
232
|
}
|
|
233
|
+
function parseDraftAttachments(value) {
|
|
234
|
+
if (value === undefined || value === null) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
if (!Array.isArray(value)) {
|
|
238
|
+
throw new Error("draft.attachments must be an array when provided.");
|
|
239
|
+
}
|
|
240
|
+
return value.map((entry, index) => {
|
|
241
|
+
const attachment = asObject(entry, `draft.attachments[${index}]`);
|
|
242
|
+
const kind = requiredString(attachment, "kind").trim().toLowerCase();
|
|
243
|
+
if (kind !== "text" && kind !== "image") {
|
|
244
|
+
throw new Error(`draft.attachments[${index}].kind must be text or image.`);
|
|
245
|
+
}
|
|
246
|
+
const textContent = optionalString(attachment, "textContent");
|
|
247
|
+
const contentBase64 = optionalString(attachment, "contentBase64");
|
|
248
|
+
if (kind === "text" && textContent === null) {
|
|
249
|
+
throw new Error(`draft.attachments[${index}].textContent is required for text attachments.`);
|
|
250
|
+
}
|
|
251
|
+
if (kind === "image" && contentBase64 === null) {
|
|
252
|
+
throw new Error(`draft.attachments[${index}].contentBase64 is required for image attachments.`);
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
index: optionalNonNegativeInteger(attachment, "index") ?? index,
|
|
256
|
+
fileName: requiredString(attachment, "fileName"),
|
|
257
|
+
contentType: optionalString(attachment, "contentType") ?? "",
|
|
258
|
+
kind,
|
|
259
|
+
byteCount: optionalNonNegativeInteger(attachment, "byteCount") ?? 0,
|
|
260
|
+
summary: optionalString(attachment, "summary") ?? "",
|
|
261
|
+
textContent,
|
|
262
|
+
contentBase64
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
}
|
|
127
266
|
function parseApiKeySecretGrant(value) {
|
|
128
267
|
if (value === undefined || value === null) {
|
|
129
268
|
return null;
|
|
@@ -136,6 +275,16 @@ function parseApiKeySecretGrant(value) {
|
|
|
136
275
|
expiresAt: requiredString(grant, "expiresAt")
|
|
137
276
|
};
|
|
138
277
|
}
|
|
278
|
+
function parseApiKeySecretGrantHandle(value) {
|
|
279
|
+
if (value === undefined || value === null)
|
|
280
|
+
return null;
|
|
281
|
+
const grant = asObject(value, "apiKeySecretGrantHandle");
|
|
282
|
+
return {
|
|
283
|
+
grantId: requiredString(grant, "grantId"),
|
|
284
|
+
provider: requiredString(grant, "provider"),
|
|
285
|
+
expiresAt: requiredString(grant, "expiresAt")
|
|
286
|
+
};
|
|
287
|
+
}
|
|
139
288
|
function parseServerLocal(value) {
|
|
140
289
|
const serverLocal = asObject(value, "serverLocal");
|
|
141
290
|
return {
|
|
@@ -580,3 +729,10 @@ function requiredNumber(source, key) {
|
|
|
580
729
|
}
|
|
581
730
|
return value;
|
|
582
731
|
}
|
|
732
|
+
function boundedInteger(source, key, minimum, maximum) {
|
|
733
|
+
const value = requiredNumber(source, key);
|
|
734
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
735
|
+
throw new Error(`${key} must be an integer from ${minimum} through ${maximum}.`);
|
|
736
|
+
}
|
|
737
|
+
return value;
|
|
738
|
+
}
|
|
@@ -2,7 +2,7 @@ export interface LaunchCommand {
|
|
|
2
2
|
readonly command: string;
|
|
3
3
|
readonly args: readonly string[];
|
|
4
4
|
}
|
|
5
|
-
export declare function buildWindowsCmdLaunch(command: string, args: readonly string[]): LaunchCommand;
|
|
5
|
+
export declare function buildWindowsCmdLaunch(command: string, args: readonly string[], comSpec?: string | undefined): LaunchCommand;
|
|
6
6
|
export declare function buildWindowsLaunchCommand(commandPath: string, args: readonly string[]): LaunchCommand;
|
|
7
7
|
export declare function windowsCommandLine(command: string, args: readonly string[]): string;
|
|
8
8
|
export declare function quoteWindowsCommandLineArg(value: string): string;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
export function buildWindowsCmdLaunch(command, args) {
|
|
2
|
+
export function buildWindowsCmdLaunch(command, args, comSpec = process.env.ComSpec) {
|
|
3
|
+
const resolvedComSpec = comSpec?.trim();
|
|
4
|
+
const commandLine = windowsCommandLine(command, args);
|
|
5
|
+
const cmdPayload = commandLine.startsWith('"') ? `"${commandLine}"` : commandLine;
|
|
3
6
|
return {
|
|
4
|
-
command:
|
|
5
|
-
args: ["/d", "/s", "/v:off", "/c",
|
|
7
|
+
command: resolvedComSpec || "cmd.exe",
|
|
8
|
+
args: ["/d", "/s", "/v:off", "/c", cmdPayload]
|
|
6
9
|
};
|
|
7
10
|
}
|
|
8
11
|
export function buildWindowsLaunchCommand(commandPath, args) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function supportsDraftImageAttachmentsFor(cliProviders: readonly string[]): boolean;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const secretKeyPattern = /(api[_-]?key|authorization|bearer|client[_-]?secret|password|secret|token)/i;
|
|
2
|
+
const attachmentContentKeyPattern = /^(textContent|contentBase64)$/i;
|
|
3
|
+
const serializedAttachmentContentPattern = /("(?:textContent|contentBase64)"\s*:\s*")(?:(?:\\.)|[^"\\])*(")/gi;
|
|
2
4
|
export function redactSecrets(value, exactSecrets = []) {
|
|
3
5
|
return redactValue(value, exactSecrets);
|
|
4
6
|
}
|
|
5
7
|
export function redactText(value, exactSecrets = []) {
|
|
6
|
-
let redacted = value;
|
|
8
|
+
let redacted = value.replace(serializedAttachmentContentPattern, "$1[REDACTED]$2");
|
|
7
9
|
for (const secret of exactSecrets) {
|
|
8
10
|
if (secret.length > 0) {
|
|
9
11
|
redacted = redacted.split(secret).join("[REDACTED]");
|
|
@@ -25,6 +27,6 @@ function redactValue(value, exactSecrets) {
|
|
|
25
27
|
}
|
|
26
28
|
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
27
29
|
key,
|
|
28
|
-
secretKeyPattern.test(key) ? "[REDACTED]" : redactValue(item, exactSecrets)
|
|
30
|
+
secretKeyPattern.test(key) || attachmentContentKeyPattern.test(key) ? "[REDACTED]" : redactValue(item, exactSecrets)
|
|
29
31
|
]));
|
|
30
32
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const forgeBridgeVersion = "0.1.3-dev.
|
|
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.
|
|
1
|
+
export const forgeBridgeVersion = "0.1.3-dev.d46a8e73d03d";
|
|
@@ -30,3 +30,14 @@ export declare function createDirectoryLink(sourcePath: string, targetPath: stri
|
|
|
30
30
|
export declare function lockfilesDiffer(baseCheckoutPath: string, worktreePath: string, packageRootRelative: string): Promise<boolean>;
|
|
31
31
|
export declare function requiresInstallFallback(entry: WarmPathEntry, baseCheckoutPath: string, worktreePath: string, sourceExists: boolean): Promise<string | null>;
|
|
32
32
|
export declare function declaresNativeModules(packageRootPath: string): Promise<boolean>;
|
|
33
|
+
export interface NpmLaunchOptions {
|
|
34
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
35
|
+
readonly execPath?: string;
|
|
36
|
+
readonly platform?: NodeJS.Platform;
|
|
37
|
+
readonly homeDir?: string;
|
|
38
|
+
readonly candidates?: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
export declare function resolveNpmLaunch(args: readonly string[], options?: NpmLaunchOptions): {
|
|
41
|
+
command: string;
|
|
42
|
+
args: readonly string[];
|
|
43
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { NodeProcessRunner } from "../shared/process/process.js";
|
|
6
|
+
import { buildWindowsCmdLaunch } from "../shared/process/windows-launch.js";
|
|
4
7
|
import { isSubPath } from "../shared/path/path.js";
|
|
5
8
|
const SKIP_DIR_NAMES = new Set([
|
|
6
9
|
".git",
|
|
@@ -184,7 +187,8 @@ async function runInstallFallback(entry, worktreePath, reason, executor) {
|
|
|
184
187
|
if (entry.ecosystem === "node") {
|
|
185
188
|
const lockPath = path.join(cwd, "package-lock.json");
|
|
186
189
|
const args = (await pathExists(lockPath)) ? ["ci"] : ["install"];
|
|
187
|
-
|
|
190
|
+
const launch = resolveNpmLaunch(args);
|
|
191
|
+
await executor.run(cwd, launch.command, launch.args);
|
|
188
192
|
return { ecosystem: entry.ecosystem, packageRoot: entry.packageRoot, reason, command: `npm ${args.join(" ")}` };
|
|
189
193
|
}
|
|
190
194
|
if (entry.ecosystem === "python") {
|
|
@@ -286,6 +290,46 @@ async function sha256File(filePath) {
|
|
|
286
290
|
const content = await fs.readFile(filePath);
|
|
287
291
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
288
292
|
}
|
|
293
|
+
export function resolveNpmLaunch(args, options = {}) {
|
|
294
|
+
const env = options.env ?? process.env;
|
|
295
|
+
const execPath = options.execPath ?? process.execPath;
|
|
296
|
+
const npmExecPath = env.npm_execpath;
|
|
297
|
+
if (npmExecPath) {
|
|
298
|
+
return { command: execPath, args: [npmExecPath, ...args] };
|
|
299
|
+
}
|
|
300
|
+
const platform = options.platform ?? process.platform;
|
|
301
|
+
if (platform === "win32") {
|
|
302
|
+
const comSpec = options.env ? env.ComSpec ?? "cmd.exe" : env.ComSpec;
|
|
303
|
+
return buildWindowsCmdLaunch("npm.cmd", args, comSpec);
|
|
304
|
+
}
|
|
305
|
+
for (const candidate of options.candidates ?? defaultNpmCliCandidates(execPath, options.homeDir ?? os.homedir())) {
|
|
306
|
+
const resolvedCandidate = tryResolveNpmCandidate(candidate);
|
|
307
|
+
if (resolvedCandidate) {
|
|
308
|
+
return { command: execPath, args: [resolvedCandidate, ...args] };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { command: "npm", args };
|
|
312
|
+
}
|
|
313
|
+
function tryResolveNpmCandidate(candidate) {
|
|
314
|
+
try {
|
|
315
|
+
if (!existsSync(candidate)) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
return realpathSync(candidate);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function defaultNpmCliCandidates(execPath, homeDir) {
|
|
325
|
+
const candidates = [];
|
|
326
|
+
const execDir = path.dirname(execPath);
|
|
327
|
+
if (path.isAbsolute(execDir)) {
|
|
328
|
+
candidates.push(path.join(execDir, "npm"), path.join(execDir, "npm-cli.js"), path.join(execDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"));
|
|
329
|
+
}
|
|
330
|
+
candidates.push("/usr/bin/npm", "/usr/local/bin/npm", path.join(homeDir, ".npm-global", "bin", "npm"), path.join(homeDir, ".npm-global", "lib", "node_modules", "npm", "bin", "npm-cli.js"));
|
|
331
|
+
return candidates;
|
|
332
|
+
}
|
|
289
333
|
const defaultCommandExecutor = {
|
|
290
334
|
async run(cwd, command, args) {
|
|
291
335
|
const result = await defaultProcessRunner.run({ command, cwd, args });
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loucompanion/forge-bridge",
|
|
3
|
-
"version": "0.1.3-dev.
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
|
+
"forge": "dist/cli/bin.js",
|
|
8
9
|
"forge-bridge": "dist/cli/bin.js"
|
|
9
10
|
},
|
|
10
11
|
"main": "./dist/index.js",
|
|
@@ -43,7 +44,8 @@
|
|
|
43
44
|
"lint": "eslint \"src/**/*.ts\" \"scripts/**/*.mjs\" \"vitest.config.ts\""
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@microsoft/signalr": "^8.0.17"
|
|
47
|
+
"@microsoft/signalr": "^8.0.17",
|
|
48
|
+
"yaml": "^2.9.0"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"@eslint/js": "^10.0.1",
|