@databricks-solutions/lakebase-scm-utils 0.2.44 → 0.2.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/index.cjs +795 -758
- package/dist/scripts/index.d.cts +1 -1
- package/dist/scripts/index.d.ts +1 -1
- package/dist/scripts/index.js +794 -758
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
- package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
- package/dist/scripts/lakebase/detect-language.cli.js +2 -2
- package/dist/scripts/lakebase/doctor.cli.cjs +3 -3
- package/dist/scripts/lakebase/doctor.cli.js +3 -3
- package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
- package/dist/scripts/lakebase/get-connection.cli.js +2 -2
- package/dist/scripts/lakebase/index.cjs +463 -395
- package/dist/scripts/lakebase/index.d.cts +33 -1
- package/dist/scripts/lakebase/index.d.ts +33 -1
- package/dist/scripts/lakebase/index.js +462 -395
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
- package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
- package/dist/scripts/lakebase/new-migration.cli.js +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2284 -2033
- package/dist/scripts/lakebase/scm-merge.cli.js +339 -88
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
- package/dist/scripts/util/index.cjs +2 -2
- package/dist/scripts/util/index.js +2 -2
- package/package.json +1 -1
- package/scripts/lakebase/doctor.ts +1 -1
- package/scripts/lakebase/scm-merge.ts +80 -1
package/dist/scripts/index.js
CHANGED
|
@@ -9719,8 +9719,8 @@ var PKG_NAME = "@databricks-solutions/lakebase-scm-utils";
|
|
|
9719
9719
|
var cached;
|
|
9720
9720
|
function substrateSelfVersion() {
|
|
9721
9721
|
if (cached !== void 0) return cached;
|
|
9722
|
-
if ("0.2.
|
|
9723
|
-
cached = "0.2.
|
|
9722
|
+
if ("0.2.45".length > 0) {
|
|
9723
|
+
cached = "0.2.45";
|
|
9724
9724
|
return cached;
|
|
9725
9725
|
}
|
|
9726
9726
|
cached = "unknown";
|
|
@@ -16021,123 +16021,500 @@ function pickRunUrl(pr) {
|
|
|
16021
16021
|
|
|
16022
16022
|
// scripts/lakebase/scm-merge.ts
|
|
16023
16023
|
init_esm_shims();
|
|
16024
|
-
|
|
16025
|
-
|
|
16024
|
+
|
|
16025
|
+
// scripts/git/sync.ts
|
|
16026
|
+
init_esm_shims();
|
|
16027
|
+
async function currentBranchName(cwd) {
|
|
16026
16028
|
try {
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
} else {
|
|
16031
|
-
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
16032
|
-
}
|
|
16033
|
-
return null;
|
|
16034
|
-
} catch (err) {
|
|
16035
|
-
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).`;
|
|
16029
|
+
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
16030
|
+
} catch {
|
|
16031
|
+
return "";
|
|
16036
16032
|
}
|
|
16037
16033
|
}
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
super(message);
|
|
16041
|
-
this.code = code;
|
|
16042
|
-
this.name = "ScmMergeError";
|
|
16043
|
-
}
|
|
16044
|
-
code;
|
|
16045
|
-
};
|
|
16046
|
-
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
16047
|
-
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
16048
|
-
function defaultMigratePredicate(run, mergedAt) {
|
|
16049
|
-
if (!run.createdAt) return false;
|
|
16050
|
-
const created = Date.parse(run.createdAt);
|
|
16051
|
-
if (!Number.isFinite(created)) return false;
|
|
16052
|
-
if (run.event && run.event !== "push") return false;
|
|
16053
|
-
return created >= mergedAt.getTime() - 5e3;
|
|
16034
|
+
async function push(args) {
|
|
16035
|
+
await exec2("git push", { cwd: args.cwd });
|
|
16054
16036
|
}
|
|
16055
|
-
function
|
|
16056
|
-
|
|
16057
|
-
if (run.event && run.event !== "push") return false;
|
|
16058
|
-
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
16059
|
-
};
|
|
16037
|
+
async function pull(args) {
|
|
16038
|
+
await exec2("git pull", { cwd: args.cwd });
|
|
16060
16039
|
}
|
|
16061
|
-
async function
|
|
16062
|
-
const
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
|
|
16068
|
-
|
|
16069
|
-
|
|
16070
|
-
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16040
|
+
async function publishBranch(args) {
|
|
16041
|
+
const remote = args.remote ?? "origin";
|
|
16042
|
+
const branch = await currentBranchName(args.cwd);
|
|
16043
|
+
if (!branch) throw new Error("No current branch");
|
|
16044
|
+
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
16045
|
+
cwd: args.cwd
|
|
16046
|
+
});
|
|
16047
|
+
}
|
|
16048
|
+
async function pushCurrentBranchForPr(args) {
|
|
16049
|
+
const remote = args.remote ?? "origin";
|
|
16050
|
+
const branch = await currentBranchName(args.cwd);
|
|
16051
|
+
if (!branch) throw new Error("No current branch");
|
|
16052
|
+
const upstreamSet = await hasUpstream({ cwd: args.cwd });
|
|
16053
|
+
if (!upstreamSet) {
|
|
16054
|
+
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
16055
|
+
cwd: args.cwd
|
|
16056
|
+
});
|
|
16057
|
+
} else {
|
|
16058
|
+
await exec2("git push", { cwd: args.cwd });
|
|
16074
16059
|
}
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16060
|
+
}
|
|
16061
|
+
async function fetch2(args) {
|
|
16062
|
+
const parts = ["git fetch"];
|
|
16063
|
+
if (args.prune) parts.push("--prune");
|
|
16064
|
+
if (args.all) parts.push("--all");
|
|
16065
|
+
await exec2(parts.join(" "), { cwd: args.cwd });
|
|
16066
|
+
}
|
|
16067
|
+
async function pullFrom(args) {
|
|
16068
|
+
await exec2(`git pull ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
16069
|
+
cwd: args.cwd
|
|
16070
|
+
});
|
|
16071
|
+
}
|
|
16072
|
+
async function pushTo(args) {
|
|
16073
|
+
await exec2(`git push ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
16074
|
+
cwd: args.cwd
|
|
16075
|
+
});
|
|
16076
|
+
}
|
|
16077
|
+
async function sync(args) {
|
|
16078
|
+
await exec2("git pull", { cwd: args.cwd });
|
|
16079
|
+
await exec2("git push", { cwd: args.cwd });
|
|
16080
|
+
}
|
|
16081
|
+
|
|
16082
|
+
// scripts/lakebase/workflow-drift.ts
|
|
16083
|
+
init_esm_shims();
|
|
16084
|
+
import * as fs29 from "fs";
|
|
16085
|
+
import * as path29 from "path";
|
|
16086
|
+
function findKitTemplatesDir(start) {
|
|
16087
|
+
let dir = start;
|
|
16088
|
+
for (let i = 0; i < 6; i++) {
|
|
16089
|
+
const candidate = path29.join(
|
|
16090
|
+
dir,
|
|
16091
|
+
"templates",
|
|
16092
|
+
"project",
|
|
16093
|
+
"common",
|
|
16094
|
+
".github",
|
|
16095
|
+
"workflows"
|
|
16079
16096
|
);
|
|
16097
|
+
if (fs29.existsSync(candidate)) return candidate;
|
|
16098
|
+
const parent = path29.dirname(dir);
|
|
16099
|
+
if (parent === dir) break;
|
|
16100
|
+
dir = parent;
|
|
16080
16101
|
}
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16102
|
+
throw new Error(
|
|
16103
|
+
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
16104
|
+
);
|
|
16105
|
+
}
|
|
16106
|
+
function unifiedDiff(name, projectContent, templateContent) {
|
|
16107
|
+
if (projectContent === templateContent) return "";
|
|
16108
|
+
const a = projectContent.split("\n");
|
|
16109
|
+
const b = templateContent.split("\n");
|
|
16110
|
+
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
16111
|
+
const max = Math.max(a.length, b.length);
|
|
16112
|
+
for (let i = 0; i < max; i++) {
|
|
16113
|
+
const av = a[i];
|
|
16114
|
+
const bv = b[i];
|
|
16115
|
+
if (av === bv) continue;
|
|
16116
|
+
if (av !== void 0) out.push(`-${i + 1}: ${av}`);
|
|
16117
|
+
if (bv !== void 0) out.push(`+${i + 1}: ${bv}`);
|
|
16086
16118
|
}
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16119
|
+
return out.join("\n");
|
|
16120
|
+
}
|
|
16121
|
+
function detectWorkflowDrift(args) {
|
|
16122
|
+
const projectWorkflowsDir = path29.join(
|
|
16123
|
+
args.projectDir,
|
|
16124
|
+
".github",
|
|
16125
|
+
"workflows"
|
|
16126
|
+
);
|
|
16127
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
16128
|
+
const kitWorkflowsDir = args.kitDir ? path29.join(
|
|
16129
|
+
args.kitDir,
|
|
16130
|
+
"templates",
|
|
16131
|
+
"project",
|
|
16132
|
+
"common",
|
|
16133
|
+
".github",
|
|
16134
|
+
"workflows"
|
|
16135
|
+
) : findKitTemplatesDir(here);
|
|
16136
|
+
const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16137
|
+
const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16138
|
+
const version = readKitVersion(kitWorkflowsDir);
|
|
16139
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16140
|
+
const files = [];
|
|
16141
|
+
for (const name of templateFiles) {
|
|
16142
|
+
seen.add(name);
|
|
16143
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
16144
|
+
const templatePath = path29.join(kitWorkflowsDir, name);
|
|
16145
|
+
if (!fs29.existsSync(projectPath2)) {
|
|
16146
|
+
files.push({ name, status: "missing" });
|
|
16147
|
+
continue;
|
|
16148
|
+
}
|
|
16149
|
+
const projectContent = fs29.readFileSync(projectPath2, "utf8");
|
|
16150
|
+
const templateContent = applyPlaceholders(fs29.readFileSync(templatePath, "utf8"), version);
|
|
16151
|
+
if (projectContent === templateContent) {
|
|
16152
|
+
files.push({ name, status: "unchanged" });
|
|
16153
|
+
} else {
|
|
16154
|
+
files.push({
|
|
16155
|
+
name,
|
|
16156
|
+
status: "drifted",
|
|
16157
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
16158
|
+
});
|
|
16159
|
+
}
|
|
16093
16160
|
}
|
|
16094
|
-
const
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
`Could not extract PR number from URL: ${current.pr_url}`,
|
|
16098
|
-
"bad-pr-url"
|
|
16099
|
-
);
|
|
16161
|
+
for (const name of projectFiles) {
|
|
16162
|
+
if (seen.has(name)) continue;
|
|
16163
|
+
files.push({ name, status: "extra" });
|
|
16100
16164
|
}
|
|
16101
|
-
const
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16165
|
+
const order = {
|
|
16166
|
+
drifted: 0,
|
|
16167
|
+
missing: 1,
|
|
16168
|
+
extra: 2,
|
|
16169
|
+
unchanged: 3
|
|
16170
|
+
};
|
|
16171
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
16172
|
+
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
16173
|
+
return {
|
|
16174
|
+
overall: hasDrift ? "drift" : "ok",
|
|
16175
|
+
files
|
|
16176
|
+
};
|
|
16177
|
+
}
|
|
16178
|
+
function readKitVersion(kitWorkflowsDir) {
|
|
16179
|
+
let dir = kitWorkflowsDir;
|
|
16180
|
+
for (let i = 0; i < 5; i++) {
|
|
16181
|
+
dir = path29.dirname(dir);
|
|
16114
16182
|
}
|
|
16115
|
-
let paired;
|
|
16116
16183
|
try {
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16184
|
+
const raw = fs29.readFileSync(path29.join(dir, "package.json"), "utf-8");
|
|
16185
|
+
const pkg = JSON.parse(raw);
|
|
16186
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
16187
|
+
} catch {
|
|
16188
|
+
return "unknown";
|
|
16189
|
+
}
|
|
16190
|
+
}
|
|
16191
|
+
function applyPlaceholders(content, version) {
|
|
16192
|
+
return content.replace(/\{\{LAKEBASE_SCM_UTILS_VERSION\}\}/g, version);
|
|
16193
|
+
}
|
|
16194
|
+
function applyCommandPlaceholders(content, version) {
|
|
16195
|
+
return content.replace(/\$\{KIT_VERSION_AT_SCAFFOLD\}/g, version);
|
|
16196
|
+
}
|
|
16197
|
+
var COMMAND_HOOK_FILE_PATTERN = /\.(pre|post)-hook\.md$/;
|
|
16198
|
+
function findKitCommandsDir(start) {
|
|
16199
|
+
let dir = start;
|
|
16200
|
+
for (let i = 0; i < 6; i++) {
|
|
16201
|
+
const candidate = path29.join(
|
|
16202
|
+
dir,
|
|
16203
|
+
"templates",
|
|
16204
|
+
"project",
|
|
16205
|
+
"common",
|
|
16206
|
+
".claude",
|
|
16207
|
+
"commands"
|
|
16127
16208
|
);
|
|
16209
|
+
if (fs29.existsSync(candidate)) return candidate;
|
|
16210
|
+
const parent = path29.dirname(dir);
|
|
16211
|
+
if (parent === dir) break;
|
|
16212
|
+
dir = parent;
|
|
16128
16213
|
}
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16214
|
+
throw new Error(
|
|
16215
|
+
`Could not locate templates/project/common/.claude/commands/ relative to ${start}. Pass explicit kitDir.`
|
|
16216
|
+
);
|
|
16217
|
+
}
|
|
16218
|
+
function parsePinnedVersion(content) {
|
|
16219
|
+
const m = content.match(/^\s*[*_>`\s]*pinned\s+to\s*:\s*[`*_]*([^\s`*_]+)[`*_]*\s*$/im);
|
|
16220
|
+
return m ? m[1] : void 0;
|
|
16221
|
+
}
|
|
16222
|
+
function detectCommandDrift(args) {
|
|
16223
|
+
const projectCommandsDir = path29.join(args.projectDir, ".claude", "commands");
|
|
16224
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
16225
|
+
const kitCommandsDir = args.kitDir ? path29.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
16226
|
+
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
16227
|
+
const templateFiles = fs29.existsSync(kitCommandsDir) ? fs29.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16228
|
+
const projectFiles = fs29.existsSync(projectCommandsDir) ? fs29.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
16229
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16230
|
+
const files = [];
|
|
16231
|
+
for (const name of templateFiles) {
|
|
16232
|
+
seen.add(name);
|
|
16233
|
+
const projectPath2 = path29.join(projectCommandsDir, name);
|
|
16234
|
+
const templatePath = path29.join(kitCommandsDir, name);
|
|
16235
|
+
const templateRaw = fs29.readFileSync(templatePath, "utf8");
|
|
16236
|
+
if (!fs29.existsSync(projectPath2)) {
|
|
16237
|
+
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
16238
|
+
continue;
|
|
16239
|
+
}
|
|
16240
|
+
const projectContent = fs29.readFileSync(projectPath2, "utf8");
|
|
16241
|
+
const pinned = parsePinnedVersion(projectContent);
|
|
16242
|
+
const versionForCompare = pinned ?? kitVersion;
|
|
16243
|
+
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
16244
|
+
if (projectContent === templateContent) {
|
|
16245
|
+
files.push({
|
|
16246
|
+
name,
|
|
16247
|
+
status: "unchanged",
|
|
16248
|
+
pinned_version: pinned,
|
|
16249
|
+
kit_version: kitVersion
|
|
16250
|
+
});
|
|
16251
|
+
} else {
|
|
16252
|
+
files.push({
|
|
16253
|
+
name,
|
|
16254
|
+
status: "drifted",
|
|
16255
|
+
pinned_version: pinned,
|
|
16256
|
+
kit_version: kitVersion,
|
|
16257
|
+
diff: unifiedDiff(name, projectContent, templateContent)
|
|
16258
|
+
});
|
|
16259
|
+
}
|
|
16260
|
+
}
|
|
16261
|
+
for (const name of projectFiles) {
|
|
16262
|
+
if (seen.has(name)) continue;
|
|
16263
|
+
files.push({ name, status: "extra", kit_version: kitVersion });
|
|
16264
|
+
}
|
|
16265
|
+
const order = {
|
|
16266
|
+
drifted: 0,
|
|
16267
|
+
missing: 1,
|
|
16268
|
+
extra: 2,
|
|
16269
|
+
unchanged: 3
|
|
16270
|
+
};
|
|
16271
|
+
files.sort((a, b) => order[a.status] - order[b.status] || a.name.localeCompare(b.name));
|
|
16272
|
+
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
16273
|
+
return { overall: hasDrift ? "drift" : "ok", files };
|
|
16274
|
+
}
|
|
16275
|
+
function readKitVersionFromCommandsDir(kitCommandsDir) {
|
|
16276
|
+
let dir = kitCommandsDir;
|
|
16277
|
+
for (let i = 0; i < 5; i++) {
|
|
16278
|
+
dir = path29.dirname(dir);
|
|
16279
|
+
}
|
|
16280
|
+
try {
|
|
16281
|
+
const raw = fs29.readFileSync(path29.join(dir, "package.json"), "utf-8");
|
|
16282
|
+
const pkg = JSON.parse(raw);
|
|
16283
|
+
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
|
16284
|
+
} catch {
|
|
16285
|
+
return "unknown";
|
|
16286
|
+
}
|
|
16287
|
+
}
|
|
16288
|
+
function detectScaffoldedDrift(args) {
|
|
16289
|
+
const workflows = detectWorkflowDrift(args);
|
|
16290
|
+
const commands = detectCommandDrift(args);
|
|
16291
|
+
return {
|
|
16292
|
+
overall: workflows.overall === "drift" || commands.overall === "drift" ? "drift" : "ok",
|
|
16293
|
+
workflows,
|
|
16294
|
+
commands
|
|
16295
|
+
};
|
|
16296
|
+
}
|
|
16297
|
+
function updateWorkflows(args) {
|
|
16298
|
+
const projectWorkflowsDir = path29.join(
|
|
16299
|
+
args.projectDir,
|
|
16300
|
+
".github",
|
|
16301
|
+
"workflows"
|
|
16302
|
+
);
|
|
16303
|
+
const here = path29.dirname(new URL(import.meta.url).pathname);
|
|
16304
|
+
const kitWorkflowsDir = args.kitDir ? path29.join(
|
|
16305
|
+
args.kitDir,
|
|
16306
|
+
"templates",
|
|
16307
|
+
"project",
|
|
16308
|
+
"common",
|
|
16309
|
+
".github",
|
|
16310
|
+
"workflows"
|
|
16311
|
+
) : findKitTemplatesDir(here);
|
|
16312
|
+
const substitute = args.substitute !== false;
|
|
16313
|
+
const dryRun = args.dryRun === true;
|
|
16314
|
+
const pruneExtras = args.pruneExtras === true;
|
|
16315
|
+
const templateFiles = fs29.existsSync(kitWorkflowsDir) ? fs29.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16316
|
+
const projectFiles = fs29.existsSync(projectWorkflowsDir) ? fs29.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16317
|
+
if (!dryRun && templateFiles.length > 0 && !fs29.existsSync(projectWorkflowsDir)) {
|
|
16318
|
+
fs29.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
16319
|
+
}
|
|
16320
|
+
const version = substitute ? readKitVersion(kitWorkflowsDir) : "";
|
|
16321
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16322
|
+
const files = [];
|
|
16323
|
+
for (const name of templateFiles) {
|
|
16324
|
+
seen.add(name);
|
|
16325
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
16326
|
+
const templatePath = path29.join(kitWorkflowsDir, name);
|
|
16327
|
+
const templateRaw = fs29.readFileSync(templatePath, "utf-8");
|
|
16328
|
+
const desired = substitute ? applyPlaceholders(templateRaw, version) : templateRaw;
|
|
16329
|
+
const existed = fs29.existsSync(projectPath2);
|
|
16330
|
+
const current = existed ? fs29.readFileSync(projectPath2, "utf-8") : "";
|
|
16331
|
+
let outcome;
|
|
16332
|
+
if (!existed) {
|
|
16333
|
+
outcome = "added";
|
|
16334
|
+
} else if (current === desired) {
|
|
16335
|
+
outcome = "unchanged";
|
|
16336
|
+
} else {
|
|
16337
|
+
outcome = "updated";
|
|
16338
|
+
}
|
|
16339
|
+
if (!dryRun && outcome !== "unchanged") {
|
|
16340
|
+
fs29.writeFileSync(projectPath2, desired);
|
|
16341
|
+
}
|
|
16342
|
+
files.push({ name, outcome });
|
|
16343
|
+
}
|
|
16344
|
+
if (pruneExtras) {
|
|
16345
|
+
for (const name of projectFiles) {
|
|
16346
|
+
if (seen.has(name)) continue;
|
|
16347
|
+
const projectPath2 = path29.join(projectWorkflowsDir, name);
|
|
16348
|
+
if (!dryRun) {
|
|
16349
|
+
fs29.unlinkSync(projectPath2);
|
|
16350
|
+
}
|
|
16351
|
+
files.push({ name, outcome: "removed" });
|
|
16352
|
+
}
|
|
16353
|
+
}
|
|
16354
|
+
const order = {
|
|
16355
|
+
added: 0,
|
|
16356
|
+
updated: 1,
|
|
16357
|
+
removed: 2,
|
|
16358
|
+
unchanged: 3
|
|
16359
|
+
};
|
|
16360
|
+
files.sort((a, b) => order[a.outcome] - order[b.outcome] || a.name.localeCompare(b.name));
|
|
16361
|
+
const changed = files.some((f) => f.outcome !== "unchanged");
|
|
16362
|
+
return { files, changed };
|
|
16363
|
+
}
|
|
16364
|
+
|
|
16365
|
+
// scripts/lakebase/scm-merge.ts
|
|
16366
|
+
async function reconcileTierToOrigin(args) {
|
|
16367
|
+
const { cwd, tier } = args;
|
|
16368
|
+
try {
|
|
16369
|
+
const head = await getCurrentBranch({ cwd });
|
|
16370
|
+
if (head === tier) {
|
|
16371
|
+
await exec2(`git pull --ff-only`, { cwd, timeout: 3e4 });
|
|
16372
|
+
} else {
|
|
16373
|
+
await exec2(`git fetch origin ${shellEscape2(`${tier}:${tier}`)}`, { cwd, timeout: 3e4 });
|
|
16374
|
+
}
|
|
16375
|
+
return null;
|
|
16376
|
+
} catch (err) {
|
|
16377
|
+
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).`;
|
|
16378
|
+
}
|
|
16379
|
+
}
|
|
16380
|
+
var ScmMergeError = class extends Error {
|
|
16381
|
+
constructor(message, code) {
|
|
16382
|
+
super(message);
|
|
16383
|
+
this.code = code;
|
|
16384
|
+
this.name = "ScmMergeError";
|
|
16385
|
+
}
|
|
16386
|
+
code;
|
|
16387
|
+
};
|
|
16388
|
+
var DEFAULT_MIGRATE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
16389
|
+
var DEFAULT_MIGRATE_POLL_MS = 30 * 1e3;
|
|
16390
|
+
function defaultMigratePredicate(run, mergedAt) {
|
|
16391
|
+
if (!run.createdAt) return false;
|
|
16392
|
+
const created = Date.parse(run.createdAt);
|
|
16393
|
+
if (!Number.isFinite(created)) return false;
|
|
16394
|
+
if (run.event && run.event !== "push") return false;
|
|
16395
|
+
return created >= mergedAt.getTime() - 5e3;
|
|
16396
|
+
}
|
|
16397
|
+
function shaMigratePredicate(mergeCommitSha) {
|
|
16398
|
+
return (run) => {
|
|
16399
|
+
if (run.event && run.event !== "push") return false;
|
|
16400
|
+
return !!run.headSha && run.headSha === mergeCommitSha;
|
|
16401
|
+
};
|
|
16402
|
+
}
|
|
16403
|
+
async function defaultRefreshPromoteWorkflows(projectDir) {
|
|
16404
|
+
const drift = detectWorkflowDrift({ projectDir });
|
|
16405
|
+
const touched = drift.files.filter((f) => f.status === "drifted").map((f) => f.name);
|
|
16406
|
+
if (touched.length === 0) return { refreshed: false };
|
|
16407
|
+
const result = updateWorkflows({ projectDir });
|
|
16408
|
+
if (!result.changed) return { refreshed: false };
|
|
16409
|
+
await exec2(`git add ${shellEscape2(".github/workflows")}`, { cwd: projectDir, timeout: 1e4 });
|
|
16410
|
+
const staged = await exec2("git diff --cached --name-only -- .github/workflows", {
|
|
16411
|
+
cwd: projectDir,
|
|
16412
|
+
timeout: 1e4
|
|
16413
|
+
});
|
|
16414
|
+
if (!staged.trim()) return { refreshed: false };
|
|
16415
|
+
await exec2(
|
|
16416
|
+
`git commit -m ${shellEscape2(
|
|
16417
|
+
"chore(ci): refresh scaffolded workflows to pinned substrate (promote uses apply-tier)"
|
|
16418
|
+
)} -- .github/workflows`,
|
|
16419
|
+
{ cwd: projectDir, timeout: 15e3 }
|
|
16420
|
+
);
|
|
16421
|
+
await pushCurrentBranchForPr({ cwd: projectDir });
|
|
16422
|
+
return { refreshed: true, detail: `refreshed + pushed ${touched.join(", ") || "workflow(s)"} onto the PR head` };
|
|
16423
|
+
}
|
|
16424
|
+
async function mergeFeature(args) {
|
|
16425
|
+
const current = readWorkflowState(args.projectDir);
|
|
16426
|
+
if (!current) {
|
|
16427
|
+
throw new ScmMergeError(
|
|
16428
|
+
"No SCM workflow state. wait-ci first.",
|
|
16429
|
+
"no-state-file"
|
|
16430
|
+
);
|
|
16431
|
+
}
|
|
16432
|
+
if (current.state !== "ci-green") {
|
|
16433
|
+
throw new ScmMergeError(
|
|
16434
|
+
`merge refuses state "${current.state}". Allowed predecessor: ci-green.`,
|
|
16435
|
+
"bad-precondition"
|
|
16436
|
+
);
|
|
16437
|
+
}
|
|
16438
|
+
if (!current.pr_url) {
|
|
16439
|
+
throw new ScmMergeError(
|
|
16440
|
+
"ci-green row is missing pr_url; cannot resolve the PR to merge.",
|
|
16441
|
+
"no-pr-url"
|
|
16442
|
+
);
|
|
16443
|
+
}
|
|
16444
|
+
if (!current.branch || !current.parent_branch) {
|
|
16445
|
+
throw new ScmMergeError(
|
|
16446
|
+
"ci-green row missing branch / parent_branch; refusing to merge.",
|
|
16447
|
+
"bad-precondition"
|
|
16448
|
+
);
|
|
16449
|
+
}
|
|
16450
|
+
const ownerRepo = await getOwnerRepo(args.projectDir);
|
|
16451
|
+
if (!ownerRepo) {
|
|
16452
|
+
throw new ScmMergeError(
|
|
16453
|
+
"No GitHub remote found at origin.",
|
|
16454
|
+
"no-github-remote"
|
|
16455
|
+
);
|
|
16456
|
+
}
|
|
16457
|
+
const pullNumber = extractPullNumber(current.pr_url);
|
|
16458
|
+
if (!pullNumber) {
|
|
16459
|
+
throw new ScmMergeError(
|
|
16460
|
+
`Could not extract PR number from URL: ${current.pr_url}`,
|
|
16461
|
+
"bad-pr-url"
|
|
16462
|
+
);
|
|
16463
|
+
}
|
|
16464
|
+
const instance = args.instance ?? current.project_id;
|
|
16465
|
+
const gitBase = await resolveGitBase(current.parent_branch, args.projectDir);
|
|
16466
|
+
const wantMigrate = args.waitMigrate !== false;
|
|
16467
|
+
let authVerified = false;
|
|
16468
|
+
if (wantMigrate && args.verifyMigrateAuth) {
|
|
16469
|
+
const probe = await args.verifyMigrateAuth();
|
|
16470
|
+
if (!probe.ok) {
|
|
16471
|
+
throw new ScmMergeError(
|
|
16472
|
+
`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.`,
|
|
16473
|
+
"migrate-auth"
|
|
16474
|
+
);
|
|
16475
|
+
}
|
|
16476
|
+
authVerified = true;
|
|
16477
|
+
}
|
|
16478
|
+
const preflightNotes = [];
|
|
16479
|
+
{
|
|
16480
|
+
const refresh = args.refreshWorkflows ?? (() => defaultRefreshPromoteWorkflows(args.projectDir));
|
|
16481
|
+
try {
|
|
16482
|
+
const r = await refresh();
|
|
16483
|
+
if (r.refreshed) {
|
|
16484
|
+
preflightNotes.push(`Refreshed stale CI workflows before merge${r.detail ? ` (${r.detail})` : ""}.`);
|
|
16485
|
+
}
|
|
16486
|
+
} catch (err) {
|
|
16487
|
+
preflightNotes.push(
|
|
16488
|
+
`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').`
|
|
16489
|
+
);
|
|
16490
|
+
}
|
|
16491
|
+
}
|
|
16492
|
+
let paired;
|
|
16493
|
+
try {
|
|
16494
|
+
paired = await mergePairedPullRequest({
|
|
16495
|
+
ownerRepo,
|
|
16496
|
+
pullNumber,
|
|
16497
|
+
lakebaseInstance: instance,
|
|
16498
|
+
method: args.method ?? "squash"
|
|
16499
|
+
});
|
|
16500
|
+
} catch (err) {
|
|
16501
|
+
throw new ScmMergeError(
|
|
16502
|
+
`mergePairedPullRequest failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
16503
|
+
"merge-failed"
|
|
16504
|
+
);
|
|
16505
|
+
}
|
|
16506
|
+
const warnings = [...preflightNotes, ...paired.warnings];
|
|
16507
|
+
let localBranchDeleted = false;
|
|
16508
|
+
let headAfter = current.branch;
|
|
16509
|
+
if (!args.skipLocalCleanup) {
|
|
16510
|
+
const switchTo = args.switchTo ?? gitBase;
|
|
16511
|
+
const head = await getCurrentBranch({ cwd: args.projectDir });
|
|
16512
|
+
if (head === current.branch) {
|
|
16513
|
+
try {
|
|
16514
|
+
await exec2(`git checkout ${shellEscape2(switchTo)}`, {
|
|
16515
|
+
cwd: args.projectDir,
|
|
16516
|
+
timeout: 1e4
|
|
16517
|
+
});
|
|
16141
16518
|
headAfter = switchTo;
|
|
16142
16519
|
try {
|
|
16143
16520
|
await exec2(`git fetch origin ${shellEscape2(switchTo)}`, {
|
|
@@ -16323,7 +16700,7 @@ init_esm_shims();
|
|
|
16323
16700
|
|
|
16324
16701
|
// scripts/git/branches.ts
|
|
16325
16702
|
init_esm_shims();
|
|
16326
|
-
async function
|
|
16703
|
+
async function currentBranchName2(cwd) {
|
|
16327
16704
|
try {
|
|
16328
16705
|
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
16329
16706
|
} catch {
|
|
@@ -16342,7 +16719,7 @@ async function listLocalBranches(args) {
|
|
|
16342
16719
|
return [];
|
|
16343
16720
|
}
|
|
16344
16721
|
if (!raw) return [];
|
|
16345
|
-
const current = await
|
|
16722
|
+
const current = await currentBranchName2(cwd);
|
|
16346
16723
|
return raw.split("\n").filter(Boolean).map((line) => {
|
|
16347
16724
|
const [name, tracking, trackInfo] = line.split("|");
|
|
16348
16725
|
let ahead = 0;
|
|
@@ -16542,15 +16919,15 @@ function parentForTopology(t, defaultLeaf) {
|
|
|
16542
16919
|
|
|
16543
16920
|
// scripts/lakebase/scm-doctor.ts
|
|
16544
16921
|
init_esm_shims();
|
|
16545
|
-
import * as
|
|
16546
|
-
import * as
|
|
16922
|
+
import * as fs30 from "fs";
|
|
16923
|
+
import * as path30 from "path";
|
|
16547
16924
|
var FEATURE_PREFIX = "feature/";
|
|
16548
16925
|
var TIER_LEAFS2 = DEFAULT_PROTECTED_TIER_NAMES;
|
|
16549
16926
|
function readEnv(projectDir) {
|
|
16550
|
-
const envPath =
|
|
16927
|
+
const envPath = path30.join(projectDir, ".env");
|
|
16551
16928
|
const out = /* @__PURE__ */ new Map();
|
|
16552
|
-
if (!
|
|
16553
|
-
const lines =
|
|
16929
|
+
if (!fs30.existsSync(envPath)) return out;
|
|
16930
|
+
const lines = fs30.readFileSync(envPath, "utf8").split("\n");
|
|
16554
16931
|
for (const line of lines) {
|
|
16555
16932
|
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.+?)\s*$/);
|
|
16556
16933
|
if (m) out.set(m[1], m[2].replace(/^["']|["']$/g, ""));
|
|
@@ -16596,622 +16973,337 @@ async function runDoctor(args, deps = {}) {
|
|
|
16596
16973
|
findings.push({
|
|
16597
16974
|
id: "github-actions-disabled",
|
|
16598
16975
|
severity: "warn",
|
|
16599
|
-
message: `GitHub Actions is disabled for ${ownerRepo}, so the kit's CI workflows (pr.yml / merge.yml) will never run , the PR branch's migrations, tests, and schema-diff comment are all skipped. This is commonly an org-level policy on EMU repos, which a repo admin cannot override.`,
|
|
16600
|
-
suggestion: "Have an org owner enable Actions for this repo (repo Settings -> Actions -> General; if it says 'disabled by the organization', it must be enabled in the org's Actions policy). Until then, run the workflow steps locally: scripts/run-tests.sh against a Lakebase branch."
|
|
16601
|
-
});
|
|
16602
|
-
}
|
|
16603
|
-
}
|
|
16604
|
-
} catch {
|
|
16605
|
-
}
|
|
16606
|
-
for (const wf of ["pr.yml", "merge.yml"]) {
|
|
16607
|
-
try {
|
|
16608
|
-
const p = path29.join(projectDir, ".github", "workflows", wf);
|
|
16609
|
-
if (!fs29.existsSync(p)) continue;
|
|
16610
|
-
const body = fs29.readFileSync(p, "utf8");
|
|
16611
|
-
if (/lakebase-scm-utils#v\d/.test(body)) {
|
|
16612
|
-
findings.push({
|
|
16613
|
-
id: "ci-workflow-substrate-pin",
|
|
16614
|
-
severity: "warn",
|
|
16615
|
-
message: `.github/workflows/${wf} hardcodes a substrate version pin (github:databricks-solutions/lakebase-scm-utils#v<ver>) instead of resolving .lakebase/scm-utils-ref at runtime, so bumping .lakebase/scm-utils-ref does NOT change the substrate CI actually runs.`,
|
|
16616
|
-
suggestion: "Re-emit the workflows from the current substrate templates so they resolve SCM_UTILS_REF from .lakebase/scm-utils-ref at CI time (updateWorkflows in scripts/lakebase/workflow-drift.ts; lakebase-doctor reports this as workflow-drift). Until then a scm-utils-ref bump will not reach CI."
|
|
16617
|
-
});
|
|
16618
|
-
}
|
|
16619
|
-
} catch {
|
|
16620
|
-
}
|
|
16621
|
-
}
|
|
16622
|
-
try {
|
|
16623
|
-
if (isGitTracked(projectDir, ".lakebase/workflow-state.json")) {
|
|
16624
|
-
findings.push({
|
|
16625
|
-
id: "scm-state-git-tracked",
|
|
16626
|
-
severity: "warn",
|
|
16627
|
-
message: ".lakebase/workflow-state.json (the per-working-tree SCM claim state) is git-tracked, so a branch checkout or `git reset --hard origin/<tier>` can restore a stale committed claim over the live one (the foreign-claim guard then refuses to drive until you abandon + reclaim).",
|
|
16628
|
-
suggestion: "If a wrong-feature-claim refusal follows a checkout, run lakebase-scm-adopt-state (or abandon + reclaim) to re-establish the live claim. The kit-ref run pin (.lakebase/kit-ref.local) already protects the kit version from the same checkout revert."
|
|
16629
|
-
});
|
|
16630
|
-
}
|
|
16631
|
-
} catch {
|
|
16632
|
-
}
|
|
16633
|
-
if (!env.has("LAKEBASE_PROJECT_ID")) {
|
|
16634
|
-
findings.push({
|
|
16635
|
-
id: "env-missing-project-id",
|
|
16636
|
-
severity: "fail",
|
|
16637
|
-
message: ".env does not contain LAKEBASE_PROJECT_ID. The post-checkout hook will exit early; workflow CLIs will need an explicit --instance.",
|
|
16638
|
-
suggestion: "Set LAKEBASE_PROJECT_ID=<your project id> in .env"
|
|
16639
|
-
});
|
|
16640
|
-
}
|
|
16641
|
-
if (!instance) {
|
|
16642
|
-
return finalize({
|
|
16643
|
-
projectDir,
|
|
16644
|
-
workflowStatePresent,
|
|
16645
|
-
state: state ?? void 0,
|
|
16646
|
-
findings
|
|
16647
|
-
});
|
|
16648
|
-
}
|
|
16649
|
-
let lakebaseBranches = [];
|
|
16650
|
-
try {
|
|
16651
|
-
lakebaseBranches = await listBranches({ instance });
|
|
16652
|
-
} catch (err) {
|
|
16653
|
-
findings.push({
|
|
16654
|
-
id: "lakebase-unreachable",
|
|
16655
|
-
severity: "fail",
|
|
16656
|
-
message: `Could not list Lakebase branches for instance ${instance}: ${err instanceof Error ? err.message : String(err)}`,
|
|
16657
|
-
suggestion: "databricks auth login (or check DATABRICKS_CONFIG_PROFILE)."
|
|
16658
|
-
});
|
|
16659
|
-
return finalize({
|
|
16660
|
-
projectDir,
|
|
16661
|
-
workflowStatePresent,
|
|
16662
|
-
state: state ?? void 0,
|
|
16663
|
-
findings
|
|
16664
|
-
});
|
|
16665
|
-
}
|
|
16666
|
-
const inferredTopology = inferTierTopology(lakebaseBranches);
|
|
16667
|
-
if (state && state.tier_topology !== inferredTopology) {
|
|
16668
|
-
findings.push({
|
|
16669
|
-
id: "tier-topology-mismatch",
|
|
16670
|
-
severity: "warn",
|
|
16671
|
-
message: `workflow-state records tier_topology=${state.tier_topology}, but the Lakebase tier inventory suggests ${inferredTopology}.`,
|
|
16672
|
-
suggestion: "lakebase-scm-adopt-state --force"
|
|
16673
|
-
});
|
|
16674
|
-
}
|
|
16675
|
-
const headBranch = await getCurrentBranch({ cwd: projectDir });
|
|
16676
|
-
if (state && state.state === "feature-claimed") {
|
|
16677
|
-
if (state.branch && headBranch && headBranch !== state.branch) {
|
|
16678
|
-
findings.push({
|
|
16679
|
-
id: "head-branch-drift",
|
|
16680
|
-
severity: "warn",
|
|
16681
|
-
message: `workflow says feature-claimed for "${state.branch}", but HEAD is on "${headBranch}".`,
|
|
16682
|
-
suggestion: `git checkout '${state.branch}'`
|
|
16683
|
-
});
|
|
16684
|
-
}
|
|
16685
|
-
if (state.branch) {
|
|
16686
|
-
const sanitized = sanitizeBranchName(state.branch);
|
|
16687
|
-
let pair;
|
|
16688
|
-
try {
|
|
16689
|
-
pair = await getBranchByName(sanitized, { instance });
|
|
16690
|
-
} catch {
|
|
16691
|
-
pair = void 0;
|
|
16692
|
-
}
|
|
16693
|
-
if (!pair) {
|
|
16694
|
-
findings.push({
|
|
16695
|
-
id: "lakebase-pair-missing",
|
|
16696
|
-
severity: "fail",
|
|
16697
|
-
message: `workflow says feature-claimed for "${state.branch}", but no Lakebase branch "${sanitized}" exists.`,
|
|
16698
|
-
suggestion: `lakebase-scm-abandon-feature # reset state; re-claim if needed`
|
|
16699
|
-
});
|
|
16700
|
-
} else if (state.lakebase_branch_uid && pair.uid !== state.lakebase_branch_uid) {
|
|
16701
|
-
findings.push({
|
|
16702
|
-
id: "lakebase-uid-drift",
|
|
16703
|
-
severity: "warn",
|
|
16704
|
-
message: `workflow records lakebase_branch_uid=${state.lakebase_branch_uid}, but the live branch reports ${pair.uid}.`,
|
|
16705
|
-
suggestion: "lakebase-scm-adopt-state --force"
|
|
16706
|
-
});
|
|
16707
|
-
}
|
|
16708
|
-
}
|
|
16709
|
-
}
|
|
16710
|
-
if (state && state.state === "feature-claimed" && state.branch) {
|
|
16711
|
-
const envBranchId = env.get("LAKEBASE_BRANCH_ID");
|
|
16712
|
-
const sanitized = sanitizeBranchName(state.branch);
|
|
16713
|
-
if (envBranchId && envBranchId !== sanitized) {
|
|
16714
|
-
findings.push({
|
|
16715
|
-
id: "env-branch-drift",
|
|
16716
|
-
severity: "warn",
|
|
16717
|
-
message: `.env LAKEBASE_BRANCH_ID=${envBranchId} but workflow says ${sanitized}. The post-checkout hook may not have run since the last branch switch.`,
|
|
16718
|
-
suggestion: `git checkout '${state.branch}' # re-fires post-checkout`
|
|
16719
|
-
});
|
|
16720
|
-
}
|
|
16721
|
-
}
|
|
16722
|
-
if (headBranch && !TIER_LEAFS2.has(headBranch) && headBranch.startsWith(FEATURE_PREFIX)) {
|
|
16723
|
-
const sanitized = sanitizeBranchName(headBranch);
|
|
16724
|
-
const paired = lakebaseBranches.some((b) => leafOf2(b) === sanitized);
|
|
16725
|
-
if (!paired) {
|
|
16726
|
-
findings.push({
|
|
16727
|
-
id: "orphan-current-branch",
|
|
16728
|
-
severity: "fail",
|
|
16729
|
-
message: `Current git branch "${headBranch}" has no Lakebase pair (post-checkout fallback retired in phase C).`,
|
|
16730
|
-
suggestion: `lakebase-scm-recover-orphans --claim --only-branch '${headBranch}'`
|
|
16731
|
-
});
|
|
16732
|
-
}
|
|
16733
|
-
}
|
|
16734
|
-
try {
|
|
16735
|
-
const heads = await collapseMigrationHeads({ projectDir, dryRun: true });
|
|
16736
|
-
if (heads.headsBefore.length > 1) {
|
|
16737
|
-
findings.push({
|
|
16738
|
-
id: "multiple-migration-heads",
|
|
16739
|
-
severity: "fail",
|
|
16740
|
-
message: `Migrations have ${heads.headsBefore.length} heads (${heads.headsBefore.join(", ")}); a sibling-feature merge left them un-collapsed. \`upgrade head\` will refuse until they are unified.`,
|
|
16741
|
-
suggestion: "lakebase-collapse-heads"
|
|
16742
|
-
});
|
|
16743
|
-
}
|
|
16744
|
-
} catch {
|
|
16745
|
-
}
|
|
16746
|
-
if (instance && state?.branch) {
|
|
16747
|
-
try {
|
|
16748
|
-
const localIds = listSchemaMigrations({ projectDir }).map((m) => m.version);
|
|
16749
|
-
let orphanRev = null;
|
|
16750
|
-
try {
|
|
16751
|
-
const status = await schemaMigrationStatus({ instance, branch: state.branch, projectDir });
|
|
16752
|
-
if (dbRevisionOrphaned(status.current, localIds)) orphanRev = status.current ?? null;
|
|
16753
|
-
} catch (e) {
|
|
16754
|
-
orphanRev = parseAlembicMissingRevision(e instanceof Error ? e.message : String(e));
|
|
16755
|
-
}
|
|
16756
|
-
if (orphanRev) {
|
|
16757
|
-
findings.push({
|
|
16758
|
-
id: "db-ahead-of-code",
|
|
16759
|
-
severity: "fail",
|
|
16760
|
-
message: `The paired branch DB is AHEAD of code: applied revision '${orphanRev}' has no local migration file. An aborted build likely migrated this branch and a git reset removed the migration file; alembic accept/deploy/promote will fail "Can't locate revision".`,
|
|
16761
|
-
suggestion: "reset the paired branch DB to the code head (downgrade/stamp + drop reset-created tables), or delete the branch and re-cut it clean from its tier"
|
|
16762
|
-
});
|
|
16763
|
-
}
|
|
16764
|
-
} catch {
|
|
16765
|
-
}
|
|
16766
|
-
}
|
|
16767
|
-
return finalize({
|
|
16768
|
-
projectDir,
|
|
16769
|
-
workflowStatePresent,
|
|
16770
|
-
state: state ?? void 0,
|
|
16771
|
-
inferredTierTopology: inferredTopology,
|
|
16772
|
-
findings
|
|
16773
|
-
});
|
|
16774
|
-
}
|
|
16775
|
-
function finalize(report) {
|
|
16776
|
-
let worst = "ok";
|
|
16777
|
-
for (const f of report.findings) {
|
|
16778
|
-
worst = worstOf(worst, f.severity);
|
|
16779
|
-
}
|
|
16780
|
-
return { ...report, worstSeverity: worst };
|
|
16781
|
-
}
|
|
16782
|
-
var ScmDoctorFixError = class extends Error {
|
|
16783
|
-
constructor(message, code) {
|
|
16784
|
-
super(message);
|
|
16785
|
-
this.code = code;
|
|
16786
|
-
this.name = "ScmDoctorFixError";
|
|
16787
|
-
}
|
|
16788
|
-
code;
|
|
16789
|
-
};
|
|
16790
|
-
var FIXABLE_FINDING_IDS = [
|
|
16791
|
-
"env-branch-drift",
|
|
16792
|
-
"head-branch-drift",
|
|
16793
|
-
"tier-topology-mismatch",
|
|
16794
|
-
"orphan-current-branch",
|
|
16795
|
-
"multiple-migration-heads",
|
|
16796
|
-
"db-ahead-of-code"
|
|
16797
|
-
];
|
|
16798
|
-
async function fixFinding(args) {
|
|
16799
|
-
if (!FIXABLE_FINDING_IDS.includes(args.findingId)) {
|
|
16800
|
-
throw new ScmDoctorFixError(
|
|
16801
|
-
`Finding "${args.findingId}" is not supported by --fix. Supported: ${FIXABLE_FINDING_IDS.join(", ")}.`,
|
|
16802
|
-
"unsupported-finding"
|
|
16803
|
-
);
|
|
16804
|
-
}
|
|
16805
|
-
const report = args.report ?? await runDoctor({ projectDir: args.projectDir, instance: args.instance });
|
|
16806
|
-
const present = report.findings.find((f) => f.id === args.findingId);
|
|
16807
|
-
if (!present) {
|
|
16808
|
-
throw new ScmDoctorFixError(
|
|
16809
|
-
`Finding "${args.findingId}" is not present in the current report. Re-run lakebase-scm-doctor to see what needs fixing.`,
|
|
16810
|
-
"finding-not-present"
|
|
16811
|
-
);
|
|
16812
|
-
}
|
|
16813
|
-
let action = "";
|
|
16814
|
-
try {
|
|
16815
|
-
switch (args.findingId) {
|
|
16816
|
-
case "env-branch-drift": {
|
|
16817
|
-
const branch = report.state?.branch;
|
|
16818
|
-
if (!branch) {
|
|
16819
|
-
throw new ScmDoctorFixError(
|
|
16820
|
-
"Cannot fix: workflow state has no branch field.",
|
|
16821
|
-
"fix-failed"
|
|
16822
|
-
);
|
|
16823
|
-
}
|
|
16824
|
-
const sanitized = sanitizeBranchName(branch);
|
|
16825
|
-
const envFile = path29.join(args.projectDir, ".env");
|
|
16826
|
-
updateEnvConnection({
|
|
16827
|
-
envPath: envFile,
|
|
16828
|
-
projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
|
|
16829
|
-
branchId: sanitized,
|
|
16830
|
-
username: readEnvVar(envFile, "DB_USERNAME") ?? "",
|
|
16831
|
-
endpointHost: readEnvVar(envFile, "LAKEBASE_HOST")
|
|
16832
|
-
});
|
|
16833
|
-
action = `rewrote .env LAKEBASE_BRANCH_ID=${sanitized} (metadata only; the app mints its token at runtime)`;
|
|
16834
|
-
break;
|
|
16835
|
-
}
|
|
16836
|
-
case "head-branch-drift": {
|
|
16837
|
-
const branch = report.state?.branch;
|
|
16838
|
-
if (!branch) {
|
|
16839
|
-
throw new ScmDoctorFixError(
|
|
16840
|
-
"Cannot fix: workflow state has no branch field.",
|
|
16841
|
-
"fix-failed"
|
|
16842
|
-
);
|
|
16843
|
-
}
|
|
16844
|
-
await exec2(`git checkout ${shellEscape3(branch)}`, {
|
|
16845
|
-
cwd: args.projectDir,
|
|
16846
|
-
timeout: 15e3
|
|
16847
|
-
});
|
|
16848
|
-
action = `git checkout ${branch} (re-fires post-checkout to resync HEAD)`;
|
|
16849
|
-
break;
|
|
16850
|
-
}
|
|
16851
|
-
case "tier-topology-mismatch": {
|
|
16852
|
-
const instance = args.instance ?? report.state?.project_id;
|
|
16853
|
-
if (!instance) {
|
|
16854
|
-
throw new ScmDoctorFixError(
|
|
16855
|
-
"Cannot fix: missing Lakebase project id.",
|
|
16856
|
-
"fix-failed"
|
|
16857
|
-
);
|
|
16858
|
-
}
|
|
16859
|
-
await adoptScmState({
|
|
16860
|
-
projectDir: args.projectDir,
|
|
16861
|
-
instance,
|
|
16862
|
-
force: true
|
|
16863
|
-
});
|
|
16864
|
-
action = `adopted state with --force to re-infer tier_topology`;
|
|
16865
|
-
break;
|
|
16866
|
-
}
|
|
16867
|
-
case "orphan-current-branch": {
|
|
16868
|
-
const instance = args.instance ?? report.state?.project_id;
|
|
16869
|
-
if (!instance) {
|
|
16870
|
-
throw new ScmDoctorFixError(
|
|
16871
|
-
"Cannot fix: missing Lakebase project id.",
|
|
16872
|
-
"fix-failed"
|
|
16873
|
-
);
|
|
16874
|
-
}
|
|
16875
|
-
const headBranch = await getCurrentBranch({ cwd: args.projectDir });
|
|
16876
|
-
if (!headBranch) {
|
|
16877
|
-
throw new ScmDoctorFixError(
|
|
16878
|
-
"Cannot fix: detached HEAD or no current branch.",
|
|
16879
|
-
"fix-failed"
|
|
16880
|
-
);
|
|
16881
|
-
}
|
|
16882
|
-
await recoverOrphans({
|
|
16883
|
-
projectDir: args.projectDir,
|
|
16884
|
-
instance,
|
|
16885
|
-
claim: true,
|
|
16886
|
-
onlyBranch: headBranch
|
|
16887
|
-
});
|
|
16888
|
-
action = `recovered orphan ${headBranch} via createFeaturePairedBranch`;
|
|
16889
|
-
break;
|
|
16890
|
-
}
|
|
16891
|
-
case "multiple-migration-heads": {
|
|
16892
|
-
const r = await collapseMigrationHeads({ projectDir: args.projectDir });
|
|
16893
|
-
if (r.status !== "ok" || !r.mergeRevision) {
|
|
16894
|
-
throw new ScmDoctorFixError(
|
|
16895
|
-
`Expected to create a merge revision but got status="${r.status}".`,
|
|
16896
|
-
"fix-failed"
|
|
16897
|
-
);
|
|
16898
|
-
}
|
|
16899
|
-
action = `collapsed ${r.headsBefore.length} heads into merge revision ${r.mergeRevision} (commit it)`;
|
|
16900
|
-
break;
|
|
16901
|
-
}
|
|
16902
|
-
case "db-ahead-of-code": {
|
|
16903
|
-
const r = await abandonFeatureBranch({ projectDir: args.projectDir, instance: args.instance, force: true });
|
|
16904
|
-
action = `abandoned the feature (deleted the polluted paired branch${r.lakebaseDeleted ? "" : " , Lakebase delete reported not-deleted"}) and reset state to '${r.state.state}'; re-claim to re-fork a clean branch from the tier`;
|
|
16905
|
-
break;
|
|
16906
|
-
}
|
|
16907
|
-
}
|
|
16908
|
-
} catch (err) {
|
|
16909
|
-
if (err instanceof ScmDoctorFixError) throw err;
|
|
16910
|
-
throw new ScmDoctorFixError(
|
|
16911
|
-
`Remediation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
16912
|
-
"fix-failed"
|
|
16913
|
-
);
|
|
16914
|
-
}
|
|
16915
|
-
const postReport = await runDoctor({
|
|
16916
|
-
projectDir: args.projectDir,
|
|
16917
|
-
instance: args.instance
|
|
16918
|
-
});
|
|
16919
|
-
return { findingId: args.findingId, action, postReport };
|
|
16920
|
-
}
|
|
16921
|
-
function shellEscape3(s) {
|
|
16922
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
16923
|
-
}
|
|
16924
|
-
|
|
16925
|
-
// scripts/lakebase/doctor.ts
|
|
16926
|
-
init_esm_shims();
|
|
16927
|
-
import * as fs31 from "fs";
|
|
16928
|
-
import * as path31 from "path";
|
|
16929
|
-
import * as cp6 from "child_process";
|
|
16930
|
-
|
|
16931
|
-
// scripts/lakebase/workflow-drift.ts
|
|
16932
|
-
init_esm_shims();
|
|
16933
|
-
import * as fs30 from "fs";
|
|
16934
|
-
import * as path30 from "path";
|
|
16935
|
-
function findKitTemplatesDir(start) {
|
|
16936
|
-
let dir = start;
|
|
16937
|
-
for (let i = 0; i < 6; i++) {
|
|
16938
|
-
const candidate = path30.join(
|
|
16939
|
-
dir,
|
|
16940
|
-
"templates",
|
|
16941
|
-
"project",
|
|
16942
|
-
"common",
|
|
16943
|
-
".github",
|
|
16944
|
-
"workflows"
|
|
16945
|
-
);
|
|
16946
|
-
if (fs30.existsSync(candidate)) return candidate;
|
|
16947
|
-
const parent = path30.dirname(dir);
|
|
16948
|
-
if (parent === dir) break;
|
|
16949
|
-
dir = parent;
|
|
16950
|
-
}
|
|
16951
|
-
throw new Error(
|
|
16952
|
-
`Could not locate templates/project/common/.github/workflows/ relative to ${start}. Pass explicit kitDir.`
|
|
16953
|
-
);
|
|
16954
|
-
}
|
|
16955
|
-
function unifiedDiff(name, projectContent, templateContent) {
|
|
16956
|
-
if (projectContent === templateContent) return "";
|
|
16957
|
-
const a = projectContent.split("\n");
|
|
16958
|
-
const b = templateContent.split("\n");
|
|
16959
|
-
const out = [`--- project/${name}`, `+++ template/${name}`];
|
|
16960
|
-
const max = Math.max(a.length, b.length);
|
|
16961
|
-
for (let i = 0; i < max; i++) {
|
|
16962
|
-
const av = a[i];
|
|
16963
|
-
const bv = b[i];
|
|
16964
|
-
if (av === bv) continue;
|
|
16965
|
-
if (av !== void 0) out.push(`-${i + 1}: ${av}`);
|
|
16966
|
-
if (bv !== void 0) out.push(`+${i + 1}: ${bv}`);
|
|
16967
|
-
}
|
|
16968
|
-
return out.join("\n");
|
|
16969
|
-
}
|
|
16970
|
-
function detectWorkflowDrift(args) {
|
|
16971
|
-
const projectWorkflowsDir = path30.join(
|
|
16972
|
-
args.projectDir,
|
|
16973
|
-
".github",
|
|
16974
|
-
"workflows"
|
|
16975
|
-
);
|
|
16976
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
16977
|
-
const kitWorkflowsDir = args.kitDir ? path30.join(
|
|
16978
|
-
args.kitDir,
|
|
16979
|
-
"templates",
|
|
16980
|
-
"project",
|
|
16981
|
-
"common",
|
|
16982
|
-
".github",
|
|
16983
|
-
"workflows"
|
|
16984
|
-
) : findKitTemplatesDir(here);
|
|
16985
|
-
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16986
|
-
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
16987
|
-
const version = readKitVersion(kitWorkflowsDir);
|
|
16988
|
-
const seen = /* @__PURE__ */ new Set();
|
|
16989
|
-
const files = [];
|
|
16990
|
-
for (const name of templateFiles) {
|
|
16991
|
-
seen.add(name);
|
|
16992
|
-
const projectPath2 = path30.join(projectWorkflowsDir, name);
|
|
16993
|
-
const templatePath = path30.join(kitWorkflowsDir, name);
|
|
16994
|
-
if (!fs30.existsSync(projectPath2)) {
|
|
16995
|
-
files.push({ name, status: "missing" });
|
|
16996
|
-
continue;
|
|
16976
|
+
message: `GitHub Actions is disabled for ${ownerRepo}, so the kit's CI workflows (pr.yml / merge.yml) will never run , the PR branch's migrations, tests, and schema-diff comment are all skipped. This is commonly an org-level policy on EMU repos, which a repo admin cannot override.`,
|
|
16977
|
+
suggestion: "Have an org owner enable Actions for this repo (repo Settings -> Actions -> General; if it says 'disabled by the organization', it must be enabled in the org's Actions policy). Until then, run the workflow steps locally: scripts/run-tests.sh against a Lakebase branch."
|
|
16978
|
+
});
|
|
16979
|
+
}
|
|
16997
16980
|
}
|
|
16998
|
-
|
|
16999
|
-
|
|
17000
|
-
|
|
17001
|
-
|
|
17002
|
-
|
|
17003
|
-
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
16981
|
+
} catch {
|
|
16982
|
+
}
|
|
16983
|
+
for (const wf of ["pr.yml", "merge.yml"]) {
|
|
16984
|
+
try {
|
|
16985
|
+
const p = path30.join(projectDir, ".github", "workflows", wf);
|
|
16986
|
+
if (!fs30.existsSync(p)) continue;
|
|
16987
|
+
const body = fs30.readFileSync(p, "utf8");
|
|
16988
|
+
if (/lakebase-scm-utils#v\d/.test(body)) {
|
|
16989
|
+
findings.push({
|
|
16990
|
+
id: "ci-workflow-substrate-pin",
|
|
16991
|
+
severity: "warn",
|
|
16992
|
+
message: `.github/workflows/${wf} hardcodes a substrate version pin (github:databricks-solutions/lakebase-scm-utils#v<ver>) instead of resolving .lakebase/scm-utils-ref at runtime, so bumping .lakebase/scm-utils-ref does NOT change the substrate CI actually runs.`,
|
|
16993
|
+
suggestion: "Re-emit the workflows from the current substrate templates so they resolve SCM_UTILS_REF from .lakebase/scm-utils-ref at CI time (updateWorkflows in scripts/lakebase/workflow-drift.ts; lakebase-doctor reports this as workflow-drift). Until then a scm-utils-ref bump will not reach CI."
|
|
16994
|
+
});
|
|
16995
|
+
}
|
|
16996
|
+
} catch {
|
|
16997
|
+
}
|
|
16998
|
+
}
|
|
16999
|
+
try {
|
|
17000
|
+
if (isGitTracked(projectDir, ".lakebase/workflow-state.json")) {
|
|
17001
|
+
findings.push({
|
|
17002
|
+
id: "scm-state-git-tracked",
|
|
17003
|
+
severity: "warn",
|
|
17004
|
+
message: ".lakebase/workflow-state.json (the per-working-tree SCM claim state) is git-tracked, so a branch checkout or `git reset --hard origin/<tier>` can restore a stale committed claim over the live one (the foreign-claim guard then refuses to drive until you abandon + reclaim).",
|
|
17005
|
+
suggestion: "If a wrong-feature-claim refusal follows a checkout, run lakebase-scm-adopt-state (or abandon + reclaim) to re-establish the live claim. The kit-ref run pin (.lakebase/kit-ref.local) already protects the kit version from the same checkout revert."
|
|
17007
17006
|
});
|
|
17008
17007
|
}
|
|
17008
|
+
} catch {
|
|
17009
17009
|
}
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
|
|
17010
|
+
if (!env.has("LAKEBASE_PROJECT_ID")) {
|
|
17011
|
+
findings.push({
|
|
17012
|
+
id: "env-missing-project-id",
|
|
17013
|
+
severity: "fail",
|
|
17014
|
+
message: ".env does not contain LAKEBASE_PROJECT_ID. The post-checkout hook will exit early; workflow CLIs will need an explicit --instance.",
|
|
17015
|
+
suggestion: "Set LAKEBASE_PROJECT_ID=<your project id> in .env"
|
|
17016
|
+
});
|
|
17013
17017
|
}
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17017
|
-
|
|
17018
|
-
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
const hasDrift = files.some((f) => f.status === "drifted" || f.status === "missing");
|
|
17022
|
-
return {
|
|
17023
|
-
overall: hasDrift ? "drift" : "ok",
|
|
17024
|
-
files
|
|
17025
|
-
};
|
|
17026
|
-
}
|
|
17027
|
-
function readKitVersion(kitWorkflowsDir) {
|
|
17028
|
-
let dir = kitWorkflowsDir;
|
|
17029
|
-
for (let i = 0; i < 5; i++) {
|
|
17030
|
-
dir = path30.dirname(dir);
|
|
17018
|
+
if (!instance) {
|
|
17019
|
+
return finalize({
|
|
17020
|
+
projectDir,
|
|
17021
|
+
workflowStatePresent,
|
|
17022
|
+
state: state ?? void 0,
|
|
17023
|
+
findings
|
|
17024
|
+
});
|
|
17031
17025
|
}
|
|
17026
|
+
let lakebaseBranches = [];
|
|
17032
17027
|
try {
|
|
17033
|
-
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17028
|
+
lakebaseBranches = await listBranches({ instance });
|
|
17029
|
+
} catch (err) {
|
|
17030
|
+
findings.push({
|
|
17031
|
+
id: "lakebase-unreachable",
|
|
17032
|
+
severity: "fail",
|
|
17033
|
+
message: `Could not list Lakebase branches for instance ${instance}: ${err instanceof Error ? err.message : String(err)}`,
|
|
17034
|
+
suggestion: "databricks auth login (or check DATABRICKS_CONFIG_PROFILE)."
|
|
17035
|
+
});
|
|
17036
|
+
return finalize({
|
|
17037
|
+
projectDir,
|
|
17038
|
+
workflowStatePresent,
|
|
17039
|
+
state: state ?? void 0,
|
|
17040
|
+
findings
|
|
17041
|
+
});
|
|
17038
17042
|
}
|
|
17039
|
-
|
|
17040
|
-
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17044
|
-
|
|
17045
|
-
|
|
17046
|
-
|
|
17047
|
-
function findKitCommandsDir(start) {
|
|
17048
|
-
let dir = start;
|
|
17049
|
-
for (let i = 0; i < 6; i++) {
|
|
17050
|
-
const candidate = path30.join(
|
|
17051
|
-
dir,
|
|
17052
|
-
"templates",
|
|
17053
|
-
"project",
|
|
17054
|
-
"common",
|
|
17055
|
-
".claude",
|
|
17056
|
-
"commands"
|
|
17057
|
-
);
|
|
17058
|
-
if (fs30.existsSync(candidate)) return candidate;
|
|
17059
|
-
const parent = path30.dirname(dir);
|
|
17060
|
-
if (parent === dir) break;
|
|
17061
|
-
dir = parent;
|
|
17043
|
+
const inferredTopology = inferTierTopology(lakebaseBranches);
|
|
17044
|
+
if (state && state.tier_topology !== inferredTopology) {
|
|
17045
|
+
findings.push({
|
|
17046
|
+
id: "tier-topology-mismatch",
|
|
17047
|
+
severity: "warn",
|
|
17048
|
+
message: `workflow-state records tier_topology=${state.tier_topology}, but the Lakebase tier inventory suggests ${inferredTopology}.`,
|
|
17049
|
+
suggestion: "lakebase-scm-adopt-state --force"
|
|
17050
|
+
});
|
|
17062
17051
|
}
|
|
17063
|
-
|
|
17064
|
-
|
|
17065
|
-
|
|
17066
|
-
|
|
17067
|
-
|
|
17068
|
-
|
|
17069
|
-
|
|
17070
|
-
}
|
|
17071
|
-
function detectCommandDrift(args) {
|
|
17072
|
-
const projectCommandsDir = path30.join(args.projectDir, ".claude", "commands");
|
|
17073
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
17074
|
-
const kitCommandsDir = args.kitDir ? path30.join(args.kitDir, "templates", "project", "common", ".claude", "commands") : findKitCommandsDir(here);
|
|
17075
|
-
const kitVersion = readKitVersionFromCommandsDir(kitCommandsDir);
|
|
17076
|
-
const templateFiles = fs30.existsSync(kitCommandsDir) ? fs30.readdirSync(kitCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
17077
|
-
const projectFiles = fs30.existsSync(projectCommandsDir) ? fs30.readdirSync(projectCommandsDir).filter((f) => f.endsWith(".md") && !COMMAND_HOOK_FILE_PATTERN.test(f)) : [];
|
|
17078
|
-
const seen = /* @__PURE__ */ new Set();
|
|
17079
|
-
const files = [];
|
|
17080
|
-
for (const name of templateFiles) {
|
|
17081
|
-
seen.add(name);
|
|
17082
|
-
const projectPath2 = path30.join(projectCommandsDir, name);
|
|
17083
|
-
const templatePath = path30.join(kitCommandsDir, name);
|
|
17084
|
-
const templateRaw = fs30.readFileSync(templatePath, "utf8");
|
|
17085
|
-
if (!fs30.existsSync(projectPath2)) {
|
|
17086
|
-
files.push({ name, status: "missing", kit_version: kitVersion });
|
|
17087
|
-
continue;
|
|
17088
|
-
}
|
|
17089
|
-
const projectContent = fs30.readFileSync(projectPath2, "utf8");
|
|
17090
|
-
const pinned = parsePinnedVersion(projectContent);
|
|
17091
|
-
const versionForCompare = pinned ?? kitVersion;
|
|
17092
|
-
const templateContent = applyCommandPlaceholders(templateRaw, versionForCompare);
|
|
17093
|
-
if (projectContent === templateContent) {
|
|
17094
|
-
files.push({
|
|
17095
|
-
name,
|
|
17096
|
-
status: "unchanged",
|
|
17097
|
-
pinned_version: pinned,
|
|
17098
|
-
kit_version: kitVersion
|
|
17099
|
-
});
|
|
17100
|
-
} else {
|
|
17101
|
-
files.push({
|
|
17102
|
-
name,
|
|
17103
|
-
status: "drifted",
|
|
17104
|
-
pinned_version: pinned,
|
|
17105
|
-
kit_version: kitVersion,
|
|
17106
|
-
diff: unifiedDiff(name, projectContent, templateContent)
|
|
17052
|
+
const headBranch = await getCurrentBranch({ cwd: projectDir });
|
|
17053
|
+
if (state && state.state === "feature-claimed") {
|
|
17054
|
+
if (state.branch && headBranch && headBranch !== state.branch) {
|
|
17055
|
+
findings.push({
|
|
17056
|
+
id: "head-branch-drift",
|
|
17057
|
+
severity: "warn",
|
|
17058
|
+
message: `workflow says feature-claimed for "${state.branch}", but HEAD is on "${headBranch}".`,
|
|
17059
|
+
suggestion: `git checkout '${state.branch}'`
|
|
17107
17060
|
});
|
|
17108
17061
|
}
|
|
17062
|
+
if (state.branch) {
|
|
17063
|
+
const sanitized = sanitizeBranchName(state.branch);
|
|
17064
|
+
let pair;
|
|
17065
|
+
try {
|
|
17066
|
+
pair = await getBranchByName(sanitized, { instance });
|
|
17067
|
+
} catch {
|
|
17068
|
+
pair = void 0;
|
|
17069
|
+
}
|
|
17070
|
+
if (!pair) {
|
|
17071
|
+
findings.push({
|
|
17072
|
+
id: "lakebase-pair-missing",
|
|
17073
|
+
severity: "fail",
|
|
17074
|
+
message: `workflow says feature-claimed for "${state.branch}", but no Lakebase branch "${sanitized}" exists.`,
|
|
17075
|
+
suggestion: `lakebase-scm-abandon-feature # reset state; re-claim if needed`
|
|
17076
|
+
});
|
|
17077
|
+
} else if (state.lakebase_branch_uid && pair.uid !== state.lakebase_branch_uid) {
|
|
17078
|
+
findings.push({
|
|
17079
|
+
id: "lakebase-uid-drift",
|
|
17080
|
+
severity: "warn",
|
|
17081
|
+
message: `workflow records lakebase_branch_uid=${state.lakebase_branch_uid}, but the live branch reports ${pair.uid}.`,
|
|
17082
|
+
suggestion: "lakebase-scm-adopt-state --force"
|
|
17083
|
+
});
|
|
17084
|
+
}
|
|
17085
|
+
}
|
|
17109
17086
|
}
|
|
17110
|
-
|
|
17111
|
-
|
|
17112
|
-
|
|
17087
|
+
if (state && state.state === "feature-claimed" && state.branch) {
|
|
17088
|
+
const envBranchId = env.get("LAKEBASE_BRANCH_ID");
|
|
17089
|
+
const sanitized = sanitizeBranchName(state.branch);
|
|
17090
|
+
if (envBranchId && envBranchId !== sanitized) {
|
|
17091
|
+
findings.push({
|
|
17092
|
+
id: "env-branch-drift",
|
|
17093
|
+
severity: "warn",
|
|
17094
|
+
message: `.env LAKEBASE_BRANCH_ID=${envBranchId} but workflow says ${sanitized}. The post-checkout hook may not have run since the last branch switch.`,
|
|
17095
|
+
suggestion: `git checkout '${state.branch}' # re-fires post-checkout`
|
|
17096
|
+
});
|
|
17097
|
+
}
|
|
17113
17098
|
}
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
}
|
|
17124
|
-
|
|
17125
|
-
let dir = kitCommandsDir;
|
|
17126
|
-
for (let i = 0; i < 5; i++) {
|
|
17127
|
-
dir = path30.dirname(dir);
|
|
17099
|
+
if (headBranch && !TIER_LEAFS2.has(headBranch) && headBranch.startsWith(FEATURE_PREFIX)) {
|
|
17100
|
+
const sanitized = sanitizeBranchName(headBranch);
|
|
17101
|
+
const paired = lakebaseBranches.some((b) => leafOf2(b) === sanitized);
|
|
17102
|
+
if (!paired) {
|
|
17103
|
+
findings.push({
|
|
17104
|
+
id: "orphan-current-branch",
|
|
17105
|
+
severity: "fail",
|
|
17106
|
+
message: `Current git branch "${headBranch}" has no Lakebase pair (post-checkout fallback retired in phase C).`,
|
|
17107
|
+
suggestion: `lakebase-scm-recover-orphans --claim --only-branch '${headBranch}'`
|
|
17108
|
+
});
|
|
17109
|
+
}
|
|
17128
17110
|
}
|
|
17129
17111
|
try {
|
|
17130
|
-
const
|
|
17131
|
-
|
|
17132
|
-
|
|
17112
|
+
const heads = await collapseMigrationHeads({ projectDir, dryRun: true });
|
|
17113
|
+
if (heads.headsBefore.length > 1) {
|
|
17114
|
+
findings.push({
|
|
17115
|
+
id: "multiple-migration-heads",
|
|
17116
|
+
severity: "fail",
|
|
17117
|
+
message: `Migrations have ${heads.headsBefore.length} heads (${heads.headsBefore.join(", ")}); a sibling-feature merge left them un-collapsed. \`upgrade head\` will refuse until they are unified.`,
|
|
17118
|
+
suggestion: "lakebase-collapse-heads"
|
|
17119
|
+
});
|
|
17120
|
+
}
|
|
17133
17121
|
} catch {
|
|
17134
|
-
return "unknown";
|
|
17135
17122
|
}
|
|
17123
|
+
if (instance && state?.branch) {
|
|
17124
|
+
try {
|
|
17125
|
+
const localIds = listSchemaMigrations({ projectDir }).map((m) => m.version);
|
|
17126
|
+
let orphanRev = null;
|
|
17127
|
+
try {
|
|
17128
|
+
const status = await schemaMigrationStatus({ instance, branch: state.branch, projectDir });
|
|
17129
|
+
if (dbRevisionOrphaned(status.current, localIds)) orphanRev = status.current ?? null;
|
|
17130
|
+
} catch (e) {
|
|
17131
|
+
orphanRev = parseAlembicMissingRevision(e instanceof Error ? e.message : String(e));
|
|
17132
|
+
}
|
|
17133
|
+
if (orphanRev) {
|
|
17134
|
+
findings.push({
|
|
17135
|
+
id: "db-ahead-of-code",
|
|
17136
|
+
severity: "fail",
|
|
17137
|
+
message: `The paired branch DB is AHEAD of code: applied revision '${orphanRev}' has no local migration file. An aborted build likely migrated this branch and a git reset removed the migration file; alembic accept/deploy/promote will fail "Can't locate revision".`,
|
|
17138
|
+
suggestion: "reset the paired branch DB to the code head (downgrade/stamp + drop reset-created tables), or delete the branch and re-cut it clean from its tier"
|
|
17139
|
+
});
|
|
17140
|
+
}
|
|
17141
|
+
} catch {
|
|
17142
|
+
}
|
|
17143
|
+
}
|
|
17144
|
+
return finalize({
|
|
17145
|
+
projectDir,
|
|
17146
|
+
workflowStatePresent,
|
|
17147
|
+
state: state ?? void 0,
|
|
17148
|
+
inferredTierTopology: inferredTopology,
|
|
17149
|
+
findings
|
|
17150
|
+
});
|
|
17136
17151
|
}
|
|
17137
|
-
function
|
|
17138
|
-
|
|
17139
|
-
const
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
commands
|
|
17144
|
-
};
|
|
17152
|
+
function finalize(report) {
|
|
17153
|
+
let worst = "ok";
|
|
17154
|
+
for (const f of report.findings) {
|
|
17155
|
+
worst = worstOf(worst, f.severity);
|
|
17156
|
+
}
|
|
17157
|
+
return { ...report, worstSeverity: worst };
|
|
17145
17158
|
}
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
"
|
|
17151
|
-
);
|
|
17152
|
-
const here = path30.dirname(new URL(import.meta.url).pathname);
|
|
17153
|
-
const kitWorkflowsDir = args.kitDir ? path30.join(
|
|
17154
|
-
args.kitDir,
|
|
17155
|
-
"templates",
|
|
17156
|
-
"project",
|
|
17157
|
-
"common",
|
|
17158
|
-
".github",
|
|
17159
|
-
"workflows"
|
|
17160
|
-
) : findKitTemplatesDir(here);
|
|
17161
|
-
const substitute = args.substitute !== false;
|
|
17162
|
-
const dryRun = args.dryRun === true;
|
|
17163
|
-
const pruneExtras = args.pruneExtras === true;
|
|
17164
|
-
const templateFiles = fs30.existsSync(kitWorkflowsDir) ? fs30.readdirSync(kitWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
17165
|
-
const projectFiles = fs30.existsSync(projectWorkflowsDir) ? fs30.readdirSync(projectWorkflowsDir).filter((f) => f.endsWith(".yml")) : [];
|
|
17166
|
-
if (!dryRun && templateFiles.length > 0 && !fs30.existsSync(projectWorkflowsDir)) {
|
|
17167
|
-
fs30.mkdirSync(projectWorkflowsDir, { recursive: true });
|
|
17159
|
+
var ScmDoctorFixError = class extends Error {
|
|
17160
|
+
constructor(message, code) {
|
|
17161
|
+
super(message);
|
|
17162
|
+
this.code = code;
|
|
17163
|
+
this.name = "ScmDoctorFixError";
|
|
17168
17164
|
}
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
} else {
|
|
17186
|
-
outcome = "updated";
|
|
17187
|
-
}
|
|
17188
|
-
if (!dryRun && outcome !== "unchanged") {
|
|
17189
|
-
fs30.writeFileSync(projectPath2, desired);
|
|
17190
|
-
}
|
|
17191
|
-
files.push({ name, outcome });
|
|
17165
|
+
code;
|
|
17166
|
+
};
|
|
17167
|
+
var FIXABLE_FINDING_IDS = [
|
|
17168
|
+
"env-branch-drift",
|
|
17169
|
+
"head-branch-drift",
|
|
17170
|
+
"tier-topology-mismatch",
|
|
17171
|
+
"orphan-current-branch",
|
|
17172
|
+
"multiple-migration-heads",
|
|
17173
|
+
"db-ahead-of-code"
|
|
17174
|
+
];
|
|
17175
|
+
async function fixFinding(args) {
|
|
17176
|
+
if (!FIXABLE_FINDING_IDS.includes(args.findingId)) {
|
|
17177
|
+
throw new ScmDoctorFixError(
|
|
17178
|
+
`Finding "${args.findingId}" is not supported by --fix. Supported: ${FIXABLE_FINDING_IDS.join(", ")}.`,
|
|
17179
|
+
"unsupported-finding"
|
|
17180
|
+
);
|
|
17192
17181
|
}
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17182
|
+
const report = args.report ?? await runDoctor({ projectDir: args.projectDir, instance: args.instance });
|
|
17183
|
+
const present = report.findings.find((f) => f.id === args.findingId);
|
|
17184
|
+
if (!present) {
|
|
17185
|
+
throw new ScmDoctorFixError(
|
|
17186
|
+
`Finding "${args.findingId}" is not present in the current report. Re-run lakebase-scm-doctor to see what needs fixing.`,
|
|
17187
|
+
"finding-not-present"
|
|
17188
|
+
);
|
|
17189
|
+
}
|
|
17190
|
+
let action = "";
|
|
17191
|
+
try {
|
|
17192
|
+
switch (args.findingId) {
|
|
17193
|
+
case "env-branch-drift": {
|
|
17194
|
+
const branch = report.state?.branch;
|
|
17195
|
+
if (!branch) {
|
|
17196
|
+
throw new ScmDoctorFixError(
|
|
17197
|
+
"Cannot fix: workflow state has no branch field.",
|
|
17198
|
+
"fix-failed"
|
|
17199
|
+
);
|
|
17200
|
+
}
|
|
17201
|
+
const sanitized = sanitizeBranchName(branch);
|
|
17202
|
+
const envFile = path30.join(args.projectDir, ".env");
|
|
17203
|
+
updateEnvConnection({
|
|
17204
|
+
envPath: envFile,
|
|
17205
|
+
projectId: readEnvVar(envFile, "LAKEBASE_PROJECT_ID") ?? "",
|
|
17206
|
+
branchId: sanitized,
|
|
17207
|
+
username: readEnvVar(envFile, "DB_USERNAME") ?? "",
|
|
17208
|
+
endpointHost: readEnvVar(envFile, "LAKEBASE_HOST")
|
|
17209
|
+
});
|
|
17210
|
+
action = `rewrote .env LAKEBASE_BRANCH_ID=${sanitized} (metadata only; the app mints its token at runtime)`;
|
|
17211
|
+
break;
|
|
17212
|
+
}
|
|
17213
|
+
case "head-branch-drift": {
|
|
17214
|
+
const branch = report.state?.branch;
|
|
17215
|
+
if (!branch) {
|
|
17216
|
+
throw new ScmDoctorFixError(
|
|
17217
|
+
"Cannot fix: workflow state has no branch field.",
|
|
17218
|
+
"fix-failed"
|
|
17219
|
+
);
|
|
17220
|
+
}
|
|
17221
|
+
await exec2(`git checkout ${shellEscape3(branch)}`, {
|
|
17222
|
+
cwd: args.projectDir,
|
|
17223
|
+
timeout: 15e3
|
|
17224
|
+
});
|
|
17225
|
+
action = `git checkout ${branch} (re-fires post-checkout to resync HEAD)`;
|
|
17226
|
+
break;
|
|
17227
|
+
}
|
|
17228
|
+
case "tier-topology-mismatch": {
|
|
17229
|
+
const instance = args.instance ?? report.state?.project_id;
|
|
17230
|
+
if (!instance) {
|
|
17231
|
+
throw new ScmDoctorFixError(
|
|
17232
|
+
"Cannot fix: missing Lakebase project id.",
|
|
17233
|
+
"fix-failed"
|
|
17234
|
+
);
|
|
17235
|
+
}
|
|
17236
|
+
await adoptScmState({
|
|
17237
|
+
projectDir: args.projectDir,
|
|
17238
|
+
instance,
|
|
17239
|
+
force: true
|
|
17240
|
+
});
|
|
17241
|
+
action = `adopted state with --force to re-infer tier_topology`;
|
|
17242
|
+
break;
|
|
17243
|
+
}
|
|
17244
|
+
case "orphan-current-branch": {
|
|
17245
|
+
const instance = args.instance ?? report.state?.project_id;
|
|
17246
|
+
if (!instance) {
|
|
17247
|
+
throw new ScmDoctorFixError(
|
|
17248
|
+
"Cannot fix: missing Lakebase project id.",
|
|
17249
|
+
"fix-failed"
|
|
17250
|
+
);
|
|
17251
|
+
}
|
|
17252
|
+
const headBranch = await getCurrentBranch({ cwd: args.projectDir });
|
|
17253
|
+
if (!headBranch) {
|
|
17254
|
+
throw new ScmDoctorFixError(
|
|
17255
|
+
"Cannot fix: detached HEAD or no current branch.",
|
|
17256
|
+
"fix-failed"
|
|
17257
|
+
);
|
|
17258
|
+
}
|
|
17259
|
+
await recoverOrphans({
|
|
17260
|
+
projectDir: args.projectDir,
|
|
17261
|
+
instance,
|
|
17262
|
+
claim: true,
|
|
17263
|
+
onlyBranch: headBranch
|
|
17264
|
+
});
|
|
17265
|
+
action = `recovered orphan ${headBranch} via createFeaturePairedBranch`;
|
|
17266
|
+
break;
|
|
17267
|
+
}
|
|
17268
|
+
case "multiple-migration-heads": {
|
|
17269
|
+
const r = await collapseMigrationHeads({ projectDir: args.projectDir });
|
|
17270
|
+
if (r.status !== "ok" || !r.mergeRevision) {
|
|
17271
|
+
throw new ScmDoctorFixError(
|
|
17272
|
+
`Expected to create a merge revision but got status="${r.status}".`,
|
|
17273
|
+
"fix-failed"
|
|
17274
|
+
);
|
|
17275
|
+
}
|
|
17276
|
+
action = `collapsed ${r.headsBefore.length} heads into merge revision ${r.mergeRevision} (commit it)`;
|
|
17277
|
+
break;
|
|
17278
|
+
}
|
|
17279
|
+
case "db-ahead-of-code": {
|
|
17280
|
+
const r = await abandonFeatureBranch({ projectDir: args.projectDir, instance: args.instance, force: true });
|
|
17281
|
+
action = `abandoned the feature (deleted the polluted paired branch${r.lakebaseDeleted ? "" : " , Lakebase delete reported not-deleted"}) and reset state to '${r.state.state}'; re-claim to re-fork a clean branch from the tier`;
|
|
17282
|
+
break;
|
|
17199
17283
|
}
|
|
17200
|
-
files.push({ name, outcome: "removed" });
|
|
17201
17284
|
}
|
|
17285
|
+
} catch (err) {
|
|
17286
|
+
if (err instanceof ScmDoctorFixError) throw err;
|
|
17287
|
+
throw new ScmDoctorFixError(
|
|
17288
|
+
`Remediation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
17289
|
+
"fix-failed"
|
|
17290
|
+
);
|
|
17202
17291
|
}
|
|
17203
|
-
const
|
|
17204
|
-
|
|
17205
|
-
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
17209
|
-
|
|
17210
|
-
|
|
17211
|
-
return { files, changed };
|
|
17292
|
+
const postReport = await runDoctor({
|
|
17293
|
+
projectDir: args.projectDir,
|
|
17294
|
+
instance: args.instance
|
|
17295
|
+
});
|
|
17296
|
+
return { findingId: args.findingId, action, postReport };
|
|
17297
|
+
}
|
|
17298
|
+
function shellEscape3(s) {
|
|
17299
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
17212
17300
|
}
|
|
17213
17301
|
|
|
17214
17302
|
// scripts/lakebase/doctor.ts
|
|
17303
|
+
init_esm_shims();
|
|
17304
|
+
import * as fs31 from "fs";
|
|
17305
|
+
import * as path31 from "path";
|
|
17306
|
+
import * as cp6 from "child_process";
|
|
17215
17307
|
function readEnvFile(projectDir) {
|
|
17216
17308
|
const envPath = path31.join(projectDir, ".env");
|
|
17217
17309
|
if (!fs31.existsSync(envPath)) return {};
|
|
@@ -17644,7 +17736,7 @@ function checkWorkflowDrift(projectDir) {
|
|
|
17644
17736
|
status: "warn",
|
|
17645
17737
|
message: `Scaffolded workflows drift from kit: ${drifted} drifted, ${missing} missing`,
|
|
17646
17738
|
detail: { files: report.files.map((f) => ({ name: f.name, status: f.status })) },
|
|
17647
|
-
hint: "
|
|
17739
|
+
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."
|
|
17648
17740
|
};
|
|
17649
17741
|
} catch (err) {
|
|
17650
17742
|
return {
|
|
@@ -18120,63 +18212,6 @@ async function discardAllChanges(args) {
|
|
|
18120
18212
|
await exec2("git clean -fd", { cwd: args.cwd });
|
|
18121
18213
|
}
|
|
18122
18214
|
|
|
18123
|
-
// scripts/git/sync.ts
|
|
18124
|
-
init_esm_shims();
|
|
18125
|
-
async function currentBranchName2(cwd) {
|
|
18126
|
-
try {
|
|
18127
|
-
return await exec2("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
18128
|
-
} catch {
|
|
18129
|
-
return "";
|
|
18130
|
-
}
|
|
18131
|
-
}
|
|
18132
|
-
async function push(args) {
|
|
18133
|
-
await exec2("git push", { cwd: args.cwd });
|
|
18134
|
-
}
|
|
18135
|
-
async function pull(args) {
|
|
18136
|
-
await exec2("git pull", { cwd: args.cwd });
|
|
18137
|
-
}
|
|
18138
|
-
async function publishBranch(args) {
|
|
18139
|
-
const remote = args.remote ?? "origin";
|
|
18140
|
-
const branch = await currentBranchName2(args.cwd);
|
|
18141
|
-
if (!branch) throw new Error("No current branch");
|
|
18142
|
-
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
18143
|
-
cwd: args.cwd
|
|
18144
|
-
});
|
|
18145
|
-
}
|
|
18146
|
-
async function pushCurrentBranchForPr(args) {
|
|
18147
|
-
const remote = args.remote ?? "origin";
|
|
18148
|
-
const branch = await currentBranchName2(args.cwd);
|
|
18149
|
-
if (!branch) throw new Error("No current branch");
|
|
18150
|
-
const upstreamSet = await hasUpstream({ cwd: args.cwd });
|
|
18151
|
-
if (!upstreamSet) {
|
|
18152
|
-
await exec2(`git push -u ${remote} ${shq(branch)}`, {
|
|
18153
|
-
cwd: args.cwd
|
|
18154
|
-
});
|
|
18155
|
-
} else {
|
|
18156
|
-
await exec2("git push", { cwd: args.cwd });
|
|
18157
|
-
}
|
|
18158
|
-
}
|
|
18159
|
-
async function fetch2(args) {
|
|
18160
|
-
const parts = ["git fetch"];
|
|
18161
|
-
if (args.prune) parts.push("--prune");
|
|
18162
|
-
if (args.all) parts.push("--all");
|
|
18163
|
-
await exec2(parts.join(" "), { cwd: args.cwd });
|
|
18164
|
-
}
|
|
18165
|
-
async function pullFrom(args) {
|
|
18166
|
-
await exec2(`git pull ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
18167
|
-
cwd: args.cwd
|
|
18168
|
-
});
|
|
18169
|
-
}
|
|
18170
|
-
async function pushTo(args) {
|
|
18171
|
-
await exec2(`git push ${shq(args.remote)} ${shq(args.branch)}`, {
|
|
18172
|
-
cwd: args.cwd
|
|
18173
|
-
});
|
|
18174
|
-
}
|
|
18175
|
-
async function sync(args) {
|
|
18176
|
-
await exec2("git pull", { cwd: args.cwd });
|
|
18177
|
-
await exec2("git push", { cwd: args.cwd });
|
|
18178
|
-
}
|
|
18179
|
-
|
|
18180
18215
|
// scripts/git/branch-tag.ts
|
|
18181
18216
|
init_esm_shims();
|
|
18182
18217
|
var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["production", "main", "master"]);
|
|
@@ -18581,6 +18616,7 @@ export {
|
|
|
18581
18616
|
createWorktree,
|
|
18582
18617
|
cutBackup,
|
|
18583
18618
|
databricksAuthPrereqMessage,
|
|
18619
|
+
defaultRefreshPromoteWorkflows,
|
|
18584
18620
|
delay,
|
|
18585
18621
|
deleteAppEndpoint,
|
|
18586
18622
|
deleteBranch,
|