@databricks-solutions/lakebase-scm-utils 0.2.44 → 0.2.46
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 +13 -4
- package/dist/scripts/index.cjs +651 -605
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +650 -605
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +12 -3
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +12 -3
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +12 -3
- package/dist/scripts/lakebase/collapse-heads.cli.js +12 -3
- package/dist/scripts/lakebase/detect-language.cli.cjs +12 -3
- package/dist/scripts/lakebase/detect-language.cli.js +12 -3
- package/dist/scripts/lakebase/doctor.cli.cjs +13 -4
- package/dist/scripts/lakebase/doctor.cli.js +13 -4
- package/dist/scripts/lakebase/get-connection.cli.cjs +12 -3
- package/dist/scripts/lakebase/get-connection.cli.js +12 -3
- package/dist/scripts/lakebase/index.cjs +472 -395
- package/dist/scripts/lakebase/index.d.cts +33 -12
- package/dist/scripts/lakebase/index.d.ts +33 -12
- package/dist/scripts/lakebase/index.js +471 -395
- package/dist/scripts/lakebase/infra-runner.cli.cjs +12 -3
- package/dist/scripts/lakebase/infra-runner.cli.js +12 -3
- package/dist/scripts/lakebase/new-migration.cli.cjs +12 -3
- package/dist/scripts/lakebase/new-migration.cli.js +12 -3
- package/dist/scripts/lakebase/schema-diff.cli.cjs +12 -3
- package/dist/scripts/lakebase/schema-diff.cli.js +12 -3
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +12 -3
- package/dist/scripts/lakebase/schema-migrate.cli.js +12 -3
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +12 -3
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +12 -3
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +12 -3
- package/dist/scripts/lakebase/scm-doctor.cli.js +12 -3
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2294 -2034
- package/dist/scripts/lakebase/scm-merge.cli.js +349 -89
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +12 -3
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +12 -3
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/scripts/lakebase/constants.ts +23 -5
- package/scripts/lakebase/doctor.ts +1 -1
- package/scripts/lakebase/get-connection.ts +17 -3
- package/scripts/lakebase/scm-merge.ts +80 -1
- package/skills/lakebase-scm-workflows/README.md +2 -1
- package/templates/project/nodejs/package.json +3 -1
- package/templates/project/nodejs/src/index.js +8 -0
- package/templates/project/nodejs/src/openapi.js +20 -0
- package/templates/project/nodejs/src/routes/health.js +18 -0
- package/templates/project/nodejs/tests/app.test.js +16 -0
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,
|
|
@@ -88905,6 +88906,7 @@ var RUNTIME_ARTIFACT_IGNORE = [
|
|
|
88905
88906
|
var DEFAULT_ENDPOINT = "primary";
|
|
88906
88907
|
var CONSORT_APPLICATION_NAME = "consort";
|
|
88907
88908
|
var SCM_UTILS_APPLICATION_NAME = "scm-utils";
|
|
88909
|
+
var SCM_CLIENT_ENV = "LAKEBASE_SCM_CLIENT";
|
|
88908
88910
|
var CONSORT_VERSION_ENV = "CONSORT_VERSION";
|
|
88909
88911
|
|
|
88910
88912
|
// scripts/lakebase/self-version.ts
|
|
@@ -88916,8 +88918,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
88916
88918
|
var cached;
|
|
88917
88919
|
function substrateSelfVersion() {
|
|
88918
88920
|
if (cached !== void 0) return cached;
|
|
88919
|
-
if ("0.2.
|
|
88920
|
-
cached = "0.2.
|
|
88921
|
+
if ("0.2.46".length > 0) {
|
|
88922
|
+
cached = "0.2.46";
|
|
88921
88923
|
return cached;
|
|
88922
88924
|
}
|
|
88923
88925
|
cached = "unknown";
|
|
@@ -88945,9 +88947,17 @@ function substrateSelfVersion() {
|
|
|
88945
88947
|
}
|
|
88946
88948
|
|
|
88947
88949
|
// scripts/lakebase/get-connection.ts
|
|
88950
|
+
function sanitizeApplicationName(raw) {
|
|
88951
|
+
const cleaned = raw.replace(/[^\x21-\x7e]+/g, "-").replace(/^-+|-+$/g, "");
|
|
88952
|
+
if (!cleaned) return `${SCM_UTILS_APPLICATION_NAME}/${substrateSelfVersion()}`;
|
|
88953
|
+
return cleaned.length > 63 ? cleaned.slice(0, 63) : cleaned;
|
|
88954
|
+
}
|
|
88948
88955
|
function connectionApplicationName() {
|
|
88956
|
+
const client = process.env[SCM_CLIENT_ENV]?.trim();
|
|
88957
|
+
if (client) return sanitizeApplicationName(client);
|
|
88949
88958
|
const consortVersion = process.env[CONSORT_VERSION_ENV]?.trim();
|
|
88950
|
-
|
|
88959
|
+
if (consortVersion) return `${CONSORT_APPLICATION_NAME}/${consortVersion}`;
|
|
88960
|
+
return `${SCM_UTILS_APPLICATION_NAME}/${substrateSelfVersion()}`;
|
|
88951
88961
|
}
|
|
88952
88962
|
async function getConnection(args) {
|
|
88953
88963
|
const endpointName = args.endpointName ?? DEFAULT_ENDPOINT;
|
|
@@ -95218,122 +95228,499 @@ function pickRunUrl(pr) {
|
|
|
95218
95228
|
|
|
95219
95229
|
// scripts/lakebase/scm-merge.ts
|
|
95220
95230
|
init_cjs_shims();
|
|
95221
|
-
|
|
95222
|
-
|
|
95231
|
+
|
|
95232
|
+
// scripts/git/sync.ts
|
|
95233
|
+
init_cjs_shims();
|
|
95234
|
+
async function currentBranchName(cwd) {
|
|
95223
95235
|
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).`;
|
|
95236
|
+
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
95237
|
+
} catch {
|
|
95238
|
+
return "";
|
|
95233
95239
|
}
|
|
95234
95240
|
}
|
|
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;
|
|
95241
|
+
async function push(args) {
|
|
95242
|
+
await exec2("git push", { cwd: args.cwd });
|
|
95251
95243
|
}
|
|
95252
|
-
function
|
|
95253
|
-
|
|
95254
|
-
if (run.event && run.event !== "push") return false;
|
|
95255
|
-
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
95256
|
-
};
|
|
95244
|
+
async function pull(args) {
|
|
95245
|
+
await exec2("git pull", { cwd: args.cwd });
|
|
95257
95246
|
}
|
|
95258
|
-
async function
|
|
95259
|
-
const
|
|
95260
|
-
|
|
95261
|
-
|
|
95262
|
-
|
|
95263
|
-
|
|
95264
|
-
|
|
95265
|
-
|
|
95266
|
-
|
|
95267
|
-
|
|
95268
|
-
|
|
95269
|
-
|
|
95270
|
-
|
|
95247
|
+
async function publishBranch(args) {
|
|
95248
|
+
const remote = args.remote ?? "origin";
|
|
95249
|
+
const branch = await currentBranchName(args.cwd);
|
|
95250
|
+
if (!branch) throw new Error("No current branch");
|
|
95251
|
+
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
95252
|
+
cwd: args.cwd
|
|
95253
|
+
});
|
|
95254
|
+
}
|
|
95255
|
+
async function pushCurrentBranchForPr(args) {
|
|
95256
|
+
const remote = args.remote ?? "origin";
|
|
95257
|
+
const branch = await currentBranchName(args.cwd);
|
|
95258
|
+
if (!branch) throw new Error("No current branch");
|
|
95259
|
+
const upstreamSet = await hasUpstream({ cwd: args.cwd });
|
|
95260
|
+
if (!upstreamSet) {
|
|
95261
|
+
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
95262
|
+
cwd: args.cwd
|
|
95263
|
+
});
|
|
95264
|
+
} else {
|
|
95265
|
+
await exec2("git push", { cwd: args.cwd });
|
|
95271
95266
|
}
|
|
95272
|
-
|
|
95273
|
-
|
|
95274
|
-
|
|
95275
|
-
|
|
95267
|
+
}
|
|
95268
|
+
async function fetch3(args) {
|
|
95269
|
+
const parts = ["git fetch"];
|
|
95270
|
+
if (args.prune) parts.push("--prune");
|
|
95271
|
+
if (args.all) parts.push("--all");
|
|
95272
|
+
await exec2(parts.join(" "), { cwd: args.cwd });
|
|
95273
|
+
}
|
|
95274
|
+
async function pullFrom(args) {
|
|
95275
|
+
await exec2(`git pull ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
95276
|
+
cwd: args.cwd
|
|
95277
|
+
});
|
|
95278
|
+
}
|
|
95279
|
+
async function pushTo(args) {
|
|
95280
|
+
await exec2(`git push ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
95281
|
+
cwd: args.cwd
|
|
95282
|
+
});
|
|
95283
|
+
}
|
|
95284
|
+
async function sync(args) {
|
|
95285
|
+
await exec2("git pull", { cwd: args.cwd });
|
|
95286
|
+
await exec2("git push", { cwd: args.cwd });
|
|
95287
|
+
}
|
|
95288
|
+
|
|
95289
|
+
// scripts/lakebase/workflow-drift.ts
|
|
95290
|
+
init_cjs_shims();
|
|
95291
|
+
var fs30 = __toESM(require("fs"), 1);
|
|
95292
|
+
var path28 = __toESM(require("path"), 1);
|
|
95293
|
+
function findKitTemplatesDir(start) {
|
|
95294
|
+
let dir = start;
|
|
95295
|
+
for (let i2 = 0; i2 < 6; i2++) {
|
|
95296
|
+
const candidate = path28.join(
|
|
95297
|
+
dir,
|
|
95298
|
+
"templates",
|
|
95299
|
+
"project",
|
|
95300
|
+
"common",
|
|
95301
|
+
".github",
|
|
95302
|
+
"workflows"
|
|
95276
95303
|
);
|
|
95304
|
+
if (fs30.existsSync(candidate)) return candidate;
|
|
95305
|
+
const parent = path28.dirname(dir);
|
|
95306
|
+
if (parent === dir) break;
|
|
95307
|
+
dir = parent;
|
|
95277
95308
|
}
|
|
95278
|
-
|
|
95279
|
-
|
|
95280
|
-
|
|
95281
|
-
|
|
95282
|
-
|
|
95309
|
+
throw new Error(
|
|
95310
|
+
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
95311
|
+
);
|
|
95312
|
+
}
|
|
95313
|
+
function unifiedDiff(name, projectContent, templateContent) {
|
|
95314
|
+
if (projectContent === templateContent) return "";
|
|
95315
|
+
const a = projectContent.split("\n");
|
|
95316
|
+
const b = templateContent.split("\n");
|
|
95317
|
+
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
95318
|
+
const max = Math.max(a.length, b.length);
|
|
95319
|
+
for (let i2 = 0; i2 < max; i2++) {
|
|
95320
|
+
const av = a[i2];
|
|
95321
|
+
const bv = b[i2];
|
|
95322
|
+
if (av === bv) continue;
|
|
95323
|
+
if (av !== void 0) out.push(`-${i2 + 1}: ${av}`);
|
|
95324
|
+
if (bv !== void 0) out.push(`+${i2 + 1}: ${bv}`);
|
|
95283
95325
|
}
|
|
95284
|
-
|
|
95285
|
-
|
|
95286
|
-
|
|
95287
|
-
|
|
95288
|
-
|
|
95289
|
-
|
|
95326
|
+
return out.join("\n");
|
|
95327
|
+
}
|
|
95328
|
+
function detectWorkflowDrift(args) {
|
|
95329
|
+
const projectWorkflowsDir = path28.join(
|
|
95330
|
+
args.projectDir,
|
|
95331
|
+
".github",
|
|
95332
|
+
"workflows"
|
|
95333
|
+
);
|
|
95334
|
+
const here = path28.dirname(new URL(importMetaUrl).pathname);
|
|
95335
|
+
const kitWorkflowsDir = args.kitDir ? path28.join(
|
|
95336
|
+
args.kitDir,
|
|
95337
|
+
"templates",
|
|
95338
|
+
"project",
|
|
95339
|
+
"common",
|
|
95340
|
+
".github",
|
|
95341
|
+
"workflows"
|
|
95342
|
+
) : findKitTemplatesDir(here);
|
|
95343
|
+
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
95344
|
+
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
95345
|
+
const version = readKitVersion(kitWorkflowsDir);
|
|
95346
|
+
const seen = /* @__PURE__ */ new Set();
|
|
95347
|
+
const files = [];
|
|
95348
|
+
for (const name of templateFiles) {
|
|
95349
|
+
seen.add(name);
|
|
95350
|
+
const projectPath2 = path28.join(projectWorkflowsDir, name);
|
|
95351
|
+
const templatePath = path28.join(kitWorkflowsDir, name);
|
|
95352
|
+
if (!fs30.existsSync(projectPath2)) {
|
|
95353
|
+
files.push({ name, status: "missing" });
|
|
95354
|
+
continue;
|
|
95355
|
+
}
|
|
95356
|
+
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
95357
|
+
const templateContent = applyPlaceholders(fs30.readFileSync(templatePath, "utf8"), version);
|
|
95358
|
+
if (projectContent === templateContent) {
|
|
95359
|
+
files.push({ name, status: "unchanged" });
|
|
95360
|
+
} else {
|
|
95361
|
+
files.push({
|
|
95362
|
+
name,
|
|
95363
|
+
status: "drifted",
|
|
95364
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
95365
|
+
});
|
|
95366
|
+
}
|
|
95290
95367
|
}
|
|
95291
|
-
const
|
|
95292
|
-
|
|
95293
|
-
|
|
95294
|
-
`Could not extract PR number from URL: ${current.pr_url}`,
|
|
95295
|
-
"bad-pr-url"
|
|
95296
|
-
);
|
|
95368
|
+
for (const name of projectFiles) {
|
|
95369
|
+
if (seen.has(name)) continue;
|
|
95370
|
+
files.push({ name, status: "extra" });
|
|
95297
95371
|
}
|
|
95298
|
-
const
|
|
95299
|
-
|
|
95300
|
-
|
|
95301
|
-
|
|
95302
|
-
|
|
95303
|
-
|
|
95304
|
-
|
|
95305
|
-
|
|
95306
|
-
|
|
95307
|
-
|
|
95308
|
-
|
|
95309
|
-
|
|
95310
|
-
|
|
95372
|
+
const order = {
|
|
95373
|
+
drifted: 0,
|
|
95374
|
+
missing: 1,
|
|
95375
|
+
extra: 2,
|
|
95376
|
+
unchanged: 3
|
|
95377
|
+
};
|
|
95378
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
95379
|
+
const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
|
|
95380
|
+
return {
|
|
95381
|
+
overall: hasDrift ? "drift" : "ok",
|
|
95382
|
+
files
|
|
95383
|
+
};
|
|
95384
|
+
}
|
|
95385
|
+
function readKitVersion(kitWorkflowsDir) {
|
|
95386
|
+
let dir = kitWorkflowsDir;
|
|
95387
|
+
for (let i2 = 0; i2 < 5; i2++) {
|
|
95388
|
+
dir = path28.dirname(dir);
|
|
95311
95389
|
}
|
|
95312
|
-
let paired;
|
|
95313
95390
|
try {
|
|
95314
|
-
|
|
95315
|
-
|
|
95316
|
-
|
|
95317
|
-
|
|
95318
|
-
|
|
95319
|
-
|
|
95320
|
-
|
|
95321
|
-
|
|
95322
|
-
|
|
95323
|
-
|
|
95391
|
+
const raw = fs30.readFileSync(path28.join(dir, "package.json"), "utf-8");
|
|
95392
|
+
const pkg = JSON.parse(raw);
|
|
95393
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
95394
|
+
} catch {
|
|
95395
|
+
return "unknown";
|
|
95396
|
+
}
|
|
95397
|
+
}
|
|
95398
|
+
function applyPlaceholders(content, version) {
|
|
95399
|
+
return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
|
|
95400
|
+
}
|
|
95401
|
+
function applyCommandPlaceholders(content, version) {
|
|
95402
|
+
return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
|
|
95403
|
+
}
|
|
95404
|
+
var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
|
|
95405
|
+
function findKitCommandsDir(start) {
|
|
95406
|
+
let dir = start;
|
|
95407
|
+
for (let i2 = 0; i2 < 6; i2++) {
|
|
95408
|
+
const candidate = path28.join(
|
|
95409
|
+
dir,
|
|
95410
|
+
"templates",
|
|
95411
|
+
"project",
|
|
95412
|
+
"common",
|
|
95413
|
+
".claude",
|
|
95414
|
+
"commands"
|
|
95324
95415
|
);
|
|
95416
|
+
if (fs30.existsSync(candidate)) return candidate;
|
|
95417
|
+
const parent = path28.dirname(dir);
|
|
95418
|
+
if (parent === dir) break;
|
|
95419
|
+
dir = parent;
|
|
95325
95420
|
}
|
|
95326
|
-
|
|
95327
|
-
|
|
95328
|
-
|
|
95329
|
-
|
|
95330
|
-
|
|
95331
|
-
|
|
95332
|
-
|
|
95333
|
-
|
|
95334
|
-
|
|
95335
|
-
|
|
95336
|
-
|
|
95421
|
+
throw new Error(
|
|
95422
|
+
`Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
|
|
95423
|
+
);
|
|
95424
|
+
}
|
|
95425
|
+
function parsePinnedVersion(content) {
|
|
95426
|
+
const m2 = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
|
|
95427
|
+
return m2 ? m2[1] : void 0;
|
|
95428
|
+
}
|
|
95429
|
+
function detectCommandDrift(args) {
|
|
95430
|
+
const projectCommandsDir = path28.join(args.projectDir, ".claude", "commands");
|
|
95431
|
+
const here = path28.dirname(new URL(importMetaUrl).pathname);
|
|
95432
|
+
const kitCommandsDir = args.kitDir ? path28.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
95433
|
+
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
95434
|
+
const templateFiles = fs30.existsSync(kitCommandsDir) ? fs30.readdirSync(kitCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
|
|
95435
|
+
const projectFiles = fs30.existsSync(projectCommandsDir) ? fs30.readdirSync(projectCommandsDir).filter((f3) => f3.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f3)) : [];
|
|
95436
|
+
const seen = /* @__PURE__ */ new Set();
|
|
95437
|
+
const files = [];
|
|
95438
|
+
for (const name of templateFiles) {
|
|
95439
|
+
seen.add(name);
|
|
95440
|
+
const projectPath2 = path28.join(projectCommandsDir, name);
|
|
95441
|
+
const templatePath = path28.join(kitCommandsDir, name);
|
|
95442
|
+
const templateRaw = fs30.readFileSync(templatePath, "utf8");
|
|
95443
|
+
if (!fs30.existsSync(projectPath2)) {
|
|
95444
|
+
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
95445
|
+
continue;
|
|
95446
|
+
}
|
|
95447
|
+
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
95448
|
+
const pinned = parsePinnedVersion(projectContent);
|
|
95449
|
+
const versionForCompare = pinned ?? kitVersion;
|
|
95450
|
+
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
95451
|
+
if (projectContent === templateContent) {
|
|
95452
|
+
files.push({
|
|
95453
|
+
name,
|
|
95454
|
+
status: "unchanged",
|
|
95455
|
+
pinned_version: pinned,
|
|
95456
|
+
kit_version: kitVersion
|
|
95457
|
+
});
|
|
95458
|
+
} else {
|
|
95459
|
+
files.push({
|
|
95460
|
+
name,
|
|
95461
|
+
status: "drifted",
|
|
95462
|
+
pinned_version: pinned,
|
|
95463
|
+
kit_version: kitVersion,
|
|
95464
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
95465
|
+
});
|
|
95466
|
+
}
|
|
95467
|
+
}
|
|
95468
|
+
for (const name of projectFiles) {
|
|
95469
|
+
if (seen.has(name)) continue;
|
|
95470
|
+
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
95471
|
+
}
|
|
95472
|
+
const order = {
|
|
95473
|
+
drifted: 0,
|
|
95474
|
+
missing: 1,
|
|
95475
|
+
extra: 2,
|
|
95476
|
+
unchanged: 3
|
|
95477
|
+
};
|
|
95478
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
95479
|
+
const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
|
|
95480
|
+
return { overall: hasDrift ? "drift" : "ok", files };
|
|
95481
|
+
}
|
|
95482
|
+
function readKitVersionFromCommandsDir(kitCommandsDir) {
|
|
95483
|
+
let dir = kitCommandsDir;
|
|
95484
|
+
for (let i2 = 0; i2 < 5; i2++) {
|
|
95485
|
+
dir = path28.dirname(dir);
|
|
95486
|
+
}
|
|
95487
|
+
try {
|
|
95488
|
+
const raw = fs30.readFileSync(path28.join(dir, "package.json"), "utf-8");
|
|
95489
|
+
const pkg = JSON.parse(raw);
|
|
95490
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
95491
|
+
} catch {
|
|
95492
|
+
return "unknown";
|
|
95493
|
+
}
|
|
95494
|
+
}
|
|
95495
|
+
function detectScaffoldedDrift(args) {
|
|
95496
|
+
const workflows = detectWorkflowDrift(args);
|
|
95497
|
+
const commands = detectCommandDrift(args);
|
|
95498
|
+
return {
|
|
95499
|
+
overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
|
|
95500
|
+
workflows,
|
|
95501
|
+
commands
|
|
95502
|
+
};
|
|
95503
|
+
}
|
|
95504
|
+
function updateWorkflows(args) {
|
|
95505
|
+
const projectWorkflowsDir = path28.join(
|
|
95506
|
+
args.projectDir,
|
|
95507
|
+
".github",
|
|
95508
|
+
"workflows"
|
|
95509
|
+
);
|
|
95510
|
+
const here = path28.dirname(new URL(importMetaUrl).pathname);
|
|
95511
|
+
const kitWorkflowsDir = args.kitDir ? path28.join(
|
|
95512
|
+
args.kitDir,
|
|
95513
|
+
"templates",
|
|
95514
|
+
"project",
|
|
95515
|
+
"common",
|
|
95516
|
+
".github",
|
|
95517
|
+
"workflows"
|
|
95518
|
+
) : findKitTemplatesDir(here);
|
|
95519
|
+
const substitute = args.substitute !== false;
|
|
95520
|
+
const dryRun = args.dryRun === true;
|
|
95521
|
+
const pruneExtras = args.pruneExtras === true;
|
|
95522
|
+
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
95523
|
+
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f3) => f3.endsWith(".yml")) : [];
|
|
95524
|
+
if (!dryRun && templateFiles.length > 0 && !fs30.existsSync(projectWorkflowsDir)) {
|
|
95525
|
+
fs30.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
95526
|
+
}
|
|
95527
|
+
const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
|
|
95528
|
+
const seen = /* @__PURE__ */ new Set();
|
|
95529
|
+
const files = [];
|
|
95530
|
+
for (const name of templateFiles) {
|
|
95531
|
+
seen.add(name);
|
|
95532
|
+
const projectPath2 = path28.join(projectWorkflowsDir, name);
|
|
95533
|
+
const templatePath = path28.join(kitWorkflowsDir, name);
|
|
95534
|
+
const templateRaw = fs30.readFileSync(templatePath, "utf-8");
|
|
95535
|
+
const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
|
|
95536
|
+
const existed = fs30.existsSync(projectPath2);
|
|
95537
|
+
const current = existed ? fs30.readFileSync(projectPath2, "utf-8") : "";
|
|
95538
|
+
let outcome;
|
|
95539
|
+
if (!existed) {
|
|
95540
|
+
outcome = "added";
|
|
95541
|
+
} else if (current === desired) {
|
|
95542
|
+
outcome = "unchanged";
|
|
95543
|
+
} else {
|
|
95544
|
+
outcome = "updated";
|
|
95545
|
+
}
|
|
95546
|
+
if (!dryRun && outcome !== "unchanged") {
|
|
95547
|
+
fs30.writeFileSync(projectPath2, desired);
|
|
95548
|
+
}
|
|
95549
|
+
files.push({ name, outcome });
|
|
95550
|
+
}
|
|
95551
|
+
if (pruneExtras) {
|
|
95552
|
+
for (const name of projectFiles) {
|
|
95553
|
+
if (seen.has(name)) continue;
|
|
95554
|
+
const projectPath2 = path28.join(projectWorkflowsDir, name);
|
|
95555
|
+
if (!dryRun) {
|
|
95556
|
+
fs30.unlinkSync(projectPath2);
|
|
95557
|
+
}
|
|
95558
|
+
files.push({ name, outcome: "removed" });
|
|
95559
|
+
}
|
|
95560
|
+
}
|
|
95561
|
+
const order = {
|
|
95562
|
+
added: 0,
|
|
95563
|
+
updated: 1,
|
|
95564
|
+
removed: 2,
|
|
95565
|
+
unchanged: 3
|
|
95566
|
+
};
|
|
95567
|
+
files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
|
|
95568
|
+
const changed = files.some((f3) => f3.outcome !== "unchanged");
|
|
95569
|
+
return { files, changed };
|
|
95570
|
+
}
|
|
95571
|
+
|
|
95572
|
+
// scripts/lakebase/scm-merge.ts
|
|
95573
|
+
async function reconcileTierToOrigin(args) {
|
|
95574
|
+
const { cwd, tier } = args;
|
|
95575
|
+
try {
|
|
95576
|
+
const head = await getCurrentBranch({ cwd });
|
|
95577
|
+
if (head === tier) {
|
|
95578
|
+
await exec2(`git pull --ff-only`, { cwd, timeout: 3e4 });
|
|
95579
|
+
} else {
|
|
95580
|
+
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
95581
|
+
}
|
|
95582
|
+
return null;
|
|
95583
|
+
} catch (err) {
|
|
95584
|
+
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).`;
|
|
95585
|
+
}
|
|
95586
|
+
}
|
|
95587
|
+
var ScmMergeError = class extends Error {
|
|
95588
|
+
constructor(message, code) {
|
|
95589
|
+
super(message);
|
|
95590
|
+
this.code = code;
|
|
95591
|
+
this.name = "ScmMergeError";
|
|
95592
|
+
}
|
|
95593
|
+
code;
|
|
95594
|
+
};
|
|
95595
|
+
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
95596
|
+
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
95597
|
+
function defaultMigratePredicate(run, mergedAt) {
|
|
95598
|
+
if (!run.createdAt) return false;
|
|
95599
|
+
const created = Date.parse(run.createdAt);
|
|
95600
|
+
if (!Number.isFinite(created)) return false;
|
|
95601
|
+
if (run.event && run.event !== "push") return false;
|
|
95602
|
+
return created >= mergedAt.getTime() - 5e3;
|
|
95603
|
+
}
|
|
95604
|
+
function shaMigratePredicate(mergeCommitSha) {
|
|
95605
|
+
return (run) => {
|
|
95606
|
+
if (run.event && run.event !== "push") return false;
|
|
95607
|
+
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
95608
|
+
};
|
|
95609
|
+
}
|
|
95610
|
+
async function defaultRefreshPromoteWorkflows(projectDir) {
|
|
95611
|
+
const drift = detectWorkflowDrift({ projectDir });
|
|
95612
|
+
const touched = drift.files.filter((f3) => f3.status === "drifted").map((f3) => f3.name);
|
|
95613
|
+
if (touched.length === 0) return { refreshed: false };
|
|
95614
|
+
const result = updateWorkflows({ projectDir });
|
|
95615
|
+
if (!result.changed) return { refreshed: false };
|
|
95616
|
+
await exec2(`git add ${shellEscape2(".github/workflows")}`, { cwd: projectDir, timeout: 1e4 });
|
|
95617
|
+
const staged = await exec2("git diff --cached --name-only -- .github/workflows", {
|
|
95618
|
+
cwd: projectDir,
|
|
95619
|
+
timeout: 1e4
|
|
95620
|
+
});
|
|
95621
|
+
if (!staged.trim()) return { refreshed: false };
|
|
95622
|
+
await exec2(
|
|
95623
|
+
`git commit -m ${shellEscape2(
|
|
95624
|
+
"chore(ci): refresh scaffolded workflows to pinned substrate (promote uses apply-tier)"
|
|
95625
|
+
)} -- .github/workflows`,
|
|
95626
|
+
{ cwd: projectDir, timeout: 15e3 }
|
|
95627
|
+
);
|
|
95628
|
+
await pushCurrentBranchForPr({ cwd: projectDir });
|
|
95629
|
+
return { refreshed: true, detail: `refreshed + pushed ${touched.join(", ") || "workflow(s)"} onto the PR head` };
|
|
95630
|
+
}
|
|
95631
|
+
async function mergeFeature(args) {
|
|
95632
|
+
const current = readWorkflowState(args.projectDir);
|
|
95633
|
+
if (!current) {
|
|
95634
|
+
throw new ScmMergeError(
|
|
95635
|
+
"No SCM workflow state. wait-ci first.",
|
|
95636
|
+
"no-state-file"
|
|
95637
|
+
);
|
|
95638
|
+
}
|
|
95639
|
+
if (current.state !== "ci-green") {
|
|
95640
|
+
throw new ScmMergeError(
|
|
95641
|
+
`merge refuses state "${current.state}". Allowed predecessor: ci-green.`,
|
|
95642
|
+
"bad-precondition"
|
|
95643
|
+
);
|
|
95644
|
+
}
|
|
95645
|
+
if (!current.pr_url) {
|
|
95646
|
+
throw new ScmMergeError(
|
|
95647
|
+
"ci-green row is missing pr_url; cannot resolve the PR to merge.",
|
|
95648
|
+
"no-pr-url"
|
|
95649
|
+
);
|
|
95650
|
+
}
|
|
95651
|
+
if (!current.branch || !current.parent_branch) {
|
|
95652
|
+
throw new ScmMergeError(
|
|
95653
|
+
"ci-green row missing branch / parent_branch; refusing to merge.",
|
|
95654
|
+
"bad-precondition"
|
|
95655
|
+
);
|
|
95656
|
+
}
|
|
95657
|
+
const ownerRepo = await getOwnerRepo(args.projectDir);
|
|
95658
|
+
if (!ownerRepo) {
|
|
95659
|
+
throw new ScmMergeError(
|
|
95660
|
+
"No GitHub remote found at origin.",
|
|
95661
|
+
"no-github-remote"
|
|
95662
|
+
);
|
|
95663
|
+
}
|
|
95664
|
+
const pullNumber = extractPullNumber(current.pr_url);
|
|
95665
|
+
if (!pullNumber) {
|
|
95666
|
+
throw new ScmMergeError(
|
|
95667
|
+
`Could not extract PR number from URL: ${current.pr_url}`,
|
|
95668
|
+
"bad-pr-url"
|
|
95669
|
+
);
|
|
95670
|
+
}
|
|
95671
|
+
const instance = args.instance ?? current.project_id;
|
|
95672
|
+
const gitBase = await resolveGitBase(current.parent_branch, args.projectDir);
|
|
95673
|
+
const wantMigrate = args.waitMigrate !== false;
|
|
95674
|
+
let authVerified = false;
|
|
95675
|
+
if (wantMigrate && args.verifyMigrateAuth) {
|
|
95676
|
+
const probe = await args.verifyMigrateAuth();
|
|
95677
|
+
if (!probe.ok) {
|
|
95678
|
+
throw new ScmMergeError(
|
|
95679
|
+
`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.`,
|
|
95680
|
+
"migrate-auth"
|
|
95681
|
+
);
|
|
95682
|
+
}
|
|
95683
|
+
authVerified = true;
|
|
95684
|
+
}
|
|
95685
|
+
const preflightNotes = [];
|
|
95686
|
+
{
|
|
95687
|
+
const refresh = args.refreshWorkflows ?? (() => defaultRefreshPromoteWorkflows(args.projectDir));
|
|
95688
|
+
try {
|
|
95689
|
+
const r2 = await refresh();
|
|
95690
|
+
if (r2.refreshed) {
|
|
95691
|
+
preflightNotes.push(`Refreshed stale CI workflows before merge${r2.detail ? ` (${r2.detail})` : ""}.`);
|
|
95692
|
+
}
|
|
95693
|
+
} catch (err) {
|
|
95694
|
+
preflightNotes.push(
|
|
95695
|
+
`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').`
|
|
95696
|
+
);
|
|
95697
|
+
}
|
|
95698
|
+
}
|
|
95699
|
+
let paired;
|
|
95700
|
+
try {
|
|
95701
|
+
paired = await mergePairedPullRequest({
|
|
95702
|
+
ownerRepo,
|
|
95703
|
+
pullNumber,
|
|
95704
|
+
lakebaseInstance: instance,
|
|
95705
|
+
method: args.method ?? "squash"
|
|
95706
|
+
});
|
|
95707
|
+
} catch (err) {
|
|
95708
|
+
throw new ScmMergeError(
|
|
95709
|
+
`mergePairedPullRequest failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
95710
|
+
"merge-failed"
|
|
95711
|
+
);
|
|
95712
|
+
}
|
|
95713
|
+
const warnings = [...preflightNotes, ...paired.warnings];
|
|
95714
|
+
let localBranchDeleted = false;
|
|
95715
|
+
let headAfter = current.branch;
|
|
95716
|
+
if (!args.skipLocalCleanup) {
|
|
95717
|
+
const switchTo = args.switchTo ?? gitBase;
|
|
95718
|
+
const head = await getCurrentBranch({ cwd: args.projectDir });
|
|
95719
|
+
if (head === current.branch) {
|
|
95720
|
+
try {
|
|
95721
|
+
await exec2(`git checkout ${shellEscape2(switchTo)}`, {
|
|
95722
|
+
cwd: args.projectDir,
|
|
95723
|
+
timeout: 1e4
|
|
95337
95724
|
});
|
|
95338
95725
|
headAfter = switchTo;
|
|
95339
95726
|
try {
|
|
@@ -95520,7 +95907,7 @@ init_cjs_shims();
|
|
|
95520
95907
|
|
|
95521
95908
|
// scripts/git/branches.ts
|
|
95522
95909
|
init_cjs_shims();
|
|
95523
|
-
async function
|
|
95910
|
+
async function currentBranchName2(cwd) {
|
|
95524
95911
|
try {
|
|
95525
95912
|
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
95526
95913
|
} catch {
|
|
@@ -95539,7 +95926,7 @@ async function listLocalBranches(args) {
|
|
|
95539
95926
|
return [];
|
|
95540
95927
|
}
|
|
95541
95928
|
if (!raw) return [];
|
|
95542
|
-
const current = await
|
|
95929
|
+
const current = await currentBranchName2(cwd);
|
|
95543
95930
|
return raw.split("\n").filter(Boolean).map((line) => {
|
|
95544
95931
|
const [name, tracking, trackInfo] = line.split("|");
|
|
95545
95932
|
let ahead = 0;
|
|
@@ -95739,15 +96126,15 @@ function parentForTopology(t2, defaultLeaf) {
|
|
|
95739
96126
|
|
|
95740
96127
|
// scripts/lakebase/scm-doctor.ts
|
|
95741
96128
|
init_cjs_shims();
|
|
95742
|
-
var
|
|
95743
|
-
var
|
|
96129
|
+
var fs31 = __toESM(require("fs"), 1);
|
|
96130
|
+
var path29 = __toESM(require("path"), 1);
|
|
95744
96131
|
var FEATURE_PREFIX = "feature/";
|
|
95745
96132
|
var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
|
|
95746
96133
|
function readEnv(projectDir) {
|
|
95747
|
-
const envPath =
|
|
96134
|
+
const envPath = path29.join(projectDir, ".env");
|
|
95748
96135
|
const out = /* @__PURE__ */ new Map();
|
|
95749
|
-
if (!
|
|
95750
|
-
const lines =
|
|
96136
|
+
if (!fs31.existsSync(envPath)) return out;
|
|
96137
|
+
const lines = fs31.readFileSync(envPath, "utf8").split("\n");
|
|
95751
96138
|
for (const line of lines) {
|
|
95752
96139
|
const m2 = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
|
|
95753
96140
|
if (m2) out.set(m2[1], m2[2].replace(/^["']|["']$/g, ""));
|
|
@@ -95802,9 +96189,9 @@ async function runDoctor(args, deps = {}) {
|
|
|
95802
96189
|
}
|
|
95803
96190
|
for (const wf of ["pr.yml", "merge.yml"]) {
|
|
95804
96191
|
try {
|
|
95805
|
-
const p =
|
|
95806
|
-
if (!
|
|
95807
|
-
const body =
|
|
96192
|
+
const p = path29.join(projectDir, ".github", "workflows", wf);
|
|
96193
|
+
if (!fs31.existsSync(p)) continue;
|
|
96194
|
+
const body = fs31.readFileSync(p, "utf8");
|
|
95808
96195
|
if (/lakebase-scm-utils#v\d/.test(body)) {
|
|
95809
96196
|
findings.push({
|
|
95810
96197
|
id: "ci-workflow-substrate-pin",
|
|
@@ -95966,449 +96353,164 @@ async function runDoctor(args, deps = {}) {
|
|
|
95966
96353
|
workflowStatePresent,
|
|
95967
96354
|
state: state ?? void 0,
|
|
95968
96355
|
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;
|
|
96194
|
-
}
|
|
96195
|
-
const projectContent = fs31.readFileSync(projectPath2, "utf8");
|
|
96196
|
-
const templateContent = applyPlaceholders(fs31.readFileSync(templatePath, "utf8"), version);
|
|
96197
|
-
if (projectContent === templateContent) {
|
|
96198
|
-
files.push({ name, status: "unchanged" });
|
|
96199
|
-
} else {
|
|
96200
|
-
files.push({
|
|
96201
|
-
name,
|
|
96202
|
-
status: "drifted",
|
|
96203
|
-
diff: unifiedDiff(name, projectContent, templateContent)
|
|
96204
|
-
});
|
|
96205
|
-
}
|
|
96206
|
-
}
|
|
96207
|
-
for (const name of projectFiles) {
|
|
96208
|
-
if (seen.has(name)) continue;
|
|
96209
|
-
files.push({ name, status: "extra" });
|
|
96210
|
-
}
|
|
96211
|
-
const order = {
|
|
96212
|
-
drifted: 0,
|
|
96213
|
-
missing: 1,
|
|
96214
|
-
extra: 2,
|
|
96215
|
-
unchanged: 3
|
|
96216
|
-
};
|
|
96217
|
-
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
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);
|
|
96228
|
-
}
|
|
96229
|
-
try {
|
|
96230
|
-
const raw = fs31.readFileSync(path29.join(dir, "package.json"), "utf-8");
|
|
96231
|
-
const pkg = JSON.parse(raw);
|
|
96232
|
-
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
96233
|
-
} catch {
|
|
96234
|
-
return "unknown";
|
|
96235
|
-
}
|
|
96236
|
-
}
|
|
96237
|
-
function applyPlaceholders(content, version) {
|
|
96238
|
-
return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
|
|
96239
|
-
}
|
|
96240
|
-
function applyCommandPlaceholders(content, version) {
|
|
96241
|
-
return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
|
|
96242
|
-
}
|
|
96243
|
-
var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
|
|
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;
|
|
96259
|
-
}
|
|
96260
|
-
throw new Error(
|
|
96261
|
-
`Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
|
|
96262
|
-
);
|
|
96263
|
-
}
|
|
96264
|
-
function parsePinnedVersion(content) {
|
|
96265
|
-
const m2 = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
|
|
96266
|
-
return m2 ? m2[1] : void 0;
|
|
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)
|
|
96304
|
-
});
|
|
96305
|
-
}
|
|
96306
|
-
}
|
|
96307
|
-
for (const name of projectFiles) {
|
|
96308
|
-
if (seen.has(name)) continue;
|
|
96309
|
-
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
96310
|
-
}
|
|
96311
|
-
const order = {
|
|
96312
|
-
drifted: 0,
|
|
96313
|
-
missing: 1,
|
|
96314
|
-
extra: 2,
|
|
96315
|
-
unchanged: 3
|
|
96316
|
-
};
|
|
96317
|
-
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
96318
|
-
const hasDrift = files.some((f3) => f3.status === "drifted" || f3.status === "missing");
|
|
96319
|
-
return { overall: hasDrift ? "drift" : "ok", files };
|
|
96320
|
-
}
|
|
96321
|
-
function readKitVersionFromCommandsDir(kitCommandsDir) {
|
|
96322
|
-
let dir = kitCommandsDir;
|
|
96323
|
-
for (let i2 = 0; i2 < 5; i2++) {
|
|
96324
|
-
dir = path29.dirname(dir);
|
|
96325
|
-
}
|
|
96326
|
-
try {
|
|
96327
|
-
const raw = fs31.readFileSync(path29.join(dir, "package.json"), "utf-8");
|
|
96328
|
-
const pkg = JSON.parse(raw);
|
|
96329
|
-
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
96330
|
-
} catch {
|
|
96331
|
-
return "unknown";
|
|
96332
|
-
}
|
|
96356
|
+
findings
|
|
96357
|
+
});
|
|
96333
96358
|
}
|
|
96334
|
-
function
|
|
96335
|
-
|
|
96336
|
-
const
|
|
96337
|
-
|
|
96338
|
-
|
|
96339
|
-
|
|
96340
|
-
commands
|
|
96341
|
-
};
|
|
96359
|
+
function finalize(report) {
|
|
96360
|
+
let worst = "ok";
|
|
96361
|
+
for (const f3 of report.findings) {
|
|
96362
|
+
worst = worstOf(worst, f3.severity);
|
|
96363
|
+
}
|
|
96364
|
+
return { ...report, worstSeverity: worst };
|
|
96342
96365
|
}
|
|
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 });
|
|
96366
|
+
var ScmDoctorFixError = class extends Error {
|
|
96367
|
+
constructor(message, code) {
|
|
96368
|
+
super(message);
|
|
96369
|
+
this.code = code;
|
|
96370
|
+
this.name = "ScmDoctorFixError";
|
|
96365
96371
|
}
|
|
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 });
|
|
96372
|
+
code;
|
|
96373
|
+
};
|
|
96374
|
+
var FIXABLE_FINDING_IDS = [
|
|
96375
|
+
"env-branch-drift",
|
|
96376
|
+
"head-branch-drift",
|
|
96377
|
+
"tier-topology-mismatch",
|
|
96378
|
+
"orphan-current-branch",
|
|
96379
|
+
"multiple-migration-heads",
|
|
96380
|
+
"db-ahead-of-code"
|
|
96381
|
+
];
|
|
96382
|
+
async function fixFinding(args) {
|
|
96383
|
+
if (!FIXABLE_FINDING_IDS.includes(args.findingId)) {
|
|
96384
|
+
throw new ScmDoctorFixError(
|
|
96385
|
+
`Finding "${args.findingId}" is not supported by --fix. Supported: ${FIXABLE_FINDING_IDS.join(", ")}.`,
|
|
96386
|
+
"unsupported-finding"
|
|
96387
|
+
);
|
|
96389
96388
|
}
|
|
96390
|
-
|
|
96391
|
-
|
|
96392
|
-
|
|
96393
|
-
|
|
96394
|
-
|
|
96395
|
-
|
|
96389
|
+
const report = args.report ?? await runDoctor({ projectDir: args.projectDir, instance: args.instance });
|
|
96390
|
+
const present = report.findings.find((f3) => f3.id === args.findingId);
|
|
96391
|
+
if (!present) {
|
|
96392
|
+
throw new ScmDoctorFixError(
|
|
96393
|
+
`Finding "${args.findingId}" is not present in the current report. Re-run lakebase-scm-doctor to see what needs fixing.`,
|
|
96394
|
+
"finding-not-present"
|
|
96395
|
+
);
|
|
96396
|
+
}
|
|
96397
|
+
let action = "";
|
|
96398
|
+
try {
|
|
96399
|
+
switch (args.findingId) {
|
|
96400
|
+
case "env-branch-drift": {
|
|
96401
|
+
const branch = report.state?.branch;
|
|
96402
|
+
if (!branch) {
|
|
96403
|
+
throw new ScmDoctorFixError(
|
|
96404
|
+
"Cannot fix: workflow state has no branch field.",
|
|
96405
|
+
"fix-failed"
|
|
96406
|
+
);
|
|
96407
|
+
}
|
|
96408
|
+
const sanitized = sanitizeBranchName(branch);
|
|
96409
|
+
const envFile = path29.join(args.projectDir, ".env");
|
|
96410
|
+
updateEnvConnection({
|
|
96411
|
+
envPath: envFile,
|
|
96412
|
+
projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
|
|
96413
|
+
branchId: sanitized,
|
|
96414
|
+
username: readEnvVar(envFile, "DB_USERNAME") ?? "",
|
|
96415
|
+
endpointHost: readEnvVar(envFile, "LAKEBASE_HOST")
|
|
96416
|
+
});
|
|
96417
|
+
action = `rewrote .env LAKEBASE_BRANCH_ID=${sanitized} (metadata only; the app mints its token at runtime)`;
|
|
96418
|
+
break;
|
|
96419
|
+
}
|
|
96420
|
+
case "head-branch-drift": {
|
|
96421
|
+
const branch = report.state?.branch;
|
|
96422
|
+
if (!branch) {
|
|
96423
|
+
throw new ScmDoctorFixError(
|
|
96424
|
+
"Cannot fix: workflow state has no branch field.",
|
|
96425
|
+
"fix-failed"
|
|
96426
|
+
);
|
|
96427
|
+
}
|
|
96428
|
+
await exec2(`git checkout ${shellEscape3(branch)}`, {
|
|
96429
|
+
cwd: args.projectDir,
|
|
96430
|
+
timeout: 15e3
|
|
96431
|
+
});
|
|
96432
|
+
action = `git checkout ${branch} (re-fires post-checkout to resync HEAD)`;
|
|
96433
|
+
break;
|
|
96434
|
+
}
|
|
96435
|
+
case "tier-topology-mismatch": {
|
|
96436
|
+
const instance = args.instance ?? report.state?.project_id;
|
|
96437
|
+
if (!instance) {
|
|
96438
|
+
throw new ScmDoctorFixError(
|
|
96439
|
+
"Cannot fix: missing Lakebase project id.",
|
|
96440
|
+
"fix-failed"
|
|
96441
|
+
);
|
|
96442
|
+
}
|
|
96443
|
+
await adoptScmState({
|
|
96444
|
+
projectDir: args.projectDir,
|
|
96445
|
+
instance,
|
|
96446
|
+
force: true
|
|
96447
|
+
});
|
|
96448
|
+
action = `adopted state with --force to re-infer tier_topology`;
|
|
96449
|
+
break;
|
|
96450
|
+
}
|
|
96451
|
+
case "orphan-current-branch": {
|
|
96452
|
+
const instance = args.instance ?? report.state?.project_id;
|
|
96453
|
+
if (!instance) {
|
|
96454
|
+
throw new ScmDoctorFixError(
|
|
96455
|
+
"Cannot fix: missing Lakebase project id.",
|
|
96456
|
+
"fix-failed"
|
|
96457
|
+
);
|
|
96458
|
+
}
|
|
96459
|
+
const headBranch = await getCurrentBranch({ cwd: args.projectDir });
|
|
96460
|
+
if (!headBranch) {
|
|
96461
|
+
throw new ScmDoctorFixError(
|
|
96462
|
+
"Cannot fix: detached HEAD or no current branch.",
|
|
96463
|
+
"fix-failed"
|
|
96464
|
+
);
|
|
96465
|
+
}
|
|
96466
|
+
await recoverOrphans({
|
|
96467
|
+
projectDir: args.projectDir,
|
|
96468
|
+
instance,
|
|
96469
|
+
claim: true,
|
|
96470
|
+
onlyBranch: headBranch
|
|
96471
|
+
});
|
|
96472
|
+
action = `recovered orphan ${headBranch} via createFeaturePairedBranch`;
|
|
96473
|
+
break;
|
|
96474
|
+
}
|
|
96475
|
+
case "multiple-migration-heads": {
|
|
96476
|
+
const r2 = await collapseMigrationHeads({ projectDir: args.projectDir });
|
|
96477
|
+
if (r2.status !== "ok" || !r2.mergeRevision) {
|
|
96478
|
+
throw new ScmDoctorFixError(
|
|
96479
|
+
`Expected to create a merge revision but got status="${r2.status}".`,
|
|
96480
|
+
"fix-failed"
|
|
96481
|
+
);
|
|
96482
|
+
}
|
|
96483
|
+
action = `collapsed ${r2.headsBefore.length} heads into merge revision ${r2.mergeRevision} (commit it)`;
|
|
96484
|
+
break;
|
|
96485
|
+
}
|
|
96486
|
+
case "db-ahead-of-code": {
|
|
96487
|
+
const r2 = await abandonFeatureBranch({ projectDir: args.projectDir, instance: args.instance, force: true });
|
|
96488
|
+
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`;
|
|
96489
|
+
break;
|
|
96396
96490
|
}
|
|
96397
|
-
files.push({ name, outcome: "removed" });
|
|
96398
96491
|
}
|
|
96492
|
+
} catch (err) {
|
|
96493
|
+
if (err instanceof ScmDoctorFixError) throw err;
|
|
96494
|
+
throw new ScmDoctorFixError(
|
|
96495
|
+
`Remediation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
96496
|
+
"fix-failed"
|
|
96497
|
+
);
|
|
96399
96498
|
}
|
|
96400
|
-
const
|
|
96401
|
-
|
|
96402
|
-
|
|
96403
|
-
|
|
96404
|
-
|
|
96405
|
-
|
|
96406
|
-
|
|
96407
|
-
|
|
96408
|
-
return { files, changed };
|
|
96499
|
+
const postReport = await runDoctor({
|
|
96500
|
+
projectDir: args.projectDir,
|
|
96501
|
+
instance: args.instance
|
|
96502
|
+
});
|
|
96503
|
+
return { findingId: args.findingId, action, postReport };
|
|
96504
|
+
}
|
|
96505
|
+
function shellEscape3(s2) {
|
|
96506
|
+
return `'${s2.replace(/'/g, "'\\''")}'`;
|
|
96409
96507
|
}
|
|
96410
96508
|
|
|
96411
96509
|
// scripts/lakebase/doctor.ts
|
|
96510
|
+
init_cjs_shims();
|
|
96511
|
+
var fs32 = __toESM(require("fs"), 1);
|
|
96512
|
+
var path30 = __toESM(require("path"), 1);
|
|
96513
|
+
var cp6 = __toESM(require("child_process"), 1);
|
|
96412
96514
|
function readEnvFile(projectDir) {
|
|
96413
96515
|
const envPath = path30.join(projectDir, ".env");
|
|
96414
96516
|
if (!fs32.existsSync(envPath)) return {};
|
|
@@ -96841,7 +96943,7 @@ function checkWorkflowDrift(projectDir) {
|
|
|
96841
96943
|
status: "warn",
|
|
96842
96944
|
message: `Scaffolded workflows drift from kit: ${drifted} drifted, ${missing} missing`,
|
|
96843
96945
|
detail: { files: report.files.map((f3) => ({ name: f3.name, status: f3.status })) },
|
|
96844
|
-
hint: "
|
|
96946
|
+
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
96947
|
};
|
|
96846
96948
|
} catch (err) {
|
|
96847
96949
|
return {
|
|
@@ -97317,63 +97419,6 @@ async function discardAllChanges(args) {
|
|
|
97317
97419
|
await exec2("git clean -fd", { cwd: args.cwd });
|
|
97318
97420
|
}
|
|
97319
97421
|
|
|
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
97422
|
// scripts/git/branch-tag.ts
|
|
97378
97423
|
init_cjs_shims();
|
|
97379
97424
|
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["production", "main", "master"]);
|
|
@@ -97779,6 +97824,7 @@ function withProxyEnv(base = {}) {
|
|
|
97779
97824
|
createWorktree,
|
|
97780
97825
|
cutBackup,
|
|
97781
97826
|
databricksAuthPrereqMessage,
|
|
97827
|
+
defaultRefreshPromoteWorkflows,
|
|
97782
97828
|
delay,
|
|
97783
97829
|
deleteAppEndpoint,
|
|
97784
97830
|
deleteBranch,
|