@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
|
@@ -2506,6 +2506,7 @@ var RUNTIME_ARTIFACT_IGNORE = [
|
|
|
2506
2506
|
var DEFAULT_ENDPOINT = "primary";
|
|
2507
2507
|
var CONSORT_APPLICATION_NAME = "consort";
|
|
2508
2508
|
var SCM_UTILS_APPLICATION_NAME = "scm-utils";
|
|
2509
|
+
var SCM_CLIENT_ENV = "LAKEBASE_SCM_CLIENT";
|
|
2509
2510
|
var CONSORT_VERSION_ENV = "CONSORT_VERSION";
|
|
2510
2511
|
|
|
2511
2512
|
// scripts/lakebase/self-version.ts
|
|
@@ -2517,8 +2518,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
2517
2518
|
var cached;
|
|
2518
2519
|
function substrateSelfVersion() {
|
|
2519
2520
|
if (cached !== void 0) return cached;
|
|
2520
|
-
if ("0.2.
|
|
2521
|
-
cached = "0.2.
|
|
2521
|
+
if ("0.2.46".length > 0) {
|
|
2522
|
+
cached = "0.2.46";
|
|
2522
2523
|
return cached;
|
|
2523
2524
|
}
|
|
2524
2525
|
cached = "unknown";
|
|
@@ -2546,9 +2547,17 @@ function substrateSelfVersion() {
|
|
|
2546
2547
|
}
|
|
2547
2548
|
|
|
2548
2549
|
// scripts/lakebase/get-connection.ts
|
|
2550
|
+
function sanitizeApplicationName(raw) {
|
|
2551
|
+
const cleaned = raw.replace(/[^\x21-\x7e]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2552
|
+
if (!cleaned) return `${SCM_UTILS_APPLICATION_NAME}/${substrateSelfVersion()}`;
|
|
2553
|
+
return cleaned.length > 63 ? cleaned.slice(0, 63) : cleaned;
|
|
2554
|
+
}
|
|
2549
2555
|
function connectionApplicationName() {
|
|
2556
|
+
const client = process.env[SCM_CLIENT_ENV]?.trim();
|
|
2557
|
+
if (client) return sanitizeApplicationName(client);
|
|
2550
2558
|
const consortVersion = process.env[CONSORT_VERSION_ENV]?.trim();
|
|
2551
|
-
|
|
2559
|
+
if (consortVersion) return `${CONSORT_APPLICATION_NAME}/${consortVersion}`;
|
|
2560
|
+
return `${SCM_UTILS_APPLICATION_NAME}/${substrateSelfVersion()}`;
|
|
2552
2561
|
}
|
|
2553
2562
|
async function getConnection(args) {
|
|
2554
2563
|
const endpointName = args.endpointName ?? DEFAULT_ENDPOINT;
|
|
@@ -2746,6 +2755,14 @@ async function getCredential(args) {
|
|
|
2746
2755
|
|
|
2747
2756
|
// scripts/git/status.ts
|
|
2748
2757
|
init_esm_shims();
|
|
2758
|
+
async function hasUpstream(args) {
|
|
2759
|
+
try {
|
|
2760
|
+
await exec2("git rev-parse --abbrev-ref @{u}", { cwd: args.cwd });
|
|
2761
|
+
return true;
|
|
2762
|
+
} catch {
|
|
2763
|
+
return false;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2749
2766
|
async function getAheadBehind(args) {
|
|
2750
2767
|
const { cwd } = args;
|
|
2751
2768
|
try {
|
|
@@ -15849,117 +15866,460 @@ function pickRunUrl(pr) {
|
|
|
15849
15866
|
|
|
15850
15867
|
// scripts/lakebase/scm-merge.ts
|
|
15851
15868
|
init_esm_shims();
|
|
15852
|
-
|
|
15853
|
-
|
|
15869
|
+
|
|
15870
|
+
// scripts/git/sync.ts
|
|
15871
|
+
init_esm_shims();
|
|
15872
|
+
async function currentBranchName(cwd) {
|
|
15854
15873
|
try {
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
} else {
|
|
15859
|
-
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
15860
|
-
}
|
|
15861
|
-
return null;
|
|
15862
|
-
} catch (err) {
|
|
15863
|
-
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).`;
|
|
15874
|
+
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
15875
|
+
} catch {
|
|
15876
|
+
return "";
|
|
15864
15877
|
}
|
|
15865
15878
|
}
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15879
|
+
async function pushCurrentBranchForPr(args) {
|
|
15880
|
+
const remote = args.remote ?? "origin";
|
|
15881
|
+
const branch = await currentBranchName(args.cwd);
|
|
15882
|
+
if (!branch) throw new Error("No current branch");
|
|
15883
|
+
const upstreamSet = await hasUpstream({ cwd: args.cwd });
|
|
15884
|
+
if (!upstreamSet) {
|
|
15885
|
+
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
15886
|
+
cwd: args.cwd
|
|
15887
|
+
});
|
|
15888
|
+
} else {
|
|
15889
|
+
await exec2("git push", { cwd: args.cwd });
|
|
15871
15890
|
}
|
|
15872
|
-
code;
|
|
15873
|
-
};
|
|
15874
|
-
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
15875
|
-
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
15876
|
-
function defaultMigratePredicate(run, mergedAt) {
|
|
15877
|
-
if (!run.createdAt) return false;
|
|
15878
|
-
const created = Date.parse(run.createdAt);
|
|
15879
|
-
if (!Number.isFinite(created)) return false;
|
|
15880
|
-
if (run.event && run.event !== "push") return false;
|
|
15881
|
-
return created >= mergedAt.getTime() - 5e3;
|
|
15882
|
-
}
|
|
15883
|
-
function shaMigratePredicate(mergeCommitSha) {
|
|
15884
|
-
return (run) => {
|
|
15885
|
-
if (run.event && run.event !== "push") return false;
|
|
15886
|
-
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
15887
|
-
};
|
|
15888
15891
|
}
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15892
|
+
|
|
15893
|
+
// scripts/lakebase/workflow-drift.ts
|
|
15894
|
+
init_esm_shims();
|
|
15895
|
+
import * as fs29 from "fs";
|
|
15896
|
+
import * as path29 from "path";
|
|
15897
|
+
function findKitTemplatesDir(start) {
|
|
15898
|
+
let dir = start;
|
|
15899
|
+
for (let i = 0; i < 6; i++) {
|
|
15900
|
+
const candidate = path29.join(
|
|
15901
|
+
dir,
|
|
15902
|
+
"templates",
|
|
15903
|
+
"project",
|
|
15904
|
+
"common",
|
|
15905
|
+
".github",
|
|
15906
|
+
"workflows"
|
|
15895
15907
|
);
|
|
15908
|
+
if (fs29.existsSync(candidate)) return candidate;
|
|
15909
|
+
const parent = path29.dirname(dir);
|
|
15910
|
+
if (parent === dir) break;
|
|
15911
|
+
dir = parent;
|
|
15896
15912
|
}
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15913
|
+
throw new Error(
|
|
15914
|
+
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
15915
|
+
);
|
|
15916
|
+
}
|
|
15917
|
+
function unifiedDiff(name, projectContent, templateContent) {
|
|
15918
|
+
if (projectContent === templateContent) return "";
|
|
15919
|
+
const a = projectContent.split("\n");
|
|
15920
|
+
const b = templateContent.split("\n");
|
|
15921
|
+
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
15922
|
+
const max = Math.max(a.length, b.length);
|
|
15923
|
+
for (let i = 0; i < max; i++) {
|
|
15924
|
+
const av = a[i];
|
|
15925
|
+
const bv = b[i];
|
|
15926
|
+
if (av === bv) continue;
|
|
15927
|
+
if (av !== void 0) out.push(`-${i + 1}: ${av}`);
|
|
15928
|
+
if (bv !== void 0) out.push(`+${i + 1}: ${bv}`);
|
|
15902
15929
|
}
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15930
|
+
return out.join("\n");
|
|
15931
|
+
}
|
|
15932
|
+
function detectWorkflowDrift(args) {
|
|
15933
|
+
const projectWorkflowsDir = path29.join(
|
|
15934
|
+
args.projectDir,
|
|
15935
|
+
".github",
|
|
15936
|
+
"workflows"
|
|
15937
|
+
);
|
|
15938
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
15939
|
+
const kitWorkflowsDir = args.kitDir ? path29.join(
|
|
15940
|
+
args.kitDir,
|
|
15941
|
+
"templates",
|
|
15942
|
+
"project",
|
|
15943
|
+
"common",
|
|
15944
|
+
".github",
|
|
15945
|
+
"workflows"
|
|
15946
|
+
) : findKitTemplatesDir(here);
|
|
15947
|
+
const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
15948
|
+
const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
15949
|
+
const version = readKitVersion(kitWorkflowsDir);
|
|
15950
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15951
|
+
const files = [];
|
|
15952
|
+
for (const name of templateFiles) {
|
|
15953
|
+
seen.add(name);
|
|
15954
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
15955
|
+
const templatePath = path29.join(kitWorkflowsDir, name);
|
|
15956
|
+
if (!fs29.existsSync(projectPath2)) {
|
|
15957
|
+
files.push({ name, status: "missing" });
|
|
15958
|
+
continue;
|
|
15959
|
+
}
|
|
15960
|
+
const projectContent = fs29.readFileSync(projectPath2, "utf8");
|
|
15961
|
+
const templateContent = applyPlaceholders(fs29.readFileSync(templatePath, "utf8"), version);
|
|
15962
|
+
if (projectContent === templateContent) {
|
|
15963
|
+
files.push({ name, status: "unchanged" });
|
|
15964
|
+
} else {
|
|
15965
|
+
files.push({
|
|
15966
|
+
name,
|
|
15967
|
+
status: "drifted",
|
|
15968
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
15969
|
+
});
|
|
15970
|
+
}
|
|
15908
15971
|
}
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
"bad-precondition"
|
|
15913
|
-
);
|
|
15972
|
+
for (const name of projectFiles) {
|
|
15973
|
+
if (seen.has(name)) continue;
|
|
15974
|
+
files.push({ name, status: "extra" });
|
|
15914
15975
|
}
|
|
15915
|
-
const
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15976
|
+
const order = {
|
|
15977
|
+
drifted: 0,
|
|
15978
|
+
missing: 1,
|
|
15979
|
+
extra: 2,
|
|
15980
|
+
unchanged: 3
|
|
15981
|
+
};
|
|
15982
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
15983
|
+
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
15984
|
+
return {
|
|
15985
|
+
overall: hasDrift ? "drift" : "ok",
|
|
15986
|
+
files
|
|
15987
|
+
};
|
|
15988
|
+
}
|
|
15989
|
+
function readKitVersion(kitWorkflowsDir) {
|
|
15990
|
+
let dir = kitWorkflowsDir;
|
|
15991
|
+
for (let i = 0; i < 5; i++) {
|
|
15992
|
+
dir = path29.dirname(dir);
|
|
15921
15993
|
}
|
|
15922
|
-
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15994
|
+
try {
|
|
15995
|
+
const raw = fs29.readFileSync(path29.join(dir, "package.json"), "utf-8");
|
|
15996
|
+
const pkg = JSON.parse(raw);
|
|
15997
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
15998
|
+
} catch {
|
|
15999
|
+
return "unknown";
|
|
16000
|
+
}
|
|
16001
|
+
}
|
|
16002
|
+
function applyPlaceholders(content, version) {
|
|
16003
|
+
return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
|
|
16004
|
+
}
|
|
16005
|
+
function applyCommandPlaceholders(content, version) {
|
|
16006
|
+
return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
|
|
16007
|
+
}
|
|
16008
|
+
var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
|
|
16009
|
+
function findKitCommandsDir(start) {
|
|
16010
|
+
let dir = start;
|
|
16011
|
+
for (let i = 0; i < 6; i++) {
|
|
16012
|
+
const candidate = path29.join(
|
|
16013
|
+
dir,
|
|
16014
|
+
"templates",
|
|
16015
|
+
"project",
|
|
16016
|
+
"common",
|
|
16017
|
+
".claude",
|
|
16018
|
+
"commands"
|
|
15927
16019
|
);
|
|
16020
|
+
if (fs29.existsSync(candidate)) return candidate;
|
|
16021
|
+
const parent = path29.dirname(dir);
|
|
16022
|
+
if (parent === dir) break;
|
|
16023
|
+
dir = parent;
|
|
15928
16024
|
}
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
16025
|
+
throw new Error(
|
|
16026
|
+
`Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
|
|
16027
|
+
);
|
|
16028
|
+
}
|
|
16029
|
+
function parsePinnedVersion(content) {
|
|
16030
|
+
const m = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
|
|
16031
|
+
return m ? m[1] : void 0;
|
|
16032
|
+
}
|
|
16033
|
+
function detectCommandDrift(args) {
|
|
16034
|
+
const projectCommandsDir = path29.join(args.projectDir, ".claude", "commands");
|
|
16035
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
16036
|
+
const kitCommandsDir = args.kitDir ? path29.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
16037
|
+
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
16038
|
+
const templateFiles = fs29.existsSync(kitCommandsDir) ? fs29.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16039
|
+
const projectFiles = fs29.existsSync(projectCommandsDir) ? fs29.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16040
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16041
|
+
const files = [];
|
|
16042
|
+
for (const name of templateFiles) {
|
|
16043
|
+
seen.add(name);
|
|
16044
|
+
const projectPath2 = path29.join(projectCommandsDir, name);
|
|
16045
|
+
const templatePath = path29.join(kitCommandsDir, name);
|
|
16046
|
+
const templateRaw = fs29.readFileSync(templatePath, "utf8");
|
|
16047
|
+
if (!fs29.existsSync(projectPath2)) {
|
|
16048
|
+
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
16049
|
+
continue;
|
|
16050
|
+
}
|
|
16051
|
+
const projectContent = fs29.readFileSync(projectPath2, "utf8");
|
|
16052
|
+
const pinned = parsePinnedVersion(projectContent);
|
|
16053
|
+
const versionForCompare = pinned ?? kitVersion;
|
|
16054
|
+
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
16055
|
+
if (projectContent === templateContent) {
|
|
16056
|
+
files.push({
|
|
16057
|
+
name,
|
|
16058
|
+
status: "unchanged",
|
|
16059
|
+
pinned_version: pinned,
|
|
16060
|
+
kit_version: kitVersion
|
|
16061
|
+
});
|
|
16062
|
+
} else {
|
|
16063
|
+
files.push({
|
|
16064
|
+
name,
|
|
16065
|
+
status: "drifted",
|
|
16066
|
+
pinned_version: pinned,
|
|
16067
|
+
kit_version: kitVersion,
|
|
16068
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
16069
|
+
});
|
|
15940
16070
|
}
|
|
15941
|
-
authVerified = true;
|
|
15942
16071
|
}
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
ownerRepo,
|
|
15947
|
-
pullNumber,
|
|
15948
|
-
lakebaseInstance: instance,
|
|
15949
|
-
method: args.method ?? "squash"
|
|
15950
|
-
});
|
|
15951
|
-
} catch (err) {
|
|
15952
|
-
throw new ScmMergeError(
|
|
15953
|
-
`mergePairedPullRequest failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
15954
|
-
"merge-failed"
|
|
15955
|
-
);
|
|
16072
|
+
for (const name of projectFiles) {
|
|
16073
|
+
if (seen.has(name)) continue;
|
|
16074
|
+
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
15956
16075
|
}
|
|
15957
|
-
const
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
16076
|
+
const order = {
|
|
16077
|
+
drifted: 0,
|
|
16078
|
+
missing: 1,
|
|
16079
|
+
extra: 2,
|
|
16080
|
+
unchanged: 3
|
|
16081
|
+
};
|
|
16082
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
16083
|
+
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
16084
|
+
return { overall: hasDrift ? "drift" : "ok", files };
|
|
16085
|
+
}
|
|
16086
|
+
function readKitVersionFromCommandsDir(kitCommandsDir) {
|
|
16087
|
+
let dir = kitCommandsDir;
|
|
16088
|
+
for (let i = 0; i < 5; i++) {
|
|
16089
|
+
dir = path29.dirname(dir);
|
|
16090
|
+
}
|
|
16091
|
+
try {
|
|
16092
|
+
const raw = fs29.readFileSync(path29.join(dir, "package.json"), "utf-8");
|
|
16093
|
+
const pkg = JSON.parse(raw);
|
|
16094
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
16095
|
+
} catch {
|
|
16096
|
+
return "unknown";
|
|
16097
|
+
}
|
|
16098
|
+
}
|
|
16099
|
+
function detectScaffoldedDrift(args) {
|
|
16100
|
+
const workflows = detectWorkflowDrift(args);
|
|
16101
|
+
const commands = detectCommandDrift(args);
|
|
16102
|
+
return {
|
|
16103
|
+
overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
|
|
16104
|
+
workflows,
|
|
16105
|
+
commands
|
|
16106
|
+
};
|
|
16107
|
+
}
|
|
16108
|
+
function updateWorkflows(args) {
|
|
16109
|
+
const projectWorkflowsDir = path29.join(
|
|
16110
|
+
args.projectDir,
|
|
16111
|
+
".github",
|
|
16112
|
+
"workflows"
|
|
16113
|
+
);
|
|
16114
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
16115
|
+
const kitWorkflowsDir = args.kitDir ? path29.join(
|
|
16116
|
+
args.kitDir,
|
|
16117
|
+
"templates",
|
|
16118
|
+
"project",
|
|
16119
|
+
"common",
|
|
16120
|
+
".github",
|
|
16121
|
+
"workflows"
|
|
16122
|
+
) : findKitTemplatesDir(here);
|
|
16123
|
+
const substitute = args.substitute !== false;
|
|
16124
|
+
const dryRun = args.dryRun === true;
|
|
16125
|
+
const pruneExtras = args.pruneExtras === true;
|
|
16126
|
+
const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16127
|
+
const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16128
|
+
if (!dryRun && templateFiles.length > 0 && !fs29.existsSync(projectWorkflowsDir)) {
|
|
16129
|
+
fs29.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
16130
|
+
}
|
|
16131
|
+
const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
|
|
16132
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16133
|
+
const files = [];
|
|
16134
|
+
for (const name of templateFiles) {
|
|
16135
|
+
seen.add(name);
|
|
16136
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
16137
|
+
const templatePath = path29.join(kitWorkflowsDir, name);
|
|
16138
|
+
const templateRaw = fs29.readFileSync(templatePath, "utf-8");
|
|
16139
|
+
const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
|
|
16140
|
+
const existed = fs29.existsSync(projectPath2);
|
|
16141
|
+
const current = existed ? fs29.readFileSync(projectPath2, "utf-8") : "";
|
|
16142
|
+
let outcome;
|
|
16143
|
+
if (!existed) {
|
|
16144
|
+
outcome = "added";
|
|
16145
|
+
} else if (current === desired) {
|
|
16146
|
+
outcome = "unchanged";
|
|
16147
|
+
} else {
|
|
16148
|
+
outcome = "updated";
|
|
16149
|
+
}
|
|
16150
|
+
if (!dryRun && outcome !== "unchanged") {
|
|
16151
|
+
fs29.writeFileSync(projectPath2, desired);
|
|
16152
|
+
}
|
|
16153
|
+
files.push({ name, outcome });
|
|
16154
|
+
}
|
|
16155
|
+
if (pruneExtras) {
|
|
16156
|
+
for (const name of projectFiles) {
|
|
16157
|
+
if (seen.has(name)) continue;
|
|
16158
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
16159
|
+
if (!dryRun) {
|
|
16160
|
+
fs29.unlinkSync(projectPath2);
|
|
16161
|
+
}
|
|
16162
|
+
files.push({ name, outcome: "removed" });
|
|
16163
|
+
}
|
|
16164
|
+
}
|
|
16165
|
+
const order = {
|
|
16166
|
+
added: 0,
|
|
16167
|
+
updated: 1,
|
|
16168
|
+
removed: 2,
|
|
16169
|
+
unchanged: 3
|
|
16170
|
+
};
|
|
16171
|
+
files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
|
|
16172
|
+
const changed = files.some((f) => f.outcome !== "unchanged");
|
|
16173
|
+
return { files, changed };
|
|
16174
|
+
}
|
|
16175
|
+
|
|
16176
|
+
// scripts/lakebase/scm-merge.ts
|
|
16177
|
+
async function reconcileTierToOrigin(args) {
|
|
16178
|
+
const { cwd, tier } = args;
|
|
16179
|
+
try {
|
|
16180
|
+
const head = await getCurrentBranch({ cwd });
|
|
16181
|
+
if (head === tier) {
|
|
16182
|
+
await exec2(`git pull --ff-only`, { cwd, timeout: 3e4 });
|
|
16183
|
+
} else {
|
|
16184
|
+
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
16185
|
+
}
|
|
16186
|
+
return null;
|
|
16187
|
+
} catch (err) {
|
|
16188
|
+
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).`;
|
|
16189
|
+
}
|
|
16190
|
+
}
|
|
16191
|
+
var ScmMergeError = class extends Error {
|
|
16192
|
+
constructor(message, code) {
|
|
16193
|
+
super(message);
|
|
16194
|
+
this.code = code;
|
|
16195
|
+
this.name = "ScmMergeError";
|
|
16196
|
+
}
|
|
16197
|
+
code;
|
|
16198
|
+
};
|
|
16199
|
+
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
16200
|
+
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
16201
|
+
function defaultMigratePredicate(run, mergedAt) {
|
|
16202
|
+
if (!run.createdAt) return false;
|
|
16203
|
+
const created = Date.parse(run.createdAt);
|
|
16204
|
+
if (!Number.isFinite(created)) return false;
|
|
16205
|
+
if (run.event && run.event !== "push") return false;
|
|
16206
|
+
return created >= mergedAt.getTime() - 5e3;
|
|
16207
|
+
}
|
|
16208
|
+
function shaMigratePredicate(mergeCommitSha) {
|
|
16209
|
+
return (run) => {
|
|
16210
|
+
if (run.event && run.event !== "push") return false;
|
|
16211
|
+
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
16212
|
+
};
|
|
16213
|
+
}
|
|
16214
|
+
async function defaultRefreshPromoteWorkflows(projectDir) {
|
|
16215
|
+
const drift = detectWorkflowDrift({ projectDir });
|
|
16216
|
+
const touched = drift.files.filter((f) => f.status === "drifted").map((f) => f.name);
|
|
16217
|
+
if (touched.length === 0) return { refreshed: false };
|
|
16218
|
+
const result = updateWorkflows({ projectDir });
|
|
16219
|
+
if (!result.changed) return { refreshed: false };
|
|
16220
|
+
await exec2(`git add ${shellEscape2(".github/workflows")}`, { cwd: projectDir, timeout: 1e4 });
|
|
16221
|
+
const staged = await exec2("git diff --cached --name-only -- .github/workflows", {
|
|
16222
|
+
cwd: projectDir,
|
|
16223
|
+
timeout: 1e4
|
|
16224
|
+
});
|
|
16225
|
+
if (!staged.trim()) return { refreshed: false };
|
|
16226
|
+
await exec2(
|
|
16227
|
+
`git commit -m ${shellEscape2(
|
|
16228
|
+
"chore(ci): refresh scaffolded workflows to pinned substrate (promote uses apply-tier)"
|
|
16229
|
+
)} -- .github/workflows`,
|
|
16230
|
+
{ cwd: projectDir, timeout: 15e3 }
|
|
16231
|
+
);
|
|
16232
|
+
await pushCurrentBranchForPr({ cwd: projectDir });
|
|
16233
|
+
return { refreshed: true, detail: `refreshed + pushed ${touched.join(", ") || "workflow(s)"} onto the PR head` };
|
|
16234
|
+
}
|
|
16235
|
+
async function mergeFeature(args) {
|
|
16236
|
+
const current = readWorkflowState(args.projectDir);
|
|
16237
|
+
if (!current) {
|
|
16238
|
+
throw new ScmMergeError(
|
|
16239
|
+
"No SCM workflow state. wait-ci first.",
|
|
16240
|
+
"no-state-file"
|
|
16241
|
+
);
|
|
16242
|
+
}
|
|
16243
|
+
if (current.state !== "ci-green") {
|
|
16244
|
+
throw new ScmMergeError(
|
|
16245
|
+
`merge refuses state "${current.state}". Allowed predecessor: ci-green.`,
|
|
16246
|
+
"bad-precondition"
|
|
16247
|
+
);
|
|
16248
|
+
}
|
|
16249
|
+
if (!current.pr_url) {
|
|
16250
|
+
throw new ScmMergeError(
|
|
16251
|
+
"ci-green row is missing pr_url; cannot resolve the PR to merge.",
|
|
16252
|
+
"no-pr-url"
|
|
16253
|
+
);
|
|
16254
|
+
}
|
|
16255
|
+
if (!current.branch || !current.parent_branch) {
|
|
16256
|
+
throw new ScmMergeError(
|
|
16257
|
+
"ci-green row missing branch / parent_branch; refusing to merge.",
|
|
16258
|
+
"bad-precondition"
|
|
16259
|
+
);
|
|
16260
|
+
}
|
|
16261
|
+
const ownerRepo = await getOwnerRepo(args.projectDir);
|
|
16262
|
+
if (!ownerRepo) {
|
|
16263
|
+
throw new ScmMergeError(
|
|
16264
|
+
"No GitHub remote found at origin.",
|
|
16265
|
+
"no-github-remote"
|
|
16266
|
+
);
|
|
16267
|
+
}
|
|
16268
|
+
const pullNumber = extractPullNumber(current.pr_url);
|
|
16269
|
+
if (!pullNumber) {
|
|
16270
|
+
throw new ScmMergeError(
|
|
16271
|
+
`Could not extract PR number from URL: ${current.pr_url}`,
|
|
16272
|
+
"bad-pr-url"
|
|
16273
|
+
);
|
|
16274
|
+
}
|
|
16275
|
+
const instance = args.instance ?? current.project_id;
|
|
16276
|
+
const gitBase = await resolveGitBase(current.parent_branch, args.projectDir);
|
|
16277
|
+
const wantMigrate = args.waitMigrate !== false;
|
|
16278
|
+
let authVerified = false;
|
|
16279
|
+
if (wantMigrate && args.verifyMigrateAuth) {
|
|
16280
|
+
const probe = await args.verifyMigrateAuth();
|
|
16281
|
+
if (!probe.ok) {
|
|
16282
|
+
throw new ScmMergeError(
|
|
16283
|
+
`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.`,
|
|
16284
|
+
"migrate-auth"
|
|
16285
|
+
);
|
|
16286
|
+
}
|
|
16287
|
+
authVerified = true;
|
|
16288
|
+
}
|
|
16289
|
+
const preflightNotes = [];
|
|
16290
|
+
{
|
|
16291
|
+
const refresh = args.refreshWorkflows ?? (() => defaultRefreshPromoteWorkflows(args.projectDir));
|
|
16292
|
+
try {
|
|
16293
|
+
const r = await refresh();
|
|
16294
|
+
if (r.refreshed) {
|
|
16295
|
+
preflightNotes.push(`Refreshed stale CI workflows before merge${r.detail ? ` (${r.detail})` : ""}.`);
|
|
16296
|
+
}
|
|
16297
|
+
} catch (err) {
|
|
16298
|
+
preflightNotes.push(
|
|
16299
|
+
`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').`
|
|
16300
|
+
);
|
|
16301
|
+
}
|
|
16302
|
+
}
|
|
16303
|
+
let paired;
|
|
16304
|
+
try {
|
|
16305
|
+
paired = await mergePairedPullRequest({
|
|
16306
|
+
ownerRepo,
|
|
16307
|
+
pullNumber,
|
|
16308
|
+
lakebaseInstance: instance,
|
|
16309
|
+
method: args.method ?? "squash"
|
|
16310
|
+
});
|
|
16311
|
+
} catch (err) {
|
|
16312
|
+
throw new ScmMergeError(
|
|
16313
|
+
`mergePairedPullRequest failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
16314
|
+
"merge-failed"
|
|
16315
|
+
);
|
|
16316
|
+
}
|
|
16317
|
+
const warnings = [...preflightNotes, ...paired.warnings];
|
|
16318
|
+
let localBranchDeleted = false;
|
|
16319
|
+
let headAfter = current.branch;
|
|
16320
|
+
if (!args.skipLocalCleanup) {
|
|
16321
|
+
const switchTo = args.switchTo ?? gitBase;
|
|
16322
|
+
const head = await getCurrentBranch({ cwd: args.projectDir });
|
|
15963
16323
|
if (head === current.branch) {
|
|
15964
16324
|
try {
|
|
15965
16325
|
await exec2(`git checkout ${shellEscape2(switchTo)}`, {
|
|
@@ -16151,7 +16511,7 @@ init_esm_shims();
|
|
|
16151
16511
|
|
|
16152
16512
|
// scripts/git/branches.ts
|
|
16153
16513
|
init_esm_shims();
|
|
16154
|
-
async function
|
|
16514
|
+
async function currentBranchName2(cwd) {
|
|
16155
16515
|
try {
|
|
16156
16516
|
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
16157
16517
|
} catch {
|
|
@@ -16170,7 +16530,7 @@ async function listLocalBranches(args) {
|
|
|
16170
16530
|
return [];
|
|
16171
16531
|
}
|
|
16172
16532
|
if (!raw) return [];
|
|
16173
|
-
const current = await
|
|
16533
|
+
const current = await currentBranchName2(cwd);
|
|
16174
16534
|
return raw.split("\n").filter(Boolean).map((line) => {
|
|
16175
16535
|
const [name, tracking, trackInfo] = line.split("|");
|
|
16176
16536
|
let ahead = 0;
|
|
@@ -16335,15 +16695,15 @@ function parentForTopology(t, defaultLeaf) {
|
|
|
16335
16695
|
|
|
16336
16696
|
// scripts/lakebase/scm-doctor.ts
|
|
16337
16697
|
init_esm_shims();
|
|
16338
|
-
import * as
|
|
16339
|
-
import * as
|
|
16698
|
+
import * as fs30 from "fs";
|
|
16699
|
+
import * as path30 from "path";
|
|
16340
16700
|
var FEATURE_PREFIX = "feature/";
|
|
16341
16701
|
var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
|
|
16342
16702
|
function readEnv(projectDir) {
|
|
16343
|
-
const envPath =
|
|
16703
|
+
const envPath = path30.join(projectDir, ".env");
|
|
16344
16704
|
const out = /* @__PURE__ */ new Map();
|
|
16345
|
-
if (!
|
|
16346
|
-
const lines =
|
|
16705
|
+
if (!fs30.existsSync(envPath)) return out;
|
|
16706
|
+
const lines = fs30.readFileSync(envPath, "utf8").split("\n");
|
|
16347
16707
|
for (const line of lines) {
|
|
16348
16708
|
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
|
|
16349
16709
|
if (m) out.set(m[1], m[2].replace(/^["']|["']$/g, ""));
|
|
@@ -16398,9 +16758,9 @@ async function runDoctor(args, deps = {}) {
|
|
|
16398
16758
|
}
|
|
16399
16759
|
for (const wf of ["pr.yml", "merge.yml"]) {
|
|
16400
16760
|
try {
|
|
16401
|
-
const p =
|
|
16402
|
-
if (!
|
|
16403
|
-
const body =
|
|
16761
|
+
const p = path30.join(projectDir, ".github", "workflows", wf);
|
|
16762
|
+
if (!fs30.existsSync(p)) continue;
|
|
16763
|
+
const body = fs30.readFileSync(p, "utf8");
|
|
16404
16764
|
if (/lakebase-scm-utils#v\d/.test(body)) {
|
|
16405
16765
|
findings.push({
|
|
16406
16766
|
id: "ci-workflow-substrate-pin",
|
|
@@ -16615,7 +16975,7 @@ async function fixFinding(args) {
|
|
|
16615
16975
|
);
|
|
16616
16976
|
}
|
|
16617
16977
|
const sanitized = sanitizeBranchName(branch);
|
|
16618
|
-
const envFile =
|
|
16978
|
+
const envFile = path30.join(args.projectDir, ".env");
|
|
16619
16979
|
updateEnvConnection({
|
|
16620
16980
|
envPath: envFile,
|
|
16621
16981
|
projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
|
|
@@ -16720,291 +17080,6 @@ init_esm_shims();
|
|
|
16720
17080
|
import * as fs31 from "fs";
|
|
16721
17081
|
import * as path31 from "path";
|
|
16722
17082
|
import * as cp6 from "child_process";
|
|
16723
|
-
|
|
16724
|
-
// scripts/lakebase/workflow-drift.ts
|
|
16725
|
-
init_esm_shims();
|
|
16726
|
-
import * as fs30 from "fs";
|
|
16727
|
-
import * as path30 from "path";
|
|
16728
|
-
function findKitTemplatesDir(start) {
|
|
16729
|
-
let dir = start;
|
|
16730
|
-
for (let i = 0; i < 6; i++) {
|
|
16731
|
-
const candidate = path30.join(
|
|
16732
|
-
dir,
|
|
16733
|
-
"templates",
|
|
16734
|
-
"project",
|
|
16735
|
-
"common",
|
|
16736
|
-
".github",
|
|
16737
|
-
"workflows"
|
|
16738
|
-
);
|
|
16739
|
-
if (fs30.existsSync(candidate)) return candidate;
|
|
16740
|
-
const parent = path30.dirname(dir);
|
|
16741
|
-
if (parent === dir) break;
|
|
16742
|
-
dir = parent;
|
|
16743
|
-
}
|
|
16744
|
-
throw new Error(
|
|
16745
|
-
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
16746
|
-
);
|
|
16747
|
-
}
|
|
16748
|
-
function unifiedDiff(name, projectContent, templateContent) {
|
|
16749
|
-
if (projectContent === templateContent) return "";
|
|
16750
|
-
const a = projectContent.split("\n");
|
|
16751
|
-
const b = templateContent.split("\n");
|
|
16752
|
-
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
16753
|
-
const max = Math.max(a.length, b.length);
|
|
16754
|
-
for (let i = 0; i < max; i++) {
|
|
16755
|
-
const av = a[i];
|
|
16756
|
-
const bv = b[i];
|
|
16757
|
-
if (av === bv) continue;
|
|
16758
|
-
if (av !== void 0) out.push(`-${i + 1}: ${av}`);
|
|
16759
|
-
if (bv !== void 0) out.push(`+${i + 1}: ${bv}`);
|
|
16760
|
-
}
|
|
16761
|
-
return out.join("\n");
|
|
16762
|
-
}
|
|
16763
|
-
function detectWorkflowDrift(args) {
|
|
16764
|
-
const projectWorkflowsDir = path30.join(
|
|
16765
|
-
args.projectDir,
|
|
16766
|
-
".github",
|
|
16767
|
-
"workflows"
|
|
16768
|
-
);
|
|
16769
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
16770
|
-
const kitWorkflowsDir = args.kitDir ? path30.join(
|
|
16771
|
-
args.kitDir,
|
|
16772
|
-
"templates",
|
|
16773
|
-
"project",
|
|
16774
|
-
"common",
|
|
16775
|
-
".github",
|
|
16776
|
-
"workflows"
|
|
16777
|
-
) : findKitTemplatesDir(here);
|
|
16778
|
-
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16779
|
-
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16780
|
-
const version = readKitVersion(kitWorkflowsDir);
|
|
16781
|
-
const seen = /* @__PURE__ */ new Set();
|
|
16782
|
-
const files = [];
|
|
16783
|
-
for (const name of templateFiles) {
|
|
16784
|
-
seen.add(name);
|
|
16785
|
-
const projectPath2 = path30.join(projectWorkflowsDir, name);
|
|
16786
|
-
const templatePath = path30.join(kitWorkflowsDir, name);
|
|
16787
|
-
if (!fs30.existsSync(projectPath2)) {
|
|
16788
|
-
files.push({ name, status: "missing" });
|
|
16789
|
-
continue;
|
|
16790
|
-
}
|
|
16791
|
-
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
16792
|
-
const templateContent = applyPlaceholders(fs30.readFileSync(templatePath, "utf8"), version);
|
|
16793
|
-
if (projectContent === templateContent) {
|
|
16794
|
-
files.push({ name, status: "unchanged" });
|
|
16795
|
-
} else {
|
|
16796
|
-
files.push({
|
|
16797
|
-
name,
|
|
16798
|
-
status: "drifted",
|
|
16799
|
-
diff: unifiedDiff(name, projectContent, templateContent)
|
|
16800
|
-
});
|
|
16801
|
-
}
|
|
16802
|
-
}
|
|
16803
|
-
for (const name of projectFiles) {
|
|
16804
|
-
if (seen.has(name)) continue;
|
|
16805
|
-
files.push({ name, status: "extra" });
|
|
16806
|
-
}
|
|
16807
|
-
const order = {
|
|
16808
|
-
drifted: 0,
|
|
16809
|
-
missing: 1,
|
|
16810
|
-
extra: 2,
|
|
16811
|
-
unchanged: 3
|
|
16812
|
-
};
|
|
16813
|
-
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
16814
|
-
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
16815
|
-
return {
|
|
16816
|
-
overall: hasDrift ? "drift" : "ok",
|
|
16817
|
-
files
|
|
16818
|
-
};
|
|
16819
|
-
}
|
|
16820
|
-
function readKitVersion(kitWorkflowsDir) {
|
|
16821
|
-
let dir = kitWorkflowsDir;
|
|
16822
|
-
for (let i = 0; i < 5; i++) {
|
|
16823
|
-
dir = path30.dirname(dir);
|
|
16824
|
-
}
|
|
16825
|
-
try {
|
|
16826
|
-
const raw = fs30.readFileSync(path30.join(dir, "package.json"), "utf-8");
|
|
16827
|
-
const pkg = JSON.parse(raw);
|
|
16828
|
-
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
16829
|
-
} catch {
|
|
16830
|
-
return "unknown";
|
|
16831
|
-
}
|
|
16832
|
-
}
|
|
16833
|
-
function applyPlaceholders(content, version) {
|
|
16834
|
-
return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
|
|
16835
|
-
}
|
|
16836
|
-
function applyCommandPlaceholders(content, version) {
|
|
16837
|
-
return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
|
|
16838
|
-
}
|
|
16839
|
-
var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
|
|
16840
|
-
function findKitCommandsDir(start) {
|
|
16841
|
-
let dir = start;
|
|
16842
|
-
for (let i = 0; i < 6; i++) {
|
|
16843
|
-
const candidate = path30.join(
|
|
16844
|
-
dir,
|
|
16845
|
-
"templates",
|
|
16846
|
-
"project",
|
|
16847
|
-
"common",
|
|
16848
|
-
".claude",
|
|
16849
|
-
"commands"
|
|
16850
|
-
);
|
|
16851
|
-
if (fs30.existsSync(candidate)) return candidate;
|
|
16852
|
-
const parent = path30.dirname(dir);
|
|
16853
|
-
if (parent === dir) break;
|
|
16854
|
-
dir = parent;
|
|
16855
|
-
}
|
|
16856
|
-
throw new Error(
|
|
16857
|
-
`Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
|
|
16858
|
-
);
|
|
16859
|
-
}
|
|
16860
|
-
function parsePinnedVersion(content) {
|
|
16861
|
-
const m = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
|
|
16862
|
-
return m ? m[1] : void 0;
|
|
16863
|
-
}
|
|
16864
|
-
function detectCommandDrift(args) {
|
|
16865
|
-
const projectCommandsDir = path30.join(args.projectDir, ".claude", "commands");
|
|
16866
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
16867
|
-
const kitCommandsDir = args.kitDir ? path30.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
16868
|
-
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
16869
|
-
const templateFiles = fs30.existsSync(kitCommandsDir) ? fs30.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16870
|
-
const projectFiles = fs30.existsSync(projectCommandsDir) ? fs30.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16871
|
-
const seen = /* @__PURE__ */ new Set();
|
|
16872
|
-
const files = [];
|
|
16873
|
-
for (const name of templateFiles) {
|
|
16874
|
-
seen.add(name);
|
|
16875
|
-
const projectPath2 = path30.join(projectCommandsDir, name);
|
|
16876
|
-
const templatePath = path30.join(kitCommandsDir, name);
|
|
16877
|
-
const templateRaw = fs30.readFileSync(templatePath, "utf8");
|
|
16878
|
-
if (!fs30.existsSync(projectPath2)) {
|
|
16879
|
-
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
16880
|
-
continue;
|
|
16881
|
-
}
|
|
16882
|
-
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
16883
|
-
const pinned = parsePinnedVersion(projectContent);
|
|
16884
|
-
const versionForCompare = pinned ?? kitVersion;
|
|
16885
|
-
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
16886
|
-
if (projectContent === templateContent) {
|
|
16887
|
-
files.push({
|
|
16888
|
-
name,
|
|
16889
|
-
status: "unchanged",
|
|
16890
|
-
pinned_version: pinned,
|
|
16891
|
-
kit_version: kitVersion
|
|
16892
|
-
});
|
|
16893
|
-
} else {
|
|
16894
|
-
files.push({
|
|
16895
|
-
name,
|
|
16896
|
-
status: "drifted",
|
|
16897
|
-
pinned_version: pinned,
|
|
16898
|
-
kit_version: kitVersion,
|
|
16899
|
-
diff: unifiedDiff(name, projectContent, templateContent)
|
|
16900
|
-
});
|
|
16901
|
-
}
|
|
16902
|
-
}
|
|
16903
|
-
for (const name of projectFiles) {
|
|
16904
|
-
if (seen.has(name)) continue;
|
|
16905
|
-
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
16906
|
-
}
|
|
16907
|
-
const order = {
|
|
16908
|
-
drifted: 0,
|
|
16909
|
-
missing: 1,
|
|
16910
|
-
extra: 2,
|
|
16911
|
-
unchanged: 3
|
|
16912
|
-
};
|
|
16913
|
-
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
16914
|
-
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
16915
|
-
return { overall: hasDrift ? "drift" : "ok", files };
|
|
16916
|
-
}
|
|
16917
|
-
function readKitVersionFromCommandsDir(kitCommandsDir) {
|
|
16918
|
-
let dir = kitCommandsDir;
|
|
16919
|
-
for (let i = 0; i < 5; i++) {
|
|
16920
|
-
dir = path30.dirname(dir);
|
|
16921
|
-
}
|
|
16922
|
-
try {
|
|
16923
|
-
const raw = fs30.readFileSync(path30.join(dir, "package.json"), "utf-8");
|
|
16924
|
-
const pkg = JSON.parse(raw);
|
|
16925
|
-
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
16926
|
-
} catch {
|
|
16927
|
-
return "unknown";
|
|
16928
|
-
}
|
|
16929
|
-
}
|
|
16930
|
-
function detectScaffoldedDrift(args) {
|
|
16931
|
-
const workflows = detectWorkflowDrift(args);
|
|
16932
|
-
const commands = detectCommandDrift(args);
|
|
16933
|
-
return {
|
|
16934
|
-
overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
|
|
16935
|
-
workflows,
|
|
16936
|
-
commands
|
|
16937
|
-
};
|
|
16938
|
-
}
|
|
16939
|
-
function updateWorkflows(args) {
|
|
16940
|
-
const projectWorkflowsDir = path30.join(
|
|
16941
|
-
args.projectDir,
|
|
16942
|
-
".github",
|
|
16943
|
-
"workflows"
|
|
16944
|
-
);
|
|
16945
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
16946
|
-
const kitWorkflowsDir = args.kitDir ? path30.join(
|
|
16947
|
-
args.kitDir,
|
|
16948
|
-
"templates",
|
|
16949
|
-
"project",
|
|
16950
|
-
"common",
|
|
16951
|
-
".github",
|
|
16952
|
-
"workflows"
|
|
16953
|
-
) : findKitTemplatesDir(here);
|
|
16954
|
-
const substitute = args.substitute !== false;
|
|
16955
|
-
const dryRun = args.dryRun === true;
|
|
16956
|
-
const pruneExtras = args.pruneExtras === true;
|
|
16957
|
-
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16958
|
-
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16959
|
-
if (!dryRun && templateFiles.length > 0 && !fs30.existsSync(projectWorkflowsDir)) {
|
|
16960
|
-
fs30.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
16961
|
-
}
|
|
16962
|
-
const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
|
|
16963
|
-
const seen = /* @__PURE__ */ new Set();
|
|
16964
|
-
const files = [];
|
|
16965
|
-
for (const name of templateFiles) {
|
|
16966
|
-
seen.add(name);
|
|
16967
|
-
const projectPath2 = path30.join(projectWorkflowsDir, name);
|
|
16968
|
-
const templatePath = path30.join(kitWorkflowsDir, name);
|
|
16969
|
-
const templateRaw = fs30.readFileSync(templatePath, "utf-8");
|
|
16970
|
-
const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
|
|
16971
|
-
const existed = fs30.existsSync(projectPath2);
|
|
16972
|
-
const current = existed ? fs30.readFileSync(projectPath2, "utf-8") : "";
|
|
16973
|
-
let outcome;
|
|
16974
|
-
if (!existed) {
|
|
16975
|
-
outcome = "added";
|
|
16976
|
-
} else if (current === desired) {
|
|
16977
|
-
outcome = "unchanged";
|
|
16978
|
-
} else {
|
|
16979
|
-
outcome = "updated";
|
|
16980
|
-
}
|
|
16981
|
-
if (!dryRun && outcome !== "unchanged") {
|
|
16982
|
-
fs30.writeFileSync(projectPath2, desired);
|
|
16983
|
-
}
|
|
16984
|
-
files.push({ name, outcome });
|
|
16985
|
-
}
|
|
16986
|
-
if (pruneExtras) {
|
|
16987
|
-
for (const name of projectFiles) {
|
|
16988
|
-
if (seen.has(name)) continue;
|
|
16989
|
-
const projectPath2 = path30.join(projectWorkflowsDir, name);
|
|
16990
|
-
if (!dryRun) {
|
|
16991
|
-
fs30.unlinkSync(projectPath2);
|
|
16992
|
-
}
|
|
16993
|
-
files.push({ name, outcome: "removed" });
|
|
16994
|
-
}
|
|
16995
|
-
}
|
|
16996
|
-
const order = {
|
|
16997
|
-
added: 0,
|
|
16998
|
-
updated: 1,
|
|
16999
|
-
removed: 2,
|
|
17000
|
-
unchanged: 3
|
|
17001
|
-
};
|
|
17002
|
-
files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
|
|
17003
|
-
const changed = files.some((f) => f.outcome !== "unchanged");
|
|
17004
|
-
return { files, changed };
|
|
17005
|
-
}
|
|
17006
|
-
|
|
17007
|
-
// scripts/lakebase/doctor.ts
|
|
17008
17083
|
function readEnvFile(projectDir) {
|
|
17009
17084
|
const envPath = path31.join(projectDir, ".env");
|
|
17010
17085
|
if (!fs31.existsSync(envPath)) return {};
|
|
@@ -17437,7 +17512,7 @@ function checkWorkflowDrift(projectDir) {
|
|
|
17437
17512
|
status: "warn",
|
|
17438
17513
|
message: `Scaffolded workflows drift from kit: ${drifted} drifted, ${missing} missing`,
|
|
17439
17514
|
detail: { files: report.files.map((f) => ({ name: f.name, status: f.status })) },
|
|
17440
|
-
hint: "
|
|
17515
|
+
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."
|
|
17441
17516
|
};
|
|
17442
17517
|
} catch (err) {
|
|
17443
17518
|
return {
|
|
@@ -17745,6 +17820,7 @@ export {
|
|
|
17745
17820
|
createUatPairedBranch,
|
|
17746
17821
|
cutBackup,
|
|
17747
17822
|
databricksAuthPrereqMessage,
|
|
17823
|
+
defaultRefreshPromoteWorkflows,
|
|
17748
17824
|
deleteAppEndpoint,
|
|
17749
17825
|
deleteBranch,
|
|
17750
17826
|
deleteLakebaseProject,
|