@promigence/sdk 0.1.0-beta.4 → 0.1.0-beta.5
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/dist/cjs/client.d.ts +40 -47
- package/dist/cjs/client.js +19 -187
- package/dist/cjs/context.d.ts +119 -0
- package/dist/cjs/context.js +598 -0
- package/dist/cjs/core.d.ts +1 -1
- package/dist/cjs/core.js +2 -1
- package/dist/cjs/github.d.ts +27 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/models.gen.d.ts +118 -31
- package/dist/cjs/proxy.d.ts +20 -0
- package/dist/cjs/proxy.js +36 -0
- package/dist/cjs/pty.js +2 -3
- package/dist/cjs/sandbox.d.ts +8 -2
- package/dist/cjs/sandbox.js +3 -3
- package/dist/cjs/ssh.js +2 -3
- package/dist/client.d.ts +40 -47
- package/dist/client.js +18 -183
- package/dist/context.d.ts +119 -0
- package/dist/context.js +588 -0
- package/dist/core.d.ts +1 -1
- package/dist/core.js +2 -1
- package/dist/github.d.ts +27 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models.gen.d.ts +118 -31
- package/dist/proxy.d.ts +20 -0
- package/dist/proxy.js +35 -0
- package/dist/pty.js +2 -3
- package/dist/sandbox.d.ts +8 -2
- package/dist/sandbox.js +3 -3
- package/dist/ssh.js +2 -3
- package/package.json +1 -1
package/dist/cjs/client.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { type Bundle } from "./bundle.js";
|
|
|
5
5
|
import { type PtyExit, type PtySession } from "./pty.js";
|
|
6
6
|
import { type SshTunnel } from "./ssh.js";
|
|
7
7
|
import type * as M from "./models.gen.js";
|
|
8
|
-
import { type GithubConnect, type GithubDisconnected, type GithubRepos, type GithubStatus, type GithubUntracked } from "./github.js";
|
|
8
|
+
import { type ConnectRequest, type ConnectTicket, type GithubConnect, type GithubDisconnected, type GithubRepos, type GithubStatus, type GithubUntracked } from "./github.js";
|
|
9
9
|
import { type GitlabConnect, type GitlabDisconnected, type GitlabRepos, type GitlabStatus, type GitlabUntracked } from "./gitlab.js";
|
|
10
10
|
export type FetchLike = typeof globalThis.fetch;
|
|
11
11
|
/**
|
|
@@ -343,7 +343,8 @@ export interface RunReport {
|
|
|
343
343
|
[extra: string]: unknown;
|
|
344
344
|
}
|
|
345
345
|
/** A build of a snapshot (`POST /snapshots`, `GET /snapshots/builds/:id`). */
|
|
346
|
-
|
|
346
|
+
/** `built`: finished and stored with no verification command to run; done, like `verified`. */
|
|
347
|
+
export type BuildState = "queued" | "running" | "verified" | "built" | "rejected" | "failed";
|
|
347
348
|
export interface SecretRecord {
|
|
348
349
|
name: string;
|
|
349
350
|
/** Length of the stored value, so a caller can sanity-check without reading it. */
|
|
@@ -353,9 +354,9 @@ export interface SecretRecord {
|
|
|
353
354
|
updated_at?: string;
|
|
354
355
|
/**
|
|
355
356
|
* The hosts the secret is bound to. Absent when it is not bound: a sandbox that names it is
|
|
356
|
-
* then given the value. When present, the sandbox
|
|
357
|
-
*
|
|
358
|
-
*
|
|
357
|
+
* then given the value. When present, requests the sandbox sends to the host's address in
|
|
358
|
+
* `urls` are authenticated with the secret, a request made straight to `https://<host>/` is not,
|
|
359
|
+
* and the value stays out of the sandbox unless a run asks for it (`raw`).
|
|
359
360
|
*/
|
|
360
361
|
hosts?: string[];
|
|
361
362
|
/**
|
|
@@ -364,7 +365,7 @@ export interface SecretRecord {
|
|
|
364
365
|
*/
|
|
365
366
|
urls?: string[];
|
|
366
367
|
/**
|
|
367
|
-
* The variables a sandbox that names this secret is also given, each
|
|
368
|
+
* The variables a sandbox that names this secret is also given, each set to its host's
|
|
368
369
|
* address in `urls`: `OPENAI_BASE_URL` for `api.openai.com`, `ANTHROPIC_BASE_URL` for
|
|
369
370
|
* `api.anthropic.com`. Absent when there is none. A variable the caller sets itself wins.
|
|
370
371
|
*/
|
|
@@ -379,10 +380,10 @@ export interface SecretRecord {
|
|
|
379
380
|
scan_responses?: boolean;
|
|
380
381
|
}
|
|
381
382
|
/**
|
|
382
|
-
* `secrets.set` options: bind the secret to hosts.
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
383
|
+
* `secrets.set` options: bind the secret to hosts. Requests a sandbox sends to the host's address
|
|
384
|
+
* the answer gives in `urls` (and to `$OPENAI_BASE_URL` / `$ANTHROPIC_BASE_URL` for those
|
|
385
|
+
* providers) are authenticated with it, one sent straight to the host is not, and the value stays
|
|
386
|
+
* out of the sandbox unless a run asks for it (`raw: "refused"` forbids that; the default allows it).
|
|
386
387
|
*
|
|
387
388
|
* Every option left out keeps what the secret already has, so setting a bound secret again with
|
|
388
389
|
* a new value (a rotation) keeps its binding. `hosts: []` removes it.
|
|
@@ -1478,8 +1479,8 @@ export interface SandboxNetworkCut {
|
|
|
1478
1479
|
* secrets: ["OPENAI_API_KEY", { name: "work_secret_7", as: "ANTHROPIC_WORK_SECRET" }]
|
|
1479
1480
|
* ```
|
|
1480
1481
|
*
|
|
1481
|
-
* A secret bound to hosts (`secrets.set(name, value, { hosts })`)
|
|
1482
|
-
* `mode: "raw"` asks for the value itself, and is refused for one stored with `raw: "refused"`.
|
|
1482
|
+
* A secret bound to hosts (`secrets.set(name, value, { hosts })`) does not put its value in the
|
|
1483
|
+
* sandbox; `mode: "raw"` asks for the value itself, and is refused for one stored with `raw: "refused"`.
|
|
1483
1484
|
*/
|
|
1484
1485
|
export type SecretBinding = string | {
|
|
1485
1486
|
name: string;
|
|
@@ -1649,6 +1650,14 @@ export interface SnapshotCreateOpts {
|
|
|
1649
1650
|
verify?: VerifySpec;
|
|
1650
1651
|
/** Image forms: a background process started before the snapshot, so forks inherit it running. */
|
|
1651
1652
|
start?: string | string[];
|
|
1653
|
+
/**
|
|
1654
|
+
* Image and Dockerfile forms: environment variables for every command, terminal and SSH
|
|
1655
|
+
* session in this snapshot's sandboxes (and its own setup, verify and start), set over the
|
|
1656
|
+
* image's `ENV` as `docker run -e` sets them. A value may build on what the image sets with
|
|
1657
|
+
* `${containerEnv:NAME}` (or `${containerEnv:NAME:default}`):
|
|
1658
|
+
* `{ PATH: "${containerEnv:PATH}:/opt/tools/bin" }`.
|
|
1659
|
+
*/
|
|
1660
|
+
env?: Record<string, string>;
|
|
1652
1661
|
tier?: Tier;
|
|
1653
1662
|
/** Image forms: the name of a stored secret holding the registry credentials. */
|
|
1654
1663
|
registrySecret?: string;
|
|
@@ -1772,28 +1781,6 @@ export declare function imageRefNamesVersion(image: string): boolean;
|
|
|
1772
1781
|
export declare function checkImageVersioned(image: string): void;
|
|
1773
1782
|
/** A git context, pinned: `https://github.com/org/app@<40 hex>`. A branch is not a pin. */
|
|
1774
1783
|
export declare const CONTEXT_GIT_RE: RegExp;
|
|
1775
|
-
/** The prefix a packed directory travels under. */
|
|
1776
|
-
export declare const CONTEXT_TAR_PREFIX = "tar;base64,";
|
|
1777
|
-
/**
|
|
1778
|
-
* Directories never worth sending. `.dockerignore` is **not** applied to a context that fits: it
|
|
1779
|
-
* is sent with the context and the build applies it, so honouring it twice could only disagree
|
|
1780
|
-
* with the build. The cost is that editing an ignored file still changes the snapshot's
|
|
1781
|
-
* identity, which is the safe direction to be wrong in. Only a context over the limit — one that
|
|
1782
|
-
* would be refused anyway — is packed again without what its `.dockerignore` excludes.
|
|
1783
|
-
*/
|
|
1784
|
-
export declare const CONTEXT_SKIP: Set<string>;
|
|
1785
|
-
/**
|
|
1786
|
-
* A `.dockerignore`, as the build reads it: one pattern a line, `#` comments, `!` re-includes,
|
|
1787
|
-
* Go `filepath.Match` syntax plus `**`, anchored at the context root, and a pattern that matches a
|
|
1788
|
-
* directory matches everything under it. The last pattern that matches a path decides. Returns
|
|
1789
|
-
* `undefined` for a file with no patterns.
|
|
1790
|
-
*/
|
|
1791
|
-
export declare function dockerignoreMatcher(text: string): {
|
|
1792
|
-
ignored: (rel: string) => boolean;
|
|
1793
|
-
exceptions: boolean;
|
|
1794
|
-
} | undefined;
|
|
1795
|
-
/** The server's limit, refused here so a 9 MB context does not cross the wire to be told no. */
|
|
1796
|
-
export declare const CONTEXT_MAX_BYTES: number;
|
|
1797
1784
|
/**
|
|
1798
1785
|
* What in a repository URL can carry a credential: a user name, password or token (`https://TOKEN@…`,
|
|
1799
1786
|
* `https://user:pass@…`, `:token@`, a percent-encoded `@`, any `@` in a scheme URL, the scp form's
|
|
@@ -1813,19 +1800,6 @@ export declare const REPO_CREDENTIALS_FIX: string;
|
|
|
1813
1800
|
export declare function checkRepoUrl(url: unknown, field: string): void;
|
|
1814
1801
|
/** Refuse a context that is neither a pinned git URL nor a packed directory. */
|
|
1815
1802
|
export declare function checkDockerfileContext(context: unknown): asserts context is string;
|
|
1816
|
-
/**
|
|
1817
|
-
* Pack a directory into the string `dockerfile.context` takes.
|
|
1818
|
-
*
|
|
1819
|
-
* The archive is only transport: the snapshot's identity is computed from the *entries* server
|
|
1820
|
-
* side, so this does not have to agree byte-for-byte with `sdk-py`'s packer — and two calls on
|
|
1821
|
-
* an unchanged directory give the same snapshot either way.
|
|
1822
|
-
*/
|
|
1823
|
-
export declare function packContext(dir: string, opts?: {
|
|
1824
|
-
maxBytes?: number;
|
|
1825
|
-
keep?: readonly string[];
|
|
1826
|
-
}): string;
|
|
1827
|
-
/** The Dockerfile's path as the context sees it, for `dockerfile.path`. */
|
|
1828
|
-
export declare function contextRelative(contextDir: string, dockerfilePath: string): string;
|
|
1829
1803
|
/**
|
|
1830
1804
|
* The `POST /snapshots` body for one of its three forms; throws `InvalidArgumentError` before
|
|
1831
1805
|
* anything is sent. Kept field-for-field with `sdk-py` (`_ops.snapshot_create_body`) so the two
|
|
@@ -2115,6 +2089,14 @@ export declare class PromigenceClient {
|
|
|
2115
2089
|
status: () => Promise<GithubStatus>;
|
|
2116
2090
|
/** `POST /integrations/github/connect` — the install link (15 minutes); open it in a browser. */
|
|
2117
2091
|
connect: () => Promise<GithubConnect>;
|
|
2092
|
+
/**
|
|
2093
|
+
* `POST /integrations/github/requests` — a connection started for you in the console: open
|
|
2094
|
+
* `connect_url` in a browser signed in to the console as you (a 404 means the console cannot start
|
|
2095
|
+
* it here: use `connect()`).
|
|
2096
|
+
*/
|
|
2097
|
+
ticket: () => Promise<ConnectTicket>;
|
|
2098
|
+
/** `GET /integrations/github/requests/:id` — one of your connection requests: `pending`, or how it ended. */
|
|
2099
|
+
request: (requestId: string) => Promise<ConnectRequest>;
|
|
2118
2100
|
/** `DELETE /integrations/github/installations/:id` — nothing is built from that account any more. */
|
|
2119
2101
|
disconnect: (installationId: number | string) => Promise<GithubDisconnected>;
|
|
2120
2102
|
/** `GET /integrations/github/repos` — one page (at most 100) across every connection. */
|
|
@@ -2135,6 +2117,14 @@ export declare class PromigenceClient {
|
|
|
2135
2117
|
status: () => Promise<GitlabStatus>;
|
|
2136
2118
|
/** `POST /integrations/gitlab/connect` — the authorize link (15 minutes); open it in a browser. */
|
|
2137
2119
|
connect: () => Promise<GitlabConnect>;
|
|
2120
|
+
/**
|
|
2121
|
+
* `POST /integrations/gitlab/requests` — a connection started for you in the console: open
|
|
2122
|
+
* `connect_url` in a browser signed in to the console as you (a 404 means the console cannot start
|
|
2123
|
+
* it here: use `connect()`).
|
|
2124
|
+
*/
|
|
2125
|
+
ticket: () => Promise<ConnectTicket>;
|
|
2126
|
+
/** `GET /integrations/gitlab/requests/:id` — one of your connection requests: `pending`, or how it ended. */
|
|
2127
|
+
request: (requestId: string) => Promise<ConnectRequest>;
|
|
2138
2128
|
/** `DELETE /integrations/gitlab/connections/:id` — revoked on GitLab; nothing is built through it any more. */
|
|
2139
2129
|
disconnect: (connectionId: string) => Promise<GitlabDisconnected>;
|
|
2140
2130
|
/** `GET /integrations/gitlab/repos` — one page (at most 100) of one connection's projects. */
|
|
@@ -2636,6 +2626,9 @@ export declare class PromigenceClient {
|
|
|
2636
2626
|
* A failure mid-loop leaves a **prefix** in the sandbox, not a corrupt file, and the
|
|
2637
2627
|
* thrown error carries how far it got (`written` in the error's evidence).
|
|
2638
2628
|
* Re-running the same `put` is safe: chunk 0 truncates.
|
|
2629
|
+
*
|
|
2630
|
+
* `mode` (`0o755`) sets the file's permission bits. Without it a file that already exists
|
|
2631
|
+
* keeps its mode, as a shell `>` does, and a new one is `0o644`.
|
|
2639
2632
|
*/
|
|
2640
2633
|
put: (sandboxId: string, path: string, data: string | Uint8Array, opts?: {
|
|
2641
2634
|
mode?: number;
|
package/dist/cjs/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PromigenceClient = exports.LONG_POLL_MS = exports.REPO_CREDENTIALS_FIX = exports.
|
|
3
|
+
exports.PromigenceClient = exports.LONG_POLL_MS = exports.REPO_CREDENTIALS_FIX = exports.CONTEXT_GIT_RE = exports.IMAGE_DIGEST_RE = exports.MAX_SOURCES = exports.NOTICE_HEADER = exports.isWaiting = exports.FILE_TIMEOUT_MS = void 0;
|
|
4
4
|
exports.execOutputBytes = execOutputBytes;
|
|
5
5
|
exports.configDir = configDir;
|
|
6
6
|
exports.credentialsPath = credentialsPath;
|
|
@@ -18,13 +18,10 @@ exports.imageRefProblem = imageRefProblem;
|
|
|
18
18
|
exports.checkImageRef = checkImageRef;
|
|
19
19
|
exports.imageRefNamesVersion = imageRefNamesVersion;
|
|
20
20
|
exports.checkImageVersioned = checkImageVersioned;
|
|
21
|
-
exports.dockerignoreMatcher = dockerignoreMatcher;
|
|
22
21
|
exports.repoUrlCredential = repoUrlCredential;
|
|
23
22
|
exports.withoutPin = withoutPin;
|
|
24
23
|
exports.checkRepoUrl = checkRepoUrl;
|
|
25
24
|
exports.checkDockerfileContext = checkDockerfileContext;
|
|
26
|
-
exports.packContext = packContext;
|
|
27
|
-
exports.contextRelative = contextRelative;
|
|
28
25
|
exports.snapshotCreateBody = snapshotCreateBody;
|
|
29
26
|
exports.errorFor = errorFor;
|
|
30
27
|
exports.isKeyedCreate = isKeyedCreate;
|
|
@@ -44,6 +41,7 @@ const caller_js_1 = require("./caller.js");
|
|
|
44
41
|
const pty_js_1 = require("./pty.js");
|
|
45
42
|
const ssh_js_1 = require("./ssh.js");
|
|
46
43
|
const http2_js_1 = require("./http2.js");
|
|
44
|
+
const context_js_1 = require("./context.js");
|
|
47
45
|
const proxy_js_1 = require("./proxy.js");
|
|
48
46
|
const trace_js_1 = require("./trace.js");
|
|
49
47
|
const keyed_gen_js_1 = require("./keyed.gen.js");
|
|
@@ -443,81 +441,6 @@ function checkImageVersioned(image) {
|
|
|
443
441
|
throw new core_js_1.InvalidArgumentError(`image ${JSON.stringify(ref)} names no tag or digest, so it would be pinned to whatever ${ref}:latest is today`, `Name the version: a tag (${ref}:<version>), ${ref}:latest if you mean the moving tag, or a digest (${ref}@sha256:<64 hex>).`);
|
|
444
442
|
}
|
|
445
443
|
exports.CONTEXT_GIT_RE = /^(?:https?:\/\/|ssh:\/\/|git:\/\/|git@)\S+@[0-9a-f]{40}$/i;
|
|
446
|
-
exports.CONTEXT_TAR_PREFIX = "tar;base64,";
|
|
447
|
-
exports.CONTEXT_SKIP = new Set([".git", "node_modules", "__pycache__", ".venv", ".DS_Store"]);
|
|
448
|
-
function dockerignoreMatcher(text) {
|
|
449
|
-
const rules = [];
|
|
450
|
-
for (const raw of text.split(/\r?\n/)) {
|
|
451
|
-
let line = raw.trim();
|
|
452
|
-
if (!line || line.startsWith("#"))
|
|
453
|
-
continue;
|
|
454
|
-
const exclusion = line.startsWith("!");
|
|
455
|
-
if (exclusion)
|
|
456
|
-
line = line.slice(1).trim();
|
|
457
|
-
const parts = [];
|
|
458
|
-
for (const seg of line.split("/")) {
|
|
459
|
-
if (seg === "" || seg === ".")
|
|
460
|
-
continue;
|
|
461
|
-
if (seg === "..")
|
|
462
|
-
parts.pop();
|
|
463
|
-
else
|
|
464
|
-
parts.push(seg);
|
|
465
|
-
}
|
|
466
|
-
const pattern = parts.join("/");
|
|
467
|
-
if (!pattern)
|
|
468
|
-
continue;
|
|
469
|
-
let re = "^";
|
|
470
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
471
|
-
const ch = pattern[i];
|
|
472
|
-
if (ch === "*") {
|
|
473
|
-
if (pattern[i + 1] === "*") {
|
|
474
|
-
i++;
|
|
475
|
-
if (pattern[i + 1] === "/")
|
|
476
|
-
i++;
|
|
477
|
-
re += i + 1 >= pattern.length ? ".*" : "(.*/)?";
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
re += "[^/]*";
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
else if (ch === "?") {
|
|
484
|
-
re += "[^/]";
|
|
485
|
-
}
|
|
486
|
-
else if (ch === "\\") {
|
|
487
|
-
re += i + 1 < pattern.length ? `\\${pattern[++i]}` : "\\\\";
|
|
488
|
-
}
|
|
489
|
-
else if (ch === "[" || ch === "]") {
|
|
490
|
-
re += ch;
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
re += /[.+()|{}$]/.test(ch) ? `\\${ch}` : ch;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
try {
|
|
497
|
-
rules.push({ re: new RegExp(`${re}$`), exclusion });
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (rules.length === 0)
|
|
503
|
-
return undefined;
|
|
504
|
-
const ignored = (rel) => {
|
|
505
|
-
const dirs = rel.split("/");
|
|
506
|
-
let matched = false;
|
|
507
|
-
for (const r of rules) {
|
|
508
|
-
if (r.exclusion !== matched)
|
|
509
|
-
continue;
|
|
510
|
-
let hit = r.re.test(rel);
|
|
511
|
-
for (let i = 1; !hit && i < dirs.length; i++)
|
|
512
|
-
hit = r.re.test(dirs.slice(0, i).join("/"));
|
|
513
|
-
if (hit)
|
|
514
|
-
matched = !r.exclusion;
|
|
515
|
-
}
|
|
516
|
-
return matched;
|
|
517
|
-
};
|
|
518
|
-
return { ignored, exceptions: rules.some((r) => r.exclusion) };
|
|
519
|
-
}
|
|
520
|
-
exports.CONTEXT_MAX_BYTES = 8 * 1024 * 1024;
|
|
521
444
|
function repoUrlCredential(url) {
|
|
522
445
|
const u = url.trim();
|
|
523
446
|
const sep = u.indexOf("://");
|
|
@@ -556,123 +479,17 @@ function checkRepoUrl(url, field) {
|
|
|
556
479
|
}
|
|
557
480
|
function checkDockerfileContext(context) {
|
|
558
481
|
const c = typeof context === "string" ? context.trim() : "";
|
|
559
|
-
if (!c.startsWith(
|
|
482
|
+
if (!c.startsWith(context_js_1.CONTEXT_TAR_PREFIX))
|
|
560
483
|
checkRepoUrl(withoutPin(c), "dockerfile.context");
|
|
561
484
|
if (!c) {
|
|
562
485
|
throw new core_js_1.InvalidArgumentError("snapshots.create dockerfile needs a context", "Pass { dockerfile: { context: 'https://github.com/org/app@<40-char sha>', path: 'Dockerfile' } }, " +
|
|
563
486
|
"or pack a directory with packContext('./app').");
|
|
564
487
|
}
|
|
565
|
-
if (c.startsWith(
|
|
488
|
+
if (c.startsWith(context_js_1.CONTEXT_TAR_PREFIX) || exports.CONTEXT_GIT_RE.test(c))
|
|
566
489
|
return;
|
|
567
490
|
throw new core_js_1.InvalidArgumentError(`dockerfile.context must be a git URL pinned to a full 40-character sha, got ${JSON.stringify(context)}`, "A branch or a tag is not a pin: pass URL@<40-char sha> (git rev-parse HEAD), or pack a local " +
|
|
568
491
|
"directory with packContext('./app').");
|
|
569
492
|
}
|
|
570
|
-
function packContext(dir, opts = {}) {
|
|
571
|
-
const max = opts.maxBytes ?? exports.CONTEXT_MAX_BYTES;
|
|
572
|
-
const keep = new Set([".dockerignore", ...(opts.keep ?? [])]);
|
|
573
|
-
const collect = (ignore) => {
|
|
574
|
-
const entries = [];
|
|
575
|
-
let total = 0;
|
|
576
|
-
const walk = (abs, rel) => {
|
|
577
|
-
for (const name of (0, node_fs_1.readdirSync)(abs).sort()) {
|
|
578
|
-
if (exports.CONTEXT_SKIP.has(name))
|
|
579
|
-
continue;
|
|
580
|
-
const childAbs = (0, node_path_1.join)(abs, name);
|
|
581
|
-
const childRel = rel ? `${rel}/${name}` : name;
|
|
582
|
-
const st = (0, node_fs_1.lstatSync)(childAbs);
|
|
583
|
-
const dropped = ignore !== undefined && !keep.has(childRel) && ignore.ignored(childRel);
|
|
584
|
-
if (st.isDirectory()) {
|
|
585
|
-
if (dropped && !ignore.exceptions)
|
|
586
|
-
continue;
|
|
587
|
-
if (!walk(childAbs, childRel))
|
|
588
|
-
return false;
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
if (dropped)
|
|
592
|
-
continue;
|
|
593
|
-
if (st.isSymbolicLink()) {
|
|
594
|
-
entries.push({ path: childRel, mode: 0o777, body: Buffer.alloc(0), link: (0, node_fs_1.readlinkSync)(childAbs) });
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
if (!st.isFile())
|
|
598
|
-
continue;
|
|
599
|
-
total += st.size;
|
|
600
|
-
if (total > max)
|
|
601
|
-
return false;
|
|
602
|
-
entries.push({ path: childRel, mode: st.mode & 0o111 ? 0o755 : 0o644, body: (0, node_fs_1.readFileSync)(childAbs) });
|
|
603
|
-
}
|
|
604
|
-
return true;
|
|
605
|
-
};
|
|
606
|
-
return walk(dir, "") ? entries : undefined;
|
|
607
|
-
};
|
|
608
|
-
let entries = collect();
|
|
609
|
-
if (entries === undefined) {
|
|
610
|
-
let ignore;
|
|
611
|
-
try {
|
|
612
|
-
ignore = dockerignoreMatcher((0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, ".dockerignore"), "utf8"));
|
|
613
|
-
}
|
|
614
|
-
catch {
|
|
615
|
-
ignore = undefined;
|
|
616
|
-
}
|
|
617
|
-
if (ignore !== undefined)
|
|
618
|
-
entries = collect(ignore);
|
|
619
|
-
if (entries === undefined) {
|
|
620
|
-
throw new core_js_1.InvalidArgumentError(`the build context is over ${max} bytes${ignore ? " even without what its .dockerignore excludes" : ""}`, `${ignore ? "Exclude more in .dockerignore" : "Add a .dockerignore"}, point --context at a smaller directory, or pass a git URL pinned to a full 40-character sha.`);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
if (entries.length === 0) {
|
|
624
|
-
throw new core_js_1.InvalidArgumentError(`the build context ${JSON.stringify(dir)} is empty`, "Point --context at the directory holding your Dockerfile.");
|
|
625
|
-
}
|
|
626
|
-
return exports.CONTEXT_TAR_PREFIX + tarOf(entries).toString("base64");
|
|
627
|
-
}
|
|
628
|
-
function contextRelative(contextDir, dockerfilePath) {
|
|
629
|
-
const rel = (0, node_path_1.relative)(contextDir, dockerfilePath).split(node_path_1.sep).join("/");
|
|
630
|
-
if (!rel || rel.startsWith("..")) {
|
|
631
|
-
throw new core_js_1.InvalidArgumentError(`${JSON.stringify(dockerfilePath)} is not inside the context ${JSON.stringify(contextDir)}`, "Pass --context DIR with the Dockerfile underneath it.");
|
|
632
|
-
}
|
|
633
|
-
return rel;
|
|
634
|
-
}
|
|
635
|
-
function tarOf(entries) {
|
|
636
|
-
const out = [];
|
|
637
|
-
for (const e of [...entries].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))) {
|
|
638
|
-
const head = Buffer.alloc(512);
|
|
639
|
-
let name = e.path;
|
|
640
|
-
let prefix = "";
|
|
641
|
-
if (Buffer.byteLength(name) > 100) {
|
|
642
|
-
const cut = name.lastIndexOf("/", name.length - 100);
|
|
643
|
-
if (cut <= 0 || Buffer.byteLength(name.slice(cut + 1)) > 100 || Buffer.byteLength(name.slice(0, cut)) > 155) {
|
|
644
|
-
throw new core_js_1.InvalidArgumentError(`the path ${JSON.stringify(e.path)} is too long for a build context`, "Shorten the path or pass a git URL pinned to a full 40-character sha.");
|
|
645
|
-
}
|
|
646
|
-
prefix = name.slice(0, cut);
|
|
647
|
-
name = name.slice(cut + 1);
|
|
648
|
-
}
|
|
649
|
-
const put = (s, at, len) => { head.write(s.slice(0, len), at, len, "utf8"); };
|
|
650
|
-
const oct = (n, at, len) => { put(n.toString(8).padStart(len - 1, "0") + "\0", at, len); };
|
|
651
|
-
put(name, 0, 100);
|
|
652
|
-
oct(e.mode, 100, 8);
|
|
653
|
-
oct(0, 108, 8);
|
|
654
|
-
oct(0, 116, 8);
|
|
655
|
-
oct(e.link === undefined ? e.body.length : 0, 124, 12);
|
|
656
|
-
oct(0, 136, 12);
|
|
657
|
-
head.write(" ", 148, 8, "utf8");
|
|
658
|
-
head.write(e.link === undefined ? "0" : "2", 156, 1, "utf8");
|
|
659
|
-
if (e.link !== undefined)
|
|
660
|
-
put(e.link, 157, 100);
|
|
661
|
-
head.write("ustar\0", 257, 6, "binary");
|
|
662
|
-
head.write("00", 263, 2, "utf8");
|
|
663
|
-
put(prefix, 345, 155);
|
|
664
|
-
let sum = 0;
|
|
665
|
-
for (const b of head)
|
|
666
|
-
sum += b;
|
|
667
|
-
put(sum.toString(8).padStart(6, "0") + "\0 ", 148, 8);
|
|
668
|
-
out.push(head);
|
|
669
|
-
if (e.link === undefined && e.body.length > 0) {
|
|
670
|
-
out.push(e.body, Buffer.alloc((512 - (e.body.length % 512)) % 512));
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
out.push(Buffer.alloc(1024));
|
|
674
|
-
return Buffer.concat(out);
|
|
675
|
-
}
|
|
676
493
|
function sourcesBody(sources) {
|
|
677
494
|
if (sources.length > exports.MAX_SOURCES) {
|
|
678
495
|
throw new core_js_1.InvalidArgumentError(`snapshots.create takes at most ${exports.MAX_SOURCES} extra sources, got ${sources.length}`, "An environment assembled from more repositories than that is cheaper to build once as " +
|
|
@@ -720,6 +537,9 @@ function snapshotCreateBody(opts) {
|
|
|
720
537
|
let body;
|
|
721
538
|
const github = typeof o.repo === "string"
|
|
722
539
|
&& (o.repo.trim().startsWith(github_js_1.GITHUB_REPO_PREFIX) || o.repo.trim().startsWith(gitlab_js_1.GITLAB_REPO_PREFIX));
|
|
540
|
+
if (o.env !== undefined && Object.keys(o.env).length && forms[0] === "repo") {
|
|
541
|
+
throw new core_js_1.InvalidArgumentError("snapshots.create env is for an image or a Dockerfile", "A repository snapshot sets its environment in its setup steps, or pass env on each command.");
|
|
542
|
+
}
|
|
723
543
|
if (!github && (o.ref !== undefined || o.from !== undefined || o.track)) {
|
|
724
544
|
throw new core_js_1.InvalidArgumentError("snapshots.create ref, from and track are for a connected repository (repo: 'github:owner/name' or 'gitlab:group/project')", "Pass { repo: 'github:your-org/app', ref: 'main' }, or drop them for this form.");
|
|
725
545
|
}
|
|
@@ -775,6 +595,8 @@ function snapshotCreateBody(opts) {
|
|
|
775
595
|
body["tier"] = o.tier;
|
|
776
596
|
if (o.registrySecret)
|
|
777
597
|
body["registry_secret"] = o.registrySecret;
|
|
598
|
+
if (o.env !== undefined && Object.keys(o.env).length)
|
|
599
|
+
body["env"] = { ...o.env };
|
|
778
600
|
}
|
|
779
601
|
if (o.alias)
|
|
780
602
|
body["alias"] = o.alias;
|
|
@@ -1521,6 +1343,11 @@ class PromigenceClient {
|
|
|
1521
1343
|
github = {
|
|
1522
1344
|
status: () => this.request({ method: "GET", path: "/integrations/github" }),
|
|
1523
1345
|
connect: () => this.request({ method: "POST", path: "/integrations/github/connect", body: {} }),
|
|
1346
|
+
ticket: () => this.request({ method: "POST", path: "/integrations/github/requests", body: {} }),
|
|
1347
|
+
request: (requestId) => this.request({
|
|
1348
|
+
method: "GET",
|
|
1349
|
+
path: `/integrations/github/requests/${encodeURIComponent(requestId)}`,
|
|
1350
|
+
}),
|
|
1524
1351
|
disconnect: (installationId) => this.request({
|
|
1525
1352
|
method: "DELETE",
|
|
1526
1353
|
path: `/integrations/github/installations/${encodeURIComponent(String(installationId))}`,
|
|
@@ -1539,6 +1366,11 @@ class PromigenceClient {
|
|
|
1539
1366
|
gitlab = {
|
|
1540
1367
|
status: () => this.request({ method: "GET", path: "/integrations/gitlab" }),
|
|
1541
1368
|
connect: () => this.request({ method: "POST", path: "/integrations/gitlab/connect", body: {} }),
|
|
1369
|
+
ticket: () => this.request({ method: "POST", path: "/integrations/gitlab/requests", body: {} }),
|
|
1370
|
+
request: (requestId) => this.request({
|
|
1371
|
+
method: "GET",
|
|
1372
|
+
path: `/integrations/gitlab/requests/${encodeURIComponent(requestId)}`,
|
|
1373
|
+
}),
|
|
1542
1374
|
disconnect: (connectionId) => this.request({
|
|
1543
1375
|
method: "DELETE",
|
|
1544
1376
|
path: `/integrations/gitlab/connections/${encodeURIComponent(connectionId)}`,
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A local directory as a Dockerfile build context: what `packContext` sends, decided before
|
|
3
|
+
* anything leaves this machine.
|
|
4
|
+
*
|
|
5
|
+
* - **`.dockerignore` is applied here, always**, with the rules `docker build` uses: Go glob syntax
|
|
6
|
+
* plus `**`, `!` to bring a path back, the last matching line wins, a matched directory takes
|
|
7
|
+
* everything under it, and paths are read from the context root. A file the `.dockerignore`
|
|
8
|
+
* excludes is never uploaded. `<Dockerfile>.dockerignore` beside the Dockerfile is read instead
|
|
9
|
+
* of the root `.dockerignore` when it exists, as `docker build` does. The Dockerfile and the
|
|
10
|
+
* ignore files themselves are always sent, whatever a pattern says.
|
|
11
|
+
* - **`.git` is never sent** unless the `.dockerignore` has the line `!.git`: git history can hold
|
|
12
|
+
* credentials long deleted from the tree.
|
|
13
|
+
* - **Without a `.dockerignore`**, `node_modules`, `__pycache__`, `.venv` and `.DS_Store` are left
|
|
14
|
+
* out too, and a note says so; with one, only what it lists is left out.
|
|
15
|
+
* - Empty directories and permission bits travel as they are on disk; a set-user-ID or
|
|
16
|
+
* set-group-ID bit is cleared, with a note.
|
|
17
|
+
* - Files that look like credentials (`.env`, private keys, cloud credentials, ...) are refused,
|
|
18
|
+
* by name, unless `allowSecrets` says otherwise. The check goes by file name: it is
|
|
19
|
+
* best-effort and cannot see what a file holds, so look over the list (`listContext`) before
|
|
20
|
+
* you send a directory.
|
|
21
|
+
* - Symbolic links travel as links, never as the file they point at.
|
|
22
|
+
*/
|
|
23
|
+
/** The prefix a packed directory travels under in `dockerfile.context`. */
|
|
24
|
+
export declare const CONTEXT_TAR_PREFIX = "tar;base64,";
|
|
25
|
+
/** The largest context accepted, counted in file bytes after `.dockerignore`. */
|
|
26
|
+
export declare const CONTEXT_MAX_BYTES: number;
|
|
27
|
+
/**
|
|
28
|
+
* Names left out when the context has no `.dockerignore`. With one, the `.dockerignore` alone
|
|
29
|
+
* decides, except for `.git` (sent only when it says `!.git`).
|
|
30
|
+
*/
|
|
31
|
+
export declare const CONTEXT_SKIP: Set<string>;
|
|
32
|
+
/** The patterns of one ignore file, compiled. */
|
|
33
|
+
export interface DockerIgnore {
|
|
34
|
+
/** The ignore file these came from (`.dockerignore` or `<Dockerfile>.dockerignore`). */
|
|
35
|
+
readonly file: string;
|
|
36
|
+
/** The patterns as the build holds them, in file order: cleaned, an exception keeping its `!`. */
|
|
37
|
+
readonly patterns: readonly string[];
|
|
38
|
+
/** Whether any line is a `!` exception. */
|
|
39
|
+
readonly exceptions: boolean;
|
|
40
|
+
/** Whether a line says `!.git`, which is what sends `.git`. */
|
|
41
|
+
readonly sendsGit: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Is `path` (relative to the context root, `/`-separated) left out? The last pattern that
|
|
44
|
+
* matches it, or one of the directories above it, decides.
|
|
45
|
+
*/
|
|
46
|
+
ignored(path: string): boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read an ignore file the way `docker build` reads it: a UTF-8 BOM is dropped; a line starting
|
|
50
|
+
* with `#` is a comment; each line is trimmed; `!` starts an exception; each pattern is cleaned
|
|
51
|
+
* (`a//b/../c` is `a/c`) and a leading `/` is dropped. A line the build cannot read (a lone `!`,
|
|
52
|
+
* an unclosed `[`, a trailing `\`) makes the build refuse the whole file, so it is refused here,
|
|
53
|
+
* naming the line, before anything is sent.
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseDockerignore(text: string, file?: string): DockerIgnore;
|
|
56
|
+
/**
|
|
57
|
+
* A `.dockerignore`'s patterns as a predicate, or `undefined` for a file with no patterns. Refuses
|
|
58
|
+
* a file the build could not read. Kept for code written against 0.1; `parseDockerignore` is the
|
|
59
|
+
* full reader.
|
|
60
|
+
*/
|
|
61
|
+
export declare function dockerignoreMatcher(text: string): {
|
|
62
|
+
ignored: (rel: string) => boolean;
|
|
63
|
+
exceptions: boolean;
|
|
64
|
+
} | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Does this path look like a credential (`.env`, a private key, cloud or registry credentials, a
|
|
67
|
+
* token file, ...)? By name only: best-effort, it cannot see what a file holds. Templates such as
|
|
68
|
+
* `.env.example` are not.
|
|
69
|
+
*/
|
|
70
|
+
export declare function secretLike(path: string): boolean;
|
|
71
|
+
/** What a build context is packed from and sent with. */
|
|
72
|
+
export interface ContextOptions {
|
|
73
|
+
/**
|
|
74
|
+
* The Dockerfile's path inside the context, `/`-separated (default `Dockerfile`). It is always
|
|
75
|
+
* sent, and `<it>.dockerignore`, when present, is read instead of the root `.dockerignore`.
|
|
76
|
+
*/
|
|
77
|
+
dockerfile?: string;
|
|
78
|
+
/** More paths inside the context to send whatever `.dockerignore` says. */
|
|
79
|
+
keep?: readonly string[];
|
|
80
|
+
/** Refuse a context whose files add up to more than this many bytes (default `CONTEXT_MAX_BYTES`). */
|
|
81
|
+
maxBytes?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Send files that look like credentials. Default `false`: they are refused, by name, and
|
|
84
|
+
* nothing is sent.
|
|
85
|
+
*/
|
|
86
|
+
allowSecrets?: boolean;
|
|
87
|
+
/** Called with each note: what was left out by default, or changed on the way. */
|
|
88
|
+
onNote?: (note: string) => void;
|
|
89
|
+
}
|
|
90
|
+
/** What a build context would hold, before anything is sent. */
|
|
91
|
+
export interface ContextListing {
|
|
92
|
+
/** Every path that would be sent, sorted; a directory's ends in `/`. */
|
|
93
|
+
paths: string[];
|
|
94
|
+
/** How many of them are files or links. */
|
|
95
|
+
files: number;
|
|
96
|
+
/** The files' bytes. */
|
|
97
|
+
bytes: number;
|
|
98
|
+
/** What was left out by default or changed on the way, one sentence each. */
|
|
99
|
+
notes: string[];
|
|
100
|
+
/** The paths that would be sent and look like credentials. */
|
|
101
|
+
secrets: string[];
|
|
102
|
+
/** The ignore file applied, or `null` when there is none. */
|
|
103
|
+
ignoreFile: string | null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* What `packContext(dir, opts)` would send, without sending or packing anything: every path, the
|
|
107
|
+
* notes, and the files that look like credentials. Refuses only what `packContext` cannot read
|
|
108
|
+
* (an ignore file the build would refuse).
|
|
109
|
+
*/
|
|
110
|
+
export declare function listContext(dir: string, opts?: ContextOptions): ContextListing;
|
|
111
|
+
/**
|
|
112
|
+
* Pack a directory into the string `dockerfile.context` takes. What is sent is decided here (see
|
|
113
|
+
* the module notes): files the `.dockerignore` excludes, and `.git`, are never uploaded; files
|
|
114
|
+
* that look like credentials are refused unless `allowSecrets`; a context over `maxBytes` after
|
|
115
|
+
* `.dockerignore` is refused. The same tree packs to the same snapshot whichever SDK packs it.
|
|
116
|
+
*/
|
|
117
|
+
export declare function packContext(dir: string, opts?: ContextOptions): string;
|
|
118
|
+
/** The Dockerfile's path as the context sees it, for `dockerfile.path`. */
|
|
119
|
+
export declare function contextRelative(contextDir: string, dockerfilePath: string): string;
|