brnch-cli 0.18.1 → 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.
- package/README.md +1 -1
- package/brnch.mjs +943 -114
- package/package.json +3 -2
package/brnch.mjs
CHANGED
|
@@ -6044,9 +6044,9 @@ var require_lexer = __commonJS({
|
|
|
6044
6044
|
}
|
|
6045
6045
|
}
|
|
6046
6046
|
*parseQuotedScalar() {
|
|
6047
|
-
const
|
|
6048
|
-
let end2 = this.buffer.indexOf(
|
|
6049
|
-
if (
|
|
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((
|
|
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
|
|
21411
|
-
return `Chave${
|
|
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
|
|
21556
|
-
return `Chave${
|
|
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,
|
|
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}${
|
|
31552
|
+
return `${prefix}${quote3}${REDACTED}${quote3}`;
|
|
31552
31553
|
});
|
|
31553
|
-
text2 = text2.replace(FLAG, (w, prefix,
|
|
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}${
|
|
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
|
|
33661
|
-
|
|
33662
|
-
|
|
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.
|
|
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,
|
|
@@ -42184,6 +42396,30 @@ function input2(deps, o) {
|
|
|
42184
42396
|
});
|
|
42185
42397
|
}
|
|
42186
42398
|
|
|
42399
|
+
// packages/cli/src/step-up.ts
|
|
42400
|
+
function needsCliStepUp(e) {
|
|
42401
|
+
const err = isBrnchApiError(e) ? e : e;
|
|
42402
|
+
return err?.code === "MFA_REQUIRED" && err.details?.step_up === "cli";
|
|
42403
|
+
}
|
|
42404
|
+
function sameOrigin(ctx, start) {
|
|
42405
|
+
try {
|
|
42406
|
+
return new URL(start.verification_uri_complete).origin === new URL(ctx.baseUrl).origin;
|
|
42407
|
+
} catch {
|
|
42408
|
+
return false;
|
|
42409
|
+
}
|
|
42410
|
+
}
|
|
42411
|
+
async function awaitStepUp(ctx, start) {
|
|
42412
|
+
const deadline = Date.parse(start.expires_at);
|
|
42413
|
+
ctx.status.blocked("auth", `Confirm it's you in the browser (code ${start.user_code})`);
|
|
42414
|
+
for (; ; ) {
|
|
42415
|
+
await ctx.deps.sleep(start.interval * 1e3);
|
|
42416
|
+
const s = await ctx.client("user").auth.stepUp.status(start.id);
|
|
42417
|
+
if (s.status === "approved") return s.step_up_expires_at;
|
|
42418
|
+
if (s.status === "denied") throw new CliError("the step-up was denied in the browser", EXIT.UNAUTHENTICATED, "access_denied");
|
|
42419
|
+
if (s.status === "expired" || ctx.deps.sys.now() > deadline + 5e3) throw new CliError(`the code expired before it was approved; run \`${BRAND.cli} auth step-up\` again`, EXIT.UNAUTHENTICATED, "expired_token");
|
|
42420
|
+
}
|
|
42421
|
+
}
|
|
42422
|
+
|
|
42187
42423
|
// packages/cli/src/pending-login.ts
|
|
42188
42424
|
import { mkdir as mkdir11, readFile as readFile9, rm as rm7, writeFile as writeFile10 } from "node:fs/promises";
|
|
42189
42425
|
import { dirname as dirname11, join as join18 } from "node:path";
|
|
@@ -42372,6 +42608,7 @@ var accountChoice = (a) => ({
|
|
|
42372
42608
|
});
|
|
42373
42609
|
async function finishLogin(ctx, start, agentRuntime, hooks) {
|
|
42374
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})`);
|
|
42375
42612
|
let tok;
|
|
42376
42613
|
try {
|
|
42377
42614
|
tok = await pollDeviceToken(oauth, start, CLI_OAUTH_CLIENT_ID, { sleep: ctx.deps.sleep });
|
|
@@ -42455,7 +42692,7 @@ function registerAuth(program2, rt) {
|
|
|
42455
42692
|
`This sign-in is for ${agentSignIn.label}, an AI coding agent (detected from ${agentSignIn.source}). The browser asks you to approve an agent sign-in: it can read and start agent sessions you sponsor, and never acts as you. To sign in as yourself, run this in your own terminal (or pass --as-human).`
|
|
42456
42693
|
);
|
|
42457
42694
|
}
|
|
42458
|
-
const
|
|
42695
|
+
const sameOrigin2 = (() => {
|
|
42459
42696
|
try {
|
|
42460
42697
|
return new URL(start.verification_uri_complete).origin === new URL(ctx.baseUrl).origin;
|
|
42461
42698
|
} catch {
|
|
@@ -42466,7 +42703,7 @@ function registerAuth(program2, rt) {
|
|
|
42466
42703
|
${codeBox(start.user_code, paint(true))}
|
|
42467
42704
|
` : `! Your one-time code: ${start.user_code}`);
|
|
42468
42705
|
let opened = false;
|
|
42469
|
-
if (o.browser && ctx.interactive &&
|
|
42706
|
+
if (o.browser && ctx.interactive && sameOrigin2 && (!canPick(ctx) || await pressEnter(ctx.deps, `Press Enter to open ${new URL(start.verification_uri).host} in your browser`)))
|
|
42470
42707
|
opened = await ctx.deps.openUrl(start.verification_uri_complete);
|
|
42471
42708
|
ctx.info(
|
|
42472
42709
|
opened ? `Opened ${start.verification_uri} in your browser. ${paint(colorEnabled(ctx.deps, "stderr")).dim("Check that it shows the same code, then approve.")}` : `Open ${start.verification_uri_complete}
|
|
@@ -42651,18 +42888,11 @@ Store them somewhere safe and offline (a password manager or on paper).`
|
|
|
42651
42888
|
}
|
|
42652
42889
|
}
|
|
42653
42890
|
const start = await stepUp.start();
|
|
42654
|
-
const sameOrigin = (() => {
|
|
42655
|
-
try {
|
|
42656
|
-
return new URL(start.verification_uri_complete).origin === new URL(ctx.baseUrl).origin;
|
|
42657
|
-
} catch {
|
|
42658
|
-
return false;
|
|
42659
|
-
}
|
|
42660
|
-
})();
|
|
42661
42891
|
ctx.info(ctx.motion ? `
|
|
42662
42892
|
${codeBox(start.user_code, paint(true))}
|
|
42663
42893
|
` : `! Your one-time code: ${start.user_code}`);
|
|
42664
42894
|
let opened = false;
|
|
42665
|
-
if (o.browser && ctx.interactive && sameOrigin && (!canPick(ctx) || await pressEnter(ctx.deps, `Press Enter to open ${new URL(start.verification_uri).host} and confirm it's you`)))
|
|
42895
|
+
if (o.browser && ctx.interactive && sameOrigin(ctx, start) && (!canPick(ctx) || await pressEnter(ctx.deps, `Press Enter to open ${new URL(start.verification_uri).host} and confirm it's you`)))
|
|
42666
42896
|
opened = await ctx.deps.openUrl(start.verification_uri_complete);
|
|
42667
42897
|
ctx.info(
|
|
42668
42898
|
opened ? `Opened ${start.verification_uri} in your browser. Check that it shows the same code, then approve and confirm it's you.` : `Open ${start.verification_uri_complete}
|
|
@@ -42670,20 +42900,12 @@ ${codeBox(start.user_code, paint(true))}
|
|
|
42670
42900
|
);
|
|
42671
42901
|
if (ctx.motion) ctx.busy(`waiting for you to confirm it's you in the browser \xB7 the code expires in ${minutesLeft(start.expires_at, ctx.deps.sys.now())}`);
|
|
42672
42902
|
else ctx.info("Waiting for approval\u2026");
|
|
42673
|
-
const
|
|
42674
|
-
|
|
42675
|
-
|
|
42676
|
-
|
|
42677
|
-
if (s.status === "approved") {
|
|
42678
|
-
return ctx.result(
|
|
42679
|
-
{ status: "approved", step_up_expires_at: s.step_up_expires_at },
|
|
42680
|
-
(v) => `${p.ok("\u2713")} Stepped up until ${v.step_up_expires_at ? `${clockTime(v.step_up_expires_at)}${left(v.step_up_expires_at)}` : "the server's limit"}: administrative actions work from this login until then.
|
|
42903
|
+
const until = await awaitStepUp(ctx, start);
|
|
42904
|
+
return ctx.result(
|
|
42905
|
+
{ status: "approved", step_up_expires_at: until },
|
|
42906
|
+
(v) => `${p.ok("\u2713")} Stepped up until ${v.step_up_expires_at ? `${clockTime(v.step_up_expires_at)}${left(v.step_up_expires_at)}` : "the server's limit"}: administrative actions work from this login until then.
|
|
42681
42907
|
${p.dim(`End it sooner: ${BRAND.cli} auth step-up --end`)}`
|
|
42682
|
-
|
|
42683
|
-
}
|
|
42684
|
-
if (s.status === "denied") throw new CliError("the step-up was denied in the browser", EXIT.UNAUTHENTICATED, "access_denied");
|
|
42685
|
-
if (s.status === "expired" || Date.now() > deadline + 5e3) throw new CliError(`the code expired before it was approved; run \`${BRAND.cli} auth step-up\` again`, EXIT.UNAUTHENTICATED, "expired_token");
|
|
42686
|
-
}
|
|
42908
|
+
);
|
|
42687
42909
|
})
|
|
42688
42910
|
);
|
|
42689
42911
|
auth.command("git-credential").description("git credential helper: answers `get` for the configured server with your token (store/erase are no-ops)").argument("<operation>", "get | store | erase (invoked by git)").action(
|
|
@@ -43459,6 +43681,7 @@ var Wizard = class {
|
|
|
43459
43681
|
/** The spinner says what the step is checking until it prints, asks, or hands over to a composed command. */
|
|
43460
43682
|
busy(step2) {
|
|
43461
43683
|
this.ctx.busy(STEP_BUSY[step2]);
|
|
43684
|
+
this.ctx.status.working(STEP_BUSY[step2]);
|
|
43462
43685
|
}
|
|
43463
43686
|
/** Global flags every composed command must keep (same server, credential and account). */
|
|
43464
43687
|
globalArgs() {
|
|
@@ -44359,6 +44582,7 @@ var Quickstart = class {
|
|
|
44359
44582
|
/** The board row says what is happening now (no-op without a board). */
|
|
44360
44583
|
running(row, text2) {
|
|
44361
44584
|
this.board?.run(row, text2);
|
|
44585
|
+
this.ctx.status.working(text2);
|
|
44362
44586
|
}
|
|
44363
44587
|
/** A step's outcome: kept for the result, and shown on the board (`show`) or as a line. */
|
|
44364
44588
|
record(step2, status, detail, show2 = detail) {
|
|
@@ -44376,6 +44600,41 @@ var Quickstart = class {
|
|
|
44376
44600
|
const g2 = this.ctx.globals;
|
|
44377
44601
|
return [...g2.url ? ["--url", g2.url] : [], ...g2.token ? ["--token", g2.token] : [], ...g2.account ? ["--account", g2.account] : [], ...g2.asHuman ? ["--as-human"] : [], ...g2.agentTool ? ["--agent-tool", g2.agentTool] : []];
|
|
44378
44602
|
}
|
|
44603
|
+
/** Whether this run already asked the person to step up (once: a second refusal is the answer). */
|
|
44604
|
+
steppedUp = false;
|
|
44605
|
+
/**
|
|
44606
|
+
* `run`, and again after the person confirms it's them in the browser when the organization requires a second factor
|
|
44607
|
+
* for it (ADR-0062): quickstart runs long enough that a step-up approved before it may have lapsed by the deploy.
|
|
44608
|
+
* `refusal` finds the error in a composed command's `--json` output.
|
|
44609
|
+
*/
|
|
44610
|
+
async withStepUp(row, run3, refusal = () => null) {
|
|
44611
|
+
try {
|
|
44612
|
+
const v = await run3();
|
|
44613
|
+
if (!needsCliStepUp(refusal(v)) || !await this.stepUp(row)) return v;
|
|
44614
|
+
} catch (e) {
|
|
44615
|
+
if (!needsCliStepUp(e) || !await this.stepUp(row)) throw e;
|
|
44616
|
+
}
|
|
44617
|
+
return run3();
|
|
44618
|
+
}
|
|
44619
|
+
async stepUp(row) {
|
|
44620
|
+
const { ctx } = this;
|
|
44621
|
+
if (!ctx.interactive || this.steppedUp) return false;
|
|
44622
|
+
this.steppedUp = true;
|
|
44623
|
+
const start = await ctx.client("user").auth.stepUp.start();
|
|
44624
|
+
const opened = sameOrigin(ctx, start) && await ctx.deps.openUrl(start.verification_uri_complete);
|
|
44625
|
+
const todo = `Your organization asks for a second factor here: confirm it's you ${opened ? "in the page that just opened" : `at ${start.verification_uri_complete}`} (code ${start.user_code})`;
|
|
44626
|
+
if (this.board) {
|
|
44627
|
+
this.running(row, "waiting for you to confirm it's you in the browser");
|
|
44628
|
+
this.board.hint(`${todo} ${this.errPaint.dim("(Ctrl-C stops; the repository is ready either way)")}`);
|
|
44629
|
+
} else ctx.info(` ${todo}. Waiting for it\u2026`);
|
|
44630
|
+
try {
|
|
44631
|
+
await awaitStepUp(ctx, start);
|
|
44632
|
+
} finally {
|
|
44633
|
+
this.board?.hint(null);
|
|
44634
|
+
}
|
|
44635
|
+
this.note(row, "confirmed: continuing");
|
|
44636
|
+
return true;
|
|
44637
|
+
}
|
|
44379
44638
|
/** A composed command, in `cwd`, as JSON. Under the board it runs quietly (its stderr and git's are returned instead). */
|
|
44380
44639
|
async runJson(argv, cwd) {
|
|
44381
44640
|
this.ctx.idle();
|
|
@@ -44625,7 +44884,7 @@ async function stepDeploy(q, dir, ns, name, o, home2) {
|
|
|
44625
44884
|
const later = `connect Cloudflare at ${integrationsUrl(ctx, ns)}, then \`${BRAND.cli} --repo ${spec} deploy setup\` and \`${BRAND.cli} --repo ${spec} deploy run production --wait\``;
|
|
44626
44885
|
q.running("deploy", "looking for a Cloudflare connection");
|
|
44627
44886
|
const repo = await ctx.repoId(spec, "user");
|
|
44628
|
-
const usable = async () => (await ctx.client("user").deploy.connections(repo)).items.some((c8) => c8.state === "active");
|
|
44887
|
+
const usable = () => q.withStepUp("deploy", async () => (await ctx.client("user").deploy.connections(repo)).items.some((c8) => c8.state === "active"));
|
|
44629
44888
|
if (!await usable()) {
|
|
44630
44889
|
if (!ctx.interactive) {
|
|
44631
44890
|
q.record("deploy", "planned", later, `connect Cloudflare, then \`${BRAND.cli} deploy setup\``);
|
|
@@ -44638,6 +44897,7 @@ async function stepDeploy(q, dir, ns, name, o, home2) {
|
|
|
44638
44897
|
q.running("deploy", "waiting for Cloudflare to be connected");
|
|
44639
44898
|
q.board.hint(`${todo} ${q.errPaint.dim("(Ctrl-C stops; the repository is ready either way)")}`);
|
|
44640
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");
|
|
44641
44901
|
const deadline2 = ctx.deps.sys.now() + o.connectTimeout * 1e3;
|
|
44642
44902
|
let ready = false;
|
|
44643
44903
|
while (!ready && ctx.deps.sys.now() < deadline2) {
|
|
@@ -44652,7 +44912,11 @@ async function stepDeploy(q, dir, ns, name, o, home2) {
|
|
|
44652
44912
|
}
|
|
44653
44913
|
q.running("deploy", "creating the deploy target");
|
|
44654
44914
|
const pinned = home2.cloudflare ? ["--connection", home2.cloudflare.connection, "--account-id", home2.cloudflare.account] : [];
|
|
44655
|
-
const setup = await q.
|
|
44915
|
+
const setup = await q.withStepUp(
|
|
44916
|
+
"deploy",
|
|
44917
|
+
() => q.runJson(["--repo", spec, "deploy", "setup", ...pinned], dir),
|
|
44918
|
+
(r) => r.value?.error
|
|
44919
|
+
);
|
|
44656
44920
|
if (setup.code === EXIT.USAGE) {
|
|
44657
44921
|
q.record("deploy", "planned", `choose what deploys it, then \`${BRAND.cli} --repo ${spec} deploy setup --connection <icn_\u2026> [--account-id <id>]\` and \`${BRAND.cli} --repo ${spec} deploy run production --wait\`${setup.reason}`, `choose a Cloudflare connection: \`${BRAND.cli} deploy setup --connection \u2026\``);
|
|
44658
44922
|
return;
|
|
@@ -44662,7 +44926,8 @@ async function stepDeploy(q, dir, ns, name, o, home2) {
|
|
|
44662
44926
|
return;
|
|
44663
44927
|
}
|
|
44664
44928
|
const deploy = ctx.client("user").deploy;
|
|
44665
|
-
|
|
44929
|
+
const target = setup.value.id;
|
|
44930
|
+
let d = await q.withStepUp("deploy", () => deploy.targets.deploy(target, {}));
|
|
44666
44931
|
const deadline = ctx.deps.sys.now() + o.deployTimeout * 1e3;
|
|
44667
44932
|
let last = "";
|
|
44668
44933
|
for (; ; ) {
|
|
@@ -46237,6 +46502,7 @@ async function waitDone(ctx, change, interval, timeout) {
|
|
|
46237
46502
|
const now = f.reviews.map((r) => `${CODE_REVIEW_PROVIDER_NAMES[r.provider]}: ${r.status}`).join(", ");
|
|
46238
46503
|
if (now && now !== said) ctx.info(now);
|
|
46239
46504
|
said = now;
|
|
46505
|
+
ctx.status.working(now ? `AI review: ${now}` : "Waiting for AI review");
|
|
46240
46506
|
if (!f.pending || Date.now() > deadline) return f;
|
|
46241
46507
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
46242
46508
|
}
|
|
@@ -46402,6 +46668,7 @@ async function followLive(ctx, c8, first2, deadline, interval) {
|
|
|
46402
46668
|
const stop = ctx.deps.ticker(250, draw2);
|
|
46403
46669
|
try {
|
|
46404
46670
|
while (!TERMINAL3.has(cur.state) && Date.now() < deadline) {
|
|
46671
|
+
ctx.status.working(`Merge ${cur.state}`);
|
|
46405
46672
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
46406
46673
|
cur = await c8.promotions.get(cur.id);
|
|
46407
46674
|
reached = stepOf(cur.state, reached);
|
|
@@ -46426,6 +46693,7 @@ function registerMerge(program2, rt) {
|
|
|
46426
46693
|
let last = p.state;
|
|
46427
46694
|
ctx.info(`promotion ${p.id}: ${p.state}`);
|
|
46428
46695
|
while (!TERMINAL3.has(p.state) && Date.now() < deadline) {
|
|
46696
|
+
ctx.status.working(`Merge ${p.state}`);
|
|
46429
46697
|
await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
|
|
46430
46698
|
p = await c8.promotions.get(p.id);
|
|
46431
46699
|
if (p.state !== last) ctx.info(`promotion ${p.id}: ${last = p.state}`);
|
|
@@ -46703,6 +46971,7 @@ function registerSecretScanning(program2, rt) {
|
|
|
46703
46971
|
const c8 = ctx.client("user");
|
|
46704
46972
|
let job2 = await c8.secretScanning.scans.start(repo);
|
|
46705
46973
|
while (o.wait && (job2.state === "queued" || job2.state === "running")) {
|
|
46974
|
+
ctx.status.working(`History scan ${job2.state}`);
|
|
46706
46975
|
await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
|
|
46707
46976
|
job2 = await c8.secretScanning.scans.get(repo, job2.id);
|
|
46708
46977
|
}
|
|
@@ -46992,9 +47261,9 @@ async function findItems(client, specs) {
|
|
|
46992
47261
|
}
|
|
46993
47262
|
async function itemIds(client, specs) {
|
|
46994
47263
|
const full = (s) => /^notif_[0-9a-z]{26}$/.test(s);
|
|
46995
|
-
const
|
|
46996
|
-
const items =
|
|
46997
|
-
return specs.map((s) => full(s) ? s : items[
|
|
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);
|
|
46998
47267
|
}
|
|
46999
47268
|
async function repoNames(client, items) {
|
|
47000
47269
|
const ids = [...new Set(items.map((i) => i.repository_id))];
|
|
@@ -47739,6 +48008,187 @@ function registerIntegrations(program2, rt) {
|
|
|
47739
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)));
|
|
47740
48009
|
}
|
|
47741
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
|
+
|
|
47742
48192
|
// packages/cli/src/commands/deploy.ts
|
|
47743
48193
|
var TERMINAL4 = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "superseded", "blocked"]);
|
|
47744
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)";
|
|
@@ -47746,7 +48196,7 @@ var renderTargets = (v) => table(
|
|
|
47746
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)}` : "-" })),
|
|
47747
48197
|
["name", "id", "platform", "resource", "env", "branch", "runs_on", "previews", "state", "last_production"]
|
|
47748
48198
|
);
|
|
47749
|
-
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}` : "-" }, [
|
|
47750
48200
|
"id",
|
|
47751
48201
|
"name",
|
|
47752
48202
|
"platform",
|
|
@@ -47761,6 +48211,7 @@ var renderTarget = (t) => kv({ ...t, connection: t.connection ? `${t.connection.
|
|
|
47761
48211
|
"build_command",
|
|
47762
48212
|
"output_directory",
|
|
47763
48213
|
"runs_on",
|
|
48214
|
+
"watch_paths",
|
|
47764
48215
|
"previews",
|
|
47765
48216
|
"preview_check_name",
|
|
47766
48217
|
"last_production",
|
|
@@ -47797,7 +48248,7 @@ async function stepsOf(ctx, spec) {
|
|
|
47797
48248
|
return v;
|
|
47798
48249
|
}
|
|
47799
48250
|
function targetOptions(cmd) {
|
|
47800
|
-
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"}]'`);
|
|
47801
48252
|
}
|
|
47802
48253
|
async function planSetup(ctx, repo, o) {
|
|
47803
48254
|
const c8 = ctx.client("user").deploy;
|
|
@@ -47872,6 +48323,7 @@ async function planSetup(ctx, repo, o) {
|
|
|
47872
48323
|
...o.previewAgentChanges !== void 0 ? { preview_agent_changes: o.previewAgentChanges } : {},
|
|
47873
48324
|
preview_check_name: o.previewCheck ?? null,
|
|
47874
48325
|
...o.runsOn ? { runs_on: o.runsOn } : {},
|
|
48326
|
+
...o.watchPath ? { watch_paths: o.watchPath } : {},
|
|
47875
48327
|
...o.branch ? { branch: o.branch } : {},
|
|
47876
48328
|
...o.steps !== void 0 ? { steps: await stepsOf(ctx, o.steps) } : {}
|
|
47877
48329
|
};
|
|
@@ -47883,6 +48335,7 @@ async function waitFor(ctx, d, interval, timeout) {
|
|
|
47883
48335
|
for (; ; ) {
|
|
47884
48336
|
if (d.status !== last) ctx.info(`${d.id}: ${d.status}${d.status_reason ? ` (${d.status_reason})` : ""}`);
|
|
47885
48337
|
last = d.status;
|
|
48338
|
+
ctx.status.working(`Deploy ${d.status}`);
|
|
47886
48339
|
if (TERMINAL4.has(d.status) || Date.now() > deadline) return d;
|
|
47887
48340
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
47888
48341
|
d = await ctx.client("user").deploy.deployments.get(d.id);
|
|
@@ -47907,6 +48360,7 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
|
|
|
47907
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(
|
|
47908
48361
|
act(rt, async (ctx, o, [ref]) => {
|
|
47909
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");
|
|
47910
48364
|
const c8 = ctx.client("user").deploy.targets;
|
|
47911
48365
|
const cur = await c8.get(await targetId(ctx, ref));
|
|
47912
48366
|
const patch = {
|
|
@@ -47926,6 +48380,7 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
|
|
|
47926
48380
|
preview_agent_changes: o.previewAgentChanges,
|
|
47927
48381
|
preview_check_name: orNull(o.previewCheck),
|
|
47928
48382
|
runs_on: o.runsOn,
|
|
48383
|
+
watch_paths: o.watchAll ? [] : o.watchPath,
|
|
47929
48384
|
state: o.disable ? "disabled" : o.enable ? "active" : void 0,
|
|
47930
48385
|
steps: await stepsOf(ctx, o.steps)
|
|
47931
48386
|
};
|
|
@@ -47972,7 +48427,7 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
|
|
|
47972
48427
|
var ITERATION_DONE = /* @__PURE__ */ new Set(["succeeded", "no_changes", "failed", "cancelled"]);
|
|
47973
48428
|
var STATES = ["working", "waiting", "stopped"];
|
|
47974
48429
|
var who2 = (a) => a.display_name ?? `${a.type} ${a.id}`;
|
|
47975
|
-
var
|
|
48430
|
+
var short2 = (oid) => oid ? oid.slice(0, 12) : null;
|
|
47976
48431
|
function vendorArg(v) {
|
|
47977
48432
|
if (!CODING_AGENT_VENDORS.includes(v)) throw usage(`unknown agent "${v}"; one of ${CODING_AGENT_VENDORS.join(", ")}`);
|
|
47978
48433
|
return v;
|
|
@@ -47992,7 +48447,7 @@ function renderIterations(d) {
|
|
|
47992
48447
|
...i,
|
|
47993
48448
|
trigger: i.trigger_ref ? `${i.trigger} (${i.trigger_ref})` : i.trigger,
|
|
47994
48449
|
by: who2(i.trigger_actor),
|
|
47995
|
-
head:
|
|
48450
|
+
head: short2(i.head_oid)
|
|
47996
48451
|
}));
|
|
47997
48452
|
const claims = d.iterations.map(reported).filter((x) => !!x);
|
|
47998
48453
|
const errors = d.iterations.filter((i) => i.error).map((i) => ` #${i.number}: ${i.error}`);
|
|
@@ -48055,6 +48510,7 @@ async function waitForIteration(ctx, start, o) {
|
|
|
48055
48510
|
const line2 = `${delegationStatusLabel(d)}${it ? ` (iteration #${it.number}: ${it.state})` : ""}`;
|
|
48056
48511
|
if (line2 !== last) ctx.info(line2);
|
|
48057
48512
|
last = line2;
|
|
48513
|
+
ctx.status.working(`${d.agent.name}: ${line2}`);
|
|
48058
48514
|
if (it ? ITERATION_DONE.has(it.state) : d.state !== "working") return { d, timedOut: false };
|
|
48059
48515
|
if (Date.now() > deadline) return { d, timedOut: true };
|
|
48060
48516
|
await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
|
|
@@ -49006,15 +49462,16 @@ function registerImage(program2, rt) {
|
|
|
49006
49462
|
ctx.result(page, (p) => listWithCursor(p, ["reference", "tags", "versions", "updated_at"]));
|
|
49007
49463
|
})
|
|
49008
49464
|
);
|
|
49009
|
-
image.command("delete <ref>").description("Delete a tag (`<image>:<tag>`)
|
|
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(
|
|
49010
49466
|
act(rt, async (ctx, _o, [spec]) => {
|
|
49011
49467
|
const ref = parseImageRef(spec, await registryHost(ctx));
|
|
49012
|
-
if (!ref
|
|
49468
|
+
if (!ref) throw usage(`invalid image reference "${spec}": expected <namespace>/<repository>[/<image>][:<tag>|@sha256:<digest>]`);
|
|
49013
49469
|
const img = await findImage(ctx, ref.name);
|
|
49014
49470
|
if (ref.digest) await ctx.client().packages.deleteVersion(img.id, ref.digest);
|
|
49015
|
-
else await ctx.client().packages.deleteTag(img.id, ref.tag);
|
|
49016
|
-
|
|
49017
|
-
|
|
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}.`);
|
|
49018
49475
|
})
|
|
49019
49476
|
);
|
|
49020
49477
|
}
|
|
@@ -49260,6 +49717,7 @@ async function waitForPromotion(ctx, p, timeout, interval) {
|
|
|
49260
49717
|
let last = p.state;
|
|
49261
49718
|
ctx.info(`promotion ${p.id}: ${p.state}`);
|
|
49262
49719
|
while (!TERMINAL5.has(p.state) && Date.now() < deadline) {
|
|
49720
|
+
ctx.status.working(`Merge ${p.state}`);
|
|
49263
49721
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
49264
49722
|
p = await ctx.client().promotions.get(p.id);
|
|
49265
49723
|
if (p.state !== last) ctx.info(`promotion ${p.id}: ${last = p.state}`);
|
|
@@ -51633,7 +52091,7 @@ function safeLegs(m) {
|
|
|
51633
52091
|
}
|
|
51634
52092
|
|
|
51635
52093
|
// packages/cli/src/commands/actions.ts
|
|
51636
|
-
var
|
|
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)"}` }, [
|
|
51637
52095
|
"id",
|
|
51638
52096
|
"workflow_name",
|
|
51639
52097
|
"run_number",
|
|
@@ -51698,7 +52156,7 @@ function registerWorkflow(program2, rt) {
|
|
|
51698
52156
|
inputs[kvp.slice(0, i)] = kvp.slice(i + 1);
|
|
51699
52157
|
}
|
|
51700
52158
|
const r = await ctx.client("user").actions.workflows.dispatch(await ctx.repoId(void 0, "user"), file2, { ...o.ref ? { ref: o.ref } : {}, inputs });
|
|
51701
|
-
ctx.result(r,
|
|
52159
|
+
ctx.result(r, renderRun2);
|
|
51702
52160
|
})
|
|
51703
52161
|
);
|
|
51704
52162
|
}
|
|
@@ -51722,6 +52180,8 @@ async function watchRun(ctx, runId, interval, timeout, logs) {
|
|
|
51722
52180
|
${text2.trimEnd()}`);
|
|
51723
52181
|
}
|
|
51724
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);
|
|
51725
52185
|
if (TERMINAL6.has(r.status) || Date.now() > deadline) return r;
|
|
51726
52186
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
51727
52187
|
}
|
|
@@ -51741,13 +52201,13 @@ function registerRun(program2, rt) {
|
|
|
51741
52201
|
const r = await c8.actions.runs.get(id2);
|
|
51742
52202
|
if (o.log) for (const j of r.jobs) ctx.info(`\u2500\u2500 ${j.name} (${j.status}${j.conclusion ? `/${j.conclusion}` : ""}) \u2500\u2500
|
|
51743
52203
|
${(await c8.actions.jobs.logs(j.id)).trimEnd()}`);
|
|
51744
|
-
ctx.result(r,
|
|
52204
|
+
ctx.result(r, renderRun2);
|
|
51745
52205
|
})
|
|
51746
52206
|
);
|
|
51747
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(
|
|
51748
52208
|
act(rt, async (ctx, o, [id2]) => {
|
|
51749
52209
|
const r = await watchRun(ctx, id2, o.interval, o.timeout, !!o.log);
|
|
51750
|
-
ctx.result(r,
|
|
52210
|
+
ctx.result(r, renderRun2);
|
|
51751
52211
|
ctx.setExit(runExit(r));
|
|
51752
52212
|
})
|
|
51753
52213
|
);
|
|
@@ -51772,8 +52232,8 @@ ${(await c8.actions.jobs.logs(j.id)).trimEnd()}`);
|
|
|
51772
52232
|
ctx.result({ job_id: job2, conclusion: status }, `job ${job2}: ${status}`);
|
|
51773
52233
|
})
|
|
51774
52234
|
);
|
|
51775
|
-
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),
|
|
51776
|
-
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 }),
|
|
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)));
|
|
51777
52237
|
}
|
|
51778
52238
|
function registerSecret(program2, rt) {
|
|
51779
52239
|
const secret = program2.command("secret").description("Actions secrets of --repo (default) or the organization (--org-level). Values are write-only");
|
|
@@ -52354,6 +52814,10 @@ ${res.stdout.slice(-8e3)}`;
|
|
|
52354
52814
|
// packages/cli/src/runner/build-image-step.ts
|
|
52355
52815
|
var BUILDKIT_IMAGE = "moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c";
|
|
52356
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;
|
|
52357
52821
|
var MAX_METADATA_OUTPUT = 32 * 1024;
|
|
52358
52822
|
var list2 = (v) => (v ?? "").split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
|
|
52359
52823
|
var lines = (v) => (v ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
@@ -52406,15 +52870,15 @@ function inside2(p) {
|
|
|
52406
52870
|
}
|
|
52407
52871
|
return parts.join("/");
|
|
52408
52872
|
}
|
|
52409
|
-
function
|
|
52410
|
-
const
|
|
52873
|
+
function buildLayout(i) {
|
|
52874
|
+
const context = inside2(i.context);
|
|
52411
52875
|
const file2 = inside2(i.file ?? `${i.context}/Dockerfile`);
|
|
52412
|
-
if (
|
|
52413
|
-
if (file2 === null) return `file: ${i.file} is outside the workspace`;
|
|
52414
|
-
|
|
52415
|
-
|
|
52416
|
-
|
|
52417
|
-
const args = ["build", "--frontend", "dockerfile.v0", "--local", `context
|
|
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}`];
|
|
52418
52882
|
args.push("--opt", `platform=${i.platforms.join(",")}`);
|
|
52419
52883
|
if (i.target) args.push("--opt", `target=${i.target}`);
|
|
52420
52884
|
for (const [k, v] of Object.entries(i.buildArgs)) args.push("--opt", `build-arg:${k}=${v}`);
|
|
@@ -52427,6 +52891,215 @@ function buildctlArgs(i, metadataFile) {
|
|
|
52427
52891
|
args.push("--metadata-file", metadataFile);
|
|
52428
52892
|
return args;
|
|
52429
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
|
+
}
|
|
52430
53103
|
async function runBuildImage(w, d) {
|
|
52431
53104
|
const fail = (msg) => {
|
|
52432
53105
|
d.log(`##[error]brnch/build-image@v1: ${msg}`);
|
|
@@ -52438,41 +53111,46 @@ async function runBuildImage(w, d) {
|
|
|
52438
53111
|
const bad = checkRefs(parsed, host, d.repository);
|
|
52439
53112
|
if (bad) return fail(bad);
|
|
52440
53113
|
if (parsed.push && !parsed.tags.length) return fail("tags are required when pushing (or set push: false)");
|
|
52441
|
-
if (d.hosted)
|
|
52442
|
-
|
|
52443
|
-
}
|
|
52444
|
-
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) {
|
|
52445
53116
|
return fail("this runner has no usable Docker (docker info failed); install Docker or run the job on a runner that has it");
|
|
52446
53117
|
}
|
|
52447
|
-
const
|
|
52448
|
-
if (typeof
|
|
52449
|
-
const
|
|
52450
|
-
|
|
52451
|
-
|
|
52452
|
-
const
|
|
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}`) } } : {} }));
|
|
52453
53138
|
try {
|
|
52454
|
-
|
|
52455
|
-
|
|
52456
|
-
if (started
|
|
52457
|
-
let ready = false;
|
|
52458
|
-
for (let i = 0; i < 30 && !ready; i++) {
|
|
52459
|
-
ready = (await d.capture(["docker", "exec", name, "buildctl", "debug", "workers"], { env: d.env })).code === 0;
|
|
52460
|
-
if (!ready) await new Promise((r) => setTimeout(r, 1e3));
|
|
52461
|
-
}
|
|
52462
|
-
d.log("##[endgroup]");
|
|
52463
|
-
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);
|
|
52464
53142
|
d.log(`##[group]Build ${parsed.tags.join(", ") || "(no tags)"} for ${parsed.platforms.join(", ")}`);
|
|
52465
|
-
const built = await
|
|
53143
|
+
const built = await builder.buildctl(args);
|
|
52466
53144
|
d.log("##[endgroup]");
|
|
52467
53145
|
if (built === null) return { outcome: "cancelled", outputs: {} };
|
|
52468
53146
|
if (built !== 0) return fail(`the build failed (buildctl exited ${built})`);
|
|
52469
|
-
const meta3 = await
|
|
53147
|
+
const meta3 = await builder.readMetadata();
|
|
52470
53148
|
let digest = "";
|
|
52471
53149
|
try {
|
|
52472
|
-
digest = String(JSON.parse(meta3
|
|
53150
|
+
digest = String(JSON.parse(meta3)["containerimage.digest"] ?? "");
|
|
52473
53151
|
} catch {
|
|
52474
53152
|
}
|
|
52475
|
-
const outputs = { metadata: meta3.
|
|
53153
|
+
const outputs = { metadata: meta3.slice(0, MAX_METADATA_OUTPUT) };
|
|
52476
53154
|
if (parsed.push) {
|
|
52477
53155
|
if (!/^sha256:[a-f0-9]{64}$/.test(digest)) return fail("the build pushed, but BuildKit reported no image digest");
|
|
52478
53156
|
const first2 = parsed.tags[0].replace(/[:@][^/]*$/, "");
|
|
@@ -52481,9 +53159,9 @@ async function runBuildImage(w, d) {
|
|
|
52481
53159
|
d.log(`pushed ${outputs.image}`);
|
|
52482
53160
|
if (parsed.cosignKey) {
|
|
52483
53161
|
d.log(`##[group]Sign ${outputs.image} with cosign`);
|
|
52484
|
-
const signArgs = ["
|
|
53162
|
+
const signArgs = ["sign", "--key", "env://COSIGN_KEY", "--yes"];
|
|
52485
53163
|
if (!parsed.cosignTlog) signArgs.push("--use-signing-config=false", "--tlog-upload=false");
|
|
52486
|
-
const signed = await
|
|
53164
|
+
const signed = await builder.sign([...signArgs, outputs.image], { COSIGN_KEY: parsed.cosignKey, COSIGN_PASSWORD: parsed.cosignPassword ?? "" });
|
|
52487
53165
|
d.log("##[endgroup]");
|
|
52488
53166
|
if (signed === null) return { outcome: "cancelled", outputs };
|
|
52489
53167
|
if (signed !== 0) return fail(`cosign could not sign ${outputs.image} (exit ${signed})`);
|
|
@@ -52494,8 +53172,9 @@ async function runBuildImage(w, d) {
|
|
|
52494
53172
|
}
|
|
52495
53173
|
return { outcome: "success", outputs };
|
|
52496
53174
|
} finally {
|
|
52497
|
-
await
|
|
53175
|
+
await builder.stop();
|
|
52498
53176
|
d.removeDir(auth);
|
|
53177
|
+
await d.run(["rm", "-rf", context], { env: d.env, timeoutMs: 6e4 });
|
|
52499
53178
|
}
|
|
52500
53179
|
}
|
|
52501
53180
|
|
|
@@ -53170,6 +53849,14 @@ async function runCodeReviewStep(spec, host) {
|
|
|
53170
53849
|
return ok && parsed.status !== "failed" ? "success" : "failure";
|
|
53171
53850
|
}
|
|
53172
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
|
+
|
|
53173
53860
|
// packages/cli/src/runner/executor.ts
|
|
53174
53861
|
var NODE_OS = { linux: "Linux", darwin: "macOS", win32: "Windows" };
|
|
53175
53862
|
var NODE_ARCH = { x64: "X64", arm64: "ARM64", arm: "ARM" };
|
|
@@ -53419,6 +54106,12 @@ var JobExecution = class {
|
|
|
53419
54106
|
}
|
|
53420
54107
|
spawnSync2("rm", ["-rf", "--", gone], { uid: this.owner.uid, gid: this.owner.gid, stdio: "ignore" });
|
|
53421
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
|
+
}
|
|
53422
54115
|
/** Run one process with streamed, masked output. Resolves to its exit code, or null if it was killed. */
|
|
53423
54116
|
exec(cmd, o) {
|
|
53424
54117
|
return new Promise((resolveExit) => {
|
|
@@ -53696,18 +54389,41 @@ var JobExecution = class {
|
|
|
53696
54389
|
}
|
|
53697
54390
|
const depth = w["fetch-depth"] === void 0 ? 1 : Number(w["fetch-depth"]);
|
|
53698
54391
|
const env = this.gitEnv();
|
|
53699
|
-
|
|
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
|
+
};
|
|
53700
54414
|
this.discard(this.workspace);
|
|
53701
54415
|
mkdirSync3(this.workspace, { mode: 493 });
|
|
53702
54416
|
const branch = a.ref.startsWith("refs/heads/") ? a.ref.slice("refs/heads/".length) : null;
|
|
53703
54417
|
const fetchAndCheckout = async () => {
|
|
53704
54418
|
for (const args of [["init", "-q"], ["remote", "add", "origin", this.gitUrl()]]) if (await git4(args) !== 0) return 1;
|
|
53705
|
-
let code2
|
|
53706
|
-
if (
|
|
53707
|
-
|
|
53708
|
-
if (
|
|
53709
|
-
|
|
53710
|
-
|
|
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]);
|
|
53711
54427
|
if (code2 !== 0) return code2;
|
|
53712
54428
|
return git4(branch ? ["checkout", "-q", "-B", branch, a.head_sha] : ["checkout", "-q", "--detach", a.head_sha]);
|
|
53713
54429
|
};
|
|
@@ -53752,6 +54468,7 @@ var JobExecution = class {
|
|
|
53752
54468
|
async buildImageStep(w, env) {
|
|
53753
54469
|
const r = await runBuildImage(w, {
|
|
53754
54470
|
baseUrl: this.opts.baseUrl,
|
|
54471
|
+
fetch: (url2, init) => fetch(url2, init),
|
|
53755
54472
|
registryHost: this.a.registry_host ?? null,
|
|
53756
54473
|
repository: this.a.repository.full_name,
|
|
53757
54474
|
jobToken: this.a.job_token,
|
|
@@ -53759,6 +54476,7 @@ var JobExecution = class {
|
|
|
53759
54476
|
workspace: this.workspace,
|
|
53760
54477
|
temp: this.temp,
|
|
53761
54478
|
hosted: this.opts.hosted,
|
|
54479
|
+
stepUser: this.opts.stepUser ? { uid: this.opts.stepUser.uid, gid: this.opts.stepUser.gid } : null,
|
|
53762
54480
|
log: (l) => this.log(l),
|
|
53763
54481
|
run: (cmd, o) => this.exec(cmd, { cwd: this.workspace, env: o.env, asStepUser: false, timeoutMs: o.timeoutMs, onLine: this.onCommandLine }),
|
|
53764
54482
|
capture: async (cmd, o) => {
|
|
@@ -53973,12 +54691,81 @@ function sanitizedEnv(env) {
|
|
|
53973
54691
|
return out;
|
|
53974
54692
|
}
|
|
53975
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
|
+
|
|
53976
54762
|
// packages/cli/src/runner/agent.ts
|
|
53977
54763
|
var RunnerAgent = class {
|
|
53978
54764
|
constructor(cfg, opts) {
|
|
53979
54765
|
this.cfg = cfg;
|
|
53980
54766
|
this.opts = opts;
|
|
53981
54767
|
this.client = this.clientFor(cfg);
|
|
54768
|
+
this.resources = new RunnerResourceCollector(opts.workDir, opts.once || cfg.ephemeral ? 1 : opts.jobs);
|
|
53982
54769
|
}
|
|
53983
54770
|
cfg;
|
|
53984
54771
|
opts;
|
|
@@ -53990,6 +54777,7 @@ var RunnerAgent = class {
|
|
|
53990
54777
|
exit = null;
|
|
53991
54778
|
heartbeatTimer = null;
|
|
53992
54779
|
taken = 0;
|
|
54780
|
+
resources;
|
|
53993
54781
|
clientFor(cfg) {
|
|
53994
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 } : {} });
|
|
53995
54783
|
}
|
|
@@ -54027,7 +54815,7 @@ var RunnerAgent = class {
|
|
|
54027
54815
|
}
|
|
54028
54816
|
async heartbeat() {
|
|
54029
54817
|
try {
|
|
54030
|
-
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() });
|
|
54031
54819
|
for (const id2 of r.cancel_jobs) this.running.get(id2)?.exec.cancel("cancelled by the server");
|
|
54032
54820
|
for (const id2 of r.forget_jobs) this.running.get(id2)?.exec.abort("the server reassigned this job");
|
|
54033
54821
|
if (r.removed) {
|
|
@@ -54074,6 +54862,7 @@ var RunnerAgent = class {
|
|
|
54074
54862
|
await this.client.runner.diagnostics(diagnostics(this.cfg)).catch(() => void 0);
|
|
54075
54863
|
const unhook = this.opts.onSignal?.((s) => this.shutdown(s)) ?? (() => void 0);
|
|
54076
54864
|
this.heartbeatTimer = setInterval(() => void this.heartbeat(), Math.max(1, me.heartbeat_seconds) * 1e3);
|
|
54865
|
+
await this.heartbeat();
|
|
54077
54866
|
const single = this.opts.once || this.cfg.ephemeral;
|
|
54078
54867
|
let backoff = 1e3;
|
|
54079
54868
|
try {
|
|
@@ -55091,6 +55880,13 @@ var EXAMPLES = {
|
|
|
55091
55880
|
"deploy run": [`${c7} deploy run production --wait || echo "deploy failed (exit $?)"`, `${c7} deploy run dpt_123 --commit 0123456789abcdef0123456789abcdef01234567`],
|
|
55092
55881
|
"deploy update": [`${c7} deploy update production --build "npm run build:prod"`, `${c7} deploy update production --disable`],
|
|
55093
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"`],
|
|
55094
55890
|
version: [`${c7} version`, `${c7} version --json`],
|
|
55095
55891
|
upgrade: [`${c7} upgrade --check`, `${c7} upgrade`, `${c7} upgrade --force`],
|
|
55096
55892
|
doctor: [`${c7} doctor`, `${c7} doctor --json | jq '.checks[] | select(.status != "ok")'`],
|
|
@@ -55231,6 +56027,7 @@ function buildProgram(rt) {
|
|
|
55231
56027
|
registerMemory(program2, rt);
|
|
55232
56028
|
registerIntegrations(program2, rt);
|
|
55233
56029
|
registerDeploy(program2, rt);
|
|
56030
|
+
registerDb(program2, rt);
|
|
55234
56031
|
registerDelegate(program2, rt);
|
|
55235
56032
|
registerAgents(program2, rt);
|
|
55236
56033
|
registerCodingAgents(program2, rt);
|
|
@@ -55305,17 +56102,44 @@ function report2(e, deps, state2) {
|
|
|
55305
56102
|
`);
|
|
55306
56103
|
return exitCode;
|
|
55307
56104
|
}
|
|
55308
|
-
|
|
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) {
|
|
55309
56113
|
const merged = { ...nodeDeps(), ...overrides };
|
|
55310
|
-
const deps = merged;
|
|
55311
56114
|
const split = splitGitArgs(argv);
|
|
55312
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
|
+
};
|
|
55313
56137
|
const invoke2 = async (sub, o = {}) => {
|
|
55314
56138
|
const out = [];
|
|
55315
56139
|
const err = [];
|
|
55316
56140
|
const errSink = (s) => void err.push(s);
|
|
55317
|
-
const quiet = o.quiet ? { stderr: { write: errSink }, stderrIsTTY: false, spawnGit: (args, g2) =>
|
|
55318
|
-
const code2 = await run2(sub, { ...
|
|
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);
|
|
55319
56143
|
return { code: code2, stdout: out.join(""), stderr: err.join("") };
|
|
55320
56144
|
};
|
|
55321
56145
|
const program2 = buildProgram({ deps, state: state2, gitArgs: split.gitArgs, invoke: invoke2 });
|
|
@@ -55330,6 +56154,11 @@ async function run2(argv, overrides = {}) {
|
|
|
55330
56154
|
await sendCrashReport(r, { baseUrl: state2.baseUrl ?? crashServer(deps.env), fetch: deps.sys.fetch, stderr: deps.stderr });
|
|
55331
56155
|
}
|
|
55332
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
|
+
}
|
|
55333
56162
|
await saveBookmark(deps, state2);
|
|
55334
56163
|
await updateHint(deps, state2);
|
|
55335
56164
|
return code;
|