@databricks-solutions/lakebase-scm-utils 0.3.5 → 0.3.7

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.
Files changed (49) hide show
  1. package/dist/scripts/index.cjs +55 -25
  2. package/dist/scripts/index.d.cts +1 -1
  3. package/dist/scripts/index.d.ts +1 -1
  4. package/dist/scripts/index.js +54 -25
  5. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
  6. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
  7. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
  8. package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
  9. package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
  10. package/dist/scripts/lakebase/detect-language.cli.js +2 -2
  11. package/dist/scripts/lakebase/doctor.cli.cjs +2 -2
  12. package/dist/scripts/lakebase/doctor.cli.js +2 -2
  13. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  15. package/dist/scripts/lakebase/index.cjs +55 -25
  16. package/dist/scripts/lakebase/index.d.cts +13 -4
  17. package/dist/scripts/lakebase/index.d.ts +13 -4
  18. package/dist/scripts/lakebase/index.js +54 -25
  19. package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
  20. package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
  21. package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
  22. package/dist/scripts/lakebase/new-migration.cli.js +2 -2
  23. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  25. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
  26. package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
  27. package/dist/scripts/lakebase/scm-abandon-feature.cli.cjs +37 -22
  28. package/dist/scripts/lakebase/scm-abandon-feature.cli.js +34 -22
  29. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
  30. package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
  31. package/dist/scripts/lakebase/scm-doctor.cli.cjs +36 -21
  32. package/dist/scripts/lakebase/scm-doctor.cli.js +36 -21
  33. package/dist/scripts/lakebase/scm-merge.cli.cjs +2 -2
  34. package/dist/scripts/lakebase/scm-merge.cli.js +2 -2
  35. package/dist/scripts/lakebase/scm-park-feature.cli.cjs +586 -0
  36. package/dist/scripts/lakebase/scm-park-feature.cli.d.cts +4 -0
  37. package/dist/scripts/lakebase/scm-park-feature.cli.d.ts +4 -0
  38. package/dist/scripts/lakebase/scm-park-feature.cli.js +546 -0
  39. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
  40. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
  41. package/dist/scripts/util/index.cjs +2 -2
  42. package/dist/scripts/util/index.js +2 -2
  43. package/package.json +2 -1
  44. package/scripts/lakebase/enable-e2e.ts +34 -4
  45. package/scripts/lakebase/scm-abandon-feature.cli.ts +3 -3
  46. package/scripts/lakebase/scm-abandon-feature.ts +19 -83
  47. package/scripts/lakebase/scm-park-feature.cli.ts +150 -0
  48. package/scripts/lakebase/scm-park-feature.ts +71 -0
  49. package/scripts/lakebase/scm-release-claim.ts +146 -0
@@ -0,0 +1,586 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // scripts/lakebase/scm-park-feature.cli.ts
32
+ var scm_park_feature_cli_exports = {};
33
+ __export(scm_park_feature_cli_exports, {
34
+ runScmParkFeatureCli: () => runScmParkFeatureCli
35
+ });
36
+ module.exports = __toCommonJS(scm_park_feature_cli_exports);
37
+
38
+ // node_modules/tsup/assets/cjs_shims.js
39
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
40
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
41
+
42
+ // scripts/lakebase/scm-park-feature.cli.ts
43
+ var path2 = __toESM(require("path"), 1);
44
+
45
+ // scripts/util/cli-entry.ts
46
+ var import_node_fs = require("fs");
47
+ var import_node_url = require("url");
48
+ function isCliEntry(importMetaUrl2) {
49
+ const invokedRaw = process.argv[1];
50
+ if (!invokedRaw) return false;
51
+ let invokedResolved;
52
+ let moduleResolved;
53
+ try {
54
+ invokedResolved = (0, import_node_fs.realpathSync)(invokedRaw);
55
+ } catch {
56
+ return false;
57
+ }
58
+ try {
59
+ moduleResolved = (0, import_node_fs.realpathSync)((0, import_node_url.fileURLToPath)(importMetaUrl2));
60
+ } catch {
61
+ return false;
62
+ }
63
+ return invokedResolved === moduleResolved;
64
+ }
65
+
66
+ // scripts/util/exec.ts
67
+ var cp = __toESM(require("child_process"), 1);
68
+ function shq(s) {
69
+ return `'${s.replace(/'/g, "'\\''")}'`;
70
+ }
71
+ function exec2(command, opts = {}) {
72
+ return new Promise((resolve2, reject) => {
73
+ const options = {
74
+ cwd: opts.cwd,
75
+ timeout: opts.timeout ?? 6e4
76
+ };
77
+ if (opts.env) {
78
+ options.env = { ...process.env, ...opts.env };
79
+ }
80
+ cp.exec(command, options, (err, stdout, stderr) => {
81
+ if (err) {
82
+ const msg = String(stderr || err.message);
83
+ reject(new Error(`${command}: ${msg}`));
84
+ return;
85
+ }
86
+ resolve2(String(stdout).trim());
87
+ });
88
+ });
89
+ }
90
+
91
+ // scripts/git/inspect.ts
92
+ async function getCurrentBranch(args) {
93
+ try {
94
+ const name = await exec2("git rev-parse --abbrev-ref HEAD", {
95
+ cwd: args.cwd
96
+ });
97
+ return name === "HEAD" ? "" : name;
98
+ } catch {
99
+ return "";
100
+ }
101
+ }
102
+
103
+ // scripts/git/status.ts
104
+ async function isDirty(args) {
105
+ try {
106
+ const ignore = args.ignore ?? [];
107
+ const untrackedFlag = args.untracked === false ? " --untracked-files=no" : "";
108
+ let command = `git status --porcelain${untrackedFlag}`;
109
+ if (ignore.length > 0) {
110
+ const excludes = ignore.map((p) => shq(`:(exclude)${p.replace(/\/+$/, "")}`)).join(" ");
111
+ command = `git status --porcelain${untrackedFlag} -- . ${excludes}`;
112
+ }
113
+ const out = await exec2(command, { cwd: args.cwd });
114
+ return out.trim().length > 0;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+
120
+ // scripts/lakebase/scm-workflow-state.ts
121
+ var fs = __toESM(require("fs"), 1);
122
+ var path = __toESM(require("path"), 1);
123
+ var import_node_child_process = require("child_process");
124
+ function isGitTracked(projectDir, rel) {
125
+ try {
126
+ (0, import_node_child_process.execFileSync)("git", ["ls-files", "--error-unmatch", "--", rel], { cwd: projectDir, stdio: "ignore" });
127
+ return true;
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+ function ensureWorkflowStateUntracked(projectDir) {
133
+ const rel = ".lakebase/workflow-state.json";
134
+ try {
135
+ if (isGitTracked(projectDir, rel)) {
136
+ (0, import_node_child_process.execFileSync)("git", ["rm", "--cached", "--quiet", "--ignore-unmatch", "--", rel], { cwd: projectDir, stdio: "ignore" });
137
+ }
138
+ } catch {
139
+ }
140
+ try {
141
+ const gitignore = path.join(projectDir, ".gitignore");
142
+ const existing = fs.existsSync(gitignore) ? fs.readFileSync(gitignore, "utf8") : "";
143
+ if (!existing.split("\n").some((l) => l.trim() === rel)) {
144
+ const sep = existing === "" || existing.endsWith("\n") ? "" : "\n";
145
+ fs.appendFileSync(
146
+ gitignore,
147
+ `${sep}# Runtime SCM claim state (per working tree): a branch checkout must never restore
148
+ # a stale committed claim over the live one (issue #203 / Finding 28).
149
+ ${rel}
150
+ `
151
+ );
152
+ }
153
+ } catch {
154
+ }
155
+ }
156
+ var SCM_STATES = [
157
+ "scaffold-complete",
158
+ "feature-claimed",
159
+ "pr-ready",
160
+ "ci-green",
161
+ "merged"
162
+ ];
163
+ var STATE_INDEX = SCM_STATES.reduce(
164
+ (acc, s, i) => ({ ...acc, [s]: i }),
165
+ {}
166
+ );
167
+ var STATE_FILE_REL = ".lakebase/workflow-state.json";
168
+ function stateFilePath(projectDir) {
169
+ return path.join(projectDir, STATE_FILE_REL);
170
+ }
171
+ function readWorkflowState(projectDir) {
172
+ const p = stateFilePath(projectDir);
173
+ if (!fs.existsSync(p)) return null;
174
+ const raw = fs.readFileSync(p, "utf8");
175
+ let parsed;
176
+ try {
177
+ parsed = JSON.parse(raw);
178
+ } catch (e) {
179
+ throw new Error(
180
+ `Failed to parse ${STATE_FILE_REL}: ${e.message}`
181
+ );
182
+ }
183
+ const result = validateWorkflowState(parsed);
184
+ if (!result.ok) {
185
+ const summary = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n");
186
+ throw new Error(
187
+ `Invalid ${STATE_FILE_REL}:
188
+ ${summary}
189
+
190
+ Fix the file or delete it to re-init.`
191
+ );
192
+ }
193
+ return result.value;
194
+ }
195
+ function writeWorkflowState(projectDir, state) {
196
+ const result = validateWorkflowState(state);
197
+ if (!result.ok) {
198
+ const summary = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join("\n");
199
+ throw new Error(`Refusing to write invalid SCM state:
200
+ ${summary}`);
201
+ }
202
+ ensureWorkflowStateUntracked(projectDir);
203
+ const dir = path.join(projectDir, ".lakebase");
204
+ fs.mkdirSync(dir, { recursive: true });
205
+ const target = stateFilePath(projectDir);
206
+ const tmp = `${target}.tmp`;
207
+ const ordered = orderForOutput(result.value);
208
+ fs.writeFileSync(tmp, `${JSON.stringify(ordered, null, 2)}
209
+ `, "utf8");
210
+ fs.renameSync(tmp, target);
211
+ }
212
+ function validateWorkflowState(value) {
213
+ const errors = [];
214
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
215
+ return {
216
+ ok: false,
217
+ errors: [{ path: "$", message: "must be an object" }]
218
+ };
219
+ }
220
+ const v = value;
221
+ if (v.version !== 1) {
222
+ errors.push({ path: "version", message: `must be 1, got ${String(v.version)}` });
223
+ }
224
+ if (typeof v.state !== "string" || !SCM_STATES.includes(v.state)) {
225
+ errors.push({
226
+ path: "state",
227
+ message: `must be one of ${SCM_STATES.join(" | ")}`
228
+ });
229
+ }
230
+ if (v.tier_topology !== 1 && v.tier_topology !== 2 && v.tier_topology !== 3) {
231
+ errors.push({
232
+ path: "tier_topology",
233
+ message: "must be 1, 2, or 3"
234
+ });
235
+ }
236
+ if (typeof v.project_id !== "string" || v.project_id.length === 0) {
237
+ errors.push({
238
+ path: "project_id",
239
+ message: "must be a non-empty string"
240
+ });
241
+ }
242
+ const stringFields = [
243
+ "feature_id",
244
+ "branch",
245
+ "parent_branch",
246
+ "lakebase_branch_uid",
247
+ "claimed_at",
248
+ "pr_url",
249
+ "pushed_at",
250
+ "ci_run_url",
251
+ "ci_green_at",
252
+ "merged_at",
253
+ "migrate_run_url",
254
+ "migrate_completed_at",
255
+ "$schema"
256
+ ];
257
+ for (const key of stringFields) {
258
+ if (v[key] === void 0) continue;
259
+ if (typeof v[key] !== "string" || v[key].length === 0) {
260
+ errors.push({
261
+ path: key,
262
+ message: "must be a non-empty string when present"
263
+ });
264
+ }
265
+ }
266
+ const requiredForState = {
267
+ "scaffold-complete": [],
268
+ "feature-claimed": [
269
+ "feature_id",
270
+ "branch",
271
+ "parent_branch",
272
+ "lakebase_branch_uid",
273
+ "claimed_at"
274
+ ],
275
+ "pr-ready": [
276
+ "feature_id",
277
+ "branch",
278
+ "parent_branch",
279
+ "lakebase_branch_uid",
280
+ "claimed_at",
281
+ "pr_url",
282
+ "pushed_at"
283
+ ],
284
+ "ci-green": [
285
+ "feature_id",
286
+ "branch",
287
+ "parent_branch",
288
+ "lakebase_branch_uid",
289
+ "claimed_at",
290
+ "pr_url",
291
+ "pushed_at",
292
+ "ci_run_url",
293
+ "ci_green_at"
294
+ ],
295
+ merged: [
296
+ "feature_id",
297
+ "branch",
298
+ "parent_branch",
299
+ "lakebase_branch_uid",
300
+ "claimed_at",
301
+ "pr_url",
302
+ "pushed_at",
303
+ "ci_run_url",
304
+ "ci_green_at",
305
+ "merged_at"
306
+ ]
307
+ };
308
+ if (typeof v.state === "string" && SCM_STATES.includes(v.state)) {
309
+ for (const key of requiredForState[v.state]) {
310
+ if (v[key] === void 0) {
311
+ errors.push({
312
+ path: key,
313
+ message: `required when state is "${v.state}"`
314
+ });
315
+ }
316
+ }
317
+ }
318
+ const allowedKeys = /* @__PURE__ */ new Set([
319
+ "$schema",
320
+ "version",
321
+ "state",
322
+ "tier_topology",
323
+ "project_id",
324
+ "feature_id",
325
+ "branch",
326
+ "parent_branch",
327
+ "lakebase_branch_uid",
328
+ "claimed_at",
329
+ "pr_url",
330
+ "pushed_at",
331
+ "ci_run_url",
332
+ "ci_green_at",
333
+ "merged_at",
334
+ "migrate_run_url",
335
+ "migrate_completed_at"
336
+ ]);
337
+ for (const key of Object.keys(v)) {
338
+ if (!allowedKeys.has(key)) {
339
+ errors.push({ path: key, message: "unknown property" });
340
+ }
341
+ }
342
+ if (errors.length > 0) return { ok: false, errors };
343
+ return { ok: true, value: v };
344
+ }
345
+ function orderForOutput(state) {
346
+ const keyOrder = [
347
+ "$schema",
348
+ "version",
349
+ "state",
350
+ "tier_topology",
351
+ "project_id",
352
+ "feature_id",
353
+ "branch",
354
+ "parent_branch",
355
+ "lakebase_branch_uid",
356
+ "claimed_at",
357
+ "pr_url",
358
+ "pushed_at",
359
+ "ci_run_url",
360
+ "ci_green_at",
361
+ "merged_at",
362
+ "migrate_run_url",
363
+ "migrate_completed_at"
364
+ ];
365
+ const out = {};
366
+ for (const k of keyOrder) {
367
+ if (state[k] !== void 0) {
368
+ out[k] = state[k];
369
+ }
370
+ }
371
+ return out;
372
+ }
373
+
374
+ // scripts/lakebase/scm-release-claim.ts
375
+ var ScmReleaseError = class extends Error {
376
+ constructor(message, code) {
377
+ super(message);
378
+ this.code = code;
379
+ this.name = "ScmReleaseError";
380
+ }
381
+ code;
382
+ };
383
+ async function prepareClaimRelease(args, verb) {
384
+ const current = readWorkflowState(args.projectDir);
385
+ if (!current) {
386
+ throw new ScmReleaseError(
387
+ `No SCM workflow state found at ${args.projectDir}/.lakebase/workflow-state.json.`,
388
+ "no-state-file"
389
+ );
390
+ }
391
+ if (current.state !== "feature-claimed") {
392
+ throw new ScmReleaseError(
393
+ `${verb} refuses state "${current.state}". Only feature-claimed is ${verb}able; later states must complete or be reverted via gh.`,
394
+ "bad-precondition"
395
+ );
396
+ }
397
+ if (!current.feature_id || !current.branch || !current.parent_branch || !current.lakebase_branch_uid) {
398
+ throw new ScmReleaseError(
399
+ `feature-claimed row is missing required invariants. Cannot ${verb} safely; consider re-adopting state first.`,
400
+ "missing-claim-fields"
401
+ );
402
+ }
403
+ if (!args.force) {
404
+ const dirty = await isDirty({ cwd: args.projectDir });
405
+ if (dirty) {
406
+ throw new ScmReleaseError(
407
+ `Working tree has uncommitted changes; refusing to ${verb} (switching HEAD off the feature branch would strand them). Commit / stash / discard first, or pass --force.`,
408
+ "dirty-working-tree"
409
+ );
410
+ }
411
+ }
412
+ const switchTo = args.switchTo ?? current.parent_branch;
413
+ const warnings = [];
414
+ const headBranch = await getCurrentBranch({ cwd: args.projectDir });
415
+ if (headBranch === current.branch) {
416
+ try {
417
+ await exec2(`git checkout ${JSON.stringify(switchTo)}`, {
418
+ cwd: args.projectDir,
419
+ timeout: 1e4
420
+ });
421
+ } catch (err) {
422
+ warnings.push(
423
+ `git checkout ${switchTo} failed: ${err instanceof Error ? err.message : String(err)}. Local branch state may be left on the feature branch.`
424
+ );
425
+ }
426
+ }
427
+ return { current, switchTo, warnings };
428
+ }
429
+ function resetToScaffoldComplete(projectDir, current) {
430
+ const reset = {
431
+ $schema: current.$schema,
432
+ version: 1,
433
+ state: "scaffold-complete",
434
+ tier_topology: current.tier_topology,
435
+ project_id: current.project_id
436
+ };
437
+ writeWorkflowState(projectDir, reset);
438
+ return reset;
439
+ }
440
+
441
+ // scripts/lakebase/scm-park-feature.ts
442
+ async function parkFeatureBranch(args) {
443
+ const { current, switchTo, warnings } = await prepareClaimRelease(
444
+ { projectDir: args.projectDir, force: args.force, switchTo: args.switchTo },
445
+ "park"
446
+ );
447
+ const reset = resetToScaffoldComplete(args.projectDir, current);
448
+ return {
449
+ state: reset,
450
+ keptBranch: current.branch,
451
+ parkedFeatureId: current.feature_id,
452
+ switchedTo: switchTo,
453
+ warnings
454
+ };
455
+ }
456
+
457
+ // scripts/lakebase/scm-park-feature.cli.ts
458
+ function parseArgs(argv) {
459
+ const out = {};
460
+ for (let i = 0; i < argv.length; i++) {
461
+ const a = argv[i];
462
+ switch (a) {
463
+ case "--project-dir":
464
+ case "--cwd":
465
+ out.projectDir = argv[++i];
466
+ break;
467
+ case "--switch-to":
468
+ out.switchTo = argv[++i];
469
+ break;
470
+ case "--force":
471
+ out.force = true;
472
+ break;
473
+ case "--json":
474
+ out.json = true;
475
+ break;
476
+ case "--pretty":
477
+ out.pretty = true;
478
+ break;
479
+ case "--help":
480
+ case "-h":
481
+ out.help = true;
482
+ break;
483
+ }
484
+ }
485
+ return out;
486
+ }
487
+ var HELP = `lakebase-scm-park-feature (phase B+)
488
+
489
+ Release a feature claim but KEEP its paired branch: switch HEAD to the parent
490
+ tier, reset workflow-state to scaffold-complete, leave the Lakebase + git
491
+ branch intact. Frees the single SCM claim so another feature can be claimed
492
+ and driven; resume the parked feature later with
493
+ 'lakebase-scm-claim-feature-branch <feature>' (it reuses the kept branch).
494
+
495
+ Usage:
496
+ lakebase-scm-park-feature [flags]
497
+
498
+ Flags:
499
+ --project-dir <dir> Project root (default: cwd)
500
+ --switch-to <branch> Branch to checkout after releasing (default: parent_branch)
501
+ --force Allow park even with a dirty working tree
502
+ (edits are kept but stranded on the feature branch)
503
+ --json Machine-readable JSON output
504
+ --pretty Pretty-print JSON (only with --json)
505
+ -h, --help Show this help
506
+
507
+ Exit codes:
508
+ 0 = parked (claim freed, branch kept, state reset to scaffold-complete)
509
+ 1 = no state file
510
+ 2 = precondition refused (wrong state, dirty tree without --force, missing fields)
511
+ 3 = substrate failure
512
+ `;
513
+ function renderHuman(report) {
514
+ if (!report.ok) {
515
+ return `lakebase-scm-park-feature: ${report.error?.code}
516
+
517
+ ${report.error?.message}`;
518
+ }
519
+ const r = report.result;
520
+ const lines = ["Feature parked (branch kept):"];
521
+ lines.push(` state : ${r.state.state}`);
522
+ lines.push(` parked_feature : ${r.parkedFeatureId}`);
523
+ lines.push(` kept_branch : ${r.keptBranch}`);
524
+ lines.push(` switched_to : ${r.switchedTo}`);
525
+ lines.push("");
526
+ lines.push(` resume with : lakebase-scm-claim-feature-branch ${r.parkedFeatureId}`);
527
+ if (r.warnings.length > 0) {
528
+ lines.push("");
529
+ lines.push("warnings:");
530
+ for (const w of r.warnings) lines.push(` - ${w}`);
531
+ }
532
+ return lines.join("\n");
533
+ }
534
+ function exitCodeForError(err) {
535
+ if (err instanceof ScmReleaseError) {
536
+ if (err.code === "no-state-file") return 1;
537
+ return 2;
538
+ }
539
+ return 3;
540
+ }
541
+ async function runScmParkFeatureCli(argv) {
542
+ const args = parseArgs(argv);
543
+ if (args.help) {
544
+ process.stdout.write(`${HELP}
545
+ `);
546
+ return 0;
547
+ }
548
+ const projectDir = path2.resolve(args.projectDir ?? process.cwd());
549
+ try {
550
+ const result = await parkFeatureBranch({
551
+ projectDir,
552
+ switchTo: args.switchTo,
553
+ force: args.force
554
+ });
555
+ const report = { ok: true, result };
556
+ if (args.json) {
557
+ const indent = args.pretty ? 2 : 0;
558
+ process.stdout.write(`${JSON.stringify(report, null, indent)}
559
+ `);
560
+ } else {
561
+ process.stdout.write(`${renderHuman(report)}
562
+ `);
563
+ }
564
+ return 0;
565
+ } catch (e) {
566
+ const err = e;
567
+ const code = err instanceof ScmReleaseError ? err.code : "substrate-failure";
568
+ const report = { ok: false, error: { code, message: err.message } };
569
+ if (args.json) {
570
+ const indent = args.pretty ? 2 : 0;
571
+ process.stdout.write(`${JSON.stringify(report, null, indent)}
572
+ `);
573
+ } else {
574
+ process.stderr.write(`${renderHuman(report)}
575
+ `);
576
+ }
577
+ return exitCodeForError(err);
578
+ }
579
+ }
580
+ if (isCliEntry(importMetaUrl)) {
581
+ void runScmParkFeatureCli(process.argv.slice(2)).then((c) => process.exit(c));
582
+ }
583
+ // Annotate the CommonJS export names for ESM import in node:
584
+ 0 && (module.exports = {
585
+ runScmParkFeatureCli
586
+ });
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ declare function runScmParkFeatureCli(argv: string[]): Promise<number>;
3
+
4
+ export { runScmParkFeatureCli };
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ declare function runScmParkFeatureCli(argv: string[]): Promise<number>;
3
+
4
+ export { runScmParkFeatureCli };