@databricks-solutions/lakebase-scm-utils 0.2.44 → 0.2.45
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/dist/scripts/index.cjs +795 -758
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +794 -758
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
- package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
- package/dist/scripts/lakebase/detect-language.cli.js +2 -2
- package/dist/scripts/lakebase/doctor.cli.cjs +3 -3
- package/dist/scripts/lakebase/doctor.cli.js +3 -3
- package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
- package/dist/scripts/lakebase/get-connection.cli.js +2 -2
- package/dist/scripts/lakebase/index.cjs +463 -395
- package/dist/scripts/lakebase/index.d.cts +33 -1
- package/dist/scripts/lakebase/index.d.ts +33 -1
- package/dist/scripts/lakebase/index.js +462 -395
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
- package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
- package/dist/scripts/lakebase/new-migration.cli.js +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2284 -2033
- package/dist/scripts/lakebase/scm-merge.cli.js +339 -88
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/scripts/lakebase/doctor.ts +1 -1
- package/scripts/lakebase/scm-merge.ts +80 -1
package/dist/scripts/index.cjs
CHANGED
|
@@ -79021,6 +79021,7 @@ __export(scripts_exports, {
|
|
|
79021
79021
|
createWorktree: () => createWorktree,
|
|
79022
79022
|
cutBackup: () => cutBackup,
|
|
79023
79023
|
databricksAuthPrereqMessage: () => databricksAuthPrereqMessage,
|
|
79024
|
+
defaultRefreshPromoteWorkflows: () => defaultRefreshPromoteWorkflows,
|
|
79024
79025
|
delay: () => delay,
|
|
79025
79026
|
deleteAppEndpoint: () => deleteAppEndpoint,
|
|
79026
79027
|
deleteBranch: () => deleteBranch,
|
|
@@ -88916,8 +88917,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
88916
88917
|
var cached;
|
|
88917
88918
|
function substrateSelfVersion() {
|
|
88918
88919
|
if (cached !== void 0) return cached;
|
|
88919
|
-
if ("0.2.
|
|
88920
|
-
cached = "0.2.
|
|
88920
|
+
if ("0.2.45".length > 0) {
|
|
88921
|
+
cached = "0.2.45";
|
|
88921
88922
|
return cached;
|
|
88922
88923
|
}
|
|
88923
88924
|
cached = "unknown";
|
|
@@ -95218,123 +95219,500 @@ function pickRunUrl(pr) {
|
|
|
95218
95219
|
|
|
95219
95220
|
// scripts/lakebase/scm-merge.ts
|
|
95220
95221
|
init_cjs_shims();
|
|
95221
|
-
|
|
95222
|
-
|
|
95222
|
+
|
|
95223
|
+
// scripts/git/sync.ts
|
|
95224
|
+
init_cjs_shims();
|
|
95225
|
+
async function currentBranchName(cwd) {
|
|
95223
95226
|
try {
|
|
95224
|
-
|
|
95225
|
-
|
|
95226
|
-
|
|
95227
|
-
} else {
|
|
95228
|
-
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
95229
|
-
}
|
|
95230
|
-
return null;
|
|
95231
|
-
} catch (err) {
|
|
95232
|
-
return `local reconcile of ${tier} to origin/${tier} failed: ${err instanceof Error ? err.message : String(err)}. Reconcile it by hand (git checkout ${tier} && git pull --ff-only).`;
|
|
95227
|
+
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
95228
|
+
} catch {
|
|
95229
|
+
return "";
|
|
95233
95230
|
}
|
|
95234
95231
|
}
|
|
95235
|
-
|
|
95236
|
-
|
|
95237
|
-
super(message);
|
|
95238
|
-
this.code = code;
|
|
95239
|
-
this.name = "ScmMergeError";
|
|
95240
|
-
}
|
|
95241
|
-
code;
|
|
95242
|
-
};
|
|
95243
|
-
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
95244
|
-
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
95245
|
-
function defaultMigratePredicate(run, mergedAt) {
|
|
95246
|
-
if (!run.createdAt) return false;
|
|
95247
|
-
const created = Date.parse(run.createdAt);
|
|
95248
|
-
if (!Number.isFinite(created)) return false;
|
|
95249
|
-
if (run.event && run.event !== "push") return false;
|
|
95250
|
-
return created >= mergedAt.getTime() - 5e3;
|
|
95232
|
+
async function push(args) {
|
|
95233
|
+
await exec2("git push", { cwd: args.cwd });
|
|
95251
95234
|
}
|
|
95252
|
-
function
|
|
95253
|
-
|
|
95254
|
-
if (run.event && run.event !== "push") return false;
|
|
95255
|
-
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
95256
|
-
};
|
|
95235
|
+
async function pull(args) {
|
|
95236
|
+
await exec2("git pull", { cwd: args.cwd });
|
|
95257
95237
|
}
|
|
95258
|
-
async function
|
|
95259
|
-
const
|
|
95260
|
-
|
|
95261
|
-
|
|
95262
|
-
|
|
95263
|
-
|
|
95264
|
-
|
|
95265
|
-
|
|
95266
|
-
|
|
95267
|
-
|
|
95268
|
-
|
|
95269
|
-
|
|
95270
|
-
|
|
95238
|
+
async function publishBranch(args) {
|
|
95239
|
+
const remote = args.remote ?? "origin";
|
|
95240
|
+
const branch = await currentBranchName(args.cwd);
|
|
95241
|
+
if (!branch) throw new Error("No current branch");
|
|
95242
|
+
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
95243
|
+
cwd: args.cwd
|
|
95244
|
+
});
|
|
95245
|
+
}
|
|
95246
|
+
async function pushCurrentBranchForPr(args) {
|
|
95247
|
+
const remote = args.remote ?? "origin";
|
|
95248
|
+
const branch = await currentBranchName(args.cwd);
|
|
95249
|
+
if (!branch) throw new Error("No current branch");
|
|
95250
|
+
const upstreamSet = await hasUpstream({ cwd: args.cwd });
|
|
95251
|
+
if (!upstreamSet) {
|
|
95252
|
+
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
95253
|
+
cwd: args.cwd
|
|
95254
|
+
});
|
|
95255
|
+
} else {
|
|
95256
|
+
await exec2("git push", { cwd: args.cwd });
|
|
95271
95257
|
}
|
|
95272
|
-
|
|
95273
|
-
|
|
95274
|
-
|
|
95275
|
-
|
|
95258
|
+
}
|
|
95259
|
+
async function fetch3(args) {
|
|
95260
|
+
const parts = ["git fetch"];
|
|
95261
|
+
if (args.prune) parts.push("--prune");
|
|
95262
|
+
if (args.all) parts.push("--all");
|
|
95263
|
+
await exec2(parts.join(" "), { cwd: args.cwd });
|
|
95264
|
+
}
|
|
95265
|
+
async function pullFrom(args) {
|
|
95266
|
+
await exec2(`git pull ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
95267
|
+
cwd: args.cwd
|
|
95268
|
+
});
|
|
95269
|
+
}
|
|
95270
|
+
async function pushTo(args) {
|
|
95271
|
+
await exec2(`git push ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
95272
|
+
cwd: args.cwd
|
|
95273
|
+
});
|
|
95274
|
+
}
|
|
95275
|
+
async function sync(args) {
|
|
95276
|
+
await exec2("git pull", { cwd: args.cwd });
|
|
95277
|
+
await exec2("git push", { cwd: args.cwd });
|
|
95278
|
+
}
|
|
95279
|
+
|
|
95280
|
+
// scripts/lakebase/workflow-drift.ts
|
|
95281
|
+
init_cjs_shims();
|
|
95282
|
+
var fs30 = __toESM(require("fs"), 1);
|
|
95283
|
+
var path28 = __toESM(require("path"), 1);
|
|
95284
|
+
function findKitTemplatesDir(start) {
|
|
95285
|
+
let dir = start;
|
|
95286
|
+
for (let i2 = 0; i2 < 6; i2++) {
|
|
95287
|
+
const candidate = path28.join(
|
|
95288
|
+
dir,
|
|
95289
|
+
"templates",
|
|
95290
|
+
"project",
|
|
95291
|
+
"common",
|
|
95292
|
+
".github",
|
|
95293
|
+
"workflows"
|
|
95276
95294
|
);
|
|
95295
|
+
if (fs30.existsSync(candidate)) return candidate;
|
|
95296
|
+
const parent = path28.dirname(dir);
|
|
95297
|
+
if (parent === dir) break;
|
|
95298
|
+
dir = parent;
|
|
95277
95299
|
}
|
|
95278
|
-
|
|
95279
|
-
|
|
95280
|
-
|
|
95281
|
-
|
|
95282
|
-
|
|
95300
|
+
throw new Error(
|
|
95301
|
+
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
95302
|
+
);
|
|
95303
|
+
}
|
|
95304
|
+
function unifiedDiff(name, projectContent, templateContent) {
|
|
95305
|
+
if (projectContent === templateContent) return "";
|
|
95306
|
+
const a = projectContent.split("\n");
|
|
95307
|
+
const b = templateContent.split("\n");
|
|
95308
|
+
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
95309
|
+
const max = Math.max(a.length, b.length);
|
|
95310
|
+
for (let i2 = 0; i2 < max; i2++) {
|
|
95311
|
+
const av = a[i2];
|
|
95312
|
+
const bv = b[i2];
|
|
95313
|
+
if (av === bv) continue;
|
|
95314
|
+
if (av !== void 0) out.push(`-${i2 + 1}: ${av}`);
|
|
95315
|
+
if (bv !== void 0) out.push(`+${i2 + 1}: ${bv}`);
|
|
95283
95316
|
}
|
|
95284
|
-
|
|
95285
|
-
|
|
95286
|
-
|
|
95287
|
-
|
|
95288
|
-
|
|
95289
|
-
|
|
95317
|
+
return out.join("\n");
|
|
95318
|
+
}
|
|
95319
|
+
function detectWorkflowDrift(args) {
|
|
95320
|
+
const projectWorkflowsDir = path28.join(
|
|
95321
|
+
args.projectDir,
|
|
95322
|
+
".github",
|
|
95323
|
+
"workflows"
|
|
95324
|
+
);
|
|
95325
|
+
const here = path28.dirname(new URL(importMetaUrl).pathname);
|
|
95326
|
+
const kitWorkflowsDir = args.kitDir ? path28.join(
|
|
95327
|
+
args.kitDir,
|
|
95328
|
+
"templates",
|
|
95329
|
+
"project",
|
|
95330
|
+
"common",
|
|
95331
|
+
".github",
|
|
95332
|
+
"workflows"
|
|
95333
|
+
) : findKitTemplatesDir(here);
|
|
95334
|
+
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
95335
|
+
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
95336
|
+
const version = readKitVersion(kitWorkflowsDir);
|
|
95337
|
+
const seen = /* @__PURE__ */ new Set();
|
|
95338
|
+
const files = [];
|
|
95339
|
+
for (const name of templateFiles) {
|
|
95340
|
+
seen.add(name);
|
|
95341
|
+
const projectPath2 = path28.join(projectWorkflowsDir, name);
|
|
95342
|
+
const templatePath = path28.join(kitWorkflowsDir, name);
|
|
95343
|
+
if (!fs30.existsSync(projectPath2)) {
|
|
95344
|
+
files.push({ name, status: "missing" });
|
|
95345
|
+
continue;
|
|
95346
|
+
}
|
|
95347
|
+
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
95348
|
+
const templateContent = applyPlaceholders(fs30.readFileSync(templatePath, "utf8"), version);
|
|
95349
|
+
if (projectContent === templateContent) {
|
|
95350
|
+
files.push({ name, status: "unchanged" });
|
|
95351
|
+
} else {
|
|
95352
|
+
files.push({
|
|
95353
|
+
name,
|
|
95354
|
+
status: "drifted",
|
|
95355
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
95356
|
+
});
|
|
95357
|
+
}
|
|
95290
95358
|
}
|
|
95291
|
-
const
|
|
95292
|
-
|
|
95293
|
-
|
|
95294
|
-
`Could not extract PR number from URL: ${current.pr_url}`,
|
|
95295
|
-
"bad-pr-url"
|
|
95296
|
-
);
|
|
95359
|
+
for (const name of projectFiles) {
|
|
95360
|
+
if (seen.has(name)) continue;
|
|
95361
|
+
files.push({ name, status: "extra" });
|
|
95297
95362
|
}
|
|
95298
|
-
const
|
|
95299
|
-
|
|
95300
|
-
|
|
95301
|
-
|
|
95302
|
-
|
|
95303
|
-
|
|
95304
|
-
|
|
95305
|
-
|
|
95306
|
-
|
|
95307
|
-
|
|
95308
|
-
|
|
95309
|
-
|
|
95310
|
-
|
|
95363
|
+
const order = {
|
|
95364
|
+
drifted: 0,
|
|
95365
|
+
missing: 1,
|
|
95366
|
+
extra: 2,
|
|
95367
|
+
unchanged: 3
|
|
95368
|
+
};
|
|
95369
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
95370
|
+
const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
|
|
95371
|
+
return {
|
|
95372
|
+
overall: hasDrift ? "drift" : "ok",
|
|
95373
|
+
files
|
|
95374
|
+
};
|
|
95375
|
+
}
|
|
95376
|
+
function readKitVersion(kitWorkflowsDir) {
|
|
95377
|
+
let dir = kitWorkflowsDir;
|
|
95378
|
+
for (let i2 = 0; i2 < 5; i2++) {
|
|
95379
|
+
dir = path28.dirname(dir);
|
|
95311
95380
|
}
|
|
95312
|
-
let paired;
|
|
95313
95381
|
try {
|
|
95314
|
-
|
|
95315
|
-
|
|
95316
|
-
|
|
95317
|
-
|
|
95318
|
-
|
|
95319
|
-
|
|
95320
|
-
|
|
95321
|
-
|
|
95322
|
-
|
|
95323
|
-
|
|
95382
|
+
const raw = fs30.readFileSync(path28.join(dir, "package.json"), "utf-8");
|
|
95383
|
+
const pkg = JSON.parse(raw);
|
|
95384
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
95385
|
+
} catch {
|
|
95386
|
+
return "unknown";
|
|
95387
|
+
}
|
|
95388
|
+
}
|
|
95389
|
+
function applyPlaceholders(content, version) {
|
|
95390
|
+
return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
|
|
95391
|
+
}
|
|
95392
|
+
function applyCommandPlaceholders(content, version) {
|
|
95393
|
+
return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
|
|
95394
|
+
}
|
|
95395
|
+
var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
|
|
95396
|
+
function findKitCommandsDir(start) {
|
|
95397
|
+
let dir = start;
|
|
95398
|
+
for (let i2 = 0; i2 < 6; i2++) {
|
|
95399
|
+
const candidate = path28.join(
|
|
95400
|
+
dir,
|
|
95401
|
+
"templates",
|
|
95402
|
+
"project",
|
|
95403
|
+
"common",
|
|
95404
|
+
".claude",
|
|
95405
|
+
"commands"
|
|
95324
95406
|
);
|
|
95407
|
+
if (fs30.existsSync(candidate)) return candidate;
|
|
95408
|
+
const parent = path28.dirname(dir);
|
|
95409
|
+
if (parent === dir) break;
|
|
95410
|
+
dir = parent;
|
|
95325
95411
|
}
|
|
95326
|
-
|
|
95327
|
-
|
|
95328
|
-
|
|
95329
|
-
|
|
95330
|
-
|
|
95331
|
-
|
|
95332
|
-
|
|
95333
|
-
|
|
95334
|
-
|
|
95335
|
-
|
|
95336
|
-
|
|
95337
|
-
|
|
95412
|
+
throw new Error(
|
|
95413
|
+
`Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
|
|
95414
|
+
);
|
|
95415
|
+
}
|
|
95416
|
+
function parsePinnedVersion(content) {
|
|
95417
|
+
const m2 = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
|
|
95418
|
+
return m2 ? m2[1] : void 0;
|
|
95419
|
+
}
|
|
95420
|
+
function detectCommandDrift(args) {
|
|
95421
|
+
const projectCommandsDir = path28.join(args.projectDir, ".claude", "commands");
|
|
95422
|
+
const here = path28.dirname(new URL(importMetaUrl).pathname);
|
|
95423
|
+
const kitCommandsDir = args.kitDir ? path28.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
95424
|
+
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
95425
|
+
const templateFiles = fs30.existsSync(kitCommandsDir) ? fs30.readdirSync(kitCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
|
|
95426
|
+
const projectFiles = fs30.existsSync(projectCommandsDir) ? fs30.readdirSync(projectCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
|
|
95427
|
+
const seen = /* @__PURE__ */ new Set();
|
|
95428
|
+
const files = [];
|
|
95429
|
+
for (const name of templateFiles) {
|
|
95430
|
+
seen.add(name);
|
|
95431
|
+
const projectPath2 = path28.join(projectCommandsDir, name);
|
|
95432
|
+
const templatePath = path28.join(kitCommandsDir, name);
|
|
95433
|
+
const templateRaw = fs30.readFileSync(templatePath, "utf8");
|
|
95434
|
+
if (!fs30.existsSync(projectPath2)) {
|
|
95435
|
+
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
95436
|
+
continue;
|
|
95437
|
+
}
|
|
95438
|
+
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
95439
|
+
const pinned = parsePinnedVersion(projectContent);
|
|
95440
|
+
const versionForCompare = pinned ?? kitVersion;
|
|
95441
|
+
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
95442
|
+
if (projectContent === templateContent) {
|
|
95443
|
+
files.push({
|
|
95444
|
+
name,
|
|
95445
|
+
status: "unchanged",
|
|
95446
|
+
pinned_version: pinned,
|
|
95447
|
+
kit_version: kitVersion
|
|
95448
|
+
});
|
|
95449
|
+
} else {
|
|
95450
|
+
files.push({
|
|
95451
|
+
name,
|
|
95452
|
+
status: "drifted",
|
|
95453
|
+
pinned_version: pinned,
|
|
95454
|
+
kit_version: kitVersion,
|
|
95455
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
95456
|
+
});
|
|
95457
|
+
}
|
|
95458
|
+
}
|
|
95459
|
+
for (const name of projectFiles) {
|
|
95460
|
+
if (seen.has(name)) continue;
|
|
95461
|
+
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
95462
|
+
}
|
|
95463
|
+
const order = {
|
|
95464
|
+
drifted: 0,
|
|
95465
|
+
missing: 1,
|
|
95466
|
+
extra: 2,
|
|
95467
|
+
unchanged: 3
|
|
95468
|
+
};
|
|
95469
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
95470
|
+
const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
|
|
95471
|
+
return { overall: hasDrift ? "drift" : "ok", files };
|
|
95472
|
+
}
|
|
95473
|
+
function readKitVersionFromCommandsDir(kitCommandsDir) {
|
|
95474
|
+
let dir = kitCommandsDir;
|
|
95475
|
+
for (let i2 = 0; i2 < 5; i2++) {
|
|
95476
|
+
dir = path28.dirname(dir);
|
|
95477
|
+
}
|
|
95478
|
+
try {
|
|
95479
|
+
const raw = fs30.readFileSync(path28.join(dir, "package.json"), "utf-8");
|
|
95480
|
+
const pkg = JSON.parse(raw);
|
|
95481
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
95482
|
+
} catch {
|
|
95483
|
+
return "unknown";
|
|
95484
|
+
}
|
|
95485
|
+
}
|
|
95486
|
+
function detectScaffoldedDrift(args) {
|
|
95487
|
+
const workflows = detectWorkflowDrift(args);
|
|
95488
|
+
const commands = detectCommandDrift(args);
|
|
95489
|
+
return {
|
|
95490
|
+
overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
|
|
95491
|
+
workflows,
|
|
95492
|
+
commands
|
|
95493
|
+
};
|
|
95494
|
+
}
|
|
95495
|
+
function updateWorkflows(args) {
|
|
95496
|
+
const projectWorkflowsDir = path28.join(
|
|
95497
|
+
args.projectDir,
|
|
95498
|
+
".github",
|
|
95499
|
+
"workflows"
|
|
95500
|
+
);
|
|
95501
|
+
const here = path28.dirname(new URL(importMetaUrl).pathname);
|
|
95502
|
+
const kitWorkflowsDir = args.kitDir ? path28.join(
|
|
95503
|
+
args.kitDir,
|
|
95504
|
+
"templates",
|
|
95505
|
+
"project",
|
|
95506
|
+
"common",
|
|
95507
|
+
".github",
|
|
95508
|
+
"workflows"
|
|
95509
|
+
) : findKitTemplatesDir(here);
|
|
95510
|
+
const substitute = args.substitute !== false;
|
|
95511
|
+
const dryRun = args.dryRun === true;
|
|
95512
|
+
const pruneExtras = args.pruneExtras === true;
|
|
95513
|
+
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
95514
|
+
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
95515
|
+
if (!dryRun && templateFiles.length > 0 && !fs30.existsSync(projectWorkflowsDir)) {
|
|
95516
|
+
fs30.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
95517
|
+
}
|
|
95518
|
+
const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
|
|
95519
|
+
const seen = /* @__PURE__ */ new Set();
|
|
95520
|
+
const files = [];
|
|
95521
|
+
for (const name of templateFiles) {
|
|
95522
|
+
seen.add(name);
|
|
95523
|
+
const projectPath2 = path28.join(projectWorkflowsDir, name);
|
|
95524
|
+
const templatePath = path28.join(kitWorkflowsDir, name);
|
|
95525
|
+
const templateRaw = fs30.readFileSync(templatePath, "utf-8");
|
|
95526
|
+
const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
|
|
95527
|
+
const existed = fs30.existsSync(projectPath2);
|
|
95528
|
+
const current = existed ? fs30.readFileSync(projectPath2, "utf-8") : "";
|
|
95529
|
+
let outcome;
|
|
95530
|
+
if (!existed) {
|
|
95531
|
+
outcome = "added";
|
|
95532
|
+
} else if (current === desired) {
|
|
95533
|
+
outcome = "unchanged";
|
|
95534
|
+
} else {
|
|
95535
|
+
outcome = "updated";
|
|
95536
|
+
}
|
|
95537
|
+
if (!dryRun && outcome !== "unchanged") {
|
|
95538
|
+
fs30.writeFileSync(projectPath2, desired);
|
|
95539
|
+
}
|
|
95540
|
+
files.push({ name, outcome });
|
|
95541
|
+
}
|
|
95542
|
+
if (pruneExtras) {
|
|
95543
|
+
for (const name of projectFiles) {
|
|
95544
|
+
if (seen.has(name)) continue;
|
|
95545
|
+
const projectPath2 = path28.join(projectWorkflowsDir, name);
|
|
95546
|
+
if (!dryRun) {
|
|
95547
|
+
fs30.unlinkSync(projectPath2);
|
|
95548
|
+
}
|
|
95549
|
+
files.push({ name, outcome: "removed" });
|
|
95550
|
+
}
|
|
95551
|
+
}
|
|
95552
|
+
const order = {
|
|
95553
|
+
added: 0,
|
|
95554
|
+
updated: 1,
|
|
95555
|
+
removed: 2,
|
|
95556
|
+
unchanged: 3
|
|
95557
|
+
};
|
|
95558
|
+
files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
|
|
95559
|
+
const changed = files.some((f3) => f3.outcome !== "unchanged");
|
|
95560
|
+
return { files, changed };
|
|
95561
|
+
}
|
|
95562
|
+
|
|
95563
|
+
// scripts/lakebase/scm-merge.ts
|
|
95564
|
+
async function reconcileTierToOrigin(args) {
|
|
95565
|
+
const { cwd, tier } = args;
|
|
95566
|
+
try {
|
|
95567
|
+
const head = await getCurrentBranch({ cwd });
|
|
95568
|
+
if (head === tier) {
|
|
95569
|
+
await exec2(`git pull --ff-only`, { cwd, timeout: 3e4 });
|
|
95570
|
+
} else {
|
|
95571
|
+
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
95572
|
+
}
|
|
95573
|
+
return null;
|
|
95574
|
+
} catch (err) {
|
|
95575
|
+
return `local reconcile of ${tier} to origin/${tier} failed: ${err instanceof Error ? err.message : String(err)}. Reconcile it by hand (git checkout ${tier} && git pull --ff-only).`;
|
|
95576
|
+
}
|
|
95577
|
+
}
|
|
95578
|
+
var ScmMergeError = class extends Error {
|
|
95579
|
+
constructor(message, code) {
|
|
95580
|
+
super(message);
|
|
95581
|
+
this.code = code;
|
|
95582
|
+
this.name = "ScmMergeError";
|
|
95583
|
+
}
|
|
95584
|
+
code;
|
|
95585
|
+
};
|
|
95586
|
+
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
95587
|
+
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
95588
|
+
function defaultMigratePredicate(run, mergedAt) {
|
|
95589
|
+
if (!run.createdAt) return false;
|
|
95590
|
+
const created = Date.parse(run.createdAt);
|
|
95591
|
+
if (!Number.isFinite(created)) return false;
|
|
95592
|
+
if (run.event && run.event !== "push") return false;
|
|
95593
|
+
return created >= mergedAt.getTime() - 5e3;
|
|
95594
|
+
}
|
|
95595
|
+
function shaMigratePredicate(mergeCommitSha) {
|
|
95596
|
+
return (run) => {
|
|
95597
|
+
if (run.event && run.event !== "push") return false;
|
|
95598
|
+
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
95599
|
+
};
|
|
95600
|
+
}
|
|
95601
|
+
async function defaultRefreshPromoteWorkflows(projectDir) {
|
|
95602
|
+
const drift = detectWorkflowDrift({ projectDir });
|
|
95603
|
+
const touched = drift.files.filter((f3) => f3.status === "drifted").map((f3) => f3.name);
|
|
95604
|
+
if (touched.length === 0) return { refreshed: false };
|
|
95605
|
+
const result = updateWorkflows({ projectDir });
|
|
95606
|
+
if (!result.changed) return { refreshed: false };
|
|
95607
|
+
await exec2(`git add ${shellEscape2(".github/workflows")}`, { cwd: projectDir, timeout: 1e4 });
|
|
95608
|
+
const staged = await exec2("git diff --cached --name-only -- .github/workflows", {
|
|
95609
|
+
cwd: projectDir,
|
|
95610
|
+
timeout: 1e4
|
|
95611
|
+
});
|
|
95612
|
+
if (!staged.trim()) return { refreshed: false };
|
|
95613
|
+
await exec2(
|
|
95614
|
+
`git commit -m ${shellEscape2(
|
|
95615
|
+
"chore(ci): refresh scaffolded workflows to pinned substrate (promote uses apply-tier)"
|
|
95616
|
+
)} -- .github/workflows`,
|
|
95617
|
+
{ cwd: projectDir, timeout: 15e3 }
|
|
95618
|
+
);
|
|
95619
|
+
await pushCurrentBranchForPr({ cwd: projectDir });
|
|
95620
|
+
return { refreshed: true, detail: `refreshed + pushed ${touched.join(", ") || "workflow(s)"} onto the PR head` };
|
|
95621
|
+
}
|
|
95622
|
+
async function mergeFeature(args) {
|
|
95623
|
+
const current = readWorkflowState(args.projectDir);
|
|
95624
|
+
if (!current) {
|
|
95625
|
+
throw new ScmMergeError(
|
|
95626
|
+
"No SCM workflow state. wait-ci first.",
|
|
95627
|
+
"no-state-file"
|
|
95628
|
+
);
|
|
95629
|
+
}
|
|
95630
|
+
if (current.state !== "ci-green") {
|
|
95631
|
+
throw new ScmMergeError(
|
|
95632
|
+
`merge refuses state "${current.state}". Allowed predecessor: ci-green.`,
|
|
95633
|
+
"bad-precondition"
|
|
95634
|
+
);
|
|
95635
|
+
}
|
|
95636
|
+
if (!current.pr_url) {
|
|
95637
|
+
throw new ScmMergeError(
|
|
95638
|
+
"ci-green row is missing pr_url; cannot resolve the PR to merge.",
|
|
95639
|
+
"no-pr-url"
|
|
95640
|
+
);
|
|
95641
|
+
}
|
|
95642
|
+
if (!current.branch || !current.parent_branch) {
|
|
95643
|
+
throw new ScmMergeError(
|
|
95644
|
+
"ci-green row missing branch / parent_branch; refusing to merge.",
|
|
95645
|
+
"bad-precondition"
|
|
95646
|
+
);
|
|
95647
|
+
}
|
|
95648
|
+
const ownerRepo = await getOwnerRepo(args.projectDir);
|
|
95649
|
+
if (!ownerRepo) {
|
|
95650
|
+
throw new ScmMergeError(
|
|
95651
|
+
"No GitHub remote found at origin.",
|
|
95652
|
+
"no-github-remote"
|
|
95653
|
+
);
|
|
95654
|
+
}
|
|
95655
|
+
const pullNumber = extractPullNumber(current.pr_url);
|
|
95656
|
+
if (!pullNumber) {
|
|
95657
|
+
throw new ScmMergeError(
|
|
95658
|
+
`Could not extract PR number from URL: ${current.pr_url}`,
|
|
95659
|
+
"bad-pr-url"
|
|
95660
|
+
);
|
|
95661
|
+
}
|
|
95662
|
+
const instance = args.instance ?? current.project_id;
|
|
95663
|
+
const gitBase = await resolveGitBase(current.parent_branch, args.projectDir);
|
|
95664
|
+
const wantMigrate = args.waitMigrate !== false;
|
|
95665
|
+
let authVerified = false;
|
|
95666
|
+
if (wantMigrate && args.verifyMigrateAuth) {
|
|
95667
|
+
const probe = await args.verifyMigrateAuth();
|
|
95668
|
+
if (!probe.ok) {
|
|
95669
|
+
throw new ScmMergeError(
|
|
95670
|
+
`Migrate-auth precondition failed: ${probe.detail ?? "the Databricks credential is not usable"}. Refusing to merge: the downstream migrate would promote git without applying the schema. Refresh your credential (databricks auth login), then re-run.`,
|
|
95671
|
+
"migrate-auth"
|
|
95672
|
+
);
|
|
95673
|
+
}
|
|
95674
|
+
authVerified = true;
|
|
95675
|
+
}
|
|
95676
|
+
const preflightNotes = [];
|
|
95677
|
+
{
|
|
95678
|
+
const refresh = args.refreshWorkflows ?? (() => defaultRefreshPromoteWorkflows(args.projectDir));
|
|
95679
|
+
try {
|
|
95680
|
+
const r2 = await refresh();
|
|
95681
|
+
if (r2.refreshed) {
|
|
95682
|
+
preflightNotes.push(`Refreshed stale CI workflows before merge${r2.detail ? ` (${r2.detail})` : ""}.`);
|
|
95683
|
+
}
|
|
95684
|
+
} catch (err) {
|
|
95685
|
+
preflightNotes.push(
|
|
95686
|
+
`Workflow self-heal skipped (${err instanceof Error ? err.message : String(err)}); if the promote's migrate-target fails on the tier guard, refresh .github/workflows (merge.yml must call 'lakebase-schema-migrate apply-tier').`
|
|
95687
|
+
);
|
|
95688
|
+
}
|
|
95689
|
+
}
|
|
95690
|
+
let paired;
|
|
95691
|
+
try {
|
|
95692
|
+
paired = await mergePairedPullRequest({
|
|
95693
|
+
ownerRepo,
|
|
95694
|
+
pullNumber,
|
|
95695
|
+
lakebaseInstance: instance,
|
|
95696
|
+
method: args.method ?? "squash"
|
|
95697
|
+
});
|
|
95698
|
+
} catch (err) {
|
|
95699
|
+
throw new ScmMergeError(
|
|
95700
|
+
`mergePairedPullRequest failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
95701
|
+
"merge-failed"
|
|
95702
|
+
);
|
|
95703
|
+
}
|
|
95704
|
+
const warnings = [...preflightNotes, ...paired.warnings];
|
|
95705
|
+
let localBranchDeleted = false;
|
|
95706
|
+
let headAfter = current.branch;
|
|
95707
|
+
if (!args.skipLocalCleanup) {
|
|
95708
|
+
const switchTo = args.switchTo ?? gitBase;
|
|
95709
|
+
const head = await getCurrentBranch({ cwd: args.projectDir });
|
|
95710
|
+
if (head === current.branch) {
|
|
95711
|
+
try {
|
|
95712
|
+
await exec2(`git checkout ${shellEscape2(switchTo)}`, {
|
|
95713
|
+
cwd: args.projectDir,
|
|
95714
|
+
timeout: 1e4
|
|
95715
|
+
});
|
|
95338
95716
|
headAfter = switchTo;
|
|
95339
95717
|
try {
|
|
95340
95718
|
await exec2(`git fetch origin ${shellEscape2(switchTo)}`, {
|
|
@@ -95520,7 +95898,7 @@ init_cjs_shims();
|
|
|
95520
95898
|
|
|
95521
95899
|
// scripts/git/branches.ts
|
|
95522
95900
|
init_cjs_shims();
|
|
95523
|
-
async function
|
|
95901
|
+
async function currentBranchName2(cwd) {
|
|
95524
95902
|
try {
|
|
95525
95903
|
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
95526
95904
|
} catch {
|
|
@@ -95539,7 +95917,7 @@ async function listLocalBranches(args) {
|
|
|
95539
95917
|
return [];
|
|
95540
95918
|
}
|
|
95541
95919
|
if (!raw) return [];
|
|
95542
|
-
const current = await
|
|
95920
|
+
const current = await currentBranchName2(cwd);
|
|
95543
95921
|
return raw.split("\n").filter(Boolean).map((line) => {
|
|
95544
95922
|
const [name, tracking, trackInfo] = line.split("|");
|
|
95545
95923
|
let ahead = 0;
|
|
@@ -95739,15 +96117,15 @@ function parentForTopology(t2, defaultLeaf) {
|
|
|
95739
96117
|
|
|
95740
96118
|
// scripts/lakebase/scm-doctor.ts
|
|
95741
96119
|
init_cjs_shims();
|
|
95742
|
-
var
|
|
95743
|
-
var
|
|
96120
|
+
var fs31 = __toESM(require("fs"), 1);
|
|
96121
|
+
var path29 = __toESM(require("path"), 1);
|
|
95744
96122
|
var FEATURE_PREFIX = "feature/";
|
|
95745
96123
|
var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
|
|
95746
96124
|
function readEnv(projectDir) {
|
|
95747
|
-
const envPath =
|
|
96125
|
+
const envPath = path29.join(projectDir, ".env");
|
|
95748
96126
|
const out = /* @__PURE__ */ new Map();
|
|
95749
|
-
if (!
|
|
95750
|
-
const lines =
|
|
96127
|
+
if (!fs31.existsSync(envPath)) return out;
|
|
96128
|
+
const lines = fs31.readFileSync(envPath, "utf8").split("\n");
|
|
95751
96129
|
for (const line of lines) {
|
|
95752
96130
|
const m2 = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
|
|
95753
96131
|
if (m2) out.set(m2[1], m2[2].replace(/^["']|["']$/g, ""));
|
|
@@ -95793,622 +96171,337 @@ async function runDoctor(args, deps = {}) {
|
|
|
95793
96171
|
findings.push({
|
|
95794
96172
|
id: "github-actions-disabled",
|
|
95795
96173
|
severity: "warn",
|
|
95796
|
-
message: `GitHub Actions is disabled for ${ownerRepo}, so the kit's CI workflows (pr.yml / merge.yml) will never run , the PR branch's migrations, tests, and schema-diff comment are all skipped. This is commonly an org-level policy on EMU repos, which a repo admin cannot override.`,
|
|
95797
|
-
suggestion: "Have an org owner enable Actions for this repo (repo Settings -> Actions -> General; if it says 'disabled by the organization', it must be enabled in the org's Actions policy). Until then, run the workflow steps locally: scripts/run-tests.sh against a Lakebase branch."
|
|
95798
|
-
});
|
|
95799
|
-
}
|
|
95800
|
-
}
|
|
95801
|
-
} catch {
|
|
95802
|
-
}
|
|
95803
|
-
for (const wf of ["pr.yml", "merge.yml"]) {
|
|
95804
|
-
try {
|
|
95805
|
-
const p = path28.join(projectDir, ".github", "workflows", wf);
|
|
95806
|
-
if (!fs30.existsSync(p)) continue;
|
|
95807
|
-
const body = fs30.readFileSync(p, "utf8");
|
|
95808
|
-
if (/lakebase-scm-utils#v\d/.test(body)) {
|
|
95809
|
-
findings.push({
|
|
95810
|
-
id: "ci-workflow-substrate-pin",
|
|
95811
|
-
severity: "warn",
|
|
95812
|
-
message: `.github/workflows/${wf} hardcodes a substrate version pin (github:databricks-solutions/lakebase-scm-utils#v<ver>) instead of resolving .lakebase/scm-utils-ref at runtime, so bumping .lakebase/scm-utils-ref does NOT change the substrate CI actually runs.`,
|
|
95813
|
-
suggestion: "Re-emit the workflows from the current substrate templates so they resolve SCM_UTILS_REF from .lakebase/scm-utils-ref at CI time (updateWorkflows in scripts/lakebase/workflow-drift.ts; lakebase-doctor reports this as workflow-drift). Until then a scm-utils-ref bump will not reach CI."
|
|
95814
|
-
});
|
|
95815
|
-
}
|
|
95816
|
-
} catch {
|
|
95817
|
-
}
|
|
95818
|
-
}
|
|
95819
|
-
try {
|
|
95820
|
-
if (isGitTracked(projectDir, ".lakebase/workflow-state.json")) {
|
|
95821
|
-
findings.push({
|
|
95822
|
-
id: "scm-state-git-tracked",
|
|
95823
|
-
severity: "warn",
|
|
95824
|
-
message: ".lakebase/workflow-state.json (the per-working-tree SCM claim state) is git-tracked, so a branch checkout or `git reset --hard origin/<tier>` can restore a stale committed claim over the live one (the foreign-claim guard then refuses to drive until you abandon + reclaim).",
|
|
95825
|
-
suggestion: "If a wrong-feature-claim refusal follows a checkout, run lakebase-scm-adopt-state (or abandon + reclaim) to re-establish the live claim. The kit-ref run pin (.lakebase/kit-ref.local) already protects the kit version from the same checkout revert."
|
|
95826
|
-
});
|
|
95827
|
-
}
|
|
95828
|
-
} catch {
|
|
95829
|
-
}
|
|
95830
|
-
if (!env.has("LAKEBASE_PROJECT_ID")) {
|
|
95831
|
-
findings.push({
|
|
95832
|
-
id: "env-missing-project-id",
|
|
95833
|
-
severity: "fail",
|
|
95834
|
-
message: ".env does not contain LAKEBASE_PROJECT_ID. The post-checkout hook will exit early; workflow CLIs will need an explicit --instance.",
|
|
95835
|
-
suggestion: "Set LAKEBASE_PROJECT_ID=<your project id> in .env"
|
|
95836
|
-
});
|
|
95837
|
-
}
|
|
95838
|
-
if (!instance) {
|
|
95839
|
-
return finalize({
|
|
95840
|
-
projectDir,
|
|
95841
|
-
workflowStatePresent,
|
|
95842
|
-
state: state ?? void 0,
|
|
95843
|
-
findings
|
|
95844
|
-
});
|
|
95845
|
-
}
|
|
95846
|
-
let lakebaseBranches = [];
|
|
95847
|
-
try {
|
|
95848
|
-
lakebaseBranches = await listBranches({ instance });
|
|
95849
|
-
} catch (err) {
|
|
95850
|
-
findings.push({
|
|
95851
|
-
id: "lakebase-unreachable",
|
|
95852
|
-
severity: "fail",
|
|
95853
|
-
message: `Could not list Lakebase branches for instance ${instance}: ${err instanceof Error ? err.message : String(err)}`,
|
|
95854
|
-
suggestion: "databricks auth login (or check DATABRICKS_CONFIG_PROFILE)."
|
|
95855
|
-
});
|
|
95856
|
-
return finalize({
|
|
95857
|
-
projectDir,
|
|
95858
|
-
workflowStatePresent,
|
|
95859
|
-
state: state ?? void 0,
|
|
95860
|
-
findings
|
|
95861
|
-
});
|
|
95862
|
-
}
|
|
95863
|
-
const inferredTopology = inferTierTopology(lakebaseBranches);
|
|
95864
|
-
if (state && state.tier_topology !== inferredTopology) {
|
|
95865
|
-
findings.push({
|
|
95866
|
-
id: "tier-topology-mismatch",
|
|
95867
|
-
severity: "warn",
|
|
95868
|
-
message: `workflow-state records tier_topology=${state.tier_topology}, but the Lakebase tier inventory suggests ${inferredTopology}.`,
|
|
95869
|
-
suggestion: "lakebase-scm-adopt-state --force"
|
|
95870
|
-
});
|
|
95871
|
-
}
|
|
95872
|
-
const headBranch = await getCurrentBranch({ cwd: projectDir });
|
|
95873
|
-
if (state && state.state === "feature-claimed") {
|
|
95874
|
-
if (state.branch && headBranch && headBranch !== state.branch) {
|
|
95875
|
-
findings.push({
|
|
95876
|
-
id: "head-branch-drift",
|
|
95877
|
-
severity: "warn",
|
|
95878
|
-
message: `workflow says feature-claimed for "${state.branch}", but HEAD is on "${headBranch}".`,
|
|
95879
|
-
suggestion: `git checkout '${state.branch}'`
|
|
95880
|
-
});
|
|
95881
|
-
}
|
|
95882
|
-
if (state.branch) {
|
|
95883
|
-
const sanitized = sanitizeBranchName(state.branch);
|
|
95884
|
-
let pair;
|
|
95885
|
-
try {
|
|
95886
|
-
pair = await getBranchByName(sanitized, { instance });
|
|
95887
|
-
} catch {
|
|
95888
|
-
pair = void 0;
|
|
95889
|
-
}
|
|
95890
|
-
if (!pair) {
|
|
95891
|
-
findings.push({
|
|
95892
|
-
id: "lakebase-pair-missing",
|
|
95893
|
-
severity: "fail",
|
|
95894
|
-
message: `workflow says feature-claimed for "${state.branch}", but no Lakebase branch "${sanitized}" exists.`,
|
|
95895
|
-
suggestion: `lakebase-scm-abandon-feature # reset state; re-claim if needed`
|
|
95896
|
-
});
|
|
95897
|
-
} else if (state.lakebase_branch_uid && pair.uid !== state.lakebase_branch_uid) {
|
|
95898
|
-
findings.push({
|
|
95899
|
-
id: "lakebase-uid-drift",
|
|
95900
|
-
severity: "warn",
|
|
95901
|
-
message: `workflow records lakebase_branch_uid=${state.lakebase_branch_uid}, but the live branch reports ${pair.uid}.`,
|
|
95902
|
-
suggestion: "lakebase-scm-adopt-state --force"
|
|
95903
|
-
});
|
|
95904
|
-
}
|
|
95905
|
-
}
|
|
95906
|
-
}
|
|
95907
|
-
if (state && state.state === "feature-claimed" && state.branch) {
|
|
95908
|
-
const envBranchId = env.get("LAKEBASE_BRANCH_ID");
|
|
95909
|
-
const sanitized = sanitizeBranchName(state.branch);
|
|
95910
|
-
if (envBranchId && envBranchId !== sanitized) {
|
|
95911
|
-
findings.push({
|
|
95912
|
-
id: "env-branch-drift",
|
|
95913
|
-
severity: "warn",
|
|
95914
|
-
message: `.env LAKEBASE_BRANCH_ID=${envBranchId} but workflow says ${sanitized}. The post-checkout hook may not have run since the last branch switch.`,
|
|
95915
|
-
suggestion: `git checkout '${state.branch}' # re-fires post-checkout`
|
|
95916
|
-
});
|
|
95917
|
-
}
|
|
95918
|
-
}
|
|
95919
|
-
if (headBranch && !TIER_LEAFS2.has(headBranch) && headBranch.startsWith(FEATURE_PREFIX)) {
|
|
95920
|
-
const sanitized = sanitizeBranchName(headBranch);
|
|
95921
|
-
const paired = lakebaseBranches.some((b) => leafOf2(b) === sanitized);
|
|
95922
|
-
if (!paired) {
|
|
95923
|
-
findings.push({
|
|
95924
|
-
id: "orphan-current-branch",
|
|
95925
|
-
severity: "fail",
|
|
95926
|
-
message: `Current git branch "${headBranch}" has no Lakebase pair (post-checkout fallback retired in phase C).`,
|
|
95927
|
-
suggestion: `lakebase-scm-recover-orphans --claim --only-branch '${headBranch}'`
|
|
95928
|
-
});
|
|
95929
|
-
}
|
|
95930
|
-
}
|
|
95931
|
-
try {
|
|
95932
|
-
const heads = await collapseMigrationHeads({ projectDir, dryRun: true });
|
|
95933
|
-
if (heads.headsBefore.length > 1) {
|
|
95934
|
-
findings.push({
|
|
95935
|
-
id: "multiple-migration-heads",
|
|
95936
|
-
severity: "fail",
|
|
95937
|
-
message: `Migrations have ${heads.headsBefore.length} heads (${heads.headsBefore.join(", ")}); a sibling-feature merge left them un-collapsed. \`upgrade head\` will refuse until they are unified.`,
|
|
95938
|
-
suggestion: "lakebase-collapse-heads"
|
|
95939
|
-
});
|
|
95940
|
-
}
|
|
95941
|
-
} catch {
|
|
95942
|
-
}
|
|
95943
|
-
if (instance && state?.branch) {
|
|
95944
|
-
try {
|
|
95945
|
-
const localIds = listSchemaMigrations({ projectDir }).map((m2) => m2.version);
|
|
95946
|
-
let orphanRev = null;
|
|
95947
|
-
try {
|
|
95948
|
-
const status = await schemaMigrationStatus({ instance, branch: state.branch, projectDir });
|
|
95949
|
-
if (dbRevisionOrphaned(status.current, localIds)) orphanRev = status.current ?? null;
|
|
95950
|
-
} catch (e2) {
|
|
95951
|
-
orphanRev = parseAlembicMissingRevision(e2 instanceof Error ? e2.message : String(e2));
|
|
95952
|
-
}
|
|
95953
|
-
if (orphanRev) {
|
|
95954
|
-
findings.push({
|
|
95955
|
-
id: "db-ahead-of-code",
|
|
95956
|
-
severity: "fail",
|
|
95957
|
-
message: `The paired branch DB is AHEAD of code: applied revision '${orphanRev}' has no local migration file. An aborted build likely migrated this branch and a git reset removed the migration file; alembic accept/deploy/promote will fail "Can't locate revision".`,
|
|
95958
|
-
suggestion: "reset the paired branch DB to the code head (downgrade/stamp + drop reset-created tables), or delete the branch and re-cut it clean from its tier"
|
|
95959
|
-
});
|
|
95960
|
-
}
|
|
95961
|
-
} catch {
|
|
95962
|
-
}
|
|
95963
|
-
}
|
|
95964
|
-
return finalize({
|
|
95965
|
-
projectDir,
|
|
95966
|
-
workflowStatePresent,
|
|
95967
|
-
state: state ?? void 0,
|
|
95968
|
-
inferredTierTopology: inferredTopology,
|
|
95969
|
-
findings
|
|
95970
|
-
});
|
|
95971
|
-
}
|
|
95972
|
-
function finalize(report) {
|
|
95973
|
-
let worst = "ok";
|
|
95974
|
-
for (const f3 of report.findings) {
|
|
95975
|
-
worst = worstOf(worst, f3.severity);
|
|
95976
|
-
}
|
|
95977
|
-
return { ...report, worstSeverity: worst };
|
|
95978
|
-
}
|
|
95979
|
-
var ScmDoctorFixError = class extends Error {
|
|
95980
|
-
constructor(message, code) {
|
|
95981
|
-
super(message);
|
|
95982
|
-
this.code = code;
|
|
95983
|
-
this.name = "ScmDoctorFixError";
|
|
95984
|
-
}
|
|
95985
|
-
code;
|
|
95986
|
-
};
|
|
95987
|
-
var FIXABLE_FINDING_IDS = [
|
|
95988
|
-
"env-branch-drift",
|
|
95989
|
-
"head-branch-drift",
|
|
95990
|
-
"tier-topology-mismatch",
|
|
95991
|
-
"orphan-current-branch",
|
|
95992
|
-
"multiple-migration-heads",
|
|
95993
|
-
"db-ahead-of-code"
|
|
95994
|
-
];
|
|
95995
|
-
async function fixFinding(args) {
|
|
95996
|
-
if (!FIXABLE_FINDING_IDS.includes(args.findingId)) {
|
|
95997
|
-
throw new ScmDoctorFixError(
|
|
95998
|
-
`Finding "${args.findingId}" is not supported by --fix. Supported: ${FIXABLE_FINDING_IDS.join(", ")}.`,
|
|
95999
|
-
"unsupported-finding"
|
|
96000
|
-
);
|
|
96001
|
-
}
|
|
96002
|
-
const report = args.report ?? await runDoctor({ projectDir: args.projectDir, instance: args.instance });
|
|
96003
|
-
const present = report.findings.find((f3) => f3.id === args.findingId);
|
|
96004
|
-
if (!present) {
|
|
96005
|
-
throw new ScmDoctorFixError(
|
|
96006
|
-
`Finding "${args.findingId}" is not present in the current report. Re-run lakebase-scm-doctor to see what needs fixing.`,
|
|
96007
|
-
"finding-not-present"
|
|
96008
|
-
);
|
|
96009
|
-
}
|
|
96010
|
-
let action = "";
|
|
96011
|
-
try {
|
|
96012
|
-
switch (args.findingId) {
|
|
96013
|
-
case "env-branch-drift": {
|
|
96014
|
-
const branch = report.state?.branch;
|
|
96015
|
-
if (!branch) {
|
|
96016
|
-
throw new ScmDoctorFixError(
|
|
96017
|
-
"Cannot fix: workflow state has no branch field.",
|
|
96018
|
-
"fix-failed"
|
|
96019
|
-
);
|
|
96020
|
-
}
|
|
96021
|
-
const sanitized = sanitizeBranchName(branch);
|
|
96022
|
-
const envFile = path28.join(args.projectDir, ".env");
|
|
96023
|
-
updateEnvConnection({
|
|
96024
|
-
envPath: envFile,
|
|
96025
|
-
projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
|
|
96026
|
-
branchId: sanitized,
|
|
96027
|
-
username: readEnvVar(envFile, "DB_USERNAME") ?? "",
|
|
96028
|
-
endpointHost: readEnvVar(envFile, "LAKEBASE_HOST")
|
|
96029
|
-
});
|
|
96030
|
-
action = `rewrote .env LAKEBASE_BRANCH_ID=${sanitized} (metadata only; the app mints its token at runtime)`;
|
|
96031
|
-
break;
|
|
96032
|
-
}
|
|
96033
|
-
case "head-branch-drift": {
|
|
96034
|
-
const branch = report.state?.branch;
|
|
96035
|
-
if (!branch) {
|
|
96036
|
-
throw new ScmDoctorFixError(
|
|
96037
|
-
"Cannot fix: workflow state has no branch field.",
|
|
96038
|
-
"fix-failed"
|
|
96039
|
-
);
|
|
96040
|
-
}
|
|
96041
|
-
await exec2(`git checkout ${shellEscape3(branch)}`, {
|
|
96042
|
-
cwd: args.projectDir,
|
|
96043
|
-
timeout: 15e3
|
|
96044
|
-
});
|
|
96045
|
-
action = `git checkout ${branch} (re-fires post-checkout to resync HEAD)`;
|
|
96046
|
-
break;
|
|
96047
|
-
}
|
|
96048
|
-
case "tier-topology-mismatch": {
|
|
96049
|
-
const instance = args.instance ?? report.state?.project_id;
|
|
96050
|
-
if (!instance) {
|
|
96051
|
-
throw new ScmDoctorFixError(
|
|
96052
|
-
"Cannot fix: missing Lakebase project id.",
|
|
96053
|
-
"fix-failed"
|
|
96054
|
-
);
|
|
96055
|
-
}
|
|
96056
|
-
await adoptScmState({
|
|
96057
|
-
projectDir: args.projectDir,
|
|
96058
|
-
instance,
|
|
96059
|
-
force: true
|
|
96060
|
-
});
|
|
96061
|
-
action = `adopted state with --force to re-infer tier_topology`;
|
|
96062
|
-
break;
|
|
96063
|
-
}
|
|
96064
|
-
case "orphan-current-branch": {
|
|
96065
|
-
const instance = args.instance ?? report.state?.project_id;
|
|
96066
|
-
if (!instance) {
|
|
96067
|
-
throw new ScmDoctorFixError(
|
|
96068
|
-
"Cannot fix: missing Lakebase project id.",
|
|
96069
|
-
"fix-failed"
|
|
96070
|
-
);
|
|
96071
|
-
}
|
|
96072
|
-
const headBranch = await getCurrentBranch({ cwd: args.projectDir });
|
|
96073
|
-
if (!headBranch) {
|
|
96074
|
-
throw new ScmDoctorFixError(
|
|
96075
|
-
"Cannot fix: detached HEAD or no current branch.",
|
|
96076
|
-
"fix-failed"
|
|
96077
|
-
);
|
|
96078
|
-
}
|
|
96079
|
-
await recoverOrphans({
|
|
96080
|
-
projectDir: args.projectDir,
|
|
96081
|
-
instance,
|
|
96082
|
-
claim: true,
|
|
96083
|
-
onlyBranch: headBranch
|
|
96084
|
-
});
|
|
96085
|
-
action = `recovered orphan ${headBranch} via createFeaturePairedBranch`;
|
|
96086
|
-
break;
|
|
96087
|
-
}
|
|
96088
|
-
case "multiple-migration-heads": {
|
|
96089
|
-
const r2 = await collapseMigrationHeads({ projectDir: args.projectDir });
|
|
96090
|
-
if (r2.status !== "ok" || !r2.mergeRevision) {
|
|
96091
|
-
throw new ScmDoctorFixError(
|
|
96092
|
-
`Expected to create a merge revision but got status="${r2.status}".`,
|
|
96093
|
-
"fix-failed"
|
|
96094
|
-
);
|
|
96095
|
-
}
|
|
96096
|
-
action = `collapsed ${r2.headsBefore.length} heads into merge revision ${r2.mergeRevision} (commit it)`;
|
|
96097
|
-
break;
|
|
96098
|
-
}
|
|
96099
|
-
case "db-ahead-of-code": {
|
|
96100
|
-
const r2 = await abandonFeatureBranch({ projectDir: args.projectDir, instance: args.instance, force: true });
|
|
96101
|
-
action = `abandoned the feature (deleted the polluted paired branch${r2.lakebaseDeleted ? "" : " , Lakebase delete reported not-deleted"}) and reset state to '${r2.state.state}'; re-claim to re-fork a clean branch from the tier`;
|
|
96102
|
-
break;
|
|
96103
|
-
}
|
|
96104
|
-
}
|
|
96105
|
-
} catch (err) {
|
|
96106
|
-
if (err instanceof ScmDoctorFixError) throw err;
|
|
96107
|
-
throw new ScmDoctorFixError(
|
|
96108
|
-
`Remediation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
96109
|
-
"fix-failed"
|
|
96110
|
-
);
|
|
96111
|
-
}
|
|
96112
|
-
const postReport = await runDoctor({
|
|
96113
|
-
projectDir: args.projectDir,
|
|
96114
|
-
instance: args.instance
|
|
96115
|
-
});
|
|
96116
|
-
return { findingId: args.findingId, action, postReport };
|
|
96117
|
-
}
|
|
96118
|
-
function shellEscape3(s2) {
|
|
96119
|
-
return `'${s2.replace(/'/g, "'\\''")}'`;
|
|
96120
|
-
}
|
|
96121
|
-
|
|
96122
|
-
// scripts/lakebase/doctor.ts
|
|
96123
|
-
init_cjs_shims();
|
|
96124
|
-
var fs32 = __toESM(require("fs"), 1);
|
|
96125
|
-
var path30 = __toESM(require("path"), 1);
|
|
96126
|
-
var cp6 = __toESM(require("child_process"), 1);
|
|
96127
|
-
|
|
96128
|
-
// scripts/lakebase/workflow-drift.ts
|
|
96129
|
-
init_cjs_shims();
|
|
96130
|
-
var fs31 = __toESM(require("fs"), 1);
|
|
96131
|
-
var path29 = __toESM(require("path"), 1);
|
|
96132
|
-
function findKitTemplatesDir(start) {
|
|
96133
|
-
let dir = start;
|
|
96134
|
-
for (let i2 = 0; i2 < 6; i2++) {
|
|
96135
|
-
const candidate = path29.join(
|
|
96136
|
-
dir,
|
|
96137
|
-
"templates",
|
|
96138
|
-
"project",
|
|
96139
|
-
"common",
|
|
96140
|
-
".github",
|
|
96141
|
-
"workflows"
|
|
96142
|
-
);
|
|
96143
|
-
if (fs31.existsSync(candidate)) return candidate;
|
|
96144
|
-
const parent = path29.dirname(dir);
|
|
96145
|
-
if (parent === dir) break;
|
|
96146
|
-
dir = parent;
|
|
96147
|
-
}
|
|
96148
|
-
throw new Error(
|
|
96149
|
-
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
96150
|
-
);
|
|
96151
|
-
}
|
|
96152
|
-
function unifiedDiff(name, projectContent, templateContent) {
|
|
96153
|
-
if (projectContent === templateContent) return "";
|
|
96154
|
-
const a = projectContent.split("\n");
|
|
96155
|
-
const b = templateContent.split("\n");
|
|
96156
|
-
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
96157
|
-
const max = Math.max(a.length, b.length);
|
|
96158
|
-
for (let i2 = 0; i2 < max; i2++) {
|
|
96159
|
-
const av = a[i2];
|
|
96160
|
-
const bv = b[i2];
|
|
96161
|
-
if (av === bv) continue;
|
|
96162
|
-
if (av !== void 0) out.push(`-${i2 + 1}: ${av}`);
|
|
96163
|
-
if (bv !== void 0) out.push(`+${i2 + 1}: ${bv}`);
|
|
96164
|
-
}
|
|
96165
|
-
return out.join("\n");
|
|
96166
|
-
}
|
|
96167
|
-
function detectWorkflowDrift(args) {
|
|
96168
|
-
const projectWorkflowsDir = path29.join(
|
|
96169
|
-
args.projectDir,
|
|
96170
|
-
".github",
|
|
96171
|
-
"workflows"
|
|
96172
|
-
);
|
|
96173
|
-
const here = path29.dirname(new URL(importMetaUrl).pathname);
|
|
96174
|
-
const kitWorkflowsDir = args.kitDir ? path29.join(
|
|
96175
|
-
args.kitDir,
|
|
96176
|
-
"templates",
|
|
96177
|
-
"project",
|
|
96178
|
-
"common",
|
|
96179
|
-
".github",
|
|
96180
|
-
"workflows"
|
|
96181
|
-
) : findKitTemplatesDir(here);
|
|
96182
|
-
const templateFiles = fs31.existsSync(kitWorkflowsDir) ? fs31.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
96183
|
-
const projectFiles = fs31.existsSync(projectWorkflowsDir) ? fs31.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
96184
|
-
const version = readKitVersion(kitWorkflowsDir);
|
|
96185
|
-
const seen = /* @__PURE__ */ new Set();
|
|
96186
|
-
const files = [];
|
|
96187
|
-
for (const name of templateFiles) {
|
|
96188
|
-
seen.add(name);
|
|
96189
|
-
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
96190
|
-
const templatePath = path29.join(kitWorkflowsDir, name);
|
|
96191
|
-
if (!fs31.existsSync(projectPath2)) {
|
|
96192
|
-
files.push({ name, status: "missing" });
|
|
96193
|
-
continue;
|
|
96174
|
+
message: `GitHub Actions is disabled for ${ownerRepo}, so the kit's CI workflows (pr.yml / merge.yml) will never run , the PR branch's migrations, tests, and schema-diff comment are all skipped. This is commonly an org-level policy on EMU repos, which a repo admin cannot override.`,
|
|
96175
|
+
suggestion: "Have an org owner enable Actions for this repo (repo Settings -> Actions -> General; if it says 'disabled by the organization', it must be enabled in the org's Actions policy). Until then, run the workflow steps locally: scripts/run-tests.sh against a Lakebase branch."
|
|
96176
|
+
});
|
|
96177
|
+
}
|
|
96194
96178
|
}
|
|
96195
|
-
|
|
96196
|
-
|
|
96197
|
-
|
|
96198
|
-
|
|
96199
|
-
|
|
96200
|
-
|
|
96201
|
-
|
|
96202
|
-
|
|
96203
|
-
|
|
96179
|
+
} catch {
|
|
96180
|
+
}
|
|
96181
|
+
for (const wf of ["pr.yml", "merge.yml"]) {
|
|
96182
|
+
try {
|
|
96183
|
+
const p = path29.join(projectDir, ".github", "workflows", wf);
|
|
96184
|
+
if (!fs31.existsSync(p)) continue;
|
|
96185
|
+
const body = fs31.readFileSync(p, "utf8");
|
|
96186
|
+
if (/lakebase-scm-utils#v\d/.test(body)) {
|
|
96187
|
+
findings.push({
|
|
96188
|
+
id: "ci-workflow-substrate-pin",
|
|
96189
|
+
severity: "warn",
|
|
96190
|
+
message: `.github/workflows/${wf} hardcodes a substrate version pin (github:databricks-solutions/lakebase-scm-utils#v<ver>) instead of resolving .lakebase/scm-utils-ref at runtime, so bumping .lakebase/scm-utils-ref does NOT change the substrate CI actually runs.`,
|
|
96191
|
+
suggestion: "Re-emit the workflows from the current substrate templates so they resolve SCM_UTILS_REF from .lakebase/scm-utils-ref at CI time (updateWorkflows in scripts/lakebase/workflow-drift.ts; lakebase-doctor reports this as workflow-drift). Until then a scm-utils-ref bump will not reach CI."
|
|
96192
|
+
});
|
|
96193
|
+
}
|
|
96194
|
+
} catch {
|
|
96195
|
+
}
|
|
96196
|
+
}
|
|
96197
|
+
try {
|
|
96198
|
+
if (isGitTracked(projectDir, ".lakebase/workflow-state.json")) {
|
|
96199
|
+
findings.push({
|
|
96200
|
+
id: "scm-state-git-tracked",
|
|
96201
|
+
severity: "warn",
|
|
96202
|
+
message: ".lakebase/workflow-state.json (the per-working-tree SCM claim state) is git-tracked, so a branch checkout or `git reset --hard origin/<tier>` can restore a stale committed claim over the live one (the foreign-claim guard then refuses to drive until you abandon + reclaim).",
|
|
96203
|
+
suggestion: "If a wrong-feature-claim refusal follows a checkout, run lakebase-scm-adopt-state (or abandon + reclaim) to re-establish the live claim. The kit-ref run pin (.lakebase/kit-ref.local) already protects the kit version from the same checkout revert."
|
|
96204
96204
|
});
|
|
96205
96205
|
}
|
|
96206
|
+
} catch {
|
|
96206
96207
|
}
|
|
96207
|
-
|
|
96208
|
-
|
|
96209
|
-
|
|
96208
|
+
if (!env.has("LAKEBASE_PROJECT_ID")) {
|
|
96209
|
+
findings.push({
|
|
96210
|
+
id: "env-missing-project-id",
|
|
96211
|
+
severity: "fail",
|
|
96212
|
+
message: ".env does not contain LAKEBASE_PROJECT_ID. The post-checkout hook will exit early; workflow CLIs will need an explicit --instance.",
|
|
96213
|
+
suggestion: "Set LAKEBASE_PROJECT_ID=<your project id> in .env"
|
|
96214
|
+
});
|
|
96210
96215
|
}
|
|
96211
|
-
|
|
96212
|
-
|
|
96213
|
-
|
|
96214
|
-
|
|
96215
|
-
|
|
96216
|
-
|
|
96217
|
-
|
|
96218
|
-
const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
|
|
96219
|
-
return {
|
|
96220
|
-
overall: hasDrift ? "drift" : "ok",
|
|
96221
|
-
files
|
|
96222
|
-
};
|
|
96223
|
-
}
|
|
96224
|
-
function readKitVersion(kitWorkflowsDir) {
|
|
96225
|
-
let dir = kitWorkflowsDir;
|
|
96226
|
-
for (let i2 = 0; i2 < 5; i2++) {
|
|
96227
|
-
dir = path29.dirname(dir);
|
|
96216
|
+
if (!instance) {
|
|
96217
|
+
return finalize({
|
|
96218
|
+
projectDir,
|
|
96219
|
+
workflowStatePresent,
|
|
96220
|
+
state: state ?? void 0,
|
|
96221
|
+
findings
|
|
96222
|
+
});
|
|
96228
96223
|
}
|
|
96224
|
+
let lakebaseBranches = [];
|
|
96229
96225
|
try {
|
|
96230
|
-
|
|
96231
|
-
|
|
96232
|
-
|
|
96233
|
-
|
|
96234
|
-
|
|
96226
|
+
lakebaseBranches = await listBranches({ instance });
|
|
96227
|
+
} catch (err) {
|
|
96228
|
+
findings.push({
|
|
96229
|
+
id: "lakebase-unreachable",
|
|
96230
|
+
severity: "fail",
|
|
96231
|
+
message: `Could not list Lakebase branches for instance ${instance}: ${err instanceof Error ? err.message : String(err)}`,
|
|
96232
|
+
suggestion: "databricks auth login (or check DATABRICKS_CONFIG_PROFILE)."
|
|
96233
|
+
});
|
|
96234
|
+
return finalize({
|
|
96235
|
+
projectDir,
|
|
96236
|
+
workflowStatePresent,
|
|
96237
|
+
state: state ?? void 0,
|
|
96238
|
+
findings
|
|
96239
|
+
});
|
|
96235
96240
|
}
|
|
96236
|
-
|
|
96237
|
-
|
|
96238
|
-
|
|
96239
|
-
|
|
96240
|
-
|
|
96241
|
-
|
|
96242
|
-
|
|
96243
|
-
|
|
96244
|
-
function findKitCommandsDir(start) {
|
|
96245
|
-
let dir = start;
|
|
96246
|
-
for (let i2 = 0; i2 < 6; i2++) {
|
|
96247
|
-
const candidate = path29.join(
|
|
96248
|
-
dir,
|
|
96249
|
-
"templates",
|
|
96250
|
-
"project",
|
|
96251
|
-
"common",
|
|
96252
|
-
".claude",
|
|
96253
|
-
"commands"
|
|
96254
|
-
);
|
|
96255
|
-
if (fs31.existsSync(candidate)) return candidate;
|
|
96256
|
-
const parent = path29.dirname(dir);
|
|
96257
|
-
if (parent === dir) break;
|
|
96258
|
-
dir = parent;
|
|
96241
|
+
const inferredTopology = inferTierTopology(lakebaseBranches);
|
|
96242
|
+
if (state && state.tier_topology !== inferredTopology) {
|
|
96243
|
+
findings.push({
|
|
96244
|
+
id: "tier-topology-mismatch",
|
|
96245
|
+
severity: "warn",
|
|
96246
|
+
message: `workflow-state records tier_topology=${state.tier_topology}, but the Lakebase tier inventory suggests ${inferredTopology}.`,
|
|
96247
|
+
suggestion: "lakebase-scm-adopt-state --force"
|
|
96248
|
+
});
|
|
96259
96249
|
}
|
|
96260
|
-
|
|
96261
|
-
|
|
96262
|
-
|
|
96263
|
-
|
|
96264
|
-
|
|
96265
|
-
|
|
96266
|
-
|
|
96267
|
-
}
|
|
96268
|
-
function detectCommandDrift(args) {
|
|
96269
|
-
const projectCommandsDir = path29.join(args.projectDir, ".claude", "commands");
|
|
96270
|
-
const here = path29.dirname(new URL(importMetaUrl).pathname);
|
|
96271
|
-
const kitCommandsDir = args.kitDir ? path29.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
96272
|
-
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
96273
|
-
const templateFiles = fs31.existsSync(kitCommandsDir) ? fs31.readdirSync(kitCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
|
|
96274
|
-
const projectFiles = fs31.existsSync(projectCommandsDir) ? fs31.readdirSync(projectCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
|
|
96275
|
-
const seen = /* @__PURE__ */ new Set();
|
|
96276
|
-
const files = [];
|
|
96277
|
-
for (const name of templateFiles) {
|
|
96278
|
-
seen.add(name);
|
|
96279
|
-
const projectPath2 = path29.join(projectCommandsDir, name);
|
|
96280
|
-
const templatePath = path29.join(kitCommandsDir, name);
|
|
96281
|
-
const templateRaw = fs31.readFileSync(templatePath, "utf8");
|
|
96282
|
-
if (!fs31.existsSync(projectPath2)) {
|
|
96283
|
-
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
96284
|
-
continue;
|
|
96285
|
-
}
|
|
96286
|
-
const projectContent = fs31.readFileSync(projectPath2, "utf8");
|
|
96287
|
-
const pinned = parsePinnedVersion(projectContent);
|
|
96288
|
-
const versionForCompare = pinned ?? kitVersion;
|
|
96289
|
-
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
96290
|
-
if (projectContent === templateContent) {
|
|
96291
|
-
files.push({
|
|
96292
|
-
name,
|
|
96293
|
-
status: "unchanged",
|
|
96294
|
-
pinned_version: pinned,
|
|
96295
|
-
kit_version: kitVersion
|
|
96296
|
-
});
|
|
96297
|
-
} else {
|
|
96298
|
-
files.push({
|
|
96299
|
-
name,
|
|
96300
|
-
status: "drifted",
|
|
96301
|
-
pinned_version: pinned,
|
|
96302
|
-
kit_version: kitVersion,
|
|
96303
|
-
diff: unifiedDiff(name, projectContent, templateContent)
|
|
96250
|
+
const headBranch = await getCurrentBranch({ cwd: projectDir });
|
|
96251
|
+
if (state && state.state === "feature-claimed") {
|
|
96252
|
+
if (state.branch && headBranch && headBranch !== state.branch) {
|
|
96253
|
+
findings.push({
|
|
96254
|
+
id: "head-branch-drift",
|
|
96255
|
+
severity: "warn",
|
|
96256
|
+
message: `workflow says feature-claimed for "${state.branch}", but HEAD is on "${headBranch}".`,
|
|
96257
|
+
suggestion: `git checkout '${state.branch}'`
|
|
96304
96258
|
});
|
|
96305
96259
|
}
|
|
96260
|
+
if (state.branch) {
|
|
96261
|
+
const sanitized = sanitizeBranchName(state.branch);
|
|
96262
|
+
let pair;
|
|
96263
|
+
try {
|
|
96264
|
+
pair = await getBranchByName(sanitized, { instance });
|
|
96265
|
+
} catch {
|
|
96266
|
+
pair = void 0;
|
|
96267
|
+
}
|
|
96268
|
+
if (!pair) {
|
|
96269
|
+
findings.push({
|
|
96270
|
+
id: "lakebase-pair-missing",
|
|
96271
|
+
severity: "fail",
|
|
96272
|
+
message: `workflow says feature-claimed for "${state.branch}", but no Lakebase branch "${sanitized}" exists.`,
|
|
96273
|
+
suggestion: `lakebase-scm-abandon-feature # reset state; re-claim if needed`
|
|
96274
|
+
});
|
|
96275
|
+
} else if (state.lakebase_branch_uid && pair.uid !== state.lakebase_branch_uid) {
|
|
96276
|
+
findings.push({
|
|
96277
|
+
id: "lakebase-uid-drift",
|
|
96278
|
+
severity: "warn",
|
|
96279
|
+
message: `workflow records lakebase_branch_uid=${state.lakebase_branch_uid}, but the live branch reports ${pair.uid}.`,
|
|
96280
|
+
suggestion: "lakebase-scm-adopt-state --force"
|
|
96281
|
+
});
|
|
96282
|
+
}
|
|
96283
|
+
}
|
|
96306
96284
|
}
|
|
96307
|
-
|
|
96308
|
-
|
|
96309
|
-
|
|
96285
|
+
if (state && state.state === "feature-claimed" && state.branch) {
|
|
96286
|
+
const envBranchId = env.get("LAKEBASE_BRANCH_ID");
|
|
96287
|
+
const sanitized = sanitizeBranchName(state.branch);
|
|
96288
|
+
if (envBranchId && envBranchId !== sanitized) {
|
|
96289
|
+
findings.push({
|
|
96290
|
+
id: "env-branch-drift",
|
|
96291
|
+
severity: "warn",
|
|
96292
|
+
message: `.env LAKEBASE_BRANCH_ID=${envBranchId} but workflow says ${sanitized}. The post-checkout hook may not have run since the last branch switch.`,
|
|
96293
|
+
suggestion: `git checkout '${state.branch}' # re-fires post-checkout`
|
|
96294
|
+
});
|
|
96295
|
+
}
|
|
96310
96296
|
}
|
|
96311
|
-
|
|
96312
|
-
|
|
96313
|
-
|
|
96314
|
-
|
|
96315
|
-
|
|
96316
|
-
|
|
96317
|
-
|
|
96318
|
-
|
|
96319
|
-
|
|
96320
|
-
}
|
|
96321
|
-
|
|
96322
|
-
let dir = kitCommandsDir;
|
|
96323
|
-
for (let i2 = 0; i2 < 5; i2++) {
|
|
96324
|
-
dir = path29.dirname(dir);
|
|
96297
|
+
if (headBranch && !TIER_LEAFS2.has(headBranch) && headBranch.startsWith(FEATURE_PREFIX)) {
|
|
96298
|
+
const sanitized = sanitizeBranchName(headBranch);
|
|
96299
|
+
const paired = lakebaseBranches.some((b) => leafOf2(b) === sanitized);
|
|
96300
|
+
if (!paired) {
|
|
96301
|
+
findings.push({
|
|
96302
|
+
id: "orphan-current-branch",
|
|
96303
|
+
severity: "fail",
|
|
96304
|
+
message: `Current git branch "${headBranch}" has no Lakebase pair (post-checkout fallback retired in phase C).`,
|
|
96305
|
+
suggestion: `lakebase-scm-recover-orphans --claim --only-branch '${headBranch}'`
|
|
96306
|
+
});
|
|
96307
|
+
}
|
|
96325
96308
|
}
|
|
96326
96309
|
try {
|
|
96327
|
-
const
|
|
96328
|
-
|
|
96329
|
-
|
|
96310
|
+
const heads = await collapseMigrationHeads({ projectDir, dryRun: true });
|
|
96311
|
+
if (heads.headsBefore.length > 1) {
|
|
96312
|
+
findings.push({
|
|
96313
|
+
id: "multiple-migration-heads",
|
|
96314
|
+
severity: "fail",
|
|
96315
|
+
message: `Migrations have ${heads.headsBefore.length} heads (${heads.headsBefore.join(", ")}); a sibling-feature merge left them un-collapsed. \`upgrade head\` will refuse until they are unified.`,
|
|
96316
|
+
suggestion: "lakebase-collapse-heads"
|
|
96317
|
+
});
|
|
96318
|
+
}
|
|
96330
96319
|
} catch {
|
|
96331
|
-
return "unknown";
|
|
96332
96320
|
}
|
|
96321
|
+
if (instance && state?.branch) {
|
|
96322
|
+
try {
|
|
96323
|
+
const localIds = listSchemaMigrations({ projectDir }).map((m2) => m2.version);
|
|
96324
|
+
let orphanRev = null;
|
|
96325
|
+
try {
|
|
96326
|
+
const status = await schemaMigrationStatus({ instance, branch: state.branch, projectDir });
|
|
96327
|
+
if (dbRevisionOrphaned(status.current, localIds)) orphanRev = status.current ?? null;
|
|
96328
|
+
} catch (e2) {
|
|
96329
|
+
orphanRev = parseAlembicMissingRevision(e2 instanceof Error ? e2.message : String(e2));
|
|
96330
|
+
}
|
|
96331
|
+
if (orphanRev) {
|
|
96332
|
+
findings.push({
|
|
96333
|
+
id: "db-ahead-of-code",
|
|
96334
|
+
severity: "fail",
|
|
96335
|
+
message: `The paired branch DB is AHEAD of code: applied revision '${orphanRev}' has no local migration file. An aborted build likely migrated this branch and a git reset removed the migration file; alembic accept/deploy/promote will fail "Can't locate revision".`,
|
|
96336
|
+
suggestion: "reset the paired branch DB to the code head (downgrade/stamp + drop reset-created tables), or delete the branch and re-cut it clean from its tier"
|
|
96337
|
+
});
|
|
96338
|
+
}
|
|
96339
|
+
} catch {
|
|
96340
|
+
}
|
|
96341
|
+
}
|
|
96342
|
+
return finalize({
|
|
96343
|
+
projectDir,
|
|
96344
|
+
workflowStatePresent,
|
|
96345
|
+
state: state ?? void 0,
|
|
96346
|
+
inferredTierTopology: inferredTopology,
|
|
96347
|
+
findings
|
|
96348
|
+
});
|
|
96333
96349
|
}
|
|
96334
|
-
function
|
|
96335
|
-
|
|
96336
|
-
const
|
|
96337
|
-
|
|
96338
|
-
|
|
96339
|
-
|
|
96340
|
-
commands
|
|
96341
|
-
};
|
|
96350
|
+
function finalize(report) {
|
|
96351
|
+
let worst = "ok";
|
|
96352
|
+
for (const f3 of report.findings) {
|
|
96353
|
+
worst = worstOf(worst, f3.severity);
|
|
96354
|
+
}
|
|
96355
|
+
return { ...report, worstSeverity: worst };
|
|
96342
96356
|
}
|
|
96343
|
-
|
|
96344
|
-
|
|
96345
|
-
|
|
96346
|
-
|
|
96347
|
-
"
|
|
96348
|
-
);
|
|
96349
|
-
const here = path29.dirname(new URL(importMetaUrl).pathname);
|
|
96350
|
-
const kitWorkflowsDir = args.kitDir ? path29.join(
|
|
96351
|
-
args.kitDir,
|
|
96352
|
-
"templates",
|
|
96353
|
-
"project",
|
|
96354
|
-
"common",
|
|
96355
|
-
".github",
|
|
96356
|
-
"workflows"
|
|
96357
|
-
) : findKitTemplatesDir(here);
|
|
96358
|
-
const substitute = args.substitute !== false;
|
|
96359
|
-
const dryRun = args.dryRun === true;
|
|
96360
|
-
const pruneExtras = args.pruneExtras === true;
|
|
96361
|
-
const templateFiles = fs31.existsSync(kitWorkflowsDir) ? fs31.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
96362
|
-
const projectFiles = fs31.existsSync(projectWorkflowsDir) ? fs31.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
96363
|
-
if (!dryRun && templateFiles.length > 0 && !fs31.existsSync(projectWorkflowsDir)) {
|
|
96364
|
-
fs31.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
96357
|
+
var ScmDoctorFixError = class extends Error {
|
|
96358
|
+
constructor(message, code) {
|
|
96359
|
+
super(message);
|
|
96360
|
+
this.code = code;
|
|
96361
|
+
this.name = "ScmDoctorFixError";
|
|
96365
96362
|
}
|
|
96366
|
-
|
|
96367
|
-
|
|
96368
|
-
|
|
96369
|
-
|
|
96370
|
-
|
|
96371
|
-
|
|
96372
|
-
|
|
96373
|
-
|
|
96374
|
-
|
|
96375
|
-
|
|
96376
|
-
|
|
96377
|
-
|
|
96378
|
-
|
|
96379
|
-
|
|
96380
|
-
|
|
96381
|
-
|
|
96382
|
-
} else {
|
|
96383
|
-
outcome = "updated";
|
|
96384
|
-
}
|
|
96385
|
-
if (!dryRun && outcome !== "unchanged") {
|
|
96386
|
-
fs31.writeFileSync(projectPath2, desired);
|
|
96387
|
-
}
|
|
96388
|
-
files.push({ name, outcome });
|
|
96363
|
+
code;
|
|
96364
|
+
};
|
|
96365
|
+
var FIXABLE_FINDING_IDS = [
|
|
96366
|
+
"env-branch-drift",
|
|
96367
|
+
"head-branch-drift",
|
|
96368
|
+
"tier-topology-mismatch",
|
|
96369
|
+
"orphan-current-branch",
|
|
96370
|
+
"multiple-migration-heads",
|
|
96371
|
+
"db-ahead-of-code"
|
|
96372
|
+
];
|
|
96373
|
+
async function fixFinding(args) {
|
|
96374
|
+
if (!FIXABLE_FINDING_IDS.includes(args.findingId)) {
|
|
96375
|
+
throw new ScmDoctorFixError(
|
|
96376
|
+
`Finding "${args.findingId}" is not supported by --fix. Supported: ${FIXABLE_FINDING_IDS.join(", ")}.`,
|
|
96377
|
+
"unsupported-finding"
|
|
96378
|
+
);
|
|
96389
96379
|
}
|
|
96390
|
-
|
|
96391
|
-
|
|
96392
|
-
|
|
96393
|
-
|
|
96394
|
-
|
|
96395
|
-
|
|
96380
|
+
const report = args.report ?? await runDoctor({ projectDir: args.projectDir, instance: args.instance });
|
|
96381
|
+
const present = report.findings.find((f3) => f3.id === args.findingId);
|
|
96382
|
+
if (!present) {
|
|
96383
|
+
throw new ScmDoctorFixError(
|
|
96384
|
+
`Finding "${args.findingId}" is not present in the current report. Re-run lakebase-scm-doctor to see what needs fixing.`,
|
|
96385
|
+
"finding-not-present"
|
|
96386
|
+
);
|
|
96387
|
+
}
|
|
96388
|
+
let action = "";
|
|
96389
|
+
try {
|
|
96390
|
+
switch (args.findingId) {
|
|
96391
|
+
case "env-branch-drift": {
|
|
96392
|
+
const branch = report.state?.branch;
|
|
96393
|
+
if (!branch) {
|
|
96394
|
+
throw new ScmDoctorFixError(
|
|
96395
|
+
"Cannot fix: workflow state has no branch field.",
|
|
96396
|
+
"fix-failed"
|
|
96397
|
+
);
|
|
96398
|
+
}
|
|
96399
|
+
const sanitized = sanitizeBranchName(branch);
|
|
96400
|
+
const envFile = path29.join(args.projectDir, ".env");
|
|
96401
|
+
updateEnvConnection({
|
|
96402
|
+
envPath: envFile,
|
|
96403
|
+
projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
|
|
96404
|
+
branchId: sanitized,
|
|
96405
|
+
username: readEnvVar(envFile, "DB_USERNAME") ?? "",
|
|
96406
|
+
endpointHost: readEnvVar(envFile, "LAKEBASE_HOST")
|
|
96407
|
+
});
|
|
96408
|
+
action = `rewrote .env LAKEBASE_BRANCH_ID=${sanitized} (metadata only; the app mints its token at runtime)`;
|
|
96409
|
+
break;
|
|
96410
|
+
}
|
|
96411
|
+
case "head-branch-drift": {
|
|
96412
|
+
const branch = report.state?.branch;
|
|
96413
|
+
if (!branch) {
|
|
96414
|
+
throw new ScmDoctorFixError(
|
|
96415
|
+
"Cannot fix: workflow state has no branch field.",
|
|
96416
|
+
"fix-failed"
|
|
96417
|
+
);
|
|
96418
|
+
}
|
|
96419
|
+
await exec2(`git checkout ${shellEscape3(branch)}`, {
|
|
96420
|
+
cwd: args.projectDir,
|
|
96421
|
+
timeout: 15e3
|
|
96422
|
+
});
|
|
96423
|
+
action = `git checkout ${branch} (re-fires post-checkout to resync HEAD)`;
|
|
96424
|
+
break;
|
|
96425
|
+
}
|
|
96426
|
+
case "tier-topology-mismatch": {
|
|
96427
|
+
const instance = args.instance ?? report.state?.project_id;
|
|
96428
|
+
if (!instance) {
|
|
96429
|
+
throw new ScmDoctorFixError(
|
|
96430
|
+
"Cannot fix: missing Lakebase project id.",
|
|
96431
|
+
"fix-failed"
|
|
96432
|
+
);
|
|
96433
|
+
}
|
|
96434
|
+
await adoptScmState({
|
|
96435
|
+
projectDir: args.projectDir,
|
|
96436
|
+
instance,
|
|
96437
|
+
force: true
|
|
96438
|
+
});
|
|
96439
|
+
action = `adopted state with --force to re-infer tier_topology`;
|
|
96440
|
+
break;
|
|
96441
|
+
}
|
|
96442
|
+
case "orphan-current-branch": {
|
|
96443
|
+
const instance = args.instance ?? report.state?.project_id;
|
|
96444
|
+
if (!instance) {
|
|
96445
|
+
throw new ScmDoctorFixError(
|
|
96446
|
+
"Cannot fix: missing Lakebase project id.",
|
|
96447
|
+
"fix-failed"
|
|
96448
|
+
);
|
|
96449
|
+
}
|
|
96450
|
+
const headBranch = await getCurrentBranch({ cwd: args.projectDir });
|
|
96451
|
+
if (!headBranch) {
|
|
96452
|
+
throw new ScmDoctorFixError(
|
|
96453
|
+
"Cannot fix: detached HEAD or no current branch.",
|
|
96454
|
+
"fix-failed"
|
|
96455
|
+
);
|
|
96456
|
+
}
|
|
96457
|
+
await recoverOrphans({
|
|
96458
|
+
projectDir: args.projectDir,
|
|
96459
|
+
instance,
|
|
96460
|
+
claim: true,
|
|
96461
|
+
onlyBranch: headBranch
|
|
96462
|
+
});
|
|
96463
|
+
action = `recovered orphan ${headBranch} via createFeaturePairedBranch`;
|
|
96464
|
+
break;
|
|
96465
|
+
}
|
|
96466
|
+
case "multiple-migration-heads": {
|
|
96467
|
+
const r2 = await collapseMigrationHeads({ projectDir: args.projectDir });
|
|
96468
|
+
if (r2.status !== "ok" || !r2.mergeRevision) {
|
|
96469
|
+
throw new ScmDoctorFixError(
|
|
96470
|
+
`Expected to create a merge revision but got status="${r2.status}".`,
|
|
96471
|
+
"fix-failed"
|
|
96472
|
+
);
|
|
96473
|
+
}
|
|
96474
|
+
action = `collapsed ${r2.headsBefore.length} heads into merge revision ${r2.mergeRevision} (commit it)`;
|
|
96475
|
+
break;
|
|
96476
|
+
}
|
|
96477
|
+
case "db-ahead-of-code": {
|
|
96478
|
+
const r2 = await abandonFeatureBranch({ projectDir: args.projectDir, instance: args.instance, force: true });
|
|
96479
|
+
action = `abandoned the feature (deleted the polluted paired branch${r2.lakebaseDeleted ? "" : " , Lakebase delete reported not-deleted"}) and reset state to '${r2.state.state}'; re-claim to re-fork a clean branch from the tier`;
|
|
96480
|
+
break;
|
|
96396
96481
|
}
|
|
96397
|
-
files.push({ name, outcome: "removed" });
|
|
96398
96482
|
}
|
|
96483
|
+
} catch (err) {
|
|
96484
|
+
if (err instanceof ScmDoctorFixError) throw err;
|
|
96485
|
+
throw new ScmDoctorFixError(
|
|
96486
|
+
`Remediation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
96487
|
+
"fix-failed"
|
|
96488
|
+
);
|
|
96399
96489
|
}
|
|
96400
|
-
const
|
|
96401
|
-
|
|
96402
|
-
|
|
96403
|
-
|
|
96404
|
-
|
|
96405
|
-
|
|
96406
|
-
|
|
96407
|
-
|
|
96408
|
-
return { files, changed };
|
|
96490
|
+
const postReport = await runDoctor({
|
|
96491
|
+
projectDir: args.projectDir,
|
|
96492
|
+
instance: args.instance
|
|
96493
|
+
});
|
|
96494
|
+
return { findingId: args.findingId, action, postReport };
|
|
96495
|
+
}
|
|
96496
|
+
function shellEscape3(s2) {
|
|
96497
|
+
return `'${s2.replace(/'/g, "'\\''")}'`;
|
|
96409
96498
|
}
|
|
96410
96499
|
|
|
96411
96500
|
// scripts/lakebase/doctor.ts
|
|
96501
|
+
init_cjs_shims();
|
|
96502
|
+
var fs32 = __toESM(require("fs"), 1);
|
|
96503
|
+
var path30 = __toESM(require("path"), 1);
|
|
96504
|
+
var cp6 = __toESM(require("child_process"), 1);
|
|
96412
96505
|
function readEnvFile(projectDir) {
|
|
96413
96506
|
const envPath = path30.join(projectDir, ".env");
|
|
96414
96507
|
if (!fs32.existsSync(envPath)) return {};
|
|
@@ -96841,7 +96934,7 @@ function checkWorkflowDrift(projectDir) {
|
|
|
96841
96934
|
status: "warn",
|
|
96842
96935
|
message: `Scaffolded workflows drift from kit: ${drifted} drifted, ${missing} missing`,
|
|
96843
96936
|
detail: { files: report.files.map((f3) => ({ name: f3.name, status: f3.status })) },
|
|
96844
|
-
hint: "
|
|
96937
|
+
hint: "Refresh with updateWorkflows (or the lakebase_workflow_drift MCP tool). A stale merge.yml calling 'lakebase-schema-migrate apply' (not 'apply-tier') makes the promote's migrate-target fail on the tier guard; the scm-merge promote also self-heals drifted workflows onto the PR head before merging."
|
|
96845
96938
|
};
|
|
96846
96939
|
} catch (err) {
|
|
96847
96940
|
return {
|
|
@@ -97317,63 +97410,6 @@ async function discardAllChanges(args) {
|
|
|
97317
97410
|
await exec2("git clean -fd", { cwd: args.cwd });
|
|
97318
97411
|
}
|
|
97319
97412
|
|
|
97320
|
-
// scripts/git/sync.ts
|
|
97321
|
-
init_cjs_shims();
|
|
97322
|
-
async function currentBranchName2(cwd) {
|
|
97323
|
-
try {
|
|
97324
|
-
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
97325
|
-
} catch {
|
|
97326
|
-
return "";
|
|
97327
|
-
}
|
|
97328
|
-
}
|
|
97329
|
-
async function push(args) {
|
|
97330
|
-
await exec2("git push", { cwd: args.cwd });
|
|
97331
|
-
}
|
|
97332
|
-
async function pull(args) {
|
|
97333
|
-
await exec2("git pull", { cwd: args.cwd });
|
|
97334
|
-
}
|
|
97335
|
-
async function publishBranch(args) {
|
|
97336
|
-
const remote = args.remote ?? "origin";
|
|
97337
|
-
const branch = await currentBranchName2(args.cwd);
|
|
97338
|
-
if (!branch) throw new Error("No current branch");
|
|
97339
|
-
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
97340
|
-
cwd: args.cwd
|
|
97341
|
-
});
|
|
97342
|
-
}
|
|
97343
|
-
async function pushCurrentBranchForPr(args) {
|
|
97344
|
-
const remote = args.remote ?? "origin";
|
|
97345
|
-
const branch = await currentBranchName2(args.cwd);
|
|
97346
|
-
if (!branch) throw new Error("No current branch");
|
|
97347
|
-
const upstreamSet = await hasUpstream({ cwd: args.cwd });
|
|
97348
|
-
if (!upstreamSet) {
|
|
97349
|
-
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
97350
|
-
cwd: args.cwd
|
|
97351
|
-
});
|
|
97352
|
-
} else {
|
|
97353
|
-
await exec2("git push", { cwd: args.cwd });
|
|
97354
|
-
}
|
|
97355
|
-
}
|
|
97356
|
-
async function fetch3(args) {
|
|
97357
|
-
const parts = ["git fetch"];
|
|
97358
|
-
if (args.prune) parts.push("--prune");
|
|
97359
|
-
if (args.all) parts.push("--all");
|
|
97360
|
-
await exec2(parts.join(" "), { cwd: args.cwd });
|
|
97361
|
-
}
|
|
97362
|
-
async function pullFrom(args) {
|
|
97363
|
-
await exec2(`git pull ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
97364
|
-
cwd: args.cwd
|
|
97365
|
-
});
|
|
97366
|
-
}
|
|
97367
|
-
async function pushTo(args) {
|
|
97368
|
-
await exec2(`git push ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
97369
|
-
cwd: args.cwd
|
|
97370
|
-
});
|
|
97371
|
-
}
|
|
97372
|
-
async function sync(args) {
|
|
97373
|
-
await exec2("git pull", { cwd: args.cwd });
|
|
97374
|
-
await exec2("git push", { cwd: args.cwd });
|
|
97375
|
-
}
|
|
97376
|
-
|
|
97377
97413
|
// scripts/git/branch-tag.ts
|
|
97378
97414
|
init_cjs_shims();
|
|
97379
97415
|
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["production", "main", "master"]);
|
|
@@ -97779,6 +97815,7 @@ function withProxyEnv(base = {}) {
|
|
|
97779
97815
|
createWorktree,
|
|
97780
97816
|
cutBackup,
|
|
97781
97817
|
databricksAuthPrereqMessage,
|
|
97818
|
+
defaultRefreshPromoteWorkflows,
|
|
97782
97819
|
delay,
|
|
97783
97820
|
deleteAppEndpoint,
|
|
97784
97821
|
deleteBranch,
|