promigence 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 (97) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +88 -0
  3. package/dist/account.js +850 -0
  4. package/dist/agent.js +374 -0
  5. package/dist/argv.js +159 -0
  6. package/dist/bin.js +332 -0
  7. package/dist/bundle.js +133 -0
  8. package/dist/capacity.js +69 -0
  9. package/dist/commands.d.ts +110 -0
  10. package/dist/commands.js +2675 -0
  11. package/dist/connectreq.js +125 -0
  12. package/dist/credfile.js +188 -0
  13. package/dist/credits.js +197 -0
  14. package/dist/devcontainer.d.ts +99 -0
  15. package/dist/devcontainer.js +744 -0
  16. package/dist/device.js +129 -0
  17. package/dist/dirsnap.js +378 -0
  18. package/dist/errfix.js +262 -0
  19. package/dist/generated.d.ts +35 -0
  20. package/dist/generated.js +307 -0
  21. package/dist/github.js +307 -0
  22. package/dist/gitlab.js +210 -0
  23. package/dist/guide.d.ts +19 -0
  24. package/dist/guide.js +366 -0
  25. package/dist/handlers.js +511 -0
  26. package/dist/index.d.ts +14 -0
  27. package/dist/index.js +7 -0
  28. package/dist/init.d.ts +40 -0
  29. package/dist/init.js +352 -0
  30. package/dist/install.d.ts +23 -0
  31. package/dist/install.js +127 -0
  32. package/dist/io.d.ts +37 -0
  33. package/dist/io.js +318 -0
  34. package/dist/live.js +469 -0
  35. package/dist/login.js +1063 -0
  36. package/dist/mcp.js +889 -0
  37. package/dist/money.js +5 -0
  38. package/dist/orgs.js +475 -0
  39. package/dist/pricing.d.ts +52 -0
  40. package/dist/pricing.js +181 -0
  41. package/dist/report.js +132 -0
  42. package/dist/routes.js +135 -0
  43. package/dist/runners-github.js +257 -0
  44. package/dist/runners-gitlab.js +125 -0
  45. package/dist/runners-spec.js +137 -0
  46. package/dist/runners.js +1458 -0
  47. package/dist/sdk/blake3.d.ts +10 -0
  48. package/dist/sdk/blake3.js +111 -0
  49. package/dist/sdk/bundle.d.ts +96 -0
  50. package/dist/sdk/bundle.js +227 -0
  51. package/dist/sdk/ca-guard.js +166 -0
  52. package/dist/sdk/caller.d.ts +73 -0
  53. package/dist/sdk/caller.js +47 -0
  54. package/dist/sdk/client.d.ts +3015 -0
  55. package/dist/sdk/client.js +2583 -0
  56. package/dist/sdk/command-errors.d.ts +41 -0
  57. package/dist/sdk/command-errors.js +36 -0
  58. package/dist/sdk/context.d.ts +119 -0
  59. package/dist/sdk/context.js +588 -0
  60. package/dist/sdk/core.d.ts +285 -0
  61. package/dist/sdk/core.js +266 -0
  62. package/dist/sdk/credstore.d.ts +90 -0
  63. package/dist/sdk/credstore.js +206 -0
  64. package/dist/sdk/dispatcher.js +86 -0
  65. package/dist/sdk/github.d.ts +158 -0
  66. package/dist/sdk/github.js +78 -0
  67. package/dist/sdk/gitlab.d.ts +104 -0
  68. package/dist/sdk/gitlab.js +20 -0
  69. package/dist/sdk/handles.d.ts +82 -0
  70. package/dist/sdk/handles.js +135 -0
  71. package/dist/sdk/hint.js +7 -0
  72. package/dist/sdk/http2.js +1921 -0
  73. package/dist/sdk/index.d.ts +34 -0
  74. package/dist/sdk/index.js +15 -0
  75. package/dist/sdk/keyed.gen.js +7 -0
  76. package/dist/sdk/models.gen.d.ts +6162 -0
  77. package/dist/sdk/models.gen.js +1 -0
  78. package/dist/sdk/proxy.js +249 -0
  79. package/dist/sdk/pty.d.ts +171 -0
  80. package/dist/sdk/pty.js +496 -0
  81. package/dist/sdk/sandbox.d.ts +537 -0
  82. package/dist/sdk/sandbox.js +725 -0
  83. package/dist/sdk/ssh.d.ts +25 -0
  84. package/dist/sdk/ssh.js +114 -0
  85. package/dist/sdk/trace.js +80 -0
  86. package/dist/sdk/transport-worker.js +466 -0
  87. package/dist/sdk/warm.js +40 -0
  88. package/dist/ssh.js +267 -0
  89. package/dist/surface.d.ts +95 -0
  90. package/dist/surface.js +1452 -0
  91. package/dist/templates.js +53 -0
  92. package/dist/term.js +50 -0
  93. package/dist/timing.js +71 -0
  94. package/dist/untar.js +324 -0
  95. package/dist/uploadguard.js +75 -0
  96. package/llms.txt +209 -0
  97. package/package.json +52 -0
@@ -0,0 +1,206 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { accessSync, constants, existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { delimiter, join } from "node:path";
5
+ export const KEYCHAIN_SERVICE = "promigence-cli";
6
+ const KEYCHAIN_LABEL = "Promigence CLI credentials";
7
+ export function storeLabel(store, path) {
8
+ switch (store) {
9
+ case "macos-keychain": return "the macOS Keychain";
10
+ case "secret-service": return "the Secret Service keyring";
11
+ default: return path ? `the credentials file (${path})` : "the credentials file";
12
+ }
13
+ }
14
+ function onPath(name, env) {
15
+ for (const dir of (env["PATH"] ?? "").split(delimiter)) {
16
+ if (!dir)
17
+ continue;
18
+ const p = join(dir, name);
19
+ try {
20
+ accessSync(p, constants.X_OK);
21
+ return p;
22
+ }
23
+ catch { }
24
+ }
25
+ return undefined;
26
+ }
27
+ const toolName = (store) => (store === "macos-keychain" ? "security" : "secret-tool");
28
+ function sessionBus(env) {
29
+ if (env["DBUS_SESSION_BUS_ADDRESS"])
30
+ return true;
31
+ const run = env["XDG_RUNTIME_DIR"];
32
+ return Boolean(run) && existsSync(join(run, "bus"));
33
+ }
34
+ function userHome(env, home = homedir) {
35
+ const h = env["HOME"];
36
+ if (h)
37
+ return h;
38
+ try {
39
+ return home() || undefined;
40
+ }
41
+ catch {
42
+ return undefined;
43
+ }
44
+ }
45
+ const warnedSetting = new Set();
46
+ export function keychainStore(env = process.env, platform = process.platform, home = homedir) {
47
+ const raw = (env["PROMIGENCE_CREDENTIAL_STORE"] ?? "").trim().toLowerCase();
48
+ switch (raw) {
49
+ case "file": return undefined;
50
+ case "macos-keychain":
51
+ case "secret-service": return raw;
52
+ case "":
53
+ case "auto": break;
54
+ default:
55
+ if (!warnedSetting.has(raw)) {
56
+ warnedSetting.add(raw);
57
+ process.emitWarning(`PROMIGENCE_CREDENTIAL_STORE=${raw} is not one of auto, file, macos-keychain, secret-service; using auto`, { code: "PROMIGENCE_CREDENTIALS" });
58
+ }
59
+ }
60
+ if (platform === "darwin") {
61
+ const h = userHome(env, home);
62
+ return h && existsSync(join(h, "Library", "Keychains")) && onPath("security", env) ? "macos-keychain" : undefined;
63
+ }
64
+ if (platform === "win32")
65
+ return undefined;
66
+ return onPath("secret-tool", env) && sessionBus(env) ? "secret-service" : undefined;
67
+ }
68
+ export function keychainsToClear(env = process.env, platform = process.platform) {
69
+ const out = new Set();
70
+ const chosen = keychainStore(env, platform);
71
+ if (chosen)
72
+ out.add(chosen);
73
+ const auto = keychainStore({ ...env, PROMIGENCE_CREDENTIAL_STORE: "auto" }, platform);
74
+ if (auto)
75
+ out.add(auto);
76
+ return [...out];
77
+ }
78
+ const LOCKED = /interaction is not allowed|is locked/i;
79
+ function failureText(e) {
80
+ if (e.code === "ENOENT")
81
+ return "its command-line tool is not installed";
82
+ if (e.code === "ETIMEDOUT" || e.signal === "SIGTERM")
83
+ return "it did not answer in time";
84
+ const stderr = String(e.stderr ?? "");
85
+ if (LOCKED.test(stderr))
86
+ return "it is locked (an SSH session cannot unlock it)";
87
+ if (/default keychain could not be found|keychain could not be found|No such keychain/i.test(stderr))
88
+ return "there is no default keychain";
89
+ if (/authorization was canceled|canceled by the user/i.test(stderr))
90
+ return "access was not allowed";
91
+ if (/Cannot autolaunch D-Bus|org\.freedesktop|D-Bus|dbus/i.test(stderr))
92
+ return "no Secret Service is answering on the session bus";
93
+ return `exit ${e.status ?? "?"}`;
94
+ }
95
+ function maybeHex(out) {
96
+ if (out.length >= 2 && out.length % 2 === 0 && /^[0-9a-f]+$/i.test(out)) {
97
+ const text = Buffer.from(out, "hex").toString("utf8");
98
+ if (text.startsWith("{"))
99
+ return text;
100
+ }
101
+ return out;
102
+ }
103
+ const READ_TIMEOUT_MS = 10_000;
104
+ const WRITE_TIMEOUT_MS = 60_000;
105
+ function runTool(store, args, env, input, timeout) {
106
+ const bin = onPath(toolName(store), env);
107
+ if (bin === undefined)
108
+ throw Object.assign(new Error(`${toolName(store)} not found`), { code: "ENOENT" });
109
+ const h = store === "macos-keychain" && !env["HOME"] ? userHome(env) : undefined;
110
+ return execFileSync(bin, args, {
111
+ encoding: "utf8", timeout, env: h ? { ...env, HOME: h } : env,
112
+ ...(input === undefined ? { stdio: ["ignore", "pipe", "pipe"] } : { input, stdio: ["pipe", "pipe", "pipe"] }),
113
+ });
114
+ }
115
+ export function keychainRead(store, account, env = process.env) {
116
+ const args = store === "macos-keychain"
117
+ ? ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account, "-w"]
118
+ : ["lookup", "service", KEYCHAIN_SERVICE, "account", account];
119
+ try {
120
+ const text = maybeHex(runTool(store, args, env, undefined, READ_TIMEOUT_MS).replace(/\r?\n$/, ""));
121
+ return text ? { status: "found", text } : { status: "absent" };
122
+ }
123
+ catch (e) {
124
+ const f = e;
125
+ const stderr = String(f.stderr ?? "");
126
+ if (store === "macos-keychain" && f.status === 44)
127
+ return { status: "absent" };
128
+ if (store === "secret-service" && f.status === 1 && !stderr.trim())
129
+ return { status: "absent" };
130
+ return { status: "unavailable", reason: failureText(f), locked: LOCKED.test(stderr) || (store === "macos-keychain" && f.status === 36) };
131
+ }
132
+ }
133
+ function quotable(account) {
134
+ return /^[^"\\\u0000-\u001f\u007f]+$/.test(account);
135
+ }
136
+ function macosDefaultKeychain(env) {
137
+ try {
138
+ const p = runTool("macos-keychain", ["default-keychain", "-d", "user"], env, undefined, READ_TIMEOUT_MS).trim().replace(/^"|"$/g, "");
139
+ return p.length > 0 && existsSync(p);
140
+ }
141
+ catch {
142
+ return false;
143
+ }
144
+ }
145
+ const MAX_SECURITY_LINE = 2048;
146
+ export function keychainWrite(store, account, text, env = process.env) {
147
+ try {
148
+ if (store === "macos-keychain") {
149
+ if (!quotable(account))
150
+ return { ok: false, reason: "the credentials path holds a quote, backslash or control character" };
151
+ if (!macosDefaultKeychain(env))
152
+ return { ok: false, reason: "there is no default keychain" };
153
+ const hex = Buffer.from(text, "utf8").toString("hex");
154
+ const line = `add-generic-password -U -s "${KEYCHAIN_SERVICE}" -a "${account}" -l "${KEYCHAIN_LABEL}" -X ${hex}\n`;
155
+ if (line.length > MAX_SECURITY_LINE)
156
+ return { ok: false, reason: "the sign-in is too long for the keychain tool's command reader" };
157
+ runTool(store, ["-i"], env, line, WRITE_TIMEOUT_MS);
158
+ }
159
+ else {
160
+ runTool(store, ["store", `--label=${KEYCHAIN_LABEL}`, "service", KEYCHAIN_SERVICE, "account", account], env, text, WRITE_TIMEOUT_MS);
161
+ }
162
+ return { ok: true };
163
+ }
164
+ catch (e) {
165
+ return { ok: false, reason: failureText(e) };
166
+ }
167
+ }
168
+ export function keychainDelete(store, account, env = process.env) {
169
+ const args = store === "macos-keychain"
170
+ ? ["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account]
171
+ : ["clear", "service", KEYCHAIN_SERVICE, "account", account];
172
+ try {
173
+ runTool(store, args, env, undefined, WRITE_TIMEOUT_MS);
174
+ return { ok: true, absent: false };
175
+ }
176
+ catch (e) {
177
+ const f = e;
178
+ if (store === "macos-keychain" && f.status === 44)
179
+ return { ok: true, absent: true };
180
+ if (f.code === "ENOENT")
181
+ return { ok: true, absent: true };
182
+ return { ok: false, reason: failureText(f), locked: LOCKED.test(String(f.stderr ?? "")) };
183
+ }
184
+ }
185
+ export function encodeCredentials(body) {
186
+ return JSON.stringify(body).replace(/[\u007f-￿]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
187
+ }
188
+ const CACHE_TTL_MS = 30_000;
189
+ const cache = new Map();
190
+ const cacheKey = (store, account) => `${store}\u0000${account}`;
191
+ export function keychainReadCached(store, account, env = process.env) {
192
+ const k = cacheKey(store, account);
193
+ const hit = cache.get(k);
194
+ if (hit && Date.now() - hit.at < CACHE_TTL_MS)
195
+ return hit.value;
196
+ const value = keychainRead(store, account, env);
197
+ if (value.status !== "unavailable")
198
+ cache.set(k, { at: Date.now(), value });
199
+ return value;
200
+ }
201
+ export function keychainCacheSet(store, account, text) {
202
+ cache.set(cacheKey(store, account), { at: Date.now(), value: text === undefined ? { status: "absent" } : { status: "found", text } });
203
+ }
204
+ export function forgetKeychainReads() {
205
+ cache.clear();
206
+ }
@@ -0,0 +1,86 @@
1
+ export const FETCH_DEFAULT_TIMEOUT_MS = 300_000;
2
+ export const LONG_REQUEST_SLACK_MS = 60_000;
3
+ const BUCKET_MS = 5 * 60_000;
4
+ const DEFAULT_DISPATCHERS = new Set(["Agent", "Dispatcher1Wrapper"]);
5
+ const DISPATCHER_KEYS = [Symbol.for("undici.globalDispatcher.1"), Symbol.for("undici.globalDispatcher.2")];
6
+ function globalDispatchers() {
7
+ const out = [];
8
+ for (let i = 0; i < DISPATCHER_KEYS.length; i++) {
9
+ const d = globalDispatcher(i);
10
+ if (d !== undefined)
11
+ out.push(d);
12
+ }
13
+ return out;
14
+ }
15
+ function globalDispatcher(i) {
16
+ const d = globalThis[DISPATCHER_KEYS[i]];
17
+ return d !== undefined && d !== null && (typeof d === "object" || typeof d === "function") ? d : undefined;
18
+ }
19
+ export function customDispatcher() {
20
+ for (let i = 0; i < DISPATCHER_KEYS.length; i++) {
21
+ const d = globalDispatcher(i);
22
+ if (d === undefined)
23
+ continue;
24
+ const name = d.constructor?.name ?? "";
25
+ if (!DEFAULT_DISPATCHERS.has(name))
26
+ return true;
27
+ }
28
+ return false;
29
+ }
30
+ function firstGlobalDispatcher() {
31
+ for (let i = 0; i < DISPATCHER_KEYS.length; i++) {
32
+ const d = globalDispatcher(i);
33
+ if (d !== undefined)
34
+ return d;
35
+ }
36
+ return undefined;
37
+ }
38
+ let composed = new WeakMap();
39
+ const timeouts = new WeakMap();
40
+ function composeWith(base, ms) {
41
+ const b = base;
42
+ if (typeof b.compose !== "function")
43
+ return undefined;
44
+ const d = b.compose((dispatch) => (opts, handler) => dispatch({ ...opts, headersTimeout: ms, bodyTimeout: ms }, handler));
45
+ if (d === null || typeof d !== "object" || typeof d.dispatch !== "function")
46
+ return undefined;
47
+ timeouts.set(d, { headersTimeout: ms, bodyTimeout: ms });
48
+ return d;
49
+ }
50
+ export function longRequestDispatcher(timeoutMs, base = firstGlobalDispatcher()) {
51
+ try {
52
+ const needed = timeoutMs + LONG_REQUEST_SLACK_MS;
53
+ if (!(needed > FETCH_DEFAULT_TIMEOUT_MS))
54
+ return undefined;
55
+ if (base === undefined)
56
+ return undefined;
57
+ const ms = Math.ceil(needed / BUCKET_MS) * BUCKET_MS;
58
+ let perBase = composed.get(base);
59
+ if (perBase === undefined) {
60
+ perBase = new Map();
61
+ composed.set(base, perBase);
62
+ }
63
+ const hit = perBase.get(ms);
64
+ if (hit !== undefined)
65
+ return hit;
66
+ const d = composeWith(base, ms);
67
+ if (d !== undefined)
68
+ perBase.set(ms, d);
69
+ return d;
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ }
75
+ export const __dispatcherInternals = {
76
+ timeoutsOf(d) {
77
+ return d !== null && typeof d === "object" ? timeouts.get(d) : undefined;
78
+ },
79
+ composeWith(ms) {
80
+ const base = globalDispatchers()[0];
81
+ return base === undefined ? undefined : composeWith(base, ms);
82
+ },
83
+ reset() {
84
+ composed = new WeakMap();
85
+ },
86
+ };
@@ -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,78 @@
1
+ import { InvalidArgumentError } from "./core.js";
2
+ import { GITLAB_REPO_PREFIX, parseGitlabRepo } from "./gitlab.js";
3
+ export const GITHUB_REPO_PREFIX = "github:";
4
+ const OWNER_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;
5
+ const NAME_RE = /^[A-Za-z0-9._-]{1,100}$/;
6
+ export function parseGithubRepo(repo) {
7
+ if (typeof repo !== "string")
8
+ return undefined;
9
+ let r = repo.trim();
10
+ if (r.startsWith(GITHUB_REPO_PREFIX))
11
+ r = r.slice(GITHUB_REPO_PREFIX.length);
12
+ if (r.endsWith(".git"))
13
+ r = r.slice(0, -4);
14
+ const parts = r.split("/");
15
+ if (parts.length !== 2)
16
+ return undefined;
17
+ const [owner, name] = parts;
18
+ if (!OWNER_RE.test(owner) || !NAME_RE.test(name) || name === "." || name === "..")
19
+ return undefined;
20
+ return { owner, name };
21
+ }
22
+ export function validGitRef(ref) {
23
+ return ref.length >= 1 && ref.length <= 255
24
+ && !ref.startsWith("-") && !ref.startsWith("/") && !ref.endsWith("/") && !ref.endsWith(".lock")
25
+ && !ref.includes("..") && !ref.includes("//") && !ref.includes("@{")
26
+ && /^[A-Za-z0-9._/+@=,-]+$/.test(ref);
27
+ }
28
+ export function githubCreateBody(o) {
29
+ const gitlab = o.repo.trim().startsWith(GITLAB_REPO_PREFIX);
30
+ const glPath = gitlab ? parseGitlabRepo(o.repo) : undefined;
31
+ const parsed = gitlab ? undefined : parseGithubRepo(o.repo);
32
+ if (gitlab && !glPath) {
33
+ throw new 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.");
34
+ }
35
+ if (!gitlab && !parsed) {
36
+ throw new 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.");
37
+ }
38
+ for (const [field, present] of o.others) {
39
+ if (present) {
40
+ throw new InvalidArgumentError(`snapshots.create ${field} is not taken with a ${gitlab ? "gitlab:" : "github:"} repository`, field === "sources"
41
+ ? "Clone further repositories in a setup step."
42
+ : "A connected repository builds in a sandbox of `from` (default promigence/base): pass the image as a snapshot with `from`, or drop the field.");
43
+ }
44
+ }
45
+ if (o.commit !== undefined && !/^[0-9a-f]{40}$/i.test(o.commit)) {
46
+ throw new 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`.");
47
+ }
48
+ if (o.ref !== undefined && !validGitRef(o.ref.trim())) {
49
+ throw new 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.");
50
+ }
51
+ if (o.ref !== undefined && o.commit !== undefined) {
52
+ throw new InvalidArgumentError("snapshots.create takes ref or commit, not both", "Pass one: a branch or tag as `ref`, or a sha as `commit`.");
53
+ }
54
+ if (o.track && !o.alias) {
55
+ throw new 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 }.");
56
+ }
57
+ if (o.track && o.commit !== undefined) {
58
+ throw new InvalidArgumentError("snapshots.create track follows a branch, not a commit", "Pass { ref: 'main', track: true } (no ref: the default branch).");
59
+ }
60
+ const body = {
61
+ repo: gitlab ? `${GITLAB_REPO_PREFIX}${glPath}` : `${GITHUB_REPO_PREFIX}${parsed.owner}/${parsed.name}`,
62
+ };
63
+ if (o.ref !== undefined)
64
+ body["ref"] = o.ref.trim();
65
+ if (o.commit !== undefined)
66
+ body["commit"] = o.commit.toLowerCase();
67
+ if (o.from)
68
+ body["from"] = o.from;
69
+ if (o.setup?.length)
70
+ body["setup"] = [...o.setup];
71
+ if (o.verify)
72
+ body["verify"] = o.verify;
73
+ if (o.tier !== undefined)
74
+ body["tier"] = o.tier;
75
+ if (o.track)
76
+ body["track"] = true;
77
+ return body;
78
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The GitLab connection (`/v1/integrations/gitlab/*`): the answers' types, and the project path
3
+ * `snapshots.create({ repo: "gitlab:group/project" })` takes.
4
+ *
5
+ * A person authorizes the platform's GitLab application once, read-only (`promigence gitlab
6
+ * connect`), and an owner of the organization confirms it in the browser; after that a snapshot is
7
+ * built from a private project by its path, with no credential ever passing through the client.
8
+ */
9
+ /** `POST /integrations/gitlab/connect`: open `authorize_url` in a browser. */
10
+ export interface GitlabConnect {
11
+ authorize_url: string;
12
+ /** RFC 3339: the link works for 15 minutes. */
13
+ expires_at: string;
14
+ gitlab_url: string;
15
+ /** Read-only: `read_api`, `read_repository`. */
16
+ scopes: string[];
17
+ }
18
+ /** One GitLab user whose read-only authorization is connected. */
19
+ export interface GitlabConnection {
20
+ connection_id: string;
21
+ username: string;
22
+ user_id: number;
23
+ gitlab_url: string;
24
+ connected_at: string;
25
+ /** The authorization stopped working on GitLab: connect again. */
26
+ broken: boolean;
27
+ broken_reason?: string;
28
+ /** Optional: a project webhook (push events) for instant rebuilds of tracked branches. */
29
+ webhook_url: string;
30
+ webhook_token: string;
31
+ }
32
+ /** An authorization waiting for an owner of the organization to confirm it. */
33
+ export interface GitlabPending {
34
+ connection_id: string;
35
+ username: string;
36
+ authorized_at: string;
37
+ expires_at: string;
38
+ message: string;
39
+ }
40
+ /** A snapshot that follows a GitLab branch: each new commit builds it again (each build billed per second). */
41
+ export interface GitlabTrack {
42
+ track_id: string;
43
+ repo: string;
44
+ branch: string;
45
+ alias: string;
46
+ head_sha?: string;
47
+ built_sha?: string;
48
+ snapshot_id?: string;
49
+ build_id?: string;
50
+ last_error?: string;
51
+ created_at: string;
52
+ }
53
+ /** `GET /integrations/gitlab`. `configured: false` on a deployment with no GitLab application. */
54
+ export interface GitlabStatus {
55
+ configured: boolean;
56
+ gitlab_url?: string;
57
+ message?: string;
58
+ connections: GitlabConnection[];
59
+ pending: GitlabPending[];
60
+ tracks: GitlabTrack[];
61
+ }
62
+ /** `DELETE /integrations/gitlab/connections/:connection_id`. */
63
+ export interface GitlabDisconnected {
64
+ connection_id: string;
65
+ username: string;
66
+ disconnected: boolean;
67
+ /** Our token was revoked on GitLab. */
68
+ revoked: boolean;
69
+ tracks_removed: number;
70
+ message: string;
71
+ }
72
+ /** One GitLab project a connection can read. */
73
+ export interface GitlabRepo {
74
+ /** `group/sub/project`: what `snapshots.create({ repo: "gitlab:<path>" })` takes. */
75
+ path: string;
76
+ project_id: number;
77
+ private: boolean;
78
+ default_branch: string;
79
+ archived: boolean;
80
+ connection_id: string;
81
+ last_activity_at?: string;
82
+ }
83
+ /** `GET /integrations/gitlab/repos`: one page of one connection's projects. */
84
+ export interface GitlabRepos {
85
+ repos: GitlabRepo[];
86
+ page: number;
87
+ per_page: number;
88
+ has_more: boolean;
89
+ }
90
+ /** `DELETE /integrations/gitlab/tracks/:track_id`. */
91
+ export interface GitlabUntracked {
92
+ track_id: string;
93
+ repo: string;
94
+ branch: string;
95
+ alias: string;
96
+ untracked: boolean;
97
+ }
98
+ /** The prefix that names a connected GitLab project in `repo`. */
99
+ export declare const GITLAB_REPO_PREFIX = "gitlab:";
100
+ /**
101
+ * `gitlab:group/sub/project` as `group/sub/project`; `undefined` for anything that is not one (2 to
102
+ * 20 path segments of letters, digits, `.` `_` `-`). The same rule as the API's.
103
+ */
104
+ export declare function parseGitlabRepo(repo: string | undefined): string | undefined;
@@ -0,0 +1,20 @@
1
+ export const GITLAB_REPO_PREFIX = "gitlab:";
2
+ const SEGMENT_RE = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,99}$/;
3
+ export function parseGitlabRepo(repo) {
4
+ if (typeof repo !== "string")
5
+ return undefined;
6
+ let r = repo.trim();
7
+ if (!r.startsWith(GITLAB_REPO_PREFIX))
8
+ return undefined;
9
+ r = r.slice(GITLAB_REPO_PREFIX.length);
10
+ if (r.endsWith(".git"))
11
+ r = r.slice(0, -4);
12
+ if (!r || r.length > 255)
13
+ return undefined;
14
+ const segs = r.split("/");
15
+ if (segs.length < 2 || segs.length > 20)
16
+ return undefined;
17
+ if (!segs.every((s) => SEGMENT_RE.test(s) && s !== ".." && !s.endsWith(".atom")))
18
+ return undefined;
19
+ return r;
20
+ }