@elie-laloum/outpost 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.fr.md +251 -0
- package/README.md +226 -0
- package/ROADMAP.md +34 -0
- package/SECURITY.md +15 -0
- package/dist/application/campaign.d.ts +33 -0
- package/dist/application/campaign.js +195 -0
- package/dist/application/campaign.js.map +1 -0
- package/dist/application/execution.d.ts +48 -0
- package/dist/application/execution.js +322 -0
- package/dist/application/execution.js.map +1 -0
- package/dist/application/interactive-brief.d.ts +3 -0
- package/dist/application/interactive-brief.js +35 -0
- package/dist/application/interactive-brief.js.map +1 -0
- package/dist/application/outpost.d.ts +91 -0
- package/dist/application/outpost.js +629 -0
- package/dist/application/outpost.js.map +1 -0
- package/dist/application/remote-workspace.d.ts +11 -0
- package/dist/application/remote-workspace.js +264 -0
- package/dist/application/remote-workspace.js.map +1 -0
- package/dist/application/tasks.d.ts +17 -0
- package/dist/application/tasks.js +36 -0
- package/dist/application/tasks.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +94 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/scaffold.d.ts +28 -0
- package/dist/cli/scaffold.js +169 -0
- package/dist/cli/scaffold.js.map +1 -0
- package/dist/cli/starters.d.ts +3 -0
- package/dist/cli/starters.js +34 -0
- package/dist/cli/starters.js.map +1 -0
- package/dist/domain/backlog.d.ts +16 -0
- package/dist/domain/backlog.js +23 -0
- package/dist/domain/backlog.js.map +1 -0
- package/dist/domain/errors.d.ts +11 -0
- package/dist/domain/errors.js +37 -0
- package/dist/domain/errors.js.map +1 -0
- package/dist/domain/ports.d.ts +183 -0
- package/dist/domain/ports.js +2 -0
- package/dist/domain/ports.js.map +1 -0
- package/dist/domain/prompts.d.ts +22 -0
- package/dist/domain/prompts.js +45 -0
- package/dist/domain/prompts.js.map +1 -0
- package/dist/domain/response.d.ts +37 -0
- package/dist/domain/response.js +50 -0
- package/dist/domain/response.js.map +1 -0
- package/dist/domain/workflow.d.ts +68 -0
- package/dist/domain/workflow.js +246 -0
- package/dist/domain/workflow.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/abort.d.ts +1 -0
- package/dist/infrastructure/abort.js +16 -0
- package/dist/infrastructure/abort.js.map +1 -0
- package/dist/infrastructure/conversations.d.ts +30 -0
- package/dist/infrastructure/conversations.js +222 -0
- package/dist/infrastructure/conversations.js.map +1 -0
- package/dist/infrastructure/files.d.ts +5 -0
- package/dist/infrastructure/files.js +85 -0
- package/dist/infrastructure/files.js.map +1 -0
- package/dist/infrastructure/git.d.ts +14 -0
- package/dist/infrastructure/git.js +304 -0
- package/dist/infrastructure/git.js.map +1 -0
- package/dist/infrastructure/journal.d.ts +10 -0
- package/dist/infrastructure/journal.js +53 -0
- package/dist/infrastructure/journal.js.map +1 -0
- package/dist/infrastructure/process.d.ts +6 -0
- package/dist/infrastructure/process.js +133 -0
- package/dist/infrastructure/process.js.map +1 -0
- package/dist/infrastructure/reporter.d.ts +10 -0
- package/dist/infrastructure/reporter.js +47 -0
- package/dist/infrastructure/reporter.js.map +1 -0
- package/dist/infrastructure/settings.d.ts +3 -0
- package/dist/infrastructure/settings.js +41 -0
- package/dist/infrastructure/settings.js.map +1 -0
- package/dist/infrastructure/shutdown.d.ts +1 -0
- package/dist/infrastructure/shutdown.js +67 -0
- package/dist/infrastructure/shutdown.js.map +1 -0
- package/dist/infrastructure/terminal.d.ts +11 -0
- package/dist/infrastructure/terminal.js +19 -0
- package/dist/infrastructure/terminal.js.map +1 -0
- package/dist/infrastructure/transfer.d.ts +3 -0
- package/dist/infrastructure/transfer.js +29 -0
- package/dist/infrastructure/transfer.js.map +1 -0
- package/dist/providers/agents.d.ts +17 -0
- package/dist/providers/agents.js +204 -0
- package/dist/providers/agents.js.map +1 -0
- package/dist/providers/backlogs.d.ts +9 -0
- package/dist/providers/backlogs.js +82 -0
- package/dist/providers/backlogs.js.map +1 -0
- package/dist/providers/cloud-files.d.ts +4 -0
- package/dist/providers/cloud-files.js +66 -0
- package/dist/providers/cloud-files.js.map +1 -0
- package/dist/providers/container.d.ts +21 -0
- package/dist/providers/container.js +310 -0
- package/dist/providers/container.js.map +1 -0
- package/dist/providers/daytona.d.ts +10 -0
- package/dist/providers/daytona.js +153 -0
- package/dist/providers/daytona.js.map +1 -0
- package/dist/providers/docker.d.ts +3 -0
- package/dist/providers/docker.js +3 -0
- package/dist/providers/docker.js.map +1 -0
- package/dist/providers/factories.d.ts +9 -0
- package/dist/providers/factories.js +12 -0
- package/dist/providers/factories.js.map +1 -0
- package/dist/providers/local.d.ts +4 -0
- package/dist/providers/local.js +64 -0
- package/dist/providers/local.js.map +1 -0
- package/dist/providers/podman.d.ts +3 -0
- package/dist/providers/podman.js +3 -0
- package/dist/providers/podman.js.map +1 -0
- package/dist/providers/vercel.d.ts +9 -0
- package/dist/providers/vercel.js +158 -0
- package/dist/providers/vercel.js.map +1 -0
- package/dist/providers/versions.d.ts +4 -0
- package/dist/providers/versions.js +5 -0
- package/dist/providers/versions.js.map +1 -0
- package/docs/api.md +147 -0
- package/docs/architecture.md +21 -0
- package/docs/migration-1.1.fr.md +44 -0
- package/docs/migration-1.1.md +38 -0
- package/docs/operations.md +101 -0
- package/docs/providers.md +119 -0
- package/docs/workflows.md +101 -0
- package/package.json +95 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backlogs.js","sourceRoot":"","sources":["../../src/providers/backlogs.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Backlog, Issue } from \"../domain/backlog.ts\";\nimport { invariant } from \"../domain/errors.ts\";\nimport { requireSuccess, type Executor } from \"../infrastructure/process.ts\";\nimport { resolveVariables } from \"../infrastructure/settings.ts\";\n\nexport interface BacklogSettings {\n readonly directory?: string;\n readonly label?: string;\n readonly deadlineMs?: number;\n}\n\nfunction issue(input: unknown): Issue {\n invariant(\n !!input && typeof input === \"object\",\n \"Tracker returned an invalid issue\",\n );\n const item = input as Record<string, unknown>;\n invariant(\n (typeof item.id === \"string\" || typeof item.number === \"number\") &&\n typeof item.title === \"string\",\n \"Tracker issue needs an id and a title\",\n );\n return {\n id: String(item.number ?? item.id),\n title: item.title,\n body: String(item.body ?? item.description ?? \"\"),\n };\n}\n\nfunction cli(program: string, settings: BacklogSettings, executor?: Executor) {\n const directory = settings.directory ?? process.cwd();\n return async (args: readonly string[], signal?: AbortSignal) => {\n signal?.throwIfAborted();\n const variables = await resolveVariables(directory);\n return (\n await requireSuccess(\n {\n executable: program,\n arguments: args,\n directory,\n variables,\n deadlineMs: settings.deadlineMs ?? 60_000,\n retain: 16_777_216,\n ...(signal ? { signal } : {}),\n },\n executor,\n )\n ).stdout;\n };\n}\n\nexport function githubBacklog(\n settings: BacklogSettings = {},\n executor?: Executor,\n): Backlog {\n const run = cli(\"gh\", settings, executor);\n const identifier = (id: string) => {\n invariant(\n /^[1-9]\\d*$/.test(id),\n \"GitHub issue id must be a positive number\",\n );\n return id;\n };\n return {\n async list(signal) {\n const endpoint = `repos/{owner}/{repo}/issues?state=open&per_page=100${settings.label ? `&labels=${encodeURIComponent(settings.label)}` : \"\"}`;\n const pages: unknown = JSON.parse(\n await run([\"api\", endpoint, \"--paginate\", \"--slurp\"], signal),\n );\n invariant(\n Array.isArray(pages) && pages.every(Array.isArray),\n \"Invalid GitHub issue pages\",\n );\n return pages\n .flat()\n .filter((item) => !item.pull_request)\n .map(issue);\n },\n async get(id, signal) {\n return issue(\n JSON.parse(\n await run(\n [\"issue\", \"view\", identifier(id), \"--json\", \"number,title,body\"],\n signal,\n ),\n ),\n );\n },\n async close(id, signal) {\n await run(\n [\"issue\", \"close\", identifier(id), \"--reason\", \"completed\"],\n signal,\n );\n },\n };\n}\n\nexport function beadsBacklog(\n settings: BacklogSettings = {},\n executor?: Executor,\n): Backlog {\n const run = cli(\"bd\", settings, executor);\n const identifier = (id: string) => {\n invariant(/^[\\w][\\w.-]*$/.test(id), \"Invalid Beads issue id\");\n return id;\n };\n return {\n async list(signal) {\n const data: unknown = JSON.parse(\n await run(\n [\n \"ready\",\n \"--json\",\n \"--limit\",\n \"0\",\n ...(settings.label ? [\"--label\", settings.label] : []),\n ],\n signal,\n ),\n );\n invariant(Array.isArray(data), \"Invalid Beads backlog\");\n return data.map(issue);\n },\n async get(id, signal) {\n const data = JSON.parse(\n await run([\"show\", identifier(id), \"--json\"], signal),\n );\n return issue(Array.isArray(data) ? data[0] : data);\n },\n async close(id, signal) {\n await run([\"close\", identifier(id)], signal);\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function uploadTree(source: string, destination: string, upload: (path: string, data: Buffer) => Promise<void>, link: (target: string, path: string) => Promise<void>, metadata?: (path: string, directory: boolean, mode: number) => Promise<void>, signal?: AbortSignal): Promise<void>;
|
|
2
|
+
export declare function saveDownload(destination: string, data: Buffer): Promise<void>;
|
|
3
|
+
export declare const manifestScript = "const f=require('node:fs'),p=require('node:path'),root=process.argv[1],items=[];function walk(path,relative){const s=f.lstatSync(path),kind=s.isSymbolicLink()?'link':s.isDirectory()?'directory':s.isFile()?'file':'unsupported';items.push({path:relative,kind,mode:s.mode&511,...(kind==='link'?{target:f.readlinkSync(path)}:{})});if(kind==='directory')for(const name of f.readdirSync(path))walk(p.join(path,name),relative?relative+'/'+name:name)}walk(root,'');console.log(JSON.stringify(items))";
|
|
4
|
+
export declare function downloadTree(source: string, destination: string, manifest: string, read: (path: string) => Promise<Buffer>, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { chmod, lstat, mkdir, readFile, readdir, symlink, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, posix } from "node:path";
|
|
3
|
+
import { OutpostError } from "../domain/errors.js";
|
|
4
|
+
import { safeDestination } from "../infrastructure/files.js";
|
|
5
|
+
export async function uploadTree(source, destination, upload, link, metadata, signal) {
|
|
6
|
+
signal?.throwIfAborted();
|
|
7
|
+
const info = await lstat(source);
|
|
8
|
+
if (info.isSymbolicLink()) {
|
|
9
|
+
const { readlink } = await import("node:fs/promises");
|
|
10
|
+
await link(await readlink(source), destination);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (info.isDirectory()) {
|
|
14
|
+
await metadata?.(destination, true, info.mode & 0o777);
|
|
15
|
+
for (const entry of await readdir(source))
|
|
16
|
+
await uploadTree(join(source, entry), posix.join(destination, entry), upload, link, metadata, signal);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!info.isFile())
|
|
20
|
+
throw new OutpostError("provider", `Unsupported upload input: ${source}`);
|
|
21
|
+
const data = await readFile(source);
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
await upload(destination, data);
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
await metadata?.(destination, false, info.mode & 0o777);
|
|
26
|
+
}
|
|
27
|
+
export async function saveDownload(destination, data) {
|
|
28
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
29
|
+
if ((await lstat(destination).catch(() => undefined))?.isSymbolicLink())
|
|
30
|
+
throw new OutpostError("provider", "Refusing to overwrite a local symlink");
|
|
31
|
+
await writeFile(destination, data);
|
|
32
|
+
}
|
|
33
|
+
export const manifestScript = `const f=require('node:fs'),p=require('node:path'),root=process.argv[1],items=[];function walk(path,relative){const s=f.lstatSync(path),kind=s.isSymbolicLink()?'link':s.isDirectory()?'directory':s.isFile()?'file':'unsupported';items.push({path:relative,kind,mode:s.mode&511,...(kind==='link'?{target:f.readlinkSync(path)}:{})});if(kind==='directory')for(const name of f.readdirSync(path))walk(p.join(path,name),relative?relative+'/'+name:name)}walk(root,'');console.log(JSON.stringify(items))`;
|
|
34
|
+
export async function downloadTree(source, destination, manifest, read, signal) {
|
|
35
|
+
const entries = JSON.parse(manifest);
|
|
36
|
+
if (!Array.isArray(entries))
|
|
37
|
+
throw new OutpostError("provider", "Invalid transfer manifest");
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
signal?.throwIfAborted();
|
|
40
|
+
if (!entry ||
|
|
41
|
+
typeof entry.path !== "string" ||
|
|
42
|
+
!Number.isSafeInteger(entry.mode))
|
|
43
|
+
throw new OutpostError("provider", "Invalid transfer entry");
|
|
44
|
+
const target = entry.path
|
|
45
|
+
? await safeDestination(destination, entry.path)
|
|
46
|
+
: destination;
|
|
47
|
+
if ((await lstat(target).catch(() => undefined))?.isSymbolicLink())
|
|
48
|
+
throw new OutpostError("provider", "Refusing to overwrite a local symlink");
|
|
49
|
+
if (entry.kind === "directory") {
|
|
50
|
+
await mkdir(target, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
else if (entry.kind === "file") {
|
|
53
|
+
const data = await read(posix.join(source, entry.path));
|
|
54
|
+
signal?.throwIfAborted();
|
|
55
|
+
await saveDownload(target, data);
|
|
56
|
+
await chmod(target, entry.mode & 0o777);
|
|
57
|
+
}
|
|
58
|
+
else if (entry.kind === "link" && typeof entry.target === "string") {
|
|
59
|
+
await mkdir(dirname(target), { recursive: true });
|
|
60
|
+
await symlink(entry.target, target);
|
|
61
|
+
}
|
|
62
|
+
else
|
|
63
|
+
throw new OutpostError("provider", "Unsupported remote file type");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=cloud-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-files.js","sourceRoot":"","sources":["../../src/providers/cloud-files.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,KAAK,EACL,KAAK,EACL,QAAQ,EACR,OAAO,EACP,OAAO,EACP,SAAS,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,WAAmB,EACnB,MAAqD,EACrD,IAAqD,EACrD,QAA4E,EAC5E,MAAoB;IAEpB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;QAC1B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACtD,MAAM,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC;QAChD,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACvB,MAAM,QAAQ,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;QACvD,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,MAAM,CAAC;YACvC,MAAM,UAAU,CACd,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,EAC9B,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,MAAM,CACP,CAAC;QACJ,OAAO;IACT,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;QAChB,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,6BAA6B,MAAM,EAAE,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,MAAM,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAChC,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,MAAM,QAAQ,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,WAAmB,EACnB,IAAY;IAEZ,MAAM,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,IAAI,CAAC,MAAM,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,cAAc,EAAE;QACrE,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,uCAAuC,CAAC,CAAC;IAC9E,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,6eAA6e,CAAC;AAE5gB,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,WAAmB,EACnB,QAAgB,EAChB,IAAuC,EACvC,MAAoB;IAEpB,MAAM,OAAO,GAAY,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QACzB,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,2BAA2B,CAAC,CAAC;IAClE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,IACE,CAAC,KAAK;YACN,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;YAC9B,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC;YAEjC,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,wBAAwB,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI;YACvB,CAAC,CAAC,MAAM,eAAe,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC;YAChD,CAAC,CAAC,WAAW,CAAC;QAChB,IAAI,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,cAAc,EAAE;YAChE,MAAM,IAAI,YAAY,CACpB,UAAU,EACV,uCAAuC,CACxC,CAAC;QACJ,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC/B,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACxD,MAAM,EAAE,cAAc,EAAE,CAAC;YACzB,MAAM,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;QAC1C,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACrE,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAClD,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;;YAAM,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,8BAA8B,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC","sourcesContent":["import {\n chmod,\n lstat,\n mkdir,\n readFile,\n readdir,\n symlink,\n writeFile,\n} from \"node:fs/promises\";\nimport { dirname, join, posix } from \"node:path\";\nimport { OutpostError } from \"../domain/errors.ts\";\nimport { safeDestination } from \"../infrastructure/files.ts\";\n\nexport async function uploadTree(\n source: string,\n destination: string,\n upload: (path: string, data: Buffer) => Promise<void>,\n link: (target: string, path: string) => Promise<void>,\n metadata?: (path: string, directory: boolean, mode: number) => Promise<void>,\n signal?: AbortSignal,\n): Promise<void> {\n signal?.throwIfAborted();\n const info = await lstat(source);\n if (info.isSymbolicLink()) {\n const { readlink } = await import(\"node:fs/promises\");\n await link(await readlink(source), destination);\n return;\n }\n if (info.isDirectory()) {\n await metadata?.(destination, true, info.mode & 0o777);\n for (const entry of await readdir(source))\n await uploadTree(\n join(source, entry),\n posix.join(destination, entry),\n upload,\n link,\n metadata,\n signal,\n );\n return;\n }\n if (!info.isFile())\n throw new OutpostError(\"provider\", `Unsupported upload input: ${source}`);\n const data = await readFile(source);\n signal?.throwIfAborted();\n await upload(destination, data);\n signal?.throwIfAborted();\n await metadata?.(destination, false, info.mode & 0o777);\n}\n\nexport async function saveDownload(\n destination: string,\n data: Buffer,\n): Promise<void> {\n await mkdir(dirname(destination), { recursive: true });\n if ((await lstat(destination).catch(() => undefined))?.isSymbolicLink())\n throw new OutpostError(\"provider\", \"Refusing to overwrite a local symlink\");\n await writeFile(destination, data);\n}\n\nexport const manifestScript = `const f=require('node:fs'),p=require('node:path'),root=process.argv[1],items=[];function walk(path,relative){const s=f.lstatSync(path),kind=s.isSymbolicLink()?'link':s.isDirectory()?'directory':s.isFile()?'file':'unsupported';items.push({path:relative,kind,mode:s.mode&511,...(kind==='link'?{target:f.readlinkSync(path)}:{})});if(kind==='directory')for(const name of f.readdirSync(path))walk(p.join(path,name),relative?relative+'/'+name:name)}walk(root,'');console.log(JSON.stringify(items))`;\n\nexport async function downloadTree(\n source: string,\n destination: string,\n manifest: string,\n read: (path: string) => Promise<Buffer>,\n signal?: AbortSignal,\n): Promise<void> {\n const entries: unknown = JSON.parse(manifest);\n if (!Array.isArray(entries))\n throw new OutpostError(\"provider\", \"Invalid transfer manifest\");\n for (const entry of entries) {\n signal?.throwIfAborted();\n if (\n !entry ||\n typeof entry.path !== \"string\" ||\n !Number.isSafeInteger(entry.mode)\n )\n throw new OutpostError(\"provider\", \"Invalid transfer entry\");\n const target = entry.path\n ? await safeDestination(destination, entry.path)\n : destination;\n if ((await lstat(target).catch(() => undefined))?.isSymbolicLink())\n throw new OutpostError(\n \"provider\",\n \"Refusing to overwrite a local symlink\",\n );\n if (entry.kind === \"directory\") {\n await mkdir(target, { recursive: true });\n } else if (entry.kind === \"file\") {\n const data = await read(posix.join(source, entry.path));\n signal?.throwIfAborted();\n await saveDownload(target, data);\n await chmod(target, entry.mode & 0o777);\n } else if (entry.kind === \"link\" && typeof entry.target === \"string\") {\n await mkdir(dirname(target), { recursive: true });\n await symlink(entry.target, target);\n } else throw new OutpostError(\"provider\", \"Unsupported remote file type\");\n }\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SandboxProvider, Variables, Volume } from "../domain/ports.ts";
|
|
2
|
+
import type { Executor } from "../infrastructure/process.ts";
|
|
3
|
+
export interface ContainerOptions {
|
|
4
|
+
readonly image?: string;
|
|
5
|
+
readonly user?: {
|
|
6
|
+
readonly uid: number;
|
|
7
|
+
readonly gid: number;
|
|
8
|
+
};
|
|
9
|
+
readonly volumes?: readonly Volume[];
|
|
10
|
+
readonly variables?: Variables;
|
|
11
|
+
readonly networks?: string | readonly string[];
|
|
12
|
+
readonly groups?: readonly (string | number)[];
|
|
13
|
+
readonly devices?: readonly string[];
|
|
14
|
+
readonly cpus?: number;
|
|
15
|
+
readonly memoryMb?: number;
|
|
16
|
+
readonly label?: "z" | "Z" | false;
|
|
17
|
+
readonly retain?: number;
|
|
18
|
+
readonly userns?: "keep-id" | false;
|
|
19
|
+
}
|
|
20
|
+
export declare function imageName(repository: string): string;
|
|
21
|
+
export declare function containerProvider(engine: "docker" | "podman", options?: ContainerOptions, executor?: Executor, platform?: NodeJS.Platform): SandboxProvider;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdir, stat } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, isAbsolute, posix, relative } from "node:path";
|
|
5
|
+
import { OutpostError, invariant } from "../domain/errors.js";
|
|
6
|
+
import { expandPath } from "../infrastructure/files.js";
|
|
7
|
+
import { executeProcess, quote, requireSuccess, } from "../infrastructure/process.js";
|
|
8
|
+
import { registerCleanup } from "../infrastructure/shutdown.js";
|
|
9
|
+
export function imageName(repository) {
|
|
10
|
+
return `outpost:${basename(repository)
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/[^a-z0-9_.-]/g, "-") || "workspace"}`;
|
|
13
|
+
}
|
|
14
|
+
export function containerProvider(engine, options = {}, executor = executeProcess, platform = process.platform) {
|
|
15
|
+
const config = { ...options };
|
|
16
|
+
if (config.cpus !== undefined)
|
|
17
|
+
invariant(Number.isFinite(config.cpus) && config.cpus > 0, "cpus must be positive");
|
|
18
|
+
if (config.memoryMb !== undefined)
|
|
19
|
+
invariant(Number.isSafeInteger(config.memoryMb) && config.memoryMb >= 64, "memoryMb must be at least 64");
|
|
20
|
+
return {
|
|
21
|
+
name: engine,
|
|
22
|
+
placement: "mounted",
|
|
23
|
+
variables: { ...config.variables },
|
|
24
|
+
async acquire(context) {
|
|
25
|
+
context.signal?.throwIfAborted();
|
|
26
|
+
const name = `outpost-${randomUUID()}`;
|
|
27
|
+
const image = config.image ?? imageName(context.repository);
|
|
28
|
+
const user = config.user ?? {
|
|
29
|
+
uid: process.getuid?.() ?? 1000,
|
|
30
|
+
gid: process.getgid?.() ?? 1000,
|
|
31
|
+
};
|
|
32
|
+
invariant(Number.isInteger(user.uid) &&
|
|
33
|
+
user.uid >= 0 &&
|
|
34
|
+
Number.isInteger(user.gid) &&
|
|
35
|
+
user.gid >= 0, "Invalid container user");
|
|
36
|
+
const call = (args, extra = {}) => requireSuccess({ executable: engine, arguments: args, deadlineMs: 60_000, ...extra }, executor);
|
|
37
|
+
if (engine === "podman" && platform === "darwin") {
|
|
38
|
+
const machines = await call(["machine", "list", "--format", "json"]);
|
|
39
|
+
const entries = JSON.parse(machines.stdout);
|
|
40
|
+
invariant(Array.isArray(entries) &&
|
|
41
|
+
entries.some((machine) => machine.Running === true || machine.State === "running"), "Start a Podman machine with podman machine start before creating a sandbox");
|
|
42
|
+
}
|
|
43
|
+
const inspection = await call([
|
|
44
|
+
"image",
|
|
45
|
+
"inspect",
|
|
46
|
+
"--format",
|
|
47
|
+
"{{.Config.User}}",
|
|
48
|
+
image,
|
|
49
|
+
]);
|
|
50
|
+
const configuredUser = inspection.stdout.trim().split(":")[0];
|
|
51
|
+
if (!config.user &&
|
|
52
|
+
configuredUser &&
|
|
53
|
+
/^\d+$/.test(configuredUser) &&
|
|
54
|
+
Number(configuredUser) !== user.uid)
|
|
55
|
+
throw new OutpostError("provider", `Image UID ${configuredUser} does not match requested UID ${user.uid}`, { image, user });
|
|
56
|
+
const root = "/workspace", home = "/home/agent";
|
|
57
|
+
const env = {
|
|
58
|
+
...context.variables,
|
|
59
|
+
HOME: home,
|
|
60
|
+
GIT_CONFIG_COUNT: "1",
|
|
61
|
+
GIT_CONFIG_KEY_0: "safe.directory",
|
|
62
|
+
GIT_CONFIG_VALUE_0: "*",
|
|
63
|
+
};
|
|
64
|
+
const internal = [{ source: context.directory, target: root }];
|
|
65
|
+
if (context.gitDirectories.length) {
|
|
66
|
+
const common = context.gitDirectories.at(-1);
|
|
67
|
+
const offset = relative(common, context.gitDirectories[0]);
|
|
68
|
+
const nested = !isAbsolute(offset) &&
|
|
69
|
+
offset !== ".." &&
|
|
70
|
+
!offset.startsWith("..\\") &&
|
|
71
|
+
!offset.startsWith("../");
|
|
72
|
+
internal.push({
|
|
73
|
+
source: common,
|
|
74
|
+
target: "/outpost/git/common",
|
|
75
|
+
});
|
|
76
|
+
if (!nested)
|
|
77
|
+
internal.push({
|
|
78
|
+
source: context.gitDirectories[0],
|
|
79
|
+
target: "/outpost/git/worktree",
|
|
80
|
+
});
|
|
81
|
+
env.GIT_DIR = nested
|
|
82
|
+
? posix.join("/outpost/git/common", offset.replaceAll("\\", "/"))
|
|
83
|
+
: "/outpost/git/worktree";
|
|
84
|
+
env.GIT_COMMON_DIR = "/outpost/git/common";
|
|
85
|
+
env.GIT_WORK_TREE = root;
|
|
86
|
+
}
|
|
87
|
+
const volumes = [];
|
|
88
|
+
const fileParents = new Set();
|
|
89
|
+
for (const volume of [...internal, ...(config.volumes ?? [])]) {
|
|
90
|
+
const source = expandPath(volume.source, context.repository);
|
|
91
|
+
const info = await stat(source).catch(() => undefined);
|
|
92
|
+
if (!info)
|
|
93
|
+
throw new OutpostError("provider", `Mount source is unavailable: ${source}`);
|
|
94
|
+
const destination = volume.target.replaceAll("\\", "/");
|
|
95
|
+
const target = destination === "~"
|
|
96
|
+
? home
|
|
97
|
+
: destination.startsWith("~/")
|
|
98
|
+
? posix.resolve(home, destination.slice(2))
|
|
99
|
+
: posix.resolve(root, destination);
|
|
100
|
+
if (info.isFile() && !internal.includes(volume)) {
|
|
101
|
+
const parent = posix.dirname(target);
|
|
102
|
+
invariant(parent === home || parent.startsWith(home + "/"), "File mounts must live inside the agent home; mount the containing directory for other destinations");
|
|
103
|
+
for (let folder = parent; folder !== home; folder = posix.dirname(folder))
|
|
104
|
+
fileParents.add(folder);
|
|
105
|
+
}
|
|
106
|
+
invariant(!source.includes(",") && !target.includes(","), "Mount paths cannot contain commas");
|
|
107
|
+
if (config.label === false || platform !== "linux")
|
|
108
|
+
volumes.push("--mount", `type=bind,source=${source},target=${target}${volume.readOnly ? ",readonly" : ""}`);
|
|
109
|
+
else
|
|
110
|
+
volumes.push("--volume", `${source}:${target}:${volume.readOnly ? "ro," : ""}${config.label ?? "z"}`);
|
|
111
|
+
}
|
|
112
|
+
const networks = typeof config.networks === "string"
|
|
113
|
+
? [config.networks]
|
|
114
|
+
: (config.networks ?? []);
|
|
115
|
+
const args = [
|
|
116
|
+
"create",
|
|
117
|
+
"--name",
|
|
118
|
+
name,
|
|
119
|
+
"--init",
|
|
120
|
+
"--user",
|
|
121
|
+
`${user.uid}:${user.gid}`,
|
|
122
|
+
...(engine === "podman" &&
|
|
123
|
+
config.userns !== false &&
|
|
124
|
+
(process.getuid?.() !== 0 || config.userns === "keep-id")
|
|
125
|
+
? [
|
|
126
|
+
"--userns",
|
|
127
|
+
config.user
|
|
128
|
+
? `keep-id:uid=${user.uid},gid=${user.gid}`
|
|
129
|
+
: "keep-id",
|
|
130
|
+
]
|
|
131
|
+
: []),
|
|
132
|
+
"--workdir",
|
|
133
|
+
root,
|
|
134
|
+
"--cap-drop",
|
|
135
|
+
"ALL",
|
|
136
|
+
...(fileParents.size ? ["--cap-add", "CHOWN"] : []),
|
|
137
|
+
"--security-opt",
|
|
138
|
+
"no-new-privileges",
|
|
139
|
+
"--tmpfs",
|
|
140
|
+
engine === "podman"
|
|
141
|
+
? "/home/agent:rw,mode=1777"
|
|
142
|
+
: `/home/agent:rw,uid=${fileParents.size ? 0 : user.uid},gid=${fileParents.size ? 0 : user.gid},mode=0700`,
|
|
143
|
+
...volumes,
|
|
144
|
+
...networks.flatMap((network) => ["--network", network]),
|
|
145
|
+
...(config.groups ?? []).flatMap((group) => [
|
|
146
|
+
"--group-add",
|
|
147
|
+
String(group),
|
|
148
|
+
]),
|
|
149
|
+
...(config.devices ?? []).flatMap((device) => ["--device", device]),
|
|
150
|
+
...(config.cpus ? ["--cpus", String(config.cpus)] : []),
|
|
151
|
+
...(config.memoryMb ? ["--memory", `${config.memoryMb}m`] : []),
|
|
152
|
+
"--entrypoint",
|
|
153
|
+
"sleep",
|
|
154
|
+
image,
|
|
155
|
+
"infinity",
|
|
156
|
+
];
|
|
157
|
+
let closed = false, releasing;
|
|
158
|
+
let unregister = () => { };
|
|
159
|
+
const release = () => {
|
|
160
|
+
closed = true;
|
|
161
|
+
releasing ??= call(["rm", "--force", name])
|
|
162
|
+
.then(() => {
|
|
163
|
+
unregister();
|
|
164
|
+
})
|
|
165
|
+
.catch((error) => {
|
|
166
|
+
releasing = undefined;
|
|
167
|
+
throw error;
|
|
168
|
+
});
|
|
169
|
+
return releasing;
|
|
170
|
+
};
|
|
171
|
+
try {
|
|
172
|
+
await call(args, {
|
|
173
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
174
|
+
});
|
|
175
|
+
await call(["start", name], context.signal ? { signal: context.signal } : {});
|
|
176
|
+
for (const parent of fileParents)
|
|
177
|
+
await call([
|
|
178
|
+
"exec",
|
|
179
|
+
"--user",
|
|
180
|
+
"0:0",
|
|
181
|
+
name,
|
|
182
|
+
"sh",
|
|
183
|
+
"-c",
|
|
184
|
+
`mkdir -p ${quote(parent)} && chown ${user.uid}:${user.gid} ${quote(parent)}`,
|
|
185
|
+
]);
|
|
186
|
+
if (fileParents.size)
|
|
187
|
+
await call([
|
|
188
|
+
"exec",
|
|
189
|
+
"--user",
|
|
190
|
+
"0:0",
|
|
191
|
+
name,
|
|
192
|
+
"chown",
|
|
193
|
+
`${user.uid}:${user.gid}`,
|
|
194
|
+
home,
|
|
195
|
+
]);
|
|
196
|
+
await call([
|
|
197
|
+
"exec",
|
|
198
|
+
name,
|
|
199
|
+
"sh",
|
|
200
|
+
"-c",
|
|
201
|
+
"command -v setsid >/dev/null && command -v kill >/dev/null",
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
catch (cause) {
|
|
205
|
+
try {
|
|
206
|
+
await release();
|
|
207
|
+
}
|
|
208
|
+
catch (cleanup) {
|
|
209
|
+
throw new AggregateError([cause, cleanup], "Sandbox provisioning and cleanup failed");
|
|
210
|
+
}
|
|
211
|
+
throw cause;
|
|
212
|
+
}
|
|
213
|
+
unregister = registerCleanup(release, () => {
|
|
214
|
+
if (!closed)
|
|
215
|
+
execFileSync(engine, ["rm", "--force", name], {
|
|
216
|
+
timeout: 10_000,
|
|
217
|
+
stdio: "ignore",
|
|
218
|
+
windowsHide: true,
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
root,
|
|
223
|
+
home,
|
|
224
|
+
async invoke(command) {
|
|
225
|
+
if (closed)
|
|
226
|
+
throw new OutpostError("provider", "Container sandbox is closed");
|
|
227
|
+
command.signal?.throwIfAborted();
|
|
228
|
+
const id = randomUUID();
|
|
229
|
+
const pidFile = `/tmp/outpost-${id}.pid`;
|
|
230
|
+
const variables = { ...env, ...command.variables };
|
|
231
|
+
invariant(Object.keys(variables).every((key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)), "Invalid environment key");
|
|
232
|
+
const environment = Object.fromEntries(Object.entries(variables).map(([key, value], index) => [
|
|
233
|
+
`OUTPOST_VALUE_${index}`,
|
|
234
|
+
value,
|
|
235
|
+
]));
|
|
236
|
+
const exports = Object.keys(variables)
|
|
237
|
+
.map((key, index) => `export ${key}="$OUTPOST_VALUE_${index}"; unset OUTPOST_VALUE_${index};`)
|
|
238
|
+
.join(" ");
|
|
239
|
+
const flags = [
|
|
240
|
+
"exec",
|
|
241
|
+
command.interactive && process.stdin.isTTY ? "-it" : "-i",
|
|
242
|
+
...(command.elevated ? ["--user", "0:0"] : []),
|
|
243
|
+
"--workdir",
|
|
244
|
+
command.directory ?? root,
|
|
245
|
+
...Object.keys(environment).flatMap((key) => ["--env", key]),
|
|
246
|
+
name,
|
|
247
|
+
];
|
|
248
|
+
const wrapper = `${exports} echo $$ > ${quote(pidFile)}; test ! -f ${quote(pidFile + ".cancel")} || exit 130; exec "$@"`;
|
|
249
|
+
const { directory: _directory, ...invocation } = command;
|
|
250
|
+
let interrupted = false;
|
|
251
|
+
try {
|
|
252
|
+
return await executor({
|
|
253
|
+
...invocation,
|
|
254
|
+
executable: engine,
|
|
255
|
+
arguments: [
|
|
256
|
+
...flags,
|
|
257
|
+
"setsid",
|
|
258
|
+
...(command.interactive ? ["--wait"] : []),
|
|
259
|
+
...(command.interactive && process.stdin.isTTY
|
|
260
|
+
? ["--ctty"]
|
|
261
|
+
: []),
|
|
262
|
+
"sh",
|
|
263
|
+
"-c",
|
|
264
|
+
wrapper,
|
|
265
|
+
"outpost",
|
|
266
|
+
command.executable,
|
|
267
|
+
...(command.arguments ?? []),
|
|
268
|
+
],
|
|
269
|
+
variables: environment,
|
|
270
|
+
retain: command.retain ?? config.retain ?? 65_536,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
catch (cause) {
|
|
274
|
+
interrupted = true;
|
|
275
|
+
try {
|
|
276
|
+
await call([
|
|
277
|
+
"exec",
|
|
278
|
+
...(command.elevated ? ["--user", "0:0"] : []),
|
|
279
|
+
name,
|
|
280
|
+
"sh",
|
|
281
|
+
"-c",
|
|
282
|
+
`touch ${quote(pidFile + ".cancel")}; if [ -f ${quote(pidFile)} ]; then p=$(cat ${quote(pidFile)}); kill -TERM -"$p" 2>/dev/null || true; sleep 0.1; kill -KILL -"$p" 2>/dev/null || true; fi`,
|
|
283
|
+
]);
|
|
284
|
+
}
|
|
285
|
+
catch (cleanup) {
|
|
286
|
+
throw new AggregateError([cause, cleanup], "Command cancellation could not be confirmed");
|
|
287
|
+
}
|
|
288
|
+
throw cause;
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
if (!closed && !interrupted)
|
|
292
|
+
await call(["exec", name, "rm", "-f", pidFile]).catch(() => undefined);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
async upload(source, destination, options = {}) {
|
|
296
|
+
options.signal?.throwIfAborted();
|
|
297
|
+
await call(["exec", name, "mkdir", "-p", posix.dirname(destination)], options);
|
|
298
|
+
await call(["cp", source, `${name}:${destination}`], options);
|
|
299
|
+
},
|
|
300
|
+
async download(source, destination, options = {}) {
|
|
301
|
+
options.signal?.throwIfAborted();
|
|
302
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
303
|
+
await call(["cp", `${name}:${source}`, destination], options);
|
|
304
|
+
},
|
|
305
|
+
release,
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=container.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/providers/container.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { execFileSync } from \"node:child_process\";\nimport { mkdir, stat } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, posix, relative } from \"node:path\";\nimport { OutpostError, invariant } from \"../domain/errors.ts\";\nimport type {\n Command,\n SandboxProvider,\n Variables,\n Volume,\n} from \"../domain/ports.ts\";\nimport { expandPath } from \"../infrastructure/files.ts\";\nimport {\n executeProcess,\n quote,\n requireSuccess,\n} from \"../infrastructure/process.ts\";\nimport type { Executor } from \"../infrastructure/process.ts\";\nimport { registerCleanup } from \"../infrastructure/shutdown.ts\";\n\nexport interface ContainerOptions {\n readonly image?: string;\n readonly user?: { readonly uid: number; readonly gid: number };\n readonly volumes?: readonly Volume[];\n readonly variables?: Variables;\n readonly networks?: string | readonly string[];\n readonly groups?: readonly (string | number)[];\n readonly devices?: readonly string[];\n readonly cpus?: number;\n readonly memoryMb?: number;\n readonly label?: \"z\" | \"Z\" | false;\n readonly retain?: number;\n readonly userns?: \"keep-id\" | false;\n}\n\nexport function imageName(repository: string): string {\n return `outpost:${\n basename(repository)\n .toLowerCase()\n .replace(/[^a-z0-9_.-]/g, \"-\") || \"workspace\"\n }`;\n}\n\nexport function containerProvider(\n engine: \"docker\" | \"podman\",\n options: ContainerOptions = {},\n executor: Executor = executeProcess,\n platform: NodeJS.Platform = process.platform,\n): SandboxProvider {\n const config = { ...options };\n if (config.cpus !== undefined)\n invariant(\n Number.isFinite(config.cpus) && config.cpus > 0,\n \"cpus must be positive\",\n );\n if (config.memoryMb !== undefined)\n invariant(\n Number.isSafeInteger(config.memoryMb) && config.memoryMb >= 64,\n \"memoryMb must be at least 64\",\n );\n return {\n name: engine,\n placement: \"mounted\",\n variables: { ...config.variables },\n async acquire(context) {\n context.signal?.throwIfAborted();\n const name = `outpost-${randomUUID()}`;\n const image = config.image ?? imageName(context.repository);\n const user = config.user ?? {\n uid: process.getuid?.() ?? 1000,\n gid: process.getgid?.() ?? 1000,\n };\n invariant(\n Number.isInteger(user.uid) &&\n user.uid >= 0 &&\n Number.isInteger(user.gid) &&\n user.gid >= 0,\n \"Invalid container user\",\n );\n const call = (args: readonly string[], extra: Partial<Command> = {}) =>\n requireSuccess(\n { executable: engine, arguments: args, deadlineMs: 60_000, ...extra },\n executor,\n );\n if (engine === \"podman\" && platform === \"darwin\") {\n const machines = await call([\"machine\", \"list\", \"--format\", \"json\"]);\n const entries: unknown = JSON.parse(machines.stdout);\n invariant(\n Array.isArray(entries) &&\n entries.some(\n (machine) =>\n machine.Running === true || machine.State === \"running\",\n ),\n \"Start a Podman machine with podman machine start before creating a sandbox\",\n );\n }\n const inspection = await call([\n \"image\",\n \"inspect\",\n \"--format\",\n \"{{.Config.User}}\",\n image,\n ]);\n const configuredUser = inspection.stdout.trim().split(\":\")[0];\n if (\n !config.user &&\n configuredUser &&\n /^\\d+$/.test(configuredUser) &&\n Number(configuredUser) !== user.uid\n )\n throw new OutpostError(\n \"provider\",\n `Image UID ${configuredUser} does not match requested UID ${user.uid}`,\n { image, user },\n );\n const root = \"/workspace\",\n home = \"/home/agent\";\n const env: Record<string, string> = {\n ...context.variables,\n HOME: home,\n GIT_CONFIG_COUNT: \"1\",\n GIT_CONFIG_KEY_0: \"safe.directory\",\n GIT_CONFIG_VALUE_0: \"*\",\n };\n const internal: Volume[] = [{ source: context.directory, target: root }];\n if (context.gitDirectories.length) {\n const common = context.gitDirectories.at(-1)!;\n const offset = relative(common, context.gitDirectories[0]!);\n const nested =\n !isAbsolute(offset) &&\n offset !== \"..\" &&\n !offset.startsWith(\"..\\\\\") &&\n !offset.startsWith(\"../\");\n internal.push({\n source: common,\n target: \"/outpost/git/common\",\n });\n if (!nested)\n internal.push({\n source: context.gitDirectories[0]!,\n target: \"/outpost/git/worktree\",\n });\n env.GIT_DIR = nested\n ? posix.join(\"/outpost/git/common\", offset.replaceAll(\"\\\\\", \"/\"))\n : \"/outpost/git/worktree\";\n env.GIT_COMMON_DIR = \"/outpost/git/common\";\n env.GIT_WORK_TREE = root;\n }\n const volumes: string[] = [];\n const fileParents = new Set<string>();\n for (const volume of [...internal, ...(config.volumes ?? [])]) {\n const source = expandPath(volume.source, context.repository);\n const info = await stat(source).catch(() => undefined);\n if (!info)\n throw new OutpostError(\n \"provider\",\n `Mount source is unavailable: ${source}`,\n );\n const destination = volume.target.replaceAll(\"\\\\\", \"/\");\n const target =\n destination === \"~\"\n ? home\n : destination.startsWith(\"~/\")\n ? posix.resolve(home, destination.slice(2))\n : posix.resolve(root, destination);\n if (info.isFile() && !internal.includes(volume)) {\n const parent = posix.dirname(target);\n invariant(\n parent === home || parent.startsWith(home + \"/\"),\n \"File mounts must live inside the agent home; mount the containing directory for other destinations\",\n );\n for (\n let folder = parent;\n folder !== home;\n folder = posix.dirname(folder)\n )\n fileParents.add(folder);\n }\n invariant(\n !source.includes(\",\") && !target.includes(\",\"),\n \"Mount paths cannot contain commas\",\n );\n if (config.label === false || platform !== \"linux\")\n volumes.push(\n \"--mount\",\n `type=bind,source=${source},target=${target}${volume.readOnly ? \",readonly\" : \"\"}`,\n );\n else\n volumes.push(\n \"--volume\",\n `${source}:${target}:${volume.readOnly ? \"ro,\" : \"\"}${config.label ?? \"z\"}`,\n );\n }\n const networks =\n typeof config.networks === \"string\"\n ? [config.networks]\n : (config.networks ?? []);\n const args = [\n \"create\",\n \"--name\",\n name,\n \"--init\",\n \"--user\",\n `${user.uid}:${user.gid}`,\n ...(engine === \"podman\" &&\n config.userns !== false &&\n (process.getuid?.() !== 0 || config.userns === \"keep-id\")\n ? [\n \"--userns\",\n config.user\n ? `keep-id:uid=${user.uid},gid=${user.gid}`\n : \"keep-id\",\n ]\n : []),\n \"--workdir\",\n root,\n \"--cap-drop\",\n \"ALL\",\n ...(fileParents.size ? [\"--cap-add\", \"CHOWN\"] : []),\n \"--security-opt\",\n \"no-new-privileges\",\n \"--tmpfs\",\n engine === \"podman\"\n ? \"/home/agent:rw,mode=1777\"\n : `/home/agent:rw,uid=${fileParents.size ? 0 : user.uid},gid=${fileParents.size ? 0 : user.gid},mode=0700`,\n ...volumes,\n ...networks.flatMap((network) => [\"--network\", network]),\n ...(config.groups ?? []).flatMap((group) => [\n \"--group-add\",\n String(group),\n ]),\n ...(config.devices ?? []).flatMap((device) => [\"--device\", device]),\n ...(config.cpus ? [\"--cpus\", String(config.cpus)] : []),\n ...(config.memoryMb ? [\"--memory\", `${config.memoryMb}m`] : []),\n \"--entrypoint\",\n \"sleep\",\n image,\n \"infinity\",\n ];\n let closed = false,\n releasing: Promise<void> | undefined;\n let unregister = () => {};\n const release = (): Promise<void> => {\n closed = true;\n releasing ??= call([\"rm\", \"--force\", name])\n .then(() => {\n unregister();\n })\n .catch((error) => {\n releasing = undefined;\n throw error;\n });\n return releasing;\n };\n try {\n await call(args, {\n ...(context.signal ? { signal: context.signal } : {}),\n });\n await call(\n [\"start\", name],\n context.signal ? { signal: context.signal } : {},\n );\n for (const parent of fileParents)\n await call([\n \"exec\",\n \"--user\",\n \"0:0\",\n name,\n \"sh\",\n \"-c\",\n `mkdir -p ${quote(parent)} && chown ${user.uid}:${user.gid} ${quote(parent)}`,\n ]);\n if (fileParents.size)\n await call([\n \"exec\",\n \"--user\",\n \"0:0\",\n name,\n \"chown\",\n `${user.uid}:${user.gid}`,\n home,\n ]);\n await call([\n \"exec\",\n name,\n \"sh\",\n \"-c\",\n \"command -v setsid >/dev/null && command -v kill >/dev/null\",\n ]);\n } catch (cause) {\n try {\n await release();\n } catch (cleanup) {\n throw new AggregateError(\n [cause, cleanup],\n \"Sandbox provisioning and cleanup failed\",\n );\n }\n throw cause;\n }\n unregister = registerCleanup(release, () => {\n if (!closed)\n execFileSync(engine, [\"rm\", \"--force\", name], {\n timeout: 10_000,\n stdio: \"ignore\",\n windowsHide: true,\n });\n });\n return {\n root,\n home,\n async invoke(command) {\n if (closed)\n throw new OutpostError(\"provider\", \"Container sandbox is closed\");\n command.signal?.throwIfAborted();\n const id = randomUUID();\n const pidFile = `/tmp/outpost-${id}.pid`;\n const variables = { ...env, ...command.variables };\n invariant(\n Object.keys(variables).every((key) =>\n /^[A-Za-z_][A-Za-z0-9_]*$/.test(key),\n ),\n \"Invalid environment key\",\n );\n const environment = Object.fromEntries(\n Object.entries(variables).map(([key, value], index) => [\n `OUTPOST_VALUE_${index}`,\n value,\n ]),\n );\n const exports = Object.keys(variables)\n .map(\n (key, index) =>\n `export ${key}=\"$OUTPOST_VALUE_${index}\"; unset OUTPOST_VALUE_${index};`,\n )\n .join(\" \");\n const flags = [\n \"exec\",\n command.interactive && process.stdin.isTTY ? \"-it\" : \"-i\",\n ...(command.elevated ? [\"--user\", \"0:0\"] : []),\n \"--workdir\",\n command.directory ?? root,\n ...Object.keys(environment).flatMap((key) => [\"--env\", key]),\n name,\n ];\n const wrapper = `${exports} echo $$ > ${quote(pidFile)}; test ! -f ${quote(pidFile + \".cancel\")} || exit 130; exec \"$@\"`;\n const { directory: _directory, ...invocation } = command;\n let interrupted = false;\n try {\n return await executor({\n ...invocation,\n executable: engine,\n arguments: [\n ...flags,\n \"setsid\",\n ...(command.interactive ? [\"--wait\"] : []),\n ...(command.interactive && process.stdin.isTTY\n ? [\"--ctty\"]\n : []),\n \"sh\",\n \"-c\",\n wrapper,\n \"outpost\",\n command.executable,\n ...(command.arguments ?? []),\n ],\n variables: environment,\n retain: command.retain ?? config.retain ?? 65_536,\n });\n } catch (cause) {\n interrupted = true;\n try {\n await call([\n \"exec\",\n ...(command.elevated ? [\"--user\", \"0:0\"] : []),\n name,\n \"sh\",\n \"-c\",\n `touch ${quote(pidFile + \".cancel\")}; if [ -f ${quote(pidFile)} ]; then p=$(cat ${quote(pidFile)}); kill -TERM -\"$p\" 2>/dev/null || true; sleep 0.1; kill -KILL -\"$p\" 2>/dev/null || true; fi`,\n ]);\n } catch (cleanup) {\n throw new AggregateError(\n [cause, cleanup],\n \"Command cancellation could not be confirmed\",\n );\n }\n throw cause;\n } finally {\n if (!closed && !interrupted)\n await call([\"exec\", name, \"rm\", \"-f\", pidFile]).catch(\n () => undefined,\n );\n }\n },\n async upload(source, destination, options = {}) {\n options.signal?.throwIfAborted();\n await call(\n [\"exec\", name, \"mkdir\", \"-p\", posix.dirname(destination)],\n options,\n );\n await call([\"cp\", source, `${name}:${destination}`], options);\n },\n async download(source, destination, options = {}) {\n options.signal?.throwIfAborted();\n await mkdir(dirname(destination), { recursive: true });\n await call([\"cp\", `${name}:${source}`, destination], options);\n },\n release,\n };\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Daytona, DaytonaConfig, CreateSandboxFromImageParams, CreateSandboxFromSnapshotParams } from "@daytona/sdk";
|
|
2
|
+
import type { SandboxProvider, Variables } from "../domain/ports.ts";
|
|
3
|
+
export interface DaytonaOptions {
|
|
4
|
+
readonly connection?: DaytonaConfig;
|
|
5
|
+
readonly create?: CreateSandboxFromImageParams | CreateSandboxFromSnapshotParams;
|
|
6
|
+
readonly variables?: Variables;
|
|
7
|
+
readonly root?: string;
|
|
8
|
+
readonly retain?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function daytona(options?: DaytonaOptions, connect?: (config?: DaytonaConfig) => Promise<Pick<Daytona, "create" | "delete">>): SandboxProvider;
|