@promigence/sdk 0.1.0-beta.4 → 0.1.0-beta.6
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 +12 -5
- 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 +12 -5
- package/dist/sandbox.js +3 -3
- package/dist/ssh.js +2 -3
- package/package.json +1 -1
package/dist/context.js
ADDED
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
import { lstatSync, readFileSync, readdirSync, readlinkSync } from "node:fs";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
import { InvalidArgumentError } from "./core.js";
|
|
4
|
+
export const CONTEXT_TAR_PREFIX = "tar;base64,";
|
|
5
|
+
export const CONTEXT_MAX_BYTES = 8 * 1024 * 1024;
|
|
6
|
+
export const CONTEXT_SKIP = new Set([".git", "node_modules", "__pycache__", ".venv", ".DS_Store"]);
|
|
7
|
+
const NAMED = 10;
|
|
8
|
+
const SPACE = "\\t\\n\\v\\f\\r \\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000";
|
|
9
|
+
const TRIM = new RegExp(`^[${SPACE}]+|[${SPACE}]+$`, "gu");
|
|
10
|
+
const goTrim = (s) => s.replace(TRIM, "");
|
|
11
|
+
function goClean(p) {
|
|
12
|
+
if (p === "")
|
|
13
|
+
return ".";
|
|
14
|
+
const rooted = p.startsWith("/");
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const seg of p.split("/")) {
|
|
17
|
+
if (seg === "" || seg === ".")
|
|
18
|
+
continue;
|
|
19
|
+
if (seg === "..") {
|
|
20
|
+
if (out.length > 0 && out[out.length - 1] !== "..")
|
|
21
|
+
out.pop();
|
|
22
|
+
else if (!rooted)
|
|
23
|
+
out.push("..");
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
out.push(seg);
|
|
27
|
+
}
|
|
28
|
+
const s = (rooted ? "/" : "") + out.join("/");
|
|
29
|
+
return s === "" ? "." : s;
|
|
30
|
+
}
|
|
31
|
+
function goDir(p) {
|
|
32
|
+
return goClean(p.slice(0, p.lastIndexOf("/") + 1));
|
|
33
|
+
}
|
|
34
|
+
function classChar(cps, i) {
|
|
35
|
+
let c = cps[i];
|
|
36
|
+
if (c === undefined || c === "-" || c === "]")
|
|
37
|
+
return undefined;
|
|
38
|
+
if (c === "\\") {
|
|
39
|
+
i += 1;
|
|
40
|
+
c = cps[i];
|
|
41
|
+
if (c === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (i + 1 >= cps.length)
|
|
45
|
+
return undefined;
|
|
46
|
+
return { cp: c.codePointAt(0), next: i + 1 };
|
|
47
|
+
}
|
|
48
|
+
function readClass(cps, i) {
|
|
49
|
+
let neg = false;
|
|
50
|
+
if (cps[i] === "^") {
|
|
51
|
+
neg = true;
|
|
52
|
+
i += 1;
|
|
53
|
+
}
|
|
54
|
+
const ranges = [];
|
|
55
|
+
for (;;) {
|
|
56
|
+
if (cps[i] === "]" && ranges.length > 0)
|
|
57
|
+
return { neg, ranges, next: i + 1 };
|
|
58
|
+
const lo = classChar(cps, i);
|
|
59
|
+
if (!lo)
|
|
60
|
+
return "a `[...]` class is empty, is never closed, or has a `-` or `]` where a character belongs";
|
|
61
|
+
i = lo.next;
|
|
62
|
+
let hi = lo;
|
|
63
|
+
if (cps[i] === "-") {
|
|
64
|
+
const h = classChar(cps, i + 1);
|
|
65
|
+
if (!h)
|
|
66
|
+
return "a `[...]` class has a range with no end";
|
|
67
|
+
hi = h;
|
|
68
|
+
i = h.next;
|
|
69
|
+
}
|
|
70
|
+
if (hi.cp < lo.cp)
|
|
71
|
+
return "a `[...]` class has a range that runs backwards";
|
|
72
|
+
ranges.push([lo.cp, hi.cp]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function compileRule(pattern, exclusion, line) {
|
|
76
|
+
const cps = [...pattern];
|
|
77
|
+
for (let i = 0; i < cps.length;) {
|
|
78
|
+
if (cps[i] === "\\") {
|
|
79
|
+
if (i + 1 >= cps.length)
|
|
80
|
+
return "it ends with a `\\` that escapes nothing";
|
|
81
|
+
i += 2;
|
|
82
|
+
}
|
|
83
|
+
else if (cps[i] === "[") {
|
|
84
|
+
const c = readClass(cps, i + 1);
|
|
85
|
+
if (typeof c === "string")
|
|
86
|
+
return c;
|
|
87
|
+
i = c.next;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
i += 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
let kind = "exact";
|
|
94
|
+
const tokens = [];
|
|
95
|
+
let first = true;
|
|
96
|
+
for (let k = 0; k < cps.length;) {
|
|
97
|
+
const ch = cps[k++];
|
|
98
|
+
if (ch === "*") {
|
|
99
|
+
if (cps[k] === "*") {
|
|
100
|
+
k += 1;
|
|
101
|
+
if (cps[k] === "/")
|
|
102
|
+
k += 1;
|
|
103
|
+
if (k >= cps.length) {
|
|
104
|
+
if (kind === "exact")
|
|
105
|
+
kind = "prefix";
|
|
106
|
+
else {
|
|
107
|
+
tokens.push({ t: "rest" });
|
|
108
|
+
kind = "glob";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
tokens.push({ t: "dirs" });
|
|
113
|
+
kind = "glob";
|
|
114
|
+
}
|
|
115
|
+
if (first)
|
|
116
|
+
kind = "suffix";
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
tokens.push({ t: "star" });
|
|
120
|
+
kind = "glob";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (ch === "?") {
|
|
124
|
+
tokens.push({ t: "one" });
|
|
125
|
+
kind = "glob";
|
|
126
|
+
}
|
|
127
|
+
else if (ch === "\\") {
|
|
128
|
+
tokens.push({ t: "lit", c: cps[k++] });
|
|
129
|
+
kind = "glob";
|
|
130
|
+
}
|
|
131
|
+
else if (ch === "[") {
|
|
132
|
+
const c = readClass(cps, k);
|
|
133
|
+
tokens.push({ t: "class", neg: c.neg, ranges: c.ranges });
|
|
134
|
+
k = c.next;
|
|
135
|
+
kind = "glob";
|
|
136
|
+
}
|
|
137
|
+
else if (ch === "]") {
|
|
138
|
+
tokens.push({ t: "lit", c: ch });
|
|
139
|
+
kind = "glob";
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
tokens.push({ t: "lit", c: ch });
|
|
143
|
+
}
|
|
144
|
+
first = false;
|
|
145
|
+
}
|
|
146
|
+
return { line, exclusion, pattern, kind, tokens };
|
|
147
|
+
}
|
|
148
|
+
function globMatch(tokens, s) {
|
|
149
|
+
const memo = tokens.map(() => new Uint8Array(s.length + 1));
|
|
150
|
+
const go = (ti, si) => {
|
|
151
|
+
if (ti === tokens.length)
|
|
152
|
+
return si === s.length;
|
|
153
|
+
const row = memo[ti];
|
|
154
|
+
if (row[si])
|
|
155
|
+
return row[si] === 2;
|
|
156
|
+
const tok = tokens[ti];
|
|
157
|
+
let ok = false;
|
|
158
|
+
switch (tok.t) {
|
|
159
|
+
case "lit":
|
|
160
|
+
ok = s[si] === tok.c && go(ti + 1, si + 1);
|
|
161
|
+
break;
|
|
162
|
+
case "one":
|
|
163
|
+
ok = si < s.length && s[si] !== "/" && go(ti + 1, si + 1);
|
|
164
|
+
break;
|
|
165
|
+
case "class": {
|
|
166
|
+
const c = s[si];
|
|
167
|
+
if (c !== undefined) {
|
|
168
|
+
const cp = c.codePointAt(0);
|
|
169
|
+
const hit = tok.ranges.some(([lo, hi]) => lo <= cp && cp <= hi);
|
|
170
|
+
ok = hit !== tok.neg && go(ti + 1, si + 1);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case "star":
|
|
175
|
+
for (let j = si;; j++) {
|
|
176
|
+
if (go(ti + 1, j)) {
|
|
177
|
+
ok = true;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
if (j >= s.length || s[j] === "/")
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
case "rest":
|
|
185
|
+
for (let j = si;; j++) {
|
|
186
|
+
if (go(ti + 1, j)) {
|
|
187
|
+
ok = true;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
if (j >= s.length || s[j] === "\n")
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
case "dirs":
|
|
195
|
+
if (go(ti + 1, si)) {
|
|
196
|
+
ok = true;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
for (let j = si; j < s.length && s[j] !== "\n"; j++) {
|
|
200
|
+
if (s[j] === "/" && go(ti + 1, j + 1)) {
|
|
201
|
+
ok = true;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
row[si] = ok ? 2 : 1;
|
|
208
|
+
return ok;
|
|
209
|
+
};
|
|
210
|
+
return go(0, 0);
|
|
211
|
+
}
|
|
212
|
+
function ruleMatches(r, path) {
|
|
213
|
+
switch (r.kind) {
|
|
214
|
+
case "exact":
|
|
215
|
+
return path === r.pattern;
|
|
216
|
+
case "prefix":
|
|
217
|
+
return path.startsWith(r.pattern.slice(0, -2));
|
|
218
|
+
case "suffix": {
|
|
219
|
+
const suffix = r.pattern.slice(2);
|
|
220
|
+
return path.endsWith(suffix) || (suffix.startsWith("/") && path === suffix.slice(1));
|
|
221
|
+
}
|
|
222
|
+
case "glob":
|
|
223
|
+
return globMatch(r.tokens, [...path]);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
export function parseDockerignore(text, file = ".dockerignore") {
|
|
227
|
+
return readIgnore(text, file);
|
|
228
|
+
}
|
|
229
|
+
function readIgnore(text, file) {
|
|
230
|
+
const rules = [];
|
|
231
|
+
const lines = (text.startsWith("") ? text.slice(1) : text).split("\n");
|
|
232
|
+
lines.forEach((raw0, idx) => {
|
|
233
|
+
const raw = raw0.endsWith("\r") ? raw0.slice(0, -1) : raw0;
|
|
234
|
+
if (raw.startsWith("#"))
|
|
235
|
+
return;
|
|
236
|
+
let p = goTrim(raw);
|
|
237
|
+
if (p === "")
|
|
238
|
+
return;
|
|
239
|
+
const invert = p.startsWith("!");
|
|
240
|
+
if (invert)
|
|
241
|
+
p = goTrim(p.slice(1));
|
|
242
|
+
if (p !== "") {
|
|
243
|
+
p = goClean(p);
|
|
244
|
+
if (p.length > 1 && p.startsWith("/"))
|
|
245
|
+
p = p.slice(1);
|
|
246
|
+
}
|
|
247
|
+
let pat = goClean(goTrim((invert ? "!" : "") + p));
|
|
248
|
+
let exclusion = false;
|
|
249
|
+
const refuse = (why) => {
|
|
250
|
+
throw new InvalidArgumentError(`${file} line ${idx + 1} (${JSON.stringify(raw)}) is not a pattern the build can read: ${why}`, `Fix or remove that line of ${file}: the build refuses the whole file while it is there.`);
|
|
251
|
+
};
|
|
252
|
+
if (pat.startsWith("!")) {
|
|
253
|
+
if (pat.length === 1)
|
|
254
|
+
refuse("a `!` with no pattern after it");
|
|
255
|
+
exclusion = true;
|
|
256
|
+
pat = pat.slice(1);
|
|
257
|
+
}
|
|
258
|
+
const rule = compileRule(pat, exclusion, idx + 1);
|
|
259
|
+
if (typeof rule === "string")
|
|
260
|
+
refuse(rule);
|
|
261
|
+
else
|
|
262
|
+
rules.push(rule);
|
|
263
|
+
});
|
|
264
|
+
const exceptions = rules.some((r) => r.exclusion);
|
|
265
|
+
const plain = (r) => {
|
|
266
|
+
let s = r.pattern;
|
|
267
|
+
if (s.endsWith("/**"))
|
|
268
|
+
s = s.slice(0, -3);
|
|
269
|
+
if (s.endsWith("/*"))
|
|
270
|
+
s = s.slice(0, -2);
|
|
271
|
+
return s;
|
|
272
|
+
};
|
|
273
|
+
const onlyPlainExceptions = rules.every((r) => !r.exclusion || !/[*?[\\]/.test(plain(r)));
|
|
274
|
+
const step = (path, parent) => {
|
|
275
|
+
let matched = false;
|
|
276
|
+
const info = rules.map(() => false);
|
|
277
|
+
rules.forEach((r, i) => {
|
|
278
|
+
let m = parent !== undefined && parent.length > 0 ? parent[i] : false;
|
|
279
|
+
if (!m) {
|
|
280
|
+
if (r.exclusion !== matched)
|
|
281
|
+
return;
|
|
282
|
+
m = ruleMatches(r, path);
|
|
283
|
+
if (!m && (parent === undefined || parent.length === 0)) {
|
|
284
|
+
const dir = goDir(path);
|
|
285
|
+
if (dir !== ".") {
|
|
286
|
+
const dirs = dir.split("/");
|
|
287
|
+
for (let k = 1; k <= dirs.length && !m; k++)
|
|
288
|
+
m = ruleMatches(r, dirs.slice(0, k).join("/"));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
info[i] = m;
|
|
293
|
+
if (m)
|
|
294
|
+
matched = !r.exclusion;
|
|
295
|
+
});
|
|
296
|
+
return { ignored: matched, info };
|
|
297
|
+
};
|
|
298
|
+
return {
|
|
299
|
+
file,
|
|
300
|
+
patterns: rules.map((r) => (r.exclusion ? `!${r.pattern}` : r.pattern)),
|
|
301
|
+
exceptions,
|
|
302
|
+
sendsGit: rules.some((r) => r.exclusion && r.pattern === ".git"),
|
|
303
|
+
step,
|
|
304
|
+
ignored(path) {
|
|
305
|
+
const parts = path.split("/");
|
|
306
|
+
let info;
|
|
307
|
+
for (let k = 1; k < parts.length; k++)
|
|
308
|
+
info = step(parts.slice(0, k).join("/"), info).info;
|
|
309
|
+
return step(path, info).ignored;
|
|
310
|
+
},
|
|
311
|
+
mayHoldException(dir) {
|
|
312
|
+
if (!exceptions)
|
|
313
|
+
return false;
|
|
314
|
+
if (!onlyPlainExceptions)
|
|
315
|
+
return true;
|
|
316
|
+
return rules.some((r) => r.exclusion && `${plain(r)}/`.startsWith(`${dir}/`));
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
export function dockerignoreMatcher(text) {
|
|
321
|
+
const ig = readIgnore(text, ".dockerignore");
|
|
322
|
+
return ig.patterns.length > 0 ? { ignored: (rel) => ig.ignored(rel), exceptions: ig.exceptions } : undefined;
|
|
323
|
+
}
|
|
324
|
+
const SECRET_ALLOW = [
|
|
325
|
+
new RegExp("\\.(example|sample|template|dist|defaults)$", "i"),
|
|
326
|
+
];
|
|
327
|
+
const SECRET_DIRS = new Set([".aws", ".azure", ".gnupg", ".ssh"]);
|
|
328
|
+
const SECRET_PATHS = [
|
|
329
|
+
new RegExp("(^|/)\\.docker/config\\.json$", "i"),
|
|
330
|
+
new RegExp("(^|/)\\.kube/config$", "i"),
|
|
331
|
+
new RegExp("(^|/)\\.config/gh/hosts\\.ya?ml$", "i"),
|
|
332
|
+
new RegExp("(^|/)\\.streamlit/secrets\\.toml$", "i"),
|
|
333
|
+
];
|
|
334
|
+
const SECRET_NAMES = [
|
|
335
|
+
new RegExp("^\\.env$", "i"),
|
|
336
|
+
new RegExp("^\\.env\\..+$", "i"),
|
|
337
|
+
new RegExp("\\.env$", "i"),
|
|
338
|
+
new RegExp("^\\.envrc$", "i"),
|
|
339
|
+
new RegExp("^\\.dev\\.vars$", "i"),
|
|
340
|
+
new RegExp("\\.(pem|key|p12|pfx|jks|keystore|ppk|p8)$", "i"),
|
|
341
|
+
new RegExp("^id_(rsa|ed25519|ecdsa|dsa)(?!.*\\.pub$)", "i"),
|
|
342
|
+
new RegExp("^\\.npmrc$", "i"),
|
|
343
|
+
new RegExp("^\\.yarnrc\\.yml$", "i"),
|
|
344
|
+
new RegExp("^\\.pypirc$", "i"),
|
|
345
|
+
new RegExp("^[._]netrc$", "i"),
|
|
346
|
+
new RegExp("^\\.git-credentials$", "i"),
|
|
347
|
+
new RegExp("^\\.pgpass$", "i"),
|
|
348
|
+
new RegExp("^\\.my\\.cnf$", "i"),
|
|
349
|
+
new RegExp("^\\.s3cfg$", "i"),
|
|
350
|
+
new RegExp("^\\.boto$", "i"),
|
|
351
|
+
new RegExp("^\\.dockercfg$", "i"),
|
|
352
|
+
new RegExp("^\\.vault-token$", "i"),
|
|
353
|
+
new RegExp("^\\.terraformrc$", "i"),
|
|
354
|
+
new RegExp("^terraform\\.rc$", "i"),
|
|
355
|
+
new RegExp("^\\.databrickscfg$", "i"),
|
|
356
|
+
new RegExp("^\\.htpasswd$", "i"),
|
|
357
|
+
new RegExp("^credentials$", "i"),
|
|
358
|
+
new RegExp("credentials.*\\.json$", "i"),
|
|
359
|
+
new RegExp("^service[-_]?account.*\\.json$", "i"),
|
|
360
|
+
new RegExp("adminsdk.*\\.json$", "i"),
|
|
361
|
+
new RegExp("^client_secret.*\\.json$", "i"),
|
|
362
|
+
new RegExp("(^|[-_.])key\\.json$", "i"),
|
|
363
|
+
new RegExp("^token\\.json$", "i"),
|
|
364
|
+
new RegExp("^auth\\.json$", "i"),
|
|
365
|
+
new RegExp("^rootkey\\.csv$", "i"),
|
|
366
|
+
new RegExp("^google-services\\.json$", "i"),
|
|
367
|
+
new RegExp("^googleservice-info\\.plist$", "i"),
|
|
368
|
+
new RegExp("^local\\.settings\\.json$", "i"),
|
|
369
|
+
new RegExp("^appsettings\\.[^.]+\\.json$", "i"),
|
|
370
|
+
new RegExp("^wp-config\\.php$", "i"),
|
|
371
|
+
new RegExp("\\.yml\\.enc$", "i"),
|
|
372
|
+
new RegExp("\\.tfstate$", "i"),
|
|
373
|
+
new RegExp("\\.tfstate\\..+$", "i"),
|
|
374
|
+
new RegExp("\\.tfvars(\\.json)?$", "i"),
|
|
375
|
+
new RegExp("kubeconfig", "i"),
|
|
376
|
+
new RegExp("^secrets\\.(ya?ml|toml|json)$", "i"),
|
|
377
|
+
new RegExp("^(.*[-_.])?(secrets?|credentials?|passw(or)?ds?|tokens?|(api|access|private|secret)[-_.]?keys?)([-_.][^/]*)?\\.(json|ya?ml|toml|txt|csv|ini|cfg|conf|properties|xml|plist)$", "i"),
|
|
378
|
+
];
|
|
379
|
+
export function secretLike(path) {
|
|
380
|
+
const parts = path.split("/").filter(Boolean);
|
|
381
|
+
const name = parts[parts.length - 1] ?? "";
|
|
382
|
+
if (parts.slice(0, -1).some((d) => SECRET_DIRS.has(d.toLowerCase())))
|
|
383
|
+
return true;
|
|
384
|
+
if (SECRET_PATHS.some((re) => re.test(path)))
|
|
385
|
+
return true;
|
|
386
|
+
if (SECRET_ALLOW.some((re) => re.test(name)))
|
|
387
|
+
return false;
|
|
388
|
+
return SECRET_NAMES.some((re) => re.test(name));
|
|
389
|
+
}
|
|
390
|
+
function namedList(items) {
|
|
391
|
+
const more = items.length > NAMED ? ` and ${items.length - NAMED} more` : "";
|
|
392
|
+
return items.slice(0, NAMED).join(", ") + more;
|
|
393
|
+
}
|
|
394
|
+
export function credentialRefusalMessage(hits) {
|
|
395
|
+
const one = hits.length === 1;
|
|
396
|
+
return `${hits.length} file${one ? " looks" : "s look"} like ${one ? "a credential" : "credentials"} and ${one ? "was" : "were"} not uploaded: ${namedList(hits)}; nothing was created`;
|
|
397
|
+
}
|
|
398
|
+
export const CREDENTIAL_CHECK_NOTE = "The check goes by file name and is best-effort: it cannot see what a file holds, so look over what you send.";
|
|
399
|
+
function insidePath(p) {
|
|
400
|
+
return goClean(p.split(sep).join("/").replace(/^\.\/+/, ""));
|
|
401
|
+
}
|
|
402
|
+
function collect(dir, opts) {
|
|
403
|
+
const dockerfile = insidePath(opts.dockerfile ?? "Dockerfile");
|
|
404
|
+
const ownIgnore = `${dockerfile}.dockerignore`;
|
|
405
|
+
let ignore;
|
|
406
|
+
for (const name of [ownIgnore, ".dockerignore"]) {
|
|
407
|
+
let st;
|
|
408
|
+
try {
|
|
409
|
+
st = lstatSync(join(dir, name));
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (!st.isFile())
|
|
415
|
+
continue;
|
|
416
|
+
ignore = readIgnore(readFileSync(join(dir, name), "utf8"), name);
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
const keep = new Set([dockerfile, ".dockerignore", ownIgnore, ...(opts.keep ?? []).map(insidePath)]);
|
|
420
|
+
const above = new Set();
|
|
421
|
+
for (const k of keep) {
|
|
422
|
+
const parts = k.split("/");
|
|
423
|
+
for (let i = 1; i < parts.length; i++)
|
|
424
|
+
above.add(parts.slice(0, i).join("/"));
|
|
425
|
+
}
|
|
426
|
+
const sendGit = ignore?.sendsGit ?? false;
|
|
427
|
+
const gitLeft = [];
|
|
428
|
+
const defaultLeft = [];
|
|
429
|
+
const setId = [];
|
|
430
|
+
const special = [];
|
|
431
|
+
let bytes = 0;
|
|
432
|
+
const walk = (abs, rel, parent) => {
|
|
433
|
+
const out = [];
|
|
434
|
+
for (const name of readdirSync(abs).sort()) {
|
|
435
|
+
const childAbs = join(abs, name);
|
|
436
|
+
const childRel = rel ? `${rel}/${name}` : name;
|
|
437
|
+
const st = lstatSync(childAbs);
|
|
438
|
+
const kept = keep.has(childRel);
|
|
439
|
+
const holdsKept = above.has(childRel);
|
|
440
|
+
if (name === ".git" && !sendGit && !kept && !holdsKept) {
|
|
441
|
+
gitLeft.push(childRel);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (!ignore && CONTEXT_SKIP.has(name) && !kept && !holdsKept) {
|
|
445
|
+
defaultLeft.push(childRel);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
let excluded = false;
|
|
449
|
+
let info;
|
|
450
|
+
if (ignore)
|
|
451
|
+
({ ignored: excluded, info } = ignore.step(childRel, parent));
|
|
452
|
+
if (kept)
|
|
453
|
+
excluded = false;
|
|
454
|
+
const raw = st.mode & 0o7777;
|
|
455
|
+
const mode = raw & ~0o6000;
|
|
456
|
+
if (st.isDirectory()) {
|
|
457
|
+
if (excluded && !holdsKept && !ignore.mayHoldException(childRel))
|
|
458
|
+
continue;
|
|
459
|
+
const inner = walk(childAbs, childRel, info);
|
|
460
|
+
if (!excluded || inner.length > 0) {
|
|
461
|
+
if (raw !== mode)
|
|
462
|
+
setId.push(childRel);
|
|
463
|
+
out.push({ rel: childRel, abs: childAbs, kind: "dir", mode, size: 0 }, ...inner);
|
|
464
|
+
}
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (excluded)
|
|
468
|
+
continue;
|
|
469
|
+
if (st.isSymbolicLink()) {
|
|
470
|
+
out.push({ rel: childRel, abs: childAbs, kind: "link", mode: 0o777, size: 0, link: readlinkSync(childAbs) });
|
|
471
|
+
}
|
|
472
|
+
else if (st.isFile()) {
|
|
473
|
+
if (raw !== mode)
|
|
474
|
+
setId.push(childRel);
|
|
475
|
+
bytes += st.size;
|
|
476
|
+
out.push({ rel: childRel, abs: childAbs, kind: "file", mode, size: st.size });
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
special.push(childRel);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return out;
|
|
483
|
+
};
|
|
484
|
+
const entries = walk(dir, "", undefined);
|
|
485
|
+
const notes = [];
|
|
486
|
+
if (gitLeft.length > 0) {
|
|
487
|
+
notes.push(`Left out ${namedList(gitLeft)}: git history can hold credentials. To send it, add the line \`!.git\` to ${ignore?.file ?? ".dockerignore"}.`);
|
|
488
|
+
}
|
|
489
|
+
if (defaultLeft.length > 0) {
|
|
490
|
+
notes.push(`Left out ${namedList(defaultLeft)}: with no .dockerignore here, ${[...CONTEXT_SKIP].filter((n) => n !== ".git").join(", ")} are not sent. Add a .dockerignore to choose: with one, only what it lists is left out.`);
|
|
491
|
+
}
|
|
492
|
+
if (setId.length > 0) {
|
|
493
|
+
notes.push(`Cleared the set-user-ID or set-group-ID bit of ${namedList(setId.sort())}: a build context never carries one.`);
|
|
494
|
+
}
|
|
495
|
+
if (special.length > 0) {
|
|
496
|
+
notes.push(`Left out ${namedList(special)}: a socket, device or pipe; a build context holds only files, directories and links.`);
|
|
497
|
+
}
|
|
498
|
+
const secrets = entries.filter((e) => e.kind === "file" && secretLike(e.rel)).map((e) => e.rel);
|
|
499
|
+
return { entries, bytes, notes, secrets, ignore };
|
|
500
|
+
}
|
|
501
|
+
export function listContext(dir, opts = {}) {
|
|
502
|
+
const c = collect(dir, opts);
|
|
503
|
+
return {
|
|
504
|
+
paths: c.entries.map((e) => (e.kind === "dir" ? `${e.rel}/` : e.rel)).sort(),
|
|
505
|
+
files: c.entries.filter((e) => e.kind !== "dir").length,
|
|
506
|
+
bytes: c.bytes,
|
|
507
|
+
notes: c.notes,
|
|
508
|
+
secrets: c.secrets,
|
|
509
|
+
ignoreFile: c.ignore?.file ?? null,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
export function packContext(dir, opts = {}) {
|
|
513
|
+
const max = opts.maxBytes ?? CONTEXT_MAX_BYTES;
|
|
514
|
+
const c = collect(dir, opts);
|
|
515
|
+
for (const n of c.notes)
|
|
516
|
+
opts.onNote?.(n);
|
|
517
|
+
if (c.bytes > max) {
|
|
518
|
+
const ig = c.ignore?.file;
|
|
519
|
+
throw new InvalidArgumentError(`the build context is over ${max} bytes${ig ? ` even without what its ${ig} excludes` : ""}`, `${ig ? `Exclude more in ${ig}` : "Add a .dockerignore"}, point --context at a smaller directory, or pass a git URL pinned to a full 40-character sha.`);
|
|
520
|
+
}
|
|
521
|
+
if (c.secrets.length > 0 && !opts.allowSecrets) {
|
|
522
|
+
throw new InvalidArgumentError(credentialRefusalMessage(c.secrets), `A snapshot keeps every file it holds, and every sandbox started from it has them. Exclude them in ${c.ignore?.file ?? ".dockerignore"}, pass the values as secrets, or pass { allowSecrets: true } if they are not real credentials. ${CREDENTIAL_CHECK_NOTE}`);
|
|
523
|
+
}
|
|
524
|
+
if (c.entries.length === 0) {
|
|
525
|
+
throw new InvalidArgumentError(`the build context ${JSON.stringify(dir)} is empty`, "Point --context at the directory holding your Dockerfile.");
|
|
526
|
+
}
|
|
527
|
+
return CONTEXT_TAR_PREFIX + tarOf(c.entries).toString("base64");
|
|
528
|
+
}
|
|
529
|
+
export function contextRelative(contextDir, dockerfilePath) {
|
|
530
|
+
const rel = relative(contextDir, dockerfilePath).split(sep).join("/");
|
|
531
|
+
if (!rel || rel.startsWith("..")) {
|
|
532
|
+
throw new InvalidArgumentError(`${JSON.stringify(dockerfilePath)} is not inside the context ${JSON.stringify(contextDir)}`, "Pass --context DIR with the Dockerfile underneath it.");
|
|
533
|
+
}
|
|
534
|
+
return rel;
|
|
535
|
+
}
|
|
536
|
+
function tarOf(entries) {
|
|
537
|
+
const out = [];
|
|
538
|
+
const named = entries.map((e) => ({ e, name: e.kind === "dir" ? `${e.rel}/` : e.rel }));
|
|
539
|
+
named.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
540
|
+
for (const { e, name: full } of named) {
|
|
541
|
+
const head = Buffer.alloc(512);
|
|
542
|
+
let name = full;
|
|
543
|
+
let prefix = "";
|
|
544
|
+
if (Buffer.byteLength(name) > 100) {
|
|
545
|
+
let cut = -1;
|
|
546
|
+
for (let i = name.indexOf("/"); i > 0 && i < name.length - 1; i = name.indexOf("/", i + 1)) {
|
|
547
|
+
if (Buffer.byteLength(name.slice(i + 1)) <= 100) {
|
|
548
|
+
if (Buffer.byteLength(name.slice(0, i)) <= 155)
|
|
549
|
+
cut = i;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (cut < 0) {
|
|
554
|
+
throw new InvalidArgumentError(`the path ${JSON.stringify(e.rel)} is too long for a build context`, "Shorten the path or pass a git URL pinned to a full 40-character sha.");
|
|
555
|
+
}
|
|
556
|
+
prefix = name.slice(0, cut);
|
|
557
|
+
name = name.slice(cut + 1);
|
|
558
|
+
}
|
|
559
|
+
if (e.link !== undefined && Buffer.byteLength(e.link) > 100) {
|
|
560
|
+
throw new InvalidArgumentError(`the link ${JSON.stringify(e.rel)} points at a path too long for a build context`, "Shorten the link's target, or pass a git URL pinned to a full 40-character sha.");
|
|
561
|
+
}
|
|
562
|
+
const body = e.kind === "file" ? readFileSync(e.abs) : Buffer.alloc(0);
|
|
563
|
+
const put = (s, at, len) => { head.write(s.slice(0, len), at, len, "utf8"); };
|
|
564
|
+
const oct = (n, at, len) => { put(n.toString(8).padStart(len - 1, "0") + "\0", at, len); };
|
|
565
|
+
put(name, 0, 100);
|
|
566
|
+
oct(e.mode, 100, 8);
|
|
567
|
+
oct(0, 108, 8);
|
|
568
|
+
oct(0, 116, 8);
|
|
569
|
+
oct(body.length, 124, 12);
|
|
570
|
+
oct(0, 136, 12);
|
|
571
|
+
head.write(" ", 148, 8, "utf8");
|
|
572
|
+
head.write(e.kind === "dir" ? "5" : e.kind === "link" ? "2" : "0", 156, 1, "utf8");
|
|
573
|
+
if (e.link !== undefined)
|
|
574
|
+
put(e.link, 157, 100);
|
|
575
|
+
head.write("ustar\0", 257, 6, "binary");
|
|
576
|
+
head.write("00", 263, 2, "utf8");
|
|
577
|
+
put(prefix, 345, 155);
|
|
578
|
+
let sum = 0;
|
|
579
|
+
for (const b of head)
|
|
580
|
+
sum += b;
|
|
581
|
+
put(sum.toString(8).padStart(6, "0") + "\0 ", 148, 8);
|
|
582
|
+
out.push(head);
|
|
583
|
+
if (body.length > 0)
|
|
584
|
+
out.push(body, Buffer.alloc((512 - (body.length % 512)) % 512));
|
|
585
|
+
}
|
|
586
|
+
out.push(Buffer.alloc(1024));
|
|
587
|
+
return Buffer.concat(out);
|
|
588
|
+
}
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Promigence SDK — constants, error hierarchy and shared types. */
|
|
2
|
-
export declare const VERSION = "0.1.0-beta.
|
|
2
|
+
export declare const VERSION = "0.1.0-beta.6";
|
|
3
3
|
export declare const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
/** The error reference; each code is an anchor on it (the API's `docs` link has the same form). */
|
|
5
5
|
export declare const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
package/dist/core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hintArg } from "./hint.js";
|
|
2
|
-
export const VERSION = "0.1.0-beta.
|
|
2
|
+
export const VERSION = "0.1.0-beta.6";
|
|
3
3
|
export const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
export const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
|
5
5
|
export const DEFAULT_API_URL = "https://api.promigence.ai/v1";
|
|
@@ -209,6 +209,7 @@ export class NotReleasedError extends PromigenceError {
|
|
|
209
209
|
export const EXIT_BY_CODE = {
|
|
210
210
|
invalid_argument: 2, unsupported: 2, not_released: 1, task_failed: 4,
|
|
211
211
|
env_failed: 3, snapshot_rejected: 6, build_failed: 3, snapshot_failed: 4,
|
|
212
|
+
not_connected: 7,
|
|
212
213
|
fork_failed: 3, start_timeout: 3, readiness_failed: 3, verify_too_slow: 3,
|
|
213
214
|
verify_each_failed: 3, toolchain_mismatch: 3, sandbox_unresponsive: 3, oom_within_tier: 3,
|
|
214
215
|
disk_full_within_tier: 3, sandbox_interrupted: 3, integrity_mismatch: 3, egress_unavailable: 3,
|
package/dist/github.d.ts
CHANGED
|
@@ -93,6 +93,33 @@ export interface GithubUntracked {
|
|
|
93
93
|
alias: string;
|
|
94
94
|
untracked: boolean;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* One request to connect a code host (`github` or `gitlab`), started by you, and how it ended.
|
|
98
|
+
* `state` is `pending` until it ends, then `connected`, `updated`, `already_connected`,
|
|
99
|
+
* `pending_provider_approval`, `declined`, `other_org`, `needs_admin`, `not_installer`,
|
|
100
|
+
* `wrong_user`, `expired` or `failed`; `message` says what happened and what to do next.
|
|
101
|
+
*/
|
|
102
|
+
export interface ConnectRequest {
|
|
103
|
+
request_id: string;
|
|
104
|
+
provider: string;
|
|
105
|
+
state: string;
|
|
106
|
+
account?: string;
|
|
107
|
+
message: string;
|
|
108
|
+
created_at: string;
|
|
109
|
+
finished_at?: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* `POST /integrations/{github,gitlab}/requests`: a connection started for you in the console. Open
|
|
113
|
+
* `connect_url` in a browser signed in to the console as you; `request_id` says how it ends.
|
|
114
|
+
*/
|
|
115
|
+
export interface ConnectTicket {
|
|
116
|
+
/** Used once, by you only, within 5 minutes. */
|
|
117
|
+
ticket_id: string;
|
|
118
|
+
request_id: string;
|
|
119
|
+
/** The console page that starts it: it carries the ticket and nothing else. */
|
|
120
|
+
connect_url: string;
|
|
121
|
+
expires_at: string;
|
|
122
|
+
}
|
|
96
123
|
/** The prefix that names a connected repository in `repo`. */
|
|
97
124
|
export declare const GITHUB_REPO_PREFIX = "github:";
|
|
98
125
|
/**
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED