@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.
- package/README.md +115 -0
- package/dist/cli/handlers/close.js +60 -0
- package/dist/cli/handlers/config.js +76 -0
- package/dist/cli/handlers/continue.js +23 -0
- package/dist/cli/handlers/deploy.js +139 -0
- package/dist/cli/handlers/destroy.js +94 -0
- package/dist/cli/handlers/doctor.js +48 -0
- package/dist/cli/handlers/init.js +30 -0
- package/dist/cli/handlers/next.js +27 -0
- package/dist/cli/handlers/prepare.js +8 -0
- package/dist/cli/handlers/promote.js +8 -0
- package/dist/cli/handlers/publish.js +8 -0
- package/dist/cli/handlers/release.js +60 -0
- package/dist/cli/handlers/rollback.js +163 -0
- package/dist/cli/handlers/save.js +87 -0
- package/dist/cli/handlers/setup.js +48 -0
- package/dist/cli/handlers/ship.js +49 -0
- package/dist/cli/handlers/start.js +97 -0
- package/dist/cli/handlers/status.js +31 -0
- package/dist/cli/handlers/teardown.js +50 -0
- package/dist/cli/handlers/utils.js +59 -0
- package/dist/cli/handlers/work.js +85 -0
- package/dist/cli/help.js +143 -0
- package/dist/cli/main.js +27 -0
- package/dist/cli/parser.js +96 -0
- package/dist/cli/registry.js +445 -0
- package/dist/cli/repair.js +89 -0
- package/dist/cli/runtime.js +165 -0
- package/dist/cli/types.js +0 -0
- package/dist/cli/workflow-state.js +171 -0
- package/dist/index.js +1 -0
- package/dist/scripts/aggregate-book.d.ts +1 -0
- package/dist/scripts/aggregate-book.js +121 -0
- package/dist/scripts/assert-release-tag-version.d.ts +1 -0
- package/dist/scripts/assert-release-tag-version.js +21 -0
- package/dist/scripts/build-dist.d.ts +1 -0
- package/dist/scripts/build-dist.js +108 -0
- package/dist/scripts/build-tenant-worker.d.ts +1 -0
- package/dist/scripts/build-tenant-worker.js +36 -0
- package/dist/scripts/cleanup-markdown.d.ts +2 -0
- package/dist/scripts/cleanup-markdown.js +373 -0
- package/dist/scripts/config-runtime-lib.d.ts +122 -0
- package/dist/scripts/config-runtime-lib.js +505 -0
- package/dist/scripts/config-treeseed.d.ts +2 -0
- package/dist/scripts/config-treeseed.js +81 -0
- package/dist/scripts/d1-migration-lib.d.ts +6 -0
- package/dist/scripts/d1-migration-lib.js +90 -0
- package/dist/scripts/deploy-lib.d.ts +127 -0
- package/dist/scripts/deploy-lib.js +841 -0
- package/dist/scripts/ensure-mailpit.d.ts +1 -0
- package/dist/scripts/ensure-mailpit.js +29 -0
- package/dist/scripts/git-workflow-lib.d.ts +25 -0
- package/dist/scripts/git-workflow-lib.js +136 -0
- package/dist/scripts/github-automation-lib.d.ts +156 -0
- package/dist/scripts/github-automation-lib.js +242 -0
- package/dist/scripts/local-dev-lib.d.ts +9 -0
- package/dist/scripts/local-dev-lib.js +84 -0
- package/dist/scripts/local-dev.d.ts +1 -0
- package/dist/scripts/local-dev.js +129 -0
- package/dist/scripts/logs-mailpit.d.ts +1 -0
- package/dist/scripts/logs-mailpit.js +2 -0
- package/dist/scripts/mailpit-runtime.d.ts +4 -0
- package/dist/scripts/mailpit-runtime.js +57 -0
- package/dist/scripts/package-tools.d.ts +22 -0
- package/dist/scripts/package-tools.js +255 -0
- package/dist/scripts/patch-starlight-content-path.d.ts +1 -0
- package/dist/scripts/patch-starlight-content-path.js +172 -0
- package/dist/scripts/paths.d.ts +17 -0
- package/dist/scripts/paths.js +26 -0
- package/dist/scripts/publish-package.d.ts +1 -0
- package/dist/scripts/publish-package.js +19 -0
- package/dist/scripts/release-verify.d.ts +1 -0
- package/dist/scripts/release-verify.js +136 -0
- package/dist/scripts/run-fixture-astro-command.d.ts +1 -0
- package/dist/scripts/run-fixture-astro-command.js +18 -0
- package/dist/scripts/save-deploy-preflight-lib.d.ts +34 -0
- package/dist/scripts/save-deploy-preflight-lib.js +69 -0
- package/dist/scripts/scaffold-site.d.ts +2 -0
- package/dist/scripts/scaffold-site.js +92 -0
- package/dist/scripts/stop-mailpit.d.ts +1 -0
- package/dist/scripts/stop-mailpit.js +5 -0
- package/dist/scripts/sync-dev-vars.d.ts +1 -0
- package/dist/scripts/sync-dev-vars.js +6 -0
- package/dist/scripts/template-registry-lib.d.ts +47 -0
- package/dist/scripts/template-registry-lib.js +137 -0
- package/dist/scripts/tenant-astro-command.d.ts +1 -0
- package/dist/scripts/tenant-astro-command.js +3 -0
- package/dist/scripts/tenant-build.d.ts +1 -0
- package/dist/scripts/tenant-build.js +16 -0
- package/dist/scripts/tenant-check.d.ts +1 -0
- package/dist/scripts/tenant-check.js +7 -0
- package/dist/scripts/tenant-d1-migrate-local.d.ts +1 -0
- package/dist/scripts/tenant-d1-migrate-local.js +11 -0
- package/dist/scripts/tenant-deploy.d.ts +2 -0
- package/dist/scripts/tenant-deploy.js +180 -0
- package/dist/scripts/tenant-destroy.d.ts +2 -0
- package/dist/scripts/tenant-destroy.js +104 -0
- package/dist/scripts/tenant-dev.d.ts +1 -0
- package/dist/scripts/tenant-dev.js +171 -0
- package/dist/scripts/tenant-lint.d.ts +1 -0
- package/dist/scripts/tenant-lint.js +4 -0
- package/dist/scripts/tenant-test.d.ts +1 -0
- package/dist/scripts/tenant-test.js +4 -0
- package/dist/scripts/test-cloudflare-local.d.ts +1 -0
- package/dist/scripts/test-cloudflare-local.js +212 -0
- package/dist/scripts/test-scaffold.d.ts +2 -0
- package/dist/scripts/test-scaffold.js +297 -0
- package/dist/scripts/treeseed.d.ts +2 -0
- package/dist/scripts/treeseed.js +4 -0
- package/dist/scripts/validate-templates.d.ts +2 -0
- package/dist/scripts/validate-templates.js +4 -0
- package/dist/scripts/watch-dev-lib.d.ts +21 -0
- package/dist/scripts/watch-dev-lib.js +277 -0
- package/dist/scripts/workspace-close.d.ts +2 -0
- package/dist/scripts/workspace-close.js +24 -0
- package/dist/scripts/workspace-command-e2e.d.ts +2 -0
- package/dist/scripts/workspace-command-e2e.js +718 -0
- package/dist/scripts/workspace-lint.d.ts +1 -0
- package/dist/scripts/workspace-lint.js +9 -0
- package/dist/scripts/workspace-preflight-lib.d.ts +36 -0
- package/dist/scripts/workspace-preflight-lib.js +179 -0
- package/dist/scripts/workspace-preflight.d.ts +2 -0
- package/dist/scripts/workspace-preflight.js +22 -0
- package/dist/scripts/workspace-publish-changed-packages.d.ts +1 -0
- package/dist/scripts/workspace-publish-changed-packages.js +16 -0
- package/dist/scripts/workspace-release-verify.d.ts +1 -0
- package/dist/scripts/workspace-release-verify.js +81 -0
- package/dist/scripts/workspace-release.d.ts +2 -0
- package/dist/scripts/workspace-release.js +42 -0
- package/dist/scripts/workspace-save-lib.d.ts +42 -0
- package/dist/scripts/workspace-save-lib.js +220 -0
- package/dist/scripts/workspace-save.d.ts +2 -0
- package/dist/scripts/workspace-save.js +124 -0
- package/dist/scripts/workspace-start-warning.d.ts +0 -0
- package/dist/scripts/workspace-start-warning.js +3 -0
- package/dist/scripts/workspace-start.d.ts +2 -0
- package/dist/scripts/workspace-start.js +71 -0
- package/dist/scripts/workspace-test-unit.d.ts +1 -0
- package/dist/scripts/workspace-test-unit.js +4 -0
- package/dist/scripts/workspace-test.d.ts +1 -0
- package/dist/scripts/workspace-test.js +11 -0
- package/dist/scripts/workspace-tools.d.ts +13 -0
- package/dist/scripts/workspace-tools.js +226 -0
- package/dist/src/cli/handlers/close.d.ts +2 -0
- package/dist/src/cli/handlers/config.d.ts +2 -0
- package/dist/src/cli/handlers/continue.d.ts +2 -0
- package/dist/src/cli/handlers/deploy.d.ts +2 -0
- package/dist/src/cli/handlers/destroy.d.ts +2 -0
- package/dist/src/cli/handlers/doctor.d.ts +2 -0
- package/dist/src/cli/handlers/init.d.ts +2 -0
- package/dist/src/cli/handlers/next.d.ts +2 -0
- package/dist/src/cli/handlers/prepare.d.ts +2 -0
- package/dist/src/cli/handlers/promote.d.ts +2 -0
- package/dist/src/cli/handlers/publish.d.ts +2 -0
- package/dist/src/cli/handlers/release.d.ts +2 -0
- package/dist/src/cli/handlers/rollback.d.ts +2 -0
- package/dist/src/cli/handlers/save.d.ts +2 -0
- package/dist/src/cli/handlers/setup.d.ts +2 -0
- package/dist/src/cli/handlers/ship.d.ts +2 -0
- package/dist/src/cli/handlers/start.d.ts +3 -0
- package/dist/src/cli/handlers/status.d.ts +2 -0
- package/dist/src/cli/handlers/teardown.d.ts +2 -0
- package/dist/src/cli/handlers/utils.d.ts +18 -0
- package/dist/src/cli/handlers/work.d.ts +2 -0
- package/dist/src/cli/help.d.ts +4 -0
- package/dist/src/cli/main.d.ts +6 -0
- package/dist/src/cli/parser.d.ts +3 -0
- package/dist/src/cli/registry.d.ts +27 -0
- package/dist/src/cli/repair.d.ts +6 -0
- package/dist/src/cli/runtime.d.ts +4 -0
- package/dist/src/cli/types.d.ts +71 -0
- package/dist/src/cli/workflow-state.d.ts +49 -0
- package/dist/src/index.d.ts +1 -0
- 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
|
+
};
|