@treeseed/sdk 0.1.1 → 0.3.0
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 +97 -494
- package/dist/{src/cli-tools.d.ts → cli-tools.d.ts} +1 -1
- package/dist/cli-tools.js +5 -3
- package/dist/{src/content-store.d.ts → content-store.d.ts} +3 -2
- package/dist/content-store.js +52 -20
- package/dist/{src/d1-store.d.ts → d1-store.d.ts} +62 -1
- package/dist/d1-store.js +625 -65
- package/dist/field-aliases.d.ts +11 -0
- package/dist/field-aliases.js +41 -0
- package/dist/graph/build.d.ts +19 -0
- package/dist/graph/build.js +949 -0
- package/dist/graph/dsl.d.ts +2 -0
- package/dist/graph/dsl.js +243 -0
- package/dist/graph/query.d.ts +47 -0
- package/dist/graph/query.js +447 -0
- package/dist/graph/ranking.d.ts +3 -0
- package/dist/graph/ranking.js +483 -0
- package/dist/graph/schema.d.ts +142 -0
- package/dist/graph/schema.js +133 -0
- package/dist/graph.d.ts +52 -0
- package/dist/graph.js +133 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +90 -2
- package/dist/model-registry.d.ts +8 -0
- package/dist/model-registry.js +351 -25
- package/dist/operations/providers/default.d.ts +10 -0
- package/dist/operations/providers/default.js +514 -0
- package/dist/operations/runtime.d.ts +7 -0
- package/dist/operations/runtime.js +60 -0
- package/dist/operations/services/config-runtime.d.ts +269 -0
- package/dist/operations/services/config-runtime.js +1397 -0
- package/dist/operations/services/d1-migration.d.ts +6 -0
- package/dist/operations/services/d1-migration.js +89 -0
- package/dist/operations/services/deploy.d.ts +371 -0
- package/dist/operations/services/deploy.js +981 -0
- package/dist/operations/services/git-workflow.d.ts +49 -0
- package/dist/operations/services/git-workflow.js +218 -0
- package/dist/operations/services/github-automation.d.ts +156 -0
- package/dist/operations/services/github-automation.js +256 -0
- package/dist/operations/services/local-dev.d.ts +9 -0
- package/dist/operations/services/local-dev.js +106 -0
- package/dist/operations/services/mailpit-runtime.d.ts +4 -0
- package/dist/operations/services/mailpit-runtime.js +59 -0
- package/dist/operations/services/railway-deploy.d.ts +53 -0
- package/dist/operations/services/railway-deploy.js +123 -0
- package/dist/operations/services/runtime-paths.d.ts +19 -0
- package/dist/operations/services/runtime-paths.js +54 -0
- package/dist/operations/services/runtime-tools.d.ts +117 -0
- package/dist/operations/services/runtime-tools.js +358 -0
- package/dist/operations/services/save-deploy-preflight.d.ts +34 -0
- package/dist/operations/services/save-deploy-preflight.js +76 -0
- package/dist/operations/services/template-registry.d.ts +88 -0
- package/dist/operations/services/template-registry.js +407 -0
- package/dist/operations/services/watch-dev.d.ts +21 -0
- package/dist/operations/services/watch-dev.js +284 -0
- package/dist/operations/services/workspace-preflight.d.ts +40 -0
- package/dist/operations/services/workspace-preflight.js +165 -0
- package/dist/operations/services/workspace-save.d.ts +42 -0
- package/dist/operations/services/workspace-save.js +235 -0
- package/dist/operations/services/workspace-tools.d.ts +16 -0
- package/dist/operations/services/workspace-tools.js +270 -0
- package/dist/operations-registry.d.ts +5 -0
- package/dist/operations-registry.js +68 -0
- package/dist/operations-types.d.ts +71 -0
- package/dist/operations-types.js +17 -0
- package/dist/operations.d.ts +6 -0
- package/dist/operations.js +16 -0
- package/dist/platform/books-data.d.ts +1 -0
- package/dist/platform/books-data.js +1 -0
- package/dist/platform/contracts.d.ts +158 -0
- package/dist/platform/contracts.js +0 -0
- package/dist/platform/deploy/config.d.ts +4 -0
- package/dist/platform/deploy/config.js +222 -0
- package/dist/platform/deploy-config.d.ts +1 -0
- package/dist/platform/deploy-config.js +1 -0
- package/dist/platform/env.yaml +394 -0
- package/dist/platform/environment.d.ts +130 -0
- package/dist/platform/environment.js +331 -0
- package/dist/platform/plugin.d.ts +2 -0
- package/dist/platform/plugin.js +4 -0
- package/dist/platform/plugins/constants.d.ts +22 -0
- package/dist/platform/plugins/constants.js +29 -0
- package/dist/platform/plugins/plugin.d.ts +51 -0
- package/dist/platform/plugins/plugin.js +6 -0
- package/dist/platform/plugins/runtime.d.ts +35 -0
- package/dist/platform/plugins/runtime.js +142 -0
- package/dist/platform/plugins.d.ts +5 -0
- package/dist/platform/plugins.js +16 -0
- package/dist/platform/site-config-schema.js +1 -0
- package/dist/platform/tenant/config.d.ts +9 -0
- package/dist/platform/tenant/config.js +154 -0
- package/dist/platform/tenant/runtime-config.d.ts +4 -0
- package/dist/platform/tenant/runtime-config.js +20 -0
- package/dist/platform/tenant-config.d.ts +1 -0
- package/dist/platform/tenant-config.js +1 -0
- package/dist/platform/utils/books-data.d.ts +29 -0
- package/dist/platform/utils/books-data.js +82 -0
- package/dist/platform/utils/site-config-schema.js +321 -0
- package/dist/remote.d.ts +175 -0
- package/dist/remote.js +202 -0
- package/dist/runtime.js +50 -3
- package/dist/scripts/aggregate-book.js +121 -0
- package/dist/scripts/build-dist.js +57 -13
- package/dist/scripts/build-tenant-worker.js +36 -0
- package/dist/scripts/cleanup-markdown.js +373 -0
- package/dist/scripts/cli-test-fixtures.js +48 -0
- package/dist/scripts/config-treeseed.js +95 -0
- package/dist/scripts/ensure-mailpit.js +29 -0
- package/dist/scripts/local-dev.js +129 -0
- package/dist/scripts/logs-mailpit.js +2 -0
- package/dist/scripts/patch-starlight-content-path.js +172 -0
- package/dist/scripts/release-verify.js +34 -5
- package/dist/scripts/run-fixture-astro-command.js +18 -0
- package/dist/scripts/scaffold-site.js +65 -0
- package/dist/scripts/stop-mailpit.js +5 -0
- package/dist/scripts/sync-dev-vars.js +6 -0
- package/dist/scripts/sync-template.js +20 -0
- package/dist/scripts/template-catalog.test.js +100 -0
- package/dist/scripts/template-command.js +31 -0
- package/dist/scripts/tenant-astro-command.js +3 -0
- package/dist/scripts/tenant-build.js +16 -0
- package/dist/scripts/tenant-check.js +7 -0
- package/dist/scripts/tenant-d1-migrate-local.js +11 -0
- package/dist/scripts/tenant-deploy.js +180 -0
- package/dist/scripts/tenant-destroy.js +104 -0
- package/dist/scripts/tenant-dev.js +171 -0
- package/dist/scripts/tenant-lint.js +4 -0
- package/dist/scripts/tenant-test.js +4 -0
- package/dist/scripts/test-cloudflare-local.js +212 -0
- package/dist/scripts/test-scaffold.js +314 -0
- package/dist/scripts/test-smoke.js +71 -13
- package/dist/scripts/treeseed-assert-release-tag-version.js +21 -0
- package/dist/scripts/treeseed-build-dist.js +134 -0
- package/dist/scripts/treeseed-publish-package.js +19 -0
- package/dist/scripts/treeseed-release-verify.js +131 -0
- package/dist/scripts/treeseed-run-ts.js +45 -0
- package/dist/scripts/validate-templates.js +6 -0
- package/dist/scripts/verify-driver.js +29 -0
- package/dist/scripts/workflow-commands.test.js +39 -0
- package/dist/scripts/workspace-close.js +24 -0
- package/dist/scripts/workspace-command-e2e.js +718 -0
- package/dist/scripts/workspace-lint.js +9 -0
- package/dist/scripts/workspace-preflight.js +22 -0
- package/dist/scripts/workspace-publish-changed-packages.js +16 -0
- package/dist/scripts/workspace-release-verify.js +81 -0
- package/dist/scripts/workspace-release.js +42 -0
- package/dist/scripts/workspace-save.js +124 -0
- package/dist/scripts/workspace-start-warning.js +3 -0
- package/dist/scripts/workspace-start.js +71 -0
- package/dist/scripts/workspace-test-unit.js +4 -0
- package/dist/scripts/workspace-test.js +11 -0
- package/dist/sdk-fields.d.ts +11 -0
- package/dist/sdk-fields.js +169 -0
- package/dist/sdk-filters.d.ts +4 -0
- package/dist/sdk-filters.js +12 -15
- package/dist/sdk-types.d.ts +796 -0
- package/dist/sdk-types.js +7 -1
- package/dist/sdk-version.d.ts +2 -0
- package/dist/sdk-version.js +42 -0
- package/dist/sdk.d.ts +215 -0
- package/dist/sdk.js +235 -11
- package/dist/stores/cursor-store.js +9 -3
- package/dist/stores/lease-store.js +8 -2
- package/dist/{src/stores → stores}/message-store.d.ts +1 -1
- package/dist/stores/message-store.js +27 -3
- package/dist/stores/operational-store.d.ts +24 -0
- package/dist/stores/operational-store.js +279 -0
- package/dist/stores/run-store.js +8 -1
- package/dist/stores/subscription-store.js +7 -5
- package/dist/template-catalog.d.ts +13 -0
- package/dist/template-catalog.js +141 -0
- package/dist/treeseed/services/compose.yml +7 -0
- package/dist/treeseed/template-catalog/catalog.fixture.json +55 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.d.ts +2 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.ts +3 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +32 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +40 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/empty/.gitkeep +1 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/knowledge/handbook/index.mdx +11 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/pages/welcome.mdx +11 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.d.ts +1 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.ts +3 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/env.yaml +1 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +19 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +26 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template/tsconfig.json +9 -0
- package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +90 -0
- package/dist/verification.d.ts +20 -0
- package/dist/verification.js +98 -0
- package/dist/workflow/operations.d.ts +396 -0
- package/dist/workflow/operations.js +841 -0
- package/dist/workflow-state.d.ts +56 -0
- package/dist/workflow-state.js +195 -0
- package/dist/workflow-support.d.ts +9 -0
- package/dist/workflow-support.js +176 -0
- package/dist/workflow.d.ts +111 -0
- package/dist/workflow.js +97 -0
- package/package.json +97 -5
- package/scripts/verify-driver.mjs +29 -0
- package/dist/scripts/.ts-run-1775616845195-odh4xzphk3l.js +0 -22
- package/dist/scripts/.ts-run-1775616848931-9386s6kwrl.js +0 -126
- package/dist/scripts/assert-release-tag-version.d.ts +0 -1
- package/dist/scripts/build-dist.d.ts +0 -1
- package/dist/scripts/package-tools.d.ts +0 -15
- package/dist/scripts/publish-package.d.ts +0 -1
- package/dist/scripts/release-verify.d.ts +0 -1
- package/dist/scripts/test-smoke.d.ts +0 -1
- package/dist/src/index.d.ts +0 -6
- package/dist/src/model-registry.d.ts +0 -4
- package/dist/src/sdk-filters.d.ts +0 -4
- package/dist/src/sdk-types.d.ts +0 -285
- package/dist/src/sdk.d.ts +0 -109
- package/dist/test/test-fixture.d.ts +0 -1
- package/dist/test/utils/envelopes.test.d.ts +0 -1
- package/dist/test/utils/sdk.test.d.ts +0 -1
- package/dist/vitest.config.d.ts +0 -2
- /package/dist/{src/frontmatter.d.ts → frontmatter.d.ts} +0 -0
- /package/dist/{src/git-runtime.d.ts → git-runtime.d.ts} +0 -0
- /package/dist/{src/runtime.d.ts → runtime.d.ts} +0 -0
- /package/dist/{src/stores → stores}/cursor-store.d.ts +0 -0
- /package/dist/{src/stores → stores}/envelopes.d.ts +0 -0
- /package/dist/{src/stores → stores}/helpers.d.ts +0 -0
- /package/dist/{src/stores → stores}/lease-store.d.ts +0 -0
- /package/dist/{src/stores → stores}/run-store.d.ts +0 -0
- /package/dist/{src/stores → stores}/subscription-store.d.ts +0 -0
- /package/dist/{src/types → types}/agents.d.ts +0 -0
- /package/dist/{src/types → types}/cloudflare.d.ts +0 -0
- /package/dist/{src/wrangler-d1.d.ts → wrangler-d1.d.ts} +0 -0
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, copyFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
|
+
import {
|
|
5
|
+
applyTreeseedEnvironmentToProcess,
|
|
6
|
+
assertTreeseedCommandEnvironment,
|
|
7
|
+
checkTreeseedProviderConnections,
|
|
8
|
+
createDefaultTreeseedMachineConfig,
|
|
9
|
+
ensureTreeseedActVerificationTooling,
|
|
10
|
+
ensureTreeseedGitignoreEntries,
|
|
11
|
+
formatTreeseedConfigEnvironmentReport,
|
|
12
|
+
formatTreeseedProviderConnectionReport,
|
|
13
|
+
getTreeseedMachineConfigPaths,
|
|
14
|
+
loadTreeseedMachineConfig,
|
|
15
|
+
resolveTreeseedMachineEnvironmentValues,
|
|
16
|
+
rotateTreeseedMachineKey,
|
|
17
|
+
runTreeseedConfigWizard,
|
|
18
|
+
writeTreeseedLocalEnvironmentFiles,
|
|
19
|
+
writeTreeseedMachineConfig
|
|
20
|
+
} from "../operations/services/config-runtime.js";
|
|
21
|
+
import {
|
|
22
|
+
assertDeploymentInitialized,
|
|
23
|
+
cleanupDestroyedState,
|
|
24
|
+
createBranchPreviewDeployTarget,
|
|
25
|
+
createPersistentDeployTarget,
|
|
26
|
+
destroyCloudflareResources,
|
|
27
|
+
ensureGeneratedWranglerConfig,
|
|
28
|
+
finalizeDeploymentState,
|
|
29
|
+
loadDeployState,
|
|
30
|
+
provisionCloudflareResources,
|
|
31
|
+
runRemoteD1Migrations,
|
|
32
|
+
syncCloudflareSecrets,
|
|
33
|
+
validateDeployPrerequisites,
|
|
34
|
+
validateDestroyPrerequisites
|
|
35
|
+
} from "../operations/services/deploy.js";
|
|
36
|
+
import {
|
|
37
|
+
assertCleanWorktree,
|
|
38
|
+
assertFeatureBranch,
|
|
39
|
+
branchExists,
|
|
40
|
+
checkoutBranch,
|
|
41
|
+
createDeprecatedTaskTag,
|
|
42
|
+
createFeatureBranchFromStaging,
|
|
43
|
+
currentManagedBranch,
|
|
44
|
+
deleteLocalBranch,
|
|
45
|
+
deleteRemoteBranch,
|
|
46
|
+
ensureLocalBranchTracking,
|
|
47
|
+
gitWorkflowRoot,
|
|
48
|
+
listTaskBranches,
|
|
49
|
+
mergeCurrentBranchIntoStaging,
|
|
50
|
+
mergeStagingIntoMain,
|
|
51
|
+
prepareReleaseBranches,
|
|
52
|
+
PRODUCTION_BRANCH,
|
|
53
|
+
pushBranch,
|
|
54
|
+
remoteBranchExists,
|
|
55
|
+
STAGING_BRANCH,
|
|
56
|
+
syncBranchWithOrigin,
|
|
57
|
+
waitForStagingAutomation
|
|
58
|
+
} from "../operations/services/git-workflow.js";
|
|
59
|
+
import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from "../operations/services/runtime-tools.js";
|
|
60
|
+
import { runTenantDeployPreflight, runWorkspaceSavePreflight } from "../operations/services/save-deploy-preflight.js";
|
|
61
|
+
import { collectCliPreflight } from "../operations/services/workspace-preflight.js";
|
|
62
|
+
import {
|
|
63
|
+
applyWorkspaceVersionChanges,
|
|
64
|
+
collectMergeConflictReport,
|
|
65
|
+
currentBranch,
|
|
66
|
+
formatMergeConflictReport,
|
|
67
|
+
gitStatusPorcelain,
|
|
68
|
+
hasMeaningfulChanges,
|
|
69
|
+
incrementVersion,
|
|
70
|
+
originRemoteUrl,
|
|
71
|
+
planWorkspaceReleaseBump,
|
|
72
|
+
repoRoot
|
|
73
|
+
} from "../operations/services/workspace-save.js";
|
|
74
|
+
import { run, workspaceRoot } from "../operations/services/workspace-tools.js";
|
|
75
|
+
import { resolveTreeseedWorkflowState } from "../workflow-state.js";
|
|
76
|
+
class TreeseedWorkflowError extends Error {
|
|
77
|
+
code;
|
|
78
|
+
operation;
|
|
79
|
+
details;
|
|
80
|
+
exitCode;
|
|
81
|
+
constructor(operation, code, message, options = {}) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = "TreeseedWorkflowError";
|
|
84
|
+
this.operation = operation;
|
|
85
|
+
this.code = code;
|
|
86
|
+
this.details = options.details;
|
|
87
|
+
this.exitCode = options.exitCode;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function defaultWrite(output, stream = "stdout") {
|
|
91
|
+
if (!output) return;
|
|
92
|
+
(stream === "stderr" ? process.stderr : process.stdout).write(`${output}
|
|
93
|
+
`);
|
|
94
|
+
}
|
|
95
|
+
function workflowError(operation, code, message, options = {}) {
|
|
96
|
+
throw new TreeseedWorkflowError(operation, code, message, options);
|
|
97
|
+
}
|
|
98
|
+
function ageDays(lastCommitDate) {
|
|
99
|
+
const timestamp = Date.parse(lastCommitDate);
|
|
100
|
+
if (!Number.isFinite(timestamp)) return null;
|
|
101
|
+
return Math.max(0, Math.floor((Date.now() - timestamp) / 864e5));
|
|
102
|
+
}
|
|
103
|
+
function withContextEnv(env, action) {
|
|
104
|
+
if (!env) {
|
|
105
|
+
return action();
|
|
106
|
+
}
|
|
107
|
+
const previous = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const [key, value] of Object.entries(env)) {
|
|
109
|
+
previous.set(key, process.env[key]);
|
|
110
|
+
if (value === void 0) {
|
|
111
|
+
delete process.env[key];
|
|
112
|
+
} else {
|
|
113
|
+
process.env[key] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return action();
|
|
118
|
+
} finally {
|
|
119
|
+
for (const [key, value] of previous.entries()) {
|
|
120
|
+
if (value === void 0) {
|
|
121
|
+
delete process.env[key];
|
|
122
|
+
} else {
|
|
123
|
+
process.env[key] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function runChild(command, args, context, cwd, label) {
|
|
129
|
+
const result = spawnSync(command, args, {
|
|
130
|
+
cwd,
|
|
131
|
+
env: { ...process.env, ...context.env ?? {} },
|
|
132
|
+
stdio: "inherit"
|
|
133
|
+
});
|
|
134
|
+
if (result.status !== 0) {
|
|
135
|
+
workflowError("dev", "unsupported_state", `${label} failed.`, { exitCode: result.status ?? 1 });
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
function runNodeScript(scriptName, context, cwd, label) {
|
|
140
|
+
const result = spawnSync(process.execPath, [packageScriptPath(scriptName)], {
|
|
141
|
+
cwd,
|
|
142
|
+
env: { ...process.env, ...context.env ?? {} },
|
|
143
|
+
stdio: "inherit"
|
|
144
|
+
});
|
|
145
|
+
if (result.status !== 0) {
|
|
146
|
+
throw new Error(`${label} failed.`);
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
function renderWorkflowStep(step) {
|
|
151
|
+
return step;
|
|
152
|
+
}
|
|
153
|
+
function normalizeConfigScopes(input) {
|
|
154
|
+
const requested = Array.isArray(input.target) ? input.target : Array.isArray(input.environment) ? input.environment : typeof input.target === "string" ? [input.target] : typeof input.environment === "string" ? [input.environment] : ["all"];
|
|
155
|
+
if (requested.includes("all")) {
|
|
156
|
+
return ["local", "staging", "prod"];
|
|
157
|
+
}
|
|
158
|
+
return ["local", "staging", "prod"].filter((scope) => requested.includes(scope));
|
|
159
|
+
}
|
|
160
|
+
function dedupeRepairActions(actions) {
|
|
161
|
+
const seen = /* @__PURE__ */ new Set();
|
|
162
|
+
return actions.filter((action) => {
|
|
163
|
+
if (seen.has(action.id)) return false;
|
|
164
|
+
seen.add(action.id);
|
|
165
|
+
return true;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function applyTreeseedSafeRepairs(tenantRoot) {
|
|
169
|
+
const actions = [];
|
|
170
|
+
ensureTreeseedGitignoreEntries(tenantRoot);
|
|
171
|
+
actions.push({ id: "gitignore", detail: "Ensured Treeseed gitignore entries are present." });
|
|
172
|
+
const envLocalPath = resolve(tenantRoot, ".env.local");
|
|
173
|
+
const envLocalExamplePath = resolve(tenantRoot, ".env.local.example");
|
|
174
|
+
if (!existsSync(envLocalPath) && existsSync(envLocalExamplePath)) {
|
|
175
|
+
copyFileSync(envLocalExamplePath, envLocalPath);
|
|
176
|
+
actions.push({ id: "env-local", detail: "Created .env.local from .env.local.example." });
|
|
177
|
+
}
|
|
178
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
179
|
+
const { configPath } = getTreeseedMachineConfigPaths(tenantRoot);
|
|
180
|
+
if (!existsSync(configPath)) {
|
|
181
|
+
const machineConfig2 = createDefaultTreeseedMachineConfig({
|
|
182
|
+
tenantRoot,
|
|
183
|
+
deployConfig,
|
|
184
|
+
tenantConfig: void 0
|
|
185
|
+
});
|
|
186
|
+
writeTreeseedMachineConfig(tenantRoot, machineConfig2);
|
|
187
|
+
actions.push({ id: "machine-config", detail: "Created the default Treeseed machine config." });
|
|
188
|
+
}
|
|
189
|
+
resolveTreeseedMachineEnvironmentValues(tenantRoot, "local");
|
|
190
|
+
actions.push({ id: "machine-key", detail: "Ensured the Treeseed machine key exists." });
|
|
191
|
+
const machineConfig = loadTreeseedMachineConfig(tenantRoot);
|
|
192
|
+
writeTreeseedMachineConfig(tenantRoot, machineConfig);
|
|
193
|
+
writeTreeseedLocalEnvironmentFiles(tenantRoot);
|
|
194
|
+
actions.push({ id: "local-env", detail: "Regenerated .env.local and .dev.vars from the current machine config." });
|
|
195
|
+
for (const scope of ["local", "staging", "prod"]) {
|
|
196
|
+
const target = createPersistentDeployTarget(scope);
|
|
197
|
+
const state = loadDeployState(tenantRoot, deployConfig, { target });
|
|
198
|
+
if (state.readiness?.initialized || scope === "local") {
|
|
199
|
+
ensureGeneratedWranglerConfig(tenantRoot, { target });
|
|
200
|
+
actions.push({ id: `wrangler-${scope}`, detail: `Regenerated the ${scope} generated Wrangler config.` });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return dedupeRepairActions(actions);
|
|
204
|
+
}
|
|
205
|
+
function bumpRootPackageJson(root, level) {
|
|
206
|
+
const packageJsonPath = resolve(root, "package.json");
|
|
207
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
208
|
+
packageJson.version = incrementVersion(String(packageJson.version ?? "0.0.0"), level);
|
|
209
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
210
|
+
`, "utf8");
|
|
211
|
+
return String(packageJson.version);
|
|
212
|
+
}
|
|
213
|
+
function createNextSteps(steps) {
|
|
214
|
+
return steps.map(renderWorkflowStep);
|
|
215
|
+
}
|
|
216
|
+
function createStatusResult(cwd) {
|
|
217
|
+
const state = resolveTreeseedWorkflowState(cwd);
|
|
218
|
+
return {
|
|
219
|
+
ok: true,
|
|
220
|
+
operation: "status",
|
|
221
|
+
payload: state,
|
|
222
|
+
nextSteps: createNextSteps(state.recommendations)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function createTasksResult(cwd) {
|
|
226
|
+
const tenantRoot = cwd;
|
|
227
|
+
const repoDir = gitWorkflowRoot(tenantRoot);
|
|
228
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
229
|
+
const dirty = gitStatusPorcelain(repoDir).length > 0;
|
|
230
|
+
const tasks = listTaskBranches(repoDir).map((branch) => {
|
|
231
|
+
const previewState = loadDeployState(tenantRoot, deployConfig, {
|
|
232
|
+
target: createBranchPreviewDeployTarget(branch.name)
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
...branch,
|
|
236
|
+
ageDays: ageDays(branch.lastCommitDate),
|
|
237
|
+
dirtyCurrent: branch.current && dirty,
|
|
238
|
+
preview: {
|
|
239
|
+
enabled: previewState.previewEnabled === true || previewState.readiness?.initialized === true,
|
|
240
|
+
url: previewState.lastDeployedUrl ?? null,
|
|
241
|
+
lastDeploymentTimestamp: previewState.lastDeploymentTimestamp ?? null
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
return { ok: true, operation: "tasks", payload: { tasks } };
|
|
246
|
+
}
|
|
247
|
+
function maybePrint(write, line, stream = "stdout") {
|
|
248
|
+
if (!line) return;
|
|
249
|
+
write(line, stream);
|
|
250
|
+
}
|
|
251
|
+
function ensureMessage(operation, message, label) {
|
|
252
|
+
const value = String(message ?? "").trim();
|
|
253
|
+
if (!value) {
|
|
254
|
+
workflowError(operation, "validation_failed", `Treeseed ${operation} requires ${label}.`);
|
|
255
|
+
}
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
function toError(operation, error) {
|
|
259
|
+
if (error instanceof TreeseedWorkflowError) {
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
if (error instanceof Error) {
|
|
263
|
+
throw new TreeseedWorkflowError(operation, "unsupported_state", error.message, {
|
|
264
|
+
details: { name: error.name },
|
|
265
|
+
exitCode: error.exitCode
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
throw new TreeseedWorkflowError(operation, "unsupported_state", String(error));
|
|
269
|
+
}
|
|
270
|
+
function previewStateFor(tenantRoot, branchName) {
|
|
271
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
272
|
+
return loadDeployState(tenantRoot, deployConfig, {
|
|
273
|
+
target: createBranchPreviewDeployTarget(branchName)
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function deployBranchPreview(tenantRoot, branchName, context, { initialize }) {
|
|
277
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "staging", override: true });
|
|
278
|
+
assertTreeseedCommandEnvironment({ tenantRoot, scope: "staging", purpose: "deploy" });
|
|
279
|
+
const target = createBranchPreviewDeployTarget(branchName);
|
|
280
|
+
const existingState = previewStateFor(tenantRoot, branchName);
|
|
281
|
+
if (initialize && !existingState.readiness?.initialized) {
|
|
282
|
+
validateDeployPrerequisites(tenantRoot, { requireRemote: true });
|
|
283
|
+
provisionCloudflareResources(tenantRoot, { target });
|
|
284
|
+
syncCloudflareSecrets(tenantRoot, { target });
|
|
285
|
+
runRemoteD1Migrations(tenantRoot, { target });
|
|
286
|
+
} else {
|
|
287
|
+
assertDeploymentInitialized(tenantRoot, { target });
|
|
288
|
+
runRemoteD1Migrations(tenantRoot, { target });
|
|
289
|
+
}
|
|
290
|
+
runTenantDeployPreflight({ cwd: tenantRoot, scope: "staging" });
|
|
291
|
+
const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
|
|
292
|
+
runNodeScript("tenant-build", context, tenantRoot, "tenant build");
|
|
293
|
+
const deployResult = spawnSync(process.execPath, [resolveWranglerBin(), "deploy", "--config", wranglerPath], {
|
|
294
|
+
cwd: tenantRoot,
|
|
295
|
+
env: { ...process.env, ...context.env ?? {} },
|
|
296
|
+
stdio: "inherit"
|
|
297
|
+
});
|
|
298
|
+
if ((deployResult.status ?? 1) !== 0) {
|
|
299
|
+
workflowError("switch", "unsupported_state", "Preview deployment failed.", {
|
|
300
|
+
exitCode: deployResult.status ?? 1,
|
|
301
|
+
details: { branchName, wranglerPath }
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const state = finalizeDeploymentState(tenantRoot, { target });
|
|
305
|
+
return {
|
|
306
|
+
initialized: existingState.readiness?.initialized !== true,
|
|
307
|
+
previewUrl: state.lastDeployedUrl ?? null,
|
|
308
|
+
lastDeploymentTimestamp: state.lastDeploymentTimestamp ?? null,
|
|
309
|
+
wranglerPath
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function destroyPreviewIfPresent(tenantRoot, branchName) {
|
|
313
|
+
const previewTarget = createBranchPreviewDeployTarget(branchName);
|
|
314
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
315
|
+
const previewState = loadDeployState(tenantRoot, deployConfig, { target: previewTarget });
|
|
316
|
+
if (previewState.readiness?.initialized) {
|
|
317
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "staging", override: true });
|
|
318
|
+
validateDestroyPrerequisites(tenantRoot, { requireRemote: true });
|
|
319
|
+
destroyCloudflareResources(tenantRoot, { target: previewTarget });
|
|
320
|
+
}
|
|
321
|
+
cleanupDestroyedState(tenantRoot, { target: previewTarget });
|
|
322
|
+
return {
|
|
323
|
+
performed: previewState.readiness?.initialized === true,
|
|
324
|
+
state: previewState
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function resolveDestroyConfirmation(context, expected, input) {
|
|
328
|
+
if (input.dryRun) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
if (input.confirm === true) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
if (typeof input.confirm === "string") {
|
|
335
|
+
return input.confirm === expected;
|
|
336
|
+
}
|
|
337
|
+
if (context.confirm) {
|
|
338
|
+
return context.confirm(
|
|
339
|
+
`Destroy Treeseed environment by confirming "${expected}"`,
|
|
340
|
+
expected
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
async function workflowStatus(helpers) {
|
|
346
|
+
return withContextEnv(helpers.context.env, () => createStatusResult(helpers.cwd()));
|
|
347
|
+
}
|
|
348
|
+
async function workflowTasks(helpers) {
|
|
349
|
+
return withContextEnv(helpers.context.env, () => createTasksResult(helpers.cwd()));
|
|
350
|
+
}
|
|
351
|
+
async function workflowConfig(helpers, input = {}) {
|
|
352
|
+
try {
|
|
353
|
+
return await withContextEnv(helpers.context.env, async () => {
|
|
354
|
+
const tenantRoot = helpers.cwd();
|
|
355
|
+
const scopes = normalizeConfigScopes(input);
|
|
356
|
+
const sync = input.syncProviders ?? input.sync ?? "all";
|
|
357
|
+
const printEnv = input.printEnv === true;
|
|
358
|
+
const revealSecrets = input.showSecrets === true;
|
|
359
|
+
const printEnvOnly = input.printEnvOnly === true;
|
|
360
|
+
const rotateMachineKeyFlag = input.rotateMachineKey === true;
|
|
361
|
+
const repairs = input.repair === false ? [] : resolveTreeseedWorkflowState(tenantRoot).deployConfigPresent ? applyTreeseedSafeRepairs(tenantRoot) : [];
|
|
362
|
+
const toolHealth = ensureTreeseedActVerificationTooling({
|
|
363
|
+
tenantRoot,
|
|
364
|
+
installIfMissing: true,
|
|
365
|
+
env: helpers.context.env,
|
|
366
|
+
write: (line) => maybePrint(helpers.write, line)
|
|
367
|
+
});
|
|
368
|
+
ensureTreeseedGitignoreEntries(tenantRoot);
|
|
369
|
+
const preflight = collectCliPreflight({ cwd: tenantRoot, requireAuth: false });
|
|
370
|
+
if (printEnvOnly) {
|
|
371
|
+
const reports = scopes.map((scope) => ({
|
|
372
|
+
scope,
|
|
373
|
+
environmentReport: formatTreeseedConfigEnvironmentReport({
|
|
374
|
+
tenantRoot,
|
|
375
|
+
scope,
|
|
376
|
+
env: helpers.context.env,
|
|
377
|
+
revealSecrets
|
|
378
|
+
}),
|
|
379
|
+
providerReport: formatTreeseedProviderConnectionReport(
|
|
380
|
+
checkTreeseedProviderConnections({ tenantRoot, scope, env: helpers.context.env })
|
|
381
|
+
)
|
|
382
|
+
}));
|
|
383
|
+
for (const report of reports) {
|
|
384
|
+
maybePrint(helpers.write, report.environmentReport);
|
|
385
|
+
maybePrint(helpers.write, report.providerReport);
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
ok: true,
|
|
389
|
+
operation: "config",
|
|
390
|
+
payload: {
|
|
391
|
+
mode: "print-env-only",
|
|
392
|
+
scopes,
|
|
393
|
+
sync,
|
|
394
|
+
secretsRevealed: revealSecrets,
|
|
395
|
+
reports,
|
|
396
|
+
repairs,
|
|
397
|
+
preflight,
|
|
398
|
+
toolHealth
|
|
399
|
+
},
|
|
400
|
+
nextSteps: createNextSteps([
|
|
401
|
+
{ operation: "config", reason: "Initialize the selected environment after reviewing the generated values.", input: { environment: scopes } }
|
|
402
|
+
])
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (rotateMachineKeyFlag) {
|
|
406
|
+
const result = rotateTreeseedMachineKey(tenantRoot);
|
|
407
|
+
return {
|
|
408
|
+
ok: true,
|
|
409
|
+
operation: "config",
|
|
410
|
+
payload: {
|
|
411
|
+
mode: "rotate-machine-key",
|
|
412
|
+
scopes,
|
|
413
|
+
sync,
|
|
414
|
+
keyPath: result.keyPath,
|
|
415
|
+
repairs,
|
|
416
|
+
preflight,
|
|
417
|
+
toolHealth
|
|
418
|
+
},
|
|
419
|
+
nextSteps: createNextSteps([
|
|
420
|
+
{ operation: "config", reason: "Inspect the regenerated local environment after the machine key rotation.", input: { environment: ["local"], printEnvOnly: true } }
|
|
421
|
+
])
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const wizardResult = await runTreeseedConfigWizard({
|
|
425
|
+
tenantRoot,
|
|
426
|
+
scopes,
|
|
427
|
+
sync,
|
|
428
|
+
authStatus: preflight.checks.auth,
|
|
429
|
+
env: helpers.context.env,
|
|
430
|
+
useInk: input.nonInteractive === true ? false : process.stdin.isTTY && process.stdout.isTTY,
|
|
431
|
+
printEnv,
|
|
432
|
+
revealSecrets,
|
|
433
|
+
write: (line) => maybePrint(helpers.write, line),
|
|
434
|
+
prompt: async (message) => {
|
|
435
|
+
if (input.nonInteractive === true) {
|
|
436
|
+
return "";
|
|
437
|
+
}
|
|
438
|
+
return String(await (helpers.context.prompt?.(message) ?? ""));
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
writeTreeseedLocalEnvironmentFiles(tenantRoot);
|
|
442
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "local", override: true });
|
|
443
|
+
const { configPath, keyPath } = getTreeseedMachineConfigPaths(tenantRoot);
|
|
444
|
+
const state = resolveTreeseedWorkflowState(tenantRoot);
|
|
445
|
+
return {
|
|
446
|
+
ok: true,
|
|
447
|
+
operation: "config",
|
|
448
|
+
payload: {
|
|
449
|
+
mode: "configure",
|
|
450
|
+
scopes,
|
|
451
|
+
sync,
|
|
452
|
+
configPath,
|
|
453
|
+
keyPath,
|
|
454
|
+
repairs,
|
|
455
|
+
preflight,
|
|
456
|
+
toolHealth,
|
|
457
|
+
result: wizardResult,
|
|
458
|
+
state
|
|
459
|
+
},
|
|
460
|
+
nextSteps: createNextSteps([
|
|
461
|
+
...scopes.includes("local") ? [{ operation: "dev", reason: "Start the local Treeseed runtime on the initialized local environment." }] : [],
|
|
462
|
+
...scopes.includes("staging") ? [{ operation: "status", reason: "Confirm staging readiness after initializing shared services." }] : [],
|
|
463
|
+
{ operation: "switch", reason: "Create or resume a task branch once the runtime foundation is ready.", input: { branch: "feature/my-change", preview: true } }
|
|
464
|
+
])
|
|
465
|
+
};
|
|
466
|
+
});
|
|
467
|
+
} catch (error) {
|
|
468
|
+
toError("config", error);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async function workflowSwitch(helpers, input) {
|
|
472
|
+
try {
|
|
473
|
+
return withContextEnv(helpers.context.env, () => {
|
|
474
|
+
const tenantRoot = helpers.cwd();
|
|
475
|
+
const branchName = String(input.branch ?? input.branchName ?? "").trim();
|
|
476
|
+
if (!branchName) {
|
|
477
|
+
workflowError("switch", "validation_failed", "Treeseed switch requires a branch name.");
|
|
478
|
+
}
|
|
479
|
+
const preview = input.preview === true;
|
|
480
|
+
const repoDir = gitWorkflowRoot(tenantRoot);
|
|
481
|
+
const currentBranchName = currentManagedBranch(tenantRoot);
|
|
482
|
+
let created = false;
|
|
483
|
+
let resumed = false;
|
|
484
|
+
let previewResult = null;
|
|
485
|
+
if (currentBranchName === branchName) {
|
|
486
|
+
resumed = true;
|
|
487
|
+
} else if (!branchExists(repoDir, branchName) && !remoteBranchExists(repoDir, branchName)) {
|
|
488
|
+
if (input.createIfMissing === false) {
|
|
489
|
+
workflowError("switch", "validation_failed", `Branch "${branchName}" does not exist locally or on origin.`);
|
|
490
|
+
}
|
|
491
|
+
const result = createFeatureBranchFromStaging(tenantRoot, branchName);
|
|
492
|
+
pushBranch(result.repoDir, branchName, { setUpstream: true });
|
|
493
|
+
created = true;
|
|
494
|
+
} else {
|
|
495
|
+
assertCleanWorktree(tenantRoot);
|
|
496
|
+
ensureLocalBranchTracking(repoDir, branchName);
|
|
497
|
+
checkoutBranch(repoDir, branchName);
|
|
498
|
+
syncBranchWithOrigin(repoDir, branchName);
|
|
499
|
+
resumed = true;
|
|
500
|
+
}
|
|
501
|
+
const stateAfterSwitch = resolveTreeseedWorkflowState(tenantRoot);
|
|
502
|
+
if (preview && !stateAfterSwitch.preview.enabled) {
|
|
503
|
+
previewResult = deployBranchPreview(tenantRoot, branchName, helpers.context, { initialize: true });
|
|
504
|
+
}
|
|
505
|
+
const state = resolveTreeseedWorkflowState(tenantRoot);
|
|
506
|
+
return {
|
|
507
|
+
ok: true,
|
|
508
|
+
operation: "switch",
|
|
509
|
+
payload: {
|
|
510
|
+
branchName,
|
|
511
|
+
created,
|
|
512
|
+
resumed,
|
|
513
|
+
previewRequested: preview,
|
|
514
|
+
preview: {
|
|
515
|
+
enabled: state.preview.enabled,
|
|
516
|
+
url: state.preview.url,
|
|
517
|
+
lastDeploymentTimestamp: state.preview.lastDeploymentTimestamp
|
|
518
|
+
},
|
|
519
|
+
previewResult,
|
|
520
|
+
state
|
|
521
|
+
},
|
|
522
|
+
nextSteps: createNextSteps([
|
|
523
|
+
state.preview.enabled ? { operation: "save", reason: "Persist and verify the current task branch, then refresh its preview deployment.", input: { message: "describe your change" } } : { operation: "dev", reason: "Start the local development environment for this task branch." },
|
|
524
|
+
{ operation: "stage", reason: "Merge the task into staging once the task branch is verified.", input: { message: "describe the resolution" } }
|
|
525
|
+
])
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
} catch (error) {
|
|
529
|
+
toError("switch", error);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function workflowDev(helpers, input = {}) {
|
|
533
|
+
try {
|
|
534
|
+
return withContextEnv(helpers.context.env, async () => {
|
|
535
|
+
if (helpers.context.transport === "api") {
|
|
536
|
+
workflowError("dev", "unsupported_transport", "Treeseed dev is not supported over the HTTP workflow API.");
|
|
537
|
+
}
|
|
538
|
+
const tenantRoot = helpers.cwd();
|
|
539
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "local", override: true });
|
|
540
|
+
assertTreeseedCommandEnvironment({ tenantRoot, scope: "local", purpose: "dev" });
|
|
541
|
+
const args = [packageScriptPath("tenant-dev")];
|
|
542
|
+
if (input.watch) {
|
|
543
|
+
args.push("--watch");
|
|
544
|
+
}
|
|
545
|
+
if (input.port !== void 0) {
|
|
546
|
+
args.push("--port", String(input.port));
|
|
547
|
+
}
|
|
548
|
+
const env = { ...process.env, ...helpers.context.env ?? {} };
|
|
549
|
+
if (input.background) {
|
|
550
|
+
const child = spawn(process.execPath, args, {
|
|
551
|
+
cwd: tenantRoot,
|
|
552
|
+
env,
|
|
553
|
+
stdio: input.stdio ?? "inherit",
|
|
554
|
+
detached: process.platform !== "win32"
|
|
555
|
+
});
|
|
556
|
+
return {
|
|
557
|
+
ok: true,
|
|
558
|
+
operation: "dev",
|
|
559
|
+
payload: {
|
|
560
|
+
watch: input.watch === true,
|
|
561
|
+
background: true,
|
|
562
|
+
command: process.execPath,
|
|
563
|
+
args,
|
|
564
|
+
cwd: tenantRoot,
|
|
565
|
+
pid: child.pid ?? null,
|
|
566
|
+
exitCode: null
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const result = spawnSync(process.execPath, args, {
|
|
571
|
+
cwd: tenantRoot,
|
|
572
|
+
env,
|
|
573
|
+
stdio: input.stdio ?? "inherit"
|
|
574
|
+
});
|
|
575
|
+
return {
|
|
576
|
+
ok: (result.status ?? 1) === 0,
|
|
577
|
+
operation: "dev",
|
|
578
|
+
payload: {
|
|
579
|
+
watch: input.watch === true,
|
|
580
|
+
background: false,
|
|
581
|
+
command: process.execPath,
|
|
582
|
+
args,
|
|
583
|
+
cwd: tenantRoot,
|
|
584
|
+
pid: null,
|
|
585
|
+
exitCode: result.status ?? 1
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
});
|
|
589
|
+
} catch (error) {
|
|
590
|
+
toError("dev", error);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async function workflowSave(helpers, input) {
|
|
594
|
+
try {
|
|
595
|
+
return withContextEnv(helpers.context.env, () => {
|
|
596
|
+
const tenantRoot = helpers.cwd();
|
|
597
|
+
const message = ensureMessage("save", input.message, "a commit message");
|
|
598
|
+
const optionsHotfix = input.hotfix === true;
|
|
599
|
+
const root = workspaceRoot(tenantRoot);
|
|
600
|
+
const gitRoot = repoRoot(root);
|
|
601
|
+
const branch = currentBranch(gitRoot);
|
|
602
|
+
const scope = branch === STAGING_BRANCH ? "staging" : branch === PRODUCTION_BRANCH ? "prod" : "local";
|
|
603
|
+
const beforeState = resolveTreeseedWorkflowState(root);
|
|
604
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope, override: true });
|
|
605
|
+
if (!branch) {
|
|
606
|
+
workflowError("save", "validation_failed", "Treeseed save requires an active git branch.");
|
|
607
|
+
}
|
|
608
|
+
if (branch === PRODUCTION_BRANCH && !optionsHotfix) {
|
|
609
|
+
workflowError("save", "unsupported_state", "Treeseed save is blocked on main unless --hotfix is explicitly set.");
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
originRemoteUrl(gitRoot);
|
|
613
|
+
} catch {
|
|
614
|
+
workflowError("save", "validation_failed", "Treeseed save requires an origin remote.");
|
|
615
|
+
}
|
|
616
|
+
if (input.verify !== false) {
|
|
617
|
+
runWorkspaceSavePreflight({ cwd: root });
|
|
618
|
+
}
|
|
619
|
+
if (!hasMeaningfulChanges(gitRoot)) {
|
|
620
|
+
workflowError("save", "validation_failed", "Treeseed save found no meaningful repository changes to commit.");
|
|
621
|
+
}
|
|
622
|
+
run("git", ["add", "-A"], { cwd: gitRoot });
|
|
623
|
+
run("git", ["commit", "-m", message], { cwd: gitRoot });
|
|
624
|
+
const head = run("git", ["rev-parse", "HEAD"], { cwd: gitRoot, capture: true }).trim();
|
|
625
|
+
try {
|
|
626
|
+
if (remoteBranchExists(gitRoot, branch)) {
|
|
627
|
+
run("git", ["pull", "--rebase", "origin", branch], { cwd: gitRoot });
|
|
628
|
+
run("git", ["push", "origin", branch], { cwd: gitRoot });
|
|
629
|
+
} else {
|
|
630
|
+
run("git", ["push", "-u", "origin", branch], { cwd: gitRoot });
|
|
631
|
+
}
|
|
632
|
+
} catch {
|
|
633
|
+
const report = collectMergeConflictReport(gitRoot);
|
|
634
|
+
throw new TreeseedWorkflowError("save", "merge_conflict", formatMergeConflictReport(report, gitRoot, branch), {
|
|
635
|
+
details: { branch, report },
|
|
636
|
+
exitCode: 12
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
let previewRefresh = null;
|
|
640
|
+
if (input.refreshPreview !== false && beforeState.branchRole === "feature" && beforeState.preview.enabled && branch) {
|
|
641
|
+
previewRefresh = deployBranchPreview(root, branch, helpers.context, { initialize: false });
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
ok: true,
|
|
645
|
+
operation: "save",
|
|
646
|
+
payload: {
|
|
647
|
+
branch,
|
|
648
|
+
scope,
|
|
649
|
+
hotfix: optionsHotfix,
|
|
650
|
+
message,
|
|
651
|
+
commitSha: head,
|
|
652
|
+
previewRefresh
|
|
653
|
+
},
|
|
654
|
+
nextSteps: createNextSteps([
|
|
655
|
+
branch === STAGING_BRANCH ? { operation: "release", reason: "Promote the validated staging branch into production.", input: { bump: "patch" } } : branch === PRODUCTION_BRANCH ? { operation: "status", reason: "Inspect production state after the explicit hotfix save." } : { operation: "stage", reason: "Merge the verified task branch into staging.", input: { message: "describe the resolution" } }
|
|
656
|
+
])
|
|
657
|
+
};
|
|
658
|
+
});
|
|
659
|
+
} catch (error) {
|
|
660
|
+
toError("save", error);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async function workflowClose(helpers, input) {
|
|
664
|
+
try {
|
|
665
|
+
return withContextEnv(helpers.context.env, () => {
|
|
666
|
+
const tenantRoot = helpers.cwd();
|
|
667
|
+
const message = ensureMessage("close", input.message, "a close reason");
|
|
668
|
+
const featureBranch = assertFeatureBranch(tenantRoot);
|
|
669
|
+
const repoDir = gitWorkflowRoot(tenantRoot);
|
|
670
|
+
assertCleanWorktree(tenantRoot);
|
|
671
|
+
const previewCleanup = input.deletePreview === false ? { performed: false } : destroyPreviewIfPresent(tenantRoot, featureBranch);
|
|
672
|
+
const deprecatedTag = createDeprecatedTaskTag(repoDir, featureBranch, `close: ${message}`);
|
|
673
|
+
const remoteDeleted = input.deleteBranch === false ? false : deleteRemoteBranch(repoDir, featureBranch);
|
|
674
|
+
syncBranchWithOrigin(repoDir, STAGING_BRANCH);
|
|
675
|
+
if (input.deleteBranch !== false) {
|
|
676
|
+
deleteLocalBranch(repoDir, featureBranch);
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
ok: true,
|
|
680
|
+
operation: "close",
|
|
681
|
+
payload: {
|
|
682
|
+
branchName: featureBranch,
|
|
683
|
+
message,
|
|
684
|
+
deprecatedTag,
|
|
685
|
+
previewCleanup,
|
|
686
|
+
remoteDeleted,
|
|
687
|
+
localDeleted: input.deleteBranch !== false
|
|
688
|
+
},
|
|
689
|
+
nextSteps: createNextSteps([
|
|
690
|
+
{ operation: "tasks", reason: "Inspect the remaining task branches after closing this one." }
|
|
691
|
+
])
|
|
692
|
+
};
|
|
693
|
+
});
|
|
694
|
+
} catch (error) {
|
|
695
|
+
toError("close", error);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
async function workflowStage(helpers, input) {
|
|
699
|
+
try {
|
|
700
|
+
return withContextEnv(helpers.context.env, () => {
|
|
701
|
+
const tenantRoot = helpers.cwd();
|
|
702
|
+
const message = ensureMessage("stage", input.message, "a resolution message");
|
|
703
|
+
const featureBranch = assertFeatureBranch(tenantRoot);
|
|
704
|
+
runWorkspaceSavePreflight({ cwd: tenantRoot });
|
|
705
|
+
const repoDir = mergeCurrentBranchIntoStaging(tenantRoot, featureBranch);
|
|
706
|
+
const stagingWait = input.waitForStaging === false ? { status: "skipped", reason: "disabled" } : waitForStagingAutomation(repoDir);
|
|
707
|
+
const previewCleanup = input.deletePreview === false ? { performed: false } : destroyPreviewIfPresent(tenantRoot, featureBranch);
|
|
708
|
+
const deprecatedTag = createDeprecatedTaskTag(repoDir, featureBranch, `stage: ${message}`);
|
|
709
|
+
const remoteDeleted = input.deleteBranch === false ? false : deleteRemoteBranch(repoDir, featureBranch);
|
|
710
|
+
if (input.deleteBranch !== false) {
|
|
711
|
+
deleteLocalBranch(repoDir, featureBranch);
|
|
712
|
+
}
|
|
713
|
+
return {
|
|
714
|
+
ok: true,
|
|
715
|
+
operation: "stage",
|
|
716
|
+
payload: {
|
|
717
|
+
branchName: featureBranch,
|
|
718
|
+
mergeTarget: STAGING_BRANCH,
|
|
719
|
+
message,
|
|
720
|
+
deprecatedTag,
|
|
721
|
+
stagingWait,
|
|
722
|
+
previewCleanup,
|
|
723
|
+
remoteDeleted,
|
|
724
|
+
localDeleted: input.deleteBranch !== false
|
|
725
|
+
},
|
|
726
|
+
nextSteps: createNextSteps([
|
|
727
|
+
{ operation: "release", reason: "Promote the updated staging branch into production when ready.", input: { bump: "patch" } },
|
|
728
|
+
{ operation: "status", reason: "Inspect staging readiness after the task branch merge." }
|
|
729
|
+
])
|
|
730
|
+
};
|
|
731
|
+
});
|
|
732
|
+
} catch (error) {
|
|
733
|
+
toError("stage", error);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
async function workflowRelease(helpers, input) {
|
|
737
|
+
try {
|
|
738
|
+
return withContextEnv(helpers.context.env, () => {
|
|
739
|
+
const level = input.bump ?? "patch";
|
|
740
|
+
const root = workspaceRoot(helpers.cwd());
|
|
741
|
+
const gitRoot = repoRoot(root);
|
|
742
|
+
prepareReleaseBranches(root);
|
|
743
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging", override: true });
|
|
744
|
+
runWorkspaceSavePreflight({ cwd: root });
|
|
745
|
+
const plan = planWorkspaceReleaseBump(level, root);
|
|
746
|
+
applyWorkspaceVersionChanges(plan);
|
|
747
|
+
const rootVersion = bumpRootPackageJson(root, level);
|
|
748
|
+
run("git", ["checkout", STAGING_BRANCH], { cwd: gitRoot });
|
|
749
|
+
run("git", ["add", "-A"], { cwd: gitRoot });
|
|
750
|
+
run("git", ["commit", "-m", `release: ${level} bump`], { cwd: gitRoot });
|
|
751
|
+
pushBranch(gitRoot, STAGING_BRANCH);
|
|
752
|
+
mergeStagingIntoMain(root);
|
|
753
|
+
run("git", ["tag", "-a", rootVersion, "-m", `release: ${rootVersion}`], { cwd: gitRoot });
|
|
754
|
+
run("git", ["push", "origin", rootVersion], { cwd: gitRoot });
|
|
755
|
+
return {
|
|
756
|
+
ok: true,
|
|
757
|
+
operation: "release",
|
|
758
|
+
payload: {
|
|
759
|
+
level,
|
|
760
|
+
rootVersion,
|
|
761
|
+
releaseTag: rootVersion,
|
|
762
|
+
stagingBranch: STAGING_BRANCH,
|
|
763
|
+
productionBranch: PRODUCTION_BRANCH,
|
|
764
|
+
touchedPackages: [...plan.touched]
|
|
765
|
+
},
|
|
766
|
+
nextSteps: createNextSteps([
|
|
767
|
+
{ operation: "status", reason: "Inspect release readiness and production state after the promotion." }
|
|
768
|
+
])
|
|
769
|
+
};
|
|
770
|
+
});
|
|
771
|
+
} catch (error) {
|
|
772
|
+
toError("release", error);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
async function workflowDestroy(helpers, input) {
|
|
776
|
+
try {
|
|
777
|
+
return withContextEnv(helpers.context.env, async () => {
|
|
778
|
+
const tenantRoot = helpers.cwd();
|
|
779
|
+
const scope = String(input.environment ?? input.target ?? "");
|
|
780
|
+
if (!scope) {
|
|
781
|
+
workflowError("destroy", "validation_failed", "Treeseed destroy requires an environment target.");
|
|
782
|
+
}
|
|
783
|
+
const target = createPersistentDeployTarget(scope);
|
|
784
|
+
const dryRun = input.dryRun === true;
|
|
785
|
+
const force = input.force === true;
|
|
786
|
+
const destroyRemote = input.destroyRemote !== false;
|
|
787
|
+
const destroyLocal = input.destroyLocal !== false;
|
|
788
|
+
const removeBuildArtifacts = input.removeBuildArtifacts === true;
|
|
789
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot, scope, override: true });
|
|
790
|
+
assertTreeseedCommandEnvironment({ tenantRoot, scope, purpose: "destroy" });
|
|
791
|
+
const deployConfig = validateDestroyPrerequisites(tenantRoot, { requireRemote: !dryRun && destroyRemote });
|
|
792
|
+
const state = loadDeployState(tenantRoot, deployConfig, { target });
|
|
793
|
+
const expectedConfirmation = deployConfig.slug;
|
|
794
|
+
const confirmed = await Promise.resolve(resolveDestroyConfirmation(helpers.context, expectedConfirmation, input));
|
|
795
|
+
if (!confirmed) {
|
|
796
|
+
workflowError("destroy", "confirmation_required", `Destroy confirmation required. Re-run with confirm="${expectedConfirmation}".`);
|
|
797
|
+
}
|
|
798
|
+
const result = destroyRemote ? destroyCloudflareResources(tenantRoot, { dryRun, force, target }) : null;
|
|
799
|
+
if (!dryRun && destroyLocal) {
|
|
800
|
+
cleanupDestroyedState(tenantRoot, { target, removeBuildArtifacts });
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
ok: true,
|
|
804
|
+
operation: "destroy",
|
|
805
|
+
payload: {
|
|
806
|
+
scope,
|
|
807
|
+
dryRun,
|
|
808
|
+
force,
|
|
809
|
+
destroyRemote,
|
|
810
|
+
destroyLocal,
|
|
811
|
+
removeBuildArtifacts,
|
|
812
|
+
expectedConfirmation,
|
|
813
|
+
stateSummary: {
|
|
814
|
+
workerName: state.workerName,
|
|
815
|
+
lastDeploymentTimestamp: state.lastDeploymentTimestamp ?? null
|
|
816
|
+
},
|
|
817
|
+
remoteResult: result
|
|
818
|
+
},
|
|
819
|
+
nextSteps: createNextSteps([
|
|
820
|
+
{ operation: "config", reason: "Recreate the destroyed environment before using it again.", input: { environment: [scope] } },
|
|
821
|
+
{ operation: "status", reason: "Confirm the environment teardown state and any remaining local runtime setup." }
|
|
822
|
+
])
|
|
823
|
+
};
|
|
824
|
+
});
|
|
825
|
+
} catch (error) {
|
|
826
|
+
toError("destroy", error);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
export {
|
|
830
|
+
TreeseedWorkflowError,
|
|
831
|
+
workflowClose,
|
|
832
|
+
workflowConfig,
|
|
833
|
+
workflowDestroy,
|
|
834
|
+
workflowDev,
|
|
835
|
+
workflowRelease,
|
|
836
|
+
workflowSave,
|
|
837
|
+
workflowStage,
|
|
838
|
+
workflowStatus,
|
|
839
|
+
workflowSwitch,
|
|
840
|
+
workflowTasks
|
|
841
|
+
};
|