brnch-cli 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/brnch.mjs +1290 -170
- package/package.json +1 -1
package/brnch.mjs
CHANGED
|
@@ -6044,9 +6044,9 @@ var require_lexer = __commonJS({
|
|
|
6044
6044
|
}
|
|
6045
6045
|
}
|
|
6046
6046
|
*parseQuotedScalar() {
|
|
6047
|
-
const
|
|
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")}`;
|
|
@@ -29789,8 +29789,8 @@ function convertBaseSchema(schema, ctx) {
|
|
|
29789
29789
|
});
|
|
29790
29790
|
}
|
|
29791
29791
|
ctx.processing.add(refPath);
|
|
29792
|
-
const
|
|
29793
|
-
const zodSchema2 = convertSchema(
|
|
29792
|
+
const resolved2 = resolveRef(refPath, ctx);
|
|
29793
|
+
const zodSchema2 = convertSchema(resolved2, ctx);
|
|
29794
29794
|
ctx.refs.set(refPath, zodSchema2);
|
|
29795
29795
|
ctx.processing.delete(refPath);
|
|
29796
29796
|
return zodSchema2;
|
|
@@ -30572,12 +30572,13 @@ 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
|
|
|
30579
30580
|
// packages/domain/src/capabilities.ts
|
|
30580
|
-
var VIEWER = ["org:read", "members:read", "repo:read", "task:read", "change:read", "session:read", "packages:read"];
|
|
30581
|
+
var VIEWER = ["org:read", "members:read", "repo:read", "task:read", "change:read", "session:read", "packages:read", "migrations:read"];
|
|
30581
30582
|
var REVIEWER = [...VIEWER, "review:comment", "review:resolve", "review:approve"];
|
|
30582
30583
|
var DEVELOPER = [
|
|
30583
30584
|
...VIEWER,
|
|
@@ -30590,9 +30591,11 @@ var DEVELOPER = [
|
|
|
30590
30591
|
"review:comment",
|
|
30591
30592
|
"evidence:report",
|
|
30592
30593
|
"promotion:request",
|
|
30593
|
-
"packages:write"
|
|
30594
|
+
"packages:write",
|
|
30595
|
+
"deploy:read"
|
|
30594
30596
|
];
|
|
30595
|
-
var
|
|
30597
|
+
var DEPLOY_AND_MIGRATIONS = ["deploy:write", "deploy:trigger", "deploy:manage", "migrations:manage", "migrations:approve"];
|
|
30598
|
+
var MAINTAINER = [...DEVELOPER, ...REVIEWER, "repo:admin", ...DEPLOY_AND_MIGRATIONS, "policy:write", "session:manage", "secret_scanning:read", "secret_scanning:resolve"];
|
|
30596
30599
|
var ADMIN = [
|
|
30597
30600
|
...MAINTAINER,
|
|
30598
30601
|
"org:admin",
|
|
@@ -31545,15 +31548,15 @@ function redactText(input3, opts = {}) {
|
|
|
31545
31548
|
hit("auth-header");
|
|
31546
31549
|
return `${prefix}${REDACTED}`;
|
|
31547
31550
|
});
|
|
31548
|
-
text2 = text2.replace(ASSIGNMENT, (w, prefix,
|
|
31551
|
+
text2 = text2.replace(ASSIGNMENT, (w, prefix, quote3, value) => {
|
|
31549
31552
|
if (isReference(value) || value.startsWith(REDACTED)) return w;
|
|
31550
31553
|
hit("assignment");
|
|
31551
|
-
return `${prefix}${
|
|
31554
|
+
return `${prefix}${quote3}${REDACTED}${quote3}`;
|
|
31552
31555
|
});
|
|
31553
|
-
text2 = text2.replace(FLAG, (w, prefix,
|
|
31556
|
+
text2 = text2.replace(FLAG, (w, prefix, quote3, value) => {
|
|
31554
31557
|
if (isReference(value) || value.startsWith(REDACTED)) return w;
|
|
31555
31558
|
hit("flag");
|
|
31556
|
-
return `${prefix}${
|
|
31559
|
+
return `${prefix}${quote3}${REDACTED}${quote3}`;
|
|
31557
31560
|
});
|
|
31558
31561
|
if (opts.entropy !== false) {
|
|
31559
31562
|
text2 = text2.replace(TOKENISH, (w) => {
|
|
@@ -31632,6 +31635,7 @@ var looksLikeHostedLabel = (label2) => /^brnch-hosted(?:-|$)/.test(label2);
|
|
|
31632
31635
|
// packages/domain/src/deploy.ts
|
|
31633
31636
|
var DEPLOY_LIMITS = {
|
|
31634
31637
|
maxStepsPerTarget: 12,
|
|
31638
|
+
maxWatchPaths: 50,
|
|
31635
31639
|
maxTargetsPerRepository: 10,
|
|
31636
31640
|
maxCommandLength: 2e3,
|
|
31637
31641
|
maxSummaryLength: 500,
|
|
@@ -31645,6 +31649,21 @@ var DEPLOY_LIMITS = {
|
|
|
31645
31649
|
var DEPLOY_RUNNER_LABELS = HOSTED_SIZES.map((s) => HOSTED_SIZE_SPECS[s].label);
|
|
31646
31650
|
var DEFAULT_DEPLOY_RUNNER = HOSTED_SIZE_SPECS.basic.label;
|
|
31647
31651
|
|
|
31652
|
+
// packages/domain/src/db-migrations.ts
|
|
31653
|
+
var DB_MIGRATION_LIMITS = {
|
|
31654
|
+
sourcesPerRepo: 20,
|
|
31655
|
+
/** Migration files read from one directory. */
|
|
31656
|
+
files: 2e3,
|
|
31657
|
+
fileBytes: 1024 * 1024,
|
|
31658
|
+
dirBytes: 16 * 1024 * 1024,
|
|
31659
|
+
/** Per statement; queue work is bounded by the platform's 15-minute limit. */
|
|
31660
|
+
statementTimeoutMaxS: 600,
|
|
31661
|
+
lockTimeoutMaxS: 60,
|
|
31662
|
+
sessionSettings: 20,
|
|
31663
|
+
/** How long a worker holds a run before another may take it over. */
|
|
31664
|
+
leaseMs: 15 * 6e4
|
|
31665
|
+
};
|
|
31666
|
+
|
|
31648
31667
|
// packages/domain/src/isolated-preview.ts
|
|
31649
31668
|
function parseJsonc(text2) {
|
|
31650
31669
|
let out = "";
|
|
@@ -31683,6 +31702,8 @@ var DELEGATION_MATRIX = Object.freeze({
|
|
|
31683
31702
|
"cloudflare/resources": { userToOrg: false, userToRepo: false, orgToRepo: true },
|
|
31684
31703
|
"cloudflare/d1_migrations": { userToOrg: false, userToRepo: false, orgToRepo: true },
|
|
31685
31704
|
"cloudflare/secrets_store": { userToOrg: false, userToRepo: false, orgToRepo: true },
|
|
31705
|
+
// Database migrations (ADR-0084) apply one repository's migration directory to the PlanetScale database it is bound to.
|
|
31706
|
+
"planetscale/db_migrations": { userToOrg: false, userToRepo: false, orgToRepo: true },
|
|
31686
31707
|
// A secret source is read by a repository's workflow runs.
|
|
31687
31708
|
"infisical/secrets_source": { userToOrg: false, userToRepo: false, orgToRepo: true },
|
|
31688
31709
|
// AI code review keys (ADR-0051): an organization's key reviews the changes of its repositories.
|
|
@@ -31734,7 +31755,7 @@ var PLANS = Object.freeze({
|
|
|
31734
31755
|
monthly_cents: 800,
|
|
31735
31756
|
annual_cents: 8e3,
|
|
31736
31757
|
per_seat: false,
|
|
31737
|
-
features: ["private_review_rules", "ai_code_review_services", "hosted_delegation", "cloudflare_previews", "cloudflare_d1_migrations", "assistant"],
|
|
31758
|
+
features: ["private_review_rules", "ai_code_review_services", "hosted_delegation", "cloudflare_previews", "cloudflare_d1_migrations", "database_migrations", "assistant"],
|
|
31738
31759
|
minutes_base: 5e3,
|
|
31739
31760
|
minutes_per_seat: 0,
|
|
31740
31761
|
concurrent_hosted_jobs: 4,
|
|
@@ -31753,7 +31774,7 @@ var PLANS = Object.freeze({
|
|
|
31753
31774
|
monthly_cents: 1e3,
|
|
31754
31775
|
annual_cents: 9600,
|
|
31755
31776
|
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"],
|
|
31777
|
+
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
31778
|
minutes_base: 3e3,
|
|
31758
31779
|
minutes_per_seat: 1e3,
|
|
31759
31780
|
concurrent_hosted_jobs: 10,
|
|
@@ -31772,7 +31793,7 @@ var PLANS = Object.freeze({
|
|
|
31772
31793
|
monthly_cents: null,
|
|
31773
31794
|
annual_cents: null,
|
|
31774
31795
|
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"],
|
|
31796
|
+
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
31797
|
minutes_base: 5e4,
|
|
31777
31798
|
minutes_per_seat: 0,
|
|
31778
31799
|
concurrent_hosted_jobs: 50,
|
|
@@ -32140,12 +32161,16 @@ var CreateInvitationRequest = external_exports.object({ email: external_exports.
|
|
|
32140
32161
|
var AcceptInvitationRequest = external_exports.object({ token: external_exports.string() });
|
|
32141
32162
|
var CreateTeamRequest = external_exports.object({ slug: Slug, name: displayLine().min(1).max(100) });
|
|
32142
32163
|
var AddTeamMemberRequest = external_exports.object({ user_id: external_exports.string() });
|
|
32164
|
+
var GrantExtraCapabilitySchema = external_exports.enum(["deploy:read", "deploy:trigger", "deploy:manage", "migrations:read", "migrations:manage", "migrations:approve"]);
|
|
32143
32165
|
var CreateGrantRequest = external_exports.object({
|
|
32144
32166
|
principal_type: external_exports.enum(["user", "team"]),
|
|
32145
32167
|
principal_id: external_exports.string().min(1).max(100),
|
|
32146
32168
|
role: RepoGrantRoleSchema,
|
|
32169
|
+
/** Beyond the role, e.g. a developers team that approves a database's migrations. */
|
|
32170
|
+
capabilities: external_exports.array(GrantExtraCapabilitySchema).max(6).optional(),
|
|
32147
32171
|
expires_at: external_exports.iso.datetime().nullable().optional()
|
|
32148
32172
|
});
|
|
32173
|
+
var UpdateGrantRequest = external_exports.object({ capabilities: external_exports.array(GrantExtraCapabilitySchema).max(6) });
|
|
32149
32174
|
var CreateRepoRequest = external_exports.object({
|
|
32150
32175
|
name: RepoName,
|
|
32151
32176
|
description: displayText().max(500).default(""),
|
|
@@ -32234,11 +32259,16 @@ var PolicyRulesSchema = external_exports.object({
|
|
|
32234
32259
|
});
|
|
32235
32260
|
var AdoptPolicyRequest = external_exports.object({ rules: PolicyRulesSchema, expected_version: external_exports.number().int().nonnegative(), note: external_exports.string().max(1e3).optional() });
|
|
32236
32261
|
var ApplySecureDefaultsRequest = external_exports.object({ expected_version: external_exports.number().int().nonnegative() });
|
|
32262
|
+
var MAX_TASK_BLOCKERS = 50;
|
|
32263
|
+
var TaskRef = external_exports.union([external_exports.number().int().positive(), external_exports.string().trim().regex(/^(task_[0-9a-z]{1,64}|#?\d{1,9})$/, "expected a task number (12 or #12) or a task_\u2026 id")]);
|
|
32264
|
+
var blockedBy = external_exports.array(TaskRef).max(MAX_TASK_BLOCKERS);
|
|
32237
32265
|
var CreateTaskRequest = external_exports.object({
|
|
32238
32266
|
title: displayLine().min(1).max(300),
|
|
32239
32267
|
description: external_exports.string().max(65536).default(""),
|
|
32240
32268
|
acceptance_criteria: external_exports.array(external_exports.string().max(1e3)).max(100).default([]),
|
|
32241
|
-
labels: external_exports.array(external_exports.string().max(50)).max(20).default([])
|
|
32269
|
+
labels: external_exports.array(external_exports.string().max(50)).max(20).default([]),
|
|
32270
|
+
/** Tasks in the same repository this one is blocked by. */
|
|
32271
|
+
blocked_by: blockedBy.default([])
|
|
32242
32272
|
});
|
|
32243
32273
|
var UpdateTaskRequest = external_exports.object({
|
|
32244
32274
|
title: displayLine().min(1).max(300).optional(),
|
|
@@ -32247,8 +32277,13 @@ var UpdateTaskRequest = external_exports.object({
|
|
|
32247
32277
|
labels: external_exports.array(external_exports.string().max(50)).max(20).optional(),
|
|
32248
32278
|
state: external_exports.enum(["open", "in_progress", "blocked", "done", "cancelled"]).optional(),
|
|
32249
32279
|
owner_id: external_exports.string().nullable().optional(),
|
|
32280
|
+
/** Replaces the whole set of blockers. Links are not task fields: changing them alone leaves `version` as it is. */
|
|
32281
|
+
blocked_by: blockedBy.optional(),
|
|
32250
32282
|
version: external_exports.number().int()
|
|
32251
32283
|
});
|
|
32284
|
+
var AddTaskDependencyRequest = external_exports.object({ blocker: TaskRef });
|
|
32285
|
+
var TaskListQuery = external_exports.object({ ready: external_exports.enum(["true", "false"]).optional().transform((v) => v === void 0 ? void 0 : v === "true") });
|
|
32286
|
+
var TaskNumberParam = external_exports.coerce.number().int().positive().max(999999999);
|
|
32252
32287
|
var ClaimTaskRequest = external_exports.object({ session_id: external_exports.string().optional() });
|
|
32253
32288
|
var AddLinkRequest = external_exports.object({ kind: external_exports.enum(["external", "change", "task"]), url: external_exports.url().optional(), target_id: external_exports.string().optional(), title: external_exports.string().max(300).optional() });
|
|
32254
32289
|
var CreateCommentRequest = external_exports.object({ body: external_exports.string().min(1).max(65536) });
|
|
@@ -32864,6 +32899,22 @@ var ActionsSettingsRequest = external_exports.object({
|
|
|
32864
32899
|
allow_untrusted_jobs_on_self_hosted_runners: external_exports.boolean().optional()
|
|
32865
32900
|
});
|
|
32866
32901
|
var RunnerLabel = external_exports.string().min(1).max(50).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "letters, digits, '.', '_' and '-'");
|
|
32902
|
+
var ResourceBytes = external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
32903
|
+
var RunnerResourceSnapshot = external_exports.object({
|
|
32904
|
+
cpu_percent: external_exports.number().min(0).max(100).nullable(),
|
|
32905
|
+
cpu_count: external_exports.number().int().min(1).max(65536),
|
|
32906
|
+
cpu_interval_ms: external_exports.number().int().min(1).max(6e5).nullable(),
|
|
32907
|
+
memory_total_bytes: ResourceBytes.positive(),
|
|
32908
|
+
memory_available_bytes: ResourceBytes,
|
|
32909
|
+
disk_total_bytes: ResourceBytes.positive().nullable(),
|
|
32910
|
+
disk_available_bytes: ResourceBytes.nullable(),
|
|
32911
|
+
uptime_seconds: external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
|
32912
|
+
job_slots: external_exports.number().int().min(1).max(32)
|
|
32913
|
+
}).strict().superRefine((s, ctx) => {
|
|
32914
|
+
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" });
|
|
32915
|
+
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" });
|
|
32916
|
+
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" });
|
|
32917
|
+
});
|
|
32867
32918
|
var TestRunnerRequest = external_exports.object({
|
|
32868
32919
|
repository_id: external_exports.string().max(64).nullable().default(null)
|
|
32869
32920
|
});
|
|
@@ -32888,8 +32939,9 @@ var PollJobsRequest = external_exports.object({
|
|
|
32888
32939
|
wait_seconds: external_exports.number().int().min(0).max(25).default(20)
|
|
32889
32940
|
});
|
|
32890
32941
|
var HeartbeatRequest = external_exports.object({
|
|
32891
|
-
running_jobs: external_exports.array(external_exports.string().max(64)).max(32).default([])
|
|
32892
|
-
|
|
32942
|
+
running_jobs: external_exports.array(external_exports.string().max(64)).max(32).default([]),
|
|
32943
|
+
resources: RunnerResourceSnapshot.optional()
|
|
32944
|
+
}).strict();
|
|
32893
32945
|
var StepUpdateRequest = external_exports.object({
|
|
32894
32946
|
name: external_exports.string().max(500),
|
|
32895
32947
|
status: external_exports.enum(["in_progress", "completed"]),
|
|
@@ -33618,7 +33670,7 @@ var CODING_AGENT_SESSION_SCOPES = [
|
|
|
33618
33670
|
var EnsureCodingAgentRequest = external_exports.object({ tool: external_exports.enum(AGENT_RUNTIMES) }).strict();
|
|
33619
33671
|
|
|
33620
33672
|
// packages/protocol/src/connections.ts
|
|
33621
|
-
var CONNECTION_PURPOSES = ["impact", "secrets_store", "import", "secrets_source", "deploy", "resources", "d1_migrations", "code_review"];
|
|
33673
|
+
var CONNECTION_PURPOSES = ["impact", "secrets_store", "import", "secrets_source", "deploy", "resources", "d1_migrations", "db_migrations", "code_review"];
|
|
33622
33674
|
var Name3 = external_exports.string().trim().min(1).max(100);
|
|
33623
33675
|
var Secret2 = external_exports.string().min(1).max(4e3);
|
|
33624
33676
|
var Id2 = external_exports.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/, "letters, digits, '.', '_', ':' and '-' only");
|
|
@@ -33657,9 +33709,10 @@ var CreateConnectionRequest = external_exports.discriminatedUnion("auth", [
|
|
|
33657
33709
|
auth: external_exports.literal("api_token"),
|
|
33658
33710
|
// Cloudflare API tokens (impact, secrets_store, deploy), GitHub fine-grained personal access tokens (import) and
|
|
33659
33711
|
// 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
|
-
|
|
33712
|
+
// Claude Code, a PlanetScale service token (`<id>:<token>`, db_migrations). The provider/purpose pair is checked
|
|
33713
|
+
// server-side.
|
|
33714
|
+
provider: external_exports.enum(["cloudflare", "planetscale", "github", "coderabbit", "greptile", "codex", "claude"]),
|
|
33715
|
+
purpose: external_exports.enum(["impact", "secrets_store", "import", "deploy", "resources", "d1_migrations", "db_migrations", "code_review"]),
|
|
33663
33716
|
name: Name3,
|
|
33664
33717
|
credentials: external_exports.object({ api_token: Secret2 })
|
|
33665
33718
|
}),
|
|
@@ -33762,6 +33815,11 @@ var targetFields = {
|
|
|
33762
33815
|
preview_check_name: external_exports.string().trim().min(1).max(100).nullable(),
|
|
33763
33816
|
/** The hosted runner size the deploy jobs run on, by its `runs-on` label: `brnch-hosted` … `brnch-hosted-4` (ADR-0066). */
|
|
33764
33817
|
runs_on: external_exports.string().trim().min(1).max(40),
|
|
33818
|
+
/**
|
|
33819
|
+
* Paths whose changes deploy the target (CODEOWNERS patterns such as `apps/web/` or `packages/**`, `!` excludes; the
|
|
33820
|
+
* last matching pattern decides). Empty: every change deploys it.
|
|
33821
|
+
*/
|
|
33822
|
+
watch_paths: external_exports.array(external_exports.string().trim().min(1).max(500)).max(50),
|
|
33765
33823
|
steps: external_exports.array(DeployStepSpecBody).max(12)
|
|
33766
33824
|
};
|
|
33767
33825
|
var CreateDeployTargetRequest = external_exports.object({
|
|
@@ -33779,6 +33837,7 @@ var CreateDeployTargetRequest = external_exports.object({
|
|
|
33779
33837
|
preview_agent_changes: targetFields.preview_agent_changes.default(false),
|
|
33780
33838
|
preview_check_name: targetFields.preview_check_name.default(null),
|
|
33781
33839
|
runs_on: targetFields.runs_on.default("brnch-hosted"),
|
|
33840
|
+
watch_paths: targetFields.watch_paths.default([]),
|
|
33782
33841
|
steps: targetFields.steps.default([])
|
|
33783
33842
|
});
|
|
33784
33843
|
var UpdateDeployTargetRequest = external_exports.object(targetFields).partial().extend({
|
|
@@ -33844,6 +33903,111 @@ var UpsertWorkerEnvRequest = external_exports.object({
|
|
|
33844
33903
|
if (v.kind === "var" && v.source !== "value") c8.addIssue({ code: "custom", message: "plain vars take a value", path: ["source"] });
|
|
33845
33904
|
});
|
|
33846
33905
|
|
|
33906
|
+
// packages/protocol/src/db-migrations.ts
|
|
33907
|
+
var DB_EXEC_ORDERS = ["linear", "linear_skip", "non_linear"];
|
|
33908
|
+
var DB_CREDENTIAL_MODES = ["managed_role", "dsn", "branch_password"];
|
|
33909
|
+
var DB_APPLY_ON = ["merge", "deploy", "manual"];
|
|
33910
|
+
var DB_LINT_LEVELS = ["error", "warning", "off"];
|
|
33911
|
+
var DB_DRIFT_MODES = ["off", "on_run", "daily"];
|
|
33912
|
+
var DbLintPolicySchema = external_exports.object({
|
|
33913
|
+
levels: external_exports.record(external_exports.string().regex(/^[A-Za-z0-9_]{1,40}$/), external_exports.enum(DB_LINT_LEVELS)).refine((r) => Object.keys(r).length <= 50, "at most 50 entries").default({}),
|
|
33914
|
+
nolint: external_exports.enum(["honor", "ignore"]).default("honor"),
|
|
33915
|
+
/** Drift found by the last check fails the deploy's migrations step. */
|
|
33916
|
+
drift_blocks_deploy: external_exports.boolean().default(false)
|
|
33917
|
+
});
|
|
33918
|
+
var Ident2 = external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_$]{0,62}$/, "letters, digits and _ only (at most 63)");
|
|
33919
|
+
var PsName = external_exports.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/, "a PlanetScale name: letters, digits, '.', '_' and '-'");
|
|
33920
|
+
var SourceFields = {
|
|
33921
|
+
name: external_exports.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9_-]*$/, "lowercase letters, digits, - and _"),
|
|
33922
|
+
/** Directory relative to the repository root ("" = the root). */
|
|
33923
|
+
path: external_exports.string().max(300),
|
|
33924
|
+
/** `wrangler` directories are D1 sources, which come from the Wrangler configuration. */
|
|
33925
|
+
format: external_exports.enum(["atlas", "plain", "golang_migrate", "drizzle", "prisma"]),
|
|
33926
|
+
engine: external_exports.enum(["postgres", "vitess"]),
|
|
33927
|
+
/** Branch whose merges create runs; the repository's default branch when omitted. */
|
|
33928
|
+
branch: external_exports.string().min(1).max(200).optional(),
|
|
33929
|
+
/** PlanetScale connection (purpose `db_migrations`) of the organization or the repository. */
|
|
33930
|
+
connection_id: external_exports.string().min(1).max(64),
|
|
33931
|
+
ps_organization: PsName,
|
|
33932
|
+
ps_database: PsName,
|
|
33933
|
+
/** The production branch migrations are applied to. */
|
|
33934
|
+
ps_branch: PsName.default("main"),
|
|
33935
|
+
/** Where the format's revisions table lives (Postgres schema; Atlas default `atlas_schema_revisions`). */
|
|
33936
|
+
revisions_schema: Ident2.nullable().default(null),
|
|
33937
|
+
revisions_table: Ident2.nullable().default(null),
|
|
33938
|
+
/** With no revision history yet, record this version as a baseline instead of applying files up to it. */
|
|
33939
|
+
baseline_version: external_exports.string().min(1).max(100).nullable().default(null),
|
|
33940
|
+
exec_order: external_exports.enum(DB_EXEC_ORDERS).default("linear"),
|
|
33941
|
+
/** Postgres session settings for migration sessions (`lock_timeout`, custom `prefix.name`, …). */
|
|
33942
|
+
session_settings: external_exports.record(external_exports.string().max(126), external_exports.string().max(500)).default({}),
|
|
33943
|
+
statement_timeout_s: external_exports.number().int().min(1).max(600).default(300),
|
|
33944
|
+
lock_timeout_s: external_exports.number().int().min(1).max(60).default(10),
|
|
33945
|
+
credential_mode: external_exports.enum(DB_CREDENTIAL_MODES).optional(),
|
|
33946
|
+
/** Postgres: the role new objects belong to (`SET ROLE`); `postgres` by default for managed roles, none for a DSN. */
|
|
33947
|
+
run_as_role: external_exports.string().regex(/^[a-z_][a-z0-9_]{0,62}$/).nullable().optional(),
|
|
33948
|
+
/** Deprecated: `apply_on` (`approval` = `merge`, `manual_only` = `manual`). */
|
|
33949
|
+
on_merge: external_exports.enum(["approval", "manual_only"]).optional(),
|
|
33950
|
+
apply_on: external_exports.enum(DB_APPLY_ON).optional(),
|
|
33951
|
+
/** `deploy` and `manual` only: whether a person approves before anything is applied (`merge` always requires it). */
|
|
33952
|
+
require_approval: external_exports.boolean().optional(),
|
|
33953
|
+
/** Validate the directory on changes (evidence `migrations/<name>`). */
|
|
33954
|
+
change_checks: external_exports.boolean().default(true),
|
|
33955
|
+
lint: DbLintPolicySchema.optional(),
|
|
33956
|
+
drift_mode: external_exports.enum(DB_DRIFT_MODES).optional()
|
|
33957
|
+
};
|
|
33958
|
+
var CreateDbMigrationSourceRequest = external_exports.object({
|
|
33959
|
+
/** Only PlanetScale sources are created here: D1 sources come from the Wrangler configuration. */
|
|
33960
|
+
provider: external_exports.literal("planetscale").default("planetscale"),
|
|
33961
|
+
...SourceFields,
|
|
33962
|
+
/** Postgres `credential_mode: dsn` only: `postgresql://user:password@host:5432/db?sslmode=verify-full`. Write-only. */
|
|
33963
|
+
dsn: external_exports.string().min(10).max(2e3).optional()
|
|
33964
|
+
});
|
|
33965
|
+
var UpdateDbMigrationSourceRequest = external_exports.object({
|
|
33966
|
+
path: SourceFields.path,
|
|
33967
|
+
branch: SourceFields.branch,
|
|
33968
|
+
connection_id: SourceFields.connection_id,
|
|
33969
|
+
ps_organization: SourceFields.ps_organization,
|
|
33970
|
+
ps_database: SourceFields.ps_database,
|
|
33971
|
+
ps_branch: PsName,
|
|
33972
|
+
revisions_schema: Ident2.nullable(),
|
|
33973
|
+
revisions_table: Ident2.nullable(),
|
|
33974
|
+
baseline_version: external_exports.string().min(1).max(100).nullable(),
|
|
33975
|
+
exec_order: external_exports.enum(DB_EXEC_ORDERS),
|
|
33976
|
+
session_settings: external_exports.record(external_exports.string().max(126), external_exports.string().max(500)),
|
|
33977
|
+
statement_timeout_s: SourceFields.statement_timeout_s.unwrap(),
|
|
33978
|
+
lock_timeout_s: SourceFields.lock_timeout_s.unwrap(),
|
|
33979
|
+
credential_mode: external_exports.enum(DB_CREDENTIAL_MODES),
|
|
33980
|
+
run_as_role: external_exports.string().regex(/^[a-z_][a-z0-9_]{0,62}$/).nullable(),
|
|
33981
|
+
on_merge: external_exports.enum(["approval", "manual_only"]),
|
|
33982
|
+
apply_on: external_exports.enum(DB_APPLY_ON),
|
|
33983
|
+
require_approval: external_exports.boolean(),
|
|
33984
|
+
change_checks: external_exports.boolean(),
|
|
33985
|
+
lint: DbLintPolicySchema,
|
|
33986
|
+
drift_mode: external_exports.enum(DB_DRIFT_MODES),
|
|
33987
|
+
state: external_exports.enum(["active", "disabled"]),
|
|
33988
|
+
dsn: external_exports.string().min(10).max(2e3).nullable()
|
|
33989
|
+
}).partial().extend({ version: external_exports.number().int().min(1) });
|
|
33990
|
+
var StartDbMigrationRunRequest = external_exports.object({
|
|
33991
|
+
/** Branch, tag or commit to apply; the source's branch when omitted. */
|
|
33992
|
+
ref: external_exports.string().min(1).max(200).optional()
|
|
33993
|
+
});
|
|
33994
|
+
var DecideDbMigrationRunRequest = external_exports.object({
|
|
33995
|
+
reason: external_exports.string().max(500).optional(),
|
|
33996
|
+
/** Approving a run a deploy waits on: redeploy that deploy at the same commit once the run succeeds. */
|
|
33997
|
+
redeploy: external_exports.boolean().optional()
|
|
33998
|
+
});
|
|
33999
|
+
|
|
34000
|
+
// packages/protocol/src/approvals.ts
|
|
34001
|
+
var PutApprovalRuleRequest = external_exports.object({
|
|
34002
|
+
required: external_exports.number().int().min(1).max(10),
|
|
34003
|
+
users: external_exports.array(external_exports.string().min(1).max(100)).max(20).default([]),
|
|
34004
|
+
teams: external_exports.array(external_exports.string().min(1).max(100)).max(10).default([]),
|
|
34005
|
+
separation_of_duties: external_exports.boolean().default(false),
|
|
34006
|
+
/** The rule's version when replacing one (optimistic concurrency); omit to create. */
|
|
34007
|
+
version: external_exports.number().int().positive().optional()
|
|
34008
|
+
});
|
|
34009
|
+
var DecideDeploymentRequest = external_exports.object({ reason: external_exports.string().max(500).optional() });
|
|
34010
|
+
|
|
33847
34011
|
// packages/protocol/src/buildkite.ts
|
|
33848
34012
|
var BUILDKITE_TRUST_MODES = ["contributor_controlled", "pipeline_settings", "target_ref_upload"];
|
|
33849
34013
|
var Slug3 = external_exports.string().trim().min(1).max(100).regex(/^[a-z0-9][a-z0-9-_]*$/i, "a Buildkite slug (letters, digits, '-' and '_')");
|
|
@@ -36185,6 +36349,8 @@ function reposGroup(t) {
|
|
|
36185
36349
|
grants: {
|
|
36186
36350
|
list: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/grants`, query: { ...q }, options: o }),
|
|
36187
36351
|
create: (repo, body, o) => t.json({ method: "POST", path: path2`/v1/repos/${repo}/grants`, json: validate2(CreateGrantRequest, body, "CreateGrantRequest"), options: o }),
|
|
36352
|
+
/** Replace the capabilities the grant carries beyond its role (ADR-0088), e.g. `["migrations:approve"]`. */
|
|
36353
|
+
update: (repo, grant, body, o) => t.json({ method: "PATCH", path: path2`/v1/repos/${repo}/grants/${grant}`, json: validate2(UpdateGrantRequest, body, "UpdateGrantRequest"), options: o }),
|
|
36188
36354
|
delete: (repo, grant, o) => t.void({ method: "DELETE", path: path2`/v1/repos/${repo}/grants/${grant}`, options: o })
|
|
36189
36355
|
}
|
|
36190
36356
|
};
|
|
@@ -36387,9 +36553,13 @@ function delegationsGroup(t) {
|
|
|
36387
36553
|
function tasksGroup(t) {
|
|
36388
36554
|
return {
|
|
36389
36555
|
create: (repo, body, o) => t.json({ method: "POST", path: path2`/v1/repos/${repo}/tasks`, json: validate2(CreateTaskRequest, body, "CreateTaskRequest"), options: o }),
|
|
36390
|
-
/**
|
|
36556
|
+
/**
|
|
36557
|
+
* `label` (name or id) and `assignee` (handle or user id) filter the list. `ready: true` keeps only the frontier:
|
|
36558
|
+
* open tasks whose blockers are all done or cancelled (`false`: every other task).
|
|
36559
|
+
*/
|
|
36391
36560
|
list: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/tasks`, query: { ...q }, options: o }),
|
|
36392
36561
|
get: (task, o) => t.json({ method: "GET", path: path2`/v1/tasks/${task}`, options: o }),
|
|
36562
|
+
byNumber: (repo, number4, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/tasks/by-number/${number4}`, options: o }),
|
|
36393
36563
|
/** `version` is required: a stale version fails with VERSION_CONFLICT instead of overwriting someone else's edit. */
|
|
36394
36564
|
update: (task, body, o) => t.json({ method: "PATCH", path: path2`/v1/tasks/${task}`, json: validate2(UpdateTaskRequest, body, "UpdateTaskRequest"), options: o }),
|
|
36395
36565
|
/** Advisory claim; CONFLICT when someone else holds it. */
|
|
@@ -36399,10 +36569,64 @@ function tasksGroup(t) {
|
|
|
36399
36569
|
create: (task, body, o) => t.json({ method: "POST", path: path2`/v1/tasks/${task}/comments`, json: validate2(CreateCommentRequest, body, "CreateCommentRequest"), options: o }),
|
|
36400
36570
|
list: (task, q = {}, o) => t.json({ method: "GET", path: path2`/v1/tasks/${task}/comments`, query: { ...q }, options: o })
|
|
36401
36571
|
},
|
|
36572
|
+
/** "Blocked by" links within the task's repository (ADR-0089). A blocker is a task number or `task_…` id. */
|
|
36573
|
+
dependencies: {
|
|
36574
|
+
list: (task, o) => t.json({ method: "GET", path: path2`/v1/tasks/${task}/dependencies`, options: o }),
|
|
36575
|
+
/** Idempotent: adding an existing link changes nothing. CYCLE_DETECTED if the blocker already waits on this task. */
|
|
36576
|
+
add: (task, body, o) => t.json({ method: "POST", path: path2`/v1/tasks/${task}/dependencies`, json: validate2(AddTaskDependencyRequest, body, "AddTaskDependencyRequest"), options: o }),
|
|
36577
|
+
remove: (task, blocker, o) => t.void({ method: "DELETE", path: path2`/v1/tasks/${task}/dependencies/${String(blocker)}`, options: o })
|
|
36578
|
+
},
|
|
36402
36579
|
addLink: (task, body, o) => t.json({ method: "POST", path: path2`/v1/tasks/${task}/links`, json: validate2(AddLinkRequest, body, "AddLinkRequest"), options: o })
|
|
36403
36580
|
};
|
|
36404
36581
|
}
|
|
36405
36582
|
|
|
36583
|
+
// packages/sdk/src/groups/db-migrations.ts
|
|
36584
|
+
function dbMigrationsGroup(t) {
|
|
36585
|
+
const decide = (decision) => (id2, reason, o) => {
|
|
36586
|
+
const body = typeof reason === "string" ? { reason } : { ...reason?.reason ? { reason: reason.reason } : {}, ...reason?.redeploy ? { redeploy: true } : {} };
|
|
36587
|
+
return t.json({ method: "POST", path: path2`/v1/db-migration-runs/${id2}/${decision}`, json: validate2(DecideDbMigrationRunRequest, body, "DecideDbMigrationRunRequest"), options: o });
|
|
36588
|
+
};
|
|
36589
|
+
return {
|
|
36590
|
+
sources: {
|
|
36591
|
+
list: (repo, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/db-migrations/sources`, options: o }),
|
|
36592
|
+
create: (repo, body, o) => t.json({ method: "POST", path: path2`/v1/repos/${repo}/db-migrations/sources`, json: validate2(CreateDbMigrationSourceRequest, body, "CreateDbMigrationSourceRequest"), options: o }),
|
|
36593
|
+
get: (id2, o) => t.json({ method: "GET", path: path2`/v1/db-migration-sources/${id2}`, options: o }),
|
|
36594
|
+
update: (id2, body, o) => t.json({ method: "PATCH", path: path2`/v1/db-migration-sources/${id2}`, json: validate2(UpdateDbMigrationSourceRequest, body, "UpdateDbMigrationSourceRequest"), options: o }),
|
|
36595
|
+
delete: (id2, o) => t.void({ method: "DELETE", path: path2`/v1/db-migration-sources/${id2}`, options: o }),
|
|
36596
|
+
/** Who may approve the source's runs now (ADR-0088). */
|
|
36597
|
+
approvers: (id2, o) => t.json({ method: "GET", path: path2`/v1/db-migration-sources/${id2}/approvers`, options: o }),
|
|
36598
|
+
/** Protect the database: every run waits for these approvals, and it can no longer apply without approval. */
|
|
36599
|
+
protect: (id2, body, o) => t.json({ method: "PUT", path: path2`/v1/db-migration-sources/${id2}/approval-rule`, json: validate2(PutApprovalRuleRequest, body, "PutApprovalRuleRequest"), options: o }),
|
|
36600
|
+
unprotect: (id2, o) => t.json({ method: "DELETE", path: path2`/v1/db-migration-sources/${id2}/approval-rule`, options: o })
|
|
36601
|
+
},
|
|
36602
|
+
/** Migration directories found in the repository tree; `configured` marks those a source already covers. */
|
|
36603
|
+
suggestions: (repo, ref, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/db-migrations/suggestions`, query: ref ? { ref } : {}, options: o }),
|
|
36604
|
+
/** What applying the source at `ref` would do. `live` reads the database now instead of the last snapshot. */
|
|
36605
|
+
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 }),
|
|
36606
|
+
/** The schema the source's migrations produce at `ref` (the source's branch by default), from replaying them. */
|
|
36607
|
+
schema: (sourceId2, ref, o) => t.json({ method: "GET", path: path2`/v1/db-migration-sources/${sourceId2}/schema`, query: ref ? { ref } : {}, options: o }),
|
|
36608
|
+
/** Compares the database with what its applied migrations produce, now, and stores the result (admins). */
|
|
36609
|
+
checkDrift: (sourceId2, o) => t.json({ method: "POST", path: path2`/v1/db-migration-sources/${sourceId2}/drift`, options: o }),
|
|
36610
|
+
/** Reads the applied versions from the database again and returns the plan at the source's branch. */
|
|
36611
|
+
refresh: (sourceId2, o) => t.json({ method: "POST", path: path2`/v1/db-migration-sources/${sourceId2}/refresh`, options: o }),
|
|
36612
|
+
/**
|
|
36613
|
+
* A manual run (`migrations:manage`). The request is the approval when the caller also holds `migrations:approve` and
|
|
36614
|
+
* the source is not protected; otherwise the run waits for approval. `run` is null when nothing is pending.
|
|
36615
|
+
*/
|
|
36616
|
+
startRun: (sourceId2, body = {}, o) => t.json({ method: "POST", path: path2`/v1/db-migration-sources/${sourceId2}/runs`, json: validate2(StartDbMigrationRunRequest, body, "StartDbMigrationRunRequest"), options: o }),
|
|
36617
|
+
runs: {
|
|
36618
|
+
list: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/db-migrations/runs`, query: { ...q }, options: o }),
|
|
36619
|
+
get: (id2, o) => t.json({ method: "GET", path: path2`/v1/db-migration-runs/${id2}`, options: o })
|
|
36620
|
+
},
|
|
36621
|
+
approve: decide("approve"),
|
|
36622
|
+
reject: decide("reject"),
|
|
36623
|
+
retry: decide("retry"),
|
|
36624
|
+
cancel: decide("cancel"),
|
|
36625
|
+
/** The plan of every source a change touches: the files the change adds (with statements), lint, schema effect. */
|
|
36626
|
+
changePlans: (changeId, o) => t.json({ method: "GET", path: path2`/v1/changes/${changeId}/db-migrations`, options: o })
|
|
36627
|
+
};
|
|
36628
|
+
}
|
|
36629
|
+
|
|
36406
36630
|
// packages/sdk/src/groups/deploy.ts
|
|
36407
36631
|
function deployGroup(t) {
|
|
36408
36632
|
return {
|
|
@@ -36420,12 +36644,22 @@ function deployGroup(t) {
|
|
|
36420
36644
|
remove: (id2, o) => t.json({ method: "DELETE", path: path2`/v1/deploy-targets/${id2}`, options: o }),
|
|
36421
36645
|
/** Deploy the branch head again (or a commit this target deployed before). */
|
|
36422
36646
|
deploy: (id2, body = {}, o) => t.json({ method: "POST", path: path2`/v1/deploy-targets/${id2}/deploy`, json: validate2(RedeployRequest, body, "RedeployRequest"), options: o }),
|
|
36423
|
-
rollback: (id2, body, o) => t.json({ method: "POST", path: path2`/v1/deploy-targets/${id2}/rollback`, json: validate2(RollbackRequest, body, "RollbackRequest"), options: o })
|
|
36647
|
+
rollback: (id2, body, o) => t.json({ method: "POST", path: path2`/v1/deploy-targets/${id2}/rollback`, json: validate2(RollbackRequest, body, "RollbackRequest"), options: o }),
|
|
36648
|
+
/** Who may approve the target's production deploys now (ADR-0088). */
|
|
36649
|
+
approvers: (id2, o) => t.json({ method: "GET", path: path2`/v1/deploy-targets/${id2}/approvers`, options: o }),
|
|
36650
|
+
/** Protect the target: production deploys and rollbacks wait for these approvals. Pass `version` to replace a rule. */
|
|
36651
|
+
protect: (id2, body, o) => t.json({ method: "PUT", path: path2`/v1/deploy-targets/${id2}/approval-rule`, json: validate2(PutApprovalRuleRequest, body, "PutApprovalRuleRequest"), options: o }),
|
|
36652
|
+
/** Remove the rule; deploys waiting for it go on. */
|
|
36653
|
+
unprotect: (id2, o) => t.json({ method: "DELETE", path: path2`/v1/deploy-targets/${id2}/approval-rule`, options: o })
|
|
36424
36654
|
},
|
|
36425
36655
|
deployments: {
|
|
36426
36656
|
list: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/deployments`, query: { ...q }, options: o }),
|
|
36427
36657
|
get: (id2, o) => t.json({ method: "GET", path: path2`/v1/deployments/${id2}`, options: o }),
|
|
36428
36658
|
cancel: (id2, o) => t.json({ method: "POST", path: path2`/v1/deployments/${id2}/cancel`, options: o }),
|
|
36659
|
+
/** Approve a deploy of a protected target (people only; the last approval needed starts it). */
|
|
36660
|
+
approve: (id2, o) => t.json({ method: "POST", path: path2`/v1/deployments/${id2}/approve`, json: {}, options: o }),
|
|
36661
|
+
/** Reject it: the deployment is cancelled. */
|
|
36662
|
+
reject: (id2, reason, o) => t.json({ method: "POST", path: path2`/v1/deployments/${id2}/reject`, json: validate2(DecideDeploymentRequest, reason ? { reason } : {}, "DecideDeploymentRequest"), options: o }),
|
|
36429
36663
|
ofChange: (change, o) => t.json({ method: "GET", path: path2`/v1/changes/${change}/deployments`, options: o })
|
|
36430
36664
|
}
|
|
36431
36665
|
};
|
|
@@ -36475,6 +36709,8 @@ function packagesGroup(t) {
|
|
|
36475
36709
|
list: (repo, q = {}, o) => t.json({ method: "GET", path: path2`/v1/repos/${repo}/packages`, query: { ...q }, options: o }),
|
|
36476
36710
|
get: (image, o) => t.json({ method: "GET", path: path2`/v1/packages/${image}`, options: o }),
|
|
36477
36711
|
versions: (image, q = {}, o) => t.json({ method: "GET", path: path2`/v1/packages/${image}/versions`, query: { ...q }, options: o }),
|
|
36712
|
+
/** Deletes an image with no versions left (`CONFLICT` otherwise). */
|
|
36713
|
+
delete: (image, o) => t.void({ method: "DELETE", path: path2`/v1/packages/${image}`, options: o }),
|
|
36478
36714
|
deleteVersion: (image, digest, o) => t.void({ method: "DELETE", path: path2`/v1/packages/${image}/versions/${digest}`, options: o }),
|
|
36479
36715
|
deleteTag: (image, tag, o) => t.void({ method: "DELETE", path: path2`/v1/packages/${image}/tags/${tag}`, options: o }),
|
|
36480
36716
|
usage: (org, o) => t.json({ method: "GET", path: path2`/v1/orgs/${org}/registry`, options: o }),
|
|
@@ -36569,6 +36805,7 @@ function createClient(options) {
|
|
|
36569
36805
|
secretSyncs: secretSyncsGroup(t),
|
|
36570
36806
|
grants: grantsGroup(t),
|
|
36571
36807
|
deploy: deployGroup(t),
|
|
36808
|
+
dbMigrations: dbMigrationsGroup(t),
|
|
36572
36809
|
codeReview: codeReviewGroup(t),
|
|
36573
36810
|
releases: releasesGroup(t),
|
|
36574
36811
|
packages: packagesGroup(t),
|
|
@@ -36901,7 +37138,7 @@ function invalidAgentToolFlag(flag) {
|
|
|
36901
37138
|
}
|
|
36902
37139
|
|
|
36903
37140
|
// packages/cli/package.json
|
|
36904
|
-
var version2 = "0.
|
|
37141
|
+
var version2 = "0.19.0";
|
|
36905
37142
|
|
|
36906
37143
|
// packages/cli/src/brand.ts
|
|
36907
37144
|
var BRAND = {
|
|
@@ -38638,18 +38875,13 @@ async function gitConfigPath(deps) {
|
|
|
38638
38875
|
}
|
|
38639
38876
|
|
|
38640
38877
|
// packages/cli/src/work-ref.ts
|
|
38641
|
-
var MAX_PAGES = 20;
|
|
38642
|
-
var PAGE = 100;
|
|
38643
38878
|
async function issueByNumber(client, repo, n) {
|
|
38644
|
-
|
|
38645
|
-
|
|
38646
|
-
|
|
38647
|
-
|
|
38648
|
-
|
|
38649
|
-
if (!page.next_cursor) return null;
|
|
38650
|
-
cursor2 = page.next_cursor;
|
|
38879
|
+
try {
|
|
38880
|
+
return (await client.tasks.byNumber(repo, n)).id;
|
|
38881
|
+
} catch (e) {
|
|
38882
|
+
if (isBrnchApiError(e) && e.code === "NOT_FOUND") return null;
|
|
38883
|
+
throw e;
|
|
38651
38884
|
}
|
|
38652
|
-
return null;
|
|
38653
38885
|
}
|
|
38654
38886
|
async function changeByNumber(client, repo, n) {
|
|
38655
38887
|
try {
|
|
@@ -38675,6 +38907,21 @@ async function resolveChangeId(spec, client, repo) {
|
|
|
38675
38907
|
if (!id2) throw notFound(`change #${n}`, repo.spec());
|
|
38676
38908
|
return id2;
|
|
38677
38909
|
}
|
|
38910
|
+
var TASK_ARG = "task number (12 or #12) in --repo, or a task_\u2026 id";
|
|
38911
|
+
async function resolveTaskId(spec, client, repo) {
|
|
38912
|
+
const text2 = spec.trim();
|
|
38913
|
+
const rest = text2.startsWith("issue:") ? text2.slice("issue:".length) : text2;
|
|
38914
|
+
if (rest.startsWith("task_")) return rest;
|
|
38915
|
+
const digits = /^#?(\d{1,9})$/.exec(rest)?.[1];
|
|
38916
|
+
if (!digits) {
|
|
38917
|
+
if (rest.startsWith("chg_") || rest.startsWith("change:")) throw usage(`${spec} is a change, not a task`);
|
|
38918
|
+
throw usage(`cannot read task "${spec}": expected a number (12 or #12) or a task_\u2026 id`);
|
|
38919
|
+
}
|
|
38920
|
+
const n = Number(digits);
|
|
38921
|
+
const id2 = await issueByNumber(client, await repo.id(), n);
|
|
38922
|
+
if (!id2) throw notFound(`task #${n}`, repo.spec());
|
|
38923
|
+
return id2;
|
|
38924
|
+
}
|
|
38678
38925
|
async function resolveWorkRef(spec, client, repoId) {
|
|
38679
38926
|
const m = /^(issue|change):(.+)$/.exec(spec.trim());
|
|
38680
38927
|
const kind = m?.[1] ?? null;
|
|
@@ -38972,6 +39219,67 @@ ${final.join("\n")}
|
|
|
38972
39219
|
deps.stderr.write("\n");
|
|
38973
39220
|
}
|
|
38974
39221
|
|
|
39222
|
+
// packages/cli/src/program-status.ts
|
|
39223
|
+
var MSG_MAX_BYTES = 512;
|
|
39224
|
+
function encodeMsg(text2) {
|
|
39225
|
+
let line2 = text2.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").trim();
|
|
39226
|
+
const enc3 = new TextEncoder();
|
|
39227
|
+
while (enc3.encode(line2).length > MSG_MAX_BYTES) line2 = [...line2].slice(0, -1).join("");
|
|
39228
|
+
return Buffer.from(line2, "utf8").toString("base64");
|
|
39229
|
+
}
|
|
39230
|
+
function programStatus(r) {
|
|
39231
|
+
const pairs = [`state=${r.state}`, `app=${BRAND.cli}`];
|
|
39232
|
+
if (r.state === "blocked") pairs.push(`kind=${r.kind}`);
|
|
39233
|
+
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)))}`);
|
|
39234
|
+
if (r.msg) pairs.push(`msg=${encodeMsg(r.msg)}`);
|
|
39235
|
+
return `\x1B]7501;${pairs.join(":")}\x1B\\`;
|
|
39236
|
+
}
|
|
39237
|
+
function programStatusEnabled(deps, agent) {
|
|
39238
|
+
const off = deps.env[envName("PROGRAM_STATUS")]?.trim().toLowerCase();
|
|
39239
|
+
if (off === "0" || off === "false" || off === "off" || off === "no") return false;
|
|
39240
|
+
return deps.stderrIsTTY && !agent && !deps.env.CI && deps.env.TERM !== "dumb";
|
|
39241
|
+
}
|
|
39242
|
+
var silentStatus = {
|
|
39243
|
+
working: () => {
|
|
39244
|
+
},
|
|
39245
|
+
blocked: () => {
|
|
39246
|
+
},
|
|
39247
|
+
ask: () => () => {
|
|
39248
|
+
},
|
|
39249
|
+
finish: () => {
|
|
39250
|
+
}
|
|
39251
|
+
};
|
|
39252
|
+
function statusReporter(out) {
|
|
39253
|
+
let current = null;
|
|
39254
|
+
let waited = false;
|
|
39255
|
+
let last = "";
|
|
39256
|
+
const send = (r) => {
|
|
39257
|
+
const s = programStatus(r);
|
|
39258
|
+
if (s === last) return;
|
|
39259
|
+
last = s;
|
|
39260
|
+
out.write(s);
|
|
39261
|
+
};
|
|
39262
|
+
const report3 = (r) => {
|
|
39263
|
+
current = r;
|
|
39264
|
+
waited = true;
|
|
39265
|
+
send(r);
|
|
39266
|
+
};
|
|
39267
|
+
return {
|
|
39268
|
+
working: (msg, progress) => report3({ state: "working", msg, ...progress !== void 0 ? { progress } : {} }),
|
|
39269
|
+
blocked: (kind, msg) => report3({ state: "blocked", kind, msg }),
|
|
39270
|
+
ask() {
|
|
39271
|
+
send({ state: "blocked", kind: "question", msg: "Waiting for your answer" });
|
|
39272
|
+
return () => send(current ?? { state: "working" });
|
|
39273
|
+
},
|
|
39274
|
+
finish(ok, msg) {
|
|
39275
|
+
if (!waited) return;
|
|
39276
|
+
send({ state: ok ? "done" : "error", msg });
|
|
39277
|
+
waited = false;
|
|
39278
|
+
current = null;
|
|
39279
|
+
}
|
|
39280
|
+
};
|
|
39281
|
+
}
|
|
39282
|
+
|
|
38975
39283
|
// packages/cli/src/context.ts
|
|
38976
39284
|
async function findProjectFile(deps) {
|
|
38977
39285
|
let dir = deps.cwd;
|
|
@@ -39239,6 +39547,9 @@ async function createContext(deps, globals, state2) {
|
|
|
39239
39547
|
function changeId(spec, pref = "agent") {
|
|
39240
39548
|
return resolveChangeId(spec, client(pref), { id: () => repoId(void 0, pref), spec: () => repoSpec() });
|
|
39241
39549
|
}
|
|
39550
|
+
function taskId(spec, pref = "agent") {
|
|
39551
|
+
return resolveTaskId(spec, client(pref), { id: () => repoId(void 0, pref), spec: () => repoSpec() });
|
|
39552
|
+
}
|
|
39242
39553
|
function org() {
|
|
39243
39554
|
const o = targetOrg ?? storedCred?.account?.default_org;
|
|
39244
39555
|
if (!o) {
|
|
@@ -39309,6 +39620,7 @@ async function createContext(deps, globals, state2) {
|
|
|
39309
39620
|
repoSpec,
|
|
39310
39621
|
repoId,
|
|
39311
39622
|
changeId,
|
|
39623
|
+
taskId,
|
|
39312
39624
|
org,
|
|
39313
39625
|
sessionId,
|
|
39314
39626
|
info,
|
|
@@ -39316,6 +39628,8 @@ async function createContext(deps, globals, state2) {
|
|
|
39316
39628
|
busy: (label2) => {
|
|
39317
39629
|
if (motion) state2.spinner?.start(label2);
|
|
39318
39630
|
},
|
|
39631
|
+
/** Tell the terminal what the command waits on (program-status.ts): the server (`working`) or the person (`blocked`). */
|
|
39632
|
+
status: state2.status ?? silentStatus,
|
|
39319
39633
|
/** Clear the spinner before handing the terminal to something that does not write through `deps` (rt.invoke). */
|
|
39320
39634
|
idle: () => state2.spinner?.stop(),
|
|
39321
39635
|
result,
|
|
@@ -39426,6 +39740,15 @@ function cell(v) {
|
|
|
39426
39740
|
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
39427
39741
|
return JSON.stringify(v);
|
|
39428
39742
|
}
|
|
39743
|
+
function actorLabel(a) {
|
|
39744
|
+
const person = a.handle ? `@${a.handle}` : a.user_name ?? null;
|
|
39745
|
+
if (a.type === "human") return person ?? a.display_name ?? `${a.type} ${a.id}`;
|
|
39746
|
+
if (a.type === "agent") {
|
|
39747
|
+
const name = a.display_name ?? `agent ${a.id}`;
|
|
39748
|
+
return person ? `${name} (agent for ${person})` : name;
|
|
39749
|
+
}
|
|
39750
|
+
return a.display_name ?? `${a.type} ${a.id}`;
|
|
39751
|
+
}
|
|
39429
39752
|
var tildePath = (path3, home2) => home2 && path3.startsWith(`${home2}/`) ? `~${path3.slice(home2.length)}` : path3;
|
|
39430
39753
|
function kv(obj3, keys) {
|
|
39431
39754
|
const o = obj3 ?? {};
|
|
@@ -39525,7 +39848,7 @@ ${snippet}
|
|
|
39525
39848
|
|
|
39526
39849
|
// packages/cli/src/commands/session.ts
|
|
39527
39850
|
var renderSession = (s) => kv(
|
|
39528
|
-
{ ...s, agent: `${s.agent.name} (${s.agent.id})`, sponsor:
|
|
39851
|
+
{ ...s, agent: `${s.agent.name} (${s.agent.id})`, sponsor: actorLabel(s.sponsor) },
|
|
39529
39852
|
["id", "title", "agent", "state", "presence", "attention", "repository_id", "task_id", "claimed_task_id", "sponsor", "scopes", "open_blockers", "last_seen_at", "expires_at", "max_expires_at", "end_reason"]
|
|
39530
39853
|
);
|
|
39531
39854
|
var renderStarted = (s) => `${renderSession(s.session)}
|
|
@@ -41030,7 +41353,7 @@ function sessionLines(s, now, you) {
|
|
|
41030
41353
|
...s.paths.length ? [` touching: ${pathList(s.paths)}`] : []
|
|
41031
41354
|
];
|
|
41032
41355
|
}
|
|
41033
|
-
var claimLine = (c8) => ` ${c8.key} ${c8.holder
|
|
41356
|
+
var claimLine = (c8) => ` ${c8.key} ${actorLabel(c8.holder)}${c8.session_id ? ` (${c8.session_id})` : ""}${c8.change ? ` \xB7 change #${c8.change.number}` : ""}${c8.note ? ` \xB7 ${c8.note}` : ""}`;
|
|
41034
41357
|
function renderStatus(st, now = Date.now()) {
|
|
41035
41358
|
const others = st.sessions.filter((s) => s.id !== st.you);
|
|
41036
41359
|
const out = [others.length ? `${others.length} other agent session${others.length === 1 ? "" : "s"} working in this repository.` : "No other agent sessions are working in this repository."];
|
|
@@ -41585,9 +41908,9 @@ async function runHook(deps, state2, tool, eventArg) {
|
|
|
41585
41908
|
let meta3 = await readMeta(deps.env, id2);
|
|
41586
41909
|
if (!meta3) {
|
|
41587
41910
|
const baseUrl = await baseUrlOf(deps);
|
|
41588
|
-
const
|
|
41589
|
-
if (!
|
|
41590
|
-
meta3 = { client_key: clientKey, tool: t, model: null, repo:
|
|
41911
|
+
const resolved2 = await resolveRepo(deps, info.cwd ?? deps.cwd, baseUrl);
|
|
41912
|
+
if (!resolved2) return;
|
|
41913
|
+
meta3 = { client_key: clientKey, tool: t, model: null, repo: resolved2.repo, repo_root: resolved2.root, base_url: baseUrl, started_at: new Date(now).toISOString(), last_event_at: new Date(now).toISOString(), ended: false };
|
|
41591
41914
|
}
|
|
41592
41915
|
if (info.model && !meta3.model) meta3.model = redactText(info.model).text.slice(0, 200);
|
|
41593
41916
|
if (!meta3.model && t === "claude-code" && typeof payload.transcript_path === "string" && /^(Stop|SessionEnd)$/.test(info.event)) meta3.model = await modelFromTranscript(deps, payload.transcript_path);
|
|
@@ -41791,22 +42114,22 @@ function registerCapture(program2, rt) {
|
|
|
41791
42114
|
act(rt, async (ctx, o, words) => {
|
|
41792
42115
|
const summary = words.join(" ").trim();
|
|
41793
42116
|
if (!summary) throw usage("decision text is required");
|
|
41794
|
-
const
|
|
41795
|
-
if (!
|
|
42117
|
+
const resolved2 = await resolveRepo(ctx.deps, ctx.deps.cwd, ctx.baseUrl);
|
|
42118
|
+
if (!resolved2) throw usage(`this directory is not a repository hosted on ${ctx.baseUrl}`);
|
|
41796
42119
|
const now = Date.now();
|
|
41797
|
-
const live = (await listSpool(ctx.deps.env)).filter((s) => s.meta.repo_root ===
|
|
42120
|
+
const live = (await listSpool(ctx.deps.env)).filter((s) => s.meta.repo_root === resolved2.root && !s.meta.ended && now - Date.parse(s.meta.last_event_at) < 12 * 36e5).sort((a, b) => b.meta.last_event_at.localeCompare(a.meta.last_event_at))[0];
|
|
41798
42121
|
let id2;
|
|
41799
42122
|
let meta3;
|
|
41800
42123
|
if (live) {
|
|
41801
42124
|
id2 = live.id;
|
|
41802
42125
|
meta3 = live.meta;
|
|
41803
42126
|
} else {
|
|
41804
|
-
const key = `manual:${spoolId(
|
|
42127
|
+
const key = `manual:${spoolId(resolved2.root)}:${new Date(now).toISOString().slice(0, 10)}`;
|
|
41805
42128
|
id2 = spoolId(key);
|
|
41806
|
-
meta3 = await readMeta(ctx.deps.env, id2) ?? { client_key: key, tool: "manual", model: null, repo:
|
|
42129
|
+
meta3 = await readMeta(ctx.deps.env, id2) ?? { client_key: key, tool: "manual", model: null, repo: resolved2.repo, repo_root: resolved2.root, base_url: ctx.baseUrl, started_at: new Date(now).toISOString(), last_event_at: new Date(now).toISOString(), ended: false };
|
|
41807
42130
|
}
|
|
41808
42131
|
const paths = (o.file ?? []).map((f) => {
|
|
41809
|
-
const rel = relative2(canonical(
|
|
42132
|
+
const rel = relative2(canonical(resolved2.root), canonical(isAbsolute6(f) ? f : resolve4(ctx.deps.cwd, f)));
|
|
41810
42133
|
if (!rel || rel.startsWith("..") || isAbsolute6(rel)) throw usage(`${f} is outside the repository`);
|
|
41811
42134
|
return rel.split(sep2).join("/");
|
|
41812
42135
|
});
|
|
@@ -41850,22 +42173,22 @@ ${report3.errors.join("\n")}` : ""}`);
|
|
|
41850
42173
|
rt.state.noUpdateHint = true;
|
|
41851
42174
|
const [cmd, ...args] = argv;
|
|
41852
42175
|
if (!cmd) throw usage(`usage: ${BRAND.cli} capture wrap -- <command> [args\u2026]`);
|
|
41853
|
-
const
|
|
42176
|
+
const resolved2 = await resolveRepo(ctx.deps, ctx.deps.cwd, ctx.baseUrl);
|
|
41854
42177
|
const cfg = await readCaptureConfig(ctx.deps.env);
|
|
41855
|
-
const before =
|
|
41856
|
-
if (!
|
|
42178
|
+
const before = resolved2 && cfg.enabled ? await snapshotWorktree(ctx.deps, resolved2.root) : null;
|
|
42179
|
+
if (!resolved2) ctx.info(`not a repository hosted on ${ctx.baseUrl}: running without capture`);
|
|
41857
42180
|
const code = await runInherit(cmd, args, ctx.deps.cwd, ctx.deps.env);
|
|
41858
42181
|
ctx.setExit(code);
|
|
41859
|
-
if (!
|
|
41860
|
-
const after = await snapshotWorktree(ctx.deps,
|
|
42182
|
+
if (!resolved2 || !before) return;
|
|
42183
|
+
const after = await snapshotWorktree(ctx.deps, resolved2.root);
|
|
41861
42184
|
if (!after) return;
|
|
41862
|
-
const diff = await ctx.deps.sys.run("git", ["-c", "core.hooksPath=/dev/null", "diff", "--no-color", "--no-renames", "--no-ext-diff", "-U0", before, after], { cwd:
|
|
42185
|
+
const diff = await ctx.deps.sys.run("git", ["-c", "core.hooksPath=/dev/null", "diff", "--no-color", "--no-renames", "--no-ext-diff", "-U0", before, after], { cwd: resolved2.root, timeoutMs: 12e4 });
|
|
41863
42186
|
const label2 = (o.tool ?? cmd.split(/[\\/]/).pop() ?? "command").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40);
|
|
41864
42187
|
const now = Date.now();
|
|
41865
42188
|
const key = `wrapped:${label2}:${randomBytes4(8).toString("hex")}`;
|
|
41866
42189
|
const id2 = spoolId(key);
|
|
41867
|
-
const meta3 = { client_key: key, tool: "wrapped", model: o.model ? redactText(o.model).text.slice(0, 200) : null, repo:
|
|
41868
|
-
const edits = await editsFromDiff(diff.stdout, { repoRoot:
|
|
42190
|
+
const meta3 = { client_key: key, tool: "wrapped", model: o.model ? redactText(o.model).text.slice(0, 200) : null, repo: resolved2.repo, repo_root: resolved2.root, base_url: ctx.baseUrl, started_at: new Date(now).toISOString(), last_event_at: new Date(now).toISOString(), ended: true };
|
|
42191
|
+
const edits = await editsFromDiff(diff.stdout, { repoRoot: resolved2.root, config: cfg, readFile: (p) => ctx.deps.readFile(p).catch(() => null) }, label2);
|
|
41869
42192
|
await writeMeta(ctx.deps.env, id2, meta3);
|
|
41870
42193
|
await appendEvents(ctx.deps.env, id2, [{ type: "capture.started", payload: { source: label2 } }, ...edits, { type: "capture.ended", payload: { reason: `exit ${code}` } }], now);
|
|
41871
42194
|
startFlush(ctx.deps);
|
|
@@ -42198,6 +42521,7 @@ function sameOrigin(ctx, start) {
|
|
|
42198
42521
|
}
|
|
42199
42522
|
async function awaitStepUp(ctx, start) {
|
|
42200
42523
|
const deadline = Date.parse(start.expires_at);
|
|
42524
|
+
ctx.status.blocked("auth", `Confirm it's you in the browser (code ${start.user_code})`);
|
|
42201
42525
|
for (; ; ) {
|
|
42202
42526
|
await ctx.deps.sleep(start.interval * 1e3);
|
|
42203
42527
|
const s = await ctx.client("user").auth.stepUp.status(start.id);
|
|
@@ -42395,6 +42719,7 @@ var accountChoice = (a) => ({
|
|
|
42395
42719
|
});
|
|
42396
42720
|
async function finishLogin(ctx, start, agentRuntime, hooks) {
|
|
42397
42721
|
const oauth = ctx.client("user", { anonymous: true }).oauth;
|
|
42722
|
+
ctx.status.blocked("auth", `Approve the sign-in in your browser (code ${start.user_code})`);
|
|
42398
42723
|
let tok;
|
|
42399
42724
|
try {
|
|
42400
42725
|
tok = await pollDeviceToken(oauth, start, CLI_OAUTH_CLIENT_ID, { sleep: ctx.deps.sleep });
|
|
@@ -43467,6 +43792,7 @@ var Wizard = class {
|
|
|
43467
43792
|
/** The spinner says what the step is checking until it prints, asks, or hands over to a composed command. */
|
|
43468
43793
|
busy(step2) {
|
|
43469
43794
|
this.ctx.busy(STEP_BUSY[step2]);
|
|
43795
|
+
this.ctx.status.working(STEP_BUSY[step2]);
|
|
43470
43796
|
}
|
|
43471
43797
|
/** Global flags every composed command must keep (same server, credential and account). */
|
|
43472
43798
|
globalArgs() {
|
|
@@ -44367,6 +44693,7 @@ var Quickstart = class {
|
|
|
44367
44693
|
/** The board row says what is happening now (no-op without a board). */
|
|
44368
44694
|
running(row, text2) {
|
|
44369
44695
|
this.board?.run(row, text2);
|
|
44696
|
+
this.ctx.status.working(text2);
|
|
44370
44697
|
}
|
|
44371
44698
|
/** A step's outcome: kept for the result, and shown on the board (`show`) or as a line. */
|
|
44372
44699
|
record(step2, status, detail, show2 = detail) {
|
|
@@ -44681,6 +45008,7 @@ async function stepDeploy(q, dir, ns, name, o, home2) {
|
|
|
44681
45008
|
q.running("deploy", "waiting for Cloudflare to be connected");
|
|
44682
45009
|
q.board.hint(`${todo} ${q.errPaint.dim("(Ctrl-C stops; the repository is ready either way)")}`);
|
|
44683
45010
|
} else ctx.info(` ${todo}. Waiting for it (Ctrl-C stops; the repository is ready either way)\u2026`);
|
|
45011
|
+
ctx.status.blocked("auth", "Connect Cloudflare in the browser");
|
|
44684
45012
|
const deadline2 = ctx.deps.sys.now() + o.connectTimeout * 1e3;
|
|
44685
45013
|
let ready = false;
|
|
44686
45014
|
while (!ready && ctx.deps.sys.now() < deadline2) {
|
|
@@ -45701,12 +46029,35 @@ function seedJson(p) {
|
|
|
45701
46029
|
}
|
|
45702
46030
|
|
|
45703
46031
|
// packages/cli/src/commands/task.ts
|
|
45704
|
-
var
|
|
46032
|
+
var BLOCKER_ARG = "the task it is blocked by: number (12 or #12) or task_\u2026 id";
|
|
46033
|
+
var resolved = (t) => t.state === "done" || t.state === "cancelled";
|
|
46034
|
+
var summaries = (list3) => list3.map((t) => `#${t.number} ${t.title} (${t.state})`).join("\n ") || "(none)";
|
|
46035
|
+
function renderDependencies(d) {
|
|
46036
|
+
const more = d.blocks_count > d.blocks.length ? `
|
|
46037
|
+
\u2026 and ${d.blocks_count - d.blocks.length} more` : "";
|
|
46038
|
+
return `Ready: ${d.is_ready ? "yes" : "no"}
|
|
46039
|
+
Blocked by:
|
|
46040
|
+
${summaries(d.blocked_by)}
|
|
46041
|
+
Blocks:
|
|
46042
|
+
${summaries(d.blocks)}${more}`;
|
|
46043
|
+
}
|
|
46044
|
+
var renderTask = (t) => kv({ ...t, claimed_by: t.claim ? actorLabel(t.claim.actor) : null }, ["id", "number", "title", "state", "owner_id", "claimed_by", "labels", "updated_at", "version"]) + (t.blocked_by?.length || t.blocks_count ? `
|
|
46045
|
+
|
|
46046
|
+
${renderDependencies(t)}` : "") + (t.description ? `
|
|
45705
46047
|
|
|
45706
46048
|
${t.description}` : "") + (t.acceptance_criteria.length ? `
|
|
45707
46049
|
|
|
45708
46050
|
Acceptance criteria:
|
|
45709
46051
|
${t.acceptance_criteria.map((c8) => ` - ${c8}`).join("\n")}` : "");
|
|
46052
|
+
var waitingOn = (t) => (t.blocked_by ?? []).filter((b) => !resolved(b)).map((b) => `#${b.number}`).join(" ");
|
|
46053
|
+
function blockersFrom(values, allowNone) {
|
|
46054
|
+
if (!values) return void 0;
|
|
46055
|
+
if (values.length === 1 && values[0] === "none") {
|
|
46056
|
+
if (!allowNone) throw usage("--blocked-by none only clears blockers on task update");
|
|
46057
|
+
return [];
|
|
46058
|
+
}
|
|
46059
|
+
return values.map((v) => v.trim().replace(/^issue:/, ""));
|
|
46060
|
+
}
|
|
45710
46061
|
async function bodyFrom(ctx, o, required2 = true) {
|
|
45711
46062
|
if (o.body !== void 0 && o.file !== void 0) throw usage("pass either --body or --file, not both");
|
|
45712
46063
|
if (o.file !== void 0) return readText(fileSpec(o.file), ctx.input);
|
|
@@ -45716,23 +46067,24 @@ async function bodyFrom(ctx, o, required2 = true) {
|
|
|
45716
46067
|
}
|
|
45717
46068
|
function registerTask(program2, rt) {
|
|
45718
46069
|
const task = program2.command("task").description("Tasks (issues) in --repo");
|
|
45719
|
-
task.command("create").description("Create a task").requiredOption("--title <title>", "title").option("--body <text>", "description (Markdown; @file or - for stdin)").option("--file <path>", "read the description from a file or - for stdin").option("--criterion <text>", "acceptance criterion (repeatable)", repeat).option("--label <label>", "label (repeatable)", repeat).action(
|
|
46070
|
+
task.command("create").description("Create a task").requiredOption("--title <title>", "title").option("--body <text>", "description (Markdown; @file or - for stdin)").option("--file <path>", "read the description from a file or - for stdin").option("--criterion <text>", "acceptance criterion (repeatable)", repeat).option("--label <label>", "label (repeatable)", repeat).option("--blocked-by <task>", `${BLOCKER_ARG} (repeatable)`, repeat).action(
|
|
45720
46071
|
act(rt, async (ctx, o) => {
|
|
45721
46072
|
const description = await bodyFrom(ctx, o, false);
|
|
45722
|
-
const t = await ctx.client().tasks.create(await ctx.repoId(), { title: o.title, description, acceptance_criteria: o.criterion, labels: o.label });
|
|
46073
|
+
const t = await ctx.client().tasks.create(await ctx.repoId(), { title: o.title, description, acceptance_criteria: o.criterion, labels: o.label, blocked_by: blockersFrom(o.blockedBy, false) });
|
|
45723
46074
|
ctx.result(t, renderTask);
|
|
45724
46075
|
})
|
|
45725
46076
|
);
|
|
45726
|
-
task.command("list").description("List tasks").option("--state <state>", "open | in_progress | blocked | done | cancelled").option("--limit <n>", "page size", (v) => parseIntOption(v, "--limit")).option("--cursor <cursor>", "page cursor").action(
|
|
46077
|
+
task.command("list").description("List tasks").option("--state <state>", "open | in_progress | blocked | done | cancelled").option("--ready", "only tasks ready to start: open, with every blocker done or cancelled").option("--limit <n>", "page size", (v) => parseIntOption(v, "--limit")).option("--cursor <cursor>", "page cursor").action(
|
|
45727
46078
|
act(rt, async (ctx, o) => {
|
|
45728
|
-
const page = await ctx.client().tasks.list(await ctx.repoId(), { state: o.state, limit: o.limit, cursor: o.cursor });
|
|
45729
|
-
ctx.result(page, (v) => listWithCursor(v, ["number", "state", "title", "id"]));
|
|
46079
|
+
const page = await ctx.client().tasks.list(await ctx.repoId(), { state: o.state, ...o.ready ? { ready: true } : {}, limit: o.limit, cursor: o.cursor });
|
|
46080
|
+
ctx.result(page, (v) => listWithCursor({ ...v, items: v.items.map((t) => ({ ...t, waiting_on: waitingOn(t) })) }, ["number", "state", "title", "waiting_on", "id"]));
|
|
45730
46081
|
})
|
|
45731
46082
|
);
|
|
45732
|
-
task.command("view
|
|
45733
|
-
task.command("update
|
|
45734
|
-
act(rt, async (ctx, o, [
|
|
46083
|
+
task.command("view").argument("<task>", TASK_ARG).description("Show a task, with what it is blocked by and what it blocks").action(act(rt, async (ctx, _o, [spec]) => ctx.result(await ctx.client().tasks.get(await ctx.taskId(spec)), renderTask)));
|
|
46084
|
+
task.command("update").argument("<task>", TASK_ARG).description("Update fields; uses the current version unless --expected-version pins it (then stale edits fail with exit 5)").option("--title <title>", "title").option("--body <text>", "description").option("--file <path>", "description from a file or -").option("--state <state>", "open | in_progress | blocked | done | cancelled").option("--owner <user-id>", "owner user id ('none' to clear)").option("--label <label>", "replace labels (repeatable)", repeat).option("--blocked-by <task>", `replace the blockers: ${BLOCKER_ARG} (repeatable; 'none' to clear)`, repeat).option("--expected-version <n>", "expected version (optimistic concurrency)", (v) => parseIntOption(v, "--expected-version")).action(
|
|
46085
|
+
act(rt, async (ctx, o, [spec]) => {
|
|
45735
46086
|
const c8 = ctx.client();
|
|
46087
|
+
const id2 = await ctx.taskId(spec);
|
|
45736
46088
|
const version4 = o.expectedVersion ?? (await c8.tasks.get(id2)).version;
|
|
45737
46089
|
const t = await c8.tasks.update(id2, {
|
|
45738
46090
|
title: o.title,
|
|
@@ -45740,17 +46092,35 @@ function registerTask(program2, rt) {
|
|
|
45740
46092
|
state: o.state,
|
|
45741
46093
|
owner_id: o.owner === void 0 ? void 0 : o.owner === "none" ? null : o.owner,
|
|
45742
46094
|
labels: o.label,
|
|
46095
|
+
blocked_by: blockersFrom(o.blockedBy, true),
|
|
45743
46096
|
version: version4
|
|
45744
46097
|
});
|
|
45745
46098
|
ctx.result(t, renderTask);
|
|
45746
46099
|
})
|
|
45747
46100
|
);
|
|
45748
|
-
task.command("
|
|
45749
|
-
|
|
45750
|
-
|
|
45751
|
-
|
|
46101
|
+
task.command("depend").argument("<task>", TASK_ARG).argument("[blocker]", BLOCKER_ARG).description("Mark <task> as blocked by [blocker] (same repository); without it, show what <task> is blocked by and blocks").action(
|
|
46102
|
+
act(rt, async (ctx, _o, [spec, blocker]) => {
|
|
46103
|
+
const c8 = ctx.client();
|
|
46104
|
+
const id2 = await ctx.taskId(spec);
|
|
46105
|
+
const deps = blocker ? await c8.tasks.dependencies.add(id2, { blocker: blockersFrom([blocker], false)[0] }) : await c8.tasks.dependencies.list(id2);
|
|
46106
|
+
ctx.result(deps, renderDependencies);
|
|
46107
|
+
})
|
|
46108
|
+
);
|
|
46109
|
+
task.command("undepend").argument("<task>", TASK_ARG).argument("<blocker>", BLOCKER_ARG).description("Remove <blocker> from what <task> is blocked by").action(
|
|
46110
|
+
act(rt, async (ctx, _o, [spec, blocker]) => {
|
|
46111
|
+
const id2 = await ctx.taskId(spec);
|
|
46112
|
+
const ref = blockersFrom([blocker], false)[0];
|
|
46113
|
+
await ctx.client().tasks.dependencies.remove(id2, ref);
|
|
46114
|
+
ctx.result({ task_id: id2, removed: ref }, `${spec} is no longer blocked by ${ref}`);
|
|
46115
|
+
})
|
|
46116
|
+
);
|
|
46117
|
+
task.command("claim").argument("<task>", TASK_ARG).description("Advisory claim (exit 5 if someone else holds it)").option("--session <id>", "claim on behalf of this agent session").action(act(rt, async (ctx, o, [spec]) => ctx.result(await ctx.client().tasks.claim(await ctx.taskId(spec), { session_id: o.session }), renderTask)));
|
|
46118
|
+
task.command("release").argument("<task>", TASK_ARG).description("Release your claim").option("--session <id>", "agent session that holds the claim").action(act(rt, async (ctx, o, [spec]) => ctx.result(await ctx.client().tasks.release(await ctx.taskId(spec), { session_id: o.session }), renderTask)));
|
|
46119
|
+
task.command("comment").argument("<task>", TASK_ARG).description("Add a comment").option("--body <text>", "comment (Markdown; @file or - for stdin)").option("--file <path>", "read the comment from a file or -").action(
|
|
46120
|
+
act(rt, async (ctx, o, [spec]) => {
|
|
46121
|
+
const id2 = await ctx.taskId(spec);
|
|
45752
46122
|
const comment = await ctx.client().tasks.comments.create(id2, { body: await bodyFrom(ctx, o) });
|
|
45753
|
-
ctx.result(comment, `Comment ${comment.id} added to ${
|
|
46123
|
+
ctx.result(comment, `Comment ${comment.id} added to ${spec}`);
|
|
45754
46124
|
})
|
|
45755
46125
|
);
|
|
45756
46126
|
}
|
|
@@ -46032,7 +46402,7 @@ var how = (strategy) => strategy ? `with ${strategy}` : "with the policy's defau
|
|
|
46032
46402
|
function renderAutoMerge(a, change, p, now) {
|
|
46033
46403
|
const ref = change ? String(change.number) : a.change_id;
|
|
46034
46404
|
const name = change ? `${p.bold(`#${change.number}`)} ${change.title}` : p.bold(a.change_id);
|
|
46035
|
-
const who3 = a.enabled_by ? a.enabled_by
|
|
46405
|
+
const who3 = a.enabled_by ? actorLabel(a.enabled_by) : null;
|
|
46036
46406
|
const out = [];
|
|
46037
46407
|
switch (a.state) {
|
|
46038
46408
|
case "enabled":
|
|
@@ -46052,7 +46422,7 @@ function renderAutoMerge(a, change, p, now) {
|
|
|
46052
46422
|
out.push(p.dim(` Turn it on: ${BRAND.cli} change auto-merge ${ref}`));
|
|
46053
46423
|
}
|
|
46054
46424
|
if (a.history.length) {
|
|
46055
|
-
const rows2 = a.history.map((h) => ({ when: ago2(h.at, now), action: h.action, actor: h.actor
|
|
46425
|
+
const rows2 = a.history.map((h) => ({ when: ago2(h.at, now), action: h.action, actor: actorLabel(h.actor), reason: h.reason }));
|
|
46056
46426
|
const w = { when: Math.max(...rows2.map((r) => r.when.length)), action: Math.max(...rows2.map((r) => r.action.length)) };
|
|
46057
46427
|
out.push("", p.bold("History"));
|
|
46058
46428
|
for (const r of rows2) out.push(` ${p.dim(r.when.padEnd(w.when))} ${r.action.padEnd(w.action)} ${r.actor}${r.reason ? p.dim(` \xB7 ${r.reason}`) : ""}`);
|
|
@@ -46118,11 +46488,11 @@ function stepOf(state2, previous) {
|
|
|
46118
46488
|
|
|
46119
46489
|
// packages/cli/src/commands/change.ts
|
|
46120
46490
|
var renderChange = (c8) => kv(
|
|
46121
|
-
{ ...c8, source: c8.source.workspace_id ?? c8.source.ref, author:
|
|
46491
|
+
{ ...c8, source: c8.source.workspace_id ?? c8.source.ref, author: actorLabel(c8.author), revision: `#${c8.current_revision.number} ${c8.current_revision.id}` },
|
|
46122
46492
|
["number", "id", "title", "state", "target_ref", "source", "task_id", "author", "revision", "current_revision.head_oid", "revision_count", "merged", "version"]
|
|
46123
46493
|
);
|
|
46124
46494
|
var renderThread = (t) => `${t.id} [${t.state}${t.blocking ? ", blocking" : ""}${t.anchor.outdated ? ", outdated" : ""}] ${t.anchor.path ?? "(change)"}${t.anchor.start_line ? `:${t.anchor.start_line}` : ""}
|
|
46125
|
-
` + t.comments.map((c8) => ` ${c8.author
|
|
46495
|
+
` + t.comments.map((c8) => ` ${actorLabel(c8.author)}: ${c8.body.split("\n").join("\n ")}`).join("\n");
|
|
46126
46496
|
function registerChange(program2, rt) {
|
|
46127
46497
|
const change = program2.command("change").description("Changes (proposed updates to a protected ref)");
|
|
46128
46498
|
change.command("create").description("Create a change from a workspace or a ref in --repo").requiredOption("--title <title>", "title").option("--body <text>", "description (Markdown; @file or - for stdin)").option("--file <path>", "read the description from a file or -").option("--workspace <id>", "source workspace").option("--ref <ref>", "source ref (e.g. refs/heads/feature)").option("--target <ref>", "target ref (default: the repository's default branch)").option("--task <id>", "linked task").option("--draft", "create as draft").action(
|
|
@@ -46142,7 +46512,7 @@ function registerChange(program2, rt) {
|
|
|
46142
46512
|
change.command("list").description("List changes in --repo").option("--state <state>", "draft | open | merged | closed").option("--limit <n>", "page size", (v) => parseIntOption(v, "--limit")).option("--cursor <cursor>", "page cursor").action(
|
|
46143
46513
|
act(rt, async (ctx, o) => {
|
|
46144
46514
|
const page = await ctx.client().changes.list(await ctx.repoId(), { state: o.state, limit: o.limit, cursor: o.cursor });
|
|
46145
|
-
ctx.result(page, (v) => listWithCursor(v, ["number", "state", "title", "target_ref", "id"]));
|
|
46515
|
+
ctx.result(page, (v) => listWithCursor({ ...v, items: v.items.map((c8) => ({ ...c8, author: actorLabel(c8.author) })) }, ["number", "state", "title", "author", "target_ref", "id"]));
|
|
46146
46516
|
})
|
|
46147
46517
|
);
|
|
46148
46518
|
change.command("view").argument("<change>", CHANGE_ARG).description("Show a change").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client().changes.get(await ctx.changeId(id2)), renderChange)));
|
|
@@ -46231,7 +46601,7 @@ function registerReview(program2, rt) {
|
|
|
46231
46601
|
const id2 = await ctx.changeId(spec);
|
|
46232
46602
|
const revision = o.revision ?? (await c8.changes.get(id2)).current_revision.id;
|
|
46233
46603
|
const r = await c8.reviews.create(id2, { revision_id: revision, decision, body: await bodyFrom(ctx, o, bodyRequired) });
|
|
46234
|
-
ctx.result(r, (v) => kv({ ...v, reviewer:
|
|
46604
|
+
ctx.result(r, (v) => kv({ ...v, reviewer: actorLabel(v.reviewer) }, ["id", "decision", "revision_id", "reviewer", "current"]));
|
|
46235
46605
|
})
|
|
46236
46606
|
);
|
|
46237
46607
|
decide("approve", "approved", "Approve a revision (approvals are bound to that revision)", false);
|
|
@@ -46285,6 +46655,7 @@ async function waitDone(ctx, change, interval, timeout) {
|
|
|
46285
46655
|
const now = f.reviews.map((r) => `${CODE_REVIEW_PROVIDER_NAMES[r.provider]}: ${r.status}`).join(", ");
|
|
46286
46656
|
if (now && now !== said) ctx.info(now);
|
|
46287
46657
|
said = now;
|
|
46658
|
+
ctx.status.working(now ? `AI review: ${now}` : "Waiting for AI review");
|
|
46288
46659
|
if (!f.pending || Date.now() > deadline) return f;
|
|
46289
46660
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
46290
46661
|
}
|
|
@@ -46351,7 +46722,7 @@ Wait for the findings: code-review wait ${spec}`);
|
|
|
46351
46722
|
|
|
46352
46723
|
// packages/cli/src/commands/evidence.ts
|
|
46353
46724
|
import { basename as basename4, extname, isAbsolute as isAbsolute8, resolve as resolve8 } from "node:path";
|
|
46354
|
-
var renderEvidence = (e) => kv({ ...e, issuer:
|
|
46725
|
+
var renderEvidence = (e) => kv({ ...e, issuer: actorLabel(e.issuer) }, ["id", "check_name", "result", "subject_kind", "subject_oid", "origin", "independence", "issuer", "summary", "attachment_ids", "revoked_at"]);
|
|
46355
46726
|
var CONTENT_TYPES = { ".txt": "text/plain", ".log": "text/plain", ".json": "application/json", ".xml": "application/xml", ".html": "text/plain" };
|
|
46356
46727
|
function registerEvidence(program2, rt) {
|
|
46357
46728
|
const ev = program2.command("evidence").description("Verification evidence");
|
|
@@ -46450,6 +46821,7 @@ async function followLive(ctx, c8, first2, deadline, interval) {
|
|
|
46450
46821
|
const stop = ctx.deps.ticker(250, draw2);
|
|
46451
46822
|
try {
|
|
46452
46823
|
while (!TERMINAL3.has(cur.state) && Date.now() < deadline) {
|
|
46824
|
+
ctx.status.working(`Merge ${cur.state}`);
|
|
46453
46825
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
46454
46826
|
cur = await c8.promotions.get(cur.id);
|
|
46455
46827
|
reached = stepOf(cur.state, reached);
|
|
@@ -46474,6 +46846,7 @@ function registerMerge(program2, rt) {
|
|
|
46474
46846
|
let last = p.state;
|
|
46475
46847
|
ctx.info(`promotion ${p.id}: ${p.state}`);
|
|
46476
46848
|
while (!TERMINAL3.has(p.state) && Date.now() < deadline) {
|
|
46849
|
+
ctx.status.working(`Merge ${p.state}`);
|
|
46477
46850
|
await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
|
|
46478
46851
|
p = await c8.promotions.get(p.id);
|
|
46479
46852
|
if (p.state !== last) ctx.info(`promotion ${p.id}: ${last = p.state}`);
|
|
@@ -46526,7 +46899,7 @@ var renderCoverage = (c8) => [
|
|
|
46526
46899
|
`code owner review ${c8.required ? "required" : "not required"} by policy v${c8.policy_version} (revision ${c8.revision_id})${c8.complete ? "" : " \u2014 path list truncated"}`,
|
|
46527
46900
|
table(c8.paths.map((p) => ({ ...p, owners: p.owners.join(" ") || "-", approved_by: p.approved_by.join(" ") || "-" })), ["path", "owners", "satisfied", "approved_by"])
|
|
46528
46901
|
].join("\n");
|
|
46529
|
-
var renderAlerts = (p) => listWithCursor({ ...p, items: p.items.map((a) => ({ ...a, where: `${a.path}:${a.line}`, pushed_by: a.pushed_by
|
|
46902
|
+
var renderAlerts = (p) => listWithCursor({ ...p, items: p.items.map((a) => ({ ...a, where: `${a.path}:${a.line}`, pushed_by: actorLabel(a.pushed_by) })) }, ["id", "state", "rule", "where", "blob_oid", "masked", "pushed_by", "created_at"]);
|
|
46530
46903
|
function registerGovernance(program2, rt) {
|
|
46531
46904
|
const change = program2.commands.find((c8) => c8.name() === "change") ?? program2.command("change");
|
|
46532
46905
|
change.command("draft").argument("<change>", CHANGE_ARG).description("Convert an open change back to a draft (drafts cannot merge and notify nobody)").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client().changes.draft(await ctx.changeId(id2)), (c8) => kv(c8, ["id", "number", "title", "state"]))));
|
|
@@ -46616,7 +46989,7 @@ new revision #${r.revision.number} ${r.revision.id}`);
|
|
|
46616
46989
|
}
|
|
46617
46990
|
const b = await ctx.client("user").pushProtection.bypasses.create(await ctx.repoId(), { rule: o.rule, path: o.path, blob_oid: o.blob, reason: o.reason, ...principal ? { principal } : {}, ...o.ref ? { ref: o.ref } : {} });
|
|
46618
46991
|
ctx.result(b, (v) => {
|
|
46619
|
-
const who3 = v.principal ? v.principal
|
|
46992
|
+
const who3 = v.principal ? actorLabel(v.principal) : "nobody";
|
|
46620
46993
|
return `${kv({ ...v, for: who3, ref: v.ref ?? "any" }, ["id", "rule", "path", "blob_oid", "for", "ref", "expires_at"])}
|
|
46621
46994
|
Only ${who3} can use it, for one push, before it expires.`;
|
|
46622
46995
|
});
|
|
@@ -46627,7 +47000,7 @@ Only ${who3} can use it, for one push, before it expires.`;
|
|
|
46627
47000
|
rt,
|
|
46628
47001
|
async (ctx, o) => ctx.result(
|
|
46629
47002
|
await ctx.client().pushProtection.bypasses.list(await ctx.repoId(), { limit: o.limit, cursor: o.cursor }),
|
|
46630
|
-
(p) => listWithCursor({ ...p, items: p.items.map((b) => ({ ...b, for: b.principal ? b.principal
|
|
47003
|
+
(p) => listWithCursor({ ...p, items: p.items.map((b) => ({ ...b, for: b.principal ? actorLabel(b.principal) : "-", created_by: actorLabel(b.created_by), used: b.used_at ? "yes" : "no" })) }, ["id", "rule", "path", "blob_oid", "for", "created_by", "used", "expires_at", "reason"])
|
|
46631
47004
|
)
|
|
46632
47005
|
)
|
|
46633
47006
|
);
|
|
@@ -46668,7 +47041,7 @@ var renderAlerts2 = (p) => listWithCursor(
|
|
|
46668
47041
|
["id", "state", "resolution", "confidence", "rule", "repo", "where", "masked", "source", "created_at"]
|
|
46669
47042
|
);
|
|
46670
47043
|
var renderAlert = (a) => [
|
|
46671
|
-
kv({ ...a, where: where(a), resolved_by: a.resolved_by ? a.resolved_by
|
|
47044
|
+
kv({ ...a, where: where(a), resolved_by: a.resolved_by ? actorLabel(a.resolved_by) : null, looks_fake: a.looks_fake?.label ?? null }, [
|
|
46672
47045
|
"id",
|
|
46673
47046
|
"state",
|
|
46674
47047
|
"resolution",
|
|
@@ -46751,6 +47124,7 @@ function registerSecretScanning(program2, rt) {
|
|
|
46751
47124
|
const c8 = ctx.client("user");
|
|
46752
47125
|
let job2 = await c8.secretScanning.scans.start(repo);
|
|
46753
47126
|
while (o.wait && (job2.state === "queued" || job2.state === "running")) {
|
|
47127
|
+
ctx.status.working(`History scan ${job2.state}`);
|
|
46754
47128
|
await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
|
|
46755
47129
|
job2 = await c8.secretScanning.scans.get(repo, job2.id);
|
|
46756
47130
|
}
|
|
@@ -47008,8 +47382,8 @@ var renderDevices = (v) => v.items.length ? table(
|
|
|
47008
47382
|
v.items.map((d) => ({ id: d.id, name: d.name ?? "-", environment: d.environment, app_version: d.app_version ?? "-", last_seen: d.last_seen_at, current: d.current ? "yes" : "" })),
|
|
47009
47383
|
["id", "name", "environment", "app_version", "last_seen", "current"]
|
|
47010
47384
|
) : "No push devices. The iOS app registers itself when you allow notifications.";
|
|
47011
|
-
var
|
|
47012
|
-
var
|
|
47385
|
+
var PAGE = 100;
|
|
47386
|
+
var MAX_PAGES = 10;
|
|
47013
47387
|
async function findItems(client, specs) {
|
|
47014
47388
|
const wanted = specs.map((s) => s.trim().toLowerCase());
|
|
47015
47389
|
for (const s of wanted) if (s.length < 4) throw usage(`"${s}" is too short to name an inbox item: use the id \`${c4} inbox list\` shows`);
|
|
@@ -47017,8 +47391,8 @@ async function findItems(client, specs) {
|
|
|
47017
47391
|
const seen = /* @__PURE__ */ new Set();
|
|
47018
47392
|
for (const view of ["all", "snoozed", "done"]) {
|
|
47019
47393
|
let cursor2;
|
|
47020
|
-
for (let i = 0; i <
|
|
47021
|
-
const page = await client.inbox.list({ view, limit:
|
|
47394
|
+
for (let i = 0; i < MAX_PAGES; i++) {
|
|
47395
|
+
const page = await client.inbox.list({ view, limit: PAGE, ...cursor2 ? { cursor: cursor2 } : {} });
|
|
47022
47396
|
for (const item of page.items) {
|
|
47023
47397
|
if (seen.has(item.id)) continue;
|
|
47024
47398
|
seen.add(item.id);
|
|
@@ -47040,9 +47414,9 @@ async function findItems(client, specs) {
|
|
|
47040
47414
|
}
|
|
47041
47415
|
async function itemIds(client, specs) {
|
|
47042
47416
|
const full = (s) => /^notif_[0-9a-z]{26}$/.test(s);
|
|
47043
|
-
const
|
|
47044
|
-
const items =
|
|
47045
|
-
return specs.map((s) => full(s) ? s : items[
|
|
47417
|
+
const short3 = specs.filter((s) => !full(s));
|
|
47418
|
+
const items = short3.length ? await findItems(client, short3) : [];
|
|
47419
|
+
return specs.map((s) => full(s) ? s : items[short3.indexOf(s)].id);
|
|
47046
47420
|
}
|
|
47047
47421
|
async function repoNames(client, items) {
|
|
47048
47422
|
const ids = [...new Set(items.map((i) => i.repository_id))];
|
|
@@ -47473,13 +47847,13 @@ function renderEntry(e, verb) {
|
|
|
47473
47847
|
if (e.flags.length) lines2.push(`flagged: ${e.flags.join(", ")}${e.flags.includes("instructions") ? "; never given to the assistant on its own" : ""}`);
|
|
47474
47848
|
return lines2.join("\n");
|
|
47475
47849
|
}
|
|
47476
|
-
var
|
|
47477
|
-
var
|
|
47850
|
+
var PAGE2 = 100;
|
|
47851
|
+
var MAX_PAGES2 = 10;
|
|
47478
47852
|
async function reviewable(ctx, org, q) {
|
|
47479
47853
|
const out = [];
|
|
47480
47854
|
let cursor2;
|
|
47481
|
-
for (let i = 0; i <
|
|
47482
|
-
const page = await ctx.client("user").assistant.memory.list(org, { ...q, status: "pending", limit:
|
|
47855
|
+
for (let i = 0; i < MAX_PAGES2; i++) {
|
|
47856
|
+
const page = await ctx.client("user").assistant.memory.list(org, { ...q, status: "pending", limit: PAGE2, ...cursor2 ? { cursor: cursor2 } : {} });
|
|
47483
47857
|
out.push(...page.items.filter((e) => e.can_review));
|
|
47484
47858
|
if (!page.next_cursor) break;
|
|
47485
47859
|
cursor2 = page.next_cursor;
|
|
@@ -47787,6 +48161,329 @@ function registerIntegrations(program2, rt) {
|
|
|
47787
48161
|
integrations.command("inherit <provider> <purpose>").description("Drop --repo's choice and inherit the nearest shared connection again").action(act(rt, async (ctx, _o, [provider, purpose]) => ctx.result(await ctx.client("user").grants.inherit(await ctx.repoId(void 0, "user"), provider, purpose), renderRepo)));
|
|
47788
48162
|
}
|
|
47789
48163
|
|
|
48164
|
+
// packages/cli/src/commands/approvals.ts
|
|
48165
|
+
function protectOptions(cmd) {
|
|
48166
|
+
return cmd.option("--user <handle>", "a person who may approve (@handle or id; repeatable)", repeat).option("--team <slug>", "a team whose members may approve (slug or id; repeatable)", repeat).option("--required <n>", "approvals needed from different people (default 1)", (v) => parseIntOption(v, "--required")).option("--separation-of-duties", "the change's author, its merger and whoever requested the deploy or run cannot approve it");
|
|
48167
|
+
}
|
|
48168
|
+
function protectBody(o, current) {
|
|
48169
|
+
const users = o.user ?? [];
|
|
48170
|
+
const teams = o.team ?? [];
|
|
48171
|
+
if (!users.length && !teams.length && o.required === void 0 && !o.separationOfDuties) throw usage("give --user, --team, --required or --separation-of-duties");
|
|
48172
|
+
return { required: o.required ?? 1, users, teams, separation_of_duties: !!o.separationOfDuties, ...current ? { version: current.version } : {} };
|
|
48173
|
+
}
|
|
48174
|
+
var who2 = (p) => p.handle ? `@${p.handle}` : p.name;
|
|
48175
|
+
var ruleSummary = (r) => r ? `${r.required} approval${r.required === 1 ? "" : "s"} from ${[...r.teams.map((t) => `@${t.slug} team`), ...r.users.map(who2)].join(", ") || "anyone who may approve"}${r.separation_of_duties ? "; not the author, merger or requester" : ""}` : "not protected";
|
|
48176
|
+
var renderApprovers = (v) => kv(
|
|
48177
|
+
{
|
|
48178
|
+
protected: ruleSummary(v.rule),
|
|
48179
|
+
capability: v.capability,
|
|
48180
|
+
eligible: v.eligible.map(who2).join(", ") || "nobody",
|
|
48181
|
+
lacking_capability: v.lacking_capability.length ? `${v.lacking_capability.map(who2).join(", ")} (named, but cannot approve without ${v.capability})` : null,
|
|
48182
|
+
unsatisfiable: v.unsatisfiable ? "yes: fewer people can approve than it needs" : null
|
|
48183
|
+
},
|
|
48184
|
+
["protected", "capability", "eligible", "lacking_capability", "unsatisfiable"]
|
|
48185
|
+
);
|
|
48186
|
+
var renderRule = (r) => `protected: ${ruleSummary(r)} (version ${r.version})`;
|
|
48187
|
+
function approvalLines(a) {
|
|
48188
|
+
if (!a) return "";
|
|
48189
|
+
const by2 = a.approvals.filter((x) => x.counted).map((x) => who2(x.user));
|
|
48190
|
+
const lines2 = [a.line ?? (a.satisfied ? `approved (${a.counted} of ${a.required})` : null), by2.length ? `approved by ${by2.join(", ")}` : null, a.you && !a.you.can_approve && a.line ? `you cannot approve: ${a.you.message}` : null].filter(Boolean);
|
|
48191
|
+
return lines2.length ? `
|
|
48192
|
+
|
|
48193
|
+
${lines2.join("\n")}` : "";
|
|
48194
|
+
}
|
|
48195
|
+
|
|
48196
|
+
// packages/cli/src/commands/db.ts
|
|
48197
|
+
async function sourceId(ctx, ref, repo) {
|
|
48198
|
+
if (ref.startsWith("dms_")) return ref;
|
|
48199
|
+
const { items } = await ctx.client("user").dbMigrations.sources.list(repo?.id ?? await ctx.repoId(void 0, "user"));
|
|
48200
|
+
const s = items.find((x) => x.name === ref);
|
|
48201
|
+
if (!s) throw new CliError(`no migration source named "${ref}" (have: ${items.map((x) => x.name).join(", ") || "none"})`, EXIT.NOT_FOUND, "NOT_FOUND");
|
|
48202
|
+
return s.id;
|
|
48203
|
+
}
|
|
48204
|
+
var plural3 = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
|
|
48205
|
+
var short = (oid) => oid.slice(0, 12);
|
|
48206
|
+
var database = (s) => {
|
|
48207
|
+
const t = s.target;
|
|
48208
|
+
return t.provider === "cloudflare_d1" ? `d1:${t.binding}\u2192${t.database_name ?? t.database_id}${t.environment ? ` (env ${t.environment})` : ""}` : `${t.organization}/${t.database}@${t.branch}`;
|
|
48209
|
+
};
|
|
48210
|
+
var PS_FORMATS = ["atlas", "plain", "golang_migrate", "drizzle", "prisma"];
|
|
48211
|
+
var PS_ENGINES = ["postgres", "vitess"];
|
|
48212
|
+
var LEVELS = ["error", "warning", "off"];
|
|
48213
|
+
function lintLines(findings2) {
|
|
48214
|
+
if (!findings2?.length) return [];
|
|
48215
|
+
return ["lint:", ...findings2.map((f) => ` ${f.level.padEnd(12)} ${(f.atlas_code ?? f.code).padEnd(6)} ${f.file}:${f.line} ${f.message}`)];
|
|
48216
|
+
}
|
|
48217
|
+
function diffLines(d) {
|
|
48218
|
+
if (!d) return [];
|
|
48219
|
+
const t = d.tables;
|
|
48220
|
+
const out = [];
|
|
48221
|
+
for (const x of t.added) out.push(` + table ${x.name} (${x.columns.map((c8) => c8.name).join(", ")})`);
|
|
48222
|
+
for (const x of t.renamed) out.push(` ~ table ${x.from} \u2192 ${x.to}`);
|
|
48223
|
+
for (const x of t.changed) {
|
|
48224
|
+
for (const c8 of x.columns.added) out.push(` + ${x.name}.${c8.name} ${c8.type}${c8.nullable ? "" : " NOT NULL"}`);
|
|
48225
|
+
for (const c8 of x.columns.renamed) out.push(` ~ ${x.name}.${c8.from} \u2192 ${c8.to}`);
|
|
48226
|
+
for (const c8 of x.columns.changed) out.push(` ~ ${x.name}.${c8.name} ${c8.before.type} \u2192 ${c8.after.type}`);
|
|
48227
|
+
for (const c8 of x.columns.dropped) out.push(` - ${x.name}.${c8}`);
|
|
48228
|
+
for (const i of x.indexes.added) out.push(` + index ${i} on ${x.name}`);
|
|
48229
|
+
for (const i of x.indexes.dropped) out.push(` - index ${i} on ${x.name}`);
|
|
48230
|
+
for (const k of x.foreign_keys.added) out.push(` + foreign key ${k} on ${x.name}`);
|
|
48231
|
+
for (const k of x.foreign_keys.dropped) out.push(` - foreign key ${k} on ${x.name}`);
|
|
48232
|
+
}
|
|
48233
|
+
for (const x of t.dropped) out.push(` - table ${x.name}`);
|
|
48234
|
+
if (!out.length && !d.unparsed.length) return ["schema effect: none"];
|
|
48235
|
+
return ["schema effect:", ...out, ...d.unparsed.length ? [` (${plural3(d.unparsed.length, "statement")} could not be interpreted, first at ${d.unparsed[0].file}:${d.unparsed[0].line})`] : []];
|
|
48236
|
+
}
|
|
48237
|
+
function renderDrift(d) {
|
|
48238
|
+
if (!d) return "drift: not checked yet";
|
|
48239
|
+
const lines2 = [`drift: ${d.status} (checked ${d.checked_at} against ${plural3(d.applied, "applied migration")})`];
|
|
48240
|
+
if (d.note) lines2.push(`note: ${d.note}`);
|
|
48241
|
+
for (const f of d.findings) lines2.push(` ${f.kind.padEnd(20)} ${f.object} ${f.detail}`);
|
|
48242
|
+
return lines2.join("\n");
|
|
48243
|
+
}
|
|
48244
|
+
function issueLines(issues) {
|
|
48245
|
+
if (!issues.length) return [];
|
|
48246
|
+
return ["issues:", ...issues.map((i) => ` ${i.level.padEnd(7)} ${i.code} ${i.file ? `${i.file}: ` : ""}${i.message}`)];
|
|
48247
|
+
}
|
|
48248
|
+
var renderSources = (v) => table(
|
|
48249
|
+
v.items.map((s) => ({ ...s, database: database(s), applied: s.snapshot ? `${s.snapshot.applied.length} (read ${s.snapshot.read_at})` : "not read yet", drift: s.drift?.status ?? "-" })),
|
|
48250
|
+
["name", "id", "provider", "path", "format", "database", "branch", "apply_on", "state", "applied", "drift"]
|
|
48251
|
+
);
|
|
48252
|
+
var renderSource = (s) => `${kv(
|
|
48253
|
+
{ ...s, approvers: ruleSummary(s.approval_rule), database: database(s), path: s.path || "(repository root)", destructive: s.lint.levels.destructive ?? "error", nolint: s.lint.nolint, ...s.target.provider === "cloudflare_d1" ? { from: `${s.target.config_path} on ${s.branch}` } : {} },
|
|
48254
|
+
s.provider === "cloudflare_d1" ? ["id", "name", "provider", "path", "format", "database", "from", "branch", "revisions_location", "apply_on", "require_approval", "approvers", "change_checks", "destructive", "nolint", "drift_mode", "state", "version"] : ["id", "name", "provider", "path", "format", "engine", "database", "branch", "revisions_location", "baseline_version", "exec_order", "credential_mode", "dsn_set", "run_as_role", "apply_on", "require_approval", "approvers", "change_checks", "destructive", "nolint", "drift_mode", "state", "version"]
|
|
48255
|
+
)}
|
|
48256
|
+
|
|
48257
|
+
` + (s.apply_on === "merge" ? `Merges to ${s.branch} that add migrations create a run waiting for approval: ${BRAND.cli} db runs` : s.apply_on === "deploy" ? `The production deploy's migrations step applies them${s.require_approval ? ", once the run is approved" : ""}` : `Runs start only by hand: ${BRAND.cli} db apply ${s.name}`);
|
|
48258
|
+
var renderSuggestions = (v) => v.items.length ? table(
|
|
48259
|
+
v.items.map((s) => ({ ...s, path: s.path || "(root)", configured: s.configured ? "yes" : "no" })),
|
|
48260
|
+
["path", "format", "files", "revisions_schema", "baseline_version", "configured", "evidence"]
|
|
48261
|
+
) : "no migration directories found";
|
|
48262
|
+
function renderPlan2(p) {
|
|
48263
|
+
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}` : ""}`;
|
|
48264
|
+
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]})` : ""}`];
|
|
48265
|
+
if (p.baseline) lines2.push(`baseline: ${p.baseline}${p.no_history ? " (recorded, not applied: the database has no revision history)" : ""}`);
|
|
48266
|
+
else if (p.no_history && p.database !== "none") lines2.push("no revision history yet: every file is pending");
|
|
48267
|
+
if (p.database !== "live" && p.note) lines2.push(`note: ${p.note}`);
|
|
48268
|
+
if (!p.pending.length) lines2.push("nothing pending");
|
|
48269
|
+
else {
|
|
48270
|
+
lines2.push(`pending (${p.pending.length}):`);
|
|
48271
|
+
for (const f of p.pending) {
|
|
48272
|
+
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);
|
|
48273
|
+
lines2.push(` ${f.name} ${plural3(f.statements, "statement")}${notes.length ? ` (${notes.join(", ")})` : ""}`);
|
|
48274
|
+
}
|
|
48275
|
+
}
|
|
48276
|
+
return [...lines2, ...issueLines(p.issues), ...lintLines(p.lint), ...diffLines(p.schema_diff)].join("\n");
|
|
48277
|
+
}
|
|
48278
|
+
var renderPlans = (v) => v.items.length ? v.items.map(renderPlan2).join("\n\n") : "no migration sources";
|
|
48279
|
+
var NEXT = {
|
|
48280
|
+
awaiting_approval: (id2) => `Waiting for approval: ${BRAND.cli} db approve ${id2} (or reject)`,
|
|
48281
|
+
failed: (id2) => `Fix the migration and merge it, then: ${BRAND.cli} db retry ${id2}`,
|
|
48282
|
+
needs_attention: (id2) => `Look at the database (PlanetScale, or the D1 bookmark's restore command), then: ${BRAND.cli} db retry ${id2} or cancel`
|
|
48283
|
+
};
|
|
48284
|
+
function renderRun(r) {
|
|
48285
|
+
const ps = r.backend?.provider === "planetscale" ? r.backend : null;
|
|
48286
|
+
const d1 = r.backend?.provider === "cloudflare_d1" ? r.backend : null;
|
|
48287
|
+
const head = kv(
|
|
48288
|
+
{
|
|
48289
|
+
...r,
|
|
48290
|
+
commit: short(r.commit_oid),
|
|
48291
|
+
change: r.change_id ?? "-",
|
|
48292
|
+
requested_by: r.requested_by ? `${r.requested_by.type} ${r.requested_by.id}` : "-",
|
|
48293
|
+
deploy_request: ps?.deploy_request_url ?? (ps?.deploy_request ? `#${ps.deploy_request}` : null),
|
|
48294
|
+
deployment_state: ps?.deployment_state ?? null,
|
|
48295
|
+
bookmark: d1?.bookmark ?? null,
|
|
48296
|
+
restore: d1?.restore?.wrangler ?? null
|
|
48297
|
+
},
|
|
48298
|
+
["id", "source_name", "status", "kind", "provider", "engine", "commit", "change", "deployment_id", "baseline", "requested_by", "approved_by", "approved_at", "decision_reason", "deploy_request", "deployment_state", "bookmark", "restore", "error", "note", "created_at"]
|
|
48299
|
+
);
|
|
48300
|
+
const files = r.files.length ? table(
|
|
48301
|
+
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 })),
|
|
48302
|
+
["name", "status", "statements", "execution_ms", "error"]
|
|
48303
|
+
) : "no files";
|
|
48304
|
+
const next = NEXT[r.status]?.(r.id);
|
|
48305
|
+
return [head, "", files, ...r.issues.length ? ["", ...issueLines(r.issues)] : [], ...next ? ["", next] : []].join("\n") + approvalLines(r.approval);
|
|
48306
|
+
}
|
|
48307
|
+
var renderRuns = (v) => listWithCursor(
|
|
48308
|
+
{ ...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` })) },
|
|
48309
|
+
["id", "source_name", "status", "kind", "commit", "files", "created_at"]
|
|
48310
|
+
);
|
|
48311
|
+
var oneOf = (flag, values) => (v) => {
|
|
48312
|
+
if (!values.includes(v)) throw usage(`${flag} must be one of ${values.join(", ")}`);
|
|
48313
|
+
return v;
|
|
48314
|
+
};
|
|
48315
|
+
function sessionSettings(pairs) {
|
|
48316
|
+
if (!pairs) return void 0;
|
|
48317
|
+
const out = {};
|
|
48318
|
+
for (const p of pairs) {
|
|
48319
|
+
const eq = p.indexOf("=");
|
|
48320
|
+
if (eq < 1) throw usage(`--session-setting must be name=value (got "${p}")`);
|
|
48321
|
+
out[p.slice(0, eq)] = p.slice(eq + 1);
|
|
48322
|
+
}
|
|
48323
|
+
return out;
|
|
48324
|
+
}
|
|
48325
|
+
function registerDb(program2, rt) {
|
|
48326
|
+
const db = program2.command("db").description("Database migrations on PlanetScale and Cloudflare D1 for --repo: sources, plans, lint, runs applied after approval, and drift");
|
|
48327
|
+
const sources = db.command("sources").description("Migration sources of --repo: a directory bound to a PlanetScale database branch or a D1 database (D1 sources come from the Wrangler configuration)").action(act(rt, async (ctx) => ctx.result(await ctx.client("user").dbMigrations.sources.list(await ctx.repoId(void 0, "user")), renderSources)));
|
|
48328
|
+
sources.command("add").description("Add a PlanetScale migration source (migrations:manage). The source is checked against PlanetScale; `db suggest` shows the directories found. D1 sources need no adding: they come from the Wrangler configuration").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>", PS_FORMATS.join(" | "), oneOf("--format", PS_FORMATS)).requiredOption("--engine <engine>", PS_ENGINES.join(" | "), oneOf("--engine", PS_ENGINES)).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>", "deprecated: --apply-on (approval = merge, manual_only = manual)", oneOf("--on-merge", ["approval", "manual_only"])).option("--apply-on <when>", `${DB_APPLY_ON.join(" | ")} (default merge: runs wait for approval after merge)`, oneOf("--apply-on", DB_APPLY_ON)).option("--no-approval", "--apply-on deploy or manual: apply without a person's approval").option("--destructive <level>", "lint level for destructive changes: error (default, fails the check) | warning | off", oneOf("--destructive", LEVELS)).option("--drift <mode>", DB_DRIFT_MODES.join(" | "), oneOf("--drift", DB_DRIFT_MODES)).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(
|
|
48329
|
+
act(rt, async (ctx, o) => {
|
|
48330
|
+
let dsn;
|
|
48331
|
+
if (o.dsnFile !== void 0) {
|
|
48332
|
+
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 -\``);
|
|
48333
|
+
dsn = (await readText(fileSpec(o.dsnFile), ctx.input)).trim();
|
|
48334
|
+
if (!dsn) throw usage("the connection string is empty");
|
|
48335
|
+
ctx.addSecret(dsn);
|
|
48336
|
+
const password = /^[a-z]+:\/\/[^:/@]*:([^@]+)@/i.exec(dsn)?.[1];
|
|
48337
|
+
if (password) ctx.addSecret(password);
|
|
48338
|
+
}
|
|
48339
|
+
const body = {
|
|
48340
|
+
name: o.name,
|
|
48341
|
+
path: o.path,
|
|
48342
|
+
format: o.format,
|
|
48343
|
+
engine: o.engine,
|
|
48344
|
+
connection_id: o.connection,
|
|
48345
|
+
ps_organization: o.organization,
|
|
48346
|
+
ps_database: o.database,
|
|
48347
|
+
ps_branch: o.psBranch,
|
|
48348
|
+
change_checks: o.changeChecks,
|
|
48349
|
+
...o.branch ? { branch: o.branch } : {},
|
|
48350
|
+
...o.revisionsSchema ? { revisions_schema: o.revisionsSchema } : {},
|
|
48351
|
+
...o.revisionsTable ? { revisions_table: o.revisionsTable } : {},
|
|
48352
|
+
...o.baseline ? { baseline_version: o.baseline } : {},
|
|
48353
|
+
...o.execOrder ? { exec_order: o.execOrder } : {},
|
|
48354
|
+
...o.onMerge ? { on_merge: o.onMerge } : {},
|
|
48355
|
+
...o.applyOn ? { apply_on: o.applyOn } : {},
|
|
48356
|
+
...o.approval === false ? { require_approval: false } : {},
|
|
48357
|
+
...o.destructive ? { lint: { levels: { destructive: o.destructive }, nolint: "honor", drift_blocks_deploy: false } } : {},
|
|
48358
|
+
...o.drift ? { drift_mode: o.drift } : {},
|
|
48359
|
+
...o.credentialMode ? { credential_mode: o.credentialMode } : {},
|
|
48360
|
+
...o.runAsRole ? { run_as_role: o.runAsRole } : {},
|
|
48361
|
+
...o.sessionSetting ? { session_settings: sessionSettings(o.sessionSetting) } : {},
|
|
48362
|
+
...o.statementTimeout !== void 0 ? { statement_timeout_s: o.statementTimeout } : {},
|
|
48363
|
+
...o.lockTimeout !== void 0 ? { lock_timeout_s: o.lockTimeout } : {},
|
|
48364
|
+
...dsn ? { dsn } : {}
|
|
48365
|
+
};
|
|
48366
|
+
ctx.result(await ctx.client("user").dbMigrations.sources.create(await ctx.repoId(void 0, "user"), body), renderSource);
|
|
48367
|
+
})
|
|
48368
|
+
);
|
|
48369
|
+
sources.command("set <source>").description("Change how a source applies, its lint and drift checks (migrations:manage; D1 sources: only these)").option("--apply-on <when>", DB_APPLY_ON.join(" | "), oneOf("--apply-on", DB_APPLY_ON)).option("--approval", "each run waits for approval (migrations:approve; a protected source's approvers)").option("--no-approval", "--apply-on deploy or manual: apply without approval").option("--destructive <level>", "lint level for destructive changes: error | warning | off", oneOf("--destructive", LEVELS)).option("--nolint <mode>", "honor | ignore: whether `-- atlas:nolint` in a file acknowledges a finding", oneOf("--nolint", ["honor", "ignore"])).option("--drift <mode>", DB_DRIFT_MODES.join(" | "), oneOf("--drift", DB_DRIFT_MODES)).option("--drift-blocks-deploy <yes|no>", "drift found by the last check fails the deploy's migrations step", oneOf("--drift-blocks-deploy", ["yes", "no"])).option("--enable", "activate the source").option("--disable", "disable the source (no runs, no checks)").action(
|
|
48370
|
+
act(rt, async (ctx, o, [ref]) => {
|
|
48371
|
+
if (o.enable && o.disable) throw usage("--enable and --disable exclude each other");
|
|
48372
|
+
const c8 = ctx.client("user").dbMigrations;
|
|
48373
|
+
const s = await c8.sources.get(await sourceId(ctx, ref));
|
|
48374
|
+
const levels = { ...s.lint.levels };
|
|
48375
|
+
if (o.destructive) {
|
|
48376
|
+
if (o.destructive === "error") delete levels.destructive;
|
|
48377
|
+
else levels.destructive = o.destructive;
|
|
48378
|
+
}
|
|
48379
|
+
const lintChanged = o.destructive || o.nolint || o.driftBlocksDeploy;
|
|
48380
|
+
const body = {
|
|
48381
|
+
version: s.version,
|
|
48382
|
+
...o.applyOn ? { apply_on: o.applyOn } : {},
|
|
48383
|
+
...o.approval !== void 0 ? { require_approval: o.approval } : {},
|
|
48384
|
+
...lintChanged ? { lint: { levels, nolint: o.nolint ?? s.lint.nolint, drift_blocks_deploy: o.driftBlocksDeploy ? o.driftBlocksDeploy === "yes" : s.lint.drift_blocks_deploy } } : {},
|
|
48385
|
+
...o.drift ? { drift_mode: o.drift } : {},
|
|
48386
|
+
...o.enable ? { state: "active" } : o.disable ? { state: "disabled" } : {}
|
|
48387
|
+
};
|
|
48388
|
+
if (Object.keys(body).length === 1) throw usage("nothing to change: give --apply-on, --approval, --destructive, --nolint, --drift, --drift-blocks-deploy, --enable or --disable");
|
|
48389
|
+
ctx.result(await c8.sources.update(s.id, body), renderSource);
|
|
48390
|
+
})
|
|
48391
|
+
);
|
|
48392
|
+
protectOptions(sources.command("protect <source>").description("Require approvals for every run of the source (people with migrations:manage). A protected database can no longer apply without approval. Replaces the current rule")).action(
|
|
48393
|
+
act(rt, async (ctx, o, [ref]) => {
|
|
48394
|
+
const c8 = ctx.client("user").dbMigrations.sources;
|
|
48395
|
+
const s = await c8.get(await sourceId(ctx, ref));
|
|
48396
|
+
ctx.result(await c8.protect(s.id, protectBody(o, s.approval_rule)), renderRule);
|
|
48397
|
+
})
|
|
48398
|
+
);
|
|
48399
|
+
sources.command("unprotect <source>").description("Remove the source's approval rule (runs still need approval as its apply mode says). Requires --yes when non-interactive").action(
|
|
48400
|
+
act(rt, async (ctx, _o, [ref]) => {
|
|
48401
|
+
const id2 = await sourceId(ctx, ref);
|
|
48402
|
+
await ctx.confirm(`remove the approval rule of ${ref}`);
|
|
48403
|
+
await ctx.client("user").dbMigrations.sources.unprotect(id2);
|
|
48404
|
+
ctx.result({ unprotected: id2 }, `${ref} is no longer protected`);
|
|
48405
|
+
})
|
|
48406
|
+
);
|
|
48407
|
+
sources.command("approvers <source>").description("Who may approve the source's runs now").action(act(rt, async (ctx, _o, [ref]) => ctx.result(await ctx.client("user").dbMigrations.sources.approvers(await sourceId(ctx, ref)), renderApprovers)));
|
|
48408
|
+
sources.command("rm <source>").description("Delete a migration source (name or id; its runs stay listed). Requires --yes when non-interactive").action(
|
|
48409
|
+
act(rt, async (ctx, _o, [ref]) => {
|
|
48410
|
+
const id2 = await sourceId(ctx, ref);
|
|
48411
|
+
await ctx.confirm(`delete migration source ${ref}`);
|
|
48412
|
+
await ctx.client("user").dbMigrations.sources.delete(id2);
|
|
48413
|
+
ctx.result({ deleted: id2 }, `deleted ${ref}`);
|
|
48414
|
+
})
|
|
48415
|
+
);
|
|
48416
|
+
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)));
|
|
48417
|
+
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 (migrations:manage)").action(
|
|
48418
|
+
act(rt, async (ctx, o, [ref]) => {
|
|
48419
|
+
const c8 = ctx.client("user").dbMigrations;
|
|
48420
|
+
const q = { ref: o.ref, live: o.live };
|
|
48421
|
+
if (ref) {
|
|
48422
|
+
const p = await c8.plan(await sourceId(ctx, ref), q);
|
|
48423
|
+
ctx.result(p, renderPlan2);
|
|
48424
|
+
if (!p.ok) ctx.setExit(EXIT.NOT_READY);
|
|
48425
|
+
return;
|
|
48426
|
+
}
|
|
48427
|
+
const { items } = await c8.sources.list(await ctx.repoId(void 0, "user"));
|
|
48428
|
+
const plans = { items: await Promise.all(items.filter((s) => s.state === "active").map((s) => c8.plan(s.id, q))) };
|
|
48429
|
+
ctx.result(plans, renderPlans);
|
|
48430
|
+
if (plans.items.some((p) => !p.ok)) ctx.setExit(EXIT.NOT_READY);
|
|
48431
|
+
})
|
|
48432
|
+
);
|
|
48433
|
+
db.command("diff <change>").description("What a change's migrations do: per source, the files it adds, lint findings and the schema effect. Exits 7 when a source would block the change").action(
|
|
48434
|
+
act(rt, async (ctx, _o, [change]) => {
|
|
48435
|
+
const plans = await ctx.client("user").dbMigrations.changePlans(await ctx.changeId(change, "user"));
|
|
48436
|
+
ctx.result(
|
|
48437
|
+
plans,
|
|
48438
|
+
(v) => v.items.length ? v.items.map((p) => {
|
|
48439
|
+
const mine = p.pending.filter((f) => f.introduced_by_change);
|
|
48440
|
+
return [`${p.source_name} (${p.provider === "cloudflare_d1" ? "Cloudflare D1" : `PlanetScale ${p.engine}`} ${p.target_label}): ${p.ok ? "ok" : "blocked"}`, mine.length ? `this change adds: ${mine.map((f) => f.name).join(", ")}` : "this change adds no migrations", ...issueLines(p.issues), ...lintLines(p.lint), ...diffLines(mine.length ? p.schema_diff : null)].join("\n");
|
|
48441
|
+
}).join("\n\n") : "no migration sources follow the change's target branch"
|
|
48442
|
+
);
|
|
48443
|
+
if (plans.items.some((p) => !p.ok)) ctx.setExit(EXIT.NOT_READY);
|
|
48444
|
+
})
|
|
48445
|
+
);
|
|
48446
|
+
db.command("drift <source>").description("The source's last drift check: its database against what the applied migrations produce. --check reads the database now (migrations:manage)").option("--check", "check now").action(
|
|
48447
|
+
act(rt, async (ctx, o, [ref]) => {
|
|
48448
|
+
const c8 = ctx.client("user").dbMigrations;
|
|
48449
|
+
const id2 = await sourceId(ctx, ref);
|
|
48450
|
+
const d = o.check ? await c8.checkDrift(id2) : (await c8.sources.get(id2)).drift;
|
|
48451
|
+
ctx.result(d, renderDrift);
|
|
48452
|
+
if (d?.status === "drift") ctx.setExit(EXIT.NOT_READY);
|
|
48453
|
+
})
|
|
48454
|
+
);
|
|
48455
|
+
db.command("refresh <source>").description("Read the applied versions from the database now and show the plan at the source's branch (migrations:manage)").action(act(rt, async (ctx, _o, [ref]) => ctx.result(await ctx.client("user").dbMigrations.refresh(await sourceId(ctx, ref)), renderPlan2)));
|
|
48456
|
+
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(
|
|
48457
|
+
act(rt, async (ctx, o) => {
|
|
48458
|
+
const repo = { id: await ctx.repoId(void 0, "user") };
|
|
48459
|
+
const q = { ...o.source ? { source_id: await sourceId(ctx, o.source, repo) } : {}, ...o.limit ? { limit: o.limit } : {}, ...o.cursor ? { cursor: o.cursor } : {} };
|
|
48460
|
+
ctx.result(await ctx.client("user").dbMigrations.runs.list(repo.id, q), renderRuns);
|
|
48461
|
+
})
|
|
48462
|
+
);
|
|
48463
|
+
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)));
|
|
48464
|
+
db.command("apply <source>").description("Apply a source's pending migrations now (migrations:manage). Your request is the approval when you may approve and the source is not protected; otherwise the run waits for approval. Requires --yes when non-interactive").option("--ref <ref>", "branch, tag or commit to apply (default: the source's branch)").action(
|
|
48465
|
+
act(rt, async (ctx, o, [ref]) => {
|
|
48466
|
+
const id2 = await sourceId(ctx, ref);
|
|
48467
|
+
await ctx.confirm(`apply the pending migrations of ${ref}${o.ref ? ` at ${o.ref}` : ""} to its database`);
|
|
48468
|
+
const r = await ctx.client("user").dbMigrations.startRun(id2, o.ref ? { ref: o.ref } : {});
|
|
48469
|
+
ctx.result(r, (v) => v.run ? renderRun(v.run) : `nothing to apply
|
|
48470
|
+
|
|
48471
|
+
${renderPlan2(v.plan)}`);
|
|
48472
|
+
})
|
|
48473
|
+
);
|
|
48474
|
+
const decisions = [
|
|
48475
|
+
["approve", "Approve a run waiting for approval: Brnch applies exactly the files it shows once it has the approvals it needs (migrations:approve; a protected source's approvers)"],
|
|
48476
|
+
["reject", "Reject a run waiting for approval; nothing is applied (migrations:approve)"],
|
|
48477
|
+
["retry", "Retry a failed run from where it stopped (migrations:approve)"],
|
|
48478
|
+
["cancel", "Cancel a run that has not finished (migrations:approve)"]
|
|
48479
|
+
];
|
|
48480
|
+
for (const [decision, description] of decisions) {
|
|
48481
|
+
const cmd = db.command(`${decision} <run>`).description(description).option("--reason <text>", "why (recorded with the decision)");
|
|
48482
|
+
if (decision === "approve") cmd.option("--redeploy", "a run a production deploy waits on: deploy the same commit again, which applies it");
|
|
48483
|
+
cmd.action(act(rt, async (ctx, o, [id2]) => ctx.result(await ctx.client("user").dbMigrations[decision](id2, o.redeploy ? { reason: o.reason, redeploy: true } : o.reason), renderRun)));
|
|
48484
|
+
}
|
|
48485
|
+
}
|
|
48486
|
+
|
|
47790
48487
|
// packages/cli/src/commands/deploy.ts
|
|
47791
48488
|
var TERMINAL4 = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "superseded", "blocked"]);
|
|
47792
48489
|
var NO_DO_PREVIEWS = "previews off: Cloudflare makes no version URLs for Workers that implement a Durable Object (--previews --preview-mode worker_previews uses Worker Previews; it needs a `previews` block in the Wrangler configuration)";
|
|
@@ -47794,7 +48491,7 @@ var renderTargets = (v) => table(
|
|
|
47794
48491
|
v.items.map((t) => ({ ...t, resource: t.resource_name, env: t.environment ?? "-", last_production: t.last_production ? `${t.last_production.status} ${t.last_production.commit_oid.slice(0, 12)}` : "-" })),
|
|
47795
48492
|
["name", "id", "platform", "resource", "env", "branch", "runs_on", "previews", "state", "last_production"]
|
|
47796
48493
|
);
|
|
47797
|
-
var renderTarget = (t) => kv({ ...t, connection: t.connection ? `${t.connection.name} (${t.connection.id}, ${t.connection.state})` : "-", last_production: t.last_production ? `${t.last_production.id} ${t.last_production.status}` : "-" }, [
|
|
48494
|
+
var renderTarget = (t) => kv({ ...t, approvers: ruleSummary(t.approval_rule), watch_paths: t.watch_paths?.length ? t.watch_paths.join(" ") : "every change", connection: t.connection ? `${t.connection.name} (${t.connection.id}, ${t.connection.state})` : "-", last_production: t.last_production ? `${t.last_production.id} ${t.last_production.status}` : "-" }, [
|
|
47798
48495
|
"id",
|
|
47799
48496
|
"name",
|
|
47800
48497
|
"platform",
|
|
@@ -47809,14 +48506,16 @@ var renderTarget = (t) => kv({ ...t, connection: t.connection ? `${t.connection.
|
|
|
47809
48506
|
"build_command",
|
|
47810
48507
|
"output_directory",
|
|
47811
48508
|
"runs_on",
|
|
48509
|
+
"watch_paths",
|
|
47812
48510
|
"previews",
|
|
47813
48511
|
"preview_check_name",
|
|
48512
|
+
"approvers",
|
|
47814
48513
|
"last_production",
|
|
47815
48514
|
"version"
|
|
47816
48515
|
]);
|
|
47817
48516
|
var renderDeployment = (d) => kv({ ...d, change: d.change ? `#${d.change.number}` : "-", logs: d.run?.logs_url ?? "-" }, ["id", "target_name", "kind", "environment", "status", "status_reason", "trigger", "commit_oid", "change", "version_id", "url", "alias_url", "verified", "duration_ms", "logs", "created_at"]) + (d.steps.length ? `
|
|
47818
48517
|
|
|
47819
|
-
${table(d.steps, ["name", "phase", "status", "summary"])}` : "");
|
|
48518
|
+
${table(d.steps, ["name", "phase", "status", "summary"])}` : "") + approvalLines(d.approval);
|
|
47820
48519
|
var renderDeployments = (v) => listWithCursor({ ...v, items: v.items.map((d) => ({ ...d, commit: d.commit_oid.slice(0, 12), change: d.change ? `#${d.change.number}` : "-" })) }, ["id", "target_name", "kind", "status", "commit", "change", "trigger", "created_at"]);
|
|
47821
48520
|
async function pick3(ctx, what, flag, options, label2) {
|
|
47822
48521
|
if (options.length === 1) return options[0];
|
|
@@ -47845,7 +48544,7 @@ async function stepsOf(ctx, spec) {
|
|
|
47845
48544
|
return v;
|
|
47846
48545
|
}
|
|
47847
48546
|
function targetOptions(cmd) {
|
|
47848
|
-
return cmd.option("--platform <platform>", "workers | pages").option("--connection <connection>", "deploy connection (icn_\u2026)").option("--account-id <id>", "Cloudflare account id").option("--env <name>", "Wrangler environment (deploys with --env)").option("--worker <name>", "Worker name (or Pages project name)").option("--root <dir>", "root directory (where the Wrangler config lives)").option("--branch <name>", "production branch (default: the repository's default branch)").option("--install <cmd>", "install command").option("--build <cmd>", "build command").option("--output-dir <dir>", "Pages: the built site").option("--previews", "upload a preview of every open change").option("--no-previews", "no previews").option("--preview-agent-changes", "also preview changes made by agents or integrations, isolated (Worker Previews only)").option("--no-preview-agent-changes", "previews only for changes people upload").option("--preview-mode <mode>", "versions (preview version with an alias) | worker_previews (`wrangler preview`: Durable Objects allowed, bindings from the `previews` block)").option("--preview-check <name>", "report previews as this check").option("--runs-on <label>", "hosted runner size for the deploy jobs: brnch-hosted (default), brnch-hosted-1 \u2026 brnch-hosted-4 (your plan sets the largest)").option("--name <name>", "target name").option("--steps <json>", `pre/post steps as a JSON array, @file or - (replaces the list), e.g. '[{"id":"migrate","kind":"d1_migrations","phase":"pre"}]'`);
|
|
48547
|
+
return cmd.option("--platform <platform>", "workers | pages").option("--connection <connection>", "deploy connection (icn_\u2026)").option("--account-id <id>", "Cloudflare account id").option("--env <name>", "Wrangler environment (deploys with --env)").option("--worker <name>", "Worker name (or Pages project name)").option("--root <dir>", "root directory (where the Wrangler config lives)").option("--branch <name>", "production branch (default: the repository's default branch)").option("--install <cmd>", "install command").option("--build <cmd>", "build command").option("--output-dir <dir>", "Pages: the built site").option("--previews", "upload a preview of every open change").option("--no-previews", "no previews").option("--preview-agent-changes", "also preview changes made by agents or integrations, isolated (Worker Previews only)").option("--no-preview-agent-changes", "previews only for changes people upload").option("--preview-mode <mode>", "versions (preview version with an alias) | worker_previews (`wrangler preview`: Durable Objects allowed, bindings from the `previews` block)").option("--preview-check <name>", "report previews as this check").option("--runs-on <label>", "hosted runner size for the deploy jobs: brnch-hosted (default), brnch-hosted-1 \u2026 brnch-hosted-4 (your plan sets the largest)").option("--watch-path <pattern>", "deploy only when these paths change (repeatable; replaces the list): CODEOWNERS patterns like apps/web/ or packages/**, ! excludes", (v, prev = []) => [...prev, v]).option("--watch-all", "deploy on every change (clears the watch paths)").option("--name <name>", "target name").option("--steps <json>", `pre/post steps as a JSON array, @file or - (replaces the list), e.g. '[{"id":"migrate","kind":"d1_migrations","phase":"pre"}]'`);
|
|
47849
48548
|
}
|
|
47850
48549
|
async function planSetup(ctx, repo, o) {
|
|
47851
48550
|
const c8 = ctx.client("user").deploy;
|
|
@@ -47920,6 +48619,7 @@ async function planSetup(ctx, repo, o) {
|
|
|
47920
48619
|
...o.previewAgentChanges !== void 0 ? { preview_agent_changes: o.previewAgentChanges } : {},
|
|
47921
48620
|
preview_check_name: o.previewCheck ?? null,
|
|
47922
48621
|
...o.runsOn ? { runs_on: o.runsOn } : {},
|
|
48622
|
+
...o.watchPath ? { watch_paths: o.watchPath } : {},
|
|
47923
48623
|
...o.branch ? { branch: o.branch } : {},
|
|
47924
48624
|
...o.steps !== void 0 ? { steps: await stepsOf(ctx, o.steps) } : {}
|
|
47925
48625
|
};
|
|
@@ -47931,6 +48631,7 @@ async function waitFor(ctx, d, interval, timeout) {
|
|
|
47931
48631
|
for (; ; ) {
|
|
47932
48632
|
if (d.status !== last) ctx.info(`${d.id}: ${d.status}${d.status_reason ? ` (${d.status_reason})` : ""}`);
|
|
47933
48633
|
last = d.status;
|
|
48634
|
+
ctx.status.working(`Deploy ${d.status}`);
|
|
47934
48635
|
if (TERMINAL4.has(d.status) || Date.now() > deadline) return d;
|
|
47935
48636
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
47936
48637
|
d = await ctx.client("user").deploy.deployments.get(d.id);
|
|
@@ -47950,11 +48651,28 @@ function registerDeploy(program2, rt) {
|
|
|
47950
48651
|
Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}`);
|
|
47951
48652
|
})
|
|
47952
48653
|
);
|
|
47953
|
-
deploy.command("targets").description("Deploy targets of --repo").action(act(rt, async (ctx) => ctx.result(await ctx.client("user").deploy.targets.list(await ctx.repoId(void 0, "user")), renderTargets)));
|
|
48654
|
+
const targets2 = deploy.command("targets").description("Deploy targets of --repo; `protect` requires approvers for a target's production deploys").action(act(rt, async (ctx) => ctx.result(await ctx.client("user").deploy.targets.list(await ctx.repoId(void 0, "user")), renderTargets)));
|
|
48655
|
+
protectOptions(targets2.command("protect <target>").description("Require approvals for the target's production deploys and rollbacks (people with deploy:manage). Replaces the current rule")).action(
|
|
48656
|
+
act(rt, async (ctx, o, [ref]) => {
|
|
48657
|
+
const c8 = ctx.client("user").deploy.targets;
|
|
48658
|
+
const t = await c8.get(await targetId(ctx, ref));
|
|
48659
|
+
ctx.result(await c8.protect(t.id, protectBody(o, t.approval_rule)), renderRule);
|
|
48660
|
+
})
|
|
48661
|
+
);
|
|
48662
|
+
targets2.command("unprotect <target>").description("Remove the target's approval rule; deploys waiting for it go on. Requires --yes when non-interactive").action(
|
|
48663
|
+
act(rt, async (ctx, _o, [ref]) => {
|
|
48664
|
+
const id2 = await targetId(ctx, ref);
|
|
48665
|
+
await ctx.confirm(`remove the approval rule of ${ref}: its production deploys will no longer wait for approval`);
|
|
48666
|
+
await ctx.client("user").deploy.targets.unprotect(id2);
|
|
48667
|
+
ctx.result({ unprotected: id2 }, `${ref} is no longer protected`);
|
|
48668
|
+
})
|
|
48669
|
+
);
|
|
48670
|
+
targets2.command("approvers <target>").description("Who may approve the target's production deploys now").action(act(rt, async (ctx, _o, [ref]) => ctx.result(await ctx.client("user").deploy.targets.approvers(await targetId(ctx, ref)), renderApprovers)));
|
|
47954
48671
|
deploy.command("target <target>").description("One deploy target (id, or name in --repo)").action(act(rt, async (ctx, _o, [ref]) => ctx.result(await ctx.client("user").deploy.targets.get(await targetId(ctx, ref)), renderTarget)));
|
|
47955
48672
|
targetOptions(deploy.command("update <target>").description("Change a deploy target (only the given flags; an empty value clears a command or the environment)")).option("--disable", "stop deploying (merges no longer deploy)").option("--enable", "deploy again").action(
|
|
47956
48673
|
act(rt, async (ctx, o, [ref]) => {
|
|
47957
48674
|
if (o.disable && o.enable) throw usage("give either --disable or --enable");
|
|
48675
|
+
if (o.watchAll && o.watchPath) throw usage("give either --watch-path or --watch-all");
|
|
47958
48676
|
const c8 = ctx.client("user").deploy.targets;
|
|
47959
48677
|
const cur = await c8.get(await targetId(ctx, ref));
|
|
47960
48678
|
const patch = {
|
|
@@ -47974,6 +48692,7 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
|
|
|
47974
48692
|
preview_agent_changes: o.previewAgentChanges,
|
|
47975
48693
|
preview_check_name: orNull(o.previewCheck),
|
|
47976
48694
|
runs_on: o.runsOn,
|
|
48695
|
+
watch_paths: o.watchAll ? [] : o.watchPath,
|
|
47977
48696
|
state: o.disable ? "disabled" : o.enable ? "active" : void 0,
|
|
47978
48697
|
steps: await stepsOf(ctx, o.steps)
|
|
47979
48698
|
};
|
|
@@ -48007,6 +48726,8 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
|
|
|
48007
48726
|
);
|
|
48008
48727
|
deploy.command("show <deployment>").description("One deployment with its steps").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client("user").deploy.deployments.get(id2), renderDeployment)));
|
|
48009
48728
|
deploy.command("cancel <deployment>").description("Cancel a queued or running deployment").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client("user").deploy.deployments.cancel(id2), renderDeployment)));
|
|
48729
|
+
deploy.command("approve <deployment>").description("Approve a deploy of a protected target (people who may approve; the last approval needed starts it)").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client("user").deploy.deployments.approve(id2), renderDeployment)));
|
|
48730
|
+
deploy.command("reject <deployment>").description("Reject a deploy waiting for approval; it is cancelled").option("--reason <text>", "why (recorded with the decision)").action(act(rt, async (ctx, o, [id2]) => ctx.result(await ctx.client("user").deploy.deployments.reject(id2, o.reason), renderDeployment)));
|
|
48010
48731
|
deploy.command("rollback <target>").description("Send all traffic back to the version of an earlier verified production deployment").requiredOption("--to <deployment>", "the deployment to roll back to (dpl_\u2026)").option("--message <text>", "deployment message").action(
|
|
48011
48732
|
act(rt, async (ctx, o, [ref]) => {
|
|
48012
48733
|
const id2 = await targetId(ctx, ref);
|
|
@@ -48019,8 +48740,7 @@ Merges to ${v.branch} deploy automatically. Deploy now: brnch deploy run ${v.id}
|
|
|
48019
48740
|
// packages/cli/src/commands/delegation.ts
|
|
48020
48741
|
var ITERATION_DONE = /* @__PURE__ */ new Set(["succeeded", "no_changes", "failed", "cancelled"]);
|
|
48021
48742
|
var STATES = ["working", "waiting", "stopped"];
|
|
48022
|
-
var
|
|
48023
|
-
var short = (oid) => oid ? oid.slice(0, 12) : null;
|
|
48743
|
+
var short2 = (oid) => oid ? oid.slice(0, 12) : null;
|
|
48024
48744
|
function vendorArg(v) {
|
|
48025
48745
|
if (!CODING_AGENT_VENDORS.includes(v)) throw usage(`unknown agent "${v}"; one of ${CODING_AGENT_VENDORS.join(", ")}`);
|
|
48026
48746
|
return v;
|
|
@@ -48039,8 +48759,8 @@ function renderIterations(d) {
|
|
|
48039
48759
|
const rows2 = d.iterations.map((i) => ({
|
|
48040
48760
|
...i,
|
|
48041
48761
|
trigger: i.trigger_ref ? `${i.trigger} (${i.trigger_ref})` : i.trigger,
|
|
48042
|
-
by:
|
|
48043
|
-
head:
|
|
48762
|
+
by: actorLabel(i.trigger_actor),
|
|
48763
|
+
head: short2(i.head_oid)
|
|
48044
48764
|
}));
|
|
48045
48765
|
const claims = d.iterations.map(reported).filter((x) => !!x);
|
|
48046
48766
|
const errors = d.iterations.filter((i) => i.error).map((i) => ` #${i.number}: ${i.error}`);
|
|
@@ -48061,7 +48781,7 @@ var renderDelegation = (d) => kv(
|
|
|
48061
48781
|
...d,
|
|
48062
48782
|
agent: `${d.agent.name} (${d.agent.vendor})`,
|
|
48063
48783
|
status: delegationStatusLabel(d),
|
|
48064
|
-
sponsor:
|
|
48784
|
+
sponsor: actorLabel(d.sponsor),
|
|
48065
48785
|
branch: `${d.source_ref} \u2192 ${d.target_ref}`,
|
|
48066
48786
|
automatic: `on review ${d.auto_on_review ? "on" : "off"}, on check failure ${d.auto_on_check_failure ? "on" : "off"}`,
|
|
48067
48787
|
budget: `iterations ${d.usage.iterations_used}/${d.budget.max_iterations}, tokens ${d.usage.tokens_used}/${d.budget.max_tokens}, ${d.budget.iteration_minutes} min per iteration`
|
|
@@ -48103,6 +48823,7 @@ async function waitForIteration(ctx, start, o) {
|
|
|
48103
48823
|
const line2 = `${delegationStatusLabel(d)}${it ? ` (iteration #${it.number}: ${it.state})` : ""}`;
|
|
48104
48824
|
if (line2 !== last) ctx.info(line2);
|
|
48105
48825
|
last = line2;
|
|
48826
|
+
ctx.status.working(`${d.agent.name}: ${line2}`);
|
|
48106
48827
|
if (it ? ITERATION_DONE.has(it.state) : d.state !== "working") return { d, timedOut: false };
|
|
48107
48828
|
if (Date.now() > deadline) return { d, timedOut: true };
|
|
48108
48829
|
await ctx.deps.sleep(Math.max(1, o.interval) * 1e3);
|
|
@@ -48742,7 +49463,18 @@ function registerRepo(program2, rt) {
|
|
|
48742
49463
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
48743
49464
|
job2 = await c8.migrations.get(job2.id);
|
|
48744
49465
|
}
|
|
48745
|
-
const
|
|
49466
|
+
const deps = job2.report.dependencies;
|
|
49467
|
+
const view = {
|
|
49468
|
+
id: job2.id,
|
|
49469
|
+
state: job2.state,
|
|
49470
|
+
issues: job2.report.counts.issues,
|
|
49471
|
+
pull_requests: job2.report.counts.pull_requests,
|
|
49472
|
+
comments: job2.report.counts.comments,
|
|
49473
|
+
releases: job2.report.counts.releases,
|
|
49474
|
+
...deps ? { blocked_by_links: `${deps.imported} imported${deps.skipped ? `, ${deps.skipped} skipped` : ""}` } : {},
|
|
49475
|
+
missing: job2.report.missing.join("; ") || "-",
|
|
49476
|
+
warnings: job2.report.warnings.join("; ") || "-"
|
|
49477
|
+
};
|
|
48746
49478
|
ctx.result(job2, () => kv(view, Object.keys(view)));
|
|
48747
49479
|
if (o.wait && job2.state !== "completed") throw new CliError(`migration ${job2.state}: ${job2.error ?? job2.report.missing.join("; ")}`, EXIT.NOT_READY);
|
|
48748
49480
|
})
|
|
@@ -48775,24 +49507,39 @@ function registerRepo(program2, rt) {
|
|
|
48775
49507
|
if (t.transport === "ssh") {
|
|
48776
49508
|
if (!t.ssh.ready) throw usage(`this machine is not set up for SSH${t.ssh.alias ? ` to ${t.ssh.alias}` : ""} yet: run \`${BRAND.cli} ssh setup\`, or clone with --https`);
|
|
48777
49509
|
const url2 = sshCloneUrl(t.ssh.alias, r.org_slug, r.name);
|
|
48778
|
-
const code2 = await ctx.deps.spawnGit(["clone", "--", url2, directory], { env: { ...ctx.deps.env, GIT_TERMINAL_PROMPT: "0" }, cwd: ctx.deps.cwd, stdoutToStderr: true });
|
|
49510
|
+
const code2 = await ctx.deps.spawnGit([...SNAPSHOT_CLONE, "clone", "--", url2, directory], { env: { ...ctx.deps.env, GIT_TERMINAL_PROMPT: "0" }, cwd: ctx.deps.cwd, stdoutToStderr: true });
|
|
48779
49511
|
if (code2 !== 0) throw new CliError(`git clone over SSH exited with status ${code2}; \`${BRAND.cli} ssh test\` explains SSH problems, --https clones without SSH`, EXIT.UNEXPECTED, "GIT_FAILED");
|
|
48780
49512
|
return ctx.result({ repository: r, directory, transport: "ssh", transport_reason: t.reason, clone_url: url2 }, cloned(r, directory, `over SSH (${url2})`, paint(colorEnabled(ctx.deps, "stdout"))));
|
|
48781
49513
|
}
|
|
48782
49514
|
if (t.approval) ctx.info(`Cloning over HTTPS: ${t.reason}. Pass --ssh to use SSH anyway.`);
|
|
48783
49515
|
const token = await ctx.freshToken("agent");
|
|
48784
|
-
const
|
|
49516
|
+
const helper2 = await credentialHelperConfigured(ctx);
|
|
49517
|
+
const list3 = helper2 && await gitAtLeast(ctx, "2.40.0") && await bundlesOffered(ctx, r.clone_url, token) ? [`--bundle-uri=${r.clone_url}/bundles`] : [];
|
|
49518
|
+
const code = await ctx.deps.spawnGit([...SNAPSHOT_CLONE, "clone", ...list3, "--", r.clone_url, directory], {
|
|
48785
49519
|
env: gitAuthEnv(ctx.deps.env, token, r.clone_url),
|
|
48786
49520
|
cwd: ctx.deps.cwd,
|
|
48787
49521
|
stdoutToStderr: true
|
|
48788
49522
|
});
|
|
48789
49523
|
if (code !== 0) throw new CliError(`git clone exited with status ${code}`, EXIT.UNEXPECTED, "GIT_FAILED");
|
|
48790
|
-
const helper2 = await credentialHelperConfigured(ctx);
|
|
48791
49524
|
ctx.info(authGuidance(BRAND.cli, helper2));
|
|
48792
49525
|
ctx.result({ repository: r, directory, transport: "https", transport_reason: t.reason, clone_url: r.clone_url, credential_helper: helper2 }, cloned(r, directory, "over HTTPS", paint(colorEnabled(ctx.deps, "stdout"))));
|
|
48793
49526
|
})
|
|
48794
49527
|
);
|
|
48795
49528
|
}
|
|
49529
|
+
var SNAPSHOT_CLONE = ["-c", "transfer.bundleURI=true"];
|
|
49530
|
+
async function bundlesOffered(ctx, cloneUrl, token) {
|
|
49531
|
+
try {
|
|
49532
|
+
const res = await ctx.deps.sys.fetch(`${cloneUrl}/bundles`, { headers: { authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(5e3) });
|
|
49533
|
+
return res.ok && (await res.text()).includes('[bundle "');
|
|
49534
|
+
} catch {
|
|
49535
|
+
return false;
|
|
49536
|
+
}
|
|
49537
|
+
}
|
|
49538
|
+
async function gitAtLeast(ctx, min) {
|
|
49539
|
+
const r = await ctx.deps.sys.run("git", ["--version"], { timeoutMs: 1e4 }).catch(() => null);
|
|
49540
|
+
const version4 = r?.code === 0 ? /git version (\d+\.\d+\.\d+)/.exec(r.stdout)?.[1] : void 0;
|
|
49541
|
+
return !!version4 && compareVersions(version4, min) >= 0;
|
|
49542
|
+
}
|
|
48796
49543
|
var hasRepo = (ctx) => {
|
|
48797
49544
|
try {
|
|
48798
49545
|
ctx.repoSpec();
|
|
@@ -48860,7 +49607,7 @@ function registerGit(program2, rt) {
|
|
|
48860
49607
|
}
|
|
48861
49608
|
|
|
48862
49609
|
// packages/cli/src/commands/release.ts
|
|
48863
|
-
var renderRelease = (r) => kv({ ...r, author: r.author
|
|
49610
|
+
var renderRelease = (r) => kv({ ...r, author: actorLabel(r.author) }, ["id", "tag", "name", "commit_oid", "draft", "author", "created_at", "notes"]);
|
|
48864
49611
|
function registerRelease(program2, rt) {
|
|
48865
49612
|
const release = program2.command("release").description("Releases of --repo (a tag with notes)");
|
|
48866
49613
|
release.command("list").description("Releases, newest first").action(
|
|
@@ -49054,15 +49801,16 @@ function registerImage(program2, rt) {
|
|
|
49054
49801
|
ctx.result(page, (p) => listWithCursor(p, ["reference", "tags", "versions", "updated_at"]));
|
|
49055
49802
|
})
|
|
49056
49803
|
);
|
|
49057
|
-
image.command("delete <ref>").description("Delete a tag (`<image>:<tag>`)
|
|
49804
|
+
image.command("delete <ref>").description("Delete a tag (`<image>:<tag>`), a version and all its tags (`<image>@sha256:\u2026`), or an image with no versions left (`<image>`); untagged versions are also collected after the retention window").action(
|
|
49058
49805
|
act(rt, async (ctx, _o, [spec]) => {
|
|
49059
49806
|
const ref = parseImageRef(spec, await registryHost(ctx));
|
|
49060
|
-
if (!ref
|
|
49807
|
+
if (!ref) throw usage(`invalid image reference "${spec}": expected <namespace>/<repository>[/<image>][:<tag>|@sha256:<digest>]`);
|
|
49061
49808
|
const img = await findImage(ctx, ref.name);
|
|
49062
49809
|
if (ref.digest) await ctx.client().packages.deleteVersion(img.id, ref.digest);
|
|
49063
|
-
else await ctx.client().packages.deleteTag(img.id, ref.tag);
|
|
49064
|
-
|
|
49065
|
-
|
|
49810
|
+
else if (ref.tag) await ctx.client().packages.deleteTag(img.id, ref.tag);
|
|
49811
|
+
else await ctx.client().packages.delete(img.id);
|
|
49812
|
+
const what = ref.digest ? `version ${ref.digest} of ${img.full_name}` : ref.tag ? `tag ${ref.tag} of ${img.full_name}` : `image ${img.full_name}`;
|
|
49813
|
+
ctx.result({ image: img.full_name, deleted: ref.digest ? { digest: ref.digest } : ref.tag ? { tag: ref.tag } : { image: img.id } }, `Deleted ${what}.`);
|
|
49066
49814
|
})
|
|
49067
49815
|
);
|
|
49068
49816
|
}
|
|
@@ -49302,12 +50050,13 @@ var renderRangeDiff = (r) => {
|
|
|
49302
50050
|
if (r.truncated) lines2.push("(truncated)");
|
|
49303
50051
|
return lines2.join("\n");
|
|
49304
50052
|
};
|
|
49305
|
-
var renderOperation = (o) => `${o.id} ${o.created_at} ${o.operation}/${o.kind} ${o.ref} ${o.old_oid.slice(0, 12)} \u2192 ${o.new_oid.slice(0, 12)} by ${o.actor
|
|
50053
|
+
var renderOperation = (o) => `${o.id} ${o.created_at} ${o.operation}/${o.kind} ${o.ref} ${o.old_oid.slice(0, 12)} \u2192 ${o.new_oid.slice(0, 12)} by ${actorLabel(o.actor)}${o.protected ? " [protected]" : ""}${o.undone_by ? ` (undone by ${o.undone_by})` : ""}`;
|
|
49306
50054
|
async function waitForPromotion(ctx, p, timeout, interval) {
|
|
49307
50055
|
const deadline = Date.now() + timeout * 1e3;
|
|
49308
50056
|
let last = p.state;
|
|
49309
50057
|
ctx.info(`promotion ${p.id}: ${p.state}`);
|
|
49310
50058
|
while (!TERMINAL5.has(p.state) && Date.now() < deadline) {
|
|
50059
|
+
ctx.status.working(`Merge ${p.state}`);
|
|
49311
50060
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
49312
50061
|
p = await ctx.client().promotions.get(p.id);
|
|
49313
50062
|
if (p.state !== last) ctx.info(`promotion ${p.id}: ${last = p.state}`);
|
|
@@ -49378,7 +50127,7 @@ next: --cursor ${v.next_cursor}` : "") : listWithCursor(v, ["id"]));
|
|
|
49378
50127
|
}
|
|
49379
50128
|
|
|
49380
50129
|
// packages/cli/src/commands/coordination.ts
|
|
49381
|
-
var holderOf = (c8) => `${c8.holder
|
|
50130
|
+
var holderOf = (c8) => `${actorLabel(c8.holder)}${c8.session_id ? ` (${c8.session_id})` : ""}`;
|
|
49382
50131
|
var renderClaim = (r) => [
|
|
49383
50132
|
`${r.already_held ? "Already yours" : "Claimed"}: ${r.claim.key}${r.claim.change ? ` (change #${r.claim.change.number})` : ""}`,
|
|
49384
50133
|
...r.skipped.length ? [`Taken by others: ${r.skipped.join(", ")}`] : [],
|
|
@@ -51681,7 +52430,7 @@ function safeLegs(m) {
|
|
|
51681
52430
|
}
|
|
51682
52431
|
|
|
51683
52432
|
// packages/cli/src/commands/actions.ts
|
|
51684
|
-
var
|
|
52433
|
+
var renderRun2 = (r) => kv({ ...r, actor: actorLabel(r.actor), trust: `${r.definition.source} ${r.definition.ref}${r.definition.trusted ? " (trusted)" : " (contributor-controlled)"}` }, [
|
|
51685
52434
|
"id",
|
|
51686
52435
|
"workflow_name",
|
|
51687
52436
|
"run_number",
|
|
@@ -51746,7 +52495,7 @@ function registerWorkflow(program2, rt) {
|
|
|
51746
52495
|
inputs[kvp.slice(0, i)] = kvp.slice(i + 1);
|
|
51747
52496
|
}
|
|
51748
52497
|
const r = await ctx.client("user").actions.workflows.dispatch(await ctx.repoId(void 0, "user"), file2, { ...o.ref ? { ref: o.ref } : {}, inputs });
|
|
51749
|
-
ctx.result(r,
|
|
52498
|
+
ctx.result(r, renderRun2);
|
|
51750
52499
|
})
|
|
51751
52500
|
);
|
|
51752
52501
|
}
|
|
@@ -51770,6 +52519,8 @@ async function watchRun(ctx, runId, interval, timeout, logs) {
|
|
|
51770
52519
|
${text2.trimEnd()}`);
|
|
51771
52520
|
}
|
|
51772
52521
|
}
|
|
52522
|
+
const finished = r.jobs.filter((j) => j.status === "completed").length;
|
|
52523
|
+
ctx.status.working(`Run ${r.status}: ${finished}/${r.jobs.length} jobs done`, r.jobs.length ? finished / r.jobs.length * 100 : void 0);
|
|
51773
52524
|
if (TERMINAL6.has(r.status) || Date.now() > deadline) return r;
|
|
51774
52525
|
await ctx.deps.sleep(Math.max(1, interval) * 1e3);
|
|
51775
52526
|
}
|
|
@@ -51789,13 +52540,13 @@ function registerRun(program2, rt) {
|
|
|
51789
52540
|
const r = await c8.actions.runs.get(id2);
|
|
51790
52541
|
if (o.log) for (const j of r.jobs) ctx.info(`\u2500\u2500 ${j.name} (${j.status}${j.conclusion ? `/${j.conclusion}` : ""}) \u2500\u2500
|
|
51791
52542
|
${(await c8.actions.jobs.logs(j.id)).trimEnd()}`);
|
|
51792
|
-
ctx.result(r,
|
|
52543
|
+
ctx.result(r, renderRun2);
|
|
51793
52544
|
})
|
|
51794
52545
|
);
|
|
51795
52546
|
run3.command("watch <run>").description("Follow a run until it completes. Exit 0 when it succeeded, 1 when it failed or was cancelled, 7 on timeout").option("--interval <seconds>", "poll interval", (v) => parseIntOption(v, "--interval"), 2).option("--timeout <seconds>", "give up after", (v) => parseIntOption(v, "--timeout"), 3600).option("--log", "print each job's log when it completes").action(
|
|
51796
52547
|
act(rt, async (ctx, o, [id2]) => {
|
|
51797
52548
|
const r = await watchRun(ctx, id2, o.interval, o.timeout, !!o.log);
|
|
51798
|
-
ctx.result(r,
|
|
52549
|
+
ctx.result(r, renderRun2);
|
|
51799
52550
|
ctx.setExit(runExit(r));
|
|
51800
52551
|
})
|
|
51801
52552
|
);
|
|
@@ -51820,8 +52571,8 @@ ${(await c8.actions.jobs.logs(j.id)).trimEnd()}`);
|
|
|
51820
52571
|
ctx.result({ job_id: job2, conclusion: status }, `job ${job2}: ${status}`);
|
|
51821
52572
|
})
|
|
51822
52573
|
);
|
|
51823
|
-
run3.command("cancel <run>").description("Cancel a run (running jobs are stopped on their runners)").action(act(rt, async (ctx, _o, [id2]) => ctx.result(await ctx.client("user").actions.runs.cancel(id2),
|
|
51824
|
-
run3.command("rerun <run>").description("Re-run a completed run (all jobs, or --failed and their dependents)").option("--failed", "only failed/cancelled jobs and jobs that depend on them").action(act(rt, async (ctx, o, [id2]) => ctx.result(await ctx.client("user").actions.runs.rerun(id2, { failed_only: !!o.failed }),
|
|
52574
|
+
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)));
|
|
52575
|
+
run3.command("rerun <run>").description("Re-run a completed run (all jobs, or --failed and their dependents)").option("--failed", "only failed/cancelled jobs and jobs that depend on them").action(act(rt, async (ctx, o, [id2]) => ctx.result(await ctx.client("user").actions.runs.rerun(id2, { failed_only: !!o.failed }), renderRun2)));
|
|
51825
52576
|
}
|
|
51826
52577
|
function registerSecret(program2, rt) {
|
|
51827
52578
|
const secret = program2.command("secret").description("Actions secrets of --repo (default) or the organization (--org-level). Values are write-only");
|
|
@@ -52402,6 +53153,10 @@ ${res.stdout.slice(-8e3)}`;
|
|
|
52402
53153
|
// packages/cli/src/runner/build-image-step.ts
|
|
52403
53154
|
var BUILDKIT_IMAGE = "moby/buildkit:v0.34.0@sha256:b059f8d7226d0b326bc871af489a5dddf65e36af4d20d14251b072974d9ee87c";
|
|
52404
53155
|
var COSIGN_IMAGE = "ghcr.io/sigstore/cosign/cosign:v3.1.3@sha256:9e5c2f2edc34351160407ca3416c61855bdf9403c3c5936e0f0be7fc261611b8";
|
|
53156
|
+
var BUILDKIT_RELEASE = { url: "https://github.com/moby/buildkit/releases/download/v0.34.0/buildkit-v0.34.0.linux-amd64.tar.gz", sha256: "14813b1fb2a40082114e6d386c6fc5e7dd5298176ce7ee1a18bc33e73e109ac3" };
|
|
53157
|
+
var COSIGN_RELEASE = { url: "https://github.com/sigstore/cosign/releases/download/v3.1.3/cosign-linux-amd64", sha256: "4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71" };
|
|
53158
|
+
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" };
|
|
53159
|
+
var BUILD_UID = 10999;
|
|
52405
53160
|
var MAX_METADATA_OUTPUT = 32 * 1024;
|
|
52406
53161
|
var list2 = (v) => (v ?? "").split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
|
|
52407
53162
|
var lines = (v) => (v ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
@@ -52454,15 +53209,15 @@ function inside2(p) {
|
|
|
52454
53209
|
}
|
|
52455
53210
|
return parts.join("/");
|
|
52456
53211
|
}
|
|
52457
|
-
function
|
|
52458
|
-
const
|
|
53212
|
+
function buildLayout(i) {
|
|
53213
|
+
const context = inside2(i.context);
|
|
52459
53214
|
const file2 = inside2(i.file ?? `${i.context}/Dockerfile`);
|
|
52460
|
-
if (
|
|
52461
|
-
if (file2 === null) return `file: ${i.file} is outside the workspace`;
|
|
52462
|
-
|
|
52463
|
-
|
|
52464
|
-
|
|
52465
|
-
const args = ["build", "--frontend", "dockerfile.v0", "--local", `context
|
|
53215
|
+
if (context === null) return `context: ${i.context} is outside the workspace`;
|
|
53216
|
+
if (file2 === null || file2 === "") return `file: ${i.file} is outside the workspace`;
|
|
53217
|
+
return { context, file: file2, filename: file2.slice(file2.lastIndexOf("/") + 1) };
|
|
53218
|
+
}
|
|
53219
|
+
function buildctlArgs(i, layout, metadataFile, root = "/workspace") {
|
|
53220
|
+
const args = ["build", "--frontend", "dockerfile.v0", "--local", `context=${root}/context`, "--local", `dockerfile=${root}/dockerfile`, "--opt", `filename=${layout.filename}`];
|
|
52466
53221
|
args.push("--opt", `platform=${i.platforms.join(",")}`);
|
|
52467
53222
|
if (i.target) args.push("--opt", `target=${i.target}`);
|
|
52468
53223
|
for (const [k, v] of Object.entries(i.buildArgs)) args.push("--opt", `build-arg:${k}=${v}`);
|
|
@@ -52475,6 +53230,215 @@ function buildctlArgs(i, metadataFile) {
|
|
|
52475
53230
|
args.push("--metadata-file", metadataFile);
|
|
52476
53231
|
return args;
|
|
52477
53232
|
}
|
|
53233
|
+
var quote2 = (s) => `'${s.replaceAll("'", `'\\''`)}'`;
|
|
53234
|
+
async function copyContext(d, layout, owner) {
|
|
53235
|
+
const fresh2 = async (hosted) => {
|
|
53236
|
+
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;
|
|
53237
|
+
const made = await d.capture(["mktemp", "-d", "/tmp/brnch-build-context.XXXXXXXX"], { env: d.env });
|
|
53238
|
+
const dir2 = made.stdout.trim();
|
|
53239
|
+
return made.code === 0 && dir2.startsWith("/tmp/brnch-build-context.") ? dir2 : null;
|
|
53240
|
+
};
|
|
53241
|
+
const staging = await fresh2(`${HOSTED_DIRS.context}-staging`);
|
|
53242
|
+
const dir = staging && await fresh2(HOSTED_DIRS.context);
|
|
53243
|
+
if (!staging || !dir) {
|
|
53244
|
+
if (staging) await d.run(["rm", "-rf", staging], { env: d.env, timeoutMs: 6e4 });
|
|
53245
|
+
return { error: "could not prepare the build context" };
|
|
53246
|
+
}
|
|
53247
|
+
const asStep = d.stepUser ? ["setpriv", `--reuid=${d.stepUser.uid}`, `--regid=${d.stepUser.gid}`, "--clear-groups", "--"] : [];
|
|
53248
|
+
const asOwner = owner !== null && owner !== 0 ? ["setpriv", `--reuid=${owner}`, `--regid=${owner}`, "--clear-groups", "--"] : [];
|
|
53249
|
+
const ws = (rel) => rel ? `${d.workspace}/${rel}` : d.workspace;
|
|
53250
|
+
const ignore = `${layout.filename}.dockerignore`;
|
|
53251
|
+
const stage = [
|
|
53252
|
+
"set -e",
|
|
53253
|
+
`w=$(cd -- ${quote2(d.workspace)} && pwd -P)`,
|
|
53254
|
+
'inside() { case "$1" in "$w"/*) ;; *) echo "brnch/build-image@v1: $2 is outside the workspace" >&2; exit 3;; esac; }',
|
|
53255
|
+
`c=$(cd -- ${quote2(ws(layout.context))} && pwd -P)`,
|
|
53256
|
+
'case "$c" in "$w"|"$w"/*) ;; *) echo "brnch/build-image@v1: the context is outside the workspace" >&2; exit 3;; esac',
|
|
53257
|
+
`fd=$(cd -- "$(dirname -- ${quote2(ws(layout.file))})" && pwd -P)`,
|
|
53258
|
+
`r=$(realpath -- "$fd/$(basename -- ${quote2(ws(layout.file))})")`,
|
|
53259
|
+
`inside "$r" ${quote2(layout.file)}`,
|
|
53260
|
+
`cp -a -- "$c/." ${quote2(`${staging}/context`)}`,
|
|
53261
|
+
`mkdir -- ${quote2(`${staging}/dockerfile`)}`,
|
|
53262
|
+
`cp -- "$r" ${quote2(`${staging}/dockerfile/${layout.filename}`)}`,
|
|
53263
|
+
// The name is the step's input: quoted into a variable, never into shell source (review of #287).
|
|
53264
|
+
`ig=${quote2(ignore)}`,
|
|
53265
|
+
'if [ -e "$fd/$ig" ]; then i=$(realpath -- "$fd/$ig"); inside "$i" "$ig"; cp -- "$i" ' + quote2(`${staging}/dockerfile/${ignore}`) + "; fi",
|
|
53266
|
+
// Readable by the builder user for stage 2; the step's own files, nothing else.
|
|
53267
|
+
`chmod -R a+rX ${quote2(staging)}`
|
|
53268
|
+
].join("; ");
|
|
53269
|
+
const fail = async () => {
|
|
53270
|
+
await d.run(["rm", "-rf", staging, dir], { env: d.env, timeoutMs: 6e4 });
|
|
53271
|
+
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` };
|
|
53272
|
+
};
|
|
53273
|
+
for (const cmd of [
|
|
53274
|
+
...d.stepUser ? [["chown", `${d.stepUser.uid}:${d.stepUser.gid}`, staging], ["chmod", "0755", staging]] : [],
|
|
53275
|
+
[...asStep, "sh", "-c", stage],
|
|
53276
|
+
...owner === null ? [] : [["chown", `${owner}:${owner}`, dir]],
|
|
53277
|
+
// No trailing "/.": a top directory swapped for a link is copied as that link, and refused below.
|
|
53278
|
+
[...asOwner, "cp", "-a", "--", `${staging}/context`, `${staging}/dockerfile`, `${dir}/`],
|
|
53279
|
+
["sh", "-c", 'for p in "$1/context" "$1/dockerfile"; do [ -d "$p" ] && [ ! -L "$p" ] || exit 1; done', "sh", dir],
|
|
53280
|
+
["rm", "-rf", staging]
|
|
53281
|
+
]) {
|
|
53282
|
+
const done2 = await d.run(cmd, { env: d.env, timeoutMs: 10 * 6e4 });
|
|
53283
|
+
if (done2 === null) return null;
|
|
53284
|
+
if (done2 !== 0) return fail();
|
|
53285
|
+
}
|
|
53286
|
+
return dir;
|
|
53287
|
+
}
|
|
53288
|
+
function buildScopes(i, host, repository = "", dockerfile = "") {
|
|
53289
|
+
const name = (ref) => ref.slice(host.length + 1).replace(/@.*$/, "").replace(/:[^/]*$/, "");
|
|
53290
|
+
const want = /* @__PURE__ */ new Map();
|
|
53291
|
+
const add = (ref, actions) => {
|
|
53292
|
+
const n = name(ref);
|
|
53293
|
+
want.set(n, /* @__PURE__ */ new Set([...want.get(n) ?? [], ...actions]));
|
|
53294
|
+
};
|
|
53295
|
+
if (i.push) for (const t of i.tags) add(t, ["pull", "push"]);
|
|
53296
|
+
for (const c8 of i.cacheTo) add(/(?:^|,)ref=([^,]+)/.exec(c8)[1], ["pull", "push"]);
|
|
53297
|
+
for (const c8 of i.cacheFrom) add(/(?:^|,)ref=([^,]+)/.exec(c8)[1], ["pull"]);
|
|
53298
|
+
if (repository && dockerfile) {
|
|
53299
|
+
for (const ref of dockerfileImages(dockerfile)) if (ownImage(ref, host, repository)) add(ref, ["pull"]);
|
|
53300
|
+
}
|
|
53301
|
+
return [...want].map(([n, a]) => `repository:${n}:${[...a].sort().join(",")}`);
|
|
53302
|
+
}
|
|
53303
|
+
function dockerfileImages(dockerfile) {
|
|
53304
|
+
const out = [];
|
|
53305
|
+
const all = dockerfile.replace(/\r\n/g, "\n").split("\n");
|
|
53306
|
+
let escape = "\\";
|
|
53307
|
+
for (const l of all) {
|
|
53308
|
+
const d = /^\s*#\s*(syntax|escape|check)\s*=\s*(\S+)\s*$/i.exec(l);
|
|
53309
|
+
if (!d) break;
|
|
53310
|
+
if (d[1].toLowerCase() === "escape" && (d[2] === "`" || d[2] === "\\")) escape = d[2];
|
|
53311
|
+
}
|
|
53312
|
+
const uncommented = all.filter((l) => !l.trim().startsWith("#")).join("\n");
|
|
53313
|
+
const lines2 = uncommented.split(`${escape}
|
|
53314
|
+
`).join(" ").split("\n");
|
|
53315
|
+
const value = String.raw`("[^"]*"|'[^']*'|[^\s,]+)`;
|
|
53316
|
+
const unquote = (v) => /^(["']).*\1$/.test(v) ? v.slice(1, -1) : v;
|
|
53317
|
+
for (const raw of lines2) {
|
|
53318
|
+
const line2 = raw.trim();
|
|
53319
|
+
if (!line2) continue;
|
|
53320
|
+
const from = /^FROM\s+(?:--platform=\S+\s+)?(\S+)/i.exec(line2);
|
|
53321
|
+
if (from) out.push(from[1]);
|
|
53322
|
+
if (/^(COPY|ADD)\s/i.test(line2)) for (const m of line2.matchAll(new RegExp(`--from=${value}`, "g"))) out.push(unquote(m[1]));
|
|
53323
|
+
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]));
|
|
53324
|
+
}
|
|
53325
|
+
return out;
|
|
53326
|
+
}
|
|
53327
|
+
var OCI_PATH = /^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*$/;
|
|
53328
|
+
function ownImage(ref, host, repository) {
|
|
53329
|
+
const m = /^([^/]+)\/([^:@]+)(?::[\w][\w.-]{0,127})?(?:@sha256:[a-f0-9]{64})?$/.exec(ref);
|
|
53330
|
+
if (!m || m[1] !== host || !OCI_PATH.test(m[2])) return false;
|
|
53331
|
+
const own2 = repository.toLowerCase();
|
|
53332
|
+
return m[2] === own2 || m[2].startsWith(`${own2}/`);
|
|
53333
|
+
}
|
|
53334
|
+
var BUILD_TOKEN_SECONDS = 21600;
|
|
53335
|
+
async function mintBuildToken(d, host, scopes) {
|
|
53336
|
+
const url2 = new URL(`${new URL(d.baseUrl).protocol}//${host}/v2/token`);
|
|
53337
|
+
url2.searchParams.set("service", host);
|
|
53338
|
+
for (const s of scopes) url2.searchParams.append("scope", s);
|
|
53339
|
+
url2.searchParams.set("expires_in", String(BUILD_TOKEN_SECONDS));
|
|
53340
|
+
try {
|
|
53341
|
+
const res = await d.fetch(url2.toString(), { headers: { authorization: `Bearer ${d.jobToken}` } });
|
|
53342
|
+
const body = await res.json().catch(() => null);
|
|
53343
|
+
if (res.ok && typeof body?.token === "string" && body.token) return body.token;
|
|
53344
|
+
return { error: `the registry did not issue a token for this build (HTTP ${res.status})` };
|
|
53345
|
+
} catch (e) {
|
|
53346
|
+
return { error: `could not reach the registry for a token: ${e.message}` };
|
|
53347
|
+
}
|
|
53348
|
+
}
|
|
53349
|
+
function dockerBuilder(d, auth, context) {
|
|
53350
|
+
const name = `brnch-buildkit-${d.jobId.replace(/[^a-z0-9]/gi, "").toLowerCase()}`;
|
|
53351
|
+
const docker = (a, timeoutMs = 10 * 6e4, env = d.env) => d.run(["docker", ...a], { env, timeoutMs });
|
|
53352
|
+
return {
|
|
53353
|
+
metadataFile: "/tmp/brnch-metadata.json",
|
|
53354
|
+
contextRoot: "/workspace",
|
|
53355
|
+
async start() {
|
|
53356
|
+
d.log(`##[group]Start BuildKit (${BUILDKIT_IMAGE.split("@")[0]})`);
|
|
53357
|
+
try {
|
|
53358
|
+
const started = await docker(["run", "-d", "--rm", "--privileged", "--name", name, "-v", `${context}:/workspace:ro`, "-v", `${auth}:/root/.docker:ro`, BUILDKIT_IMAGE]);
|
|
53359
|
+
if (started === null) return null;
|
|
53360
|
+
if (started !== 0) return "could not start the BuildKit container";
|
|
53361
|
+
return await waitReady(() => d.capture(["docker", "exec", name, "buildctl", "debug", "workers"], { env: d.env })) ? "" : "BuildKit did not become ready within 30 seconds";
|
|
53362
|
+
} finally {
|
|
53363
|
+
d.log("##[endgroup]");
|
|
53364
|
+
}
|
|
53365
|
+
},
|
|
53366
|
+
buildctl: (args) => docker(["exec", name, "buildctl", ...args], null),
|
|
53367
|
+
readMetadata: async () => (await d.capture(["docker", "exec", name, "cat", "/tmp/brnch-metadata.json"], { env: d.env })).stdout,
|
|
53368
|
+
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 }),
|
|
53369
|
+
stop: async () => {
|
|
53370
|
+
await d.capture(["docker", "rm", "-f", name], { env: d.env });
|
|
53371
|
+
}
|
|
53372
|
+
};
|
|
53373
|
+
}
|
|
53374
|
+
function hostedBuilder(d, auth, signing, context) {
|
|
53375
|
+
const tools = `${HOSTED_DIRS.tools}/build-image`;
|
|
53376
|
+
const bin = `${tools}/buildkit/bin`;
|
|
53377
|
+
const sock = `unix://${HOSTED_DIRS.client}/buildkitd.sock`;
|
|
53378
|
+
const asBuilder = (cmd) => ["setpriv", `--reuid=${BUILD_UID}`, `--regid=${BUILD_UID}`, "--clear-groups", "--", ...cmd];
|
|
53379
|
+
const buildctl = `${HOSTED_DIRS.client}/buildctl`;
|
|
53380
|
+
const clientEnv = { ...d.env, DOCKER_CONFIG: auth, HOME: HOSTED_DIRS.client };
|
|
53381
|
+
const run3 = (cmd, timeoutMs = 10 * 6e4, env = d.env) => d.run(cmd, { env, timeoutMs });
|
|
53382
|
+
const fetchChecked = async (url2, sha2562, out) => {
|
|
53383
|
+
const got = await run3(["curl", "-fsSL", "--retry", "3", "-o", out, url2], 5 * 6e4);
|
|
53384
|
+
if (got === null) return null;
|
|
53385
|
+
if (got !== 0) return `could not download ${url2}`;
|
|
53386
|
+
const sum = (await d.capture(["sha256sum", out], { env: d.env })).stdout.split(/\s/)[0];
|
|
53387
|
+
return sum === sha2562 ? "" : `${url2} has sha256 ${sum || "(unreadable)"}, not the pinned ${sha2562}`;
|
|
53388
|
+
};
|
|
53389
|
+
return {
|
|
53390
|
+
metadataFile: `${HOSTED_DIRS.client}/metadata.json`,
|
|
53391
|
+
contextRoot: context,
|
|
53392
|
+
async start() {
|
|
53393
|
+
d.log(`##[group]Start BuildKit (${BUILDKIT_RELEASE.url.split("/").at(-1)}, beside the job)`);
|
|
53394
|
+
try {
|
|
53395
|
+
for (const dir of [HOSTED_DIRS.tools, tools, `${tools}/buildkit`, HOSTED_DIRS.state, HOSTED_DIRS.run]) {
|
|
53396
|
+
const made = await run3(["install", "-d", "-m", "0700", dir]);
|
|
53397
|
+
if (made !== 0) return made === null ? null : `could not create ${dir}`;
|
|
53398
|
+
}
|
|
53399
|
+
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]]) {
|
|
53400
|
+
const done2 = await run3(cmd);
|
|
53401
|
+
if (done2 !== 0) return done2 === null ? null : `could not prepare the build user (${cmd[0]})`;
|
|
53402
|
+
}
|
|
53403
|
+
const tgz = `${tools}/buildkit.tar.gz`;
|
|
53404
|
+
const got = await fetchChecked(BUILDKIT_RELEASE.url, BUILDKIT_RELEASE.sha256, tgz);
|
|
53405
|
+
if (got !== "") return got;
|
|
53406
|
+
const unpacked = await run3(["tar", "-xzf", tgz, "-C", `${tools}/buildkit`]);
|
|
53407
|
+
if (unpacked !== 0) return unpacked === null ? null : "could not unpack BuildKit";
|
|
53408
|
+
const client = await run3(["install", "-m", "0755", `${bin}/buildctl`, buildctl]);
|
|
53409
|
+
if (client !== 0) return client === null ? null : "could not install buildctl for the build user";
|
|
53410
|
+
if (signing) {
|
|
53411
|
+
const cosign = await fetchChecked(COSIGN_RELEASE.url, COSIGN_RELEASE.sha256, `${tools}/cosign`);
|
|
53412
|
+
if (cosign !== "") return cosign;
|
|
53413
|
+
if (await run3(["chmod", "0700", `${tools}/cosign`]) !== 0) return "could not install cosign";
|
|
53414
|
+
}
|
|
53415
|
+
const daemon = `${bin}/buildkitd --root ${HOSTED_DIRS.state} --addr ${sock} --group ${BUILD_UID} --oci-worker-snapshotter=overlayfs --containerd-worker=false`;
|
|
53416
|
+
const started = await run3(["sh", "-c", `PATH=${bin}:$PATH nohup ${daemon} > ${HOSTED_DIRS.run}/buildkitd.log 2>&1 & echo $! > ${HOSTED_DIRS.run}/buildkitd.pid`]);
|
|
53417
|
+
if (started !== 0) return started === null ? null : "could not start buildkitd";
|
|
53418
|
+
if (await waitReady(() => d.capture(asBuilder([buildctl, "--addr", sock, "debug", "workers"]), { env: clientEnv }))) return "";
|
|
53419
|
+
d.log((await d.capture(["tail", "-20", `${HOSTED_DIRS.run}/buildkitd.log`], { env: d.env })).stdout);
|
|
53420
|
+
return "BuildKit did not become ready within 30 seconds";
|
|
53421
|
+
} finally {
|
|
53422
|
+
d.log("##[endgroup]");
|
|
53423
|
+
}
|
|
53424
|
+
},
|
|
53425
|
+
// buildctl reads registry credentials from DOCKER_CONFIG on the client side and hands them to buildkitd's session.
|
|
53426
|
+
buildctl: (args) => run3(asBuilder([buildctl, "--addr", sock, ...args]), null, clientEnv),
|
|
53427
|
+
readMetadata: async () => (await d.capture(["cat", `${HOSTED_DIRS.client}/metadata.json`], { env: d.env })).stdout,
|
|
53428
|
+
sign: (args, env) => run3([`${tools}/cosign`, ...args], 5 * 6e4, { ...d.env, ...env, DOCKER_CONFIG: auth, HOME: auth }),
|
|
53429
|
+
stop: async () => {
|
|
53430
|
+
const pid = `${HOSTED_DIRS.run}/buildkitd.pid`;
|
|
53431
|
+
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 });
|
|
53432
|
+
}
|
|
53433
|
+
};
|
|
53434
|
+
}
|
|
53435
|
+
async function waitReady(probe) {
|
|
53436
|
+
for (let i = 0; i < 30; i++) {
|
|
53437
|
+
if ((await probe()).code === 0) return true;
|
|
53438
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
53439
|
+
}
|
|
53440
|
+
return false;
|
|
53441
|
+
}
|
|
52478
53442
|
async function runBuildImage(w, d) {
|
|
52479
53443
|
const fail = (msg) => {
|
|
52480
53444
|
d.log(`##[error]brnch/build-image@v1: ${msg}`);
|
|
@@ -52486,41 +53450,46 @@ async function runBuildImage(w, d) {
|
|
|
52486
53450
|
const bad = checkRefs(parsed, host, d.repository);
|
|
52487
53451
|
if (bad) return fail(bad);
|
|
52488
53452
|
if (parsed.push && !parsed.tags.length) return fail("tags are required when pushing (or set push: false)");
|
|
52489
|
-
if (d.hosted)
|
|
52490
|
-
|
|
52491
|
-
}
|
|
52492
|
-
if ((await d.capture(["docker", "info", "--format", "{{.ServerVersion}}"], { env: d.env })).code !== 0) {
|
|
53453
|
+
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");
|
|
53454
|
+
if (!d.hosted && (await d.capture(["docker", "info", "--format", "{{.ServerVersion}}"], { env: d.env })).code !== 0) {
|
|
52493
53455
|
return fail("this runner has no usable Docker (docker info failed); install Docker or run the job on a runner that has it");
|
|
52494
53456
|
}
|
|
52495
|
-
const
|
|
52496
|
-
if (typeof
|
|
52497
|
-
const
|
|
52498
|
-
|
|
52499
|
-
|
|
52500
|
-
const
|
|
53457
|
+
const layout = buildLayout(parsed);
|
|
53458
|
+
if (typeof layout === "string") return fail(layout);
|
|
53459
|
+
const context = await copyContext(d, layout, d.hosted ? BUILD_UID : d.stepUser ? 0 : null);
|
|
53460
|
+
if (context === null) return { outcome: "cancelled", outputs: {} };
|
|
53461
|
+
if (typeof context !== "string") return fail(context.error);
|
|
53462
|
+
const auth = d.hosted ? `${HOSTED_DIRS.client}/docker` : `${d.temp}/build-image-${d.jobId}`;
|
|
53463
|
+
const builder = d.hosted ? hostedBuilder(d, auth, !!parsed.cosignKey && parsed.push, context) : dockerBuilder(d, auth, context);
|
|
53464
|
+
const args = buildctlArgs(parsed, layout, builder.metadataFile, builder.contextRoot);
|
|
53465
|
+
const dockerfile = (await d.capture(["cat", "--", `${context}/dockerfile/${layout.filename}`], { env: d.env })).stdout;
|
|
53466
|
+
const scopes = buildScopes(parsed, host, d.repository, dockerfile);
|
|
53467
|
+
let credential = null;
|
|
53468
|
+
if (scopes.length) {
|
|
53469
|
+
const minted = await mintBuildToken(d, host, scopes);
|
|
53470
|
+
if (typeof minted !== "string") {
|
|
53471
|
+
await d.run(["rm", "-rf", context], { env: d.env, timeoutMs: 6e4 });
|
|
53472
|
+
return fail(minted.error);
|
|
53473
|
+
}
|
|
53474
|
+
credential = minted;
|
|
53475
|
+
}
|
|
53476
|
+
d.writePrivate(`${auth}/config.json`, JSON.stringify({ auths: credential ? { [host]: { auth: btoa(`brnch:${credential}`) } } : {} }));
|
|
52501
53477
|
try {
|
|
52502
|
-
|
|
52503
|
-
|
|
52504
|
-
if (started
|
|
52505
|
-
let ready = false;
|
|
52506
|
-
for (let i = 0; i < 30 && !ready; i++) {
|
|
52507
|
-
ready = (await d.capture(["docker", "exec", name, "buildctl", "debug", "workers"], { env: d.env })).code === 0;
|
|
52508
|
-
if (!ready) await new Promise((r) => setTimeout(r, 1e3));
|
|
52509
|
-
}
|
|
52510
|
-
d.log("##[endgroup]");
|
|
52511
|
-
if (!ready) return fail("BuildKit did not become ready within 30 seconds");
|
|
53478
|
+
const started = await builder.start();
|
|
53479
|
+
if (started === null) return { outcome: "cancelled", outputs: {} };
|
|
53480
|
+
if (started) return fail(started);
|
|
52512
53481
|
d.log(`##[group]Build ${parsed.tags.join(", ") || "(no tags)"} for ${parsed.platforms.join(", ")}`);
|
|
52513
|
-
const built = await
|
|
53482
|
+
const built = await builder.buildctl(args);
|
|
52514
53483
|
d.log("##[endgroup]");
|
|
52515
53484
|
if (built === null) return { outcome: "cancelled", outputs: {} };
|
|
52516
53485
|
if (built !== 0) return fail(`the build failed (buildctl exited ${built})`);
|
|
52517
|
-
const meta3 = await
|
|
53486
|
+
const meta3 = await builder.readMetadata();
|
|
52518
53487
|
let digest = "";
|
|
52519
53488
|
try {
|
|
52520
|
-
digest = String(JSON.parse(meta3
|
|
53489
|
+
digest = String(JSON.parse(meta3)["containerimage.digest"] ?? "");
|
|
52521
53490
|
} catch {
|
|
52522
53491
|
}
|
|
52523
|
-
const outputs = { metadata: meta3.
|
|
53492
|
+
const outputs = { metadata: meta3.slice(0, MAX_METADATA_OUTPUT) };
|
|
52524
53493
|
if (parsed.push) {
|
|
52525
53494
|
if (!/^sha256:[a-f0-9]{64}$/.test(digest)) return fail("the build pushed, but BuildKit reported no image digest");
|
|
52526
53495
|
const first2 = parsed.tags[0].replace(/[:@][^/]*$/, "");
|
|
@@ -52529,9 +53498,9 @@ async function runBuildImage(w, d) {
|
|
|
52529
53498
|
d.log(`pushed ${outputs.image}`);
|
|
52530
53499
|
if (parsed.cosignKey) {
|
|
52531
53500
|
d.log(`##[group]Sign ${outputs.image} with cosign`);
|
|
52532
|
-
const signArgs = ["
|
|
53501
|
+
const signArgs = ["sign", "--key", "env://COSIGN_KEY", "--yes"];
|
|
52533
53502
|
if (!parsed.cosignTlog) signArgs.push("--use-signing-config=false", "--tlog-upload=false");
|
|
52534
|
-
const signed = await
|
|
53503
|
+
const signed = await builder.sign([...signArgs, outputs.image], { COSIGN_KEY: parsed.cosignKey, COSIGN_PASSWORD: parsed.cosignPassword ?? "" });
|
|
52535
53504
|
d.log("##[endgroup]");
|
|
52536
53505
|
if (signed === null) return { outcome: "cancelled", outputs };
|
|
52537
53506
|
if (signed !== 0) return fail(`cosign could not sign ${outputs.image} (exit ${signed})`);
|
|
@@ -52542,8 +53511,9 @@ async function runBuildImage(w, d) {
|
|
|
52542
53511
|
}
|
|
52543
53512
|
return { outcome: "success", outputs };
|
|
52544
53513
|
} finally {
|
|
52545
|
-
await
|
|
53514
|
+
await builder.stop();
|
|
52546
53515
|
d.removeDir(auth);
|
|
53516
|
+
await d.run(["rm", "-rf", context], { env: d.env, timeoutMs: 6e4 });
|
|
52547
53517
|
}
|
|
52548
53518
|
}
|
|
52549
53519
|
|
|
@@ -53218,6 +54188,14 @@ async function runCodeReviewStep(spec, host) {
|
|
|
53218
54188
|
return ok && parsed.status !== "failed" ? "success" : "failure";
|
|
53219
54189
|
}
|
|
53220
54190
|
|
|
54191
|
+
// packages/cli/src/runner/checkout-retry.ts
|
|
54192
|
+
var CHECKOUT_RETRY_DELAYS_MS = [15e3, 3e4, 6e4, 12e4, 24e4];
|
|
54193
|
+
var RPC_FAILED = /\bRPC failed; (?:HTTP (?:429|5\d\d)\b|curl (?:6|7|16|18|28|35|52|55|56|92)\b)/;
|
|
54194
|
+
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_)/;
|
|
54195
|
+
function transientGitFailure(line2) {
|
|
54196
|
+
return RPC_FAILED.test(line2) || UNABLE_TO_ACCESS.test(line2);
|
|
54197
|
+
}
|
|
54198
|
+
|
|
53221
54199
|
// packages/cli/src/runner/executor.ts
|
|
53222
54200
|
var NODE_OS = { linux: "Linux", darwin: "macOS", win32: "Windows" };
|
|
53223
54201
|
var NODE_ARCH = { x64: "X64", arm64: "ARM64", arm: "ARM" };
|
|
@@ -53467,6 +54445,12 @@ var JobExecution = class {
|
|
|
53467
54445
|
}
|
|
53468
54446
|
spawnSync2("rm", ["-rf", "--", gone], { uid: this.owner.uid, gid: this.owner.gid, stdio: "ignore" });
|
|
53469
54447
|
}
|
|
54448
|
+
/** Wait `ms`, cut short when the job is cancelled: whether the job may go on. */
|
|
54449
|
+
async pause(ms) {
|
|
54450
|
+
const until = Date.now() + ms;
|
|
54451
|
+
while (!this.cancelled && Date.now() < until) await new Promise((r) => setTimeout(r, Math.min(1e3, until - Date.now())));
|
|
54452
|
+
return !this.cancelled;
|
|
54453
|
+
}
|
|
53470
54454
|
/** Run one process with streamed, masked output. Resolves to its exit code, or null if it was killed. */
|
|
53471
54455
|
exec(cmd, o) {
|
|
53472
54456
|
return new Promise((resolveExit) => {
|
|
@@ -53744,18 +54728,41 @@ var JobExecution = class {
|
|
|
53744
54728
|
}
|
|
53745
54729
|
const depth = w["fetch-depth"] === void 0 ? 1 : Number(w["fetch-depth"]);
|
|
53746
54730
|
const env = this.gitEnv();
|
|
53747
|
-
|
|
54731
|
+
let transient = false;
|
|
54732
|
+
const onLine = (l) => {
|
|
54733
|
+
if (transientGitFailure(l)) transient = true;
|
|
54734
|
+
return false;
|
|
54735
|
+
};
|
|
54736
|
+
const git4 = (args) => {
|
|
54737
|
+
transient = false;
|
|
54738
|
+
return this.exec(["git", ...args], { cwd: this.workspace, env, asStepUser: false, timeoutMs: 30 * 6e4, onLine });
|
|
54739
|
+
};
|
|
54740
|
+
const fetchCommit = async (args) => {
|
|
54741
|
+
for (let attempt = 0; ; attempt++) {
|
|
54742
|
+
const code2 = await git4(["fetch", ...args]);
|
|
54743
|
+
const delay = CHECKOUT_RETRY_DELAYS_MS[attempt];
|
|
54744
|
+
if (code2 === 0 || code2 === null || !transient) return code2;
|
|
54745
|
+
if (delay === void 0 || Date.now() + delay >= this.deadline) {
|
|
54746
|
+
this.log("##[error]the server kept failing the fetch; the repository's storage may be busy. Run the job again in a few minutes");
|
|
54747
|
+
return code2;
|
|
54748
|
+
}
|
|
54749
|
+
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})`);
|
|
54750
|
+
if (!await this.pause(delay)) return null;
|
|
54751
|
+
}
|
|
54752
|
+
};
|
|
53748
54753
|
this.discard(this.workspace);
|
|
53749
54754
|
mkdirSync3(this.workspace, { mode: 493 });
|
|
53750
54755
|
const branch = a.ref.startsWith("refs/heads/") ? a.ref.slice("refs/heads/".length) : null;
|
|
53751
54756
|
const fetchAndCheckout = async () => {
|
|
53752
54757
|
for (const args of [["init", "-q"], ["remote", "add", "origin", this.gitUrl()]]) if (await git4(args) !== 0) return 1;
|
|
53753
|
-
let code2
|
|
53754
|
-
if (
|
|
53755
|
-
|
|
53756
|
-
if (
|
|
53757
|
-
|
|
53758
|
-
|
|
54758
|
+
let code2;
|
|
54759
|
+
if (depth > 0) {
|
|
54760
|
+
code2 = await fetchCommit(["--no-tags", "--prune", `--depth=${depth}`, "origin", a.head_sha]);
|
|
54761
|
+
if (code2 !== 0 && code2 !== null && !transient) {
|
|
54762
|
+
this.log("##[brnch] the shallow fetch failed; fetching full history of the commit");
|
|
54763
|
+
code2 = await fetchCommit(["--no-tags", "origin", a.head_sha]);
|
|
54764
|
+
}
|
|
54765
|
+
} else code2 = await fetchCommit(["--no-tags", "origin", a.head_sha]);
|
|
53759
54766
|
if (code2 !== 0) return code2;
|
|
53760
54767
|
return git4(branch ? ["checkout", "-q", "-B", branch, a.head_sha] : ["checkout", "-q", "--detach", a.head_sha]);
|
|
53761
54768
|
};
|
|
@@ -53800,6 +54807,7 @@ var JobExecution = class {
|
|
|
53800
54807
|
async buildImageStep(w, env) {
|
|
53801
54808
|
const r = await runBuildImage(w, {
|
|
53802
54809
|
baseUrl: this.opts.baseUrl,
|
|
54810
|
+
fetch: (url2, init) => fetch(url2, init),
|
|
53803
54811
|
registryHost: this.a.registry_host ?? null,
|
|
53804
54812
|
repository: this.a.repository.full_name,
|
|
53805
54813
|
jobToken: this.a.job_token,
|
|
@@ -53807,6 +54815,7 @@ var JobExecution = class {
|
|
|
53807
54815
|
workspace: this.workspace,
|
|
53808
54816
|
temp: this.temp,
|
|
53809
54817
|
hosted: this.opts.hosted,
|
|
54818
|
+
stepUser: this.opts.stepUser ? { uid: this.opts.stepUser.uid, gid: this.opts.stepUser.gid } : null,
|
|
53810
54819
|
log: (l) => this.log(l),
|
|
53811
54820
|
run: (cmd, o) => this.exec(cmd, { cwd: this.workspace, env: o.env, asStepUser: false, timeoutMs: o.timeoutMs, onLine: this.onCommandLine }),
|
|
53812
54821
|
capture: async (cmd, o) => {
|
|
@@ -54021,12 +55030,81 @@ function sanitizedEnv(env) {
|
|
|
54021
55030
|
return out;
|
|
54022
55031
|
}
|
|
54023
55032
|
|
|
55033
|
+
// packages/cli/src/runner/resources.ts
|
|
55034
|
+
import { statfs } from "node:fs/promises";
|
|
55035
|
+
import { cpus, freemem, totalmem, uptime } from "node:os";
|
|
55036
|
+
function cpuPercent(before, after) {
|
|
55037
|
+
if (!before.length || before.length !== after.length) return null;
|
|
55038
|
+
let idle = 0;
|
|
55039
|
+
let total = 0;
|
|
55040
|
+
for (let i = 0; i < after.length; i++) {
|
|
55041
|
+
for (const key of ["user", "nice", "sys", "idle", "irq"]) {
|
|
55042
|
+
const delta = after[i].times[key] - before[i].times[key];
|
|
55043
|
+
if (delta < 0 || !Number.isFinite(delta)) return null;
|
|
55044
|
+
total += delta;
|
|
55045
|
+
if (key === "idle") idle += delta;
|
|
55046
|
+
}
|
|
55047
|
+
}
|
|
55048
|
+
return total > 0 ? Math.round((1 - idle / total) * 1e3) / 10 : null;
|
|
55049
|
+
}
|
|
55050
|
+
var RunnerResourceCollector = class {
|
|
55051
|
+
constructor(workDir, jobSlots) {
|
|
55052
|
+
this.workDir = workDir;
|
|
55053
|
+
this.jobSlots = jobSlots;
|
|
55054
|
+
}
|
|
55055
|
+
workDir;
|
|
55056
|
+
jobSlots;
|
|
55057
|
+
previous = null;
|
|
55058
|
+
disk = null;
|
|
55059
|
+
diskPending = false;
|
|
55060
|
+
sample() {
|
|
55061
|
+
const at = performance.now();
|
|
55062
|
+
try {
|
|
55063
|
+
this.refreshDisk();
|
|
55064
|
+
const current = cpus();
|
|
55065
|
+
const interval = this.previous ? Math.round(at - this.previous.at) : 0;
|
|
55066
|
+
const cpu = this.previous && interval > 0 && interval <= 6e5 ? cpuPercent(this.previous.cpus, current) : null;
|
|
55067
|
+
this.previous = { cpus: current, at };
|
|
55068
|
+
const disk = this.disk && at - this.disk.at <= 6e4 ? this.disk : null;
|
|
55069
|
+
const totalMemory = totalmem();
|
|
55070
|
+
const parsed = RunnerResourceSnapshot.safeParse({
|
|
55071
|
+
cpu_percent: cpu,
|
|
55072
|
+
cpu_count: current.length,
|
|
55073
|
+
cpu_interval_ms: cpu === null ? null : interval,
|
|
55074
|
+
memory_total_bytes: totalMemory,
|
|
55075
|
+
memory_available_bytes: Math.min(totalMemory, Math.max(0, freemem())),
|
|
55076
|
+
disk_total_bytes: disk?.total ?? null,
|
|
55077
|
+
disk_available_bytes: disk?.available ?? null,
|
|
55078
|
+
uptime_seconds: Math.floor(uptime()),
|
|
55079
|
+
job_slots: this.jobSlots
|
|
55080
|
+
});
|
|
55081
|
+
return parsed.success ? parsed.data : void 0;
|
|
55082
|
+
} catch {
|
|
55083
|
+
return void 0;
|
|
55084
|
+
}
|
|
55085
|
+
}
|
|
55086
|
+
refreshDisk() {
|
|
55087
|
+
if (this.diskPending) return;
|
|
55088
|
+
this.diskPending = true;
|
|
55089
|
+
void statfs(this.workDir).then((s) => {
|
|
55090
|
+
const total = s.blocks * s.bsize;
|
|
55091
|
+
const available = Math.min(total, Math.max(0, s.bavail * s.bsize));
|
|
55092
|
+
this.disk = Number.isSafeInteger(total) && total > 0 && Number.isSafeInteger(available) ? { total, available, at: performance.now() } : null;
|
|
55093
|
+
}).catch(() => {
|
|
55094
|
+
this.disk = null;
|
|
55095
|
+
}).finally(() => {
|
|
55096
|
+
this.diskPending = false;
|
|
55097
|
+
});
|
|
55098
|
+
}
|
|
55099
|
+
};
|
|
55100
|
+
|
|
54024
55101
|
// packages/cli/src/runner/agent.ts
|
|
54025
55102
|
var RunnerAgent = class {
|
|
54026
55103
|
constructor(cfg, opts) {
|
|
54027
55104
|
this.cfg = cfg;
|
|
54028
55105
|
this.opts = opts;
|
|
54029
55106
|
this.client = this.clientFor(cfg);
|
|
55107
|
+
this.resources = new RunnerResourceCollector(opts.workDir, opts.once || cfg.ephemeral ? 1 : opts.jobs);
|
|
54030
55108
|
}
|
|
54031
55109
|
cfg;
|
|
54032
55110
|
opts;
|
|
@@ -54038,6 +55116,7 @@ var RunnerAgent = class {
|
|
|
54038
55116
|
exit = null;
|
|
54039
55117
|
heartbeatTimer = null;
|
|
54040
55118
|
taken = 0;
|
|
55119
|
+
resources;
|
|
54041
55120
|
clientFor(cfg) {
|
|
54042
55121
|
return createClient({ baseUrl: cfg.url, token: cfg.credential, userAgent: `${BRAND.cli}-runner/${BRAND.version}`, headers: { "x-brnch-runner-version": BRAND.version }, ...this.opts.fetch ? { fetch: this.opts.fetch } : {} });
|
|
54043
55122
|
}
|
|
@@ -54075,7 +55154,7 @@ var RunnerAgent = class {
|
|
|
54075
55154
|
}
|
|
54076
55155
|
async heartbeat() {
|
|
54077
55156
|
try {
|
|
54078
|
-
const r = await this.client.runner.heartbeat({ running_jobs: [...this.running.keys()] });
|
|
55157
|
+
const r = await this.client.runner.heartbeat({ running_jobs: [...this.running.keys()], resources: this.resources.sample() });
|
|
54079
55158
|
for (const id2 of r.cancel_jobs) this.running.get(id2)?.exec.cancel("cancelled by the server");
|
|
54080
55159
|
for (const id2 of r.forget_jobs) this.running.get(id2)?.exec.abort("the server reassigned this job");
|
|
54081
55160
|
if (r.removed) {
|
|
@@ -54122,6 +55201,7 @@ var RunnerAgent = class {
|
|
|
54122
55201
|
await this.client.runner.diagnostics(diagnostics(this.cfg)).catch(() => void 0);
|
|
54123
55202
|
const unhook = this.opts.onSignal?.((s) => this.shutdown(s)) ?? (() => void 0);
|
|
54124
55203
|
this.heartbeatTimer = setInterval(() => void this.heartbeat(), Math.max(1, me.heartbeat_seconds) * 1e3);
|
|
55204
|
+
await this.heartbeat();
|
|
54125
55205
|
const single = this.opts.once || this.cfg.ephemeral;
|
|
54126
55206
|
let backoff = 1e3;
|
|
54127
55207
|
try {
|
|
@@ -55139,6 +56219,13 @@ var EXAMPLES = {
|
|
|
55139
56219
|
"deploy run": [`${c7} deploy run production --wait || echo "deploy failed (exit $?)"`, `${c7} deploy run dpt_123 --commit 0123456789abcdef0123456789abcdef01234567`],
|
|
55140
56220
|
"deploy update": [`${c7} deploy update production --build "npm run build:prod"`, `${c7} deploy update production --disable`],
|
|
55141
56221
|
"deploy rollback": [`${c7} deploy rollback production --to dpl_123`],
|
|
56222
|
+
"db sources add": [
|
|
56223
|
+
`${c7} db suggest # the migration directories found`,
|
|
56224
|
+
`${c7} db sources add --name app --path db/migrations --format atlas --engine postgres --connection icn_123 --organization acme --database app --revisions-schema app_atlas`,
|
|
56225
|
+
`op read "op://Infra/app-dsn/credential" | ${c7} db sources add --name app \u2026 --credential-mode dsn --dsn-file -`
|
|
56226
|
+
],
|
|
56227
|
+
"db plan": [`${c7} db plan app`, `${c7} db plan app --ref my-branch --live`],
|
|
56228
|
+
"db approve": [`${c7} db runs --source app`, `${c7} db approve dmr_123 --reason "reviewed the plan"`],
|
|
55142
56229
|
version: [`${c7} version`, `${c7} version --json`],
|
|
55143
56230
|
upgrade: [`${c7} upgrade --check`, `${c7} upgrade`, `${c7} upgrade --force`],
|
|
55144
56231
|
doctor: [`${c7} doctor`, `${c7} doctor --json | jq '.checks[] | select(.status != "ok")'`],
|
|
@@ -55279,6 +56366,7 @@ function buildProgram(rt) {
|
|
|
55279
56366
|
registerMemory(program2, rt);
|
|
55280
56367
|
registerIntegrations(program2, rt);
|
|
55281
56368
|
registerDeploy(program2, rt);
|
|
56369
|
+
registerDb(program2, rt);
|
|
55282
56370
|
registerDelegate(program2, rt);
|
|
55283
56371
|
registerAgents(program2, rt);
|
|
55284
56372
|
registerCodingAgents(program2, rt);
|
|
@@ -55353,17 +56441,44 @@ function report2(e, deps, state2) {
|
|
|
55353
56441
|
`);
|
|
55354
56442
|
return exitCode;
|
|
55355
56443
|
}
|
|
55356
|
-
|
|
56444
|
+
function agentToolFlag(argv) {
|
|
56445
|
+
for (let i = 0; i < argv.length; i++) {
|
|
56446
|
+
if (argv[i] === "--agent-tool") return argv[i + 1] ?? null;
|
|
56447
|
+
if (argv[i].startsWith("--agent-tool=")) return argv[i].slice("--agent-tool=".length);
|
|
56448
|
+
}
|
|
56449
|
+
return null;
|
|
56450
|
+
}
|
|
56451
|
+
async function run2(argv, overrides = {}, parentStatus) {
|
|
55357
56452
|
const merged = { ...nodeDeps(), ...overrides };
|
|
55358
|
-
const deps = merged;
|
|
55359
56453
|
const split = splitGitArgs(argv);
|
|
55360
56454
|
const state2 = { exitCode: EXIT.OK, wroteResult: false, secrets: [], json: wantsJson(split.argv) };
|
|
56455
|
+
state2.status = parentStatus ?? (programStatusEnabled(merged, detectAgent(merged.env, agentToolFlag(split.argv))) ? statusReporter(merged.stderr) : silentStatus);
|
|
56456
|
+
const status = state2.status;
|
|
56457
|
+
const deps = {
|
|
56458
|
+
...merged,
|
|
56459
|
+
prompt: async (q) => {
|
|
56460
|
+
const answered2 = status.ask();
|
|
56461
|
+
try {
|
|
56462
|
+
return await merged.prompt(q);
|
|
56463
|
+
} finally {
|
|
56464
|
+
answered2();
|
|
56465
|
+
}
|
|
56466
|
+
},
|
|
56467
|
+
keys: merged.keys ? (fn2) => {
|
|
56468
|
+
const answered2 = status.ask();
|
|
56469
|
+
const stop = merged.keys(fn2);
|
|
56470
|
+
return () => {
|
|
56471
|
+
stop();
|
|
56472
|
+
answered2();
|
|
56473
|
+
};
|
|
56474
|
+
} : null
|
|
56475
|
+
};
|
|
55361
56476
|
const invoke2 = async (sub, o = {}) => {
|
|
55362
56477
|
const out = [];
|
|
55363
56478
|
const err = [];
|
|
55364
56479
|
const errSink = (s) => void err.push(s);
|
|
55365
|
-
const quiet = o.quiet ? { stderr: { write: errSink }, stderrIsTTY: false, spawnGit: (args, g2) =>
|
|
55366
|
-
const code2 = await run2(sub, { ...
|
|
56480
|
+
const quiet = o.quiet ? { stderr: { write: errSink }, stderrIsTTY: false, spawnGit: (args, g2) => merged.spawnGit(args, { ...g2, onOutput: errSink }) } : {};
|
|
56481
|
+
const code2 = await run2(sub, { ...merged, ...o.cwd ? { cwd: o.cwd } : {}, ...o.capture ? { stdout: { write: (s) => out.push(s) } } : {}, ...quiet }, status);
|
|
55367
56482
|
return { code: code2, stdout: out.join(""), stderr: err.join("") };
|
|
55368
56483
|
};
|
|
55369
56484
|
const program2 = buildProgram({ deps, state: state2, gitArgs: split.gitArgs, invoke: invoke2 });
|
|
@@ -55378,6 +56493,11 @@ async function run2(argv, overrides = {}) {
|
|
|
55378
56493
|
await sendCrashReport(r, { baseUrl: state2.baseUrl ?? crashServer(deps.env), fetch: deps.sys.fetch, stderr: deps.stderr });
|
|
55379
56494
|
}
|
|
55380
56495
|
}
|
|
56496
|
+
if (!parentStatus) {
|
|
56497
|
+
const name = commandPath(program2, split.argv);
|
|
56498
|
+
const what = name ? `${BRAND.cli} ${name}` : BRAND.cli;
|
|
56499
|
+
status.finish(code === EXIT.OK, code === EXIT.OK ? `${what} finished` : `${what} failed (exit ${code})`);
|
|
56500
|
+
}
|
|
55381
56501
|
await saveBookmark(deps, state2);
|
|
55382
56502
|
await updateHint(deps, state2);
|
|
55383
56503
|
return code;
|