@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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { applyTreeseedEnvironmentToProcess } from "../../scripts/config-runtime-lib.js";
|
|
4
|
+
import { PRODUCTION_BRANCH, STAGING_BRANCH, mergeStagingIntoMain, prepareReleaseBranches, pushBranch } from "../../scripts/git-workflow-lib.js";
|
|
5
|
+
import { applyWorkspaceVersionChanges, incrementVersion, planWorkspaceReleaseBump, repoRoot } from "../../scripts/workspace-save-lib.js";
|
|
6
|
+
import { run, workspaceRoot } from "../../scripts/workspace-tools.js";
|
|
7
|
+
import { runWorkspaceSavePreflight } from "../../scripts/save-deploy-preflight-lib.js";
|
|
8
|
+
import { guidedResult } from "./utils.js";
|
|
9
|
+
function bumpRootPackageJson(root, level) {
|
|
10
|
+
const packageJsonPath = resolve(root, "package.json");
|
|
11
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
12
|
+
packageJson.version = incrementVersion(packageJson.version, level);
|
|
13
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
14
|
+
`, "utf8");
|
|
15
|
+
return packageJson.version;
|
|
16
|
+
}
|
|
17
|
+
const handleRelease = (invocation, context) => {
|
|
18
|
+
const commandName = invocation.commandName || "release";
|
|
19
|
+
const level = ["major", "minor", "patch"].find((candidate) => invocation.args[candidate] === true);
|
|
20
|
+
const root = workspaceRoot();
|
|
21
|
+
const gitRoot = repoRoot(root);
|
|
22
|
+
prepareReleaseBranches(root);
|
|
23
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope: "staging" });
|
|
24
|
+
runWorkspaceSavePreflight({ cwd: root });
|
|
25
|
+
const plan = planWorkspaceReleaseBump(level, root);
|
|
26
|
+
applyWorkspaceVersionChanges(plan);
|
|
27
|
+
const rootVersion = bumpRootPackageJson(root, level);
|
|
28
|
+
run("git", ["checkout", STAGING_BRANCH], { cwd: gitRoot });
|
|
29
|
+
run("git", ["add", "-A"], { cwd: gitRoot });
|
|
30
|
+
run("git", ["commit", "-m", `release: ${level} bump`], { cwd: gitRoot });
|
|
31
|
+
pushBranch(gitRoot, STAGING_BRANCH);
|
|
32
|
+
mergeStagingIntoMain(root);
|
|
33
|
+
return {
|
|
34
|
+
...guidedResult({
|
|
35
|
+
command: commandName,
|
|
36
|
+
summary: `Treeseed ${commandName} completed successfully.`,
|
|
37
|
+
facts: [
|
|
38
|
+
{ label: "Staging branch", value: STAGING_BRANCH },
|
|
39
|
+
{ label: "Production branch", value: PRODUCTION_BRANCH },
|
|
40
|
+
{ label: "Release level", value: level ?? "(unknown)" },
|
|
41
|
+
{ label: "Root version", value: rootVersion },
|
|
42
|
+
{ label: "Updated packages", value: plan.touched.size }
|
|
43
|
+
],
|
|
44
|
+
nextSteps: [
|
|
45
|
+
"Monitor CI for the production deployment triggered from main.",
|
|
46
|
+
"treeseed status"
|
|
47
|
+
],
|
|
48
|
+
report: {
|
|
49
|
+
level,
|
|
50
|
+
rootVersion,
|
|
51
|
+
stagingBranch: STAGING_BRANCH,
|
|
52
|
+
productionBranch: PRODUCTION_BRANCH,
|
|
53
|
+
touchedPackages: [...plan.touched]
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export {
|
|
59
|
+
handleRelease
|
|
60
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, symlinkSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, relative, resolve } from "node:path";
|
|
4
|
+
import { applyTreeseedEnvironmentToProcess } from "../../scripts/config-runtime-lib.js";
|
|
5
|
+
import {
|
|
6
|
+
createPersistentDeployTarget,
|
|
7
|
+
deployTargetLabel,
|
|
8
|
+
ensureGeneratedWranglerConfig,
|
|
9
|
+
finalizeDeploymentState,
|
|
10
|
+
loadDeployState
|
|
11
|
+
} from "../../scripts/deploy-lib.js";
|
|
12
|
+
import { loadCliDeployConfig, packageScriptPath, resolveWranglerBin } from "../../scripts/package-tools.js";
|
|
13
|
+
import { run } from "../../scripts/workspace-tools.js";
|
|
14
|
+
import { repoRoot } from "../../scripts/workspace-save-lib.js";
|
|
15
|
+
import { guidedResult } from "./utils.js";
|
|
16
|
+
import { copyTreeseedOperationalState } from "../repair.js";
|
|
17
|
+
function selectRollbackCommit(state, requested) {
|
|
18
|
+
const history = Array.isArray(state.deploymentHistory) ? state.deploymentHistory : [];
|
|
19
|
+
if (requested) return requested;
|
|
20
|
+
const latestCommit = typeof state.lastDeployedCommit === "string" ? state.lastDeployedCommit : null;
|
|
21
|
+
const previous = [...history].reverse().find((entry) => typeof entry.commit === "string" && entry.commit !== latestCommit);
|
|
22
|
+
return typeof previous?.commit === "string" ? previous.commit : latestCommit;
|
|
23
|
+
}
|
|
24
|
+
function selectRollbackEntry(state, requested) {
|
|
25
|
+
const history = Array.isArray(state.deploymentHistory) ? state.deploymentHistory : [];
|
|
26
|
+
if (requested) {
|
|
27
|
+
return history.find((entry) => entry.commit === requested) ?? null;
|
|
28
|
+
}
|
|
29
|
+
const latestCommit = typeof state.lastDeployedCommit === "string" ? state.lastDeployedCommit : null;
|
|
30
|
+
return [...history].reverse().find((entry) => typeof entry.commit === "string" && entry.commit !== latestCommit) ?? null;
|
|
31
|
+
}
|
|
32
|
+
function isRollbackCompatible(state, candidate) {
|
|
33
|
+
if (!candidate) {
|
|
34
|
+
return { ok: true, reason: null };
|
|
35
|
+
}
|
|
36
|
+
const current = state.runtimeCompatibility;
|
|
37
|
+
const currentGeneration = typeof current?.envelopeSchemaGeneration === "string" ? current.envelopeSchemaGeneration : null;
|
|
38
|
+
const currentWave = typeof current?.migrationWaveId === "string" ? current.migrationWaveId : null;
|
|
39
|
+
const candidateGeneration = typeof candidate.envelopeSchemaGeneration === "string" ? candidate.envelopeSchemaGeneration : null;
|
|
40
|
+
const candidateWave = typeof candidate.migrationWaveId === "string" ? candidate.migrationWaveId : null;
|
|
41
|
+
if (currentGeneration && candidateGeneration && currentGeneration !== candidateGeneration) {
|
|
42
|
+
return { ok: false, reason: `Rollback target uses envelope generation ${candidateGeneration} but the current environment is on ${currentGeneration}.` };
|
|
43
|
+
}
|
|
44
|
+
if (currentWave && candidateWave && currentWave !== candidateWave) {
|
|
45
|
+
return { ok: false, reason: `Rollback target was deployed before migration wave ${currentWave} and cannot be safely reused without a forward-compatible data bridge.` };
|
|
46
|
+
}
|
|
47
|
+
return { ok: true, reason: null };
|
|
48
|
+
}
|
|
49
|
+
function runGitWorktree(repoDir, args) {
|
|
50
|
+
return run("git", ["worktree", ...args], { cwd: repoDir, capture: true });
|
|
51
|
+
}
|
|
52
|
+
const handleRollback = (invocation, context) => {
|
|
53
|
+
const scope = invocation.positionals[0];
|
|
54
|
+
if (scope !== "staging" && scope !== "prod") {
|
|
55
|
+
return guidedResult({
|
|
56
|
+
command: "rollback",
|
|
57
|
+
summary: "Treeseed rollback requires an explicit persistent target.",
|
|
58
|
+
facts: [{ label: "Provided target", value: scope ?? "(none)" }],
|
|
59
|
+
nextSteps: ["treeseed rollback staging", "treeseed rollback prod"],
|
|
60
|
+
report: { scope: scope ?? null },
|
|
61
|
+
exitCode: 1
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const requestedCommit = typeof invocation.args.to === "string" ? invocation.args.to : null;
|
|
65
|
+
const tenantRoot = context.cwd;
|
|
66
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
67
|
+
const target = createPersistentDeployTarget(scope);
|
|
68
|
+
const state = loadDeployState(tenantRoot, deployConfig, { target });
|
|
69
|
+
const rollbackEntry = selectRollbackEntry(state, requestedCommit);
|
|
70
|
+
const rollbackCommit = selectRollbackCommit(state, requestedCommit);
|
|
71
|
+
if (!rollbackCommit) {
|
|
72
|
+
return guidedResult({
|
|
73
|
+
command: "rollback",
|
|
74
|
+
summary: `No rollback candidate is recorded for ${scope}.`,
|
|
75
|
+
facts: [{ label: "Target", value: scope }],
|
|
76
|
+
nextSteps: ["treeseed status"],
|
|
77
|
+
report: { scope, rollbackCommit: null, deploymentHistory: state.deploymentHistory ?? [] },
|
|
78
|
+
exitCode: 1
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const compatibility = isRollbackCompatible(state, rollbackEntry);
|
|
82
|
+
if (!compatibility.ok) {
|
|
83
|
+
return guidedResult({
|
|
84
|
+
command: "rollback",
|
|
85
|
+
summary: `Treeseed rollback refused for ${scope}.`,
|
|
86
|
+
facts: [
|
|
87
|
+
{ label: "Target", value: scope },
|
|
88
|
+
{ label: "Requested commit", value: rollbackCommit },
|
|
89
|
+
{ label: "Reason", value: compatibility.reason ?? "Unknown compatibility boundary" }
|
|
90
|
+
],
|
|
91
|
+
nextSteps: ["treeseed status", `treeseed publish --environment ${scope}`],
|
|
92
|
+
report: { scope, rollbackCommit, compatibility, deploymentHistory: state.deploymentHistory ?? [] },
|
|
93
|
+
exitCode: 1
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const gitRoot = repoRoot(tenantRoot);
|
|
97
|
+
const tenantRelativePath = relative(gitRoot, tenantRoot);
|
|
98
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "treeseed-rollback-"));
|
|
99
|
+
const tempTenantRoot = resolve(tempRoot, tenantRelativePath);
|
|
100
|
+
const currentNodeModules = resolve(tenantRoot, "node_modules");
|
|
101
|
+
let finalizedState = null;
|
|
102
|
+
try {
|
|
103
|
+
runGitWorktree(gitRoot, ["add", "--detach", tempRoot, rollbackCommit]);
|
|
104
|
+
copyTreeseedOperationalState(tenantRoot, tempTenantRoot);
|
|
105
|
+
if (existsSync(currentNodeModules) && !existsSync(resolve(tempTenantRoot, "node_modules"))) {
|
|
106
|
+
symlinkSync(currentNodeModules, resolve(tempTenantRoot, "node_modules"), "dir");
|
|
107
|
+
}
|
|
108
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot, scope });
|
|
109
|
+
const { wranglerPath } = ensureGeneratedWranglerConfig(tempTenantRoot, { target });
|
|
110
|
+
const buildResult = context.spawn(process.execPath, [packageScriptPath("tenant-build")], {
|
|
111
|
+
cwd: tempTenantRoot,
|
|
112
|
+
env: { ...context.env },
|
|
113
|
+
stdio: "inherit"
|
|
114
|
+
});
|
|
115
|
+
if ((buildResult.status ?? 1) !== 0) {
|
|
116
|
+
return { exitCode: buildResult.status ?? 1 };
|
|
117
|
+
}
|
|
118
|
+
const publishResult = context.spawn(process.execPath, [resolveWranglerBin(), "deploy", "--config", wranglerPath], {
|
|
119
|
+
cwd: tempTenantRoot,
|
|
120
|
+
env: { ...context.env },
|
|
121
|
+
stdio: "inherit"
|
|
122
|
+
});
|
|
123
|
+
if ((publishResult.status ?? 1) !== 0) {
|
|
124
|
+
return { exitCode: publishResult.status ?? 1 };
|
|
125
|
+
}
|
|
126
|
+
const previousCommit = process.env.TREESEED_DEPLOY_COMMIT;
|
|
127
|
+
process.env.TREESEED_DEPLOY_COMMIT = rollbackCommit;
|
|
128
|
+
try {
|
|
129
|
+
finalizedState = finalizeDeploymentState(tenantRoot, { target });
|
|
130
|
+
} finally {
|
|
131
|
+
if (previousCommit) {
|
|
132
|
+
process.env.TREESEED_DEPLOY_COMMIT = previousCommit;
|
|
133
|
+
} else {
|
|
134
|
+
delete process.env.TREESEED_DEPLOY_COMMIT;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} finally {
|
|
138
|
+
try {
|
|
139
|
+
runGitWorktree(gitRoot, ["remove", "--force", tempRoot]);
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return guidedResult({
|
|
144
|
+
command: "rollback",
|
|
145
|
+
summary: `Treeseed rollback completed for ${scope}.`,
|
|
146
|
+
facts: [
|
|
147
|
+
{ label: "Target", value: deployTargetLabel(target) },
|
|
148
|
+
{ label: "Rolled back commit", value: rollbackCommit },
|
|
149
|
+
{ label: "Deployed URL", value: finalizedState?.lastDeployedUrl }
|
|
150
|
+
],
|
|
151
|
+
nextSteps: ["treeseed status", `treeseed publish --environment ${scope}`],
|
|
152
|
+
report: {
|
|
153
|
+
scope,
|
|
154
|
+
rollbackCommit,
|
|
155
|
+
rollbackEntry,
|
|
156
|
+
target: deployTargetLabel(target),
|
|
157
|
+
finalizedState
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
export {
|
|
162
|
+
handleRollback
|
|
163
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { applyTreeseedEnvironmentToProcess } from "../../scripts/config-runtime-lib.js";
|
|
2
|
+
import {
|
|
3
|
+
collectMergeConflictReport,
|
|
4
|
+
currentBranch,
|
|
5
|
+
formatMergeConflictReport,
|
|
6
|
+
hasMeaningfulChanges,
|
|
7
|
+
originRemoteUrl,
|
|
8
|
+
repoRoot
|
|
9
|
+
} from "../../scripts/workspace-save-lib.js";
|
|
10
|
+
import { PRODUCTION_BRANCH, STAGING_BRANCH, remoteBranchExists } from "../../scripts/git-workflow-lib.js";
|
|
11
|
+
import { run, workspaceRoot } from "../../scripts/workspace-tools.js";
|
|
12
|
+
import { runWorkspaceSavePreflight } from "../../scripts/save-deploy-preflight-lib.js";
|
|
13
|
+
import { guidedResult } from "./utils.js";
|
|
14
|
+
const handleSave = (invocation, context) => {
|
|
15
|
+
const commandName = invocation.commandName || "save";
|
|
16
|
+
const optionsHotfix = invocation.args.hotfix === true;
|
|
17
|
+
const message = invocation.positionals.join(" ").trim();
|
|
18
|
+
const root = workspaceRoot();
|
|
19
|
+
const gitRoot = repoRoot(root);
|
|
20
|
+
const branch = currentBranch(gitRoot);
|
|
21
|
+
const scope = branch === STAGING_BRANCH ? "staging" : branch === PRODUCTION_BRANCH ? "prod" : "local";
|
|
22
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot: root, scope });
|
|
23
|
+
if (!message) {
|
|
24
|
+
return { exitCode: 1, stderr: [`Treeseed ${commandName} requires a commit message. Usage: treeseed ${commandName} <message>`] };
|
|
25
|
+
}
|
|
26
|
+
if (!branch) {
|
|
27
|
+
return { exitCode: 1, stderr: ["Treeseed save requires an active git branch."] };
|
|
28
|
+
}
|
|
29
|
+
if (branch === PRODUCTION_BRANCH && !optionsHotfix) {
|
|
30
|
+
return {
|
|
31
|
+
exitCode: 1,
|
|
32
|
+
stderr: [`Treeseed ${commandName} is blocked on main. Use \`treeseed promote\` for normal production promotion or \`treeseed ${commandName} --hotfix\` for an explicit hotfix.`]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
originRemoteUrl(gitRoot);
|
|
37
|
+
} catch {
|
|
38
|
+
return { exitCode: 1, stderr: [`Treeseed ${commandName} requires an origin remote.`] };
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
runWorkspaceSavePreflight({ cwd: root });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return { exitCode: error?.exitCode ?? 1, stderr: [error instanceof Error ? error.message : String(error)] };
|
|
44
|
+
}
|
|
45
|
+
if (!hasMeaningfulChanges(gitRoot)) {
|
|
46
|
+
return { exitCode: 1, stderr: [`Treeseed ${commandName} found no meaningful repository changes to commit.`] };
|
|
47
|
+
}
|
|
48
|
+
run("git", ["add", "-A"], { cwd: gitRoot });
|
|
49
|
+
run("git", ["commit", "-m", message], { cwd: gitRoot });
|
|
50
|
+
try {
|
|
51
|
+
if (remoteBranchExists(gitRoot, branch)) {
|
|
52
|
+
run("git", ["pull", "--rebase", "origin", branch], { cwd: gitRoot });
|
|
53
|
+
run("git", ["push", "origin", branch], { cwd: gitRoot });
|
|
54
|
+
} else {
|
|
55
|
+
run("git", ["push", "-u", "origin", branch], { cwd: gitRoot });
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
const report = collectMergeConflictReport(gitRoot);
|
|
59
|
+
return {
|
|
60
|
+
exitCode: 12,
|
|
61
|
+
stderr: [formatMergeConflictReport(report, gitRoot, branch)]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
...guidedResult({
|
|
66
|
+
command: commandName,
|
|
67
|
+
summary: `Treeseed ${commandName} completed successfully.`,
|
|
68
|
+
facts: [
|
|
69
|
+
{ label: "Branch", value: branch },
|
|
70
|
+
{ label: "Environment scope", value: scope },
|
|
71
|
+
{ label: "Hotfix", value: optionsHotfix ? "yes" : "no" }
|
|
72
|
+
],
|
|
73
|
+
nextSteps: [
|
|
74
|
+
branch === STAGING_BRANCH ? "treeseed deploy --environment staging" : branch === PRODUCTION_BRANCH ? "Monitor CI or run `treeseed deploy --environment prod` only if you intentionally need a manual production publish." : "treeseed close"
|
|
75
|
+
],
|
|
76
|
+
report: {
|
|
77
|
+
branch,
|
|
78
|
+
scope,
|
|
79
|
+
hotfix: optionsHotfix,
|
|
80
|
+
message
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export {
|
|
86
|
+
handleSave
|
|
87
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { handleConfig } from "./config.js";
|
|
2
|
+
import { handleDoctor } from "./doctor.js";
|
|
3
|
+
import { guidedResult } from "./utils.js";
|
|
4
|
+
import { applyTreeseedSafeRepairs } from "../repair.js";
|
|
5
|
+
import { resolveTreeseedWorkflowState } from "../workflow-state.js";
|
|
6
|
+
const handleSetup = async (invocation, context) => {
|
|
7
|
+
const tenantRoot = context.cwd;
|
|
8
|
+
const scopes = Array.isArray(invocation.args.environment) ? invocation.args.environment : invocation.args.environment ? [invocation.args.environment] : ["local"];
|
|
9
|
+
const repairs = resolveTreeseedWorkflowState(tenantRoot).deployConfigPresent ? applyTreeseedSafeRepairs(tenantRoot) : [];
|
|
10
|
+
const configResult = await handleConfig({
|
|
11
|
+
...invocation,
|
|
12
|
+
commandName: "config",
|
|
13
|
+
args: {
|
|
14
|
+
...invocation.args,
|
|
15
|
+
environment: scopes,
|
|
16
|
+
sync: invocation.args.sync ?? "none"
|
|
17
|
+
},
|
|
18
|
+
positionals: [],
|
|
19
|
+
rawArgs: [
|
|
20
|
+
...scopes.flatMap((scope) => ["--environment", scope]),
|
|
21
|
+
...invocation.args.json === true ? ["--json"] : []
|
|
22
|
+
]
|
|
23
|
+
}, context);
|
|
24
|
+
if ((configResult.exitCode ?? 0) !== 0) {
|
|
25
|
+
return configResult;
|
|
26
|
+
}
|
|
27
|
+
const doctorResult = handleDoctor({ ...invocation, args: { ...invocation.args } }, context);
|
|
28
|
+
const doctorPayload = doctorResult.report ?? {};
|
|
29
|
+
return guidedResult({
|
|
30
|
+
command: "setup",
|
|
31
|
+
summary: "Treeseed setup completed.",
|
|
32
|
+
facts: [
|
|
33
|
+
{ label: "Initialized environments", value: scopes.join(", ") },
|
|
34
|
+
{ label: "Safe repairs", value: repairs.length },
|
|
35
|
+
{ label: "Blocking issues remaining", value: Array.isArray(doctorPayload.mustFixNow) ? doctorPayload.mustFixNow?.length ?? 0 : 0 }
|
|
36
|
+
],
|
|
37
|
+
nextSteps: ["treeseed dev", "treeseed work feature/my-change"],
|
|
38
|
+
report: {
|
|
39
|
+
repairs,
|
|
40
|
+
scopes,
|
|
41
|
+
config: configResult.report,
|
|
42
|
+
doctor: doctorPayload
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
export {
|
|
47
|
+
handleSetup
|
|
48
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { guidedResult } from "./utils.js";
|
|
2
|
+
import { handleSave } from "./save.js";
|
|
3
|
+
import { handleDeploy } from "./deploy.js";
|
|
4
|
+
import { resolveTreeseedWorkflowState } from "../workflow-state.js";
|
|
5
|
+
const handleShip = async (invocation, context) => {
|
|
6
|
+
const beforeState = resolveTreeseedWorkflowState(context.cwd);
|
|
7
|
+
const saveResult = await handleSave({
|
|
8
|
+
...invocation,
|
|
9
|
+
commandName: "save"
|
|
10
|
+
}, context);
|
|
11
|
+
if ((saveResult.exitCode ?? 0) !== 0) {
|
|
12
|
+
return saveResult;
|
|
13
|
+
}
|
|
14
|
+
let previewRefresh = null;
|
|
15
|
+
if (beforeState.branchRole === "feature" && beforeState.preview.enabled && beforeState.branchName) {
|
|
16
|
+
const publishResult = await handleDeploy({
|
|
17
|
+
commandName: "deploy",
|
|
18
|
+
args: {
|
|
19
|
+
targetBranch: beforeState.branchName,
|
|
20
|
+
json: invocation.args.json
|
|
21
|
+
},
|
|
22
|
+
positionals: [],
|
|
23
|
+
rawArgs: ["--target-branch", beforeState.branchName, ...invocation.args.json === true ? ["--json"] : []]
|
|
24
|
+
}, context);
|
|
25
|
+
if ((publishResult.exitCode ?? 0) !== 0) {
|
|
26
|
+
return publishResult;
|
|
27
|
+
}
|
|
28
|
+
previewRefresh = publishResult.report ?? null;
|
|
29
|
+
}
|
|
30
|
+
const afterState = resolveTreeseedWorkflowState(context.cwd);
|
|
31
|
+
return guidedResult({
|
|
32
|
+
command: "ship",
|
|
33
|
+
summary: "Treeseed ship completed successfully.",
|
|
34
|
+
facts: [
|
|
35
|
+
{ label: "Branch", value: afterState.branchName ?? beforeState.branchName ?? "(none)" },
|
|
36
|
+
{ label: "Branch role", value: afterState.branchRole },
|
|
37
|
+
{ label: "Preview refreshed", value: previewRefresh ? "yes" : "no" }
|
|
38
|
+
],
|
|
39
|
+
nextSteps: afterState.recommendations.map((item) => `${item.command} # ${item.reason}`),
|
|
40
|
+
report: {
|
|
41
|
+
state: afterState,
|
|
42
|
+
save: saveResult.report,
|
|
43
|
+
previewRefresh
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
handleShip
|
|
49
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyTreeseedEnvironmentToProcess,
|
|
3
|
+
assertTreeseedCommandEnvironment
|
|
4
|
+
} from "../../scripts/config-runtime-lib.js";
|
|
5
|
+
import {
|
|
6
|
+
createBranchPreviewDeployTarget,
|
|
7
|
+
deployTargetLabel,
|
|
8
|
+
ensureGeneratedWranglerConfig,
|
|
9
|
+
finalizeDeploymentState,
|
|
10
|
+
printDeploySummary,
|
|
11
|
+
provisionCloudflareResources,
|
|
12
|
+
runRemoteD1Migrations,
|
|
13
|
+
syncCloudflareSecrets,
|
|
14
|
+
validateDeployPrerequisites
|
|
15
|
+
} from "../../scripts/deploy-lib.js";
|
|
16
|
+
import { createFeatureBranchFromStaging, pushBranch } from "../../scripts/git-workflow-lib.js";
|
|
17
|
+
import { packageScriptPath, resolveWranglerBin } from "../../scripts/package-tools.js";
|
|
18
|
+
import { guidedResult } from "./utils.js";
|
|
19
|
+
function provisionBranchPreview(branchName, context, commandName = "start") {
|
|
20
|
+
const tenantRoot = context.cwd;
|
|
21
|
+
applyTreeseedEnvironmentToProcess({ tenantRoot, scope: "staging" });
|
|
22
|
+
assertTreeseedCommandEnvironment({ tenantRoot, scope: "staging", purpose: "deploy" });
|
|
23
|
+
validateDeployPrerequisites(tenantRoot, { requireRemote: true });
|
|
24
|
+
const target = createBranchPreviewDeployTarget(branchName);
|
|
25
|
+
const summary = provisionCloudflareResources(tenantRoot, { target });
|
|
26
|
+
printDeploySummary(summary);
|
|
27
|
+
const { wranglerPath } = ensureGeneratedWranglerConfig(tenantRoot, { target });
|
|
28
|
+
syncCloudflareSecrets(tenantRoot, { target });
|
|
29
|
+
runRemoteD1Migrations(tenantRoot, { target });
|
|
30
|
+
const buildResult = context.spawn(process.execPath, [packageScriptPath("tenant-build")], {
|
|
31
|
+
cwd: tenantRoot,
|
|
32
|
+
env: { ...context.env },
|
|
33
|
+
stdio: "inherit"
|
|
34
|
+
});
|
|
35
|
+
if ((buildResult.status ?? 1) !== 0) {
|
|
36
|
+
return { exitCode: buildResult.status ?? 1 };
|
|
37
|
+
}
|
|
38
|
+
const deployResult = context.spawn(process.execPath, [resolveWranglerBin(), "deploy", "--config", wranglerPath], {
|
|
39
|
+
cwd: tenantRoot,
|
|
40
|
+
env: { ...context.env },
|
|
41
|
+
stdio: "inherit"
|
|
42
|
+
});
|
|
43
|
+
if ((deployResult.status ?? 1) !== 0) {
|
|
44
|
+
return { exitCode: deployResult.status ?? 1 };
|
|
45
|
+
}
|
|
46
|
+
const state = finalizeDeploymentState(tenantRoot, { target });
|
|
47
|
+
return guidedResult({
|
|
48
|
+
command: commandName,
|
|
49
|
+
summary: `Treeseed ${commandName} preview completed for ${branchName}.`,
|
|
50
|
+
facts: [
|
|
51
|
+
{ label: "Target", value: deployTargetLabel(target) },
|
|
52
|
+
{ label: "Preview URL", value: state.lastDeployedUrl }
|
|
53
|
+
],
|
|
54
|
+
nextSteps: [
|
|
55
|
+
'treeseed save "describe your change"',
|
|
56
|
+
`treeseed deploy --target-branch ${branchName}`
|
|
57
|
+
],
|
|
58
|
+
report: {
|
|
59
|
+
branchName,
|
|
60
|
+
preview: true,
|
|
61
|
+
target: deployTargetLabel(target),
|
|
62
|
+
previewUrl: state.lastDeployedUrl
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const handleStart = (invocation, context) => {
|
|
67
|
+
const commandName = invocation.commandName || "start";
|
|
68
|
+
const branchName = invocation.positionals[0];
|
|
69
|
+
const preview = invocation.args.preview === true;
|
|
70
|
+
const tenantRoot = context.cwd;
|
|
71
|
+
const result = createFeatureBranchFromStaging(tenantRoot, branchName);
|
|
72
|
+
pushBranch(result.repoDir, branchName, { setUpstream: true });
|
|
73
|
+
if (!preview) {
|
|
74
|
+
return guidedResult({
|
|
75
|
+
command: commandName,
|
|
76
|
+
summary: `Created feature branch ${branchName} from staging.`,
|
|
77
|
+
facts: [
|
|
78
|
+
{ label: "Branch", value: branchName },
|
|
79
|
+
{ label: "Preview", value: "disabled" }
|
|
80
|
+
],
|
|
81
|
+
nextSteps: [
|
|
82
|
+
"treeseed dev",
|
|
83
|
+
'treeseed save "describe your change"'
|
|
84
|
+
],
|
|
85
|
+
report: {
|
|
86
|
+
branchName,
|
|
87
|
+
preview: false,
|
|
88
|
+
baseBranch: result.baseBranch
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return provisionBranchPreview(branchName, context, commandName);
|
|
93
|
+
};
|
|
94
|
+
export {
|
|
95
|
+
handleStart,
|
|
96
|
+
provisionBranchPreview
|
|
97
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { guidedResult } from "./utils.js";
|
|
2
|
+
import { resolveTreeseedWorkflowState } from "../workflow-state.js";
|
|
3
|
+
const handleStatus = (_invocation, context) => {
|
|
4
|
+
const state = resolveTreeseedWorkflowState(context.cwd);
|
|
5
|
+
return guidedResult({
|
|
6
|
+
command: "status",
|
|
7
|
+
summary: "Treeseed workflow status",
|
|
8
|
+
facts: [
|
|
9
|
+
{ label: "Workspace root", value: state.workspaceRoot ? "yes" : "no" },
|
|
10
|
+
{ label: "Tenant config present", value: state.deployConfigPresent ? "yes" : "no" },
|
|
11
|
+
{ label: "Branch", value: state.branchName ?? "(none)" },
|
|
12
|
+
{ label: "Branch role", value: state.branchRole },
|
|
13
|
+
{ label: "Mapped environment", value: state.environment },
|
|
14
|
+
{ label: "Dirty worktree", value: state.dirtyWorktree ? "yes" : "no" },
|
|
15
|
+
{ label: "Local initialized", value: state.persistentEnvironments.local.initialized ? "yes" : "no" },
|
|
16
|
+
{ label: "Staging initialized", value: state.persistentEnvironments.staging.initialized ? "yes" : "no" },
|
|
17
|
+
{ label: "Prod initialized", value: state.persistentEnvironments.prod.initialized ? "yes" : "no" },
|
|
18
|
+
{ label: "Preview enabled", value: state.preview.enabled ? "yes" : "no" },
|
|
19
|
+
{ label: "Preview URL", value: state.preview.url ?? "(none)" },
|
|
20
|
+
{ label: "GitHub auth", value: state.auth.gh ? "ready" : "not ready" },
|
|
21
|
+
{ label: "Wrangler auth", value: state.auth.wrangler ? "ready" : "not ready" }
|
|
22
|
+
],
|
|
23
|
+
nextSteps: state.recommendations.map((item) => `${item.command} # ${item.reason}`),
|
|
24
|
+
report: {
|
|
25
|
+
state
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
handleStatus
|
|
31
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { currentManagedBranch, PRODUCTION_BRANCH, STAGING_BRANCH } from "../../scripts/git-workflow-lib.js";
|
|
2
|
+
import { guidedResult } from "./utils.js";
|
|
3
|
+
import { handleClose } from "./close.js";
|
|
4
|
+
import { handleDestroy } from "./destroy.js";
|
|
5
|
+
const handleTeardown = async (invocation, context) => {
|
|
6
|
+
const commandName = invocation.commandName || "teardown";
|
|
7
|
+
const environment = typeof invocation.args.environment === "string" ? invocation.args.environment : null;
|
|
8
|
+
if (environment) {
|
|
9
|
+
return handleDestroy({
|
|
10
|
+
...invocation,
|
|
11
|
+
commandName
|
|
12
|
+
}, context);
|
|
13
|
+
}
|
|
14
|
+
const branch = currentManagedBranch(context.cwd);
|
|
15
|
+
if (!branch || branch === STAGING_BRANCH || branch === PRODUCTION_BRANCH) {
|
|
16
|
+
return guidedResult({
|
|
17
|
+
command: commandName,
|
|
18
|
+
summary: "Treeseed teardown needs an explicit persistent environment on staging or main.",
|
|
19
|
+
facts: [{ label: "Current branch", value: branch ?? "(none)" }],
|
|
20
|
+
nextSteps: [
|
|
21
|
+
"treeseed teardown --environment staging",
|
|
22
|
+
"treeseed teardown --environment prod"
|
|
23
|
+
],
|
|
24
|
+
report: {
|
|
25
|
+
branch
|
|
26
|
+
},
|
|
27
|
+
exitCode: 1
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (invocation.args.dryRun === true) {
|
|
31
|
+
return guidedResult({
|
|
32
|
+
command: commandName,
|
|
33
|
+
summary: `Dry run: Treeseed would merge ${branch} into staging and remove any branch preview artifacts.`,
|
|
34
|
+
facts: [{ label: "Branch", value: branch }],
|
|
35
|
+
nextSteps: ["treeseed teardown"],
|
|
36
|
+
report: {
|
|
37
|
+
branch,
|
|
38
|
+
dryRun: true,
|
|
39
|
+
mode: "feature-close"
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return handleClose({
|
|
44
|
+
...invocation,
|
|
45
|
+
commandName
|
|
46
|
+
}, context);
|
|
47
|
+
};
|
|
48
|
+
export {
|
|
49
|
+
handleTeardown
|
|
50
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function ok(lines = []) {
|
|
2
|
+
return { exitCode: 0, stdout: lines };
|
|
3
|
+
}
|
|
4
|
+
function fail(message, exitCode = 1) {
|
|
5
|
+
return { exitCode, stderr: [message] };
|
|
6
|
+
}
|
|
7
|
+
function guidedResult(options) {
|
|
8
|
+
const lines = [options.summary];
|
|
9
|
+
const facts = (options.facts ?? []).filter((fact) => fact.value !== void 0 && fact.value !== null && `${fact.value}`.length > 0);
|
|
10
|
+
if (facts.length > 0) {
|
|
11
|
+
lines.push("");
|
|
12
|
+
for (const fact of facts) {
|
|
13
|
+
lines.push(`${fact.label}: ${fact.value}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if ((options.nextSteps ?? []).length > 0) {
|
|
17
|
+
lines.push("", "Next steps:");
|
|
18
|
+
for (const step of options.nextSteps ?? []) {
|
|
19
|
+
lines.push(`- ${step}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
exitCode: options.exitCode ?? 0,
|
|
24
|
+
stdout: lines,
|
|
25
|
+
stderr: options.stderr,
|
|
26
|
+
report: {
|
|
27
|
+
command: options.command,
|
|
28
|
+
ok: (options.exitCode ?? 0) === 0,
|
|
29
|
+
summary: options.summary,
|
|
30
|
+
facts: facts.map((fact) => ({ label: fact.label, value: fact.value })),
|
|
31
|
+
nextSteps: options.nextSteps ?? [],
|
|
32
|
+
...options.report ?? {}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function writeResult(result, context) {
|
|
37
|
+
if (context.outputFormat === "json") {
|
|
38
|
+
const payload = result.report ?? {
|
|
39
|
+
ok: (result.exitCode ?? 0) === 0,
|
|
40
|
+
stdout: result.stdout ?? [],
|
|
41
|
+
stderr: result.stderr ?? []
|
|
42
|
+
};
|
|
43
|
+
context.write(JSON.stringify(payload, null, 2), (result.exitCode ?? 0) === 0 ? "stdout" : "stderr");
|
|
44
|
+
return result.exitCode ?? 0;
|
|
45
|
+
}
|
|
46
|
+
for (const line of result.stdout ?? []) {
|
|
47
|
+
context.write(line, "stdout");
|
|
48
|
+
}
|
|
49
|
+
for (const line of result.stderr ?? []) {
|
|
50
|
+
context.write(line, "stderr");
|
|
51
|
+
}
|
|
52
|
+
return result.exitCode ?? 0;
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
fail,
|
|
56
|
+
guidedResult,
|
|
57
|
+
ok,
|
|
58
|
+
writeResult
|
|
59
|
+
};
|