@databricks-solutions/lakebase-scm-utils 0.2.44 → 0.2.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/index.cjs +795 -758
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +794 -758
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
- package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
- package/dist/scripts/lakebase/detect-language.cli.js +2 -2
- package/dist/scripts/lakebase/doctor.cli.cjs +3 -3
- package/dist/scripts/lakebase/doctor.cli.js +3 -3
- package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
- package/dist/scripts/lakebase/get-connection.cli.js +2 -2
- package/dist/scripts/lakebase/index.cjs +463 -395
- package/dist/scripts/lakebase/index.d.cts +33 -1
- package/dist/scripts/lakebase/index.d.ts +33 -1
- package/dist/scripts/lakebase/index.js +462 -395
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
- package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
- package/dist/scripts/lakebase/new-migration.cli.js +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2284 -2033
- package/dist/scripts/lakebase/scm-merge.cli.js +339 -88
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/scripts/lakebase/doctor.ts +1 -1
- package/scripts/lakebase/scm-merge.ts +80 -1
|
@@ -2517,8 +2517,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
2517
2517
|
var cached;
|
|
2518
2518
|
function substrateSelfVersion() {
|
|
2519
2519
|
if (cached !== void 0) return cached;
|
|
2520
|
-
if ("0.2.
|
|
2521
|
-
cached = "0.2.
|
|
2520
|
+
if ("0.2.45".length > 0) {
|
|
2521
|
+
cached = "0.2.45";
|
|
2522
2522
|
return cached;
|
|
2523
2523
|
}
|
|
2524
2524
|
cached = "unknown";
|
|
@@ -2746,6 +2746,14 @@ async function getCredential(args) {
|
|
|
2746
2746
|
|
|
2747
2747
|
// scripts/git/status.ts
|
|
2748
2748
|
init_esm_shims();
|
|
2749
|
+
async function hasUpstream(args) {
|
|
2750
|
+
try {
|
|
2751
|
+
await exec2("git rev-parse --abbrev-ref @{u}", { cwd: args.cwd });
|
|
2752
|
+
return true;
|
|
2753
|
+
} catch {
|
|
2754
|
+
return false;
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2749
2757
|
async function getAheadBehind(args) {
|
|
2750
2758
|
const { cwd } = args;
|
|
2751
2759
|
try {
|
|
@@ -15849,118 +15857,461 @@ function pickRunUrl(pr) {
|
|
|
15849
15857
|
|
|
15850
15858
|
// scripts/lakebase/scm-merge.ts
|
|
15851
15859
|
init_esm_shims();
|
|
15852
|
-
|
|
15853
|
-
|
|
15860
|
+
|
|
15861
|
+
// scripts/git/sync.ts
|
|
15862
|
+
init_esm_shims();
|
|
15863
|
+
async function currentBranchName(cwd) {
|
|
15854
15864
|
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).`;
|
|
15865
|
+
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
15866
|
+
} catch {
|
|
15867
|
+
return "";
|
|
15864
15868
|
}
|
|
15865
15869
|
}
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15870
|
+
async function pushCurrentBranchForPr(args) {
|
|
15871
|
+
const remote = args.remote ?? "origin";
|
|
15872
|
+
const branch = await currentBranchName(args.cwd);
|
|
15873
|
+
if (!branch) throw new Error("No current branch");
|
|
15874
|
+
const upstreamSet = await hasUpstream({ cwd: args.cwd });
|
|
15875
|
+
if (!upstreamSet) {
|
|
15876
|
+
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
15877
|
+
cwd: args.cwd
|
|
15878
|
+
});
|
|
15879
|
+
} else {
|
|
15880
|
+
await exec2("git push", { cwd: args.cwd });
|
|
15871
15881
|
}
|
|
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
15882
|
}
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15883
|
+
|
|
15884
|
+
// scripts/lakebase/workflow-drift.ts
|
|
15885
|
+
init_esm_shims();
|
|
15886
|
+
import * as fs29 from "fs";
|
|
15887
|
+
import * as path29 from "path";
|
|
15888
|
+
function findKitTemplatesDir(start) {
|
|
15889
|
+
let dir = start;
|
|
15890
|
+
for (let i = 0; i < 6; i++) {
|
|
15891
|
+
const candidate = path29.join(
|
|
15892
|
+
dir,
|
|
15893
|
+
"templates",
|
|
15894
|
+
"project",
|
|
15895
|
+
"common",
|
|
15896
|
+
".github",
|
|
15897
|
+
"workflows"
|
|
15895
15898
|
);
|
|
15899
|
+
if (fs29.existsSync(candidate)) return candidate;
|
|
15900
|
+
const parent = path29.dirname(dir);
|
|
15901
|
+
if (parent === dir) break;
|
|
15902
|
+
dir = parent;
|
|
15896
15903
|
}
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15904
|
+
throw new Error(
|
|
15905
|
+
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
15906
|
+
);
|
|
15907
|
+
}
|
|
15908
|
+
function unifiedDiff(name, projectContent, templateContent) {
|
|
15909
|
+
if (projectContent === templateContent) return "";
|
|
15910
|
+
const a = projectContent.split("\n");
|
|
15911
|
+
const b = templateContent.split("\n");
|
|
15912
|
+
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
15913
|
+
const max = Math.max(a.length, b.length);
|
|
15914
|
+
for (let i = 0; i < max; i++) {
|
|
15915
|
+
const av = a[i];
|
|
15916
|
+
const bv = b[i];
|
|
15917
|
+
if (av === bv) continue;
|
|
15918
|
+
if (av !== void 0) out.push(`-${i + 1}: ${av}`);
|
|
15919
|
+
if (bv !== void 0) out.push(`+${i + 1}: ${bv}`);
|
|
15902
15920
|
}
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15921
|
+
return out.join("\n");
|
|
15922
|
+
}
|
|
15923
|
+
function detectWorkflowDrift(args) {
|
|
15924
|
+
const projectWorkflowsDir = path29.join(
|
|
15925
|
+
args.projectDir,
|
|
15926
|
+
".github",
|
|
15927
|
+
"workflows"
|
|
15928
|
+
);
|
|
15929
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
15930
|
+
const kitWorkflowsDir = args.kitDir ? path29.join(
|
|
15931
|
+
args.kitDir,
|
|
15932
|
+
"templates",
|
|
15933
|
+
"project",
|
|
15934
|
+
"common",
|
|
15935
|
+
".github",
|
|
15936
|
+
"workflows"
|
|
15937
|
+
) : findKitTemplatesDir(here);
|
|
15938
|
+
const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
15939
|
+
const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
15940
|
+
const version = readKitVersion(kitWorkflowsDir);
|
|
15941
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15942
|
+
const files = [];
|
|
15943
|
+
for (const name of templateFiles) {
|
|
15944
|
+
seen.add(name);
|
|
15945
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
15946
|
+
const templatePath = path29.join(kitWorkflowsDir, name);
|
|
15947
|
+
if (!fs29.existsSync(projectPath2)) {
|
|
15948
|
+
files.push({ name, status: "missing" });
|
|
15949
|
+
continue;
|
|
15950
|
+
}
|
|
15951
|
+
const projectContent = fs29.readFileSync(projectPath2, "utf8");
|
|
15952
|
+
const templateContent = applyPlaceholders(fs29.readFileSync(templatePath, "utf8"), version);
|
|
15953
|
+
if (projectContent === templateContent) {
|
|
15954
|
+
files.push({ name, status: "unchanged" });
|
|
15955
|
+
} else {
|
|
15956
|
+
files.push({
|
|
15957
|
+
name,
|
|
15958
|
+
status: "drifted",
|
|
15959
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
15960
|
+
});
|
|
15961
|
+
}
|
|
15908
15962
|
}
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
"bad-precondition"
|
|
15913
|
-
);
|
|
15963
|
+
for (const name of projectFiles) {
|
|
15964
|
+
if (seen.has(name)) continue;
|
|
15965
|
+
files.push({ name, status: "extra" });
|
|
15914
15966
|
}
|
|
15915
|
-
const
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15967
|
+
const order = {
|
|
15968
|
+
drifted: 0,
|
|
15969
|
+
missing: 1,
|
|
15970
|
+
extra: 2,
|
|
15971
|
+
unchanged: 3
|
|
15972
|
+
};
|
|
15973
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
15974
|
+
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
15975
|
+
return {
|
|
15976
|
+
overall: hasDrift ? "drift" : "ok",
|
|
15977
|
+
files
|
|
15978
|
+
};
|
|
15979
|
+
}
|
|
15980
|
+
function readKitVersion(kitWorkflowsDir) {
|
|
15981
|
+
let dir = kitWorkflowsDir;
|
|
15982
|
+
for (let i = 0; i < 5; i++) {
|
|
15983
|
+
dir = path29.dirname(dir);
|
|
15921
15984
|
}
|
|
15922
|
-
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15985
|
+
try {
|
|
15986
|
+
const raw = fs29.readFileSync(path29.join(dir, "package.json"), "utf-8");
|
|
15987
|
+
const pkg = JSON.parse(raw);
|
|
15988
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
15989
|
+
} catch {
|
|
15990
|
+
return "unknown";
|
|
15991
|
+
}
|
|
15992
|
+
}
|
|
15993
|
+
function applyPlaceholders(content, version) {
|
|
15994
|
+
return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
|
|
15995
|
+
}
|
|
15996
|
+
function applyCommandPlaceholders(content, version) {
|
|
15997
|
+
return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
|
|
15998
|
+
}
|
|
15999
|
+
var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
|
|
16000
|
+
function findKitCommandsDir(start) {
|
|
16001
|
+
let dir = start;
|
|
16002
|
+
for (let i = 0; i < 6; i++) {
|
|
16003
|
+
const candidate = path29.join(
|
|
16004
|
+
dir,
|
|
16005
|
+
"templates",
|
|
16006
|
+
"project",
|
|
16007
|
+
"common",
|
|
16008
|
+
".claude",
|
|
16009
|
+
"commands"
|
|
15927
16010
|
);
|
|
16011
|
+
if (fs29.existsSync(candidate)) return candidate;
|
|
16012
|
+
const parent = path29.dirname(dir);
|
|
16013
|
+
if (parent === dir) break;
|
|
16014
|
+
dir = parent;
|
|
15928
16015
|
}
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
16016
|
+
throw new Error(
|
|
16017
|
+
`Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
|
|
16018
|
+
);
|
|
16019
|
+
}
|
|
16020
|
+
function parsePinnedVersion(content) {
|
|
16021
|
+
const m = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
|
|
16022
|
+
return m ? m[1] : void 0;
|
|
16023
|
+
}
|
|
16024
|
+
function detectCommandDrift(args) {
|
|
16025
|
+
const projectCommandsDir = path29.join(args.projectDir, ".claude", "commands");
|
|
16026
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
16027
|
+
const kitCommandsDir = args.kitDir ? path29.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
16028
|
+
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
16029
|
+
const templateFiles = fs29.existsSync(kitCommandsDir) ? fs29.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16030
|
+
const projectFiles = fs29.existsSync(projectCommandsDir) ? fs29.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16031
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16032
|
+
const files = [];
|
|
16033
|
+
for (const name of templateFiles) {
|
|
16034
|
+
seen.add(name);
|
|
16035
|
+
const projectPath2 = path29.join(projectCommandsDir, name);
|
|
16036
|
+
const templatePath = path29.join(kitCommandsDir, name);
|
|
16037
|
+
const templateRaw = fs29.readFileSync(templatePath, "utf8");
|
|
16038
|
+
if (!fs29.existsSync(projectPath2)) {
|
|
16039
|
+
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
16040
|
+
continue;
|
|
16041
|
+
}
|
|
16042
|
+
const projectContent = fs29.readFileSync(projectPath2, "utf8");
|
|
16043
|
+
const pinned = parsePinnedVersion(projectContent);
|
|
16044
|
+
const versionForCompare = pinned ?? kitVersion;
|
|
16045
|
+
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
16046
|
+
if (projectContent === templateContent) {
|
|
16047
|
+
files.push({
|
|
16048
|
+
name,
|
|
16049
|
+
status: "unchanged",
|
|
16050
|
+
pinned_version: pinned,
|
|
16051
|
+
kit_version: kitVersion
|
|
16052
|
+
});
|
|
16053
|
+
} else {
|
|
16054
|
+
files.push({
|
|
16055
|
+
name,
|
|
16056
|
+
status: "drifted",
|
|
16057
|
+
pinned_version: pinned,
|
|
16058
|
+
kit_version: kitVersion,
|
|
16059
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
16060
|
+
});
|
|
15940
16061
|
}
|
|
15941
|
-
authVerified = true;
|
|
15942
16062
|
}
|
|
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
|
-
);
|
|
16063
|
+
for (const name of projectFiles) {
|
|
16064
|
+
if (seen.has(name)) continue;
|
|
16065
|
+
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
15956
16066
|
}
|
|
15957
|
-
const
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
16067
|
+
const order = {
|
|
16068
|
+
drifted: 0,
|
|
16069
|
+
missing: 1,
|
|
16070
|
+
extra: 2,
|
|
16071
|
+
unchanged: 3
|
|
16072
|
+
};
|
|
16073
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
16074
|
+
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
16075
|
+
return { overall: hasDrift ? "drift" : "ok", files };
|
|
16076
|
+
}
|
|
16077
|
+
function readKitVersionFromCommandsDir(kitCommandsDir) {
|
|
16078
|
+
let dir = kitCommandsDir;
|
|
16079
|
+
for (let i = 0; i < 5; i++) {
|
|
16080
|
+
dir = path29.dirname(dir);
|
|
16081
|
+
}
|
|
16082
|
+
try {
|
|
16083
|
+
const raw = fs29.readFileSync(path29.join(dir, "package.json"), "utf-8");
|
|
16084
|
+
const pkg = JSON.parse(raw);
|
|
16085
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
16086
|
+
} catch {
|
|
16087
|
+
return "unknown";
|
|
16088
|
+
}
|
|
16089
|
+
}
|
|
16090
|
+
function detectScaffoldedDrift(args) {
|
|
16091
|
+
const workflows = detectWorkflowDrift(args);
|
|
16092
|
+
const commands = detectCommandDrift(args);
|
|
16093
|
+
return {
|
|
16094
|
+
overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
|
|
16095
|
+
workflows,
|
|
16096
|
+
commands
|
|
16097
|
+
};
|
|
16098
|
+
}
|
|
16099
|
+
function updateWorkflows(args) {
|
|
16100
|
+
const projectWorkflowsDir = path29.join(
|
|
16101
|
+
args.projectDir,
|
|
16102
|
+
".github",
|
|
16103
|
+
"workflows"
|
|
16104
|
+
);
|
|
16105
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
16106
|
+
const kitWorkflowsDir = args.kitDir ? path29.join(
|
|
16107
|
+
args.kitDir,
|
|
16108
|
+
"templates",
|
|
16109
|
+
"project",
|
|
16110
|
+
"common",
|
|
16111
|
+
".github",
|
|
16112
|
+
"workflows"
|
|
16113
|
+
) : findKitTemplatesDir(here);
|
|
16114
|
+
const substitute = args.substitute !== false;
|
|
16115
|
+
const dryRun = args.dryRun === true;
|
|
16116
|
+
const pruneExtras = args.pruneExtras === true;
|
|
16117
|
+
const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16118
|
+
const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16119
|
+
if (!dryRun && templateFiles.length > 0 && !fs29.existsSync(projectWorkflowsDir)) {
|
|
16120
|
+
fs29.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
16121
|
+
}
|
|
16122
|
+
const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
|
|
16123
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16124
|
+
const files = [];
|
|
16125
|
+
for (const name of templateFiles) {
|
|
16126
|
+
seen.add(name);
|
|
16127
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
16128
|
+
const templatePath = path29.join(kitWorkflowsDir, name);
|
|
16129
|
+
const templateRaw = fs29.readFileSync(templatePath, "utf-8");
|
|
16130
|
+
const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
|
|
16131
|
+
const existed = fs29.existsSync(projectPath2);
|
|
16132
|
+
const current = existed ? fs29.readFileSync(projectPath2, "utf-8") : "";
|
|
16133
|
+
let outcome;
|
|
16134
|
+
if (!existed) {
|
|
16135
|
+
outcome = "added";
|
|
16136
|
+
} else if (current === desired) {
|
|
16137
|
+
outcome = "unchanged";
|
|
16138
|
+
} else {
|
|
16139
|
+
outcome = "updated";
|
|
16140
|
+
}
|
|
16141
|
+
if (!dryRun && outcome !== "unchanged") {
|
|
16142
|
+
fs29.writeFileSync(projectPath2, desired);
|
|
16143
|
+
}
|
|
16144
|
+
files.push({ name, outcome });
|
|
16145
|
+
}
|
|
16146
|
+
if (pruneExtras) {
|
|
16147
|
+
for (const name of projectFiles) {
|
|
16148
|
+
if (seen.has(name)) continue;
|
|
16149
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
16150
|
+
if (!dryRun) {
|
|
16151
|
+
fs29.unlinkSync(projectPath2);
|
|
16152
|
+
}
|
|
16153
|
+
files.push({ name, outcome: "removed" });
|
|
16154
|
+
}
|
|
16155
|
+
}
|
|
16156
|
+
const order = {
|
|
16157
|
+
added: 0,
|
|
16158
|
+
updated: 1,
|
|
16159
|
+
removed: 2,
|
|
16160
|
+
unchanged: 3
|
|
16161
|
+
};
|
|
16162
|
+
files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
|
|
16163
|
+
const changed = files.some((f) => f.outcome !== "unchanged");
|
|
16164
|
+
return { files, changed };
|
|
16165
|
+
}
|
|
16166
|
+
|
|
16167
|
+
// scripts/lakebase/scm-merge.ts
|
|
16168
|
+
async function reconcileTierToOrigin(args) {
|
|
16169
|
+
const { cwd, tier } = args;
|
|
16170
|
+
try {
|
|
16171
|
+
const head = await getCurrentBranch({ cwd });
|
|
16172
|
+
if (head === tier) {
|
|
16173
|
+
await exec2(`git pull --ff-only`, { cwd, timeout: 3e4 });
|
|
16174
|
+
} else {
|
|
16175
|
+
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
16176
|
+
}
|
|
16177
|
+
return null;
|
|
16178
|
+
} catch (err) {
|
|
16179
|
+
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).`;
|
|
16180
|
+
}
|
|
16181
|
+
}
|
|
16182
|
+
var ScmMergeError = class extends Error {
|
|
16183
|
+
constructor(message, code) {
|
|
16184
|
+
super(message);
|
|
16185
|
+
this.code = code;
|
|
16186
|
+
this.name = "ScmMergeError";
|
|
16187
|
+
}
|
|
16188
|
+
code;
|
|
16189
|
+
};
|
|
16190
|
+
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
16191
|
+
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
16192
|
+
function defaultMigratePredicate(run, mergedAt) {
|
|
16193
|
+
if (!run.createdAt) return false;
|
|
16194
|
+
const created = Date.parse(run.createdAt);
|
|
16195
|
+
if (!Number.isFinite(created)) return false;
|
|
16196
|
+
if (run.event && run.event !== "push") return false;
|
|
16197
|
+
return created >= mergedAt.getTime() - 5e3;
|
|
16198
|
+
}
|
|
16199
|
+
function shaMigratePredicate(mergeCommitSha) {
|
|
16200
|
+
return (run) => {
|
|
16201
|
+
if (run.event && run.event !== "push") return false;
|
|
16202
|
+
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
16203
|
+
};
|
|
16204
|
+
}
|
|
16205
|
+
async function defaultRefreshPromoteWorkflows(projectDir) {
|
|
16206
|
+
const drift = detectWorkflowDrift({ projectDir });
|
|
16207
|
+
const touched = drift.files.filter((f) => f.status === "drifted").map((f) => f.name);
|
|
16208
|
+
if (touched.length === 0) return { refreshed: false };
|
|
16209
|
+
const result = updateWorkflows({ projectDir });
|
|
16210
|
+
if (!result.changed) return { refreshed: false };
|
|
16211
|
+
await exec2(`git add ${shellEscape2(".github/workflows")}`, { cwd: projectDir, timeout: 1e4 });
|
|
16212
|
+
const staged = await exec2("git diff --cached --name-only -- .github/workflows", {
|
|
16213
|
+
cwd: projectDir,
|
|
16214
|
+
timeout: 1e4
|
|
16215
|
+
});
|
|
16216
|
+
if (!staged.trim()) return { refreshed: false };
|
|
16217
|
+
await exec2(
|
|
16218
|
+
`git commit -m ${shellEscape2(
|
|
16219
|
+
"chore(ci): refresh scaffolded workflows to pinned substrate (promote uses apply-tier)"
|
|
16220
|
+
)} -- .github/workflows`,
|
|
16221
|
+
{ cwd: projectDir, timeout: 15e3 }
|
|
16222
|
+
);
|
|
16223
|
+
await pushCurrentBranchForPr({ cwd: projectDir });
|
|
16224
|
+
return { refreshed: true, detail: `refreshed + pushed ${touched.join(", ") || "workflow(s)"} onto the PR head` };
|
|
16225
|
+
}
|
|
16226
|
+
async function mergeFeature(args) {
|
|
16227
|
+
const current = readWorkflowState(args.projectDir);
|
|
16228
|
+
if (!current) {
|
|
16229
|
+
throw new ScmMergeError(
|
|
16230
|
+
"No SCM workflow state. wait-ci first.",
|
|
16231
|
+
"no-state-file"
|
|
16232
|
+
);
|
|
16233
|
+
}
|
|
16234
|
+
if (current.state !== "ci-green") {
|
|
16235
|
+
throw new ScmMergeError(
|
|
16236
|
+
`merge refuses state "${current.state}". Allowed predecessor: ci-green.`,
|
|
16237
|
+
"bad-precondition"
|
|
16238
|
+
);
|
|
16239
|
+
}
|
|
16240
|
+
if (!current.pr_url) {
|
|
16241
|
+
throw new ScmMergeError(
|
|
16242
|
+
"ci-green row is missing pr_url; cannot resolve the PR to merge.",
|
|
16243
|
+
"no-pr-url"
|
|
16244
|
+
);
|
|
16245
|
+
}
|
|
16246
|
+
if (!current.branch || !current.parent_branch) {
|
|
16247
|
+
throw new ScmMergeError(
|
|
16248
|
+
"ci-green row missing branch / parent_branch; refusing to merge.",
|
|
16249
|
+
"bad-precondition"
|
|
16250
|
+
);
|
|
16251
|
+
}
|
|
16252
|
+
const ownerRepo = await getOwnerRepo(args.projectDir);
|
|
16253
|
+
if (!ownerRepo) {
|
|
16254
|
+
throw new ScmMergeError(
|
|
16255
|
+
"No GitHub remote found at origin.",
|
|
16256
|
+
"no-github-remote"
|
|
16257
|
+
);
|
|
16258
|
+
}
|
|
16259
|
+
const pullNumber = extractPullNumber(current.pr_url);
|
|
16260
|
+
if (!pullNumber) {
|
|
16261
|
+
throw new ScmMergeError(
|
|
16262
|
+
`Could not extract PR number from URL: ${current.pr_url}`,
|
|
16263
|
+
"bad-pr-url"
|
|
16264
|
+
);
|
|
16265
|
+
}
|
|
16266
|
+
const instance = args.instance ?? current.project_id;
|
|
16267
|
+
const gitBase = await resolveGitBase(current.parent_branch, args.projectDir);
|
|
16268
|
+
const wantMigrate = args.waitMigrate !== false;
|
|
16269
|
+
let authVerified = false;
|
|
16270
|
+
if (wantMigrate && args.verifyMigrateAuth) {
|
|
16271
|
+
const probe = await args.verifyMigrateAuth();
|
|
16272
|
+
if (!probe.ok) {
|
|
16273
|
+
throw new ScmMergeError(
|
|
16274
|
+
`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.`,
|
|
16275
|
+
"migrate-auth"
|
|
16276
|
+
);
|
|
16277
|
+
}
|
|
16278
|
+
authVerified = true;
|
|
16279
|
+
}
|
|
16280
|
+
const preflightNotes = [];
|
|
16281
|
+
{
|
|
16282
|
+
const refresh = args.refreshWorkflows ?? (() => defaultRefreshPromoteWorkflows(args.projectDir));
|
|
16283
|
+
try {
|
|
16284
|
+
const r = await refresh();
|
|
16285
|
+
if (r.refreshed) {
|
|
16286
|
+
preflightNotes.push(`Refreshed stale CI workflows before merge${r.detail ? ` (${r.detail})` : ""}.`);
|
|
16287
|
+
}
|
|
16288
|
+
} catch (err) {
|
|
16289
|
+
preflightNotes.push(
|
|
16290
|
+
`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').`
|
|
16291
|
+
);
|
|
16292
|
+
}
|
|
16293
|
+
}
|
|
16294
|
+
let paired;
|
|
16295
|
+
try {
|
|
16296
|
+
paired = await mergePairedPullRequest({
|
|
16297
|
+
ownerRepo,
|
|
16298
|
+
pullNumber,
|
|
16299
|
+
lakebaseInstance: instance,
|
|
16300
|
+
method: args.method ?? "squash"
|
|
16301
|
+
});
|
|
16302
|
+
} catch (err) {
|
|
16303
|
+
throw new ScmMergeError(
|
|
16304
|
+
`mergePairedPullRequest failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
16305
|
+
"merge-failed"
|
|
16306
|
+
);
|
|
16307
|
+
}
|
|
16308
|
+
const warnings = [...preflightNotes, ...paired.warnings];
|
|
16309
|
+
let localBranchDeleted = false;
|
|
16310
|
+
let headAfter = current.branch;
|
|
16311
|
+
if (!args.skipLocalCleanup) {
|
|
16312
|
+
const switchTo = args.switchTo ?? gitBase;
|
|
16313
|
+
const head = await getCurrentBranch({ cwd: args.projectDir });
|
|
16314
|
+
if (head === current.branch) {
|
|
15964
16315
|
try {
|
|
15965
16316
|
await exec2(`git checkout ${shellEscape2(switchTo)}`, {
|
|
15966
16317
|
cwd: args.projectDir,
|
|
@@ -16151,7 +16502,7 @@ init_esm_shims();
|
|
|
16151
16502
|
|
|
16152
16503
|
// scripts/git/branches.ts
|
|
16153
16504
|
init_esm_shims();
|
|
16154
|
-
async function
|
|
16505
|
+
async function currentBranchName2(cwd) {
|
|
16155
16506
|
try {
|
|
16156
16507
|
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
16157
16508
|
} catch {
|
|
@@ -16170,7 +16521,7 @@ async function listLocalBranches(args) {
|
|
|
16170
16521
|
return [];
|
|
16171
16522
|
}
|
|
16172
16523
|
if (!raw) return [];
|
|
16173
|
-
const current = await
|
|
16524
|
+
const current = await currentBranchName2(cwd);
|
|
16174
16525
|
return raw.split("\n").filter(Boolean).map((line) => {
|
|
16175
16526
|
const [name, tracking, trackInfo] = line.split("|");
|
|
16176
16527
|
let ahead = 0;
|
|
@@ -16335,15 +16686,15 @@ function parentForTopology(t, defaultLeaf) {
|
|
|
16335
16686
|
|
|
16336
16687
|
// scripts/lakebase/scm-doctor.ts
|
|
16337
16688
|
init_esm_shims();
|
|
16338
|
-
import * as
|
|
16339
|
-
import * as
|
|
16689
|
+
import * as fs30 from "fs";
|
|
16690
|
+
import * as path30 from "path";
|
|
16340
16691
|
var FEATURE_PREFIX = "feature/";
|
|
16341
16692
|
var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
|
|
16342
16693
|
function readEnv(projectDir) {
|
|
16343
|
-
const envPath =
|
|
16694
|
+
const envPath = path30.join(projectDir, ".env");
|
|
16344
16695
|
const out = /* @__PURE__ */ new Map();
|
|
16345
|
-
if (!
|
|
16346
|
-
const lines =
|
|
16696
|
+
if (!fs30.existsSync(envPath)) return out;
|
|
16697
|
+
const lines = fs30.readFileSync(envPath, "utf8").split("\n");
|
|
16347
16698
|
for (const line of lines) {
|
|
16348
16699
|
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
|
|
16349
16700
|
if (m) out.set(m[1], m[2].replace(/^["']|["']$/g, ""));
|
|
@@ -16398,9 +16749,9 @@ async function runDoctor(args, deps = {}) {
|
|
|
16398
16749
|
}
|
|
16399
16750
|
for (const wf of ["pr.yml", "merge.yml"]) {
|
|
16400
16751
|
try {
|
|
16401
|
-
const p =
|
|
16402
|
-
if (!
|
|
16403
|
-
const body =
|
|
16752
|
+
const p = path30.join(projectDir, ".github", "workflows", wf);
|
|
16753
|
+
if (!fs30.existsSync(p)) continue;
|
|
16754
|
+
const body = fs30.readFileSync(p, "utf8");
|
|
16404
16755
|
if (/lakebase-scm-utils#v\d/.test(body)) {
|
|
16405
16756
|
findings.push({
|
|
16406
16757
|
id: "ci-workflow-substrate-pin",
|
|
@@ -16615,7 +16966,7 @@ async function fixFinding(args) {
|
|
|
16615
16966
|
);
|
|
16616
16967
|
}
|
|
16617
16968
|
const sanitized = sanitizeBranchName(branch);
|
|
16618
|
-
const envFile =
|
|
16969
|
+
const envFile = path30.join(args.projectDir, ".env");
|
|
16619
16970
|
updateEnvConnection({
|
|
16620
16971
|
envPath: envFile,
|
|
16621
16972
|
projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
|
|
@@ -16720,291 +17071,6 @@ init_esm_shims();
|
|
|
16720
17071
|
import * as fs31 from "fs";
|
|
16721
17072
|
import * as path31 from "path";
|
|
16722
17073
|
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
17074
|
function readEnvFile(projectDir) {
|
|
17009
17075
|
const envPath = path31.join(projectDir, ".env");
|
|
17010
17076
|
if (!fs31.existsSync(envPath)) return {};
|
|
@@ -17437,7 +17503,7 @@ function checkWorkflowDrift(projectDir) {
|
|
|
17437
17503
|
status: "warn",
|
|
17438
17504
|
message: `Scaffolded workflows drift from kit: ${drifted} drifted, ${missing} missing`,
|
|
17439
17505
|
detail: { files: report.files.map((f) => ({ name: f.name, status: f.status })) },
|
|
17440
|
-
hint: "
|
|
17506
|
+
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
17507
|
};
|
|
17442
17508
|
} catch (err) {
|
|
17443
17509
|
return {
|
|
@@ -17745,6 +17811,7 @@ export {
|
|
|
17745
17811
|
createUatPairedBranch,
|
|
17746
17812
|
cutBackup,
|
|
17747
17813
|
databricksAuthPrereqMessage,
|
|
17814
|
+
defaultRefreshPromoteWorkflows,
|
|
17748
17815
|
deleteAppEndpoint,
|
|
17749
17816
|
deleteBranch,
|
|
17750
17817
|
deleteLakebaseProject,
|