@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
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # `@treeseed/cli`
2
+
3
+ Operator-facing Treeseed CLI package.
4
+
5
+ This package publishes the `treeseed` binary. It provides the unified TypeScript command interface for Treeseed workspace setup, branch workflow, deployment, validation, and release automation.
6
+
7
+ ## Requirements
8
+
9
+ - Node `>=20`
10
+ - npm as the canonical package manager for install, CI, and release flows
11
+
12
+ ## Install
13
+
14
+ Install the CLI alongside the Treeseed runtime package:
15
+
16
+ ```bash
17
+ npm install @treeseed/cli @treeseed/core
18
+ ```
19
+
20
+ `@treeseed/cli` depends on `@treeseed/core`, `@treeseed/sdk`, and `@treeseed/agent` at package runtime. In normal consumer installs, npm resolves those package dependencies automatically.
21
+
22
+ After installation, the published binary is available as:
23
+
24
+ ```bash
25
+ treeseed --help
26
+ ```
27
+
28
+ ## Primary Workflow
29
+
30
+ The main workflow commands exposed by the current CLI are:
31
+
32
+ - `treeseed setup`
33
+ - `treeseed work <branch-name> [--preview]`
34
+ - `treeseed ship "<commit message>"`
35
+ - `treeseed publish --environment <local|staging|prod>`
36
+ - `treeseed promote --major|--minor|--patch`
37
+ - `treeseed rollback <staging|prod> [--to <deploy-id|commit>]`
38
+ - `treeseed teardown [--environment <local|staging|prod>]`
39
+ - `treeseed status [--json]`
40
+ - `treeseed next [--json]`
41
+ - `treeseed continue [--json]`
42
+ - `treeseed doctor [--fix] [--json]`
43
+
44
+ The CLI also keeps compatibility commands such as `init`, `config`, `start`, `deploy`, `save`, `release`, `close`, and `destroy`.
45
+
46
+ Use `treeseed help` for the full command list and `treeseed help <command>` for command-specific usage, options, and examples.
47
+
48
+ ## Common Commands
49
+
50
+ ```bash
51
+ treeseed setup
52
+ treeseed work feature/search-improvements --preview
53
+ treeseed ship "feat: add search filters"
54
+ treeseed publish --environment staging
55
+ treeseed promote --patch
56
+ treeseed status --json
57
+ ```
58
+
59
+ ## Maintainer Workflow
60
+
61
+ All package maintenance commands are npm-based and run from the `cli/` package root.
62
+
63
+ Install dependencies:
64
+
65
+ ```bash
66
+ npm install
67
+ ```
68
+
69
+ Build the published package output:
70
+
71
+ ```bash
72
+ npm run build
73
+ ```
74
+
75
+ Run the package test suite:
76
+
77
+ ```bash
78
+ npm test
79
+ ```
80
+
81
+ Run full release verification:
82
+
83
+ ```bash
84
+ npm run release:verify
85
+ ```
86
+
87
+ The release verification flow is intentionally stricter than a normal test run:
88
+
89
+ 1. Build `dist`
90
+ 2. Validate publishable output for forbidden workspace references
91
+ 3. Run the CLI test suite
92
+ 4. Run scaffold verification
93
+ 5. Pack the CLI tarball
94
+ 6. Smoke-test the packed install by running `treeseed --help` from the packed artifact
95
+
96
+ ## CI And Publishing
97
+
98
+ The GitHub Actions workflows under `.github/workflows/` assume this package is the repository root for the standalone CLI repository.
99
+
100
+ - `ci.yml` uses `npm ci`, `npm run build`, `npm test`, and `npm run release:verify`
101
+ - `publish.yml` uses the same verification path before publishing to npm
102
+ - `publish.yml` validates that the pushed tag matches the package version before `npm publish`
103
+
104
+ Release tags must use this format:
105
+
106
+ ```text
107
+ <version>
108
+ ```
109
+
110
+ For example, package version `0.1.0` publishes from tag `0.1.0`.
111
+
112
+ ## Notes
113
+
114
+ - `package-lock.json` should be committed and kept current so `npm ci` remains reproducible in CI and release jobs.
115
+ - The README intentionally documents the command surface at a high level. The canonical source of command usage and options is the CLI help output generated from `src/cli/registry.ts`.
@@ -0,0 +1,60 @@
1
+ import { applyTreeseedEnvironmentToProcess } from "../../scripts/config-runtime-lib.js";
2
+ import {
3
+ cleanupDestroyedState,
4
+ createBranchPreviewDeployTarget,
5
+ destroyCloudflareResources,
6
+ loadDeployState,
7
+ printDestroySummary,
8
+ validateDestroyPrerequisites
9
+ } from "../../scripts/deploy-lib.js";
10
+ import {
11
+ assertFeatureBranch,
12
+ deleteLocalBranch,
13
+ deleteRemoteBranch,
14
+ mergeCurrentBranchIntoStaging
15
+ } from "../../scripts/git-workflow-lib.js";
16
+ import { loadCliDeployConfig } from "../../scripts/package-tools.js";
17
+ import { runWorkspaceSavePreflight } from "../../scripts/save-deploy-preflight-lib.js";
18
+ import { guidedResult } from "./utils.js";
19
+ const handleClose = (_invocation, context) => {
20
+ const commandName = _invocation.commandName || "close";
21
+ const tenantRoot = context.cwd;
22
+ const featureBranch = assertFeatureBranch(tenantRoot);
23
+ const previewTarget = createBranchPreviewDeployTarget(featureBranch);
24
+ const deployConfig = loadCliDeployConfig(tenantRoot);
25
+ const previewState = loadDeployState(tenantRoot, deployConfig, { target: previewTarget });
26
+ runWorkspaceSavePreflight({ cwd: tenantRoot });
27
+ const repoDir = mergeCurrentBranchIntoStaging(tenantRoot, featureBranch);
28
+ if (previewState.readiness?.initialized) {
29
+ applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "staging" });
30
+ validateDestroyPrerequisites(tenantRoot, { requireRemote: true });
31
+ const result = destroyCloudflareResources(tenantRoot, { target: previewTarget });
32
+ printDestroySummary(result);
33
+ }
34
+ cleanupDestroyedState(tenantRoot, { target: previewTarget });
35
+ deleteRemoteBranch(repoDir, featureBranch);
36
+ deleteLocalBranch(repoDir, featureBranch);
37
+ return {
38
+ ...guidedResult({
39
+ command: commandName,
40
+ summary: `Treeseed ${commandName} completed successfully.`,
41
+ facts: [
42
+ { label: "Merged branch", value: featureBranch },
43
+ { label: "Merge target", value: "staging" },
44
+ { label: "Preview cleanup", value: previewState.readiness?.initialized ? "performed" : "not needed" }
45
+ ],
46
+ nextSteps: [
47
+ "treeseed deploy --environment staging",
48
+ "treeseed release --patch"
49
+ ],
50
+ report: {
51
+ branchName: featureBranch,
52
+ mergeTarget: "staging",
53
+ previewCleanup: previewState.readiness?.initialized === true
54
+ }
55
+ })
56
+ };
57
+ };
58
+ export {
59
+ handleClose
60
+ };
@@ -0,0 +1,76 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { collectCliPreflight } from "../../scripts/workspace-preflight-lib.js";
4
+ import {
5
+ applyTreeseedEnvironmentToProcess,
6
+ ensureTreeseedGitignoreEntries,
7
+ getTreeseedMachineConfigPaths,
8
+ runTreeseedConfigWizard,
9
+ writeTreeseedLocalEnvironmentFiles
10
+ } from "../../scripts/config-runtime-lib.js";
11
+ import { guidedResult } from "./utils.js";
12
+ const handleConfig = async (invocation, context) => {
13
+ const commandName = invocation.commandName || "config";
14
+ const tenantRoot = context.cwd;
15
+ const scopes = Array.isArray(invocation.args.environment) ? invocation.args.environment : invocation.args.environment ? [invocation.args.environment] : ["local", "staging", "prod"];
16
+ const sync = typeof invocation.args.sync === "string" ? invocation.args.sync : "none";
17
+ ensureTreeseedGitignoreEntries(tenantRoot);
18
+ const preflight = collectCliPreflight({ cwd: tenantRoot, requireAuth: false });
19
+ const rl = readline.createInterface({ input, output });
20
+ try {
21
+ if (context.outputFormat !== "json") {
22
+ context.write("Treeseed configuration wizard", "stdout");
23
+ context.write("This command writes a local machine config, generates .env.local and .dev.vars, and can sync GitHub or Cloudflare settings.", "stdout");
24
+ context.write('Enter a value to set it, press Enter to keep the current/default value, or enter "-" to clear a value.\n', "stdout");
25
+ }
26
+ const result = await runTreeseedConfigWizard({
27
+ tenantRoot,
28
+ scopes,
29
+ sync,
30
+ authStatus: preflight.checks.auth,
31
+ write: context.outputFormat === "json" ? () => {
32
+ } : ((line) => context.write(line, "stdout")),
33
+ prompt: async (message) => {
34
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
35
+ return "";
36
+ }
37
+ try {
38
+ return await rl.question(message);
39
+ } catch {
40
+ return "";
41
+ }
42
+ }
43
+ });
44
+ writeTreeseedLocalEnvironmentFiles(tenantRoot);
45
+ applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "local" });
46
+ const { configPath, keyPath } = getTreeseedMachineConfigPaths(tenantRoot);
47
+ return guidedResult({
48
+ command: commandName,
49
+ summary: `Treeseed ${commandName} completed successfully.`,
50
+ facts: [
51
+ { label: "Machine config", value: configPath },
52
+ { label: "Machine key", value: keyPath },
53
+ { label: "Updated values", value: result.updated.length },
54
+ { label: "Initialized environments", value: result.initialized.map((entry) => entry.scope).join(", ") || "(none)" },
55
+ { label: "GitHub sync", value: result.synced.github ? `${result.synced.github.secrets.length} secrets, ${result.synced.github.variables.length} variables` : "not run" },
56
+ { label: "Cloudflare sync", value: result.synced.cloudflare ? `${result.synced.cloudflare.secrets.length} secrets, ${result.synced.cloudflare.varsManagedByWranglerConfig.length} vars` : "not run" }
57
+ ],
58
+ nextSteps: [
59
+ ...scopes.includes("local") ? ["treeseed dev"] : [],
60
+ ...scopes.includes("staging") ? ["treeseed deploy --environment staging"] : [],
61
+ ...scopes.includes("prod") ? ["treeseed deploy --environment prod"] : []
62
+ ],
63
+ report: {
64
+ scopes,
65
+ sync,
66
+ result,
67
+ preflight
68
+ }
69
+ });
70
+ } finally {
71
+ rl.close();
72
+ }
73
+ };
74
+ export {
75
+ handleConfig
76
+ };
@@ -0,0 +1,23 @@
1
+ import { guidedResult } from "./utils.js";
2
+ import { resolveTreeseedWorkflowState } from "../workflow-state.js";
3
+ const handleContinue = (_invocation, context) => {
4
+ const state = resolveTreeseedWorkflowState(context.cwd);
5
+ const next = state.recommendations[0] ?? null;
6
+ return guidedResult({
7
+ command: "continue",
8
+ summary: next ? "Treeseed selected the safest next workflow step." : "Treeseed could not infer a next workflow step.",
9
+ facts: [
10
+ { label: "Branch", value: state.branchName ?? "(none)" },
11
+ { label: "Suggested command", value: next?.command ?? "(none)" }
12
+ ],
13
+ nextSteps: next ? [`${next.command} # ${next.reason}`] : ["treeseed doctor"],
14
+ report: {
15
+ state,
16
+ selected: next
17
+ },
18
+ exitCode: next ? 0 : 1
19
+ });
20
+ };
21
+ export {
22
+ handleContinue
23
+ };
@@ -0,0 +1,139 @@
1
+ import { applyTreeseedEnvironmentToProcess } from "../../scripts/config-runtime-lib.js";
2
+ import {
3
+ assertDeploymentInitialized,
4
+ createBranchPreviewDeployTarget,
5
+ createPersistentDeployTarget,
6
+ deployTargetLabel,
7
+ ensureGeneratedWranglerConfig,
8
+ finalizeDeploymentState,
9
+ runRemoteD1Migrations
10
+ } from "../../scripts/deploy-lib.js";
11
+ import { currentManagedBranch, PRODUCTION_BRANCH, STAGING_BRANCH } from "../../scripts/git-workflow-lib.js";
12
+ import { packageScriptPath, resolveWranglerBin } from "../../scripts/package-tools.js";
13
+ import { runTenantDeployPreflight } from "../../scripts/save-deploy-preflight-lib.js";
14
+ import { guidedResult } from "./utils.js";
15
+ function inferEnvironmentFromBranch(tenantRoot) {
16
+ const branch = currentManagedBranch(tenantRoot);
17
+ if (branch === STAGING_BRANCH) return "staging";
18
+ if (branch === PRODUCTION_BRANCH) return "prod";
19
+ return null;
20
+ }
21
+ const handleDeploy = (invocation, context) => {
22
+ const commandName = invocation.commandName || "deploy";
23
+ const tenantRoot = context.cwd;
24
+ const environment = typeof invocation.args.environment === "string" ? invocation.args.environment : void 0;
25
+ const targetBranch = typeof invocation.args.targetBranch === "string" ? invocation.args.targetBranch : void 0;
26
+ const dryRun = invocation.args.dryRun === true;
27
+ const only = typeof invocation.args.only === "string" ? invocation.args.only : null;
28
+ const name = typeof invocation.args.name === "string" ? invocation.args.name : null;
29
+ const target = targetBranch ? createBranchPreviewDeployTarget(targetBranch) : createPersistentDeployTarget(environment ?? (context.env.CI ? inferEnvironmentFromBranch(tenantRoot) : null));
30
+ const scope = targetBranch ? "staging" : String(environment ?? (context.env.CI ? inferEnvironmentFromBranch(tenantRoot) : ""));
31
+ const executedSteps = [];
32
+ const nextSteps = [];
33
+ let deployUrl = null;
34
+ let migratedDatabase = null;
35
+ applyTreeseedEnvironmentToProcess({ tenantRoot, scope });
36
+ const allowedSteps = /* @__PURE__ */ new Set(["migrate", "build", "publish"]);
37
+ if (only && !allowedSteps.has(only)) {
38
+ throw new Error(`Unsupported deploy step "${only}". Expected one of ${[...allowedSteps].join(", ")}.`);
39
+ }
40
+ const shouldRun = (step) => !only || only === step;
41
+ if (scope === "local") {
42
+ runTenantDeployPreflight({ cwd: tenantRoot, scope: "local" });
43
+ const buildOnly = context.spawn(process.execPath, [packageScriptPath("tenant-build")], {
44
+ cwd: tenantRoot,
45
+ env: { ...context.env },
46
+ stdio: "inherit"
47
+ });
48
+ return guidedResult({
49
+ command: commandName,
50
+ summary: buildOnly.status === 0 ? "Treeseed local deploy completed as a build-only publish target." : "Treeseed local deploy failed.",
51
+ exitCode: buildOnly.status ?? 1,
52
+ facts: [
53
+ { label: "Target", value: "local" },
54
+ { label: "Dry run", value: dryRun ? "yes" : "no" }
55
+ ],
56
+ nextSteps: buildOnly.status === 0 ? ["treeseed preview", 'treeseed save "describe your change"'] : ["treeseed doctor"],
57
+ report: {
58
+ target: "local",
59
+ dryRun,
60
+ only,
61
+ name,
62
+ executedSteps: ["build"]
63
+ }
64
+ });
65
+ }
66
+ assertDeploymentInitialized(tenantRoot, { target });
67
+ runTenantDeployPreflight({ cwd: tenantRoot, scope });
68
+ const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
69
+ if (shouldRun("migrate")) {
70
+ const result = runRemoteD1Migrations(tenantRoot, { dryRun, target });
71
+ executedSteps.push("migrate");
72
+ migratedDatabase = result.databaseName;
73
+ }
74
+ if (shouldRun("build")) {
75
+ if (dryRun) {
76
+ executedSteps.push("build");
77
+ } else {
78
+ const buildResult = context.spawn(process.execPath, [packageScriptPath("tenant-build")], {
79
+ cwd: tenantRoot,
80
+ env: { ...context.env },
81
+ stdio: "inherit"
82
+ });
83
+ if ((buildResult.status ?? 1) !== 0) {
84
+ return { exitCode: buildResult.status ?? 1 };
85
+ }
86
+ executedSteps.push("build");
87
+ }
88
+ }
89
+ if (shouldRun("publish")) {
90
+ if (dryRun) {
91
+ executedSteps.push("publish");
92
+ } else {
93
+ const publishResult = context.spawn(process.execPath, [resolveWranglerBin(), "deploy", "--config", wranglerPath], {
94
+ cwd: tenantRoot,
95
+ env: { ...context.env },
96
+ stdio: "inherit"
97
+ });
98
+ if ((publishResult.status ?? 1) !== 0) {
99
+ return { exitCode: publishResult.status ?? 1 };
100
+ }
101
+ const finalizedState = finalizeDeploymentState(tenantRoot, { target });
102
+ deployUrl = finalizedState.lastDeployedUrl ?? null;
103
+ executedSteps.push("publish");
104
+ }
105
+ }
106
+ if (scope === "staging") {
107
+ nextSteps.push("treeseed release --patch");
108
+ }
109
+ if (scope !== "local") {
110
+ nextSteps.push(`treeseed status`);
111
+ }
112
+ return guidedResult({
113
+ command: commandName,
114
+ summary: dryRun ? `Treeseed ${commandName} dry run completed for ${deployTargetLabel(target)}.` : `Treeseed ${commandName} completed for ${deployTargetLabel(target)}.`,
115
+ facts: [
116
+ { label: "Target", value: deployTargetLabel(target) },
117
+ { label: "Target label", value: name ?? "(none)" },
118
+ { label: "Dry run", value: dryRun ? "yes" : "no" },
119
+ { label: "Executed steps", value: executedSteps.join(", ") || "(none)" },
120
+ { label: "Migrated database", value: migratedDatabase ?? "(none)" },
121
+ { label: "Preview URL", value: deployUrl ?? (dryRun ? "(dry run)" : "(not reported)") }
122
+ ],
123
+ nextSteps,
124
+ report: {
125
+ target: deployTargetLabel(target),
126
+ scope,
127
+ dryRun,
128
+ only,
129
+ name,
130
+ wranglerPath,
131
+ executedSteps,
132
+ migratedDatabase,
133
+ deployUrl
134
+ }
135
+ });
136
+ };
137
+ export {
138
+ handleDeploy
139
+ };
@@ -0,0 +1,94 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { applyTreeseedEnvironmentToProcess, assertTreeseedCommandEnvironment } from "../../scripts/config-runtime-lib.js";
4
+ import {
5
+ cleanupDestroyedState,
6
+ createPersistentDeployTarget,
7
+ destroyCloudflareResources,
8
+ loadDeployState,
9
+ printDestroySummary,
10
+ validateDestroyPrerequisites
11
+ } from "../../scripts/deploy-lib.js";
12
+ import { guidedResult } from "./utils.js";
13
+ function printDangerMessage(context, deployConfig, state, expectedConfirmation) {
14
+ context.write("DANGER: treeseed destroy will permanently delete this site and its Cloudflare resources.", "stderr");
15
+ context.write(` Site: ${deployConfig.name}`, "stderr");
16
+ context.write(` Slug: ${deployConfig.slug}`, "stderr");
17
+ context.write(` Worker: ${state.workerName}`, "stderr");
18
+ context.write(` D1: ${state.d1Databases.SITE_DATA_DB.databaseName}`, "stderr");
19
+ context.write(` KV FORM_GUARD_KV: ${state.kvNamespaces.FORM_GUARD_KV.name}`, "stderr");
20
+ context.write(` KV SESSION: ${state.kvNamespaces.SESSION.name}`, "stderr");
21
+ context.write(" This action is irreversible.", "stderr");
22
+ context.write(` To continue, type exactly: ${expectedConfirmation}`, "stderr");
23
+ }
24
+ const handleDestroy = async (invocation, context) => {
25
+ const commandName = invocation.commandName || "destroy";
26
+ const tenantRoot = context.cwd;
27
+ const scope = String(invocation.args.environment);
28
+ const target = createPersistentDeployTarget(scope);
29
+ const dryRun = invocation.args.dryRun === true;
30
+ const force = invocation.args.force === true;
31
+ const skipConfirmation = invocation.args.skipConfirmation === true;
32
+ const confirm = typeof invocation.args.confirm === "string" ? invocation.args.confirm : null;
33
+ const removeBuildArtifacts = invocation.args.removeBuildArtifacts === true;
34
+ applyTreeseedEnvironmentToProcess({ tenantRoot, scope });
35
+ assertTreeseedCommandEnvironment({ tenantRoot, scope, purpose: "destroy" });
36
+ const deployConfig = validateDestroyPrerequisites(tenantRoot, { requireRemote: !dryRun });
37
+ const state = loadDeployState(tenantRoot, deployConfig, { target });
38
+ const expectedConfirmation = deployConfig.slug;
39
+ if (!skipConfirmation) {
40
+ printDangerMessage(context, deployConfig, state, expectedConfirmation);
41
+ const confirmed = confirm !== null ? confirm === expectedConfirmation : await (async () => {
42
+ const rl = readline.createInterface({ input, output });
43
+ try {
44
+ return (await rl.question("Confirmation: ")).trim() === expectedConfirmation;
45
+ } finally {
46
+ rl.close();
47
+ }
48
+ })();
49
+ if (!confirmed) {
50
+ return { exitCode: 1, stderr: ["Destroy aborted: confirmation text did not match."] };
51
+ }
52
+ }
53
+ const result = destroyCloudflareResources(tenantRoot, { dryRun, force, target });
54
+ printDestroySummary(result);
55
+ if (dryRun) {
56
+ return guidedResult({
57
+ command: commandName,
58
+ summary: `Treeseed ${commandName} dry run completed.`,
59
+ facts: [
60
+ { label: "Environment", value: scope },
61
+ { label: "Remote deletion", value: "skipped" }
62
+ ],
63
+ nextSteps: ["treeseed status"],
64
+ report: {
65
+ scope,
66
+ dryRun: true,
67
+ force,
68
+ removeBuildArtifacts
69
+ }
70
+ });
71
+ }
72
+ cleanupDestroyedState(tenantRoot, { target, removeBuildArtifacts });
73
+ return guidedResult({
74
+ command: commandName,
75
+ summary: `Treeseed ${commandName} completed and local deployment state was removed.`,
76
+ facts: [
77
+ { label: "Environment", value: scope },
78
+ { label: "Removed build artifacts", value: removeBuildArtifacts ? "yes" : "no" }
79
+ ],
80
+ nextSteps: [
81
+ `treeseed config --environment ${scope}`,
82
+ "treeseed status"
83
+ ],
84
+ report: {
85
+ scope,
86
+ dryRun: false,
87
+ force,
88
+ removeBuildArtifacts
89
+ }
90
+ });
91
+ };
92
+ export {
93
+ handleDestroy
94
+ };
@@ -0,0 +1,48 @@
1
+ import { collectCliPreflight } from "../../scripts/workspace-preflight-lib.js";
2
+ import { guidedResult } from "./utils.js";
3
+ import { resolveTreeseedWorkflowState } from "../workflow-state.js";
4
+ import { applyTreeseedSafeRepairs } from "../repair.js";
5
+ const handleDoctor = (invocation, context) => {
6
+ const performedFixes = invocation.args.fix === true && resolveTreeseedWorkflowState(context.cwd).deployConfigPresent ? applyTreeseedSafeRepairs(context.cwd) : [];
7
+ const state = resolveTreeseedWorkflowState(context.cwd);
8
+ const preflight = collectCliPreflight({ cwd: context.cwd, requireAuth: false });
9
+ const mustFixNow = [];
10
+ const optional = [];
11
+ if (!state.workspaceRoot) mustFixNow.push("Run Treeseed from the workspace root so package commands and workflow state resolve correctly.");
12
+ if (!state.repoRoot) mustFixNow.push("Initialize or clone the git repository before using save, close, deploy, or release flows.");
13
+ if (!state.deployConfigPresent) mustFixNow.push("Create or restore treeseed.site.yaml so the tenant contract can be loaded.");
14
+ if (preflight.missingCommands.includes("git")) mustFixNow.push("Install Git.");
15
+ if (preflight.missingCommands.includes("npm")) mustFixNow.push("Install npm 10 or newer.");
16
+ if (!state.files.machineConfig) mustFixNow.push("Run `treeseed config --environment local` to create the local machine config.");
17
+ if (!state.files.envLocal) optional.push("Create `.env.local` or run `treeseed config --environment local` to generate it.");
18
+ if (!state.files.devVars) optional.push("Generate `.dev.vars` by running `treeseed config --environment local`.");
19
+ if (!state.auth.gh) optional.push("Authenticate `gh` if you want GitHub-backed save or release automation.");
20
+ if (!state.auth.wrangler) optional.push("Authenticate `wrangler` before staging, preview, or production deployment work.");
21
+ if (!state.auth.copilot) optional.push("Configure Copilot CLI only if you rely on local Copilot-assisted workflows.");
22
+ return guidedResult({
23
+ command: "doctor",
24
+ summary: mustFixNow.length === 0 ? "Treeseed doctor found no blocking issues." : "Treeseed doctor found issues that need attention.",
25
+ facts: [
26
+ { label: "Must fix now", value: mustFixNow.length },
27
+ { label: "Optional follow-up", value: optional.length },
28
+ { label: "Safe fixes applied", value: performedFixes.length },
29
+ { label: "Branch", value: state.branchName ?? "(none)" },
30
+ { label: "Workspace root", value: state.workspaceRoot ? "yes" : "no" }
31
+ ],
32
+ nextSteps: [
33
+ ...mustFixNow.map((item) => item),
34
+ ...mustFixNow.length === 0 ? optional : optional.map((item) => `Optional: ${item}`)
35
+ ],
36
+ report: {
37
+ state,
38
+ preflight,
39
+ performedFixes,
40
+ mustFixNow,
41
+ optional
42
+ },
43
+ exitCode: mustFixNow.length === 0 ? 0 : 1
44
+ });
45
+ };
46
+ export {
47
+ handleDoctor
48
+ };
@@ -0,0 +1,30 @@
1
+ import { packageScriptPath } from "../../scripts/package-tools.js";
2
+ import { guidedResult } from "./utils.js";
3
+ const handleInit = (invocation, context) => {
4
+ const directory = invocation.positionals[0];
5
+ const result = context.spawn(process.execPath, [packageScriptPath("scaffold-site"), ...invocation.rawArgs], {
6
+ cwd: context.cwd,
7
+ env: { ...context.env },
8
+ stdio: "inherit"
9
+ });
10
+ if ((result.status ?? 1) !== 0) {
11
+ return { exitCode: result.status ?? 1 };
12
+ }
13
+ return guidedResult({
14
+ command: "init",
15
+ summary: "Treeseed init completed successfully.",
16
+ facts: [{ label: "Directory", value: directory ?? "(current directory)" }],
17
+ nextSteps: [
18
+ `cd ${directory}`,
19
+ "treeseed doctor",
20
+ "treeseed config --environment local",
21
+ "treeseed dev"
22
+ ],
23
+ report: {
24
+ directory: directory ?? null
25
+ }
26
+ });
27
+ };
28
+ export {
29
+ handleInit
30
+ };
@@ -0,0 +1,27 @@
1
+ import { guidedResult } from "./utils.js";
2
+ import { resolveTreeseedWorkflowState } from "../workflow-state.js";
3
+ const handleNext = (_invocation, context) => {
4
+ const state = resolveTreeseedWorkflowState(context.cwd);
5
+ return guidedResult({
6
+ command: "next",
7
+ summary: "Treeseed next-step recommendations",
8
+ facts: [
9
+ { label: "Branch", value: state.branchName ?? "(none)" },
10
+ { label: "Branch role", value: state.branchRole },
11
+ { label: "Dirty worktree", value: state.dirtyWorktree ? "yes" : "no" }
12
+ ],
13
+ nextSteps: state.recommendations.map((item) => `${item.command} # ${item.reason}`),
14
+ report: {
15
+ state: {
16
+ branchName: state.branchName,
17
+ branchRole: state.branchRole,
18
+ dirtyWorktree: state.dirtyWorktree,
19
+ environment: state.environment
20
+ },
21
+ recommendations: state.recommendations
22
+ }
23
+ });
24
+ };
25
+ export {
26
+ handleNext
27
+ };
@@ -0,0 +1,8 @@
1
+ import { handleConfig } from "./config.js";
2
+ const handlePrepare = (invocation, context) => handleConfig({
3
+ ...invocation,
4
+ commandName: invocation.commandName || "prepare"
5
+ }, context);
6
+ export {
7
+ handlePrepare
8
+ };
@@ -0,0 +1,8 @@
1
+ import { handleRelease } from "./release.js";
2
+ const handlePromote = (invocation, context) => handleRelease({
3
+ ...invocation,
4
+ commandName: invocation.commandName || "promote"
5
+ }, context);
6
+ export {
7
+ handlePromote
8
+ };
@@ -0,0 +1,8 @@
1
+ import { handleDeploy } from "./deploy.js";
2
+ const handlePublish = (invocation, context) => handleDeploy({
3
+ ...invocation,
4
+ commandName: invocation.commandName || "publish"
5
+ }, context);
6
+ export {
7
+ handlePublish
8
+ };