toolcraft 0.0.23 → 0.0.24
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 +2 -2
- package/dist/cli.compile-check.js +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +50 -13
- package/dist/error-report.js +32 -3
- package/dist/human-in-loop/approval-tasks.d.ts +1 -0
- package/dist/human-in-loop/approval-tasks.js +7 -5
- package/dist/human-in-loop/approvals-commands.js +51 -8
- package/dist/human-in-loop/runner.js +24 -19
- package/dist/human-in-loop/state-machine.d.ts +3 -3
- package/dist/human-in-loop/state-machine.js +13 -5
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -1
- package/dist/mcp-proxy.js +85 -19
- package/dist/mcp.compile-check.js +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +50 -8
- package/dist/renderer.js +119 -13
- package/dist/sdk.compile-check.js +1 -0
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.js +56 -11
- package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +1 -1
- package/node_modules/@poe-code/agent-defs/dist/registry.js +22 -11
- package/node_modules/@poe-code/agent-defs/package.json +1 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +5 -1
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +1 -1
- package/node_modules/@poe-code/agent-human-in-loop/package.json +1 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +1 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +41 -92
- package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +4 -1
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +14 -2
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +11 -4
- package/node_modules/@poe-code/agent-mcp-config/package.json +1 -1
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +200 -22
- package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +7 -1
- package/node_modules/@poe-code/config-mutations/dist/formats/index.js +1 -1
- package/node_modules/@poe-code/config-mutations/dist/formats/json.js +11 -7
- package/node_modules/@poe-code/config-mutations/dist/formats/object.d.ts +4 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/object.js +27 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +12 -9
- package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +12 -9
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +11 -1
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +10 -1
- package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +25 -1
- package/node_modules/@poe-code/config-mutations/dist/types.d.ts +12 -2
- package/node_modules/@poe-code/config-mutations/package.json +1 -1
- package/node_modules/@poe-code/design-system/dist/acp/components.js +3 -1
- package/node_modules/@poe-code/design-system/dist/components/browser.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/components/browser.js +6 -1
- package/node_modules/@poe-code/design-system/dist/components/color.js +9 -8
- package/node_modules/@poe-code/design-system/dist/components/command-errors.js +3 -2
- package/node_modules/@poe-code/design-system/dist/components/detail-card.d.ts +22 -0
- package/node_modules/@poe-code/design-system/dist/components/detail-card.js +69 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +88 -11
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
- package/node_modules/@poe-code/design-system/dist/components/table.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/components/table.js +82 -5
- package/node_modules/@poe-code/design-system/dist/components/template.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/components/template.js +198 -32
- package/node_modules/@poe-code/design-system/dist/components/text.js +29 -5
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +2 -2
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +77 -32
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +28 -5
- package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +45 -28
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal-width.js +71 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +32 -10
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +3 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +57 -6
- package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.js +12 -15
- package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/design-system/dist/index.js +2 -1
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -1
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +8 -5
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +1 -1
- package/node_modules/@poe-code/design-system/dist/static/menu.js +8 -2
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +10 -4
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +9 -2
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +19 -2
- package/node_modules/@poe-code/design-system/package.json +2 -1
- package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +244 -110
- package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +16 -4
- package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +3 -2
- package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +16 -1
- package/node_modules/@poe-code/process-runner/dist/index.d.ts +1 -0
- package/node_modules/@poe-code/process-runner/dist/index.js +1 -0
- package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +30 -8
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +3 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.d.ts +57 -0
- package/node_modules/@poe-code/process-runner/dist/workspace-transfer.js +484 -0
- package/node_modules/@poe-code/process-runner/package.json +1 -1
- package/node_modules/@poe-code/task-list/README.md +0 -2
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +3 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +89 -59
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +9 -3
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +460 -99
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +156 -154
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +79 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +120 -132
- package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/task-list/dist/index.js +2 -0
- package/node_modules/@poe-code/task-list/dist/move.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/move.js +215 -0
- package/node_modules/@poe-code/task-list/dist/open.js +3 -4
- package/node_modules/@poe-code/task-list/dist/state-machine.js +3 -1
- package/node_modules/@poe-code/task-list/dist/state.js +9 -0
- package/node_modules/@poe-code/task-list/dist/types.d.ts +48 -13
- package/node_modules/@poe-code/task-list/package.json +1 -2
- package/node_modules/auth-store/dist/create-secret-store.js +4 -1
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +7 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +69 -7
- package/node_modules/auth-store/dist/index.d.ts +1 -1
- package/node_modules/auth-store/dist/keychain-store.d.ts +4 -1
- package/node_modules/auth-store/dist/keychain-store.js +18 -16
- package/node_modules/auth-store/dist/provider-store.d.ts +5 -1
- package/node_modules/auth-store/dist/provider-store.js +55 -7
- package/node_modules/auth-store/dist/types.d.ts +3 -1
- package/node_modules/auth-store/package.json +2 -1
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +46 -15
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +49 -12
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +6 -1
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +1 -1
- package/node_modules/mcp-oauth/package.json +1 -0
- package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +1 -1
- package/node_modules/tiny-mcp-client/dist/internal.d.ts +8 -4
- package/node_modules/tiny-mcp-client/dist/internal.js +237 -67
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +1 -1
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +4 -7
- package/node_modules/tiny-mcp-client/package.json +2 -1
- package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +1 -1
- package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +46 -0
- package/node_modules/tiny-mcp-client/src/internal.ts +279 -77
- package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +1 -1
- package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +5 -10
- package/node_modules/tiny-mcp-client/src/transports.test.ts +588 -6
- package/package.json +10 -12
- package/node_modules/@poe-code/file-lock/README.md +0 -52
- package/node_modules/@poe-code/file-lock/dist/index.d.ts +0 -1
- package/node_modules/@poe-code/file-lock/dist/index.js +0 -1
- package/node_modules/@poe-code/file-lock/dist/lock.d.ts +0 -27
- package/node_modules/@poe-code/file-lock/dist/lock.js +0 -203
- package/node_modules/@poe-code/file-lock/package.json +0 -23
|
@@ -2,13 +2,19 @@ import { color } from "../components/color.js";
|
|
|
2
2
|
import { symbols } from "../components/symbols.js";
|
|
3
3
|
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
4
4
|
import { getTheme } from "../internal/theme-detect.js";
|
|
5
|
+
function renderMarkdownInline(value) {
|
|
6
|
+
return value.replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " ");
|
|
7
|
+
}
|
|
5
8
|
export function renderMenu(opts) {
|
|
6
9
|
const format = resolveOutputFormat();
|
|
7
10
|
const selectedIndex = opts.selectedIndex ?? 0;
|
|
11
|
+
if (!Number.isInteger(selectedIndex) || !Number.isFinite(selectedIndex)) {
|
|
12
|
+
throw new Error("selectedIndex must be a finite integer.");
|
|
13
|
+
}
|
|
8
14
|
if (format === "markdown") {
|
|
9
15
|
return [
|
|
10
|
-
`**${opts.message}**`,
|
|
11
|
-
...opts.options.map((option, index) => `- [${index === selectedIndex ? "x" : " "}] ${option.label}`)
|
|
16
|
+
`**${renderMarkdownInline(opts.message)}**`,
|
|
17
|
+
...opts.options.map((option, index) => `- [${index === selectedIndex ? "x" : " "}] ${renderMarkdownInline(option.label)}`)
|
|
12
18
|
].join("\n");
|
|
13
19
|
}
|
|
14
20
|
if (format === "json") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { color } from "../components/color.js";
|
|
2
2
|
import { symbols } from "../components/symbols.js";
|
|
3
3
|
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
4
|
-
export const SPINNER_FRAMES = ["◒", "◐", "◓", "◑"];
|
|
4
|
+
export const SPINNER_FRAMES = Object.freeze(["◒", "◐", "◓", "◑"]);
|
|
5
5
|
export function renderSpinnerFrame(options) {
|
|
6
6
|
const format = resolveOutputFormat();
|
|
7
7
|
if (format === "markdown") {
|
|
@@ -16,22 +16,28 @@ export function renderSpinnerFrame(options) {
|
|
|
16
16
|
})}\n`;
|
|
17
17
|
}
|
|
18
18
|
const frame = options.frame ?? 0;
|
|
19
|
-
const
|
|
19
|
+
const index = ((frame % SPINNER_FRAMES.length) + SPINNER_FRAMES.length) % SPINNER_FRAMES.length;
|
|
20
|
+
const spinnerChar = color.magenta(SPINNER_FRAMES[index]);
|
|
20
21
|
const timerSuffix = options.timer ? color.dim(` [${options.timer}]`) : "";
|
|
21
22
|
const bar = color.gray(symbols.bar);
|
|
22
23
|
return `${spinnerChar} ${options.message}${timerSuffix}\n${bar}`;
|
|
23
24
|
}
|
|
25
|
+
function renderMarkdownInline(value) {
|
|
26
|
+
return value.replaceAll("\r\n", " ").replaceAll("\n", " ").replaceAll("\r", " ");
|
|
27
|
+
}
|
|
24
28
|
export function renderSpinnerStopped(options) {
|
|
25
29
|
const format = resolveOutputFormat();
|
|
26
30
|
if (format === "markdown") {
|
|
27
|
-
return `- ${options.message}${options.timer ? ` [${options.timer}]` : ""}\n`;
|
|
31
|
+
return `- ${renderMarkdownInline(options.message)}${options.timer ? ` [${renderMarkdownInline(options.timer)}]` : ""}\n`;
|
|
28
32
|
}
|
|
29
33
|
if (format === "json") {
|
|
30
34
|
return `${JSON.stringify({
|
|
31
35
|
type: "spinner",
|
|
32
36
|
state: "stopped",
|
|
33
37
|
message: options.message,
|
|
34
|
-
|
|
38
|
+
code: options.code ?? 0,
|
|
39
|
+
...(options.timer ? { timer: options.timer } : {}),
|
|
40
|
+
...(options.subtext ? { subtext: options.subtext } : {})
|
|
35
41
|
})}\n`;
|
|
36
42
|
}
|
|
37
43
|
const code = options.code ?? 0;
|
|
@@ -32,7 +32,12 @@ class YamlSubsetParser {
|
|
|
32
32
|
throw new FrontmatterParseError("Invalid mapping entry.");
|
|
33
33
|
}
|
|
34
34
|
this.position += 1;
|
|
35
|
-
result
|
|
35
|
+
Object.defineProperty(result, entry.key, {
|
|
36
|
+
configurable: true,
|
|
37
|
+
enumerable: true,
|
|
38
|
+
value: this.readEntryValue(entry, expectedIndent),
|
|
39
|
+
writable: true
|
|
40
|
+
});
|
|
36
41
|
}
|
|
37
42
|
return result;
|
|
38
43
|
}
|
|
@@ -345,8 +350,10 @@ function sliceFrontmatterBlock(content, start, end) {
|
|
|
345
350
|
function startsWithFrontmatterFence(value) {
|
|
346
351
|
return (value.startsWith("---\n") ||
|
|
347
352
|
value.startsWith("---\r\n") ||
|
|
353
|
+
value.startsWith("---\r") ||
|
|
348
354
|
value.startsWith("\uFEFF---\n") ||
|
|
349
|
-
value.startsWith("\uFEFF---\r\n")
|
|
355
|
+
value.startsWith("\uFEFF---\r\n") ||
|
|
356
|
+
value.startsWith("\uFEFF---\r"));
|
|
350
357
|
}
|
|
351
358
|
function stripBom(value) {
|
|
352
359
|
return value.startsWith("\uFEFF") ? value.slice(1) : value;
|
|
@@ -6,7 +6,11 @@ import { typography } from "../tokens/typography.js";
|
|
|
6
6
|
import { widths } from "../tokens/widths.js";
|
|
7
7
|
const lineChar = "─";
|
|
8
8
|
export function render(ast, options = {}) {
|
|
9
|
-
const
|
|
9
|
+
const requestedWidth = options.width ?? process.stdout.columns ?? widths.maxLine;
|
|
10
|
+
if (!Number.isFinite(requestedWidth) || requestedWidth <= 0) {
|
|
11
|
+
throw new Error("width must be a positive finite number.");
|
|
12
|
+
}
|
|
13
|
+
const width = Math.max(1, requestedWidth);
|
|
10
14
|
const context = {
|
|
11
15
|
width,
|
|
12
16
|
showFrontmatter: options.showFrontmatter ?? false,
|
|
@@ -215,7 +219,20 @@ function formatFrontmatterValue(value) {
|
|
|
215
219
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
216
220
|
return String(value);
|
|
217
221
|
}
|
|
218
|
-
|
|
222
|
+
const ancestors = [];
|
|
223
|
+
return JSON.stringify(value, function (_key, nestedValue) {
|
|
224
|
+
if (typeof nestedValue !== "object" || nestedValue === null) {
|
|
225
|
+
return nestedValue;
|
|
226
|
+
}
|
|
227
|
+
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
228
|
+
ancestors.pop();
|
|
229
|
+
}
|
|
230
|
+
if (ancestors.includes(nestedValue)) {
|
|
231
|
+
return "[Circular]";
|
|
232
|
+
}
|
|
233
|
+
ancestors.push(nestedValue);
|
|
234
|
+
return nestedValue;
|
|
235
|
+
});
|
|
219
236
|
}
|
|
220
237
|
function renderHtml(node, context) {
|
|
221
238
|
const value = stripHtmlTags(node.value).trim();
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poe-code/design-system",
|
|
3
3
|
"version": "0.0.2",
|
|
4
|
+
"private": true,
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
7
8
|
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
+
"build": "node ../../scripts/guard-package-dist.mjs && tsc",
|
|
9
10
|
"postbuild": "node scripts/smoke-built-exports.cjs",
|
|
10
11
|
"lint": "cd ../.. && eslint packages/design-system --ext ts && tsc -p packages/design-system/tsconfig.json --noEmit",
|
|
11
12
|
"test": "cd ../.. && vitest run $(rg --files packages/design-system/src -g '*.test.ts' | sort | tr '\\n' ' ')",
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { buildDockerRunArgs } from "./args.js";
|
|
7
7
|
import { buildContextArgs, detectContext } from "./context.js";
|
|
8
8
|
import { detectEngine } from "./engine.js";
|
|
9
9
|
import { createHostRunner } from "../host/host-runner.js";
|
|
10
|
+
import { downloadWorkspace as downloadTransferredWorkspace, uploadWorkspace as uploadTransferredWorkspace } from "../workspace-transfer.js";
|
|
10
11
|
const containerCommand = ["sh", "-c", "while :; do sleep 3600; done"];
|
|
11
12
|
export const dockerExecutionEnvFactory = {
|
|
12
13
|
type: "docker",
|
|
@@ -53,109 +54,53 @@ export const dockerExecutionEnvFactory = {
|
|
|
53
54
|
});
|
|
54
55
|
},
|
|
55
56
|
async attach(envId, context) {
|
|
56
|
-
const
|
|
57
|
+
const reattachContext = parseDockerReattachContext(context?.reattachContext);
|
|
58
|
+
const engine = reattachContext?.engine ?? detectEngine();
|
|
57
59
|
return createDockerEnv({
|
|
58
60
|
id: envId,
|
|
59
61
|
spec: createAttachedSpec(context?.cwd),
|
|
60
62
|
runner: createHostRunner(),
|
|
61
63
|
engine,
|
|
62
|
-
context: detectContext(),
|
|
64
|
+
context: reattachContext === undefined ? detectContext() : reattachContext.context,
|
|
63
65
|
attachedJobId: context?.jobId
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
};
|
|
67
69
|
function createDockerEnv(input) {
|
|
68
70
|
const containerRef = input.id;
|
|
71
|
+
const workspaceTransferEnv = {
|
|
72
|
+
cwd: input.spec.cwd,
|
|
73
|
+
uploadDir: "/tmp/poe-workspace-transfer",
|
|
74
|
+
workspaceDir: input.spec.cwd,
|
|
75
|
+
remoteFs: createContainerWorkspaceFileSystem(input)
|
|
76
|
+
};
|
|
77
|
+
let detachedJobContext = input.attachedJobId === undefined
|
|
78
|
+
? null
|
|
79
|
+
: { id: input.attachedJobId, tool: input.spec.jobLabel.tool, argv: input.spec.jobLabel.argv };
|
|
69
80
|
return {
|
|
70
81
|
id: containerRef,
|
|
82
|
+
reattachContext: { engine: input.engine, context: input.context },
|
|
71
83
|
job: input.attachedJobId === undefined
|
|
72
84
|
? null
|
|
73
|
-
: createContainerJob(containerRef, input.runner, input.engine, input.context,
|
|
85
|
+
: createContainerJob(containerRef, input.runner, input.engine, input.context, detachedJobContext),
|
|
86
|
+
setDetachedJobContext(context) {
|
|
87
|
+
detachedJobContext = context;
|
|
88
|
+
},
|
|
74
89
|
async uploadWorkspace() {
|
|
75
|
-
|
|
76
|
-
const archivePath = path.join(tempDir, "workspace.tar");
|
|
77
|
-
try {
|
|
78
|
-
const excludeArgs = input.spec.uploadIgnoreFiles.flatMap((ignored) => [
|
|
79
|
-
"--exclude",
|
|
80
|
-
ignored
|
|
81
|
-
]);
|
|
82
|
-
const tarArgs = [...excludeArgs, "-cf", archivePath, "-C", input.spec.cwd, "."];
|
|
83
|
-
await runOrThrow(input.runner, {
|
|
84
|
-
command: "tar",
|
|
85
|
-
args: tarArgs,
|
|
86
|
-
stdout: "pipe",
|
|
87
|
-
stderr: "pipe"
|
|
88
|
-
});
|
|
89
|
-
await runOrThrow(input.runner, {
|
|
90
|
-
command: input.engine,
|
|
91
|
-
args: [
|
|
92
|
-
...buildContextArgs(input.engine, input.context),
|
|
93
|
-
"cp",
|
|
94
|
-
archivePath,
|
|
95
|
-
`${containerRef}:/tmp/poe-workspace-upload.tar`
|
|
96
|
-
],
|
|
97
|
-
stdout: "pipe",
|
|
98
|
-
stderr: "pipe"
|
|
99
|
-
});
|
|
100
|
-
await runOrThrow(input.runner, {
|
|
101
|
-
command: input.engine,
|
|
102
|
-
args: [
|
|
103
|
-
...buildContextArgs(input.engine, input.context),
|
|
104
|
-
"exec",
|
|
105
|
-
containerRef,
|
|
106
|
-
"sh",
|
|
107
|
-
"-c",
|
|
108
|
-
`mkdir -p ${shellQuote(input.spec.cwd)} && tar -xf /tmp/poe-workspace-upload.tar -C ${shellQuote(input.spec.cwd)}`
|
|
109
|
-
],
|
|
110
|
-
stdout: "pipe",
|
|
111
|
-
stderr: "pipe"
|
|
112
|
-
});
|
|
90
|
+
if (readRunnerSync(input.spec.runner) === "none") {
|
|
113
91
|
return { files: 0, bytes: 0, skipped: [] };
|
|
114
92
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
93
|
+
return uploadTransferredWorkspace(workspaceTransferEnv, {
|
|
94
|
+
runner: readWorkspaceTransferRunner(input.spec.runner),
|
|
95
|
+
workspaceExclude: input.spec.uploadIgnoreFiles
|
|
96
|
+
});
|
|
118
97
|
},
|
|
119
98
|
async downloadWorkspace(opts) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
await runOrThrow(input.runner, {
|
|
124
|
-
command: input.engine,
|
|
125
|
-
args: [
|
|
126
|
-
...buildContextArgs(input.engine, input.context),
|
|
127
|
-
"exec",
|
|
128
|
-
containerRef,
|
|
129
|
-
"sh",
|
|
130
|
-
"-c",
|
|
131
|
-
`tar -cf /tmp/poe-workspace-download.tar -C ${shellQuote(input.spec.cwd)} .`
|
|
132
|
-
],
|
|
133
|
-
stdout: "pipe",
|
|
134
|
-
stderr: "pipe"
|
|
135
|
-
});
|
|
136
|
-
await runOrThrow(input.runner, {
|
|
137
|
-
command: input.engine,
|
|
138
|
-
args: [
|
|
139
|
-
...buildContextArgs(input.engine, input.context),
|
|
140
|
-
"cp",
|
|
141
|
-
`${containerRef}:/tmp/poe-workspace-download.tar`,
|
|
142
|
-
archivePath
|
|
143
|
-
],
|
|
144
|
-
stdout: "pipe",
|
|
145
|
-
stderr: "pipe"
|
|
146
|
-
});
|
|
147
|
-
const extractMode = opts.conflictPolicy === "refuse" ? "-xkf" : "-xf";
|
|
148
|
-
await runOrThrow(input.runner, {
|
|
149
|
-
command: "tar",
|
|
150
|
-
args: [extractMode, archivePath, "-C", input.spec.cwd],
|
|
151
|
-
stdout: "pipe",
|
|
152
|
-
stderr: "pipe"
|
|
153
|
-
});
|
|
99
|
+
const sync = readRunnerSync(input.spec.runner);
|
|
100
|
+
if (sync === "upload" || sync === "none") {
|
|
154
101
|
return { files: 0, bytes: 0, conflicts: [] };
|
|
155
102
|
}
|
|
156
|
-
|
|
157
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
158
|
-
}
|
|
103
|
+
return downloadTransferredWorkspace(workspaceTransferEnv, opts);
|
|
159
104
|
},
|
|
160
105
|
exec(spec) {
|
|
161
106
|
return input.runner.exec({
|
|
@@ -174,18 +119,19 @@ function createDockerEnv(input) {
|
|
|
174
119
|
stdin: spec.stdin,
|
|
175
120
|
stdout: spec.stdout,
|
|
176
121
|
stderr: spec.stderr,
|
|
177
|
-
tty: spec.tty
|
|
122
|
+
tty: spec.tty,
|
|
123
|
+
signal: spec.signal
|
|
178
124
|
});
|
|
179
125
|
},
|
|
180
126
|
async detach() {
|
|
181
|
-
return createContainerJob(containerRef, input.runner, input.engine, input.context);
|
|
127
|
+
return createContainerJob(containerRef, input.runner, input.engine, input.context, detachedJobContext);
|
|
182
128
|
},
|
|
183
129
|
shell() {
|
|
184
130
|
const shellSpec = input.spec.shellSpec;
|
|
185
131
|
return this.exec({
|
|
186
132
|
command: shellSpec?.command ?? input.spec.env.SHELL ?? "sh",
|
|
187
133
|
...(shellSpec?.args ? { args: shellSpec.args } : {}),
|
|
188
|
-
cwd: input.spec.cwd,
|
|
134
|
+
cwd: shellSpec?.cwd ?? input.spec.cwd,
|
|
189
135
|
env: shellSpec && "env" in shellSpec ? shellSpec.env : input.spec.env,
|
|
190
136
|
stdin: "inherit",
|
|
191
137
|
stdout: "inherit",
|
|
@@ -203,6 +149,14 @@ function createDockerEnv(input) {
|
|
|
203
149
|
}
|
|
204
150
|
};
|
|
205
151
|
}
|
|
152
|
+
function parseDockerReattachContext(value) {
|
|
153
|
+
if (value !== undefined &&
|
|
154
|
+
(value.engine === "docker" || value.engine === "podman") &&
|
|
155
|
+
(value.context === null || typeof value.context === "string")) {
|
|
156
|
+
return { engine: value.engine, context: value.context };
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
206
160
|
async function resolveImage(input) {
|
|
207
161
|
if (input.runtime.image !== undefined) {
|
|
208
162
|
return input.runtime.image;
|
|
@@ -222,9 +176,10 @@ export async function buildDockerRuntimeTemplate(input) {
|
|
|
222
176
|
const dockerfilePath = path.resolve(input.cwd, input.runtime.dockerfile ?? path.join(".poe-code", "Dockerfile"));
|
|
223
177
|
const buildContext = path.resolve(input.cwd, input.runtime.build_context ?? ".");
|
|
224
178
|
const dockerfileBytes = await readFile(dockerfilePath);
|
|
225
|
-
const
|
|
179
|
+
const buildContextFiles = await readBuildContextFiles(buildContext);
|
|
180
|
+
const hash = hashDockerTemplate(dockerfileBytes, buildContextFiles, input.runtime.build_args ?? {}, engine);
|
|
226
181
|
const cached = input.force ? null : await input.state?.templates.get("docker", hash);
|
|
227
|
-
if (cached?.image !== undefined) {
|
|
182
|
+
if (cached?.image !== undefined && (await imageExists(runner, engine, context, cached.image))) {
|
|
228
183
|
return {
|
|
229
184
|
backend: "docker",
|
|
230
185
|
hash,
|
|
@@ -256,10 +211,18 @@ export async function buildDockerRuntimeTemplate(input) {
|
|
|
256
211
|
cached: false
|
|
257
212
|
};
|
|
258
213
|
}
|
|
259
|
-
function hashDockerTemplate(dockerfileBytes, buildArgs) {
|
|
214
|
+
function hashDockerTemplate(dockerfileBytes, buildContextFiles, buildArgs, engine) {
|
|
260
215
|
const hash = createHash("sha256");
|
|
261
216
|
hash.update(dockerfileBytes);
|
|
262
217
|
hash.update("\0");
|
|
218
|
+
hash.update(engine);
|
|
219
|
+
hash.update("\0");
|
|
220
|
+
for (const file of buildContextFiles) {
|
|
221
|
+
hash.update(file.relativePath);
|
|
222
|
+
hash.update("\0");
|
|
223
|
+
hash.update(file.bytes);
|
|
224
|
+
hash.update("\0");
|
|
225
|
+
}
|
|
263
226
|
for (const [key, value] of sortedBuildArgs(buildArgs)) {
|
|
264
227
|
hash.update(key);
|
|
265
228
|
hash.update("=");
|
|
@@ -268,6 +231,39 @@ function hashDockerTemplate(dockerfileBytes, buildArgs) {
|
|
|
268
231
|
}
|
|
269
232
|
return hash.digest("hex");
|
|
270
233
|
}
|
|
234
|
+
async function readBuildContextFiles(buildContext) {
|
|
235
|
+
const files = [];
|
|
236
|
+
await collectBuildContextFiles(buildContext, "", files);
|
|
237
|
+
return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
238
|
+
}
|
|
239
|
+
async function collectBuildContextFiles(buildContext, relativeDir, files) {
|
|
240
|
+
const absoluteDir = path.join(buildContext, relativeDir);
|
|
241
|
+
const entries = await readdir(absoluteDir, { withFileTypes: true });
|
|
242
|
+
for (const entry of entries) {
|
|
243
|
+
const relativePath = path.join(relativeDir, entry.name);
|
|
244
|
+
if (entry.isDirectory()) {
|
|
245
|
+
await collectBuildContextFiles(buildContext, relativePath, files);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (!entry.isFile()) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
files.push({
|
|
252
|
+
relativePath: relativePath.split(path.sep).join("/"),
|
|
253
|
+
bytes: await readFile(path.join(buildContext, relativePath))
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function imageExists(runner, engine, context, image) {
|
|
258
|
+
const handle = runner.exec({
|
|
259
|
+
command: engine,
|
|
260
|
+
args: [...buildContextArgs(engine, context), "image", "inspect", image],
|
|
261
|
+
stdout: "pipe",
|
|
262
|
+
stderr: "pipe"
|
|
263
|
+
});
|
|
264
|
+
const result = await handle.result;
|
|
265
|
+
return result.exitCode === 0;
|
|
266
|
+
}
|
|
271
267
|
async function buildImage(input) {
|
|
272
268
|
await runOrThrow(input.runner, {
|
|
273
269
|
command: input.engine,
|
|
@@ -336,13 +332,108 @@ function buildEnvArgs(env) {
|
|
|
336
332
|
function createContainerName() {
|
|
337
333
|
return `poe-env-${randomBytes(6).toString("hex")}`;
|
|
338
334
|
}
|
|
339
|
-
function
|
|
335
|
+
function readRunnerSync(runner) {
|
|
336
|
+
if (typeof runner !== "object" || runner === null || !("sync" in runner)) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
const sync = runner.sync;
|
|
340
|
+
return sync === "both" || sync === "upload" || sync === "none" ? sync : undefined;
|
|
341
|
+
}
|
|
342
|
+
function readWorkspaceTransferRunner(runner) {
|
|
343
|
+
if (typeof runner !== "object" || runner === null) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
const record = runner;
|
|
347
|
+
const uploadMaxFileMb = typeof record.upload_max_file_mb === "number" ? record.upload_max_file_mb : undefined;
|
|
348
|
+
const workspace = typeof record.workspace === "object" && record.workspace !== null
|
|
349
|
+
&& Array.isArray(record.workspace.exclude)
|
|
350
|
+
? { exclude: record.workspace.exclude.filter((value) => typeof value === "string") }
|
|
351
|
+
: undefined;
|
|
352
|
+
return { ...(uploadMaxFileMb === undefined ? {} : { upload_max_file_mb: uploadMaxFileMb }), ...(workspace === undefined ? {} : { workspace }) };
|
|
353
|
+
}
|
|
354
|
+
function createContainerWorkspaceFileSystem(input) {
|
|
355
|
+
const execShell = (command) => runAndRead(input.runner, {
|
|
356
|
+
command: input.engine,
|
|
357
|
+
args: [...buildContextArgs(input.engine, input.context), "exec", input.id, "sh", "-c", command],
|
|
358
|
+
stdout: "pipe",
|
|
359
|
+
stderr: "pipe"
|
|
360
|
+
});
|
|
361
|
+
async function readRemoteFile(targetPath) {
|
|
362
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "poe-docker-read-"));
|
|
363
|
+
const destinationPath = path.join(tempDir, "content");
|
|
364
|
+
try {
|
|
365
|
+
await runOrThrow(input.runner, {
|
|
366
|
+
command: input.engine,
|
|
367
|
+
args: [...buildContextArgs(input.engine, input.context), "cp", `${input.id}:${targetPath}`, destinationPath],
|
|
368
|
+
stdout: "pipe",
|
|
369
|
+
stderr: "pipe"
|
|
370
|
+
});
|
|
371
|
+
return await readFile(destinationPath);
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async function readFileFromContainer(targetPath, encoding) {
|
|
378
|
+
const contents = await readRemoteFile(targetPath);
|
|
379
|
+
return encoding === undefined ? contents : contents.toString(encoding);
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
async mkdir(targetPath) {
|
|
383
|
+
await execShell(`mkdir -p ${shellQuote(targetPath)}`);
|
|
384
|
+
},
|
|
385
|
+
async readdir(targetPath) {
|
|
386
|
+
const output = await execShell(`for item in ${shellQuote(targetPath)}/* ${shellQuote(targetPath)}/.[!.]* ${shellQuote(targetPath)}/..?*; do [ -e "$item" ] || continue; if [ -d "$item" ]; then kind=d; size=0; else kind=f; size=$(wc -c < "$item"); fi; printf '%s\\t%s\\t%s\\n' "\${item##*/}" "$kind" "$size"; done`);
|
|
387
|
+
return output.split("\n").filter(Boolean).map((line) => {
|
|
388
|
+
const [name = "", kind = "f"] = line.split("\t");
|
|
389
|
+
return { name, isFile: () => kind === "f", isDirectory: () => kind === "d" };
|
|
390
|
+
});
|
|
391
|
+
},
|
|
392
|
+
readFile: readFileFromContainer,
|
|
393
|
+
async writeFile(targetPath, data) {
|
|
394
|
+
const tempDir = mkdtempSync(path.join(tmpdir(), "poe-docker-write-"));
|
|
395
|
+
const sourcePath = path.join(tempDir, "content");
|
|
396
|
+
try {
|
|
397
|
+
await writeFile(sourcePath, data);
|
|
398
|
+
await runOrThrow(input.runner, {
|
|
399
|
+
command: input.engine,
|
|
400
|
+
args: [...buildContextArgs(input.engine, input.context), "cp", sourcePath, `${input.id}:${targetPath}`],
|
|
401
|
+
stdout: "pipe",
|
|
402
|
+
stderr: "pipe"
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
finally {
|
|
406
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
async stat(targetPath) {
|
|
410
|
+
const output = await execShell(`if [ -d ${shellQuote(targetPath)} ]; then printf 'd\\t0'; elif [ -f ${shellQuote(targetPath)} ]; then printf 'f\\t'; wc -c < ${shellQuote(targetPath)}; else printf 'missing'; fi`);
|
|
411
|
+
if (output.trim() === "missing") {
|
|
412
|
+
throw Object.assign(new Error(`ENOENT: ${targetPath}`), { code: "ENOENT" });
|
|
413
|
+
}
|
|
414
|
+
const [kind = "f", rawSize = "0"] = output.trim().split("\t");
|
|
415
|
+
return { size: Number(rawSize.trim()), isFile: () => kind === "f", isDirectory: () => kind === "d" };
|
|
416
|
+
},
|
|
417
|
+
async rename(oldPath, newPath) {
|
|
418
|
+
await execShell(`mv ${shellQuote(oldPath)} ${shellQuote(newPath)}`);
|
|
419
|
+
},
|
|
420
|
+
async rm(targetPath) {
|
|
421
|
+
await execShell(`rm -rf ${shellQuote(targetPath)}`);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function createContainerJob(containerId, runner, engine, context, detachedJobContext = null) {
|
|
426
|
+
const jobId = detachedJobContext?.id ?? containerId;
|
|
340
427
|
return {
|
|
341
428
|
id: jobId,
|
|
342
429
|
envId: containerId,
|
|
343
|
-
tool: "docker",
|
|
344
|
-
argv: ["attach", containerId],
|
|
430
|
+
tool: detachedJobContext?.tool ?? "docker",
|
|
431
|
+
argv: detachedJobContext?.argv ?? ["attach", containerId],
|
|
345
432
|
async status() {
|
|
433
|
+
if (detachedJobContext !== null) {
|
|
434
|
+
const exitCode = await readDetachedExitCode(containerId, jobId, runner, engine, context);
|
|
435
|
+
return exitCode === null ? "running" : "exited";
|
|
436
|
+
}
|
|
346
437
|
const handle = runner.exec({
|
|
347
438
|
command: engine,
|
|
348
439
|
args: [
|
|
@@ -360,29 +451,48 @@ function createContainerJob(containerId, runner, engine, context, jobId = contai
|
|
|
360
451
|
if (result.exitCode !== 0) {
|
|
361
452
|
return "lost";
|
|
362
453
|
}
|
|
363
|
-
return stdout.trim() === "
|
|
454
|
+
return stdout.trim() === "exited" ? "exited" : "running";
|
|
364
455
|
},
|
|
365
456
|
async *stream(opts) {
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
457
|
+
const logFile = shellQuote(`/tmp/poe-jobs/${jobId}.log`);
|
|
458
|
+
const sinceCondition = opts?.since === undefined
|
|
459
|
+
? ""
|
|
460
|
+
: ` && test $(stat -c %Y ${logFile} 2>/dev/null || stat -f %m ${logFile}) -ge ${Math.ceil(opts.since.getTime() / 1000)}`;
|
|
461
|
+
let byteOffset = opts?.sinceByte ?? 0;
|
|
462
|
+
while (true) {
|
|
463
|
+
const stdout = await runAndRead(runner, {
|
|
464
|
+
command: engine,
|
|
465
|
+
args: [
|
|
466
|
+
...buildContextArgs(engine, context),
|
|
467
|
+
"exec",
|
|
468
|
+
containerId,
|
|
469
|
+
"sh",
|
|
470
|
+
"-c",
|
|
471
|
+
`test -f ${logFile}${sinceCondition} && tail -c +${byteOffset + 1} ${logFile} || true`
|
|
472
|
+
],
|
|
473
|
+
stdout: "pipe",
|
|
474
|
+
stderr: "pipe"
|
|
475
|
+
});
|
|
476
|
+
if (stdout.length > 0) {
|
|
477
|
+
yield { byteOffset, data: stdout };
|
|
478
|
+
byteOffset += Buffer.byteLength(stdout);
|
|
479
|
+
}
|
|
480
|
+
if (opts?.follow !== true || (await this.status()) !== "running") {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
383
484
|
}
|
|
384
485
|
},
|
|
385
486
|
async wait() {
|
|
487
|
+
if (detachedJobContext !== null) {
|
|
488
|
+
while (true) {
|
|
489
|
+
const exitCode = await readDetachedExitCode(containerId, jobId, runner, engine, context);
|
|
490
|
+
if (exitCode !== null) {
|
|
491
|
+
return { exitCode };
|
|
492
|
+
}
|
|
493
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
386
496
|
const handle = runner.exec({
|
|
387
497
|
command: engine,
|
|
388
498
|
args: [...buildContextArgs(engine, context), "wait", containerId],
|
|
@@ -391,7 +501,8 @@ function createContainerJob(containerId, runner, engine, context, jobId = contai
|
|
|
391
501
|
});
|
|
392
502
|
const stdout = await readStream(handle.stdout);
|
|
393
503
|
const result = await handle.result;
|
|
394
|
-
|
|
504
|
+
const exitCode = Number.parseInt(stdout.trim(), 10);
|
|
505
|
+
return { exitCode: Number.isNaN(exitCode) ? result.exitCode : exitCode };
|
|
395
506
|
},
|
|
396
507
|
async kill(signal) {
|
|
397
508
|
const args = signal === undefined || signal === "SIGTERM"
|
|
@@ -406,6 +517,29 @@ function createContainerJob(containerId, runner, engine, context, jobId = contai
|
|
|
406
517
|
}
|
|
407
518
|
};
|
|
408
519
|
}
|
|
520
|
+
async function readDetachedExitCode(containerId, jobId, runner, engine, context) {
|
|
521
|
+
const exitFile = shellQuote(`/tmp/poe-jobs/${jobId}.exit`);
|
|
522
|
+
const handle = runner.exec({
|
|
523
|
+
command: engine,
|
|
524
|
+
args: [
|
|
525
|
+
...buildContextArgs(engine, context),
|
|
526
|
+
"exec",
|
|
527
|
+
containerId,
|
|
528
|
+
"sh",
|
|
529
|
+
"-c",
|
|
530
|
+
`test -f ${exitFile} && cat ${exitFile} || true`
|
|
531
|
+
],
|
|
532
|
+
stdout: "pipe",
|
|
533
|
+
stderr: "pipe"
|
|
534
|
+
});
|
|
535
|
+
const stdout = await readStream(handle.stdout);
|
|
536
|
+
const result = await handle.result;
|
|
537
|
+
if (result.exitCode !== 0) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
const exitCode = Number.parseInt(stdout.trim(), 10);
|
|
541
|
+
return Number.isNaN(exitCode) ? null : exitCode;
|
|
542
|
+
}
|
|
409
543
|
function createAttachedSpec(cwd = "/workspace") {
|
|
410
544
|
return {
|
|
411
545
|
cwd,
|
|
@@ -9,6 +9,16 @@ export function createDockerRunner(options) {
|
|
|
9
9
|
return {
|
|
10
10
|
name: "docker",
|
|
11
11
|
exec(spec) {
|
|
12
|
+
if (spec.signal?.aborted === true) {
|
|
13
|
+
return {
|
|
14
|
+
pid: null,
|
|
15
|
+
stdin: null,
|
|
16
|
+
stdout: null,
|
|
17
|
+
stderr: null,
|
|
18
|
+
result: Promise.resolve({ exitCode: 1 }),
|
|
19
|
+
kill() { }
|
|
20
|
+
};
|
|
21
|
+
}
|
|
12
22
|
const stdinMode = spec.stdin ?? "ignore";
|
|
13
23
|
const stdoutMode = spec.stdout ?? "pipe";
|
|
14
24
|
const stderrMode = spec.stderr ?? "pipe";
|
|
@@ -44,9 +54,6 @@ export function createDockerRunner(options) {
|
|
|
44
54
|
const result = new Promise((resolve) => {
|
|
45
55
|
resolveResult = resolve;
|
|
46
56
|
});
|
|
47
|
-
const cleanupAbort = bindAbortSignal(spec.signal, () => {
|
|
48
|
-
spawnControlCommand(engine, context, ["stop", containerName]);
|
|
49
|
-
});
|
|
50
57
|
const settleResult = (exitCode) => {
|
|
51
58
|
if (isResultSettled) {
|
|
52
59
|
return;
|
|
@@ -55,6 +62,10 @@ export function createDockerRunner(options) {
|
|
|
55
62
|
cleanupAbort();
|
|
56
63
|
resolveResult?.({ exitCode });
|
|
57
64
|
};
|
|
65
|
+
const cleanupAbort = bindAbortSignal(spec.signal, () => {
|
|
66
|
+
settleResult(1);
|
|
67
|
+
spawnControlCommand(engine, context, ["stop", containerName]);
|
|
68
|
+
});
|
|
58
69
|
child.once("error", () => {
|
|
59
70
|
settleResult(1);
|
|
60
71
|
});
|
|
@@ -112,9 +123,10 @@ function isContainerNameCharacter(char) {
|
|
|
112
123
|
return char === "." || char === "_" || char === "-";
|
|
113
124
|
}
|
|
114
125
|
function spawnControlCommand(engine, context, args) {
|
|
115
|
-
childProcess.spawn(engine, [...buildContextArgs(engine, context), ...args], {
|
|
126
|
+
const child = childProcess.spawn(engine, [...buildContextArgs(engine, context), ...args], {
|
|
116
127
|
stdio: "ignore"
|
|
117
128
|
});
|
|
129
|
+
child.once("error", () => undefined);
|
|
118
130
|
}
|
|
119
131
|
function bindAbortSignal(signal, onAbort) {
|
|
120
132
|
if (signal === undefined) {
|