brnch-cli 0.18.2 → 0.18.3

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 (3) hide show
  1. package/README.md +1 -1
  2. package/brnch.mjs +869 -88
  3. package/package.json +1 -1
package/brnch.mjs CHANGED
@@ -6044,9 +6044,9 @@ var require_lexer = __commonJS({
6044
6044
  }
6045
6045
  }
6046
6046
  *parseQuotedScalar() {
6047
- const quote2 = this.charAt(0);
6048
- let end2 = this.buffer.indexOf(quote2, this.pos + 1);
6049
- if (quote2 === "'") {
6047
+ const quote3 = this.charAt(0);
6048
+ let end2 = this.buffer.indexOf(quote3, this.pos + 1);
6049
+ if (quote3 === "'") {
6050
6050
  while (end2 !== -1 && this.buffer[end2 + 1] === "'")
6051
6051
  end2 = this.buffer.indexOf("'", end2 + 2);
6052
6052
  } else {
@@ -9960,7 +9960,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
9960
9960
  const negativeNumberArg = (arg) => {
9961
9961
  if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg)) return false;
9962
9962
  return !this._getCommandAndAncestors().some(
9963
- (cmd) => cmd.options.map((opt) => opt.short).some((short2) => /^-\d$/.test(short2))
9963
+ (cmd) => cmd.options.map((opt) => opt.short).some((short3) => /^-\d$/.test(short3))
9964
9964
  );
9965
9965
  };
9966
9966
  let activeVariadicOption = null;
@@ -21407,8 +21407,8 @@ var error43 = () => {
21407
21407
  case "not_multiple_of":
21408
21408
  return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
21409
21409
  case "unrecognized_keys": {
21410
- const plural3 = issue2.keys.length > 1 ? "s" : "";
21411
- return `Chave${plural3} inv\xE1lida${plural3}: ${joinValues(issue2.keys, ", ")}`;
21410
+ const plural4 = issue2.keys.length > 1 ? "s" : "";
21411
+ return `Chave${plural4} inv\xE1lida${plural4}: ${joinValues(issue2.keys, ", ")}`;
21412
21412
  }
21413
21413
  case "invalid_key":
21414
21414
  return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
@@ -21552,8 +21552,8 @@ var error44 = () => {
21552
21552
  case "not_multiple_of":
21553
21553
  return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
21554
21554
  case "unrecognized_keys": {
21555
- const plural3 = issue2.keys.length > 1 ? "s" : "";
21556
- return `Chave${plural3} inv\xE1lida${plural3}: ${joinValues(issue2.keys, ", ")}`;
21555
+ const plural4 = issue2.keys.length > 1 ? "s" : "";
21556
+ return `Chave${plural4} inv\xE1lida${plural4}: ${joinValues(issue2.keys, ", ")}`;
21557
21557
  }
21558
21558
  case "invalid_key":
21559
21559
  return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
@@ -30572,7 +30572,8 @@ var PLATFORM_TOKEN_PREFIXES = {
30572
30572
  finv: "organization invitation token",
30573
30573
  fsfa: "pending second-factor sign-in",
30574
30574
  fbkc: "Buildkite checkout token",
30575
- fsvw: "staff read-only view"
30575
+ fsvw: "staff read-only view",
30576
+ fcrt: "container registry token"
30576
30577
  };
30577
30578
  var PLATFORM_TOKEN_RE = new RegExp(`^(${Object.keys(PLATFORM_TOKEN_PREFIXES).join("|")})_[0-9a-f]{64}$`);
30578
30579
 
@@ -31545,15 +31546,15 @@ function redactText(input3, opts = {}) {
31545
31546
  hit("auth-header");
31546
31547
  return `${prefix}${REDACTED}`;
31547
31548
  });
31548
- text2 = text2.replace(ASSIGNMENT, (w, prefix, quote2, value) => {
31549
+ text2 = text2.replace(ASSIGNMENT, (w, prefix, quote3, value) => {
31549
31550
  if (isReference(value) || value.startsWith(REDACTED)) return w;
31550
31551
  hit("assignment");
31551
- return `${prefix}${quote2}${REDACTED}${quote2}`;
31552
+ return `${prefix}${quote3}${REDACTED}${quote3}`;
31552
31553
  });
31553
- text2 = text2.replace(FLAG, (w, prefix, quote2, value) => {
31554
+ text2 = text2.replace(FLAG, (w, prefix, quote3, value) => {
31554
31555
  if (isReference(value) || value.startsWith(REDACTED)) return w;
31555
31556
  hit("flag");
31556
- return `${prefix}${quote2}${REDACTED}${quote2}`;
31557
+ return `${prefix}${quote3}${REDACTED}${quote3}`;
31557
31558
  });
31558
31559
  if (opts.entropy !== false) {
31559
31560
  text2 = text2.replace(TOKENISH, (w) => {
@@ -31632,6 +31633,7 @@ var looksLikeHostedLabel = (label2) => /^brnch-hosted(?:-|$)/.test(label2);
31632
31633
  // packages/domain/src/deploy.ts
31633
31634
  var DEPLOY_LIMITS = {
31634
31635
  maxStepsPerTarget: 12,
31636
+ maxWatchPaths: 50,
31635
31637
  maxTargetsPerRepository: 10,
31636
31638
  maxCommandLength: 2e3,
31637
31639
  maxSummaryLength: 500,
@@ -31645,6 +31647,21 @@ var DEPLOY_LIMITS = {
31645
31647
  var DEPLOY_RUNNER_LABELS = HOSTED_SIZES.map((s) => HOSTED_SIZE_SPECS[s].label);
31646
31648
  var DEFAULT_DEPLOY_RUNNER = HOSTED_SIZE_SPECS.basic.label;
31647
31649
 
31650
+ // packages/domain/src/db-migrations.ts
31651
+ var DB_MIGRATION_LIMITS = {
31652
+ sourcesPerRepo: 20,
31653
+ /** Migration files read from one directory. */
31654
+ files: 2e3,
31655
+ fileBytes: 1024 * 1024,
31656
+ dirBytes: 16 * 1024 * 1024,
31657
+ /** Per statement; queue work is bounded by the platform's 15-minute limit. */
31658
+ statementTimeoutMaxS: 600,
31659
+ lockTimeoutMaxS: 60,
31660
+ sessionSettings: 20,
31661
+ /** How long a worker holds a run before another may take it over. */
31662
+ leaseMs: 15 * 6e4
31663
+ };
31664
+
31648
31665
  // packages/domain/src/isolated-preview.ts
31649
31666
  function parseJsonc(text2) {
31650
31667
  let out = "";
@@ -31683,6 +31700,8 @@ var DELEGATION_MATRIX = Object.freeze({
31683
31700
  "cloudflare/resources": { userToOrg: false, userToRepo: false, orgToRepo: true },
31684
31701
  "cloudflare/d1_migrations": { userToOrg: false, userToRepo: false, orgToRepo: true },
31685
31702
  "cloudflare/secrets_store": { userToOrg: false, userToRepo: false, orgToRepo: true },
31703
+ // Database migrations (ADR-0084) apply one repository's migration directory to the PlanetScale database it is bound to.
31704
+ "planetscale/db_migrations": { userToOrg: false, userToRepo: false, orgToRepo: true },
31686
31705
  // A secret source is read by a repository's workflow runs.
31687
31706
  "infisical/secrets_source": { userToOrg: false, userToRepo: false, orgToRepo: true },
31688
31707
  // AI code review keys (ADR-0051): an organization's key reviews the changes of its repositories.
@@ -31734,7 +31753,7 @@ var PLANS = Object.freeze({
31734
31753
  monthly_cents: 800,
31735
31754
  annual_cents: 8e3,
31736
31755
  per_seat: false,
31737
- features: ["private_review_rules", "ai_code_review_services", "hosted_delegation", "cloudflare_previews", "cloudflare_d1_migrations", "assistant"],
31756
+ features: ["private_review_rules", "ai_code_review_services", "hosted_delegation", "cloudflare_previews", "cloudflare_d1_migrations", "database_migrations", "assistant"],
31738
31757
  minutes_base: 5e3,
31739
31758
  minutes_per_seat: 0,
31740
31759
  concurrent_hosted_jobs: 4,
@@ -31753,7 +31772,7 @@ var PLANS = Object.freeze({
31753
31772
  monthly_cents: 1e3,
31754
31773
  annual_cents: 9600,
31755
31774
  per_seat: true,
31756
- features: ["private_review_rules", "private_members_unlimited", "ai_code_review_services", "hosted_delegation", "cloudflare_previews", "cloudflare_d1_migrations", "premium_ai_models", "assistant", "assistant_proactive"],
31775
+ features: ["private_review_rules", "private_members_unlimited", "ai_code_review_services", "hosted_delegation", "cloudflare_previews", "cloudflare_d1_migrations", "database_migrations", "premium_ai_models", "assistant", "assistant_proactive"],
31757
31776
  minutes_base: 3e3,
31758
31777
  minutes_per_seat: 1e3,
31759
31778
  concurrent_hosted_jobs: 10,
@@ -31772,7 +31791,7 @@ var PLANS = Object.freeze({
31772
31791
  monthly_cents: null,
31773
31792
  annual_cents: null,
31774
31793
  per_seat: true,
31775
- features: ["private_review_rules", "private_members_unlimited", "ai_code_review_services", "hosted_delegation", "cloudflare_previews", "cloudflare_d1_migrations", "premium_ai_models", "assistant", "assistant_proactive"],
31794
+ features: ["private_review_rules", "private_members_unlimited", "ai_code_review_services", "hosted_delegation", "cloudflare_previews", "cloudflare_d1_migrations", "database_migrations", "premium_ai_models", "assistant", "assistant_proactive"],
31776
31795
  minutes_base: 5e4,
31777
31796
  minutes_per_seat: 0,
31778
31797
  concurrent_hosted_jobs: 50,
@@ -32864,6 +32883,22 @@ var ActionsSettingsRequest = external_exports.object({
32864
32883
  allow_untrusted_jobs_on_self_hosted_runners: external_exports.boolean().optional()
32865
32884
  });
32866
32885
  var RunnerLabel = external_exports.string().min(1).max(50).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "letters, digits, '.', '_' and '-'");
32886
+ var ResourceBytes = external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
32887
+ var RunnerResourceSnapshot = external_exports.object({
32888
+ cpu_percent: external_exports.number().min(0).max(100).nullable(),
32889
+ cpu_count: external_exports.number().int().min(1).max(65536),
32890
+ cpu_interval_ms: external_exports.number().int().min(1).max(6e5).nullable(),
32891
+ memory_total_bytes: ResourceBytes.positive(),
32892
+ memory_available_bytes: ResourceBytes,
32893
+ disk_total_bytes: ResourceBytes.positive().nullable(),
32894
+ disk_available_bytes: ResourceBytes.nullable(),
32895
+ uptime_seconds: external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
32896
+ job_slots: external_exports.number().int().min(1).max(32)
32897
+ }).strict().superRefine((s, ctx) => {
32898
+ if (s.memory_available_bytes > s.memory_total_bytes) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["memory_available_bytes"], message: "available memory exceeds total memory" });
32899
+ if (s.disk_total_bytes === null !== (s.disk_available_bytes === null) || s.disk_available_bytes !== null && s.disk_available_bytes > s.disk_total_bytes) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["disk_available_bytes"], message: "disk readings must be a valid pair" });
32900
+ if (s.cpu_percent === null !== (s.cpu_interval_ms === null)) ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["cpu_interval_ms"], message: "CPU usage requires a sample interval" });
32901
+ });
32867
32902
  var TestRunnerRequest = external_exports.object({
32868
32903
  repository_id: external_exports.string().max(64).nullable().default(null)
32869
32904
  });
@@ -32888,8 +32923,9 @@ var PollJobsRequest = external_exports.object({
32888
32923
  wait_seconds: external_exports.number().int().min(0).max(25).default(20)
32889
32924
  });
32890
32925
  var HeartbeatRequest = external_exports.object({
32891
- running_jobs: external_exports.array(external_exports.string().max(64)).max(32).default([])
32892
- });
32926
+ running_jobs: external_exports.array(external_exports.string().max(64)).max(32).default([]),
32927
+ resources: RunnerResourceSnapshot.optional()
32928
+ }).strict();
32893
32929
  var StepUpdateRequest = external_exports.object({
32894
32930
  name: external_exports.string().max(500),
32895
32931
  status: external_exports.enum(["in_progress", "completed"]),
@@ -33618,7 +33654,7 @@ var CODING_AGENT_SESSION_SCOPES = [
33618
33654
  var EnsureCodingAgentRequest = external_exports.object({ tool: external_exports.enum(AGENT_RUNTIMES) }).strict();
33619
33655
 
33620
33656
  // packages/protocol/src/connections.ts
33621
- var CONNECTION_PURPOSES = ["impact", "secrets_store", "import", "secrets_source", "deploy", "resources", "d1_migrations", "code_review"];
33657
+ var CONNECTION_PURPOSES = ["impact", "secrets_store", "import", "secrets_source", "deploy", "resources", "d1_migrations", "db_migrations", "code_review"];
33622
33658
  var Name3 = external_exports.string().trim().min(1).max(100);
33623
33659
  var Secret2 = external_exports.string().min(1).max(4e3);
33624
33660
  var Id2 = external_exports.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/, "letters, digits, '.', '_', ':' and '-' only");
@@ -33657,9 +33693,10 @@ var CreateConnectionRequest = external_exports.discriminatedUnion("auth", [
33657
33693
  auth: external_exports.literal("api_token"),
33658
33694
  // Cloudflare API tokens (impact, secrets_store, deploy), GitHub fine-grained personal access tokens (import) and
33659
33695
  // AI code review keys (code_review, ADR-0051): CodeRabbit, Greptile, an OpenAI key for Codex, an Anthropic key for
33660
- // Claude Code. The provider/purpose pair is checked server-side.
33661
- provider: external_exports.enum(["cloudflare", "github", "coderabbit", "greptile", "codex", "claude"]),
33662
- purpose: external_exports.enum(["impact", "secrets_store", "import", "deploy", "resources", "d1_migrations", "code_review"]),
33696
+ // Claude Code, a PlanetScale service token (`<id>:<token>`, db_migrations). The provider/purpose pair is checked
33697
+ // server-side.
33698
+ provider: external_exports.enum(["cloudflare", "planetscale", "github", "coderabbit", "greptile", "codex", "claude"]),
33699
+ purpose: external_exports.enum(["impact", "secrets_store", "import", "deploy", "resources", "d1_migrations", "db_migrations", "code_review"]),
33663
33700
  name: Name3,
33664
33701
  credentials: external_exports.object({ api_token: Secret2 })
33665
33702
  }),
@@ -33762,6 +33799,11 @@ var targetFields = {
33762
33799
  preview_check_name: external_exports.string().trim().min(1).max(100).nullable(),
33763
33800
  /** The hosted runner size the deploy jobs run on, by its `runs-on` label: `brnch-hosted` … `brnch-hosted-4` (ADR-0066). */
33764
33801
  runs_on: external_exports.string().trim().min(1).max(40),
33802
+ /**
33803
+ * Paths whose changes deploy the target (CODEOWNERS patterns such as `apps/web/` or `packages/**`, `!` excludes; the
33804
+ * last matching pattern decides). Empty: every change deploys it.
33805
+ */
33806
+ watch_paths: external_exports.array(external_exports.string().trim().min(1).max(500)).max(50),
33765
33807
  steps: external_exports.array(DeployStepSpecBody).max(12)
33766
33808
  };
33767
33809
  var CreateDeployTargetRequest = external_exports.object({
@@ -33779,6 +33821,7 @@ var CreateDeployTargetRequest = external_exports.object({
33779
33821
  preview_agent_changes: targetFields.preview_agent_changes.default(false),
33780
33822
  preview_check_name: targetFields.preview_check_name.default(null),
33781
33823
  runs_on: targetFields.runs_on.default("brnch-hosted"),
33824
+ watch_paths: targetFields.watch_paths.default([]),
33782
33825
  steps: targetFields.steps.default([])
33783
33826
  });
33784
33827
  var UpdateDeployTargetRequest = external_exports.object(targetFields).partial().extend({
@@ -33844,6 +33887,77 @@ var UpsertWorkerEnvRequest = external_exports.object({
33844
33887
  if (v.kind === "var" && v.source !== "value") c8.addIssue({ code: "custom", message: "plain vars take a value", path: ["source"] });
33845
33888
  });
33846
33889
 
33890
+ // packages/protocol/src/db-migrations.ts
33891
+ var DB_MIGRATION_FORMATS = ["atlas", "plain", "golang_migrate", "drizzle", "prisma"];
33892
+ var DB_ENGINE_KINDS = ["postgres", "vitess"];
33893
+ var DB_EXEC_ORDERS = ["linear", "linear_skip", "non_linear"];
33894
+ var DB_CREDENTIAL_MODES = ["managed_role", "dsn", "branch_password"];
33895
+ var Ident2 = external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_$]{0,62}$/, "letters, digits and _ only (at most 63)");
33896
+ var PsName = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/, "a PlanetScale name: letters, digits, '.', '_' and '-'");
33897
+ var SourceFields = {
33898
+ name: external_exports.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9_-]*$/, "lowercase letters, digits, - and _"),
33899
+ /** Directory relative to the repository root ("" = the root). */
33900
+ path: external_exports.string().max(300),
33901
+ format: external_exports.enum(DB_MIGRATION_FORMATS),
33902
+ engine: external_exports.enum(DB_ENGINE_KINDS),
33903
+ /** Branch whose merges create runs; the repository's default branch when omitted. */
33904
+ branch: external_exports.string().min(1).max(200).optional(),
33905
+ /** PlanetScale connection (purpose `db_migrations`) of the organization or the repository. */
33906
+ connection_id: external_exports.string().min(1).max(64),
33907
+ ps_organization: PsName,
33908
+ ps_database: PsName,
33909
+ /** The production branch migrations are applied to. */
33910
+ ps_branch: PsName.default("main"),
33911
+ /** Where the format's revisions table lives (Postgres schema; Atlas default `atlas_schema_revisions`). */
33912
+ revisions_schema: Ident2.nullable().default(null),
33913
+ revisions_table: Ident2.nullable().default(null),
33914
+ /** With no revision history yet, record this version as a baseline instead of applying files up to it. */
33915
+ baseline_version: external_exports.string().min(1).max(100).nullable().default(null),
33916
+ exec_order: external_exports.enum(DB_EXEC_ORDERS).default("linear"),
33917
+ /** Postgres session settings for migration sessions (`lock_timeout`, custom `prefix.name`, …). */
33918
+ session_settings: external_exports.record(external_exports.string().max(126), external_exports.string().max(500)).default({}),
33919
+ statement_timeout_s: external_exports.number().int().min(1).max(600).default(300),
33920
+ lock_timeout_s: external_exports.number().int().min(1).max(60).default(10),
33921
+ credential_mode: external_exports.enum(DB_CREDENTIAL_MODES).optional(),
33922
+ /** Postgres: the role new objects belong to (`SET ROLE`); `postgres` by default for managed roles, none for a DSN. */
33923
+ run_as_role: external_exports.string().regex(/^[a-z_][a-z0-9_]{0,62}$/).nullable().optional(),
33924
+ /** `approval`: merges create runs that wait for approval. `manual_only`: runs start only by hand. */
33925
+ on_merge: external_exports.enum(["approval", "manual_only"]).default("approval"),
33926
+ /** Validate the directory on changes (evidence `migrations/<name>`). */
33927
+ change_checks: external_exports.boolean().default(true)
33928
+ };
33929
+ var CreateDbMigrationSourceRequest = external_exports.object({
33930
+ ...SourceFields,
33931
+ /** Postgres `credential_mode: dsn` only: `postgresql://user:password@host:5432/db?sslmode=verify-full`. Write-only. */
33932
+ dsn: external_exports.string().min(10).max(2e3).optional()
33933
+ });
33934
+ var UpdateDbMigrationSourceRequest = external_exports.object({
33935
+ path: SourceFields.path,
33936
+ branch: SourceFields.branch,
33937
+ connection_id: SourceFields.connection_id,
33938
+ ps_organization: SourceFields.ps_organization,
33939
+ ps_database: SourceFields.ps_database,
33940
+ ps_branch: PsName,
33941
+ revisions_schema: Ident2.nullable(),
33942
+ revisions_table: Ident2.nullable(),
33943
+ baseline_version: external_exports.string().min(1).max(100).nullable(),
33944
+ exec_order: external_exports.enum(DB_EXEC_ORDERS),
33945
+ session_settings: external_exports.record(external_exports.string().max(126), external_exports.string().max(500)),
33946
+ statement_timeout_s: SourceFields.statement_timeout_s.unwrap(),
33947
+ lock_timeout_s: SourceFields.lock_timeout_s.unwrap(),
33948
+ credential_mode: external_exports.enum(DB_CREDENTIAL_MODES),
33949
+ run_as_role: external_exports.string().regex(/^[a-z_][a-z0-9_]{0,62}$/).nullable(),
33950
+ on_merge: external_exports.enum(["approval", "manual_only"]),
33951
+ change_checks: external_exports.boolean(),
33952
+ state: external_exports.enum(["active", "disabled"]),
33953
+ dsn: external_exports.string().min(10).max(2e3).nullable()
33954
+ }).partial().extend({ version: external_exports.number().int().min(1) });
33955
+ var StartDbMigrationRunRequest = external_exports.object({
33956
+ /** Branch, tag or commit to apply; the source's branch when omitted. */
33957
+ ref: external_exports.string().min(1).max(200).optional()
33958
+ });
33959
+ var DecideDbMigrationRunRequest = external_exports.object({ reason: external_exports.string().max(500).optional() });
33960
+
33847
33961
  // packages/protocol/src/buildkite.ts
33848
33962
  var BUILDKITE_TRUST_MODES = ["contributor_controlled", "pipeline_settings", "target_ref_upload"];
33849
33963
  var Slug3 = external_exports.string().trim().min(1).max(100).regex(/^[a-z0-9][a-z0-9-_]*$/i, "a Buildkite slug (letters, digits, '-' and '_')");
@@ -36403,6 +36517,38 @@ function tasksGroup(t) {
36403
36517
  };
36404
36518
  }
36405
36519
 
36520
+ // packages/sdk/src/groups/db-migrations.ts
36521
+ function dbMigrationsGroup(t) {
36522
+ const decide = (decision) => (id2, reason, o) => t.json({ method: "POST", path: path2`/v1/db-migration-runs/${id2}/${decision}`, json: validate2(DecideDbMigrationRunRequest, reason ? { reason } : {}, "DecideDbMigrationRunRequest"), options: o });
36523
+ return {
36524
+ sources: {
36525
+ list: (repo, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/db-migrations/sources`, options: o }),
36526
+ create: (repo, body, o) => t.json({ method: "POST", path: path2`/v1/repos/${repo}/db-migrations/sources`, json: validate2(CreateDbMigrationSourceRequest, body, "CreateDbMigrationSourceRequest"), options: o }),
36527
+ get: (id2, o) => t.json({ method: "GET", path: path2`/v1/db-migration-sources/${id2}`, options: o }),
36528
+ update: (id2, body, o) => t.json({ method: "PATCH", path: path2`/v1/db-migration-sources/${id2}`, json: validate2(UpdateDbMigrationSourceRequest, body, "UpdateDbMigrationSourceRequest"), options: o }),
36529
+ delete: (id2, o) => t.void({ method: "DELETE", path: path2`/v1/db-migration-sources/${id2}`, options: o })
36530
+ },
36531
+ /** Migration directories found in the repository tree; `configured` marks those a source already covers. */
36532
+ suggestions: (repo, ref, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/db-migrations/suggestions`, query: ref ? { ref } : {}, options: o }),
36533
+ /** What applying the source at `ref` would do. `live` reads the database now instead of the last snapshot. */
36534
+ plan: (sourceId2, q = {}, o) => t.json({ method: "GET", path: path2`/v1/db-migration-sources/${sourceId2}/plan`, query: { ref: q.ref, live: q.live ? "true" : void 0 }, options: o }),
36535
+ /** Reads the applied versions from the database again and returns the plan at the source's branch. */
36536
+ refresh: (sourceId2, o) => t.json({ method: "POST", path: path2`/v1/db-migration-sources/${sourceId2}/refresh`, options: o }),
36537
+ /** A manual run: the caller's request is the approval. `run` is null when nothing is pending. */
36538
+ startRun: (sourceId2, body = {}, o) => t.json({ method: "POST", path: path2`/v1/db-migration-sources/${sourceId2}/runs`, json: validate2(StartDbMigrationRunRequest, body, "StartDbMigrationRunRequest"), options: o }),
36539
+ runs: {
36540
+ list: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/db-migrations/runs`, query: { ...q }, options: o }),
36541
+ get: (id2, o) => t.json({ method: "GET", path: path2`/v1/db-migration-runs/${id2}`, options: o })
36542
+ },
36543
+ approve: decide("approve"),
36544
+ reject: decide("reject"),
36545
+ retry: decide("retry"),
36546
+ cancel: decide("cancel"),
36547
+ /** The plan of every source a change touches, with the files the change adds or edits marked. */
36548
+ changePlans: (changeId, o) => t.json({ method: "GET", path: path2`/v1/changes/${changeId}/db-migrations`, options: o })
36549
+ };
36550
+ }
36551
+
36406
36552
  // packages/sdk/src/groups/deploy.ts
36407
36553
  function deployGroup(t) {
36408
36554
  return {
@@ -36475,6 +36621,8 @@ function packagesGroup(t) {
36475
36621
  list: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/packages`, query: { ...q }, options: o }),
36476
36622
  get: (image, o) => t.json({ method: "GET", path: path2`/v1/packages/${image}`, options: o }),
36477
36623
  versions: (image, q = {}, o) => t.json({ method: "GET", path: path2`/v1/packages/${image}/versions`, query: { ...q }, options: o }),
36624
+ /** Deletes an image with no versions left (`CONFLICT` otherwise). */
36625
+ delete: (image, o) => t.void({ method: "DELETE", path: path2`/v1/packages/${image}`, options: o }),
36478
36626
  deleteVersion: (image, digest, o) => t.void({ method: "DELETE", path: path2`/v1/packages/${image}/versions/${digest}`, options: o }),
36479
36627
  deleteTag: (image, tag, o) => t.void({ method: "DELETE", path: path2`/v1/packages/${image}/tags/${tag}`, options: o }),
36480
36628
  usage: (org, o) => t.json({ method: "GET", path: path2`/v1/orgs/${org}/registry`, options: o }),
@@ -36569,6 +36717,7 @@ function createClient(options) {
36569
36717
  secretSyncs: secretSyncsGroup(t),
36570
36718
  grants: grantsGroup(t),
36571
36719
  deploy: deployGroup(t),
36720
+ dbMigrations: dbMigrationsGroup(t),
36572
36721
  codeReview: codeReviewGroup(t),
36573
36722
  releases: releasesGroup(t),
36574
36723
  packages: packagesGroup(t),
@@ -36901,7 +37050,7 @@ function invalidAgentToolFlag(flag) {
36901
37050
  }
36902
37051
 
36903
37052
  // packages/cli/package.json
36904
- var version2 = "0.18.2";
37053
+ var version2 = "0.18.3";
36905
37054
 
36906
37055
  // packages/cli/src/brand.ts
36907
37056
  var BRAND = {
@@ -38972,6 +39121,67 @@ ${final.join("\n")}
38972
39121
  deps.stderr.write("\n");
38973
39122
  }
38974
39123
 
39124
+ // packages/cli/src/program-status.ts
39125
+ var MSG_MAX_BYTES = 512;
39126
+ function encodeMsg(text2) {
39127
+ let line2 = text2.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").trim();
39128
+ const enc3 = new TextEncoder();
39129
+ while (enc3.encode(line2).length > MSG_MAX_BYTES) line2 = [...line2].slice(0, -1).join("");
39130
+ return Buffer.from(line2, "utf8").toString("base64");
39131
+ }
39132
+ function programStatus(r) {
39133
+ const pairs = [`state=${r.state}`, `app=${BRAND.cli}`];
39134
+ if (r.state === "blocked") pairs.push(`kind=${r.kind}`);
39135
+ if (r.state === "working" && r.progress !== void 0 && Number.isFinite(r.progress)) pairs.push(`progress=${Math.min(100, Math.max(0, Math.round(r.progress)))}`);
39136
+ if (r.msg) pairs.push(`msg=${encodeMsg(r.msg)}`);
39137
+ return `\x1B]7501;${pairs.join(":")}\x1B\\`;
39138
+ }
39139
+ function programStatusEnabled(deps, agent) {
39140
+ const off = deps.env[envName("PROGRAM_STATUS")]?.trim().toLowerCase();
39141
+ if (off === "0" || off === "false" || off === "off" || off === "no") return false;
39142
+ return deps.stderrIsTTY && !agent && !deps.env.CI && deps.env.TERM !== "dumb";
39143
+ }
39144
+ var silentStatus = {
39145
+ working: () => {
39146
+ },
39147
+ blocked: () => {
39148
+ },
39149
+ ask: () => () => {
39150
+ },
39151
+ finish: () => {
39152
+ }
39153
+ };
39154
+ function statusReporter(out) {
39155
+ let current = null;
39156
+ let waited = false;
39157
+ let last = "";
39158
+ const send = (r) => {
39159
+ const s = programStatus(r);
39160
+ if (s === last) return;
39161
+ last = s;
39162
+ out.write(s);
39163
+ };
39164
+ const report3 = (r) => {
39165
+ current = r;
39166
+ waited = true;
39167
+ send(r);
39168
+ };
39169
+ return {
39170
+ working: (msg, progress) => report3({ state: "working", msg, ...progress !== void 0 ? { progress } : {} }),
39171
+ blocked: (kind, msg) => report3({ state: "blocked", kind, msg }),
39172
+ ask() {
39173
+ send({ state: "blocked", kind: "question", msg: "Waiting for your answer" });
39174
+ return () => send(current ?? { state: "working" });
39175
+ },
39176
+ finish(ok, msg) {
39177
+ if (!waited) return;
39178
+ send({ state: ok ? "done" : "error", msg });
39179
+ waited = false;
39180
+ current = null;
39181
+ }
39182
+ };
39183
+ }
39184
+
38975
39185
  // packages/cli/src/context.ts
38976
39186
  async function findProjectFile(deps) {
38977
39187
  let dir = deps.cwd;
@@ -39316,6 +39526,8 @@ async function createContext(deps, globals, state2) {
39316
39526
  busy: (label2) => {
39317
39527
  if (motion) state2.spinner?.start(label2);
39318
39528
  },
39529
+ /** Tell the terminal what the command waits on (program-status.ts): the server (`working`) or the person (`blocked`). */
39530
+ status: state2.status ?? silentStatus,
39319
39531
  /** Clear the spinner before handing the terminal to something that does not write through `deps` (rt.invoke). */
39320
39532
  idle: () => state2.spinner?.stop(),
39321
39533
  result,
@@ -42198,6 +42410,7 @@ function sameOrigin(ctx, start) {
42198
42410
  }
42199
42411
  async function awaitStepUp(ctx, start) {
42200
42412
  const deadline = Date.parse(start.expires_at);
42413
+ ctx.status.blocked("auth", `Confirm it's you in the browser (code ${start.user_code})`);
42201
42414
  for (; ; ) {
42202
42415
  await ctx.deps.sleep(start.interval * 1e3);
42203
42416
  const s = await ctx.client("user").auth.stepUp.status(start.id);
@@ -42395,6 +42608,7 @@ var accountChoice = (a) => ({
42395
42608
  });
42396
42609
  async function finishLogin(ctx, start, agentRuntime, hooks) {
42397
42610
  const oauth = ctx.client("user", { anonymous: true }).oauth;
42611
+ ctx.status.blocked("auth", `Approve the sign-in in your browser (code ${start.user_code})`);
42398
42612
  let tok;
42399
42613
  try {
42400
42614
  tok = await pollDeviceToken(oauth, start, CLI_OAUTH_CLIENT_ID, { sleep: ctx.deps.sleep });
@@ -43467,6 +43681,7 @@ var Wizard = class {
43467
43681
  /** The spinner says what the step is checking until it prints, asks, or hands over to a composed command. */
43468
43682
  busy(step2) {
43469
43683
  this.ctx.busy(STEP_BUSY[step2]);
43684
+ this.ctx.status.working(STEP_BUSY[step2]);
43470
43685
  }
43471
43686
  /** Global flags every composed command must keep (same server, credential and account). */
43472
43687
  globalArgs() {
@@ -44367,6 +44582,7 @@ var Quickstart = class {
44367
44582
  /** The board row says what is happening now (no-op without a board). */
44368
44583
  running(row, text2) {
44369
44584
  this.board?.run(row, text2);
44585
+ this.ctx.status.working(text2);
44370
44586
  }
44371
44587
  /** A step's outcome: kept for the result, and shown on the board (`show`) or as a line. */
44372
44588
  record(step2, status, detail, show2 = detail) {
@@ -44681,6 +44897,7 @@ async function stepDeploy(q, dir, ns, name, o, home2) {
44681
44897
  q.running("deploy", "waiting for Cloudflare to be connected");
44682
44898
  q.board.hint(`${todo} ${q.errPaint.dim("(Ctrl-C stops; the repository is ready either way)")}`);
44683
44899
  } else ctx.info(` ${todo}. Waiting for it (Ctrl-C stops; the repository is ready either way)\u2026`);
44900
+ ctx.status.blocked("auth", "Connect Cloudflare in the browser");
44684
44901
  const deadline2 = ctx.deps.sys.now() + o.connectTimeout * 1e3;
44685
44902
  let ready = false;
44686
44903
  while (!ready && ctx.deps.sys.now() < deadline2) {
@@ -46285,6 +46502,7 @@ async function waitDone(ctx, change, interval, timeout) {
46285
46502
  const now = f.reviews.map((r) => `${CODE_REVIEW_PROVIDER_NAMES[r.provider]}: ${r.status}`).join(", ");
46286
46503
  if (now && now !== said) ctx.info(now);
46287
46504
  said = now;
46505
+ ctx.status.working(now ? `AI review: ${now}` : "Waiting for AI review");
46288
46506
  if (!f.pending || Date.now() > deadline) return f;
46289
46507
  await ctx.deps.sleep(Math.max(1, interval) * 1e3);
46290
46508
  }
@@ -46450,6 +46668,7 @@ async function followLive(ctx, c8, first2, deadline, interval) {
46450
46668
  const stop = ctx.deps.ticker(250, draw2);
46451
46669
  try {
46452
46670
  while (!TERMINAL3.has(cur.state) && Date.now() < deadline) {
46671
+ ctx.status.working(`Merge ${cur.state}`);
46453
46672
  await ctx.deps.sleep(Math.max(1, interval) * 1e3);
46454
46673
  cur = await c8.promotions.get(cur.id);
46455
46674
  reached = stepOf(cur.state, reached);
@@ -46474,6 +46693,7 @@ function registerMerge(program2, rt) {
46474
46693
  let last = p.state;
46475
46694
  ctx.info(`promotion ${p.id}: ${p.state}`);
46476
46695
  while (!TERMINAL3.has(p.state) && Date.now() < deadline) {
46696
+ ctx.status.working(`Merge ${p.state}`);
46477
46697
  await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
46478
46698
  p = await c8.promotions.get(p.id);
46479
46699
  if (p.state !== last) ctx.info(`promotion ${p.id}: ${last = p.state}`);
@@ -46751,6 +46971,7 @@ function registerSecretScanning(program2, rt) {
46751
46971
  const c8 = ctx.client("user");
46752
46972
  let job2 = await c8.secretScanning.scans.start(repo);
46753
46973
  while (o.wait && (job2.state === "queued" || job2.state === "running")) {
46974
+ ctx.status.working(`History scan ${job2.state}`);
46754
46975
  await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
46755
46976
  job2 = await c8.secretScanning.scans.get(repo, job2.id);
46756
46977
  }
@@ -47040,9 +47261,9 @@ async function findItems(client, specs) {
47040
47261
  }
47041
47262
  async function itemIds(client, specs) {
47042
47263
  const full = (s) => /^notif_[0-9a-z]{26}$/.test(s);
47043
- const short2 = specs.filter((s) => !full(s));
47044
- const items = short2.length ? await findItems(client, short2) : [];
47045
- return specs.map((s) => full(s) ? s : items[short2.indexOf(s)].id);
47264
+ const short3 = specs.filter((s) => !full(s));
47265
+ const items = short3.length ? await findItems(client, short3) : [];
47266
+ return specs.map((s) => full(s) ? s : items[short3.indexOf(s)].id);
47046
47267
  }
47047
47268
  async function repoNames(client, items) {
47048
47269
  const ids = [...new Set(items.map((i) => i.repository_id))];
@@ -47787,6 +48008,187 @@ function registerIntegrations(program2, rt) {
47787
48008
  integrations.command("inherit <provider> <purpose>").description("Drop --repo's choice and inherit the nearest shared connection again").action(act(rt, async (ctx, _o, [provider, purpose]) => ctx.result(await ctx.client("user").grants.inherit(await ctx.repoId(void 0, "user"), provider, purpose), renderRepo)));
47788
48009
  }
47789
48010
 
48011
+ // packages/cli/src/commands/db.ts
48012
+ async function sourceId(ctx, ref, repo) {
48013
+ if (ref.startsWith("dms_")) return ref;
48014
+ const { items } = await ctx.client("user").dbMigrations.sources.list(repo?.id ?? await ctx.repoId(void 0, "user"));
48015
+ const s = items.find((x) => x.name === ref);
48016
+ if (!s) throw new CliError(`no migration source named "${ref}" (have: ${items.map((x) => x.name).join(", ") || "none"})`, EXIT.NOT_FOUND, "NOT_FOUND");
48017
+ return s.id;
48018
+ }
48019
+ var plural3 = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
48020
+ var short = (oid) => oid.slice(0, 12);
48021
+ var database = (s) => `${s.ps_organization}/${s.ps_database}@${s.ps_branch}`;
48022
+ function issueLines(issues) {
48023
+ if (!issues.length) return [];
48024
+ return ["issues:", ...issues.map((i) => ` ${i.level.padEnd(7)} ${i.code} ${i.file ? `${i.file}: ` : ""}${i.message}`)];
48025
+ }
48026
+ var renderSources = (v) => table(
48027
+ v.items.map((s) => ({ ...s, database: database(s), applied: s.snapshot ? `${s.snapshot.applied.length} (read ${s.snapshot.read_at})` : "not read yet" })),
48028
+ ["name", "id", "path", "format", "engine", "database", "branch", "on_merge", "state", "applied"]
48029
+ );
48030
+ var renderSource = (s) => `${kv({ ...s, database: database(s), path: s.path || "(repository root)" }, ["id", "name", "path", "format", "engine", "database", "branch", "revisions_location", "baseline_version", "exec_order", "credential_mode", "dsn_set", "run_as_role", "on_merge", "change_checks", "state", "version"])}
48031
+
48032
+ ` + (s.on_merge === "approval" ? `Merges to ${s.branch} that add migrations create a run waiting for approval: ${BRAND.cli} db runs` : `Runs start only by hand: ${BRAND.cli} db apply ${s.name}`);
48033
+ var renderSuggestions = (v) => v.items.length ? table(
48034
+ v.items.map((s) => ({ ...s, path: s.path || "(root)", configured: s.configured ? "yes" : "no" })),
48035
+ ["path", "format", "files", "revisions_schema", "baseline_version", "configured", "evidence"]
48036
+ ) : "no migration directories found";
48037
+ function renderPlan2(p) {
48038
+ const db = p.database === "live" ? "read now" : p.database === "snapshot" ? `as last read${p.read_at ? ` ${p.read_at}` : ""}` : `not read${p.note ? `: ${p.note}` : ""}`;
48039
+ const lines2 = [`${p.source_name} at ${p.ref} (${short(p.commit_oid)}): ${p.ok ? "ok" : "has errors"}`, `database: ${db}`, `files: ${p.files}, applied: ${p.applied.length}${p.applied.length ? ` (latest ${p.applied[p.applied.length - 1]})` : ""}`];
48040
+ if (p.baseline) lines2.push(`baseline: ${p.baseline}${p.no_history ? " (recorded, not applied: the database has no revision history)" : ""}`);
48041
+ else if (p.no_history && p.database !== "none") lines2.push("no revision history yet: every file is pending");
48042
+ if (p.database !== "live" && p.note) lines2.push(`note: ${p.note}`);
48043
+ if (!p.pending.length) lines2.push("nothing pending");
48044
+ else {
48045
+ lines2.push(`pending (${p.pending.length}):`);
48046
+ for (const f of p.pending) {
48047
+ const notes = [f.resume_from ? `resumes at statement ${f.resume_from + 1}` : "", f.tx_mode === "none" ? "no transaction" : "", f.introduced_by_change ? "in this change" : ""].filter(Boolean);
48048
+ lines2.push(` ${f.name} ${plural3(f.statements, "statement")}${notes.length ? ` (${notes.join(", ")})` : ""}`);
48049
+ }
48050
+ }
48051
+ return [...lines2, ...issueLines(p.issues)].join("\n");
48052
+ }
48053
+ var renderPlans = (v) => v.items.length ? v.items.map(renderPlan2).join("\n\n") : "no migration sources";
48054
+ var NEXT = {
48055
+ awaiting_approval: (id2) => `Waiting for a repository admin: ${BRAND.cli} db approve ${id2} (or reject)`,
48056
+ failed: (id2) => `Fix the migration and merge it, then: ${BRAND.cli} db retry ${id2}`,
48057
+ needs_attention: (id2) => `Look at the run in PlanetScale, then: ${BRAND.cli} db retry ${id2} or cancel`
48058
+ };
48059
+ function renderRun(r) {
48060
+ const ps = r.planetscale;
48061
+ const head = kv(
48062
+ {
48063
+ ...r,
48064
+ commit: short(r.commit_oid),
48065
+ change: r.change_id ?? "-",
48066
+ requested_by: r.requested_by ? `${r.requested_by.type} ${r.requested_by.id}` : "-",
48067
+ deploy_request: ps?.deploy_request_url ?? (ps?.deploy_request ? `#${ps.deploy_request}` : null),
48068
+ deployment_state: ps?.deployment_state ?? null
48069
+ },
48070
+ ["id", "source_name", "status", "kind", "engine", "commit", "change", "baseline", "requested_by", "approved_by", "approved_at", "decision_reason", "deploy_request", "deployment_state", "error", "note", "created_at"]
48071
+ );
48072
+ const files = r.files.length ? table(
48073
+ r.files.map((f) => ({ ...f, statements: f.applied_statements ? `${f.applied_statements}/${f.statements}` : f.statements, error: f.error ? `${f.error}${f.error_statement ? ` (in: ${f.error_statement})` : ""}` : null })),
48074
+ ["name", "status", "statements", "execution_ms", "error"]
48075
+ ) : "no files";
48076
+ const next = NEXT[r.status]?.(r.id);
48077
+ return [head, "", files, ...r.issues.length ? ["", ...issueLines(r.issues)] : [], ...next ? ["", next] : []].join("\n");
48078
+ }
48079
+ var renderRuns = (v) => listWithCursor(
48080
+ { ...v, items: v.items.map((r) => ({ ...r, commit: short(r.commit_oid), files: `${r.files.filter((f) => f.status === "applied").length}/${r.files.length} applied` })) },
48081
+ ["id", "source_name", "status", "kind", "commit", "files", "created_at"]
48082
+ );
48083
+ var oneOf = (flag, values) => (v) => {
48084
+ if (!values.includes(v)) throw usage(`${flag} must be one of ${values.join(", ")}`);
48085
+ return v;
48086
+ };
48087
+ function sessionSettings(pairs) {
48088
+ if (!pairs) return void 0;
48089
+ const out = {};
48090
+ for (const p of pairs) {
48091
+ const eq = p.indexOf("=");
48092
+ if (eq < 1) throw usage(`--session-setting must be name=value (got "${p}")`);
48093
+ out[p.slice(0, eq)] = p.slice(eq + 1);
48094
+ }
48095
+ return out;
48096
+ }
48097
+ function registerDb(program2, rt) {
48098
+ const db = program2.command("db").description("Database migrations on PlanetScale for --repo: sources, plans, and runs applied after merge and approval");
48099
+ const sources = db.command("sources").description("Migration sources of --repo: a directory bound to a PlanetScale database branch").action(act(rt, async (ctx) => ctx.result(await ctx.client("user").dbMigrations.sources.list(await ctx.repoId(void 0, "user")), renderSources)));
48100
+ sources.command("add").description("Add a migration source (repository admins). The source is checked against PlanetScale; `db suggest` shows the directories found").requiredOption("--name <name>", "source name (lowercase letters, digits, - and _)").requiredOption("--path <dir>", 'migration directory relative to the repository root ("" for the root)').requiredOption("--format <format>", DB_MIGRATION_FORMATS.join(" | "), oneOf("--format", DB_MIGRATION_FORMATS)).requiredOption("--engine <engine>", DB_ENGINE_KINDS.join(" | "), oneOf("--engine", DB_ENGINE_KINDS)).requiredOption("--connection <connection>", "PlanetScale connection for database migrations (icn_\u2026)").requiredOption("--organization <org>", "PlanetScale organization").requiredOption("--database <db>", "PlanetScale database").option("--ps-branch <branch>", "PlanetScale branch migrations are applied to", "main").option("--branch <branch>", "repository branch whose merges create runs (default: the default branch)").option("--revisions-schema <schema>", "where the revisions table lives (Postgres schema)").option("--revisions-table <table>", "revisions table name (default: the format's own)").option("--baseline <version>", "with no revision history yet, record this version as applied instead of applying files up to it").option("--exec-order <order>", DB_EXEC_ORDERS.join(" | "), oneOf("--exec-order", DB_EXEC_ORDERS)).option("--on-merge <mode>", "approval (merges create runs waiting for approval) | manual_only", oneOf("--on-merge", ["approval", "manual_only"])).option("--credential-mode <mode>", DB_CREDENTIAL_MODES.join(" | "), oneOf("--credential-mode", DB_CREDENTIAL_MODES)).option("--run-as-role <role>", "Postgres: SET ROLE for migration sessions").option("--session-setting <name=value>", "Postgres session setting for migration sessions (repeatable)", repeat).option("--statement-timeout <seconds>", "statement timeout (at most 600)", (v) => parseIntOption(v, "--statement-timeout")).option("--lock-timeout <seconds>", "lock timeout (at most 60)", (v) => parseIntOption(v, "--lock-timeout")).option("--no-change-checks", "do not validate the directory on changes").option("--dsn-file <path|->", "--credential-mode dsn: file holding the connection string, or - for stdin (never a flag value; never printed)").action(
48101
+ act(rt, async (ctx, o) => {
48102
+ let dsn;
48103
+ if (o.dsnFile !== void 0) {
48104
+ if (o.dsnFile === "-" && ctx.deps.stdinIsTTY) throw usage(`pipe the connection string on stdin, e.g. \`pbpaste | ${BRAND.cli} db sources add \u2026 --dsn-file -\``);
48105
+ dsn = (await readText(fileSpec(o.dsnFile), ctx.input)).trim();
48106
+ if (!dsn) throw usage("the connection string is empty");
48107
+ ctx.addSecret(dsn);
48108
+ const password = /^[a-z]+:\/\/[^:/@]*:([^@]+)@/i.exec(dsn)?.[1];
48109
+ if (password) ctx.addSecret(password);
48110
+ }
48111
+ const body = {
48112
+ name: o.name,
48113
+ path: o.path,
48114
+ format: o.format,
48115
+ engine: o.engine,
48116
+ connection_id: o.connection,
48117
+ ps_organization: o.organization,
48118
+ ps_database: o.database,
48119
+ ps_branch: o.psBranch,
48120
+ change_checks: o.changeChecks,
48121
+ ...o.branch ? { branch: o.branch } : {},
48122
+ ...o.revisionsSchema ? { revisions_schema: o.revisionsSchema } : {},
48123
+ ...o.revisionsTable ? { revisions_table: o.revisionsTable } : {},
48124
+ ...o.baseline ? { baseline_version: o.baseline } : {},
48125
+ ...o.execOrder ? { exec_order: o.execOrder } : {},
48126
+ ...o.onMerge ? { on_merge: o.onMerge } : {},
48127
+ ...o.credentialMode ? { credential_mode: o.credentialMode } : {},
48128
+ ...o.runAsRole ? { run_as_role: o.runAsRole } : {},
48129
+ ...o.sessionSetting ? { session_settings: sessionSettings(o.sessionSetting) } : {},
48130
+ ...o.statementTimeout !== void 0 ? { statement_timeout_s: o.statementTimeout } : {},
48131
+ ...o.lockTimeout !== void 0 ? { lock_timeout_s: o.lockTimeout } : {},
48132
+ ...dsn ? { dsn } : {}
48133
+ };
48134
+ ctx.result(await ctx.client("user").dbMigrations.sources.create(await ctx.repoId(void 0, "user"), body), renderSource);
48135
+ })
48136
+ );
48137
+ sources.command("rm <source>").description("Delete a migration source (name or id; its runs stay listed). Requires --yes when non-interactive").action(
48138
+ act(rt, async (ctx, _o, [ref]) => {
48139
+ const id2 = await sourceId(ctx, ref);
48140
+ await ctx.confirm(`delete migration source ${ref}`);
48141
+ await ctx.client("user").dbMigrations.sources.delete(id2);
48142
+ ctx.result({ deleted: id2 }, `deleted ${ref}`);
48143
+ })
48144
+ );
48145
+ db.command("suggest").description("Migration directories found in --repo (atlas.sum, drizzle journals, *.up.sql, numbered SQL files), and whether a source covers each").option("--ref <ref>", "branch, tag or commit (default: the default branch)").action(act(rt, async (ctx, o) => ctx.result(await ctx.client("user").dbMigrations.suggestions(await ctx.repoId(void 0, "user"), o.ref), renderSuggestions)));
48146
+ db.command("plan [source]").description("What applying a source (every source when omitted) would do: pending files and issues. Exits 7 when a plan has errors").option("--ref <ref>", "branch, tag or commit (default: the source's branch)").option("--live", "read the database now instead of its last snapshot (repository admins)").action(
48147
+ act(rt, async (ctx, o, [ref]) => {
48148
+ const c8 = ctx.client("user").dbMigrations;
48149
+ const q = { ref: o.ref, live: o.live };
48150
+ if (ref) {
48151
+ const p = await c8.plan(await sourceId(ctx, ref), q);
48152
+ ctx.result(p, renderPlan2);
48153
+ if (!p.ok) ctx.setExit(EXIT.NOT_READY);
48154
+ return;
48155
+ }
48156
+ const { items } = await c8.sources.list(await ctx.repoId(void 0, "user"));
48157
+ const plans = { items: await Promise.all(items.filter((s) => s.state === "active").map((s) => c8.plan(s.id, q))) };
48158
+ ctx.result(plans, renderPlans);
48159
+ if (plans.items.some((p) => !p.ok)) ctx.setExit(EXIT.NOT_READY);
48160
+ })
48161
+ );
48162
+ db.command("refresh <source>").description("Read the applied versions from the database now and show the plan at the source's branch (repository admins)").action(act(rt, async (ctx, _o, [ref]) => ctx.result(await ctx.client("user").dbMigrations.refresh(await sourceId(ctx, ref)), renderPlan2)));
48163
+ db.command("runs").description("Migration runs of --repo, newest first").option("--source <source>", "only this source (name or id)").option("--limit <n>", "page size", (v) => parseIntOption(v, "--limit")).option("--cursor <cursor>", "page cursor").action(
48164
+ act(rt, async (ctx, o) => {
48165
+ const repo = { id: await ctx.repoId(void 0, "user") };
48166
+ const q = { ...o.source ? { source_id: await sourceId(ctx, o.source, repo) } : {}, ...o.limit ? { limit: o.limit } : {}, ...o.cursor ? { cursor: o.cursor } : {} };
48167
+ ctx.result(await ctx.client("user").dbMigrations.runs.list(repo.id, q), renderRuns);
48168
+ })
48169
+ );
48170
+ db.command("run <run>").description("One migration run: status, files and issues").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client("user").dbMigrations.runs.get(id2), renderRun)));
48171
+ db.command("apply <source>").description("Apply a source's pending migrations now (repository admins; your request is the approval). Requires --yes when non-interactive").option("--ref <ref>", "branch, tag or commit to apply (default: the source's branch)").action(
48172
+ act(rt, async (ctx, o, [ref]) => {
48173
+ const id2 = await sourceId(ctx, ref);
48174
+ await ctx.confirm(`apply the pending migrations of ${ref}${o.ref ? ` at ${o.ref}` : ""} to its database`);
48175
+ const r = await ctx.client("user").dbMigrations.startRun(id2, o.ref ? { ref: o.ref } : {});
48176
+ ctx.result(r, (v) => v.run ? renderRun(v.run) : `nothing to apply
48177
+
48178
+ ${renderPlan2(v.plan)}`);
48179
+ })
48180
+ );
48181
+ const decisions = [
48182
+ ["approve", "Approve a run waiting for approval: Brnch applies exactly the files it shows (repository admins)"],
48183
+ ["reject", "Reject a run waiting for approval; nothing is applied (repository admins)"],
48184
+ ["retry", "Retry a failed run from where it stopped (repository admins)"],
48185
+ ["cancel", "Cancel a run that has not finished (repository admins)"]
48186
+ ];
48187
+ for (const [decision, description] of decisions) {
48188
+ db.command(`${decision} <run>`).description(description).option("--reason <text>", "why (recorded with the decision)").action(act(rt, async (ctx, o, [id2]) => ctx.result(await ctx.client("user").dbMigrations[decision](id2, o.reason), renderRun)));
48189
+ }
48190
+ }
48191
+
47790
48192
  // packages/cli/src/commands/deploy.ts
47791
48193
  var TERMINAL4 = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "superseded", "blocked"]);
47792
48194
  var NO_DO_PREVIEWS = "previews off: Cloudflare makes no version URLs for Workers that implement a Durable Object (--previews --preview-mode worker_previews uses Worker Previews; it needs a `previews` block in the Wrangler configuration)";
@@ -47794,7 +48196,7 @@ var renderTargets = (v) => table(
47794
48196
  v.items.map((t) => ({ ...t, resource: t.resource_name, env: t.environment ?? "-", last_production: t.last_production ? `${t.last_production.status} ${t.last_production.commit_oid.slice(0, 12)}` : "-" })),
47795
48197
  ["name", "id", "platform", "resource", "env", "branch", "runs_on", "previews", "state", "last_production"]
47796
48198
  );
47797
- var renderTarget = (t) => kv({ ...t, connection: t.connection ? `${t.connection.name} (${t.connection.id}, ${t.connection.state})` : "-", last_production: t.last_production ? `${t.last_production.id} ${t.last_production.status}` : "-" }, [
48199
+ var renderTarget = (t) => kv({ ...t, watch_paths: t.watch_paths?.length ? t.watch_paths.join(" ") : "every change", connection: t.connection ? `${t.connection.name} (${t.connection.id}, ${t.connection.state})` : "-", last_production: t.last_production ? `${t.last_production.id} ${t.last_production.status}` : "-" }, [
47798
48200
  "id",
47799
48201
  "name",
47800
48202
  "platform",
@@ -47809,6 +48211,7 @@ var renderTarget = (t) => kv({ ...t, connection: t.connection ? `${t.connection.
47809
48211
  "build_command",
47810
48212
  "output_directory",
47811
48213
  "runs_on",
48214
+ "watch_paths",
47812
48215
  "previews",
47813
48216
  "preview_check_name",
47814
48217
  "last_production",
@@ -47845,7 +48248,7 @@ async function stepsOf(ctx, spec) {
47845
48248
  return v;
47846
48249
  }
47847
48250
  function targetOptions(cmd) {
47848
- return cmd.option("--platform <platform>", "workers | pages").option("--connection <connection>", "deploy connection (icn_\u2026)").option("--account-id <id>", "Cloudflare account id").option("--env <name>", "Wrangler environment (deploys with --env)").option("--worker <name>", "Worker name (or Pages project name)").option("--root <dir>", "root directory (where the Wrangler config lives)").option("--branch <name>", "production branch (default: the repository's default branch)").option("--install <cmd>", "install command").option("--build <cmd>", "build command").option("--output-dir <dir>", "Pages: the built site").option("--previews", "upload a preview of every open change").option("--no-previews", "no previews").option("--preview-agent-changes", "also preview changes made by agents or integrations, isolated (Worker Previews only)").option("--no-preview-agent-changes", "previews only for changes people upload").option("--preview-mode <mode>", "versions (preview version with an alias) | worker_previews (`wrangler preview`: Durable Objects allowed, bindings from the `previews` block)").option("--preview-check <name>", "report previews as this check").option("--runs-on <label>", "hosted runner size for the deploy jobs: brnch-hosted (default), brnch-hosted-1 \u2026 brnch-hosted-4 (your plan sets the largest)").option("--name <name>", "target name").option("--steps <json>", `pre/post steps as a JSON array, @file or - (replaces the list), e.g. '[{"id":"migrate","kind":"d1_migrations","phase":"pre"}]'`);
48251
+ return cmd.option("--platform <platform>", "workers | pages").option("--connection <connection>", "deploy connection (icn_\u2026)").option("--account-id <id>", "Cloudflare account id").option("--env <name>", "Wrangler environment (deploys with --env)").option("--worker <name>", "Worker name (or Pages project name)").option("--root <dir>", "root directory (where the Wrangler config lives)").option("--branch <name>", "production branch (default: the repository's default branch)").option("--install <cmd>", "install command").option("--build <cmd>", "build command").option("--output-dir <dir>", "Pages: the built site").option("--previews", "upload a preview of every open change").option("--no-previews", "no previews").option("--preview-agent-changes", "also preview changes made by agents or integrations, isolated (Worker Previews only)").option("--no-preview-agent-changes", "previews only for changes people upload").option("--preview-mode <mode>", "versions (preview version with an alias) | worker_previews (`wrangler preview`: Durable Objects allowed, bindings from the `previews` block)").option("--preview-check <name>", "report previews as this check").option("--runs-on <label>", "hosted runner size for the deploy jobs: brnch-hosted (default), brnch-hosted-1 \u2026 brnch-hosted-4 (your plan sets the largest)").option("--watch-path <pattern>", "deploy only when these paths change (repeatable; replaces the list): CODEOWNERS patterns like apps/web/ or packages/**, ! excludes", (v, prev = []) => [...prev, v]).option("--watch-all", "deploy on every change (clears the watch paths)").option("--name <name>", "target name").option("--steps <json>", `pre/post steps as a JSON array, @file or - (replaces the list), e.g. '[{"id":"migrate","kind":"d1_migrations","phase":"pre"}]'`);
47849
48252
  }
47850
48253
  async function planSetup(ctx, repo, o) {
47851
48254
  const c8 = ctx.client("user").deploy;
@@ -47920,6 +48323,7 @@ async function planSetup(ctx, repo, o) {
47920
48323
  ...o.previewAgentChanges !== void 0 ? { preview_agent_changes: o.previewAgentChanges } : {},
47921
48324
  preview_check_name: o.previewCheck ?? null,
47922
48325
  ...o.runsOn ? { runs_on: o.runsOn } : {},
48326
+ ...o.watchPath ? { watch_paths: o.watchPath } : {},
47923
48327
  ...o.branch ? { branch: o.branch } : {},
47924
48328
  ...o.steps !== void 0 ? { steps: await stepsOf(ctx, o.steps) } : {}
47925
48329
  };
@@ -47931,6 +48335,7 @@ async function waitFor(ctx, d, interval, timeout) {
47931
48335
  for (; ; ) {
47932
48336
  if (d.status !== last) ctx.info(`${d.id}: ${d.status}${d.status_reason ? ` (${d.status_reason})` : ""}`);
47933
48337
  last = d.status;
48338
+ ctx.status.working(`Deploy ${d.status}`);
47934
48339
  if (TERMINAL4.has(d.status) || Date.now() > deadline) return d;
47935
48340
  await ctx.deps.sleep(Math.max(1, interval) * 1e3);
47936
48341
  d = await ctx.client("user").deploy.deployments.get(d.id);
@@ -47955,6 +48360,7 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
47955
48360
  targetOptions(deploy.command("update <target>").description("Change a deploy target (only the given flags; an empty value clears a command or the environment)")).option("--disable", "stop deploying (merges no longer deploy)").option("--enable", "deploy again").action(
47956
48361
  act(rt, async (ctx, o, [ref]) => {
47957
48362
  if (o.disable && o.enable) throw usage("give either --disable or --enable");
48363
+ if (o.watchAll && o.watchPath) throw usage("give either --watch-path or --watch-all");
47958
48364
  const c8 = ctx.client("user").deploy.targets;
47959
48365
  const cur = await c8.get(await targetId(ctx, ref));
47960
48366
  const patch = {
@@ -47974,6 +48380,7 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
47974
48380
  preview_agent_changes: o.previewAgentChanges,
47975
48381
  preview_check_name: orNull(o.previewCheck),
47976
48382
  runs_on: o.runsOn,
48383
+ watch_paths: o.watchAll ? [] : o.watchPath,
47977
48384
  state: o.disable ? "disabled" : o.enable ? "active" : void 0,
47978
48385
  steps: await stepsOf(ctx, o.steps)
47979
48386
  };
@@ -48020,7 +48427,7 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
48020
48427
  var ITERATION_DONE = /* @__PURE__ */ new Set(["succeeded", "no_changes", "failed", "cancelled"]);
48021
48428
  var STATES = ["working", "waiting", "stopped"];
48022
48429
  var who2 = (a) => a.display_name ?? `${a.type} ${a.id}`;
48023
- var short = (oid) => oid ? oid.slice(0, 12) : null;
48430
+ var short2 = (oid) => oid ? oid.slice(0, 12) : null;
48024
48431
  function vendorArg(v) {
48025
48432
  if (!CODING_AGENT_VENDORS.includes(v)) throw usage(`unknown agent "${v}"; one of ${CODING_AGENT_VENDORS.join(", ")}`);
48026
48433
  return v;
@@ -48040,7 +48447,7 @@ function renderIterations(d) {
48040
48447
  ...i,
48041
48448
  trigger: i.trigger_ref ? `${i.trigger} (${i.trigger_ref})` : i.trigger,
48042
48449
  by: who2(i.trigger_actor),
48043
- head: short(i.head_oid)
48450
+ head: short2(i.head_oid)
48044
48451
  }));
48045
48452
  const claims = d.iterations.map(reported).filter((x) => !!x);
48046
48453
  const errors = d.iterations.filter((i) => i.error).map((i) => ` #${i.number}: ${i.error}`);
@@ -48103,6 +48510,7 @@ async function waitForIteration(ctx, start, o) {
48103
48510
  const line2 = `${delegationStatusLabel(d)}${it ? ` (iteration #${it.number}: ${it.state})` : ""}`;
48104
48511
  if (line2 !== last) ctx.info(line2);
48105
48512
  last = line2;
48513
+ ctx.status.working(`${d.agent.name}: ${line2}`);
48106
48514
  if (it ? ITERATION_DONE.has(it.state) : d.state !== "working") return { d, timedOut: false };
48107
48515
  if (Date.now() > deadline) return { d, timedOut: true };
48108
48516
  await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
@@ -49054,15 +49462,16 @@ function registerImage(program2, rt) {
49054
49462
  ctx.result(page, (p) => listWithCursor(p, ["reference", "tags", "versions", "updated_at"]));
49055
49463
  })
49056
49464
  );
49057
- image.command("delete <ref>").description("Delete a tag (`<image>:<tag>`) or a version and all its tags (`<image>@sha256:\u2026`); untagged versions are also collected after the retention window").action(
49465
+ image.command("delete <ref>").description("Delete a tag (`<image>:<tag>`), a version and all its tags (`<image>@sha256:\u2026`), or an image with no versions left (`<image>`); untagged versions are also collected after the retention window").action(
49058
49466
  act(rt, async (ctx, _o, [spec]) => {
49059
49467
  const ref = parseImageRef(spec, await registryHost(ctx));
49060
- if (!ref || !ref.tag && !ref.digest) throw usage(`invalid image reference "${spec}": expected <namespace>/<repository>[/<image>]:<tag> or \u2026@sha256:<digest>`);
49468
+ if (!ref) throw usage(`invalid image reference "${spec}": expected <namespace>/<repository>[/<image>][:<tag>|@sha256:<digest>]`);
49061
49469
  const img = await findImage(ctx, ref.name);
49062
49470
  if (ref.digest) await ctx.client().packages.deleteVersion(img.id, ref.digest);
49063
- else await ctx.client().packages.deleteTag(img.id, ref.tag);
49064
- const what = ref.digest ? `version ${ref.digest}` : `tag ${ref.tag}`;
49065
- ctx.result({ image: img.full_name, deleted: ref.digest ? { digest: ref.digest } : { tag: ref.tag } }, `Deleted ${what} of ${img.full_name}.`);
49471
+ else if (ref.tag) await ctx.client().packages.deleteTag(img.id, ref.tag);
49472
+ else await ctx.client().packages.delete(img.id);
49473
+ const what = ref.digest ? `version ${ref.digest} of ${img.full_name}` : ref.tag ? `tag ${ref.tag} of ${img.full_name}` : `image ${img.full_name}`;
49474
+ ctx.result({ image: img.full_name, deleted: ref.digest ? { digest: ref.digest } : ref.tag ? { tag: ref.tag } : { image: img.id } }, `Deleted ${what}.`);
49066
49475
  })
49067
49476
  );
49068
49477
  }
@@ -49308,6 +49717,7 @@ async function waitForPromotion(ctx, p, timeout, interval) {
49308
49717
  let last = p.state;
49309
49718
  ctx.info(`promotion ${p.id}: ${p.state}`);
49310
49719
  while (!TERMINAL5.has(p.state) && Date.now() < deadline) {
49720
+ ctx.status.working(`Merge ${p.state}`);
49311
49721
  await ctx.deps.sleep(Math.max(1, interval) * 1e3);
49312
49722
  p = await ctx.client().promotions.get(p.id);
49313
49723
  if (p.state !== last) ctx.info(`promotion ${p.id}: ${last = p.state}`);
@@ -51681,7 +52091,7 @@ function safeLegs(m) {
51681
52091
  }
51682
52092
 
51683
52093
  // packages/cli/src/commands/actions.ts
51684
- var renderRun = (r) => kv({ ...r, actor: r.actor.display_name ?? r.actor.id, trust: `${r.definition.source} ${r.definition.ref}${r.definition.trusted ? " (trusted)" : " (contributor-controlled)"}` }, [
52094
+ var renderRun2 = (r) => kv({ ...r, actor: r.actor.display_name ?? r.actor.id, trust: `${r.definition.source} ${r.definition.ref}${r.definition.trusted ? " (trusted)" : " (contributor-controlled)"}` }, [
51685
52095
  "id",
51686
52096
  "workflow_name",
51687
52097
  "run_number",
@@ -51746,7 +52156,7 @@ function registerWorkflow(program2, rt) {
51746
52156
  inputs[kvp.slice(0, i)] = kvp.slice(i + 1);
51747
52157
  }
51748
52158
  const r = await ctx.client("user").actions.workflows.dispatch(await ctx.repoId(void 0, "user"), file2, { ...o.ref ? { ref: o.ref } : {}, inputs });
51749
- ctx.result(r, renderRun);
52159
+ ctx.result(r, renderRun2);
51750
52160
  })
51751
52161
  );
51752
52162
  }
@@ -51770,6 +52180,8 @@ async function watchRun(ctx, runId, interval, timeout, logs) {
51770
52180
  ${text2.trimEnd()}`);
51771
52181
  }
51772
52182
  }
52183
+ const finished = r.jobs.filter((j) => j.status === "completed").length;
52184
+ ctx.status.working(`Run ${r.status}: ${finished}/${r.jobs.length} jobs done`, r.jobs.length ? finished / r.jobs.length * 100 : void 0);
51773
52185
  if (TERMINAL6.has(r.status) || Date.now() > deadline) return r;
51774
52186
  await ctx.deps.sleep(Math.max(1, interval) * 1e3);
51775
52187
  }
@@ -51789,13 +52201,13 @@ function registerRun(program2, rt) {
51789
52201
  const r = await c8.actions.runs.get(id2);
51790
52202
  if (o.log) for (const j of r.jobs) ctx.info(`\u2500\u2500 ${j.name} (${j.status}${j.conclusion ? `/${j.conclusion}` : ""}) \u2500\u2500
51791
52203
  ${(await c8.actions.jobs.logs(j.id)).trimEnd()}`);
51792
- ctx.result(r, renderRun);
52204
+ ctx.result(r, renderRun2);
51793
52205
  })
51794
52206
  );
51795
52207
  run3.command("watch <run>").description("Follow a run until it completes. Exit 0 when it succeeded, 1 when it failed or was cancelled, 7 on timeout").option("--interval <seconds>", "poll interval", (v) => parseIntOption(v, "--interval"), 2).option("--timeout <seconds>", "give up after", (v) => parseIntOption(v, "--timeout"), 3600).option("--log", "print each job's log when it completes").action(
51796
52208
  act(rt, async (ctx, o, [id2]) => {
51797
52209
  const r = await watchRun(ctx, id2, o.interval, o.timeout, !!o.log);
51798
- ctx.result(r, renderRun);
52210
+ ctx.result(r, renderRun2);
51799
52211
  ctx.setExit(runExit(r));
51800
52212
  })
51801
52213
  );
@@ -51820,8 +52232,8 @@ ${(await c8.actions.jobs.logs(j.id)).trimEnd()}`);
51820
52232
  ctx.result({ job_id: job2, conclusion: status }, `job ${job2}: ${status}`);
51821
52233
  })
51822
52234
  );
51823
- run3.command("cancel <run>").description("Cancel a run (running jobs are stopped on their runners)").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client("user").actions.runs.cancel(id2), renderRun)));
51824
- run3.command("rerun <run>").description("Re-run a completed run (all jobs, or --failed and their dependents)").option("--failed", "only failed/cancelled jobs and jobs that depend on them").action(act(rt, async (ctx, o, [id2]) => ctx.result(await ctx.client("user").actions.runs.rerun(id2, { failed_only: !!o.failed }), renderRun)));
52235
+ run3.command("cancel <run>").description("Cancel a run (running jobs are stopped on their runners)").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client("user").actions.runs.cancel(id2), renderRun2)));
52236
+ run3.command("rerun <run>").description("Re-run a completed run (all jobs, or --failed and their dependents)").option("--failed", "only failed/cancelled jobs and jobs that depend on them").action(act(rt, async (ctx, o, [id2]) => ctx.result(await ctx.client("user").actions.runs.rerun(id2, { failed_only: !!o.failed }), renderRun2)));
51825
52237
  }
51826
52238
  function registerSecret(program2, rt) {
51827
52239
  const secret = program2.command("secret").description("Actions secrets of --repo (default) or the organization (--org-level). Values are write-only");
@@ -52402,6 +52814,10 @@ ${res.stdout.slice(-8e3)}`;
52402
52814
  // packages/cli/src/runner/build-image-step.ts
52403
52815
  var BUILDKIT_IMAGE = "moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c";
52404
52816
  var COSIGN_IMAGE = "ghcr.io/sigstore/cosign/cosign:v3.1.3@sha256:9e5c2f2edc34351160407ca3416c61855bdf9403c3c5936e0f0be7fc261611b8";
52817
+ var BUILDKIT_RELEASE = { url: "https://github.com/moby/buildkit/releases/download/v0.34.0/buildkit-v0.34.0.linux-amd64.tar.gz", sha256: "14813b1fb2a40082114e6d386c6fc5e7dd5298176ce7ee1a18bc33e73e109ac3" };
52818
+ var COSIGN_RELEASE = { url: "https://github.com/sigstore/cosign/releases/download/v3.1.3/cosign-linux-amd64", sha256: "4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71" };
52819
+ var HOSTED_DIRS = { tools: "/opt/brnch/tools", state: "/var/lib/brnch-buildkit", run: "/run/brnch-buildkit", client: "/run/brnch-buildkit-client", context: "/run/brnch-build-context" };
52820
+ var BUILD_UID = 10999;
52405
52821
  var MAX_METADATA_OUTPUT = 32 * 1024;
52406
52822
  var list2 = (v) => (v ?? "").split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
52407
52823
  var lines = (v) => (v ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
@@ -52454,15 +52870,15 @@ function inside2(p) {
52454
52870
  }
52455
52871
  return parts.join("/");
52456
52872
  }
52457
- function buildctlArgs(i, metadataFile) {
52458
- const ctx = inside2(i.context);
52873
+ function buildLayout(i) {
52874
+ const context = inside2(i.context);
52459
52875
  const file2 = inside2(i.file ?? `${i.context}/Dockerfile`);
52460
- if (ctx === null) return `context: ${i.context} is outside the workspace`;
52461
- if (file2 === null) return `file: ${i.file} is outside the workspace`;
52462
- const slash = file2.lastIndexOf("/");
52463
- const dir = slash >= 0 ? file2.slice(0, slash) : "";
52464
- const base2 = slash >= 0 ? file2.slice(slash + 1) : file2;
52465
- const args = ["build", "--frontend", "dockerfile.v0", "--local", `context=/workspace/${ctx}`.replace(/\/$/, ""), "--local", `dockerfile=/workspace/${dir}`.replace(/\/$/, ""), "--opt", `filename=${base2}`];
52876
+ if (context === null) return `context: ${i.context} is outside the workspace`;
52877
+ if (file2 === null || file2 === "") return `file: ${i.file} is outside the workspace`;
52878
+ return { context, file: file2, filename: file2.slice(file2.lastIndexOf("/") + 1) };
52879
+ }
52880
+ function buildctlArgs(i, layout, metadataFile, root = "/workspace") {
52881
+ const args = ["build", "--frontend", "dockerfile.v0", "--local", `context=${root}/context`, "--local", `dockerfile=${root}/dockerfile`, "--opt", `filename=${layout.filename}`];
52466
52882
  args.push("--opt", `platform=${i.platforms.join(",")}`);
52467
52883
  if (i.target) args.push("--opt", `target=${i.target}`);
52468
52884
  for (const [k, v] of Object.entries(i.buildArgs)) args.push("--opt", `build-arg:${k}=${v}`);
@@ -52475,6 +52891,215 @@ function buildctlArgs(i, metadataFile) {
52475
52891
  args.push("--metadata-file", metadataFile);
52476
52892
  return args;
52477
52893
  }
52894
+ var quote2 = (s) => `'${s.replaceAll("'", `'\\''`)}'`;
52895
+ async function copyContext(d, layout, owner) {
52896
+ const fresh2 = async (hosted) => {
52897
+ if (d.hosted) return await d.run(["sh", "-c", `rm -rf ${hosted} && install -d -m 0700 ${hosted}`], { env: d.env, timeoutMs: 6e4 }) === 0 ? hosted : null;
52898
+ const made = await d.capture(["mktemp", "-d", "/tmp/brnch-build-context.XXXXXXXX"], { env: d.env });
52899
+ const dir2 = made.stdout.trim();
52900
+ return made.code === 0 && dir2.startsWith("/tmp/brnch-build-context.") ? dir2 : null;
52901
+ };
52902
+ const staging = await fresh2(`${HOSTED_DIRS.context}-staging`);
52903
+ const dir = staging && await fresh2(HOSTED_DIRS.context);
52904
+ if (!staging || !dir) {
52905
+ if (staging) await d.run(["rm", "-rf", staging], { env: d.env, timeoutMs: 6e4 });
52906
+ return { error: "could not prepare the build context" };
52907
+ }
52908
+ const asStep = d.stepUser ? ["setpriv", `--reuid=${d.stepUser.uid}`, `--regid=${d.stepUser.gid}`, "--clear-groups", "--"] : [];
52909
+ const asOwner = owner !== null && owner !== 0 ? ["setpriv", `--reuid=${owner}`, `--regid=${owner}`, "--clear-groups", "--"] : [];
52910
+ const ws = (rel) => rel ? `${d.workspace}/${rel}` : d.workspace;
52911
+ const ignore = `${layout.filename}.dockerignore`;
52912
+ const stage = [
52913
+ "set -e",
52914
+ `w=$(cd -- ${quote2(d.workspace)} && pwd -P)`,
52915
+ 'inside() { case "$1" in "$w"/*) ;; *) echo "brnch/build-image@v1: $2 is outside the workspace" >&2; exit 3;; esac; }',
52916
+ `c=$(cd -- ${quote2(ws(layout.context))} && pwd -P)`,
52917
+ 'case "$c" in "$w"|"$w"/*) ;; *) echo "brnch/build-image@v1: the context is outside the workspace" >&2; exit 3;; esac',
52918
+ `fd=$(cd -- "$(dirname -- ${quote2(ws(layout.file))})" && pwd -P)`,
52919
+ `r=$(realpath -- "$fd/$(basename -- ${quote2(ws(layout.file))})")`,
52920
+ `inside "$r" ${quote2(layout.file)}`,
52921
+ `cp -a -- "$c/." ${quote2(`${staging}/context`)}`,
52922
+ `mkdir -- ${quote2(`${staging}/dockerfile`)}`,
52923
+ `cp -- "$r" ${quote2(`${staging}/dockerfile/${layout.filename}`)}`,
52924
+ // The name is the step's input: quoted into a variable, never into shell source (review of #287).
52925
+ `ig=${quote2(ignore)}`,
52926
+ 'if [ -e "$fd/$ig" ]; then i=$(realpath -- "$fd/$ig"); inside "$i" "$ig"; cp -- "$i" ' + quote2(`${staging}/dockerfile/${ignore}`) + "; fi",
52927
+ // Readable by the builder user for stage 2; the step's own files, nothing else.
52928
+ `chmod -R a+rX ${quote2(staging)}`
52929
+ ].join("; ");
52930
+ const fail = async () => {
52931
+ await d.run(["rm", "-rf", staging, dir], { env: d.env, timeoutMs: 6e4 });
52932
+ return { error: `could not copy the build context (${layout.context || "."}) and ${layout.file}: they must be a directory and a file in the workspace that the job can read` };
52933
+ };
52934
+ for (const cmd of [
52935
+ ...d.stepUser ? [["chown", `${d.stepUser.uid}:${d.stepUser.gid}`, staging], ["chmod", "0755", staging]] : [],
52936
+ [...asStep, "sh", "-c", stage],
52937
+ ...owner === null ? [] : [["chown", `${owner}:${owner}`, dir]],
52938
+ // No trailing "/.": a top directory swapped for a link is copied as that link, and refused below.
52939
+ [...asOwner, "cp", "-a", "--", `${staging}/context`, `${staging}/dockerfile`, `${dir}/`],
52940
+ ["sh", "-c", 'for p in "$1/context" "$1/dockerfile"; do [ -d "$p" ] && [ ! -L "$p" ] || exit 1; done', "sh", dir],
52941
+ ["rm", "-rf", staging]
52942
+ ]) {
52943
+ const done2 = await d.run(cmd, { env: d.env, timeoutMs: 10 * 6e4 });
52944
+ if (done2 === null) return null;
52945
+ if (done2 !== 0) return fail();
52946
+ }
52947
+ return dir;
52948
+ }
52949
+ function buildScopes(i, host, repository = "", dockerfile = "") {
52950
+ const name = (ref) => ref.slice(host.length + 1).replace(/@.*$/, "").replace(/:[^/]*$/, "");
52951
+ const want = /* @__PURE__ */ new Map();
52952
+ const add = (ref, actions) => {
52953
+ const n = name(ref);
52954
+ want.set(n, /* @__PURE__ */ new Set([...want.get(n) ?? [], ...actions]));
52955
+ };
52956
+ if (i.push) for (const t of i.tags) add(t, ["pull", "push"]);
52957
+ for (const c8 of i.cacheTo) add(/(?:^|,)ref=([^,]+)/.exec(c8)[1], ["pull", "push"]);
52958
+ for (const c8 of i.cacheFrom) add(/(?:^|,)ref=([^,]+)/.exec(c8)[1], ["pull"]);
52959
+ if (repository && dockerfile) {
52960
+ for (const ref of dockerfileImages(dockerfile)) if (ownImage(ref, host, repository)) add(ref, ["pull"]);
52961
+ }
52962
+ return [...want].map(([n, a]) => `repository:${n}:${[...a].sort().join(",")}`);
52963
+ }
52964
+ function dockerfileImages(dockerfile) {
52965
+ const out = [];
52966
+ const all = dockerfile.replace(/\r\n/g, "\n").split("\n");
52967
+ let escape = "\\";
52968
+ for (const l of all) {
52969
+ const d = /^\s*#\s*(syntax|escape|check)\s*=\s*(\S+)\s*$/i.exec(l);
52970
+ if (!d) break;
52971
+ if (d[1].toLowerCase() === "escape" && (d[2] === "`" || d[2] === "\\")) escape = d[2];
52972
+ }
52973
+ const uncommented = all.filter((l) => !l.trim().startsWith("#")).join("\n");
52974
+ const lines2 = uncommented.split(`${escape}
52975
+ `).join(" ").split("\n");
52976
+ const value = String.raw`("[^"]*"|'[^']*'|[^\s,]+)`;
52977
+ const unquote = (v) => /^(["']).*\1$/.test(v) ? v.slice(1, -1) : v;
52978
+ for (const raw of lines2) {
52979
+ const line2 = raw.trim();
52980
+ if (!line2) continue;
52981
+ const from = /^FROM\s+(?:--platform=\S+\s+)?(\S+)/i.exec(line2);
52982
+ if (from) out.push(from[1]);
52983
+ if (/^(COPY|ADD)\s/i.test(line2)) for (const m of line2.matchAll(new RegExp(`--from=${value}`, "g"))) out.push(unquote(m[1]));
52984
+ if (/^RUN\s/i.test(line2)) for (const m of line2.matchAll(new RegExp(String.raw`--mount=\S*?\bfrom=${value}`, "g"))) out.push(unquote(m[1]));
52985
+ }
52986
+ return out;
52987
+ }
52988
+ var OCI_PATH = /^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*$/;
52989
+ function ownImage(ref, host, repository) {
52990
+ const m = /^([^/]+)\/([^:@]+)(?::[\w][\w.-]{0,127})?(?:@sha256:[a-f0-9]{64})?$/.exec(ref);
52991
+ if (!m || m[1] !== host || !OCI_PATH.test(m[2])) return false;
52992
+ const own2 = repository.toLowerCase();
52993
+ return m[2] === own2 || m[2].startsWith(`${own2}/`);
52994
+ }
52995
+ var BUILD_TOKEN_SECONDS = 21600;
52996
+ async function mintBuildToken(d, host, scopes) {
52997
+ const url2 = new URL(`${new URL(d.baseUrl).protocol}//${host}/v2/token`);
52998
+ url2.searchParams.set("service", host);
52999
+ for (const s of scopes) url2.searchParams.append("scope", s);
53000
+ url2.searchParams.set("expires_in", String(BUILD_TOKEN_SECONDS));
53001
+ try {
53002
+ const res = await d.fetch(url2.toString(), { headers: { authorization: `Bearer ${d.jobToken}` } });
53003
+ const body = await res.json().catch(() => null);
53004
+ if (res.ok && typeof body?.token === "string" && body.token) return body.token;
53005
+ return { error: `the registry did not issue a token for this build (HTTP ${res.status})` };
53006
+ } catch (e) {
53007
+ return { error: `could not reach the registry for a token: ${e.message}` };
53008
+ }
53009
+ }
53010
+ function dockerBuilder(d, auth, context) {
53011
+ const name = `brnch-buildkit-${d.jobId.replace(/[^a-z0-9]/gi, "").toLowerCase()}`;
53012
+ const docker = (a, timeoutMs = 10 * 6e4, env = d.env) => d.run(["docker", ...a], { env, timeoutMs });
53013
+ return {
53014
+ metadataFile: "/tmp/brnch-metadata.json",
53015
+ contextRoot: "/workspace",
53016
+ async start() {
53017
+ d.log(`##[group]Start BuildKit (${BUILDKIT_IMAGE.split("@")[0]})`);
53018
+ try {
53019
+ const started = await docker(["run", "-d", "--rm", "--privileged", "--name", name, "-v", `${context}:/workspace:ro`, "-v", `${auth}:/root/.docker:ro`, BUILDKIT_IMAGE]);
53020
+ if (started === null) return null;
53021
+ if (started !== 0) return "could not start the BuildKit container";
53022
+ return await waitReady(() => d.capture(["docker", "exec", name, "buildctl", "debug", "workers"], { env: d.env })) ? "" : "BuildKit did not become ready within 30 seconds";
53023
+ } finally {
53024
+ d.log("##[endgroup]");
53025
+ }
53026
+ },
53027
+ buildctl: (args) => docker(["exec", name, "buildctl", ...args], null),
53028
+ readMetadata: async () => (await d.capture(["docker", "exec", name, "cat", "/tmp/brnch-metadata.json"], { env: d.env })).stdout,
53029
+ sign: (args, env) => docker(["run", "--rm", "--user", "0", "-v", `${auth}:/root/.docker:ro`, "-e", "DOCKER_CONFIG=/root/.docker", "-e", "COSIGN_KEY", "-e", "COSIGN_PASSWORD", COSIGN_IMAGE, ...args], 5 * 6e4, { ...d.env, ...env }),
53030
+ stop: async () => {
53031
+ await d.capture(["docker", "rm", "-f", name], { env: d.env });
53032
+ }
53033
+ };
53034
+ }
53035
+ function hostedBuilder(d, auth, signing, context) {
53036
+ const tools = `${HOSTED_DIRS.tools}/build-image`;
53037
+ const bin = `${tools}/buildkit/bin`;
53038
+ const sock = `unix://${HOSTED_DIRS.client}/buildkitd.sock`;
53039
+ const asBuilder = (cmd) => ["setpriv", `--reuid=${BUILD_UID}`, `--regid=${BUILD_UID}`, "--clear-groups", "--", ...cmd];
53040
+ const buildctl = `${HOSTED_DIRS.client}/buildctl`;
53041
+ const clientEnv = { ...d.env, DOCKER_CONFIG: auth, HOME: HOSTED_DIRS.client };
53042
+ const run3 = (cmd, timeoutMs = 10 * 6e4, env = d.env) => d.run(cmd, { env, timeoutMs });
53043
+ const fetchChecked = async (url2, sha2562, out) => {
53044
+ const got = await run3(["curl", "-fsSL", "--retry", "3", "-o", out, url2], 5 * 6e4);
53045
+ if (got === null) return null;
53046
+ if (got !== 0) return `could not download ${url2}`;
53047
+ const sum = (await d.capture(["sha256sum", out], { env: d.env })).stdout.split(/\s/)[0];
53048
+ return sum === sha2562 ? "" : `${url2} has sha256 ${sum || "(unreadable)"}, not the pinned ${sha2562}`;
53049
+ };
53050
+ return {
53051
+ metadataFile: `${HOSTED_DIRS.client}/metadata.json`,
53052
+ contextRoot: context,
53053
+ async start() {
53054
+ d.log(`##[group]Start BuildKit (${BUILDKIT_RELEASE.url.split("/").at(-1)}, beside the job)`);
53055
+ try {
53056
+ for (const dir of [HOSTED_DIRS.tools, tools, `${tools}/buildkit`, HOSTED_DIRS.state, HOSTED_DIRS.run]) {
53057
+ const made = await run3(["install", "-d", "-m", "0700", dir]);
53058
+ if (made !== 0) return made === null ? null : `could not create ${dir}`;
53059
+ }
53060
+ for (const cmd of [["install", "-d", "-m", "0700", "-o", String(BUILD_UID), "-g", String(BUILD_UID), HOSTED_DIRS.client], ["chown", "-R", `${BUILD_UID}:${BUILD_UID}`, auth]]) {
53061
+ const done2 = await run3(cmd);
53062
+ if (done2 !== 0) return done2 === null ? null : `could not prepare the build user (${cmd[0]})`;
53063
+ }
53064
+ const tgz = `${tools}/buildkit.tar.gz`;
53065
+ const got = await fetchChecked(BUILDKIT_RELEASE.url, BUILDKIT_RELEASE.sha256, tgz);
53066
+ if (got !== "") return got;
53067
+ const unpacked = await run3(["tar", "-xzf", tgz, "-C", `${tools}/buildkit`]);
53068
+ if (unpacked !== 0) return unpacked === null ? null : "could not unpack BuildKit";
53069
+ const client = await run3(["install", "-m", "0755", `${bin}/buildctl`, buildctl]);
53070
+ if (client !== 0) return client === null ? null : "could not install buildctl for the build user";
53071
+ if (signing) {
53072
+ const cosign = await fetchChecked(COSIGN_RELEASE.url, COSIGN_RELEASE.sha256, `${tools}/cosign`);
53073
+ if (cosign !== "") return cosign;
53074
+ if (await run3(["chmod", "0700", `${tools}/cosign`]) !== 0) return "could not install cosign";
53075
+ }
53076
+ const daemon = `${bin}/buildkitd --root ${HOSTED_DIRS.state} --addr ${sock} --group ${BUILD_UID} --oci-worker-snapshotter=overlayfs --containerd-worker=false`;
53077
+ const started = await run3(["sh", "-c", `PATH=${bin}:$PATH nohup ${daemon} > ${HOSTED_DIRS.run}/buildkitd.log 2>&1 & echo $! > ${HOSTED_DIRS.run}/buildkitd.pid`]);
53078
+ if (started !== 0) return started === null ? null : "could not start buildkitd";
53079
+ if (await waitReady(() => d.capture(asBuilder([buildctl, "--addr", sock, "debug", "workers"]), { env: clientEnv }))) return "";
53080
+ d.log((await d.capture(["tail", "-20", `${HOSTED_DIRS.run}/buildkitd.log`], { env: d.env })).stdout);
53081
+ return "BuildKit did not become ready within 30 seconds";
53082
+ } finally {
53083
+ d.log("##[endgroup]");
53084
+ }
53085
+ },
53086
+ // buildctl reads registry credentials from DOCKER_CONFIG on the client side and hands them to buildkitd's session.
53087
+ buildctl: (args) => run3(asBuilder([buildctl, "--addr", sock, ...args]), null, clientEnv),
53088
+ readMetadata: async () => (await d.capture(["cat", `${HOSTED_DIRS.client}/metadata.json`], { env: d.env })).stdout,
53089
+ sign: (args, env) => run3([`${tools}/cosign`, ...args], 5 * 6e4, { ...d.env, ...env, DOCKER_CONFIG: auth, HOME: auth }),
53090
+ stop: async () => {
53091
+ const pid = `${HOSTED_DIRS.run}/buildkitd.pid`;
53092
+ await d.capture(["sh", "-c", `p=$(cat ${pid} 2>/dev/null); if [ -n "$p" ]; then kill $p 2>/dev/null; for i in 1 2 3 4 5 6 7 8 9 10; do kill -0 $p 2>/dev/null || break; sleep 1; done; kill -9 $p 2>/dev/null; fi; rm -rf ${HOSTED_DIRS.client} ${pid}`], { env: d.env });
53093
+ }
53094
+ };
53095
+ }
53096
+ async function waitReady(probe) {
53097
+ for (let i = 0; i < 30; i++) {
53098
+ if ((await probe()).code === 0) return true;
53099
+ await new Promise((r) => setTimeout(r, 1e3));
53100
+ }
53101
+ return false;
53102
+ }
52478
53103
  async function runBuildImage(w, d) {
52479
53104
  const fail = (msg) => {
52480
53105
  d.log(`##[error]brnch/build-image@v1: ${msg}`);
@@ -52486,41 +53111,46 @@ async function runBuildImage(w, d) {
52486
53111
  const bad = checkRefs(parsed, host, d.repository);
52487
53112
  if (bad) return fail(bad);
52488
53113
  if (parsed.push && !parsed.tags.length) return fail("tags are required when pushing (or set push: false)");
52489
- if (d.hosted) {
52490
- return fail("hosted runners cannot build images yet: BuildKit inside Cloudflare Containers is waiting on a capability probe (ADR-0083 \xA76). Run this job on a self-hosted runner with Docker (runs-on: [self-hosted, \u2026]).");
52491
- }
52492
- if ((await d.capture(["docker", "info", "--format", "{{.ServerVersion}}"], { env: d.env })).code !== 0) {
53114
+ if (d.hosted && parsed.platforms.some((p) => p !== "linux/amd64")) return fail("hosted runners are x86_64 and build linux/amd64 only; build linux/arm64 on a self-hosted runner that can");
53115
+ if (!d.hosted && (await d.capture(["docker", "info", "--format", "{{.ServerVersion}}"], { env: d.env })).code !== 0) {
52493
53116
  return fail("this runner has no usable Docker (docker info failed); install Docker or run the job on a runner that has it");
52494
53117
  }
52495
- const args = buildctlArgs(parsed, "/tmp/brnch-metadata.json");
52496
- if (typeof args === "string") return fail(args);
52497
- const auth = `${d.temp}/build-image-${d.jobId}`;
52498
- d.writePrivate(`${auth}/config.json`, JSON.stringify({ auths: { [host]: { auth: btoa(`brnch:${d.jobToken}`) } } }));
52499
- const name = `brnch-buildkit-${d.jobId.replace(/[^a-z0-9]/gi, "").toLowerCase()}`;
52500
- const docker = (a, timeoutMs = 10 * 6e4, env = d.env) => d.run(["docker", ...a], { env, timeoutMs });
53118
+ const layout = buildLayout(parsed);
53119
+ if (typeof layout === "string") return fail(layout);
53120
+ const context = await copyContext(d, layout, d.hosted ? BUILD_UID : d.stepUser ? 0 : null);
53121
+ if (context === null) return { outcome: "cancelled", outputs: {} };
53122
+ if (typeof context !== "string") return fail(context.error);
53123
+ const auth = d.hosted ? `${HOSTED_DIRS.client}/docker` : `${d.temp}/build-image-${d.jobId}`;
53124
+ const builder = d.hosted ? hostedBuilder(d, auth, !!parsed.cosignKey && parsed.push, context) : dockerBuilder(d, auth, context);
53125
+ const args = buildctlArgs(parsed, layout, builder.metadataFile, builder.contextRoot);
53126
+ const dockerfile = (await d.capture(["cat", "--", `${context}/dockerfile/${layout.filename}`], { env: d.env })).stdout;
53127
+ const scopes = buildScopes(parsed, host, d.repository, dockerfile);
53128
+ let credential = null;
53129
+ if (scopes.length) {
53130
+ const minted = await mintBuildToken(d, host, scopes);
53131
+ if (typeof minted !== "string") {
53132
+ await d.run(["rm", "-rf", context], { env: d.env, timeoutMs: 6e4 });
53133
+ return fail(minted.error);
53134
+ }
53135
+ credential = minted;
53136
+ }
53137
+ d.writePrivate(`${auth}/config.json`, JSON.stringify({ auths: credential ? { [host]: { auth: btoa(`brnch:${credential}`) } } : {} }));
52501
53138
  try {
52502
- d.log(`##[group]Start BuildKit (${BUILDKIT_IMAGE.split("@")[0]})`);
52503
- const started = await docker(["run", "-d", "--rm", "--privileged", "--name", name, "-v", `${d.workspace}:/workspace:ro`, "-v", `${auth}:/root/.docker:ro`, BUILDKIT_IMAGE]);
52504
- if (started !== 0) return started === null ? { outcome: "cancelled", outputs: {} } : fail("could not start the BuildKit container");
52505
- let ready = false;
52506
- for (let i = 0; i < 30 && !ready; i++) {
52507
- ready = (await d.capture(["docker", "exec", name, "buildctl", "debug", "workers"], { env: d.env })).code === 0;
52508
- if (!ready) await new Promise((r) => setTimeout(r, 1e3));
52509
- }
52510
- d.log("##[endgroup]");
52511
- if (!ready) return fail("BuildKit did not become ready within 30 seconds");
53139
+ const started = await builder.start();
53140
+ if (started === null) return { outcome: "cancelled", outputs: {} };
53141
+ if (started) return fail(started);
52512
53142
  d.log(`##[group]Build ${parsed.tags.join(", ") || "(no tags)"} for ${parsed.platforms.join(", ")}`);
52513
- const built = await docker(["exec", name, "buildctl", ...args], null);
53143
+ const built = await builder.buildctl(args);
52514
53144
  d.log("##[endgroup]");
52515
53145
  if (built === null) return { outcome: "cancelled", outputs: {} };
52516
53146
  if (built !== 0) return fail(`the build failed (buildctl exited ${built})`);
52517
- const meta3 = await d.capture(["docker", "exec", name, "cat", "/tmp/brnch-metadata.json"], { env: d.env });
53147
+ const meta3 = await builder.readMetadata();
52518
53148
  let digest = "";
52519
53149
  try {
52520
- digest = String(JSON.parse(meta3.stdout)["containerimage.digest"] ?? "");
53150
+ digest = String(JSON.parse(meta3)["containerimage.digest"] ?? "");
52521
53151
  } catch {
52522
53152
  }
52523
- const outputs = { metadata: meta3.stdout.slice(0, MAX_METADATA_OUTPUT) };
53153
+ const outputs = { metadata: meta3.slice(0, MAX_METADATA_OUTPUT) };
52524
53154
  if (parsed.push) {
52525
53155
  if (!/^sha256:[a-f0-9]{64}$/.test(digest)) return fail("the build pushed, but BuildKit reported no image digest");
52526
53156
  const first2 = parsed.tags[0].replace(/[:@][^/]*$/, "");
@@ -52529,9 +53159,9 @@ async function runBuildImage(w, d) {
52529
53159
  d.log(`pushed ${outputs.image}`);
52530
53160
  if (parsed.cosignKey) {
52531
53161
  d.log(`##[group]Sign ${outputs.image} with cosign`);
52532
- const signArgs = ["run", "--rm", "--user", "0", "-v", `${auth}:/root/.docker:ro`, "-e", "DOCKER_CONFIG=/root/.docker", "-e", "COSIGN_KEY", "-e", "COSIGN_PASSWORD", COSIGN_IMAGE, "sign", "--key", "env://COSIGN_KEY", "--yes"];
53162
+ const signArgs = ["sign", "--key", "env://COSIGN_KEY", "--yes"];
52533
53163
  if (!parsed.cosignTlog) signArgs.push("--use-signing-config=false", "--tlog-upload=false");
52534
- const signed = await docker([...signArgs, outputs.image], 5 * 6e4, { ...d.env, COSIGN_KEY: parsed.cosignKey, COSIGN_PASSWORD: parsed.cosignPassword ?? "" });
53164
+ const signed = await builder.sign([...signArgs, outputs.image], { COSIGN_KEY: parsed.cosignKey, COSIGN_PASSWORD: parsed.cosignPassword ?? "" });
52535
53165
  d.log("##[endgroup]");
52536
53166
  if (signed === null) return { outcome: "cancelled", outputs };
52537
53167
  if (signed !== 0) return fail(`cosign could not sign ${outputs.image} (exit ${signed})`);
@@ -52542,8 +53172,9 @@ async function runBuildImage(w, d) {
52542
53172
  }
52543
53173
  return { outcome: "success", outputs };
52544
53174
  } finally {
52545
- await d.capture(["docker", "rm", "-f", name], { env: d.env });
53175
+ await builder.stop();
52546
53176
  d.removeDir(auth);
53177
+ await d.run(["rm", "-rf", context], { env: d.env, timeoutMs: 6e4 });
52547
53178
  }
52548
53179
  }
52549
53180
 
@@ -53218,6 +53849,14 @@ async function runCodeReviewStep(spec, host) {
53218
53849
  return ok && parsed.status !== "failed" ? "success" : "failure";
53219
53850
  }
53220
53851
 
53852
+ // packages/cli/src/runner/checkout-retry.ts
53853
+ var CHECKOUT_RETRY_DELAYS_MS = [15e3, 3e4, 6e4, 12e4, 24e4];
53854
+ var RPC_FAILED = /\bRPC failed; (?:HTTP (?:429|5\d\d)\b|curl (?:6|7|16|18|28|35|52|55|56|92)\b)/;
53855
+ var UNABLE_TO_ACCESS = /\bunable to access '[^']*': (?:The requested URL returned error: (?:429|5\d\d)\b|Failed to connect|Could not resolve host|Connection timed out|Operation timed out|Recv failure|Send failure|Empty reply from server|OpenSSL SSL_)/;
53856
+ function transientGitFailure(line2) {
53857
+ return RPC_FAILED.test(line2) || UNABLE_TO_ACCESS.test(line2);
53858
+ }
53859
+
53221
53860
  // packages/cli/src/runner/executor.ts
53222
53861
  var NODE_OS = { linux: "Linux", darwin: "macOS", win32: "Windows" };
53223
53862
  var NODE_ARCH = { x64: "X64", arm64: "ARM64", arm: "ARM" };
@@ -53467,6 +54106,12 @@ var JobExecution = class {
53467
54106
  }
53468
54107
  spawnSync2("rm", ["-rf", "--", gone], { uid: this.owner.uid, gid: this.owner.gid, stdio: "ignore" });
53469
54108
  }
54109
+ /** Wait `ms`, cut short when the job is cancelled: whether the job may go on. */
54110
+ async pause(ms) {
54111
+ const until = Date.now() + ms;
54112
+ while (!this.cancelled && Date.now() < until) await new Promise((r) => setTimeout(r, Math.min(1e3, until - Date.now())));
54113
+ return !this.cancelled;
54114
+ }
53470
54115
  /** Run one process with streamed, masked output. Resolves to its exit code, or null if it was killed. */
53471
54116
  exec(cmd, o) {
53472
54117
  return new Promise((resolveExit) => {
@@ -53744,18 +54389,41 @@ var JobExecution = class {
53744
54389
  }
53745
54390
  const depth = w["fetch-depth"] === void 0 ? 1 : Number(w["fetch-depth"]);
53746
54391
  const env = this.gitEnv();
53747
- const git4 = (args) => this.exec(["git", ...args], { cwd: this.workspace, env, asStepUser: false, timeoutMs: 30 * 6e4 });
54392
+ let transient = false;
54393
+ const onLine = (l) => {
54394
+ if (transientGitFailure(l)) transient = true;
54395
+ return false;
54396
+ };
54397
+ const git4 = (args) => {
54398
+ transient = false;
54399
+ return this.exec(["git", ...args], { cwd: this.workspace, env, asStepUser: false, timeoutMs: 30 * 6e4, onLine });
54400
+ };
54401
+ const fetchCommit = async (args) => {
54402
+ for (let attempt = 0; ; attempt++) {
54403
+ const code2 = await git4(["fetch", ...args]);
54404
+ const delay = CHECKOUT_RETRY_DELAYS_MS[attempt];
54405
+ if (code2 === 0 || code2 === null || !transient) return code2;
54406
+ if (delay === void 0 || Date.now() + delay >= this.deadline) {
54407
+ this.log("##[error]the server kept failing the fetch; the repository's storage may be busy. Run the job again in a few minutes");
54408
+ return code2;
54409
+ }
54410
+ this.log(`##[brnch] the server could not answer the fetch; retrying in ${delay / 1e3} s (attempt ${attempt + 2} of ${CHECKOUT_RETRY_DELAYS_MS.length + 1})`);
54411
+ if (!await this.pause(delay)) return null;
54412
+ }
54413
+ };
53748
54414
  this.discard(this.workspace);
53749
54415
  mkdirSync3(this.workspace, { mode: 493 });
53750
54416
  const branch = a.ref.startsWith("refs/heads/") ? a.ref.slice("refs/heads/".length) : null;
53751
54417
  const fetchAndCheckout = async () => {
53752
54418
  for (const args of [["init", "-q"], ["remote", "add", "origin", this.gitUrl()]]) if (await git4(args) !== 0) return 1;
53753
- let code2 = depth > 0 ? await git4(["fetch", "--no-tags", "--prune", `--depth=${depth}`, "origin", a.head_sha]) : 1;
53754
- if (code2 !== 0) {
53755
- if (this.cancelled) return null;
53756
- if (depth > 0) this.log("##[brnch] shallow fetch is not available here; fetching full history of the commit");
53757
- code2 = await git4(["fetch", "--no-tags", "origin", a.head_sha]);
53758
- }
54419
+ let code2;
54420
+ if (depth > 0) {
54421
+ code2 = await fetchCommit(["--no-tags", "--prune", `--depth=${depth}`, "origin", a.head_sha]);
54422
+ if (code2 !== 0 && code2 !== null && !transient) {
54423
+ this.log("##[brnch] the shallow fetch failed; fetching full history of the commit");
54424
+ code2 = await fetchCommit(["--no-tags", "origin", a.head_sha]);
54425
+ }
54426
+ } else code2 = await fetchCommit(["--no-tags", "origin", a.head_sha]);
53759
54427
  if (code2 !== 0) return code2;
53760
54428
  return git4(branch ? ["checkout", "-q", "-B", branch, a.head_sha] : ["checkout", "-q", "--detach", a.head_sha]);
53761
54429
  };
@@ -53800,6 +54468,7 @@ var JobExecution = class {
53800
54468
  async buildImageStep(w, env) {
53801
54469
  const r = await runBuildImage(w, {
53802
54470
  baseUrl: this.opts.baseUrl,
54471
+ fetch: (url2, init) => fetch(url2, init),
53803
54472
  registryHost: this.a.registry_host ?? null,
53804
54473
  repository: this.a.repository.full_name,
53805
54474
  jobToken: this.a.job_token,
@@ -53807,6 +54476,7 @@ var JobExecution = class {
53807
54476
  workspace: this.workspace,
53808
54477
  temp: this.temp,
53809
54478
  hosted: this.opts.hosted,
54479
+ stepUser: this.opts.stepUser ? { uid: this.opts.stepUser.uid, gid: this.opts.stepUser.gid } : null,
53810
54480
  log: (l) => this.log(l),
53811
54481
  run: (cmd, o) => this.exec(cmd, { cwd: this.workspace, env: o.env, asStepUser: false, timeoutMs: o.timeoutMs, onLine: this.onCommandLine }),
53812
54482
  capture: async (cmd, o) => {
@@ -54021,12 +54691,81 @@ function sanitizedEnv(env) {
54021
54691
  return out;
54022
54692
  }
54023
54693
 
54694
+ // packages/cli/src/runner/resources.ts
54695
+ import { statfs } from "node:fs/promises";
54696
+ import { cpus, freemem, totalmem, uptime } from "node:os";
54697
+ function cpuPercent(before, after) {
54698
+ if (!before.length || before.length !== after.length) return null;
54699
+ let idle = 0;
54700
+ let total = 0;
54701
+ for (let i = 0; i < after.length; i++) {
54702
+ for (const key of ["user", "nice", "sys", "idle", "irq"]) {
54703
+ const delta = after[i].times[key] - before[i].times[key];
54704
+ if (delta < 0 || !Number.isFinite(delta)) return null;
54705
+ total += delta;
54706
+ if (key === "idle") idle += delta;
54707
+ }
54708
+ }
54709
+ return total > 0 ? Math.round((1 - idle / total) * 1e3) / 10 : null;
54710
+ }
54711
+ var RunnerResourceCollector = class {
54712
+ constructor(workDir, jobSlots) {
54713
+ this.workDir = workDir;
54714
+ this.jobSlots = jobSlots;
54715
+ }
54716
+ workDir;
54717
+ jobSlots;
54718
+ previous = null;
54719
+ disk = null;
54720
+ diskPending = false;
54721
+ sample() {
54722
+ const at = performance.now();
54723
+ try {
54724
+ this.refreshDisk();
54725
+ const current = cpus();
54726
+ const interval = this.previous ? Math.round(at - this.previous.at) : 0;
54727
+ const cpu = this.previous && interval > 0 && interval <= 6e5 ? cpuPercent(this.previous.cpus, current) : null;
54728
+ this.previous = { cpus: current, at };
54729
+ const disk = this.disk && at - this.disk.at <= 6e4 ? this.disk : null;
54730
+ const totalMemory = totalmem();
54731
+ const parsed = RunnerResourceSnapshot.safeParse({
54732
+ cpu_percent: cpu,
54733
+ cpu_count: current.length,
54734
+ cpu_interval_ms: cpu === null ? null : interval,
54735
+ memory_total_bytes: totalMemory,
54736
+ memory_available_bytes: Math.min(totalMemory, Math.max(0, freemem())),
54737
+ disk_total_bytes: disk?.total ?? null,
54738
+ disk_available_bytes: disk?.available ?? null,
54739
+ uptime_seconds: Math.floor(uptime()),
54740
+ job_slots: this.jobSlots
54741
+ });
54742
+ return parsed.success ? parsed.data : void 0;
54743
+ } catch {
54744
+ return void 0;
54745
+ }
54746
+ }
54747
+ refreshDisk() {
54748
+ if (this.diskPending) return;
54749
+ this.diskPending = true;
54750
+ void statfs(this.workDir).then((s) => {
54751
+ const total = s.blocks * s.bsize;
54752
+ const available = Math.min(total, Math.max(0, s.bavail * s.bsize));
54753
+ this.disk = Number.isSafeInteger(total) && total > 0 && Number.isSafeInteger(available) ? { total, available, at: performance.now() } : null;
54754
+ }).catch(() => {
54755
+ this.disk = null;
54756
+ }).finally(() => {
54757
+ this.diskPending = false;
54758
+ });
54759
+ }
54760
+ };
54761
+
54024
54762
  // packages/cli/src/runner/agent.ts
54025
54763
  var RunnerAgent = class {
54026
54764
  constructor(cfg, opts) {
54027
54765
  this.cfg = cfg;
54028
54766
  this.opts = opts;
54029
54767
  this.client = this.clientFor(cfg);
54768
+ this.resources = new RunnerResourceCollector(opts.workDir, opts.once || cfg.ephemeral ? 1 : opts.jobs);
54030
54769
  }
54031
54770
  cfg;
54032
54771
  opts;
@@ -54038,6 +54777,7 @@ var RunnerAgent = class {
54038
54777
  exit = null;
54039
54778
  heartbeatTimer = null;
54040
54779
  taken = 0;
54780
+ resources;
54041
54781
  clientFor(cfg) {
54042
54782
  return createClient({ baseUrl: cfg.url, token: cfg.credential, userAgent: `${BRAND.cli}-runner/${BRAND.version}`, headers: { "x-brnch-runner-version": BRAND.version }, ...this.opts.fetch ? { fetch: this.opts.fetch } : {} });
54043
54783
  }
@@ -54075,7 +54815,7 @@ var RunnerAgent = class {
54075
54815
  }
54076
54816
  async heartbeat() {
54077
54817
  try {
54078
- const r = await this.client.runner.heartbeat({ running_jobs: [...this.running.keys()] });
54818
+ const r = await this.client.runner.heartbeat({ running_jobs: [...this.running.keys()], resources: this.resources.sample() });
54079
54819
  for (const id2 of r.cancel_jobs) this.running.get(id2)?.exec.cancel("cancelled by the server");
54080
54820
  for (const id2 of r.forget_jobs) this.running.get(id2)?.exec.abort("the server reassigned this job");
54081
54821
  if (r.removed) {
@@ -54122,6 +54862,7 @@ var RunnerAgent = class {
54122
54862
  await this.client.runner.diagnostics(diagnostics(this.cfg)).catch(() => void 0);
54123
54863
  const unhook = this.opts.onSignal?.((s) => this.shutdown(s)) ?? (() => void 0);
54124
54864
  this.heartbeatTimer = setInterval(() => void this.heartbeat(), Math.max(1, me.heartbeat_seconds) * 1e3);
54865
+ await this.heartbeat();
54125
54866
  const single = this.opts.once || this.cfg.ephemeral;
54126
54867
  let backoff = 1e3;
54127
54868
  try {
@@ -55139,6 +55880,13 @@ var EXAMPLES = {
55139
55880
  "deploy run": [`${c7} deploy run production --wait || echo "deploy failed (exit $?)"`, `${c7} deploy run dpt_123 --commit 0123456789abcdef0123456789abcdef01234567`],
55140
55881
  "deploy update": [`${c7} deploy update production --build "npm run build:prod"`, `${c7} deploy update production --disable`],
55141
55882
  "deploy rollback": [`${c7} deploy rollback production --to dpl_123`],
55883
+ "db sources add": [
55884
+ `${c7} db suggest # the migration directories found`,
55885
+ `${c7} db sources add --name app --path db/migrations --format atlas --engine postgres --connection icn_123 --organization acme --database app --revisions-schema app_atlas`,
55886
+ `op read "op://Infra/app-dsn/credential" | ${c7} db sources add --name app \u2026 --credential-mode dsn --dsn-file -`
55887
+ ],
55888
+ "db plan": [`${c7} db plan app`, `${c7} db plan app --ref my-branch --live`],
55889
+ "db approve": [`${c7} db runs --source app`, `${c7} db approve dmr_123 --reason "reviewed the plan"`],
55142
55890
  version: [`${c7} version`, `${c7} version --json`],
55143
55891
  upgrade: [`${c7} upgrade --check`, `${c7} upgrade`, `${c7} upgrade --force`],
55144
55892
  doctor: [`${c7} doctor`, `${c7} doctor --json | jq '.checks[] | select(.status != "ok")'`],
@@ -55279,6 +56027,7 @@ function buildProgram(rt) {
55279
56027
  registerMemory(program2, rt);
55280
56028
  registerIntegrations(program2, rt);
55281
56029
  registerDeploy(program2, rt);
56030
+ registerDb(program2, rt);
55282
56031
  registerDelegate(program2, rt);
55283
56032
  registerAgents(program2, rt);
55284
56033
  registerCodingAgents(program2, rt);
@@ -55353,17 +56102,44 @@ function report2(e, deps, state2) {
55353
56102
  `);
55354
56103
  return exitCode;
55355
56104
  }
55356
- async function run2(argv, overrides = {}) {
56105
+ function agentToolFlag(argv) {
56106
+ for (let i = 0; i < argv.length; i++) {
56107
+ if (argv[i] === "--agent-tool") return argv[i + 1] ?? null;
56108
+ if (argv[i].startsWith("--agent-tool=")) return argv[i].slice("--agent-tool=".length);
56109
+ }
56110
+ return null;
56111
+ }
56112
+ async function run2(argv, overrides = {}, parentStatus) {
55357
56113
  const merged = { ...nodeDeps(), ...overrides };
55358
- const deps = merged;
55359
56114
  const split = splitGitArgs(argv);
55360
56115
  const state2 = { exitCode: EXIT.OK, wroteResult: false, secrets: [], json: wantsJson(split.argv) };
56116
+ state2.status = parentStatus ?? (programStatusEnabled(merged, detectAgent(merged.env, agentToolFlag(split.argv))) ? statusReporter(merged.stderr) : silentStatus);
56117
+ const status = state2.status;
56118
+ const deps = {
56119
+ ...merged,
56120
+ prompt: async (q) => {
56121
+ const answered2 = status.ask();
56122
+ try {
56123
+ return await merged.prompt(q);
56124
+ } finally {
56125
+ answered2();
56126
+ }
56127
+ },
56128
+ keys: merged.keys ? (fn2) => {
56129
+ const answered2 = status.ask();
56130
+ const stop = merged.keys(fn2);
56131
+ return () => {
56132
+ stop();
56133
+ answered2();
56134
+ };
56135
+ } : null
56136
+ };
55361
56137
  const invoke2 = async (sub, o = {}) => {
55362
56138
  const out = [];
55363
56139
  const err = [];
55364
56140
  const errSink = (s) => void err.push(s);
55365
- const quiet = o.quiet ? { stderr: { write: errSink }, stderrIsTTY: false, spawnGit: (args, g2) => deps.spawnGit(args, { ...g2, onOutput: errSink }) } : {};
55366
- const code2 = await run2(sub, { ...deps, ...o.cwd ? { cwd: o.cwd } : {}, ...o.capture ? { stdout: { write: (s) => out.push(s) } } : {}, ...quiet });
56141
+ const quiet = o.quiet ? { stderr: { write: errSink }, stderrIsTTY: false, spawnGit: (args, g2) => merged.spawnGit(args, { ...g2, onOutput: errSink }) } : {};
56142
+ const code2 = await run2(sub, { ...merged, ...o.cwd ? { cwd: o.cwd } : {}, ...o.capture ? { stdout: { write: (s) => out.push(s) } } : {}, ...quiet }, status);
55367
56143
  return { code: code2, stdout: out.join(""), stderr: err.join("") };
55368
56144
  };
55369
56145
  const program2 = buildProgram({ deps, state: state2, gitArgs: split.gitArgs, invoke: invoke2 });
@@ -55378,6 +56154,11 @@ async function run2(argv, overrides = {}) {
55378
56154
  await sendCrashReport(r, { baseUrl: state2.baseUrl ?? crashServer(deps.env), fetch: deps.sys.fetch, stderr: deps.stderr });
55379
56155
  }
55380
56156
  }
56157
+ if (!parentStatus) {
56158
+ const name = commandPath(program2, split.argv);
56159
+ const what = name ? `${BRAND.cli} ${name}` : BRAND.cli;
56160
+ status.finish(code === EXIT.OK, code === EXIT.OK ? `${what} finished` : `${what} failed (exit ${code})`);
56161
+ }
55381
56162
  await saveBookmark(deps, state2);
55382
56163
  await updateHint(deps, state2);
55383
56164
  return code;