run402 4.130.4 → 4.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/assets.mjs +5 -5
- package/lib/ci.mjs +64 -18
- package/lib/deploy-v2.mjs +1 -1
- package/package.json +1 -1
- package/vault-surface.json +1 -1
package/lib/assets.mjs
CHANGED
|
@@ -362,7 +362,7 @@ const VALID_SORTS = new Set(["key:asc", "createdAt:asc", "createdAt:desc"]);
|
|
|
362
362
|
function parseSortFlag(raw) {
|
|
363
363
|
if (typeof raw !== "string" || !VALID_SORTS.has(raw)) {
|
|
364
364
|
fail({
|
|
365
|
-
code: "
|
|
365
|
+
code: "INVALID_SORT",
|
|
366
366
|
message: `--sort must be one of: ${[...VALID_SORTS].join(", ")} — got: ${raw}`,
|
|
367
367
|
details: { flag: "--sort", value: raw },
|
|
368
368
|
});
|
|
@@ -390,7 +390,7 @@ function applyFilterFlag(filter, raw) {
|
|
|
390
390
|
const value = raw.slice(eq + 1);
|
|
391
391
|
if (!VALID_FILTER_KEYS.has(key)) {
|
|
392
392
|
fail({
|
|
393
|
-
code: "
|
|
393
|
+
code: "INVALID_FILTER_KEY",
|
|
394
394
|
message: `--filter key ${key} not in: ${[...VALID_FILTER_KEYS].join(", ")}`,
|
|
395
395
|
details: { flag: "--filter", key },
|
|
396
396
|
});
|
|
@@ -398,7 +398,7 @@ function applyFilterFlag(filter, raw) {
|
|
|
398
398
|
if (key === "is_image") {
|
|
399
399
|
if (value !== "true" && value !== "false") {
|
|
400
400
|
fail({
|
|
401
|
-
code: "
|
|
401
|
+
code: "INVALID_FILTER_KEY",
|
|
402
402
|
message: `--filter is_image must be 'true' or 'false', got: ${value}`,
|
|
403
403
|
});
|
|
404
404
|
}
|
|
@@ -407,7 +407,7 @@ function applyFilterFlag(filter, raw) {
|
|
|
407
407
|
const n = Number(value);
|
|
408
408
|
if (!Number.isSafeInteger(n) || n < 0) {
|
|
409
409
|
fail({
|
|
410
|
-
code: "
|
|
410
|
+
code: "INVALID_FILTER_KEY",
|
|
411
411
|
message: `--filter ${key} must be a non-negative integer, got: ${value}`,
|
|
412
412
|
});
|
|
413
413
|
}
|
|
@@ -415,7 +415,7 @@ function applyFilterFlag(filter, raw) {
|
|
|
415
415
|
} else {
|
|
416
416
|
if (value.length === 0) {
|
|
417
417
|
fail({
|
|
418
|
-
code: "
|
|
418
|
+
code: "INVALID_FILTER_KEY",
|
|
419
419
|
message: `--filter ${key} must be a non-empty string`,
|
|
420
420
|
});
|
|
421
421
|
}
|
package/lib/ci.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const { version: RUN402_VERSION } = JSON.parse(
|
|
|
21
21
|
const HELP = `run402 ci — Manage CI/OIDC deploy bindings
|
|
22
22
|
|
|
23
23
|
Usage:
|
|
24
|
-
run402 ci link github [--project <project_id>] [--manifest <path>] [--repo <owner/repo>] [--branch <name> | --environment <name>] [--repository-id <repository_id>] [--workflow <path>] [--expires-at <iso>] [--route-scope <pattern> ...] [--force]
|
|
24
|
+
run402 ci link github [--project <project_id>] [--manifest <path>] [--repo <owner/repo>] [--branch <name> | --environment <name>] [--repository-id <repository_id>] [--workflow <path> | --no-workflow] [--expires-at <iso>] [--route-scope <pattern> ...] [--force]
|
|
25
25
|
run402 ci list [--project <project_id>]
|
|
26
26
|
run402 ci revoke <binding_id>
|
|
27
27
|
run402 ci set-asset-scopes <binding_id> <scope1> [<scope2> ...]
|
|
@@ -37,16 +37,19 @@ const SUB_HELP = {
|
|
|
37
37
|
link: `run402 ci link github — Link GitHub Actions OIDC for run402 deploy
|
|
38
38
|
|
|
39
39
|
Usage:
|
|
40
|
-
run402 ci link github [--project <project_id>] [--manifest <path>] [--repo <owner/repo>] [--branch <name> | --environment <name>] [--repository-id <repository_id>] [--workflow <path>] [--expires-at <iso>] [--route-scope <pattern> ...] [--force]
|
|
40
|
+
run402 ci link github [--project <project_id>] [--manifest <path>] [--repo <owner/repo>] [--branch <name> | --environment <name>] [--repository-id <repository_id>] [--workflow <path> | --no-workflow] [--expires-at <iso>] [--route-scope <pattern> ...] [--force]
|
|
41
41
|
|
|
42
42
|
Options:
|
|
43
43
|
--project <project_id> Project ID (defaults to the active project)
|
|
44
44
|
--manifest <path> Manifest path used by the generated workflow (default: run402.deploy.json)
|
|
45
|
-
--repo <owner/repo> GitHub repo (
|
|
45
|
+
--repo <owner/repo> GitHub repo as owner/repo, owner@<id>/repo@<id> (the spelling of an immutable
|
|
46
|
+
OIDC subject, which also gives the repository id) or a github.com URL
|
|
47
|
+
(default: inferred from origin remote)
|
|
46
48
|
--branch <name> Branch subject and push trigger (default: current branch)
|
|
47
49
|
--environment <name> GitHub environment subject; adds job.environment
|
|
48
50
|
--repository-id <repository_id> Numeric GitHub repository id when API lookup is unavailable
|
|
49
51
|
--workflow <path> Workflow path (default: .github/workflows/run402-deploy.yml)
|
|
52
|
+
--no-workflow Create the binding only, for a repository whose workflow already runs run402 deploy
|
|
50
53
|
--expires-at <iso> Optional binding expiration timestamp
|
|
51
54
|
--route-scope <pattern> Optional exact path or final wildcard route scope, repeatable (examples: /admin, /api/*)
|
|
52
55
|
--force Overwrite an existing workflow file
|
|
@@ -107,8 +110,8 @@ function parseFlags(args, allowed, { repeatable = new Set() } = {}) {
|
|
|
107
110
|
details: { flag: arg },
|
|
108
111
|
});
|
|
109
112
|
}
|
|
110
|
-
if (arg === "--force") {
|
|
111
|
-
flags.
|
|
113
|
+
if (arg === "--force" || arg === "--no-workflow") {
|
|
114
|
+
flags[arg.slice(2).replace(/-/g, "_")] = true;
|
|
112
115
|
continue;
|
|
113
116
|
}
|
|
114
117
|
if (!args[i + 1] || args[i + 1].startsWith("--")) {
|
|
@@ -181,6 +184,21 @@ function parseGithubRemote(remote) {
|
|
|
181
184
|
return null;
|
|
182
185
|
}
|
|
183
186
|
|
|
187
|
+
// `@` and `:` cannot appear in a GitHub owner or repository name.
|
|
188
|
+
const GITHUB_REPO_NAME = /^[^/\s@:]+\/[^/\s@:]+$/;
|
|
189
|
+
const GITHUB_REPO_IMMUTABLE = /^([^/\s@:]+)@(\d+)\/([^/\s@:]+)@(\d+)$/;
|
|
190
|
+
|
|
191
|
+
// --repo takes the repository however the owner has it: owner/repo, the
|
|
192
|
+
// owner@<owner_id>/repo@<repo_id> spelling of GitHub's immutable OIDC subject
|
|
193
|
+
// (which also carries the repository id), or a github.com URL. The binding is
|
|
194
|
+
// always written in the name form; the gateway matches either spelling.
|
|
195
|
+
function resolveRepo(value) {
|
|
196
|
+
const immutable = GITHUB_REPO_IMMUTABLE.exec(value);
|
|
197
|
+
if (immutable) return { repo: `${immutable[1]}/${immutable[3]}`, repoId: immutable[4] };
|
|
198
|
+
const repo = parseGithubRemote(value) || value;
|
|
199
|
+
return GITHUB_REPO_NAME.test(repo) ? { repo, repoId: null } : null;
|
|
200
|
+
}
|
|
201
|
+
|
|
184
202
|
function inferBranch() {
|
|
185
203
|
return git(["branch", "--show-current"]);
|
|
186
204
|
}
|
|
@@ -259,6 +277,7 @@ async function linkGithub(args) {
|
|
|
259
277
|
"--environment",
|
|
260
278
|
"--repository-id",
|
|
261
279
|
"--workflow",
|
|
280
|
+
"--no-workflow",
|
|
262
281
|
"--expires-at",
|
|
263
282
|
"--route-scope",
|
|
264
283
|
"--force",
|
|
@@ -277,18 +296,41 @@ async function linkGithub(args) {
|
|
|
277
296
|
});
|
|
278
297
|
}
|
|
279
298
|
|
|
299
|
+
if (flags.no_workflow) {
|
|
300
|
+
const clash = ["--workflow", "--manifest", "--force"].find((flag) => flags[flag.slice(2)] !== undefined);
|
|
301
|
+
if (clash) {
|
|
302
|
+
fail({
|
|
303
|
+
code: "BAD_USAGE",
|
|
304
|
+
message: `--no-workflow writes no workflow, so ${clash} has nothing to apply to.`,
|
|
305
|
+
details: { flag: clash },
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
280
310
|
const projectId = resolveProjectId(flags.project);
|
|
281
|
-
const
|
|
282
|
-
if (!
|
|
311
|
+
const resolved = resolveRepo(flags.repo || inferRepo() || "");
|
|
312
|
+
if (!resolved) {
|
|
283
313
|
fail({
|
|
284
314
|
code: "GITHUB_REPO_REQUIRED",
|
|
285
|
-
message:
|
|
315
|
+
message: flags.repo
|
|
316
|
+
? `--repo must be owner/repo, owner@<id>/repo@<id> or a github.com URL; got ${JSON.stringify(flags.repo)}.`
|
|
317
|
+
: "Could not infer GitHub owner/repo from git remote origin.",
|
|
286
318
|
hint: "Pass --repo <owner/repo>.",
|
|
287
319
|
});
|
|
288
320
|
}
|
|
321
|
+
const { repo } = resolved;
|
|
322
|
+
if (resolved.repoId && flags.repository_id && resolved.repoId !== flags.repository_id) {
|
|
323
|
+
fail({
|
|
324
|
+
code: "GITHUB_REPOSITORY_ID_CONFLICT",
|
|
325
|
+
message: `--repo names repository id ${resolved.repoId}, but --repository-id is ${flags.repository_id}.`,
|
|
326
|
+
hint: "Drop --repository-id, or pass the --repo of the repository it names.",
|
|
327
|
+
details: { repo, repo_id_in_repo: resolved.repoId, repository_id: flags.repository_id },
|
|
328
|
+
});
|
|
329
|
+
}
|
|
289
330
|
const branch = flags.environment ? (flags.branch || inferBranch() || null) : (flags.branch || inferBranch());
|
|
290
331
|
const subject = buildSubject(repo, { branch, environment: flags.environment });
|
|
291
|
-
const
|
|
332
|
+
const repositoryIdProvided = flags.repository_id || resolved.repoId;
|
|
333
|
+
const repositoryId = repositoryIdProvided || await fetchGithubRepositoryId(repo);
|
|
292
334
|
if (!repositoryId) {
|
|
293
335
|
fail({
|
|
294
336
|
code: "GITHUB_REPOSITORY_ID_REQUIRED",
|
|
@@ -298,9 +340,9 @@ async function linkGithub(args) {
|
|
|
298
340
|
});
|
|
299
341
|
}
|
|
300
342
|
|
|
301
|
-
const workflowPath = flags.workflow || ".github/workflows/run402-deploy.yml";
|
|
302
|
-
const absWorkflowPath = resolve(process.cwd(), workflowPath);
|
|
303
|
-
if (existsSync(absWorkflowPath) && !flags.force) {
|
|
343
|
+
const workflowPath = flags.no_workflow ? null : flags.workflow || ".github/workflows/run402-deploy.yml";
|
|
344
|
+
const absWorkflowPath = workflowPath && resolve(process.cwd(), workflowPath);
|
|
345
|
+
if (absWorkflowPath && existsSync(absWorkflowPath) && !flags.force) {
|
|
304
346
|
fail({
|
|
305
347
|
code: "WORKFLOW_EXISTS",
|
|
306
348
|
message: `Workflow already exists: ${workflowPath}`,
|
|
@@ -309,7 +351,7 @@ async function linkGithub(args) {
|
|
|
309
351
|
});
|
|
310
352
|
}
|
|
311
353
|
|
|
312
|
-
const manifest = flags.manifest || "run402.deploy.json";
|
|
354
|
+
const manifest = flags.no_workflow ? null : flags.manifest || "run402.deploy.json";
|
|
313
355
|
const routeScopes = Array.isArray(flags.route_scope) ? flags.route_scope : [];
|
|
314
356
|
const nonce = randomBytes(16).toString("hex");
|
|
315
357
|
const values = {
|
|
@@ -334,7 +376,7 @@ async function linkGithub(args) {
|
|
|
334
376
|
});
|
|
335
377
|
}
|
|
336
378
|
|
|
337
|
-
const workflow = generateWorkflow({
|
|
379
|
+
const workflow = workflowPath && generateWorkflow({
|
|
338
380
|
branch,
|
|
339
381
|
environment: flags.environment || null,
|
|
340
382
|
manifest,
|
|
@@ -348,8 +390,10 @@ async function linkGithub(args) {
|
|
|
348
390
|
signed_delegation: signedDelegation,
|
|
349
391
|
});
|
|
350
392
|
const outputRouteScopes = Array.isArray(binding.route_scopes) ? binding.route_scopes : [];
|
|
351
|
-
|
|
352
|
-
|
|
393
|
+
if (absWorkflowPath) {
|
|
394
|
+
mkdirSync(dirname(absWorkflowPath), { recursive: true });
|
|
395
|
+
writeFileSync(absWorkflowPath, workflow, { encoding: "utf8", mode: 0o644 });
|
|
396
|
+
}
|
|
353
397
|
console.log(JSON.stringify({
|
|
354
398
|
binding_id: binding.id,
|
|
355
399
|
project_id: projectId,
|
|
@@ -359,12 +403,14 @@ async function linkGithub(args) {
|
|
|
359
403
|
allowed_actions: [...V1_CI_ALLOWED_ACTIONS],
|
|
360
404
|
route_scopes: outputRouteScopes,
|
|
361
405
|
github_repository_id: repositoryId,
|
|
362
|
-
github_repository_id_status:
|
|
406
|
+
github_repository_id_status: repositoryIdProvided ? "provided" : "verified",
|
|
363
407
|
workflow_path: workflowPath,
|
|
364
408
|
manifest_path: manifest,
|
|
365
409
|
run402_version: RUN402_VERSION,
|
|
366
410
|
delegation_chain_id: DEFAULT_CI_DELEGATION_CHAIN_ID,
|
|
367
|
-
bootstrap_caveat:
|
|
411
|
+
bootstrap_caveat: workflowPath
|
|
412
|
+
? "Commit the generated workflow and manifest before expecting GitHub Actions deploys."
|
|
413
|
+
: "No workflow was written (--no-workflow): the repository's existing workflow deploys once it runs run402 deploy with permissions: id-token: write; re-run it.",
|
|
368
414
|
consent_summary: [
|
|
369
415
|
"This binding lets matching GitHub Actions workflows deploy site, function, and database changes to the project.",
|
|
370
416
|
outputRouteScopes.length > 0
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -1501,7 +1501,7 @@ const CI_DEPLOY_ERROR_GUIDANCE = {
|
|
|
1501
1501
|
],
|
|
1502
1502
|
},
|
|
1503
1503
|
access_denied: {
|
|
1504
|
-
hint: "
|
|
1504
|
+
hint: "No active binding on this project matches this workflow's OIDC subject (details.subject); next_actions names the command that links it.",
|
|
1505
1505
|
next_actions: [
|
|
1506
1506
|
editRequestAction("run402 ci list --project <prj_...>", "Inspect active CI bindings for this project."),
|
|
1507
1507
|
editRequestAction("run402 ci link github", "Link this repository, branch, and environment to Run402."),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.131.0",
|
|
4
4
|
"description": "CLI for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/vault-surface.json
CHANGED