@databricks-solutions/lakebase-scm-utils 0.2.43 → 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/github/index.cjs +21 -4
- package/dist/scripts/github/index.js +21 -4
- package/dist/scripts/github/pr.cli.cjs +21 -4
- package/dist/scripts/github/pr.cli.js +21 -4
- package/dist/scripts/index.cjs +1224 -1157
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +1223 -1157
- package/dist/scripts/lakebase/branch.cli.cjs +21 -4
- package/dist/scripts/lakebase/branch.cli.js +21 -4
- package/dist/scripts/lakebase/ci-app-endpoint.cli.cjs +21 -4
- package/dist/scripts/lakebase/ci-app-endpoint.cli.js +21 -4
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +23 -6
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +23 -6
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +23 -6
- package/dist/scripts/lakebase/collapse-heads.cli.js +23 -6
- package/dist/scripts/lakebase/cut-backup.cli.cjs +21 -4
- package/dist/scripts/lakebase/cut-backup.cli.js +21 -4
- package/dist/scripts/lakebase/cut-tier.cli.cjs +21 -4
- package/dist/scripts/lakebase/cut-tier.cli.js +21 -4
- package/dist/scripts/lakebase/detect-language.cli.cjs +23 -6
- package/dist/scripts/lakebase/detect-language.cli.js +23 -6
- package/dist/scripts/lakebase/doctor.cli.cjs +24 -7
- package/dist/scripts/lakebase/doctor.cli.js +24 -7
- package/dist/scripts/lakebase/get-connection.cli.cjs +23 -6
- package/dist/scripts/lakebase/get-connection.cli.js +23 -6
- package/dist/scripts/lakebase/index.cjs +489 -391
- package/dist/scripts/lakebase/index.d.cts +33 -1
- package/dist/scripts/lakebase/index.d.ts +33 -1
- package/dist/scripts/lakebase/index.js +488 -391
- package/dist/scripts/lakebase/infra-runner.cli.cjs +23 -6
- package/dist/scripts/lakebase/infra-runner.cli.js +23 -6
- package/dist/scripts/lakebase/new-migration.cli.cjs +23 -6
- package/dist/scripts/lakebase/new-migration.cli.js +23 -6
- package/dist/scripts/lakebase/schema-diff.cli.cjs +23 -6
- package/dist/scripts/lakebase/schema-diff.cli.js +23 -6
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +23 -6
- package/dist/scripts/lakebase/schema-migrate.cli.js +23 -6
- package/dist/scripts/lakebase/scm-abandon-feature.cli.cjs +21 -4
- package/dist/scripts/lakebase/scm-abandon-feature.cli.js +21 -4
- package/dist/scripts/lakebase/scm-adopt-state.cli.cjs +21 -4
- package/dist/scripts/lakebase/scm-adopt-state.cli.js +21 -4
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +23 -6
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +23 -6
- package/dist/scripts/lakebase/scm-cleanup.cli.cjs +21 -4
- package/dist/scripts/lakebase/scm-cleanup.cli.js +21 -4
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +23 -6
- package/dist/scripts/lakebase/scm-doctor.cli.js +23 -6
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2305 -2037
- package/dist/scripts/lakebase/scm-merge.cli.js +360 -92
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +23 -6
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +23 -6
- package/dist/scripts/lakebase/scm-recover-orphans.cli.cjs +21 -4
- package/dist/scripts/lakebase/scm-recover-orphans.cli.js +21 -4
- package/dist/scripts/util/index.cjs +23 -6
- package/dist/scripts/util/index.js +23 -6
- package/package.json +1 -1
- package/scripts/lakebase/databricks-cli.ts +44 -4
- package/scripts/lakebase/doctor.ts +1 -1
- package/scripts/lakebase/runner-setup.ts +18 -2
- package/scripts/lakebase/scm-merge.ts +80 -1
- package/scripts/lakebase/secret-auth.ts +9 -2
- package/templates/project/common/scripts/run-tests.sh +10 -0
|
@@ -1784,16 +1784,33 @@ stdout: ${stdout}` : killed || signal ? `
|
|
|
1784
1784
|
}
|
|
1785
1785
|
async function runDatabricks(args, opts = {}) {
|
|
1786
1786
|
const { argv, env, profile } = buildInvocation(args, opts);
|
|
1787
|
+
const timeout = opts.timeout ?? KIT_TIMEOUTS.cliDefault;
|
|
1787
1788
|
try {
|
|
1788
|
-
|
|
1789
|
-
env,
|
|
1790
|
-
|
|
1791
|
-
});
|
|
1789
|
+
if (opts.input !== void 0) {
|
|
1790
|
+
return await execDatabricksWithStdin(argv, opts.input, env, timeout);
|
|
1791
|
+
}
|
|
1792
|
+
const { stdout } = await execFileP("databricks", argv, { env, timeout });
|
|
1792
1793
|
return stdout.toString();
|
|
1793
1794
|
} catch (err) {
|
|
1794
1795
|
throw classifyDatabricksError(err, argv, profile);
|
|
1795
1796
|
}
|
|
1796
1797
|
}
|
|
1798
|
+
function execDatabricksWithStdin(argv, input, env, timeout) {
|
|
1799
|
+
return new Promise((resolve2, reject) => {
|
|
1800
|
+
const child = execFile("databricks", argv, { env, timeout }, (err, stdout, stderr) => {
|
|
1801
|
+
if (err) {
|
|
1802
|
+
err.stdout = String(stdout ?? "");
|
|
1803
|
+
err.stderr = String(stderr ?? "");
|
|
1804
|
+
reject(err);
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
resolve2(String(stdout ?? ""));
|
|
1808
|
+
});
|
|
1809
|
+
child.stdin?.on("error", () => {
|
|
1810
|
+
});
|
|
1811
|
+
child.stdin?.end(input);
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1797
1814
|
function runDatabricksSync(args, opts = {}) {
|
|
1798
1815
|
const { argv, env, profile } = buildInvocation(args, opts);
|
|
1799
1816
|
try {
|
|
@@ -2500,8 +2517,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
2500
2517
|
var cached;
|
|
2501
2518
|
function substrateSelfVersion() {
|
|
2502
2519
|
if (cached !== void 0) return cached;
|
|
2503
|
-
if ("0.2.
|
|
2504
|
-
cached = "0.2.
|
|
2520
|
+
if ("0.2.45".length > 0) {
|
|
2521
|
+
cached = "0.2.45";
|
|
2505
2522
|
return cached;
|
|
2506
2523
|
}
|
|
2507
2524
|
cached = "unknown";
|
|
@@ -2729,6 +2746,14 @@ async function getCredential(args) {
|
|
|
2729
2746
|
|
|
2730
2747
|
// scripts/git/status.ts
|
|
2731
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
|
+
}
|
|
2732
2757
|
async function getAheadBehind(args) {
|
|
2733
2758
|
const { cwd } = args;
|
|
2734
2759
|
try {
|
|
@@ -12760,8 +12785,20 @@ async function setupRunner(args) {
|
|
|
12760
12785
|
if (needsConfig) {
|
|
12761
12786
|
report("Registering runner with GitHub...");
|
|
12762
12787
|
const regToken = await createRegistrationToken(args.fullRepoName);
|
|
12763
|
-
cp4.
|
|
12764
|
-
|
|
12788
|
+
cp4.execFileSync(
|
|
12789
|
+
"./config.sh",
|
|
12790
|
+
[
|
|
12791
|
+
"--url",
|
|
12792
|
+
`https://github.com/${args.fullRepoName}`,
|
|
12793
|
+
"--token",
|
|
12794
|
+
regToken,
|
|
12795
|
+
"--name",
|
|
12796
|
+
name,
|
|
12797
|
+
"--labels",
|
|
12798
|
+
"self-hosted",
|
|
12799
|
+
"--unattended",
|
|
12800
|
+
"--replace"
|
|
12801
|
+
],
|
|
12765
12802
|
{ cwd: dir, timeout: KIT_TIMEOUTS.cliLong }
|
|
12766
12803
|
);
|
|
12767
12804
|
}
|
|
@@ -15820,104 +15857,447 @@ function pickRunUrl(pr) {
|
|
|
15820
15857
|
|
|
15821
15858
|
// scripts/lakebase/scm-merge.ts
|
|
15822
15859
|
init_esm_shims();
|
|
15823
|
-
|
|
15824
|
-
|
|
15860
|
+
|
|
15861
|
+
// scripts/git/sync.ts
|
|
15862
|
+
init_esm_shims();
|
|
15863
|
+
async function currentBranchName(cwd) {
|
|
15825
15864
|
try {
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
15829
|
-
} else {
|
|
15830
|
-
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
15831
|
-
}
|
|
15832
|
-
return null;
|
|
15833
|
-
} catch (err) {
|
|
15834
|
-
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 "";
|
|
15835
15868
|
}
|
|
15836
15869
|
}
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
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 });
|
|
15842
15881
|
}
|
|
15843
|
-
code;
|
|
15844
|
-
};
|
|
15845
|
-
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
15846
|
-
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
15847
|
-
function defaultMigratePredicate(run, mergedAt) {
|
|
15848
|
-
if (!run.createdAt) return false;
|
|
15849
|
-
const created = Date.parse(run.createdAt);
|
|
15850
|
-
if (!Number.isFinite(created)) return false;
|
|
15851
|
-
if (run.event && run.event !== "push") return false;
|
|
15852
|
-
return created >= mergedAt.getTime() - 5e3;
|
|
15853
|
-
}
|
|
15854
|
-
function shaMigratePredicate(mergeCommitSha) {
|
|
15855
|
-
return (run) => {
|
|
15856
|
-
if (run.event && run.event !== "push") return false;
|
|
15857
|
-
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
15858
|
-
};
|
|
15859
15882
|
}
|
|
15860
|
-
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
|
|
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"
|
|
15866
15898
|
);
|
|
15899
|
+
if (fs29.existsSync(candidate)) return candidate;
|
|
15900
|
+
const parent = path29.dirname(dir);
|
|
15901
|
+
if (parent === dir) break;
|
|
15902
|
+
dir = parent;
|
|
15867
15903
|
}
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
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}`);
|
|
15873
15920
|
}
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
15878
|
-
|
|
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
|
+
}
|
|
15879
15962
|
}
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
"bad-precondition"
|
|
15884
|
-
);
|
|
15963
|
+
for (const name of projectFiles) {
|
|
15964
|
+
if (seen.has(name)) continue;
|
|
15965
|
+
files.push({ name, status: "extra" });
|
|
15885
15966
|
}
|
|
15886
|
-
const
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
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);
|
|
15892
15984
|
}
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
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"
|
|
15898
16010
|
);
|
|
16011
|
+
if (fs29.existsSync(candidate)) return candidate;
|
|
16012
|
+
const parent = path29.dirname(dir);
|
|
16013
|
+
if (parent === dir) break;
|
|
16014
|
+
dir = parent;
|
|
15899
16015
|
}
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
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
|
+
});
|
|
15911
16061
|
}
|
|
15912
|
-
authVerified = true;
|
|
15913
16062
|
}
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
16063
|
+
for (const name of projectFiles) {
|
|
16064
|
+
if (seen.has(name)) continue;
|
|
16065
|
+
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
16066
|
+
}
|
|
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"
|
|
15921
16301
|
});
|
|
15922
16302
|
} catch (err) {
|
|
15923
16303
|
throw new ScmMergeError(
|
|
@@ -15925,7 +16305,7 @@ async function mergeFeature(args) {
|
|
|
15925
16305
|
"merge-failed"
|
|
15926
16306
|
);
|
|
15927
16307
|
}
|
|
15928
|
-
const warnings = [...paired.warnings];
|
|
16308
|
+
const warnings = [...preflightNotes, ...paired.warnings];
|
|
15929
16309
|
let localBranchDeleted = false;
|
|
15930
16310
|
let headAfter = current.branch;
|
|
15931
16311
|
if (!args.skipLocalCleanup) {
|
|
@@ -16122,7 +16502,7 @@ init_esm_shims();
|
|
|
16122
16502
|
|
|
16123
16503
|
// scripts/git/branches.ts
|
|
16124
16504
|
init_esm_shims();
|
|
16125
|
-
async function
|
|
16505
|
+
async function currentBranchName2(cwd) {
|
|
16126
16506
|
try {
|
|
16127
16507
|
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
16128
16508
|
} catch {
|
|
@@ -16141,7 +16521,7 @@ async function listLocalBranches(args) {
|
|
|
16141
16521
|
return [];
|
|
16142
16522
|
}
|
|
16143
16523
|
if (!raw) return [];
|
|
16144
|
-
const current = await
|
|
16524
|
+
const current = await currentBranchName2(cwd);
|
|
16145
16525
|
return raw.split("\n").filter(Boolean).map((line) => {
|
|
16146
16526
|
const [name, tracking, trackInfo] = line.split("|");
|
|
16147
16527
|
let ahead = 0;
|
|
@@ -16306,15 +16686,15 @@ function parentForTopology(t, defaultLeaf) {
|
|
|
16306
16686
|
|
|
16307
16687
|
// scripts/lakebase/scm-doctor.ts
|
|
16308
16688
|
init_esm_shims();
|
|
16309
|
-
import * as
|
|
16310
|
-
import * as
|
|
16689
|
+
import * as fs30 from "fs";
|
|
16690
|
+
import * as path30 from "path";
|
|
16311
16691
|
var FEATURE_PREFIX = "feature/";
|
|
16312
16692
|
var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
|
|
16313
16693
|
function readEnv(projectDir) {
|
|
16314
|
-
const envPath =
|
|
16694
|
+
const envPath = path30.join(projectDir, ".env");
|
|
16315
16695
|
const out = /* @__PURE__ */ new Map();
|
|
16316
|
-
if (!
|
|
16317
|
-
const lines =
|
|
16696
|
+
if (!fs30.existsSync(envPath)) return out;
|
|
16697
|
+
const lines = fs30.readFileSync(envPath, "utf8").split("\n");
|
|
16318
16698
|
for (const line of lines) {
|
|
16319
16699
|
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
|
|
16320
16700
|
if (m) out.set(m[1], m[2].replace(/^["']|["']$/g, ""));
|
|
@@ -16369,9 +16749,9 @@ async function runDoctor(args, deps = {}) {
|
|
|
16369
16749
|
}
|
|
16370
16750
|
for (const wf of ["pr.yml", "merge.yml"]) {
|
|
16371
16751
|
try {
|
|
16372
|
-
const p =
|
|
16373
|
-
if (!
|
|
16374
|
-
const body =
|
|
16752
|
+
const p = path30.join(projectDir, ".github", "workflows", wf);
|
|
16753
|
+
if (!fs30.existsSync(p)) continue;
|
|
16754
|
+
const body = fs30.readFileSync(p, "utf8");
|
|
16375
16755
|
if (/lakebase-scm-utils#v\d/.test(body)) {
|
|
16376
16756
|
findings.push({
|
|
16377
16757
|
id: "ci-workflow-substrate-pin",
|
|
@@ -16586,7 +16966,7 @@ async function fixFinding(args) {
|
|
|
16586
16966
|
);
|
|
16587
16967
|
}
|
|
16588
16968
|
const sanitized = sanitizeBranchName(branch);
|
|
16589
|
-
const envFile =
|
|
16969
|
+
const envFile = path30.join(args.projectDir, ".env");
|
|
16590
16970
|
updateEnvConnection({
|
|
16591
16971
|
envPath: envFile,
|
|
16592
16972
|
projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
|
|
@@ -16691,291 +17071,6 @@ init_esm_shims();
|
|
|
16691
17071
|
import * as fs31 from "fs";
|
|
16692
17072
|
import * as path31 from "path";
|
|
16693
17073
|
import * as cp6 from "child_process";
|
|
16694
|
-
|
|
16695
|
-
// scripts/lakebase/workflow-drift.ts
|
|
16696
|
-
init_esm_shims();
|
|
16697
|
-
import * as fs30 from "fs";
|
|
16698
|
-
import * as path30 from "path";
|
|
16699
|
-
function findKitTemplatesDir(start) {
|
|
16700
|
-
let dir = start;
|
|
16701
|
-
for (let i = 0; i < 6; i++) {
|
|
16702
|
-
const candidate = path30.join(
|
|
16703
|
-
dir,
|
|
16704
|
-
"templates",
|
|
16705
|
-
"project",
|
|
16706
|
-
"common",
|
|
16707
|
-
".github",
|
|
16708
|
-
"workflows"
|
|
16709
|
-
);
|
|
16710
|
-
if (fs30.existsSync(candidate)) return candidate;
|
|
16711
|
-
const parent = path30.dirname(dir);
|
|
16712
|
-
if (parent === dir) break;
|
|
16713
|
-
dir = parent;
|
|
16714
|
-
}
|
|
16715
|
-
throw new Error(
|
|
16716
|
-
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
16717
|
-
);
|
|
16718
|
-
}
|
|
16719
|
-
function unifiedDiff(name, projectContent, templateContent) {
|
|
16720
|
-
if (projectContent === templateContent) return "";
|
|
16721
|
-
const a = projectContent.split("\n");
|
|
16722
|
-
const b = templateContent.split("\n");
|
|
16723
|
-
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
16724
|
-
const max = Math.max(a.length, b.length);
|
|
16725
|
-
for (let i = 0; i < max; i++) {
|
|
16726
|
-
const av = a[i];
|
|
16727
|
-
const bv = b[i];
|
|
16728
|
-
if (av === bv) continue;
|
|
16729
|
-
if (av !== void 0) out.push(`-${i + 1}: ${av}`);
|
|
16730
|
-
if (bv !== void 0) out.push(`+${i + 1}: ${bv}`);
|
|
16731
|
-
}
|
|
16732
|
-
return out.join("\n");
|
|
16733
|
-
}
|
|
16734
|
-
function detectWorkflowDrift(args) {
|
|
16735
|
-
const projectWorkflowsDir = path30.join(
|
|
16736
|
-
args.projectDir,
|
|
16737
|
-
".github",
|
|
16738
|
-
"workflows"
|
|
16739
|
-
);
|
|
16740
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
16741
|
-
const kitWorkflowsDir = args.kitDir ? path30.join(
|
|
16742
|
-
args.kitDir,
|
|
16743
|
-
"templates",
|
|
16744
|
-
"project",
|
|
16745
|
-
"common",
|
|
16746
|
-
".github",
|
|
16747
|
-
"workflows"
|
|
16748
|
-
) : findKitTemplatesDir(here);
|
|
16749
|
-
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16750
|
-
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16751
|
-
const version = readKitVersion(kitWorkflowsDir);
|
|
16752
|
-
const seen = /* @__PURE__ */ new Set();
|
|
16753
|
-
const files = [];
|
|
16754
|
-
for (const name of templateFiles) {
|
|
16755
|
-
seen.add(name);
|
|
16756
|
-
const projectPath2 = path30.join(projectWorkflowsDir, name);
|
|
16757
|
-
const templatePath = path30.join(kitWorkflowsDir, name);
|
|
16758
|
-
if (!fs30.existsSync(projectPath2)) {
|
|
16759
|
-
files.push({ name, status: "missing" });
|
|
16760
|
-
continue;
|
|
16761
|
-
}
|
|
16762
|
-
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
16763
|
-
const templateContent = applyPlaceholders(fs30.readFileSync(templatePath, "utf8"), version);
|
|
16764
|
-
if (projectContent === templateContent) {
|
|
16765
|
-
files.push({ name, status: "unchanged" });
|
|
16766
|
-
} else {
|
|
16767
|
-
files.push({
|
|
16768
|
-
name,
|
|
16769
|
-
status: "drifted",
|
|
16770
|
-
diff: unifiedDiff(name, projectContent, templateContent)
|
|
16771
|
-
});
|
|
16772
|
-
}
|
|
16773
|
-
}
|
|
16774
|
-
for (const name of projectFiles) {
|
|
16775
|
-
if (seen.has(name)) continue;
|
|
16776
|
-
files.push({ name, status: "extra" });
|
|
16777
|
-
}
|
|
16778
|
-
const order = {
|
|
16779
|
-
drifted: 0,
|
|
16780
|
-
missing: 1,
|
|
16781
|
-
extra: 2,
|
|
16782
|
-
unchanged: 3
|
|
16783
|
-
};
|
|
16784
|
-
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
16785
|
-
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
16786
|
-
return {
|
|
16787
|
-
overall: hasDrift ? "drift" : "ok",
|
|
16788
|
-
files
|
|
16789
|
-
};
|
|
16790
|
-
}
|
|
16791
|
-
function readKitVersion(kitWorkflowsDir) {
|
|
16792
|
-
let dir = kitWorkflowsDir;
|
|
16793
|
-
for (let i = 0; i < 5; i++) {
|
|
16794
|
-
dir = path30.dirname(dir);
|
|
16795
|
-
}
|
|
16796
|
-
try {
|
|
16797
|
-
const raw = fs30.readFileSync(path30.join(dir, "package.json"), "utf-8");
|
|
16798
|
-
const pkg = JSON.parse(raw);
|
|
16799
|
-
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
16800
|
-
} catch {
|
|
16801
|
-
return "unknown";
|
|
16802
|
-
}
|
|
16803
|
-
}
|
|
16804
|
-
function applyPlaceholders(content, version) {
|
|
16805
|
-
return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
|
|
16806
|
-
}
|
|
16807
|
-
function applyCommandPlaceholders(content, version) {
|
|
16808
|
-
return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
|
|
16809
|
-
}
|
|
16810
|
-
var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
|
|
16811
|
-
function findKitCommandsDir(start) {
|
|
16812
|
-
let dir = start;
|
|
16813
|
-
for (let i = 0; i < 6; i++) {
|
|
16814
|
-
const candidate = path30.join(
|
|
16815
|
-
dir,
|
|
16816
|
-
"templates",
|
|
16817
|
-
"project",
|
|
16818
|
-
"common",
|
|
16819
|
-
".claude",
|
|
16820
|
-
"commands"
|
|
16821
|
-
);
|
|
16822
|
-
if (fs30.existsSync(candidate)) return candidate;
|
|
16823
|
-
const parent = path30.dirname(dir);
|
|
16824
|
-
if (parent === dir) break;
|
|
16825
|
-
dir = parent;
|
|
16826
|
-
}
|
|
16827
|
-
throw new Error(
|
|
16828
|
-
`Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
|
|
16829
|
-
);
|
|
16830
|
-
}
|
|
16831
|
-
function parsePinnedVersion(content) {
|
|
16832
|
-
const m = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
|
|
16833
|
-
return m ? m[1] : void 0;
|
|
16834
|
-
}
|
|
16835
|
-
function detectCommandDrift(args) {
|
|
16836
|
-
const projectCommandsDir = path30.join(args.projectDir, ".claude", "commands");
|
|
16837
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
16838
|
-
const kitCommandsDir = args.kitDir ? path30.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
16839
|
-
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
16840
|
-
const templateFiles = fs30.existsSync(kitCommandsDir) ? fs30.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16841
|
-
const projectFiles = fs30.existsSync(projectCommandsDir) ? fs30.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16842
|
-
const seen = /* @__PURE__ */ new Set();
|
|
16843
|
-
const files = [];
|
|
16844
|
-
for (const name of templateFiles) {
|
|
16845
|
-
seen.add(name);
|
|
16846
|
-
const projectPath2 = path30.join(projectCommandsDir, name);
|
|
16847
|
-
const templatePath = path30.join(kitCommandsDir, name);
|
|
16848
|
-
const templateRaw = fs30.readFileSync(templatePath, "utf8");
|
|
16849
|
-
if (!fs30.existsSync(projectPath2)) {
|
|
16850
|
-
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
16851
|
-
continue;
|
|
16852
|
-
}
|
|
16853
|
-
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
16854
|
-
const pinned = parsePinnedVersion(projectContent);
|
|
16855
|
-
const versionForCompare = pinned ?? kitVersion;
|
|
16856
|
-
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
16857
|
-
if (projectContent === templateContent) {
|
|
16858
|
-
files.push({
|
|
16859
|
-
name,
|
|
16860
|
-
status: "unchanged",
|
|
16861
|
-
pinned_version: pinned,
|
|
16862
|
-
kit_version: kitVersion
|
|
16863
|
-
});
|
|
16864
|
-
} else {
|
|
16865
|
-
files.push({
|
|
16866
|
-
name,
|
|
16867
|
-
status: "drifted",
|
|
16868
|
-
pinned_version: pinned,
|
|
16869
|
-
kit_version: kitVersion,
|
|
16870
|
-
diff: unifiedDiff(name, projectContent, templateContent)
|
|
16871
|
-
});
|
|
16872
|
-
}
|
|
16873
|
-
}
|
|
16874
|
-
for (const name of projectFiles) {
|
|
16875
|
-
if (seen.has(name)) continue;
|
|
16876
|
-
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
16877
|
-
}
|
|
16878
|
-
const order = {
|
|
16879
|
-
drifted: 0,
|
|
16880
|
-
missing: 1,
|
|
16881
|
-
extra: 2,
|
|
16882
|
-
unchanged: 3
|
|
16883
|
-
};
|
|
16884
|
-
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
16885
|
-
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
16886
|
-
return { overall: hasDrift ? "drift" : "ok", files };
|
|
16887
|
-
}
|
|
16888
|
-
function readKitVersionFromCommandsDir(kitCommandsDir) {
|
|
16889
|
-
let dir = kitCommandsDir;
|
|
16890
|
-
for (let i = 0; i < 5; i++) {
|
|
16891
|
-
dir = path30.dirname(dir);
|
|
16892
|
-
}
|
|
16893
|
-
try {
|
|
16894
|
-
const raw = fs30.readFileSync(path30.join(dir, "package.json"), "utf-8");
|
|
16895
|
-
const pkg = JSON.parse(raw);
|
|
16896
|
-
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
16897
|
-
} catch {
|
|
16898
|
-
return "unknown";
|
|
16899
|
-
}
|
|
16900
|
-
}
|
|
16901
|
-
function detectScaffoldedDrift(args) {
|
|
16902
|
-
const workflows = detectWorkflowDrift(args);
|
|
16903
|
-
const commands = detectCommandDrift(args);
|
|
16904
|
-
return {
|
|
16905
|
-
overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
|
|
16906
|
-
workflows,
|
|
16907
|
-
commands
|
|
16908
|
-
};
|
|
16909
|
-
}
|
|
16910
|
-
function updateWorkflows(args) {
|
|
16911
|
-
const projectWorkflowsDir = path30.join(
|
|
16912
|
-
args.projectDir,
|
|
16913
|
-
".github",
|
|
16914
|
-
"workflows"
|
|
16915
|
-
);
|
|
16916
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
16917
|
-
const kitWorkflowsDir = args.kitDir ? path30.join(
|
|
16918
|
-
args.kitDir,
|
|
16919
|
-
"templates",
|
|
16920
|
-
"project",
|
|
16921
|
-
"common",
|
|
16922
|
-
".github",
|
|
16923
|
-
"workflows"
|
|
16924
|
-
) : findKitTemplatesDir(here);
|
|
16925
|
-
const substitute = args.substitute !== false;
|
|
16926
|
-
const dryRun = args.dryRun === true;
|
|
16927
|
-
const pruneExtras = args.pruneExtras === true;
|
|
16928
|
-
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16929
|
-
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16930
|
-
if (!dryRun && templateFiles.length > 0 && !fs30.existsSync(projectWorkflowsDir)) {
|
|
16931
|
-
fs30.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
16932
|
-
}
|
|
16933
|
-
const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
|
|
16934
|
-
const seen = /* @__PURE__ */ new Set();
|
|
16935
|
-
const files = [];
|
|
16936
|
-
for (const name of templateFiles) {
|
|
16937
|
-
seen.add(name);
|
|
16938
|
-
const projectPath2 = path30.join(projectWorkflowsDir, name);
|
|
16939
|
-
const templatePath = path30.join(kitWorkflowsDir, name);
|
|
16940
|
-
const templateRaw = fs30.readFileSync(templatePath, "utf-8");
|
|
16941
|
-
const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
|
|
16942
|
-
const existed = fs30.existsSync(projectPath2);
|
|
16943
|
-
const current = existed ? fs30.readFileSync(projectPath2, "utf-8") : "";
|
|
16944
|
-
let outcome;
|
|
16945
|
-
if (!existed) {
|
|
16946
|
-
outcome = "added";
|
|
16947
|
-
} else if (current === desired) {
|
|
16948
|
-
outcome = "unchanged";
|
|
16949
|
-
} else {
|
|
16950
|
-
outcome = "updated";
|
|
16951
|
-
}
|
|
16952
|
-
if (!dryRun && outcome !== "unchanged") {
|
|
16953
|
-
fs30.writeFileSync(projectPath2, desired);
|
|
16954
|
-
}
|
|
16955
|
-
files.push({ name, outcome });
|
|
16956
|
-
}
|
|
16957
|
-
if (pruneExtras) {
|
|
16958
|
-
for (const name of projectFiles) {
|
|
16959
|
-
if (seen.has(name)) continue;
|
|
16960
|
-
const projectPath2 = path30.join(projectWorkflowsDir, name);
|
|
16961
|
-
if (!dryRun) {
|
|
16962
|
-
fs30.unlinkSync(projectPath2);
|
|
16963
|
-
}
|
|
16964
|
-
files.push({ name, outcome: "removed" });
|
|
16965
|
-
}
|
|
16966
|
-
}
|
|
16967
|
-
const order = {
|
|
16968
|
-
added: 0,
|
|
16969
|
-
updated: 1,
|
|
16970
|
-
removed: 2,
|
|
16971
|
-
unchanged: 3
|
|
16972
|
-
};
|
|
16973
|
-
files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
|
|
16974
|
-
const changed = files.some((f) => f.outcome !== "unchanged");
|
|
16975
|
-
return { files, changed };
|
|
16976
|
-
}
|
|
16977
|
-
|
|
16978
|
-
// scripts/lakebase/doctor.ts
|
|
16979
17074
|
function readEnvFile(projectDir) {
|
|
16980
17075
|
const envPath = path31.join(projectDir, ".env");
|
|
16981
17076
|
if (!fs31.existsSync(envPath)) return {};
|
|
@@ -17408,7 +17503,7 @@ function checkWorkflowDrift(projectDir) {
|
|
|
17408
17503
|
status: "warn",
|
|
17409
17504
|
message: `Scaffolded workflows drift from kit: ${drifted} drifted, ${missing} missing`,
|
|
17410
17505
|
detail: { files: report.files.map((f) => ({ name: f.name, status: f.status })) },
|
|
17411
|
-
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."
|
|
17412
17507
|
};
|
|
17413
17508
|
} catch (err) {
|
|
17414
17509
|
return {
|
|
@@ -17519,9 +17614,10 @@ async function ensureLakebaseSecretAuth(args) {
|
|
|
17519
17614
|
if (typeof pat !== "string" || !pat) {
|
|
17520
17615
|
throw new Error("databricks tokens create returned no token_value");
|
|
17521
17616
|
}
|
|
17522
|
-
await runDatabricks(["secrets", "put-secret",
|
|
17617
|
+
await runDatabricks(["secrets", "put-secret", "--json", "@/dev/stdin"], {
|
|
17523
17618
|
profile,
|
|
17524
|
-
timeout: timeoutMs
|
|
17619
|
+
timeout: timeoutMs,
|
|
17620
|
+
input: JSON.stringify({ scope: scopeName, key: keyName, string_value: pat })
|
|
17525
17621
|
});
|
|
17526
17622
|
let aclGranted = false;
|
|
17527
17623
|
if (servicePrincipalClientId) {
|
|
@@ -17715,6 +17811,7 @@ export {
|
|
|
17715
17811
|
createUatPairedBranch,
|
|
17716
17812
|
cutBackup,
|
|
17717
17813
|
databricksAuthPrereqMessage,
|
|
17814
|
+
defaultRefreshPromoteWorkflows,
|
|
17718
17815
|
deleteAppEndpoint,
|
|
17719
17816
|
deleteBranch,
|
|
17720
17817
|
deleteLakebaseProject,
|