@promigence/sdk 0.1.0-beta.12

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.
Files changed (104) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +54 -0
  3. package/dist/blake3.d.ts +10 -0
  4. package/dist/blake3.js +111 -0
  5. package/dist/bundle.d.ts +96 -0
  6. package/dist/bundle.js +227 -0
  7. package/dist/ca-guard.d.ts +1 -0
  8. package/dist/ca-guard.js +166 -0
  9. package/dist/caller.d.ts +73 -0
  10. package/dist/caller.js +47 -0
  11. package/dist/cjs/blake3.d.ts +10 -0
  12. package/dist/cjs/blake3.js +116 -0
  13. package/dist/cjs/bundle.d.ts +96 -0
  14. package/dist/cjs/bundle.js +242 -0
  15. package/dist/cjs/ca-guard.d.ts +1 -0
  16. package/dist/cjs/ca-guard.js +174 -0
  17. package/dist/cjs/caller.d.ts +73 -0
  18. package/dist/cjs/caller.js +57 -0
  19. package/dist/cjs/client.d.ts +3015 -0
  20. package/dist/cjs/client.js +2617 -0
  21. package/dist/cjs/command-errors.d.ts +41 -0
  22. package/dist/cjs/command-errors.js +43 -0
  23. package/dist/cjs/context.d.ts +119 -0
  24. package/dist/cjs/context.js +598 -0
  25. package/dist/cjs/core.d.ts +285 -0
  26. package/dist/cjs/core.js +290 -0
  27. package/dist/cjs/credstore.d.ts +90 -0
  28. package/dist/cjs/credstore.js +219 -0
  29. package/dist/cjs/dispatcher.d.ts +58 -0
  30. package/dist/cjs/dispatcher.js +91 -0
  31. package/dist/cjs/github.d.ts +158 -0
  32. package/dist/cjs/github.js +84 -0
  33. package/dist/cjs/gitlab.d.ts +104 -0
  34. package/dist/cjs/gitlab.js +24 -0
  35. package/dist/cjs/handles.d.ts +82 -0
  36. package/dist/cjs/handles.js +139 -0
  37. package/dist/cjs/hint.d.ts +1 -0
  38. package/dist/cjs/hint.js +10 -0
  39. package/dist/cjs/http2.d.ts +206 -0
  40. package/dist/cjs/http2.js +1932 -0
  41. package/dist/cjs/index.d.ts +34 -0
  42. package/dist/cjs/index.js +31 -0
  43. package/dist/cjs/keyed.gen.d.ts +1 -0
  44. package/dist/cjs/keyed.gen.js +10 -0
  45. package/dist/cjs/models.gen.d.ts +6162 -0
  46. package/dist/cjs/models.gen.js +2 -0
  47. package/dist/cjs/package.json +1 -0
  48. package/dist/cjs/proxy.d.ts +40 -0
  49. package/dist/cjs/proxy.js +257 -0
  50. package/dist/cjs/pty.d.ts +171 -0
  51. package/dist/cjs/pty.js +504 -0
  52. package/dist/cjs/sandbox.d.ts +537 -0
  53. package/dist/cjs/sandbox.js +736 -0
  54. package/dist/cjs/ssh.d.ts +25 -0
  55. package/dist/cjs/ssh.js +117 -0
  56. package/dist/cjs/trace.d.ts +33 -0
  57. package/dist/cjs/trace.js +88 -0
  58. package/dist/cjs/transport-worker.d.ts +1 -0
  59. package/dist/cjs/transport-worker.js +468 -0
  60. package/dist/cjs/warm.d.ts +1 -0
  61. package/dist/cjs/warm.js +43 -0
  62. package/dist/client.d.ts +3015 -0
  63. package/dist/client.js +2583 -0
  64. package/dist/command-errors.d.ts +41 -0
  65. package/dist/command-errors.js +36 -0
  66. package/dist/context.d.ts +119 -0
  67. package/dist/context.js +588 -0
  68. package/dist/core.d.ts +285 -0
  69. package/dist/core.js +266 -0
  70. package/dist/credstore.d.ts +90 -0
  71. package/dist/credstore.js +206 -0
  72. package/dist/dispatcher.d.ts +58 -0
  73. package/dist/dispatcher.js +86 -0
  74. package/dist/github.d.ts +158 -0
  75. package/dist/github.js +78 -0
  76. package/dist/gitlab.d.ts +104 -0
  77. package/dist/gitlab.js +20 -0
  78. package/dist/handles.d.ts +82 -0
  79. package/dist/handles.js +135 -0
  80. package/dist/hint.d.ts +1 -0
  81. package/dist/hint.js +7 -0
  82. package/dist/http2.d.ts +206 -0
  83. package/dist/http2.js +1921 -0
  84. package/dist/index.d.ts +34 -0
  85. package/dist/index.js +15 -0
  86. package/dist/keyed.gen.d.ts +1 -0
  87. package/dist/keyed.gen.js +7 -0
  88. package/dist/models.gen.d.ts +6162 -0
  89. package/dist/models.gen.js +1 -0
  90. package/dist/proxy.d.ts +40 -0
  91. package/dist/proxy.js +249 -0
  92. package/dist/pty.d.ts +171 -0
  93. package/dist/pty.js +496 -0
  94. package/dist/sandbox.d.ts +537 -0
  95. package/dist/sandbox.js +725 -0
  96. package/dist/ssh.d.ts +25 -0
  97. package/dist/ssh.js +114 -0
  98. package/dist/trace.d.ts +33 -0
  99. package/dist/trace.js +80 -0
  100. package/dist/transport-worker.d.ts +1 -0
  101. package/dist/transport-worker.js +466 -0
  102. package/dist/warm.d.ts +1 -0
  103. package/dist/warm.js +40 -0
  104. package/package.json +54 -0
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KEYCHAIN_SERVICE = void 0;
4
+ exports.storeLabel = storeLabel;
5
+ exports.keychainStore = keychainStore;
6
+ exports.keychainsToClear = keychainsToClear;
7
+ exports.keychainRead = keychainRead;
8
+ exports.keychainWrite = keychainWrite;
9
+ exports.keychainDelete = keychainDelete;
10
+ exports.encodeCredentials = encodeCredentials;
11
+ exports.keychainReadCached = keychainReadCached;
12
+ exports.keychainCacheSet = keychainCacheSet;
13
+ exports.forgetKeychainReads = forgetKeychainReads;
14
+ const node_child_process_1 = require("node:child_process");
15
+ const node_fs_1 = require("node:fs");
16
+ const node_os_1 = require("node:os");
17
+ const node_path_1 = require("node:path");
18
+ exports.KEYCHAIN_SERVICE = "promigence-cli";
19
+ const KEYCHAIN_LABEL = "Promigence CLI credentials";
20
+ function storeLabel(store, path) {
21
+ switch (store) {
22
+ case "macos-keychain": return "the macOS Keychain";
23
+ case "secret-service": return "the Secret Service keyring";
24
+ default: return path ? `the credentials file (${path})` : "the credentials file";
25
+ }
26
+ }
27
+ function onPath(name, env) {
28
+ for (const dir of (env["PATH"] ?? "").split(node_path_1.delimiter)) {
29
+ if (!dir)
30
+ continue;
31
+ const p = (0, node_path_1.join)(dir, name);
32
+ try {
33
+ (0, node_fs_1.accessSync)(p, node_fs_1.constants.X_OK);
34
+ return p;
35
+ }
36
+ catch { }
37
+ }
38
+ return undefined;
39
+ }
40
+ const toolName = (store) => (store === "macos-keychain" ? "security" : "secret-tool");
41
+ function sessionBus(env) {
42
+ if (env["DBUS_SESSION_BUS_ADDRESS"])
43
+ return true;
44
+ const run = env["XDG_RUNTIME_DIR"];
45
+ return Boolean(run) && (0, node_fs_1.existsSync)((0, node_path_1.join)(run, "bus"));
46
+ }
47
+ function userHome(env, home = node_os_1.homedir) {
48
+ const h = env["HOME"];
49
+ if (h)
50
+ return h;
51
+ try {
52
+ return home() || undefined;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
58
+ const warnedSetting = new Set();
59
+ function keychainStore(env = process.env, platform = process.platform, home = node_os_1.homedir) {
60
+ const raw = (env["PROMIGENCE_CREDENTIAL_STORE"] ?? "").trim().toLowerCase();
61
+ switch (raw) {
62
+ case "file": return undefined;
63
+ case "macos-keychain":
64
+ case "secret-service": return raw;
65
+ case "":
66
+ case "auto": break;
67
+ default:
68
+ if (!warnedSetting.has(raw)) {
69
+ warnedSetting.add(raw);
70
+ process.emitWarning(`PROMIGENCE_CREDENTIAL_STORE=${raw} is not one of auto, file, macos-keychain, secret-service; using auto`, { code: "PROMIGENCE_CREDENTIALS" });
71
+ }
72
+ }
73
+ if (platform === "darwin") {
74
+ const h = userHome(env, home);
75
+ return h && (0, node_fs_1.existsSync)((0, node_path_1.join)(h, "Library", "Keychains")) && onPath("security", env) ? "macos-keychain" : undefined;
76
+ }
77
+ if (platform === "win32")
78
+ return undefined;
79
+ return onPath("secret-tool", env) && sessionBus(env) ? "secret-service" : undefined;
80
+ }
81
+ function keychainsToClear(env = process.env, platform = process.platform) {
82
+ const out = new Set();
83
+ const chosen = keychainStore(env, platform);
84
+ if (chosen)
85
+ out.add(chosen);
86
+ const auto = keychainStore({ ...env, PROMIGENCE_CREDENTIAL_STORE: "auto" }, platform);
87
+ if (auto)
88
+ out.add(auto);
89
+ return [...out];
90
+ }
91
+ const LOCKED = /interaction is not allowed|is locked/i;
92
+ function failureText(e) {
93
+ if (e.code === "ENOENT")
94
+ return "its command-line tool is not installed";
95
+ if (e.code === "ETIMEDOUT" || e.signal === "SIGTERM")
96
+ return "it did not answer in time";
97
+ const stderr = String(e.stderr ?? "");
98
+ if (LOCKED.test(stderr))
99
+ return "it is locked (an SSH session cannot unlock it)";
100
+ if (/default keychain could not be found|keychain could not be found|No such keychain/i.test(stderr))
101
+ return "there is no default keychain";
102
+ if (/authorization was canceled|canceled by the user/i.test(stderr))
103
+ return "access was not allowed";
104
+ if (/Cannot autolaunch D-Bus|org\.freedesktop|D-Bus|dbus/i.test(stderr))
105
+ return "no Secret Service is answering on the session bus";
106
+ return `exit ${e.status ?? "?"}`;
107
+ }
108
+ function maybeHex(out) {
109
+ if (out.length >= 2 && out.length % 2 === 0 && /^[0-9a-f]+$/i.test(out)) {
110
+ const text = Buffer.from(out, "hex").toString("utf8");
111
+ if (text.startsWith("{"))
112
+ return text;
113
+ }
114
+ return out;
115
+ }
116
+ const READ_TIMEOUT_MS = 10_000;
117
+ const WRITE_TIMEOUT_MS = 60_000;
118
+ function runTool(store, args, env, input, timeout) {
119
+ const bin = onPath(toolName(store), env);
120
+ if (bin === undefined)
121
+ throw Object.assign(new Error(`${toolName(store)} not found`), { code: "ENOENT" });
122
+ const h = store === "macos-keychain" && !env["HOME"] ? userHome(env) : undefined;
123
+ return (0, node_child_process_1.execFileSync)(bin, args, {
124
+ encoding: "utf8", timeout, env: h ? { ...env, HOME: h } : env,
125
+ ...(input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input, stdio: ["pipe", "pipe", "pipe"] }),
126
+ });
127
+ }
128
+ function keychainRead(store, account, env = process.env) {
129
+ const args = store === "macos-keychain"
130
+ ? ["find-generic-password", "-s", exports.KEYCHAIN_SERVICE, "-a", account, "-w"]
131
+ : ["lookup", "service", exports.KEYCHAIN_SERVICE, "account", account];
132
+ try {
133
+ const text = maybeHex(runTool(store, args, env, undefined, READ_TIMEOUT_MS).replace(/\r?\n$/, ""));
134
+ return text ? { status: "found", text } : { status: "absent" };
135
+ }
136
+ catch (e) {
137
+ const f = e;
138
+ const stderr = String(f.stderr ?? "");
139
+ if (store === "macos-keychain" && f.status === 44)
140
+ return { status: "absent" };
141
+ if (store === "secret-service" && f.status === 1 && !stderr.trim())
142
+ return { status: "absent" };
143
+ return { status: "unavailable", reason: failureText(f), locked: LOCKED.test(stderr) || (store === "macos-keychain" && f.status === 36) };
144
+ }
145
+ }
146
+ function quotable(account) {
147
+ return /^[^"\\\u0000-\u001f\u007f]+$/.test(account);
148
+ }
149
+ function macosDefaultKeychain(env) {
150
+ try {
151
+ const p = runTool("macos-keychain", ["default-keychain", "-d", "user"], env, undefined, READ_TIMEOUT_MS).trim().replace(/^"|"$/g, "");
152
+ return p.length > 0 && (0, node_fs_1.existsSync)(p);
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
158
+ const MAX_SECURITY_LINE = 2048;
159
+ function keychainWrite(store, account, text, env = process.env) {
160
+ try {
161
+ if (store === "macos-keychain") {
162
+ if (!quotable(account))
163
+ return { ok: false, reason: "the credentials path holds a quote, backslash or control character" };
164
+ if (!macosDefaultKeychain(env))
165
+ return { ok: false, reason: "there is no default keychain" };
166
+ const hex = Buffer.from(text, "utf8").toString("hex");
167
+ const line = `add-generic-password -U -s "${exports.KEYCHAIN_SERVICE}" -a "${account}" -l "${KEYCHAIN_LABEL}" -X ${hex}\n`;
168
+ if (line.length > MAX_SECURITY_LINE)
169
+ return { ok: false, reason: "the sign-in is too long for the keychain tool's command reader" };
170
+ runTool(store, ["-i"], env, line, WRITE_TIMEOUT_MS);
171
+ }
172
+ else {
173
+ runTool(store, ["store", `--label=${KEYCHAIN_LABEL}`, "service", exports.KEYCHAIN_SERVICE, "account", account], env, text, WRITE_TIMEOUT_MS);
174
+ }
175
+ return { ok: true };
176
+ }
177
+ catch (e) {
178
+ return { ok: false, reason: failureText(e) };
179
+ }
180
+ }
181
+ function keychainDelete(store, account, env = process.env) {
182
+ const args = store === "macos-keychain"
183
+ ? ["delete-generic-password", "-s", exports.KEYCHAIN_SERVICE, "-a", account]
184
+ : ["clear", "service", exports.KEYCHAIN_SERVICE, "account", account];
185
+ try {
186
+ runTool(store, args, env, undefined, WRITE_TIMEOUT_MS);
187
+ return { ok: true, absent: false };
188
+ }
189
+ catch (e) {
190
+ const f = e;
191
+ if (store === "macos-keychain" && f.status === 44)
192
+ return { ok: true, absent: true };
193
+ if (f.code === "ENOENT")
194
+ return { ok: true, absent: true };
195
+ return { ok: false, reason: failureText(f), locked: LOCKED.test(String(f.stderr ?? "")) };
196
+ }
197
+ }
198
+ function encodeCredentials(body) {
199
+ return JSON.stringify(body).replace(/[\u007f-￿]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
200
+ }
201
+ const CACHE_TTL_MS = 30_000;
202
+ const cache = new Map();
203
+ const cacheKey = (store, account) => `${store}\u0000${account}`;
204
+ function keychainReadCached(store, account, env = process.env) {
205
+ const k = cacheKey(store, account);
206
+ const hit = cache.get(k);
207
+ if (hit && Date.now() - hit.at < CACHE_TTL_MS)
208
+ return hit.value;
209
+ const value = keychainRead(store, account, env);
210
+ if (value.status !== "unavailable")
211
+ cache.set(k, { at: Date.now(), value });
212
+ return value;
213
+ }
214
+ function keychainCacheSet(store, account, text) {
215
+ cache.set(cacheKey(store, account), { at: Date.now(), value: text === undefined ? { status: "absent" } : { status: "found", text } });
216
+ }
217
+ function forgetKeychainReads() {
218
+ cache.clear();
219
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The dispatcher for a request that may legitimately outlast Node's `fetch`.
3
+ *
4
+ * On Node.js `globalThis.fetch` is undici, and undici's dispatcher fails any request whose
5
+ * response headers have not arrived 300 s after it was sent (`headersTimeout`, error code
6
+ * `UND_ERR_HEADERS_TIMEOUT`) and any body that stalls for 300 s (`bodyTimeout`), whatever
7
+ * `AbortSignal` the caller gave it. A foreground exec (`POST /sandboxes/:id/execs`) is ONE
8
+ * request that lasts as long as the command runs, so a six-minute build failed client-side at
9
+ * 300 s and was reported "unreachable" while the sandbox and the command were fine. `pause` and
10
+ * `snapshot` carry a ten-minute bound and were exposed the same way. The HTTP/2 transport has no
11
+ * socket-level timeout of its
12
+ * own and is bounded by the request's signal alone, so it never had this problem — but every
13
+ * request it hands back to `fetch` did.
14
+ *
15
+ * The rule: a request whose bound plus a minute of slack exceeds undici's default carries a
16
+ * `dispatcher` whose `headersTimeout` and `bodyTimeout` are at least that, so the SDK's own
17
+ * `AbortSignal.timeout` is always the timer that fires first and the error a customer sees names
18
+ * the bound they can raise. The dispatcher is the process's global one composed with a single
19
+ * interceptor that sets the two timeouts on each request (undici's `Dispatcher.compose`; verified
20
+ * on Node 22.19 / undici 6.21 and Node 26.10 / undici 8.10): the same connection pool, the same
21
+ * proxy or mock agent if the caller installed one, the same TLS options — only the timeouts
22
+ * change. One composed dispatcher per five-minute bucket of timeout, per global dispatcher,
23
+ * shared by every client in the process.
24
+ *
25
+ * Feature-detected, no dependency: where there is no global dispatcher (Bun, Deno, a browser),
26
+ * no `compose` (an older undici), or anything about it looks unfamiliar, nothing is attached and
27
+ * the request travels exactly as it did before. The worst case is the old behaviour, never a
28
+ * request that fails for a new reason.
29
+ */
30
+ /** undici's default `headersTimeout` and `bodyTimeout`: what a request without a dispatcher gets. */
31
+ export declare const FETCH_DEFAULT_TIMEOUT_MS = 300000;
32
+ /** Margin between the SDK's bound and the socket-level timeout, so the SDK's is the one that fires. */
33
+ export declare const LONG_REQUEST_SLACK_MS = 60000;
34
+ /**
35
+ * True when someone called undici's `setGlobalDispatcher` (a proxy agent, a mock agent). `fetch`
36
+ * reads the dispatcher on every call, so this is checked on every request, not once. An unknown
37
+ * name counts as custom: the safe direction is `fetch`.
38
+ */
39
+ export declare function customDispatcher(): boolean;
40
+ interface Timeouts {
41
+ headersTimeout: number;
42
+ bodyTimeout: number;
43
+ }
44
+ /**
45
+ * The dispatcher a request bounded by `timeoutMs` should carry, or `undefined` when the default
46
+ * already waits long enough or nothing better can be had here. Never throws.
47
+ */
48
+ export declare function longRequestDispatcher(timeoutMs: number, base?: object | undefined): object | undefined;
49
+ /** Test and diagnostics view. Not part of the public API. */
50
+ export declare const __dispatcherInternals: {
51
+ /** The timeouts a dispatcher of ours sets, or `undefined` for any other value. */
52
+ timeoutsOf(d: unknown): Timeouts | undefined;
53
+ /** The process's global dispatcher composed with exactly these timeouts (never cached). */
54
+ composeWith(ms: number): object | undefined;
55
+ /** Forget every composed dispatcher. */
56
+ reset(): void;
57
+ };
58
+ export {};
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__dispatcherInternals = exports.LONG_REQUEST_SLACK_MS = exports.FETCH_DEFAULT_TIMEOUT_MS = void 0;
4
+ exports.customDispatcher = customDispatcher;
5
+ exports.longRequestDispatcher = longRequestDispatcher;
6
+ exports.FETCH_DEFAULT_TIMEOUT_MS = 300_000;
7
+ exports.LONG_REQUEST_SLACK_MS = 60_000;
8
+ const BUCKET_MS = 5 * 60_000;
9
+ const DEFAULT_DISPATCHERS = new Set(["Agent", "Dispatcher1Wrapper"]);
10
+ const DISPATCHER_KEYS = [Symbol.for("undici.globalDispatcher.1"), Symbol.for("undici.globalDispatcher.2")];
11
+ function globalDispatchers() {
12
+ const out = [];
13
+ for (let i = 0; i < DISPATCHER_KEYS.length; i++) {
14
+ const d = globalDispatcher(i);
15
+ if (d !== undefined)
16
+ out.push(d);
17
+ }
18
+ return out;
19
+ }
20
+ function globalDispatcher(i) {
21
+ const d = globalThis[DISPATCHER_KEYS[i]];
22
+ return d !== undefined && d !== null && (typeof d === "object" || typeof d === "function") ? d : undefined;
23
+ }
24
+ function customDispatcher() {
25
+ for (let i = 0; i < DISPATCHER_KEYS.length; i++) {
26
+ const d = globalDispatcher(i);
27
+ if (d === undefined)
28
+ continue;
29
+ const name = d.constructor?.name ?? "";
30
+ if (!DEFAULT_DISPATCHERS.has(name))
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+ function firstGlobalDispatcher() {
36
+ for (let i = 0; i < DISPATCHER_KEYS.length; i++) {
37
+ const d = globalDispatcher(i);
38
+ if (d !== undefined)
39
+ return d;
40
+ }
41
+ return undefined;
42
+ }
43
+ let composed = new WeakMap();
44
+ const timeouts = new WeakMap();
45
+ function composeWith(base, ms) {
46
+ const b = base;
47
+ if (typeof b.compose !== "function")
48
+ return undefined;
49
+ const d = b.compose((dispatch) => (opts, handler) => dispatch({ ...opts, headersTimeout: ms, bodyTimeout: ms }, handler));
50
+ if (d === null || typeof d !== "object" || typeof d.dispatch !== "function")
51
+ return undefined;
52
+ timeouts.set(d, { headersTimeout: ms, bodyTimeout: ms });
53
+ return d;
54
+ }
55
+ function longRequestDispatcher(timeoutMs, base = firstGlobalDispatcher()) {
56
+ try {
57
+ const needed = timeoutMs + exports.LONG_REQUEST_SLACK_MS;
58
+ if (!(needed > exports.FETCH_DEFAULT_TIMEOUT_MS))
59
+ return undefined;
60
+ if (base === undefined)
61
+ return undefined;
62
+ const ms = Math.ceil(needed / BUCKET_MS) * BUCKET_MS;
63
+ let perBase = composed.get(base);
64
+ if (perBase === undefined) {
65
+ perBase = new Map();
66
+ composed.set(base, perBase);
67
+ }
68
+ const hit = perBase.get(ms);
69
+ if (hit !== undefined)
70
+ return hit;
71
+ const d = composeWith(base, ms);
72
+ if (d !== undefined)
73
+ perBase.set(ms, d);
74
+ return d;
75
+ }
76
+ catch {
77
+ return undefined;
78
+ }
79
+ }
80
+ exports.__dispatcherInternals = {
81
+ timeoutsOf(d) {
82
+ return d !== null && typeof d === "object" ? timeouts.get(d) : undefined;
83
+ },
84
+ composeWith(ms) {
85
+ const base = globalDispatchers()[0];
86
+ return base === undefined ? undefined : composeWith(base, ms);
87
+ },
88
+ reset() {
89
+ composed = new WeakMap();
90
+ },
91
+ };
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The GitHub connection (`/v1/integrations/github/*`): the answers' types, and the `POST
3
+ * /snapshots` body for a connected repository (`repo: "github:owner/name"`).
4
+ *
5
+ * A customer installs the platform's GitHub App once (`promigence github connect`); after that a
6
+ * snapshot is built from a private repository by name, with no credential ever passing through
7
+ * the client. Field-for-field with `sdk-py` (`_ops.github_create_body`).
8
+ */
9
+ /** `POST /integrations/github/connect`: open `install_url` in a browser. */
10
+ export interface GithubConnect {
11
+ install_url: string;
12
+ /** RFC 3339: the link works for 15 minutes. */
13
+ expires_at: string;
14
+ app_slug: string;
15
+ }
16
+ /** One connected GitHub account. */
17
+ export interface GithubConnection {
18
+ installation_id: number;
19
+ account: string;
20
+ /** `User` or `Organization`. */
21
+ account_type: string;
22
+ /** `all` or `selected`. */
23
+ repository_selection: string;
24
+ suspended: boolean;
25
+ connected_at: string;
26
+ /** Where the account changes what the App can read, or uninstalls it. */
27
+ manage_url: string;
28
+ }
29
+ /** An install GitHub has not confirmed yet. */
30
+ export interface GithubPending {
31
+ installation_id: number;
32
+ account?: string;
33
+ claimed_at: string;
34
+ state: string;
35
+ message: string;
36
+ }
37
+ /** A snapshot that follows a branch: every push builds it again (each build billed per second). */
38
+ export interface GithubTrack {
39
+ track_id: string;
40
+ repo: string;
41
+ branch: string;
42
+ alias: string;
43
+ head_sha?: string;
44
+ built_sha?: string;
45
+ snapshot_id?: string;
46
+ build_id?: string;
47
+ last_error?: string;
48
+ created_at: string;
49
+ }
50
+ /** `GET /integrations/github`. `configured: false` on a deployment with no GitHub App. */
51
+ export interface GithubStatus {
52
+ configured: boolean;
53
+ app_slug?: string;
54
+ message?: string;
55
+ connections: GithubConnection[];
56
+ pending: GithubPending[];
57
+ tracks: GithubTrack[];
58
+ }
59
+ /** `DELETE /integrations/github/installations/:installation_id`. */
60
+ export interface GithubDisconnected {
61
+ installation_id: number;
62
+ account: string;
63
+ disconnected: boolean;
64
+ tracks_removed: number;
65
+ /** The App stays installed on GitHub until it is uninstalled there. */
66
+ uninstall_url: string;
67
+ message: string;
68
+ }
69
+ /** One repository the organization's connections can read. */
70
+ export interface GithubRepo {
71
+ /** `owner/name`: what `snapshots.create({ repo: "github:owner/name" })` takes. */
72
+ full_name: string;
73
+ private: boolean;
74
+ default_branch: string;
75
+ installation_id: number;
76
+ account: string;
77
+ archived: boolean;
78
+ pushed_at?: string;
79
+ }
80
+ /** `GET /integrations/github/repos`: one page, across every connection. */
81
+ export interface GithubRepos {
82
+ repos: GithubRepo[];
83
+ page: number;
84
+ per_page: number;
85
+ total: number;
86
+ has_more: boolean;
87
+ }
88
+ /** `DELETE /integrations/github/tracks/:track_id`: the snapshot stays; pushes stop rebuilding it. */
89
+ export interface GithubUntracked {
90
+ track_id: string;
91
+ repo: string;
92
+ branch: string;
93
+ alias: string;
94
+ untracked: boolean;
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
+ }
123
+ /** The prefix that names a connected repository in `repo`. */
124
+ export declare const GITHUB_REPO_PREFIX = "github:";
125
+ /**
126
+ * `owner/name` or `github:owner/name` as `{ owner, name }`; `undefined` for anything else — a
127
+ * URL, the scp form, a path. What decides that `snapshot create --repo` means a connected
128
+ * repository rather than a git URL.
129
+ */
130
+ export declare function parseGithubRepo(repo: string | undefined): {
131
+ owner: string;
132
+ name: string;
133
+ } | undefined;
134
+ /** A git ref the API takes: a branch, a tag, `heads/<branch>`, `tags/<tag>` or a commit sha. */
135
+ export declare function validGitRef(ref: string): boolean;
136
+ /** The options of `snapshots.create` the connected-repository form reads. */
137
+ export interface GithubCreateInput {
138
+ repo: string;
139
+ commit?: string | undefined;
140
+ ref?: string | undefined;
141
+ from?: string | undefined;
142
+ track?: boolean | undefined;
143
+ setup?: string[] | undefined;
144
+ verify?: {
145
+ cmd: string;
146
+ allow_fast: boolean;
147
+ } | undefined;
148
+ tier?: string | undefined;
149
+ alias?: string | undefined;
150
+ /** Inputs of the other forms: each one present is refused, by name. */
151
+ others: Array<[string, boolean]>;
152
+ }
153
+ /**
154
+ * The `POST /snapshots` body for a connected repository: `repo: "github:owner/name"` or
155
+ * `"gitlab:group/project"`. Refuses, before anything is sent, what the API refuses: another form's
156
+ * inputs, `ref` with `commit`, `track` without an `alias` or with a `commit`.
157
+ */
158
+ export declare function githubCreateBody(o: GithubCreateInput): Record<string, unknown>;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GITHUB_REPO_PREFIX = void 0;
4
+ exports.parseGithubRepo = parseGithubRepo;
5
+ exports.validGitRef = validGitRef;
6
+ exports.githubCreateBody = githubCreateBody;
7
+ const core_js_1 = require("./core.js");
8
+ const gitlab_js_1 = require("./gitlab.js");
9
+ exports.GITHUB_REPO_PREFIX = "github:";
10
+ const OWNER_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;
11
+ const NAME_RE = /^[A-Za-z0-9._-]{1,100}$/;
12
+ function parseGithubRepo(repo) {
13
+ if (typeof repo !== "string")
14
+ return undefined;
15
+ let r = repo.trim();
16
+ if (r.startsWith(exports.GITHUB_REPO_PREFIX))
17
+ r = r.slice(exports.GITHUB_REPO_PREFIX.length);
18
+ if (r.endsWith(".git"))
19
+ r = r.slice(0, -4);
20
+ const parts = r.split("/");
21
+ if (parts.length !== 2)
22
+ return undefined;
23
+ const [owner, name] = parts;
24
+ if (!OWNER_RE.test(owner) || !NAME_RE.test(name) || name === "." || name === "..")
25
+ return undefined;
26
+ return { owner, name };
27
+ }
28
+ function validGitRef(ref) {
29
+ return ref.length >= 1 && ref.length <= 255
30
+ && !ref.startsWith("-") && !ref.startsWith("/") && !ref.endsWith("/") && !ref.endsWith(".lock")
31
+ && !ref.includes("..") && !ref.includes("//") && !ref.includes("@{")
32
+ && /^[A-Za-z0-9._/+@=,-]+$/.test(ref);
33
+ }
34
+ function githubCreateBody(o) {
35
+ const gitlab = o.repo.trim().startsWith(gitlab_js_1.GITLAB_REPO_PREFIX);
36
+ const glPath = gitlab ? (0, gitlab_js_1.parseGitlabRepo)(o.repo) : undefined;
37
+ const parsed = gitlab ? undefined : parseGithubRepo(o.repo);
38
+ if (gitlab && !glPath) {
39
+ throw new core_js_1.InvalidArgumentError(`snapshots.create repo must be gitlab:group/project for a connected GitLab project, got ${JSON.stringify(o.repo)}`, "Pass { repo: 'gitlab:your-group/app', ref: 'main' }; `promigence repos --provider gitlab` lists what your connection can read.");
40
+ }
41
+ if (!gitlab && !parsed) {
42
+ throw new core_js_1.InvalidArgumentError(`snapshots.create repo must be github:owner/name for a connected repository, got ${JSON.stringify(o.repo)}`, "Pass { repo: 'github:your-org/app', ref: 'main' }; `promigence repos` lists what your connection can read.");
43
+ }
44
+ for (const [field, present] of o.others) {
45
+ if (present) {
46
+ throw new core_js_1.InvalidArgumentError(`snapshots.create ${field} is not taken with a ${gitlab ? "gitlab:" : "github:"} repository`, field === "sources"
47
+ ? "Clone further repositories in a setup step."
48
+ : "A connected repository builds in a sandbox of `from` (default promigence/base): pass the image as a snapshot with `from`, or drop the field.");
49
+ }
50
+ }
51
+ if (o.commit !== undefined && !/^[0-9a-f]{40}$/i.test(o.commit)) {
52
+ throw new core_js_1.InvalidArgumentError(`snapshots.create commit must be a full 40-character sha, got ${JSON.stringify(o.commit)}`, "Pass the full sha, or a branch or tag as `ref`.");
53
+ }
54
+ if (o.ref !== undefined && !validGitRef(o.ref.trim())) {
55
+ throw new core_js_1.InvalidArgumentError(`snapshots.create ref must be a branch, a tag or a commit sha, got ${JSON.stringify(o.ref)}`, "Pass { ref: 'main' }, { ref: 'v1.2.0' } or a commit sha.");
56
+ }
57
+ if (o.ref !== undefined && o.commit !== undefined) {
58
+ throw new core_js_1.InvalidArgumentError("snapshots.create takes ref or commit, not both", "Pass one: a branch or tag as `ref`, or a sha as `commit`.");
59
+ }
60
+ if (o.track && !o.alias) {
61
+ throw new core_js_1.InvalidArgumentError("snapshots.create track needs an alias", "Every push builds the snapshot again and moves the alias to it: pass { alias: 'app-main', track: true }.");
62
+ }
63
+ if (o.track && o.commit !== undefined) {
64
+ throw new core_js_1.InvalidArgumentError("snapshots.create track follows a branch, not a commit", "Pass { ref: 'main', track: true } (no ref: the default branch).");
65
+ }
66
+ const body = {
67
+ repo: gitlab ? `${gitlab_js_1.GITLAB_REPO_PREFIX}${glPath}` : `${exports.GITHUB_REPO_PREFIX}${parsed.owner}/${parsed.name}`,
68
+ };
69
+ if (o.ref !== undefined)
70
+ body["ref"] = o.ref.trim();
71
+ if (o.commit !== undefined)
72
+ body["commit"] = o.commit.toLowerCase();
73
+ if (o.from)
74
+ body["from"] = o.from;
75
+ if (o.setup?.length)
76
+ body["setup"] = [...o.setup];
77
+ if (o.verify)
78
+ body["verify"] = o.verify;
79
+ if (o.tier !== undefined)
80
+ body["tier"] = o.tier;
81
+ if (o.track)
82
+ body["track"] = true;
83
+ return body;
84
+ }