@treeseed/cli 0.1.1

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 (174) hide show
  1. package/README.md +115 -0
  2. package/dist/cli/handlers/close.js +60 -0
  3. package/dist/cli/handlers/config.js +76 -0
  4. package/dist/cli/handlers/continue.js +23 -0
  5. package/dist/cli/handlers/deploy.js +139 -0
  6. package/dist/cli/handlers/destroy.js +94 -0
  7. package/dist/cli/handlers/doctor.js +48 -0
  8. package/dist/cli/handlers/init.js +30 -0
  9. package/dist/cli/handlers/next.js +27 -0
  10. package/dist/cli/handlers/prepare.js +8 -0
  11. package/dist/cli/handlers/promote.js +8 -0
  12. package/dist/cli/handlers/publish.js +8 -0
  13. package/dist/cli/handlers/release.js +60 -0
  14. package/dist/cli/handlers/rollback.js +163 -0
  15. package/dist/cli/handlers/save.js +87 -0
  16. package/dist/cli/handlers/setup.js +48 -0
  17. package/dist/cli/handlers/ship.js +49 -0
  18. package/dist/cli/handlers/start.js +97 -0
  19. package/dist/cli/handlers/status.js +31 -0
  20. package/dist/cli/handlers/teardown.js +50 -0
  21. package/dist/cli/handlers/utils.js +59 -0
  22. package/dist/cli/handlers/work.js +85 -0
  23. package/dist/cli/help.js +143 -0
  24. package/dist/cli/main.js +27 -0
  25. package/dist/cli/parser.js +96 -0
  26. package/dist/cli/registry.js +445 -0
  27. package/dist/cli/repair.js +89 -0
  28. package/dist/cli/runtime.js +165 -0
  29. package/dist/cli/types.js +0 -0
  30. package/dist/cli/workflow-state.js +171 -0
  31. package/dist/index.js +1 -0
  32. package/dist/scripts/aggregate-book.d.ts +1 -0
  33. package/dist/scripts/aggregate-book.js +121 -0
  34. package/dist/scripts/assert-release-tag-version.d.ts +1 -0
  35. package/dist/scripts/assert-release-tag-version.js +21 -0
  36. package/dist/scripts/build-dist.d.ts +1 -0
  37. package/dist/scripts/build-dist.js +108 -0
  38. package/dist/scripts/build-tenant-worker.d.ts +1 -0
  39. package/dist/scripts/build-tenant-worker.js +36 -0
  40. package/dist/scripts/cleanup-markdown.d.ts +2 -0
  41. package/dist/scripts/cleanup-markdown.js +373 -0
  42. package/dist/scripts/config-runtime-lib.d.ts +122 -0
  43. package/dist/scripts/config-runtime-lib.js +505 -0
  44. package/dist/scripts/config-treeseed.d.ts +2 -0
  45. package/dist/scripts/config-treeseed.js +81 -0
  46. package/dist/scripts/d1-migration-lib.d.ts +6 -0
  47. package/dist/scripts/d1-migration-lib.js +90 -0
  48. package/dist/scripts/deploy-lib.d.ts +127 -0
  49. package/dist/scripts/deploy-lib.js +841 -0
  50. package/dist/scripts/ensure-mailpit.d.ts +1 -0
  51. package/dist/scripts/ensure-mailpit.js +29 -0
  52. package/dist/scripts/git-workflow-lib.d.ts +25 -0
  53. package/dist/scripts/git-workflow-lib.js +136 -0
  54. package/dist/scripts/github-automation-lib.d.ts +156 -0
  55. package/dist/scripts/github-automation-lib.js +242 -0
  56. package/dist/scripts/local-dev-lib.d.ts +9 -0
  57. package/dist/scripts/local-dev-lib.js +84 -0
  58. package/dist/scripts/local-dev.d.ts +1 -0
  59. package/dist/scripts/local-dev.js +129 -0
  60. package/dist/scripts/logs-mailpit.d.ts +1 -0
  61. package/dist/scripts/logs-mailpit.js +2 -0
  62. package/dist/scripts/mailpit-runtime.d.ts +4 -0
  63. package/dist/scripts/mailpit-runtime.js +57 -0
  64. package/dist/scripts/package-tools.d.ts +22 -0
  65. package/dist/scripts/package-tools.js +255 -0
  66. package/dist/scripts/patch-starlight-content-path.d.ts +1 -0
  67. package/dist/scripts/patch-starlight-content-path.js +172 -0
  68. package/dist/scripts/paths.d.ts +17 -0
  69. package/dist/scripts/paths.js +26 -0
  70. package/dist/scripts/publish-package.d.ts +1 -0
  71. package/dist/scripts/publish-package.js +19 -0
  72. package/dist/scripts/release-verify.d.ts +1 -0
  73. package/dist/scripts/release-verify.js +136 -0
  74. package/dist/scripts/run-fixture-astro-command.d.ts +1 -0
  75. package/dist/scripts/run-fixture-astro-command.js +18 -0
  76. package/dist/scripts/save-deploy-preflight-lib.d.ts +34 -0
  77. package/dist/scripts/save-deploy-preflight-lib.js +69 -0
  78. package/dist/scripts/scaffold-site.d.ts +2 -0
  79. package/dist/scripts/scaffold-site.js +92 -0
  80. package/dist/scripts/stop-mailpit.d.ts +1 -0
  81. package/dist/scripts/stop-mailpit.js +5 -0
  82. package/dist/scripts/sync-dev-vars.d.ts +1 -0
  83. package/dist/scripts/sync-dev-vars.js +6 -0
  84. package/dist/scripts/template-registry-lib.d.ts +47 -0
  85. package/dist/scripts/template-registry-lib.js +137 -0
  86. package/dist/scripts/tenant-astro-command.d.ts +1 -0
  87. package/dist/scripts/tenant-astro-command.js +3 -0
  88. package/dist/scripts/tenant-build.d.ts +1 -0
  89. package/dist/scripts/tenant-build.js +16 -0
  90. package/dist/scripts/tenant-check.d.ts +1 -0
  91. package/dist/scripts/tenant-check.js +7 -0
  92. package/dist/scripts/tenant-d1-migrate-local.d.ts +1 -0
  93. package/dist/scripts/tenant-d1-migrate-local.js +11 -0
  94. package/dist/scripts/tenant-deploy.d.ts +2 -0
  95. package/dist/scripts/tenant-deploy.js +180 -0
  96. package/dist/scripts/tenant-destroy.d.ts +2 -0
  97. package/dist/scripts/tenant-destroy.js +104 -0
  98. package/dist/scripts/tenant-dev.d.ts +1 -0
  99. package/dist/scripts/tenant-dev.js +171 -0
  100. package/dist/scripts/tenant-lint.d.ts +1 -0
  101. package/dist/scripts/tenant-lint.js +4 -0
  102. package/dist/scripts/tenant-test.d.ts +1 -0
  103. package/dist/scripts/tenant-test.js +4 -0
  104. package/dist/scripts/test-cloudflare-local.d.ts +1 -0
  105. package/dist/scripts/test-cloudflare-local.js +212 -0
  106. package/dist/scripts/test-scaffold.d.ts +2 -0
  107. package/dist/scripts/test-scaffold.js +297 -0
  108. package/dist/scripts/treeseed.d.ts +2 -0
  109. package/dist/scripts/treeseed.js +4 -0
  110. package/dist/scripts/validate-templates.d.ts +2 -0
  111. package/dist/scripts/validate-templates.js +4 -0
  112. package/dist/scripts/watch-dev-lib.d.ts +21 -0
  113. package/dist/scripts/watch-dev-lib.js +277 -0
  114. package/dist/scripts/workspace-close.d.ts +2 -0
  115. package/dist/scripts/workspace-close.js +24 -0
  116. package/dist/scripts/workspace-command-e2e.d.ts +2 -0
  117. package/dist/scripts/workspace-command-e2e.js +718 -0
  118. package/dist/scripts/workspace-lint.d.ts +1 -0
  119. package/dist/scripts/workspace-lint.js +9 -0
  120. package/dist/scripts/workspace-preflight-lib.d.ts +36 -0
  121. package/dist/scripts/workspace-preflight-lib.js +179 -0
  122. package/dist/scripts/workspace-preflight.d.ts +2 -0
  123. package/dist/scripts/workspace-preflight.js +22 -0
  124. package/dist/scripts/workspace-publish-changed-packages.d.ts +1 -0
  125. package/dist/scripts/workspace-publish-changed-packages.js +16 -0
  126. package/dist/scripts/workspace-release-verify.d.ts +1 -0
  127. package/dist/scripts/workspace-release-verify.js +81 -0
  128. package/dist/scripts/workspace-release.d.ts +2 -0
  129. package/dist/scripts/workspace-release.js +42 -0
  130. package/dist/scripts/workspace-save-lib.d.ts +42 -0
  131. package/dist/scripts/workspace-save-lib.js +220 -0
  132. package/dist/scripts/workspace-save.d.ts +2 -0
  133. package/dist/scripts/workspace-save.js +124 -0
  134. package/dist/scripts/workspace-start-warning.d.ts +0 -0
  135. package/dist/scripts/workspace-start-warning.js +3 -0
  136. package/dist/scripts/workspace-start.d.ts +2 -0
  137. package/dist/scripts/workspace-start.js +71 -0
  138. package/dist/scripts/workspace-test-unit.d.ts +1 -0
  139. package/dist/scripts/workspace-test-unit.js +4 -0
  140. package/dist/scripts/workspace-test.d.ts +1 -0
  141. package/dist/scripts/workspace-test.js +11 -0
  142. package/dist/scripts/workspace-tools.d.ts +13 -0
  143. package/dist/scripts/workspace-tools.js +226 -0
  144. package/dist/src/cli/handlers/close.d.ts +2 -0
  145. package/dist/src/cli/handlers/config.d.ts +2 -0
  146. package/dist/src/cli/handlers/continue.d.ts +2 -0
  147. package/dist/src/cli/handlers/deploy.d.ts +2 -0
  148. package/dist/src/cli/handlers/destroy.d.ts +2 -0
  149. package/dist/src/cli/handlers/doctor.d.ts +2 -0
  150. package/dist/src/cli/handlers/init.d.ts +2 -0
  151. package/dist/src/cli/handlers/next.d.ts +2 -0
  152. package/dist/src/cli/handlers/prepare.d.ts +2 -0
  153. package/dist/src/cli/handlers/promote.d.ts +2 -0
  154. package/dist/src/cli/handlers/publish.d.ts +2 -0
  155. package/dist/src/cli/handlers/release.d.ts +2 -0
  156. package/dist/src/cli/handlers/rollback.d.ts +2 -0
  157. package/dist/src/cli/handlers/save.d.ts +2 -0
  158. package/dist/src/cli/handlers/setup.d.ts +2 -0
  159. package/dist/src/cli/handlers/ship.d.ts +2 -0
  160. package/dist/src/cli/handlers/start.d.ts +3 -0
  161. package/dist/src/cli/handlers/status.d.ts +2 -0
  162. package/dist/src/cli/handlers/teardown.d.ts +2 -0
  163. package/dist/src/cli/handlers/utils.d.ts +18 -0
  164. package/dist/src/cli/handlers/work.d.ts +2 -0
  165. package/dist/src/cli/help.d.ts +4 -0
  166. package/dist/src/cli/main.d.ts +6 -0
  167. package/dist/src/cli/parser.d.ts +3 -0
  168. package/dist/src/cli/registry.d.ts +27 -0
  169. package/dist/src/cli/repair.d.ts +6 -0
  170. package/dist/src/cli/runtime.d.ts +4 -0
  171. package/dist/src/cli/types.d.ts +71 -0
  172. package/dist/src/cli/workflow-state.d.ts +49 -0
  173. package/dist/src/index.d.ts +1 -0
  174. package/package.json +57 -0
@@ -0,0 +1,85 @@
1
+ import {
2
+ assertCleanWorktree,
3
+ branchExists,
4
+ checkoutBranch,
5
+ currentManagedBranch,
6
+ ensureLocalBranchTracking,
7
+ gitWorkflowRoot,
8
+ remoteBranchExists,
9
+ syncBranchWithOrigin
10
+ } from "../../scripts/git-workflow-lib.js";
11
+ import { guidedResult } from "./utils.js";
12
+ import { provisionBranchPreview } from "./start.js";
13
+ import { handleStart } from "./start.js";
14
+ import { resolveTreeseedWorkflowState } from "../workflow-state.js";
15
+ const handleWork = async (invocation, context) => {
16
+ const commandName = invocation.commandName || "work";
17
+ const branchName = invocation.positionals[0];
18
+ const preview = invocation.args.preview === true;
19
+ const repoDir = gitWorkflowRoot(context.cwd);
20
+ const currentBranch = currentManagedBranch(context.cwd);
21
+ if (currentBranch === branchName) {
22
+ const state2 = resolveTreeseedWorkflowState(context.cwd);
23
+ if (preview && !state2.preview.enabled) {
24
+ return provisionBranchPreview(branchName, context, commandName);
25
+ }
26
+ return guidedResult({
27
+ command: commandName,
28
+ summary: `Already working on ${branchName}.`,
29
+ facts: [
30
+ { label: "Branch", value: branchName },
31
+ { label: "Preview", value: state2.preview.enabled ? "enabled" : "disabled" },
32
+ { label: "Preview URL", value: state2.preview.url ?? "(none)" }
33
+ ],
34
+ nextSteps: [
35
+ preview && !state2.preview.enabled ? `treeseed work ${branchName} --preview` : "treeseed dev",
36
+ 'treeseed ship "describe your change"'
37
+ ],
38
+ report: {
39
+ branchName,
40
+ resumed: true,
41
+ preview: state2.preview.enabled,
42
+ previewUrl: state2.preview.url
43
+ }
44
+ });
45
+ }
46
+ if (!branchExists(repoDir, branchName) && !remoteBranchExists(repoDir, branchName)) {
47
+ return handleStart({
48
+ ...invocation,
49
+ commandName
50
+ }, context);
51
+ }
52
+ assertCleanWorktree(context.cwd);
53
+ ensureLocalBranchTracking(repoDir, branchName);
54
+ checkoutBranch(repoDir, branchName);
55
+ syncBranchWithOrigin(repoDir, branchName);
56
+ if (preview) {
57
+ const state2 = resolveTreeseedWorkflowState(context.cwd);
58
+ if (!state2.preview.enabled) {
59
+ return provisionBranchPreview(branchName, context, commandName);
60
+ }
61
+ }
62
+ const state = resolveTreeseedWorkflowState(context.cwd);
63
+ return guidedResult({
64
+ command: commandName,
65
+ summary: `Resumed feature branch ${branchName}.`,
66
+ facts: [
67
+ { label: "Branch", value: branchName },
68
+ { label: "Preview", value: state.preview.enabled ? "enabled" : "disabled" },
69
+ { label: "Preview URL", value: state.preview.url ?? "(none)" }
70
+ ],
71
+ nextSteps: [
72
+ state.preview.enabled ? `treeseed publish --target-branch ${branchName}` : "treeseed dev",
73
+ 'treeseed ship "describe your change"'
74
+ ],
75
+ report: {
76
+ branchName,
77
+ resumed: true,
78
+ preview: state.preview.enabled,
79
+ previewUrl: state.preview.url
80
+ }
81
+ });
82
+ };
83
+ export {
84
+ handleWork
85
+ };
@@ -0,0 +1,143 @@
1
+ import { findCommandSpec, listCommandNames, TRESEED_COMMAND_SPECS } from "./registry.js";
2
+ const GROUP_ORDER = [
3
+ "Workflow",
4
+ "Local Development",
5
+ "Validation",
6
+ "Release Utilities",
7
+ "Utilities",
8
+ "Passthrough"
9
+ ];
10
+ function levenshtein(left, right) {
11
+ const rows = Array.from({ length: left.length + 1 }, () => new Array(right.length + 1).fill(0));
12
+ for (let i = 0; i <= left.length; i += 1) rows[i][0] = i;
13
+ for (let j = 0; j <= right.length; j += 1) rows[0][j] = j;
14
+ for (let i = 1; i <= left.length; i += 1) {
15
+ for (let j = 1; j <= right.length; j += 1) {
16
+ const cost = left[i - 1] === right[j - 1] ? 0 : 1;
17
+ rows[i][j] = Math.min(
18
+ rows[i - 1][j] + 1,
19
+ rows[i][j - 1] + 1,
20
+ rows[i - 1][j - 1] + cost
21
+ );
22
+ }
23
+ }
24
+ return rows[left.length][right.length];
25
+ }
26
+ function formatSection(title, lines) {
27
+ if (lines.length === 0) return "";
28
+ return `${title}
29
+ ${lines.join("\n")}`;
30
+ }
31
+ function groupedCommands() {
32
+ const groups = /* @__PURE__ */ new Map();
33
+ for (const group of GROUP_ORDER) groups.set(group, []);
34
+ for (const spec of TRESEED_COMMAND_SPECS) {
35
+ const entries = groups.get(spec.group) ?? [];
36
+ entries.push(spec);
37
+ groups.set(spec.group, entries);
38
+ }
39
+ return groups;
40
+ }
41
+ function renderUsage(spec) {
42
+ if (spec.usage) return spec.usage;
43
+ const args = (spec.arguments ?? []).map((arg) => arg.required ? `<${arg.name}>` : `[${arg.name}]`);
44
+ const options = (spec.options ?? []).map((option) => option.repeatable ? `[${option.flags}]...` : `[${option.flags}]`);
45
+ return ["treeseed", spec.name, ...args, ...options].join(" ").replace(/\s+/gu, " ").trim();
46
+ }
47
+ function suggestTreeseedCommands(input) {
48
+ const normalized = input.trim().toLowerCase();
49
+ if (!normalized) return [];
50
+ return listCommandNames().map((name) => ({ name, score: levenshtein(normalized, name) })).sort((left, right) => left.score - right.score || left.name.localeCompare(right.name)).slice(0, 3).map((entry) => entry.name);
51
+ }
52
+ function renderTreeseedHelp(commandName) {
53
+ if (!commandName) {
54
+ const groups = groupedCommands();
55
+ const primaryWorkflow = ["setup", "work", "dev", "ship", "publish", "promote", "rollback", "teardown"].map((name) => findCommandSpec(name)).filter((spec2) => Boolean(spec2));
56
+ const workflowGuidance = ["status", "next", "continue", "doctor"].map((name) => findCommandSpec(name)).filter((spec2) => Boolean(spec2));
57
+ const sections2 = [
58
+ "Treeseed CLI",
59
+ "Unified TypeScript command interface for Treeseed project setup, branch workflow, deployment, and release automation.",
60
+ "",
61
+ "Usage",
62
+ " treeseed <command> [args...]",
63
+ " treeseed help [command]",
64
+ "",
65
+ formatSection("Primary Workflow", primaryWorkflow.map((command) => {
66
+ const spacer = command.name.length < 18 ? " ".repeat(18 - command.name.length) : " ";
67
+ return ` ${command.name}${spacer}${command.summary}`;
68
+ })),
69
+ "",
70
+ formatSection("Workflow Guidance", workflowGuidance.map((command) => {
71
+ const spacer = command.name.length < 18 ? " ".repeat(18 - command.name.length) : " ";
72
+ return ` ${command.name}${spacer}${command.summary}`;
73
+ })),
74
+ "",
75
+ ...GROUP_ORDER.filter((group) => group !== "Workflow").map((group) => formatSection(group, (groups.get(group) ?? []).map((command) => {
76
+ const spacer = command.name.length < 18 ? " ".repeat(18 - command.name.length) : " ";
77
+ return ` ${command.name}${spacer}${command.summary}`;
78
+ }))).filter(Boolean),
79
+ "",
80
+ formatSection("Common Flows", [
81
+ " treeseed setup",
82
+ " treeseed work feature/my-change --preview",
83
+ ' treeseed ship "feat: describe your change"',
84
+ " treeseed publish --environment staging",
85
+ " treeseed promote --patch",
86
+ " treeseed teardown"
87
+ ]),
88
+ "",
89
+ formatSection("Help", [
90
+ " treeseed --help",
91
+ " treeseed help publish",
92
+ " treeseed publish --help"
93
+ ]),
94
+ "",
95
+ "Notes",
96
+ " - Workspace-only commands must be run from a Treeseed workspace root.",
97
+ " - Use `treeseed setup`, `treeseed work`, `treeseed ship`, `treeseed publish`, `treeseed promote`, and `treeseed teardown` for the simplified workflow.",
98
+ " - `config`, `deploy`, `start`, `save`, `release`, `close`, and `destroy` remain available as compatibility commands.",
99
+ " - Use `--json` on guidance and main workflow commands when an AI agent or script needs machine-readable output."
100
+ ];
101
+ return sections2.filter(Boolean).join("\n");
102
+ }
103
+ const spec = findCommandSpec(commandName);
104
+ if (!spec) {
105
+ const suggestions = suggestTreeseedCommands(commandName);
106
+ const lines = [`Unknown treeseed command: ${commandName}`];
107
+ if (suggestions.length > 0) {
108
+ lines.push(`Did you mean: ${suggestions.map((item) => `\`${item}\``).join(", ")}?`);
109
+ }
110
+ lines.push("Run `treeseed help` to see the full command list.");
111
+ return lines.join("\n");
112
+ }
113
+ const formatOptions = (spec.options ?? []).map((option) => {
114
+ const spacer = option.flags.length < 28 ? " ".repeat(28 - option.flags.length) : " ";
115
+ return ` ${option.flags}${spacer}${option.description}`;
116
+ });
117
+ const formatArguments = (spec.arguments ?? []).map((arg) => {
118
+ const rendered = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
119
+ return ` ${rendered} ${arg.description}`;
120
+ });
121
+ const sections = [
122
+ `${spec.name} ${spec.summary}`,
123
+ spec.description,
124
+ "",
125
+ formatSection("Usage", [` ${renderUsage(spec)}`]),
126
+ formatArguments.length > 0 ? `
127
+ ${formatSection("Arguments", formatArguments)}` : "",
128
+ formatOptions.length > 0 ? `
129
+ ${formatSection("Options", formatOptions)}` : "",
130
+ (spec.examples ?? []).length > 0 ? `
131
+ ${formatSection("Examples", spec.examples.map((example) => ` ${example}`))}` : "",
132
+ (spec.notes ?? []).length > 0 ? `
133
+ ${formatSection("Notes", spec.notes.map((note) => ` - ${note}`))}` : "",
134
+ (spec.related ?? []).length > 0 ? `
135
+ Related: ${spec.related.map((item) => `\`${item}\``).join(", ")}` : ""
136
+ ];
137
+ return sections.filter(Boolean).join("\n");
138
+ }
139
+ export {
140
+ renderTreeseedHelp,
141
+ renderUsage,
142
+ suggestTreeseedCommands
143
+ };
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { resolve } from "node:path";
4
+ import { runTreeseedCli } from "./runtime.js";
5
+ import { runTreeseedCli as runTreeseedCli2, executeTreeseedCommand, createTreeseedCommandContext } from "./runtime.js";
6
+ import { renderTreeseedHelp, renderUsage, suggestTreeseedCommands } from "./help.js";
7
+ import { findCommandSpec, listCommandNames, TRESEED_COMMAND_SPECS } from "./registry.js";
8
+ import { parseTreeseedInvocation, validateTreeseedInvocation } from "./parser.js";
9
+ const currentFile = fileURLToPath(import.meta.url);
10
+ const entryFile = resolve(process.argv[1] ?? "");
11
+ if (entryFile === currentFile) {
12
+ const exitCode = await runTreeseedCli(process.argv.slice(2));
13
+ process.exit(exitCode);
14
+ }
15
+ export {
16
+ TRESEED_COMMAND_SPECS,
17
+ createTreeseedCommandContext,
18
+ executeTreeseedCommand,
19
+ findCommandSpec,
20
+ listCommandNames,
21
+ parseTreeseedInvocation,
22
+ renderTreeseedHelp,
23
+ renderUsage,
24
+ runTreeseedCli2 as runTreeseedCli,
25
+ suggestTreeseedCommands,
26
+ validateTreeseedInvocation
27
+ };
@@ -0,0 +1,96 @@
1
+ function optionKey(spec) {
2
+ return spec.name;
3
+ }
4
+ function parseTreeseedInvocation(command, argv) {
5
+ const args = {};
6
+ const positionals = [];
7
+ const options = new Map((command.options ?? []).map((spec) => [spec.name, spec]));
8
+ const byFlag = /* @__PURE__ */ new Map();
9
+ for (const spec of options.values()) {
10
+ const firstToken = spec.flags.split(/[ ,|]+/).find((token) => token.startsWith("--") || token.startsWith("-"));
11
+ if (firstToken) byFlag.set(firstToken, spec);
12
+ }
13
+ const rest = [...argv];
14
+ while (rest.length > 0) {
15
+ const current = rest.shift();
16
+ if (current === "--") {
17
+ positionals.push(...rest);
18
+ break;
19
+ }
20
+ if (current.startsWith("-")) {
21
+ const [flag, inlineValue] = current.split("=", 2);
22
+ const spec = byFlag.get(flag);
23
+ if (!spec) {
24
+ throw new Error(`Unknown option: ${flag}`);
25
+ }
26
+ if (spec.kind === "boolean") {
27
+ args[optionKey(spec)] = true;
28
+ continue;
29
+ }
30
+ const value = inlineValue ?? rest.shift();
31
+ if (!value) {
32
+ throw new Error(`Missing value for ${flag}`);
33
+ }
34
+ if (spec.kind === "enum" && spec.values && !spec.values.includes(value)) {
35
+ throw new Error(`Invalid value for ${flag}: ${value}. Expected one of ${spec.values.join(", ")}.`);
36
+ }
37
+ if (spec.repeatable) {
38
+ const currentValues = Array.isArray(args[optionKey(spec)]) ? args[optionKey(spec)] : [];
39
+ args[optionKey(spec)] = [...currentValues, value];
40
+ } else {
41
+ args[optionKey(spec)] = value;
42
+ }
43
+ continue;
44
+ }
45
+ positionals.push(current);
46
+ }
47
+ const messageTailArg = (command.arguments ?? []).find((arg) => arg.kind === "message_tail");
48
+ if (messageTailArg && positionals.length > 1) {
49
+ const [first, ...restPositional] = positionals;
50
+ return {
51
+ commandName: command.name,
52
+ args,
53
+ positionals: first ? [first, ...restPositional] : restPositional,
54
+ rawArgs: argv
55
+ };
56
+ }
57
+ return {
58
+ commandName: command.name,
59
+ args,
60
+ positionals,
61
+ rawArgs: argv
62
+ };
63
+ }
64
+ function validateTreeseedInvocation(command, invocation) {
65
+ const errors = [];
66
+ const args = command.arguments ?? [];
67
+ const positionals = [...invocation.positionals];
68
+ for (const arg of args) {
69
+ if (arg.kind === "message_tail") {
70
+ if (positionals.join(" ").trim().length === 0 && arg.required) {
71
+ errors.push(`Missing required argument: ${arg.name}`);
72
+ }
73
+ continue;
74
+ }
75
+ const next = positionals.shift();
76
+ if (!next && arg.required) {
77
+ errors.push(`Missing required argument: ${arg.name}`);
78
+ }
79
+ }
80
+ if (command.name === "release" || command.name === "promote") {
81
+ const selected = ["major", "minor", "patch"].filter((name) => invocation.args[name] === true);
82
+ if (selected.length !== 1) {
83
+ errors.push(`Treeseed ${command.name} requires exactly one version bump flag: --major, --minor, or --patch.`);
84
+ }
85
+ }
86
+ if (command.name === "deploy" || command.name === "publish") {
87
+ if (!invocation.args.environment && !invocation.args.targetBranch && !process.env.CI) {
88
+ errors.push(`Treeseed ${command.name} requires \`--environment local|staging|prod\` or \`--target-branch <branch>\` outside CI.`);
89
+ }
90
+ }
91
+ return errors;
92
+ }
93
+ export {
94
+ parseTreeseedInvocation,
95
+ validateTreeseedInvocation
96
+ };