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