@treeseed/cli 0.6.7 → 0.6.8
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/dist/cli/handlers/close.js +1 -0
- package/dist/cli/handlers/config.js +13 -1
- package/dist/cli/handlers/dev.js +9 -2
- package/dist/cli/handlers/release.js +65 -7
- package/dist/cli/handlers/save.js +62 -3
- package/dist/cli/handlers/stage.js +1 -0
- package/dist/cli/handlers/status-ui.d.ts +4 -0
- package/dist/cli/handlers/status-ui.js +162 -0
- package/dist/cli/handlers/status.js +105 -56
- package/dist/cli/handlers/switch.js +1 -0
- package/dist/cli/handlers/utils.d.ts +4 -0
- package/dist/cli/handlers/utils.js +7 -0
- package/dist/cli/handlers/workflow.js +4 -2
- package/dist/cli/handlers/workspace.d.ts +2 -0
- package/dist/cli/handlers/workspace.js +51 -0
- package/dist/cli/operations-registry.js +79 -9
- package/dist/cli/registry.d.ts +1 -0
- package/dist/cli/registry.js +5 -0
- package/dist/cli/runtime.js +18 -9
- package/package.json +3 -2
|
@@ -4,6 +4,7 @@ const handleClose = async (invocation, context) => {
|
|
|
4
4
|
try {
|
|
5
5
|
const result = await createWorkflowSdk(context).close({
|
|
6
6
|
message: invocation.positionals.join(" ").trim(),
|
|
7
|
+
workspaceLinks: typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0,
|
|
7
8
|
plan: invocation.args.plan === true || invocation.args.dryRun === true,
|
|
8
9
|
dryRun: invocation.args.dryRun === true
|
|
9
10
|
});
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
collectTreeseedConfigContext,
|
|
5
5
|
ensureTreeseedActVerificationTooling,
|
|
6
6
|
ensureTreeseedSecretSessionForConfig,
|
|
7
|
-
findNearestTreeseedRoot
|
|
7
|
+
findNearestTreeseedRoot,
|
|
8
|
+
formatTreeseedDependencyFailureDetails,
|
|
9
|
+
installTreeseedDependencies
|
|
8
10
|
} from "@treeseed/sdk/workflow-support";
|
|
9
11
|
import { fail, guidedResult } from "./utils.js";
|
|
10
12
|
import { buildCliConfigPages, runCliConfigEditor } from "./config-ui.js";
|
|
@@ -192,6 +194,16 @@ const handleConfig = async (invocation, context) => {
|
|
|
192
194
|
if (!tenantRoot) {
|
|
193
195
|
return fail("Treeseed config requires a Treeseed project. Run the command from inside a tenant or initialize one first.");
|
|
194
196
|
}
|
|
197
|
+
const dependencyInstall = await installTreeseedDependencies({
|
|
198
|
+
tenantRoot,
|
|
199
|
+
force: invocation.args.installMissingTooling === true,
|
|
200
|
+
env: context.env,
|
|
201
|
+
write: context.write
|
|
202
|
+
});
|
|
203
|
+
if (!dependencyInstall.ok) {
|
|
204
|
+
return fail(`Treeseed dependency initialization failed:
|
|
205
|
+
- ${formatTreeseedDependencyFailureDetails(dependencyInstall)}`);
|
|
206
|
+
}
|
|
195
207
|
applyTreeseedSafeRepairs(tenantRoot);
|
|
196
208
|
const toolAvailability = ensureTreeseedActVerificationTooling({
|
|
197
209
|
tenantRoot,
|
package/dist/cli/handlers/dev.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
|
-
import { resolveTreeseedLaunchEnvironment } from "@treeseed/sdk/workflow-support";
|
|
4
|
+
import { ensureLocalWorkspaceLinks, findNearestTreeseedWorkspaceRoot, resolveTreeseedLaunchEnvironment } from "@treeseed/sdk/workflow-support";
|
|
5
5
|
import { workflowErrorResult } from "./workflow.js";
|
|
6
6
|
const require2 = createRequire(import.meta.url);
|
|
7
7
|
function resolveCoreDevEntrypoint(cwd) {
|
|
@@ -43,6 +43,12 @@ function resolveCoreDevEntrypoint(cwd) {
|
|
|
43
43
|
const handleDev = async (invocation, context) => {
|
|
44
44
|
try {
|
|
45
45
|
const watch = invocation.commandName === "dev:watch" || invocation.args.watch === true;
|
|
46
|
+
const workspaceRoot = findNearestTreeseedWorkspaceRoot(context.cwd);
|
|
47
|
+
const workspaceLinksMode = typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0;
|
|
48
|
+
const workspaceLinks = workspaceRoot ? ensureLocalWorkspaceLinks(workspaceRoot, { env: context.env, mode: workspaceLinksMode }) : null;
|
|
49
|
+
if (workspaceLinks?.created.length) {
|
|
50
|
+
context.write(`[workspace][link] Linked ${workspaceLinks.created.length} local workspace package paths.`, "stdout");
|
|
51
|
+
}
|
|
46
52
|
const resolved = resolveCoreDevEntrypoint(context.cwd);
|
|
47
53
|
const args = watch ? [...resolved.args, "--watch"] : resolved.args;
|
|
48
54
|
const result = context.spawn(resolved.command, args, {
|
|
@@ -61,7 +67,8 @@ const handleDev = async (invocation, context) => {
|
|
|
61
67
|
ok: (result.status ?? 1) === 0,
|
|
62
68
|
watch,
|
|
63
69
|
executable: resolved.command,
|
|
64
|
-
args
|
|
70
|
+
args,
|
|
71
|
+
workspaceLinks
|
|
65
72
|
}
|
|
66
73
|
};
|
|
67
74
|
} catch (error) {
|
|
@@ -1,15 +1,72 @@
|
|
|
1
1
|
import { guidedResult } from "./utils.js";
|
|
2
2
|
import { createWorkflowSdk, renderWorkflowNextSteps, workflowErrorResult } from "./workflow.js";
|
|
3
|
+
function formatReleasePlanSections(payload) {
|
|
4
|
+
const sections = [];
|
|
5
|
+
const selection = payload.packageSelection ?? {};
|
|
6
|
+
const selected = selection.selected ?? [];
|
|
7
|
+
if (selected.length > 0 || (selection.changed ?? []).length > 0 || (selection.dependents ?? []).length > 0) {
|
|
8
|
+
sections.push({
|
|
9
|
+
title: "Package selection",
|
|
10
|
+
lines: [
|
|
11
|
+
`Changed: ${(selection.changed ?? []).join(", ") || "none"}`,
|
|
12
|
+
`Dependents: ${(selection.dependents ?? []).join(", ") || "none"}`,
|
|
13
|
+
`Selected: ${selected.join(", ") || "none"}`
|
|
14
|
+
]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const versions = Object.entries(payload.plannedVersions ?? {});
|
|
18
|
+
if (versions.length > 0) {
|
|
19
|
+
sections.push({
|
|
20
|
+
title: "Planned versions",
|
|
21
|
+
lines: versions.map(([name, version]) => `- ${name}: ${version}`)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const rewrites = payload.plannedDevReferenceRewrites ?? [];
|
|
25
|
+
if (rewrites.length > 0) {
|
|
26
|
+
sections.push({
|
|
27
|
+
title: "Dependency rewrites",
|
|
28
|
+
lines: rewrites.map((rewrite) => {
|
|
29
|
+
const target = rewrite.dependencyName ? `${rewrite.field ?? "dependencies"}.${rewrite.dependencyName}` : rewrite.filePath ?? "lockfile";
|
|
30
|
+
return `- ${rewrite.repoName ?? "repo"} ${target}: ${rewrite.reason ?? "dev-ref"} ${rewrite.spec ?? ""}`.trim();
|
|
31
|
+
})
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const waits = payload.plannedPublishWaits ?? [];
|
|
35
|
+
if (waits.length > 0) {
|
|
36
|
+
sections.push({
|
|
37
|
+
title: "Publish waits",
|
|
38
|
+
lines: waits.map((wait) => `- ${wait.name ?? "package"}: ${wait.workflow ?? "publish.yml"} on ${wait.branch ?? "main"} (${wait.status ?? "planned"})`)
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const steps = payload.plannedSteps ?? [];
|
|
42
|
+
if (steps.length > 0) {
|
|
43
|
+
sections.push({
|
|
44
|
+
title: "Execution order",
|
|
45
|
+
lines: steps.map((step, index) => `${index + 1}. ${step.description ?? step.id ?? "step"}`)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if ((payload.blockers ?? []).length > 0) {
|
|
49
|
+
sections.push({
|
|
50
|
+
title: "Blockers",
|
|
51
|
+
lines: (payload.blockers ?? []).map((blocker) => `- ${blocker}`)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return sections;
|
|
55
|
+
}
|
|
3
56
|
const handleRelease = async (invocation, context) => {
|
|
4
57
|
try {
|
|
5
58
|
const bump = ["major", "minor", "patch"].find((candidate) => invocation.args[candidate] === true) ?? "patch";
|
|
6
59
|
const result = await createWorkflowSdk(context).release({
|
|
7
60
|
bump,
|
|
61
|
+
workspaceLinks: typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0,
|
|
8
62
|
plan: invocation.args.plan === true || invocation.args.dryRun === true,
|
|
9
63
|
dryRun: invocation.args.dryRun === true
|
|
10
64
|
});
|
|
11
65
|
const payload = result.payload;
|
|
12
|
-
const
|
|
66
|
+
const publishWait = payload.publishWait ?? [];
|
|
67
|
+
const completedPublishes = publishWait.filter((entry) => entry.status === "completed").length;
|
|
68
|
+
const plannedPublishes = payload.plannedPublishWaits?.length ?? 0;
|
|
69
|
+
const releasedCommit = typeof payload.releasedCommit === "string" && payload.releasedCommit.length > 0 ? payload.releasedCommit.slice(0, 12) : result.executionMode === "plan" ? "planned" : "not available";
|
|
13
70
|
return guidedResult({
|
|
14
71
|
command: invocation.commandName || "release",
|
|
15
72
|
summary: result.executionMode === "plan" ? "Treeseed release plan ready." : "Treeseed release completed successfully.",
|
|
@@ -19,15 +76,16 @@ const handleRelease = async (invocation, context) => {
|
|
|
19
76
|
{ label: "Production branch", value: payload.productionBranch },
|
|
20
77
|
{ label: "Merge strategy", value: payload.mergeStrategy },
|
|
21
78
|
{ label: "Release level", value: payload.level },
|
|
22
|
-
{ label: "Root version", value: payload.rootVersion },
|
|
23
|
-
{ label: "Release tag", value: payload.releaseTag },
|
|
24
|
-
{ label: "Released commit", value:
|
|
79
|
+
{ label: "Root version", value: payload.rootVersion ?? payload.plannedVersions?.["@treeseed/market"] ?? "(planned)" },
|
|
80
|
+
{ label: "Release tag", value: payload.releaseTag ?? payload.rootVersion ?? payload.plannedVersions?.["@treeseed/market"] ?? "(planned)" },
|
|
81
|
+
{ label: "Released commit", value: releasedCommit },
|
|
25
82
|
{ label: "Changed packages", value: String(payload.packageSelection.changed.length) },
|
|
26
83
|
{ label: "Dependent packages", value: String(payload.packageSelection.dependents.length) },
|
|
27
|
-
{ label: "Released packages", value: String(payload.touchedPackages.length) },
|
|
28
|
-
{ label: "Publish waits", value: String(completedPublishes) },
|
|
29
|
-
{ label: "Final branch", value: payload.finalBranch }
|
|
84
|
+
{ label: result.executionMode === "plan" ? "Packages planned" : "Released packages", value: String((payload.touchedPackages ?? payload.packageSelection.selected).length) },
|
|
85
|
+
{ label: "Publish waits", value: result.executionMode === "plan" ? String(plannedPublishes) : String(completedPublishes) },
|
|
86
|
+
{ label: "Final branch", value: payload.finalBranch ?? (result.executionMode === "plan" ? payload.stagingBranch : "(unknown)") }
|
|
30
87
|
],
|
|
88
|
+
sections: result.executionMode === "plan" ? formatReleasePlanSections(payload) : [],
|
|
31
89
|
nextSteps: renderWorkflowNextSteps(result),
|
|
32
90
|
report: result
|
|
33
91
|
});
|
|
@@ -1,16 +1,64 @@
|
|
|
1
1
|
import { guidedResult } from "./utils.js";
|
|
2
2
|
import { createWorkflowSdk, renderWorkflowNextSteps, workflowErrorResult } from "./workflow.js";
|
|
3
|
+
function formatRepoPlanSummary(repo) {
|
|
4
|
+
const branch = repo.currentBranch && repo.targetBranch && repo.currentBranch !== repo.targetBranch ? `${repo.currentBranch} -> ${repo.targetBranch}` : repo.targetBranch ?? repo.currentBranch ?? "unknown";
|
|
5
|
+
const version = repo.plannedVersion ? `, version ${repo.currentVersion ?? "?"} -> ${repo.plannedVersion}` : repo.currentVersion ? `, version ${repo.currentVersion}` : "";
|
|
6
|
+
return `${repo.name} (${repo.kind ?? "repo"}, ${repo.branchMode ?? "unknown"}, ${repo.dirty ? "dirty" : "clean"}, branch ${branch}${version})`;
|
|
7
|
+
}
|
|
8
|
+
function formatSavePlanSections(repositoryPlan) {
|
|
9
|
+
if (!repositoryPlan) return [];
|
|
10
|
+
const reposByName = new Map([
|
|
11
|
+
...repositoryPlan.repos ?? [],
|
|
12
|
+
...repositoryPlan.rootRepo ? [repositoryPlan.rootRepo] : []
|
|
13
|
+
].map((repo) => [repo.name, repo]));
|
|
14
|
+
const sections = [];
|
|
15
|
+
const repoLines = [
|
|
16
|
+
...(repositoryPlan.repos ?? []).map((repo) => `- ${formatRepoPlanSummary(repo)}`),
|
|
17
|
+
...repositoryPlan.rootRepo ? [`- ${formatRepoPlanSummary(repositoryPlan.rootRepo)}`] : []
|
|
18
|
+
];
|
|
19
|
+
if (repoLines.length > 0) {
|
|
20
|
+
sections.push({ title: "Repositories", lines: repoLines });
|
|
21
|
+
}
|
|
22
|
+
const versionEntries = Object.entries(repositoryPlan.plannedVersions ?? {});
|
|
23
|
+
if (versionEntries.length > 0) {
|
|
24
|
+
sections.push({
|
|
25
|
+
title: "Planned package versions",
|
|
26
|
+
lines: versionEntries.map(([name, version]) => `- ${name}: ${version}`)
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const waveLines = [];
|
|
30
|
+
for (const wave of repositoryPlan.waves ?? []) {
|
|
31
|
+
waveLines.push(`Wave ${wave.index}${wave.parallel ? " (parallel, concurrency 3)" : ""}: ${wave.repos.join(", ")}`);
|
|
32
|
+
for (const entry of wave.commands) {
|
|
33
|
+
const repo = reposByName.get(entry.repo);
|
|
34
|
+
waveLines.push(` ${entry.repo}${repo?.plannedDependencySpec ? ` -> ${repo.plannedDependencySpec}` : ""}`);
|
|
35
|
+
entry.commands.forEach((command, index) => {
|
|
36
|
+
waveLines.push(` ${index + 1}. ${command}`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (waveLines.length > 0) {
|
|
41
|
+
sections.push({ title: "Execution order", lines: waveLines });
|
|
42
|
+
}
|
|
43
|
+
return sections;
|
|
44
|
+
}
|
|
3
45
|
const handleSave = async (invocation, context) => {
|
|
4
46
|
try {
|
|
5
|
-
const result = await createWorkflowSdk(context
|
|
47
|
+
const result = await createWorkflowSdk(context, {
|
|
48
|
+
write: context.outputFormat === "json" ? (() => {
|
|
49
|
+
}) : context.write
|
|
50
|
+
}).save({
|
|
6
51
|
message: invocation.positionals.join(" ").trim(),
|
|
7
52
|
hotfix: invocation.args.hotfix === true,
|
|
8
53
|
preview: invocation.args.preview === true,
|
|
54
|
+
workspaceLinks: typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0,
|
|
9
55
|
plan: invocation.args.plan === true || invocation.args.dryRun === true,
|
|
10
56
|
dryRun: invocation.args.dryRun === true
|
|
11
57
|
});
|
|
12
58
|
const payload = result.payload;
|
|
59
|
+
const commitSha = typeof payload.commitSha === "string" && payload.commitSha.length > 0 ? payload.commitSha.slice(0, 12) : "not applicable";
|
|
13
60
|
const savedRepos = (payload.repos ?? []).filter((repo) => repo.committed || repo.pushed).map((repo) => `${repo.name}@${String(repo.commitSha ?? "").slice(0, 12)}`).join(", ");
|
|
61
|
+
const plannedRepos = result.executionMode === "plan" ? (payload.repositoryPlan?.repos ?? payload.repos ?? []).map((repo) => repo.name).join(", ") : "";
|
|
14
62
|
return guidedResult({
|
|
15
63
|
command: invocation.commandName || "save",
|
|
16
64
|
summary: result.executionMode === "plan" ? "Treeseed save plan ready." : payload.noChanges ? "Treeseed save found no new changes and confirmed branch sync." : "Treeseed save completed successfully.",
|
|
@@ -19,12 +67,23 @@ const handleSave = async (invocation, context) => {
|
|
|
19
67
|
{ label: "Branch", value: payload.branch },
|
|
20
68
|
{ label: "Environment scope", value: payload.scope },
|
|
21
69
|
{ label: "Hotfix", value: payload.hotfix ? "yes" : "no" },
|
|
22
|
-
{
|
|
70
|
+
{
|
|
71
|
+
label: result.executionMode === "plan" ? "Interrupted save" : "Resumed run",
|
|
72
|
+
value: result.executionMode === "plan" ? payload.autoResumeCandidate?.runId ? `will resume ${payload.autoResumeCandidate.runId}` : "none" : payload.resumedRunId ?? "none"
|
|
73
|
+
},
|
|
74
|
+
{ label: "Commit", value: commitSha },
|
|
23
75
|
{ label: "Commit created", value: payload.commitCreated ? "yes" : "no" },
|
|
24
|
-
{
|
|
76
|
+
{
|
|
77
|
+
label: result.executionMode === "plan" ? "Workspace repos planned" : "Workspace repos",
|
|
78
|
+
value: result.executionMode === "plan" ? plannedRepos || "not applicable" : savedRepos || ((payload.repos ?? []).length > 0 ? "none saved" : "not applicable")
|
|
79
|
+
},
|
|
25
80
|
{ label: "Market pushed", value: payload.rootRepo?.pushed ? "yes" : "no" },
|
|
26
81
|
{ label: "Preview action", value: payload.previewAction?.status ?? "skipped" }
|
|
27
82
|
],
|
|
83
|
+
sections: result.executionMode === "plan" ? [
|
|
84
|
+
...payload.plannedSteps?.length ? [{ title: "Dependency mode transitions", lines: payload.plannedSteps.filter((step) => /workspace-(?:link|unlink)/u.test(String(step.id ?? ""))).map((step) => `- ${step.description ?? step.id}`) }] : [],
|
|
85
|
+
...formatSavePlanSections(payload.repositoryPlan)
|
|
86
|
+
] : [],
|
|
28
87
|
nextSteps: renderWorkflowNextSteps(result),
|
|
29
88
|
report: result
|
|
30
89
|
});
|
|
@@ -4,6 +4,7 @@ const handleStage = async (invocation, context) => {
|
|
|
4
4
|
try {
|
|
5
5
|
const result = await createWorkflowSdk(context).stage({
|
|
6
6
|
message: invocation.positionals.join(" ").trim(),
|
|
7
|
+
workspaceLinks: typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0,
|
|
7
8
|
plan: invocation.args.plan === true || invocation.args.dryRun === true,
|
|
8
9
|
dryRun: invocation.args.dryRun === true
|
|
9
10
|
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TreeseedCommandContext } from '../types.js';
|
|
2
|
+
type StatusState = Record<string, any>;
|
|
3
|
+
export declare function renderTreeseedStatusInk(state: StatusState, context?: Pick<TreeseedCommandContext, 'outputFormat' | 'interactiveUi'>): Promise<boolean>;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Box, render, Text, useApp, useWindowSize } from "ink";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { truncateLine, wrapText } from "../ui/framework.js";
|
|
4
|
+
const SCOPES = [
|
|
5
|
+
{ id: "local", label: "Local" },
|
|
6
|
+
{ id: "staging", label: "Staging" },
|
|
7
|
+
{ id: "prod", label: "Production" }
|
|
8
|
+
];
|
|
9
|
+
function statusTone(ok, warnings = 0) {
|
|
10
|
+
if (!ok) return "red";
|
|
11
|
+
if (warnings > 0) return "yellow";
|
|
12
|
+
return "green";
|
|
13
|
+
}
|
|
14
|
+
function yesNo(value) {
|
|
15
|
+
return value ? "yes" : "no";
|
|
16
|
+
}
|
|
17
|
+
function providerText(provider) {
|
|
18
|
+
if (!provider) return "unknown";
|
|
19
|
+
if (provider.applicable === false) return provider.detail ?? "not applicable";
|
|
20
|
+
const base = provider.configured ? "configured" : "missing";
|
|
21
|
+
if (!provider.live) return base;
|
|
22
|
+
if (provider.live.skipped) return `${base} / ${provider.live.detail}`;
|
|
23
|
+
return `${base} / ${provider.live.ready ? "live ok" : "live failed"}`;
|
|
24
|
+
}
|
|
25
|
+
function providerColor(provider) {
|
|
26
|
+
if (provider?.applicable === false) return "gray";
|
|
27
|
+
if (!provider?.configured) return "red";
|
|
28
|
+
if (provider.live && !provider.live.ready && !provider.live.skipped) return "red";
|
|
29
|
+
if (provider.live?.skipped) return "yellow";
|
|
30
|
+
return "green";
|
|
31
|
+
}
|
|
32
|
+
function envRows(state, scope, width) {
|
|
33
|
+
const env = state.environmentStatus?.[scope] ?? state.persistentEnvironments?.[scope] ?? {};
|
|
34
|
+
const readiness = state.readiness?.[scope] ?? {};
|
|
35
|
+
const provider = state.providerStatus?.[scope] ?? {};
|
|
36
|
+
const blockers = Array.isArray(env.blockers) ? env.blockers : readiness.blockers ?? [];
|
|
37
|
+
const warnings = Array.isArray(env.warnings) ? env.warnings : readiness.warnings ?? [];
|
|
38
|
+
const providerRows = [
|
|
39
|
+
{ label: "GitHub", value: providerText(provider.github), color: providerColor(provider.github) },
|
|
40
|
+
{ label: "Cloudflare", value: providerText(provider.cloudflare), color: providerColor(provider.cloudflare) },
|
|
41
|
+
{ label: "Railway", value: providerText(provider.railway), color: providerColor(provider.railway) }
|
|
42
|
+
];
|
|
43
|
+
if (scope === "local") {
|
|
44
|
+
providerRows.push({ label: "Local dev", value: providerText(provider.localDevelopment), color: providerColor(provider.localDevelopment) });
|
|
45
|
+
}
|
|
46
|
+
const rows = [
|
|
47
|
+
{ label: "Phase", value: env.phase ?? "pending", color: statusTone(Boolean(env.ready), warnings.length) },
|
|
48
|
+
{ label: "Ready", value: yesNo(Boolean(env.ready)), color: statusTone(Boolean(env.ready), warnings.length) },
|
|
49
|
+
{ label: "Configured", value: yesNo(Boolean(env.configured)), color: env.configured ? "green" : "yellow" },
|
|
50
|
+
{ label: "Initialized", value: yesNo(Boolean(env.initialized)), color: env.initialized ? "green" : "yellow" },
|
|
51
|
+
{ label: "Provisioned", value: yesNo(Boolean(env.provisioned)), color: scope === "local" || env.provisioned ? "green" : "yellow" },
|
|
52
|
+
{ label: "Deployable", value: yesNo(Boolean(env.deployable)), color: env.deployable ? "green" : "yellow" },
|
|
53
|
+
...providerRows,
|
|
54
|
+
{ label: "Last deploy", value: env.lastDeploymentTimestamp ?? "(none)", color: env.lastDeploymentTimestamp ? "white" : "gray" },
|
|
55
|
+
{ label: "URL", value: env.lastDeployedUrl ?? "(none)", color: env.lastDeployedUrl ? "cyan" : "gray" }
|
|
56
|
+
];
|
|
57
|
+
const issueRows = [
|
|
58
|
+
...blockers.slice(0, 4).map((value) => ({ label: "Blocker", value, color: "red" })),
|
|
59
|
+
...warnings.slice(0, 3).map((value) => ({ label: "Warning", value, color: "yellow" }))
|
|
60
|
+
];
|
|
61
|
+
return [...rows, ...issueRows].flatMap((row) => {
|
|
62
|
+
const prefix = `${row.label}: `;
|
|
63
|
+
const wrapped = wrapText(String(row.value), Math.max(1, width - prefix.length));
|
|
64
|
+
return wrapped.map((line, index) => ({
|
|
65
|
+
text: index === 0 ? `${prefix}${line}` : `${" ".repeat(prefix.length)}${line}`,
|
|
66
|
+
color: row.color
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function SummaryPanel(props) {
|
|
71
|
+
const state = props.state;
|
|
72
|
+
const packageBlockers = state.packageSync?.blockers ?? [];
|
|
73
|
+
const workflowBlockers = state.workflowControl?.blockers ?? [];
|
|
74
|
+
const rows = [
|
|
75
|
+
{ label: "Workspace", value: state.workspaceRoot ? state.cwd : "(not a workspace)", color: state.workspaceRoot ? "green" : "red" },
|
|
76
|
+
{ label: "Branch", value: `${state.branchName ?? "(none)"} (${state.branchRole})`, color: state.dirtyWorktree ? "yellow" : "green" },
|
|
77
|
+
{ label: "Environment", value: state.environment, color: state.environment === "none" ? "yellow" : "cyan" },
|
|
78
|
+
{ label: "Worktree", value: state.dirtyWorktree ? "dirty" : "clean", color: state.dirtyWorktree ? "yellow" : "green" },
|
|
79
|
+
{ label: "Packages", value: `${state.packageSync?.mode ?? "unknown"} / ${state.packageSync?.dependencyMode ?? "unknown"}`, color: packageBlockers.length > 0 ? "red" : "green" },
|
|
80
|
+
{ label: "Workflow", value: workflowBlockers.length > 0 ? workflowBlockers.join(" | ") : "no active blockers", color: workflowBlockers.length > 0 ? "red" : "green" },
|
|
81
|
+
{ label: "Secrets", value: state.secrets?.keyAgentRunning ? state.secrets.keyAgentUnlocked ? "agent unlocked" : "agent locked" : "agent stopped", color: state.secrets?.keyAgentUnlocked ? "green" : "yellow" },
|
|
82
|
+
{ label: "Market", value: state.marketConnection?.projectSlug ?? state.marketConnection?.projectId ?? "(not paired)", color: state.marketConnection?.configured ? "green" : "gray" }
|
|
83
|
+
];
|
|
84
|
+
return React.createElement(
|
|
85
|
+
Box,
|
|
86
|
+
{ flexDirection: "column", borderStyle: "round", borderColor: "cyan", width: props.width, paddingX: 1 },
|
|
87
|
+
React.createElement(Text, { color: "cyan", bold: true }, truncateLine("Project Status", props.width - 4)),
|
|
88
|
+
...rows.map((row) => React.createElement(
|
|
89
|
+
Text,
|
|
90
|
+
{ key: row.label, color: row.color },
|
|
91
|
+
truncateLine(`${row.label}: ${row.value}`, props.width - 4)
|
|
92
|
+
))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function EnvironmentPanel(props) {
|
|
96
|
+
const env = props.state.environmentStatus?.[props.scope] ?? {};
|
|
97
|
+
const blockers = env.blockers ?? [];
|
|
98
|
+
const warnings = env.warnings ?? [];
|
|
99
|
+
const rows = envRows(props.state, props.scope, props.width - 4);
|
|
100
|
+
return React.createElement(
|
|
101
|
+
Box,
|
|
102
|
+
{ flexDirection: "column", borderStyle: "round", borderColor: statusTone(Boolean(env.ready), warnings.length), width: props.width, paddingX: 1 },
|
|
103
|
+
React.createElement(Text, { color: statusTone(Boolean(env.ready), warnings.length), bold: true }, truncateLine(`${props.label} ${blockers.length ? "blocked" : warnings.length ? "warning" : "ready"}`, props.width - 4)),
|
|
104
|
+
...rows.slice(0, 18).map((row, index) => React.createElement(
|
|
105
|
+
Text,
|
|
106
|
+
{ key: `${props.scope}-${index}`, color: row.color ?? "white" },
|
|
107
|
+
truncateLine(row.text, props.width - 4)
|
|
108
|
+
))
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function ServicesPanel(props) {
|
|
112
|
+
const services = Object.entries(props.state.managedServices ?? {});
|
|
113
|
+
const nextSteps = Array.isArray(props.state.recommendations) ? props.state.recommendations : [];
|
|
114
|
+
const serviceRows = services.length > 0 ? services.map(([key, service]) => `${key}: ${service.enabled ? service.initialized ? "deployed" : "not deployed" : "disabled"}${service.lastDeployedUrl ? ` (${service.lastDeployedUrl})` : ""}`) : ["(none)"];
|
|
115
|
+
const nextRows = nextSteps.length > 0 ? nextSteps.map((step) => `${step.operation}: ${step.reason ?? ""}`) : ["No next steps."];
|
|
116
|
+
return React.createElement(
|
|
117
|
+
Box,
|
|
118
|
+
{ flexDirection: "column", borderStyle: "round", borderColor: "gray", width: props.width, paddingX: 1 },
|
|
119
|
+
React.createElement(Text, { color: "yellow", bold: true }, truncateLine("Services and Next Steps", props.width - 4)),
|
|
120
|
+
...serviceRows.slice(0, 6).map((line, index) => React.createElement(Text, { key: `svc-${index}`, color: line.includes("not deployed") ? "yellow" : line.includes("disabled") ? "gray" : "green" }, truncateLine(line, props.width - 4))),
|
|
121
|
+
...nextRows.slice(0, 4).map((line, index) => React.createElement(Text, { key: `next-${index}`, color: "cyan" }, truncateLine(`Next: ${line}`, props.width - 4)))
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
function StatusDashboard(props) {
|
|
125
|
+
const { exit } = useApp();
|
|
126
|
+
const windowSize = useWindowSize();
|
|
127
|
+
const width = Math.max(72, windowSize?.columns ?? 100);
|
|
128
|
+
const stacked = width < 118;
|
|
129
|
+
const columnWidth = stacked ? width : Math.max(28, Math.floor((width - 2) / 3));
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
const timer = setTimeout(() => exit(), 20);
|
|
132
|
+
return () => clearTimeout(timer);
|
|
133
|
+
}, [exit]);
|
|
134
|
+
return React.createElement(
|
|
135
|
+
Box,
|
|
136
|
+
{ flexDirection: "column", width },
|
|
137
|
+
React.createElement(SummaryPanel, { state: props.state, width }),
|
|
138
|
+
React.createElement(
|
|
139
|
+
Box,
|
|
140
|
+
{ flexDirection: stacked ? "column" : "row", width },
|
|
141
|
+
...SCOPES.map((scope) => React.createElement(EnvironmentPanel, {
|
|
142
|
+
key: scope.id,
|
|
143
|
+
state: props.state,
|
|
144
|
+
scope: scope.id,
|
|
145
|
+
label: scope.label,
|
|
146
|
+
width: columnWidth
|
|
147
|
+
}))
|
|
148
|
+
),
|
|
149
|
+
React.createElement(ServicesPanel, { state: props.state, width })
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
async function renderTreeseedStatusInk(state, context = {}) {
|
|
153
|
+
if (context.outputFormat === "json" || context.interactiveUi === false || !process.stdout.isTTY) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const instance = render(React.createElement(StatusDashboard, { state }), { exitOnCtrlC: false });
|
|
157
|
+
await instance.waitUntilExit();
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
renderTreeseedStatusInk
|
|
162
|
+
};
|
|
@@ -1,67 +1,116 @@
|
|
|
1
1
|
import { guidedResult } from "./utils.js";
|
|
2
2
|
import { createWorkflowSdk, renderWorkflowNextSteps, workflowErrorResult } from "./workflow.js";
|
|
3
|
-
|
|
3
|
+
import { renderTreeseedStatusInk } from "./status-ui.js";
|
|
4
|
+
const SCOPES = [
|
|
5
|
+
{ id: "local", label: "Local" },
|
|
6
|
+
{ id: "staging", label: "Staging" },
|
|
7
|
+
{ id: "prod", label: "Production" }
|
|
8
|
+
];
|
|
9
|
+
function yesNo(value) {
|
|
10
|
+
return value ? "yes" : "no";
|
|
11
|
+
}
|
|
12
|
+
function providerSummary(provider) {
|
|
13
|
+
if (!provider) return "unknown";
|
|
14
|
+
if (provider.applicable === false) return provider.detail ?? "not applicable";
|
|
15
|
+
const configured = provider.configured ? "configured" : "missing";
|
|
16
|
+
if (!provider.live) return configured;
|
|
17
|
+
if (provider.live.skipped) return `${configured}, skipped: ${provider.live.detail}`;
|
|
18
|
+
return `${configured}, ${provider.live.ready ? "live ok" : `live failed: ${provider.live.detail}`}`;
|
|
19
|
+
}
|
|
20
|
+
function environmentLines(state, scope) {
|
|
21
|
+
const env = state.environmentStatus?.[scope] ?? state.persistentEnvironments?.[scope] ?? {};
|
|
22
|
+
const providers = state.providerStatus?.[scope] ?? {};
|
|
23
|
+
const blockers = Array.isArray(env.blockers) ? env.blockers : [];
|
|
24
|
+
const warnings = Array.isArray(env.warnings) ? env.warnings : [];
|
|
25
|
+
const providerLines = [
|
|
26
|
+
`GitHub: ${providerSummary(providers.github)}`,
|
|
27
|
+
`Cloudflare: ${providerSummary(providers.cloudflare)}`,
|
|
28
|
+
`Railway: ${providerSummary(providers.railway)}`
|
|
29
|
+
];
|
|
30
|
+
if (scope === "local") {
|
|
31
|
+
providerLines.push(`Local development: ${providerSummary(providers.localDevelopment)}`);
|
|
32
|
+
}
|
|
33
|
+
return [
|
|
34
|
+
`Phase: ${env.phase ?? "pending"}`,
|
|
35
|
+
`Ready: ${yesNo(Boolean(env.ready))}`,
|
|
36
|
+
`Configured: ${yesNo(Boolean(env.configured))}`,
|
|
37
|
+
`Initialized: ${yesNo(Boolean(env.initialized))}`,
|
|
38
|
+
`Provisioned: ${yesNo(Boolean(env.provisioned))}`,
|
|
39
|
+
`Deployable: ${yesNo(Boolean(env.deployable))}`,
|
|
40
|
+
...providerLines,
|
|
41
|
+
`Last deploy: ${env.lastDeploymentTimestamp ?? "(none)"}`,
|
|
42
|
+
`URL: ${env.lastDeployedUrl ?? "(none)"}`,
|
|
43
|
+
...blockers.length > 0 ? blockers.map((blocker) => `BLOCKER: ${blocker}`) : ["Blockers: none"],
|
|
44
|
+
...warnings.length > 0 ? warnings.map((warning) => `WARNING: ${warning}`) : ["Warnings: none"]
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
function statusFacts(state, live) {
|
|
48
|
+
return [
|
|
49
|
+
{ label: "Mode", value: live ? "saved state + live provider checks" : "saved state" },
|
|
50
|
+
{ label: "Workspace root", value: state.workspaceRoot ? "yes" : "no" },
|
|
51
|
+
{ label: "Tenant config present", value: state.deployConfigPresent ? "yes" : "no" },
|
|
52
|
+
{ label: "Branch", value: state.branchName ?? "(none)" },
|
|
53
|
+
{ label: "Branch role", value: state.branchRole },
|
|
54
|
+
{ label: "Mapped environment", value: state.environment },
|
|
55
|
+
{ label: "Dirty worktree", value: state.dirtyWorktree ? "yes" : "no" },
|
|
56
|
+
{ label: "Package mode", value: state.packageSync.mode },
|
|
57
|
+
{ label: "Dependency mode", value: state.packageSync.dependencyMode ?? "(unknown)" },
|
|
58
|
+
{ label: "Full package checkout", value: state.packageSync.completeCheckout ? "yes" : "no" },
|
|
59
|
+
{ label: "Package branch aligned", value: state.packageSync.aligned ? "yes" : "no" },
|
|
60
|
+
{ label: "Dirty package repos", value: state.packageSync.dirty ? "yes" : "no" },
|
|
61
|
+
{ label: "Package blockers", value: state.packageSync.blockers.length > 0 ? state.packageSync.blockers.join(" | ") : "(none)" },
|
|
62
|
+
{ label: "Preview enabled", value: state.preview.enabled ? "yes" : "no" },
|
|
63
|
+
{ label: "Preview URL", value: state.preview.url ?? "(none)" },
|
|
64
|
+
{ label: "Remote API auth", value: state.auth.remoteApi ? "ready" : "not ready" },
|
|
65
|
+
{ label: "Wrapped machine key", value: state.secrets.wrappedKeyPresent ? "present" : "missing" },
|
|
66
|
+
{ label: "Key migration", value: state.secrets.migrationRequired ? "required" : "not needed" },
|
|
67
|
+
{ label: "Key agent", value: state.secrets.keyAgentRunning ? state.secrets.keyAgentUnlocked ? "running/unlocked" : "running/locked" : "stopped" },
|
|
68
|
+
{ label: "Startup passphrase env", value: state.secrets.startupPassphraseConfigured ? "configured" : "unset" },
|
|
69
|
+
{ label: "Market project", value: state.marketConnection.projectSlug ?? state.marketConnection.projectId ?? "(not paired)" },
|
|
70
|
+
{ label: "Market team", value: state.marketConnection.teamSlug ?? state.marketConnection.teamId ?? "(not paired)" },
|
|
71
|
+
{ label: "Market mode", value: state.marketConnection.connectionMode ?? "(not paired)" },
|
|
72
|
+
{ label: "Hub mode", value: state.marketConnection.hubMode ?? "(unknown)" },
|
|
73
|
+
{ label: "Runtime mode", value: state.marketConnection.runtimeMode ?? "(unknown)" },
|
|
74
|
+
{ label: "Runtime registration", value: state.marketConnection.runtimeRegistration ?? "(none)" },
|
|
75
|
+
{ label: "Runtime ready", value: state.marketConnection.runtimeReady ? "yes" : "no" },
|
|
76
|
+
{ label: "Current workstream", value: state.marketConnection.currentWorkstreamId ?? "(none)" },
|
|
77
|
+
{ label: "Approval blockers", value: state.marketConnection.approvalBlockers.length > 0 ? state.marketConnection.approvalBlockers.join(" | ") : "(none)" }
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
const handleStatus = async (invocation, context) => {
|
|
4
81
|
try {
|
|
5
|
-
const
|
|
82
|
+
const live = invocation.args.live === true;
|
|
83
|
+
const result = await createWorkflowSdk(context).status({ live });
|
|
6
84
|
const state = result.payload;
|
|
85
|
+
const nextSteps = renderWorkflowNextSteps(result);
|
|
86
|
+
const report = {
|
|
87
|
+
...result,
|
|
88
|
+
state,
|
|
89
|
+
live
|
|
90
|
+
};
|
|
91
|
+
if (await renderTreeseedStatusInk(state, context)) {
|
|
92
|
+
return {
|
|
93
|
+
exitCode: 0,
|
|
94
|
+
stdout: [],
|
|
95
|
+
report
|
|
96
|
+
};
|
|
97
|
+
}
|
|
7
98
|
return guidedResult({
|
|
8
99
|
command: "status",
|
|
9
100
|
summary: "Treeseed workflow status",
|
|
10
|
-
facts:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{ label: "Dirty package repos", value: state.packageSync.dirty ? "yes" : "no" },
|
|
21
|
-
{ label: "Package blockers", value: state.packageSync.blockers.length > 0 ? state.packageSync.blockers.join(" | ") : "(none)" },
|
|
22
|
-
{ label: "Local state", value: state.persistentEnvironments.local.phase },
|
|
23
|
-
{ label: "Staging state", value: state.persistentEnvironments.staging.phase },
|
|
24
|
-
{ label: "Prod state", value: state.persistentEnvironments.prod.phase },
|
|
25
|
-
{ label: "Local initialized", value: state.persistentEnvironments.local.initialized ? "yes" : "no" },
|
|
26
|
-
{ label: "Staging initialized", value: state.persistentEnvironments.staging.initialized ? "yes" : "no" },
|
|
27
|
-
{ label: "Prod initialized", value: state.persistentEnvironments.prod.initialized ? "yes" : "no" },
|
|
28
|
-
{ label: "Staging blockers", value: state.persistentEnvironments.staging.blockers.length > 0 ? state.persistentEnvironments.staging.blockers.join(" | ") : "(none)" },
|
|
29
|
-
{ label: "Prod blockers", value: state.persistentEnvironments.prod.blockers.length > 0 ? state.persistentEnvironments.prod.blockers.join(" | ") : "(none)" },
|
|
30
|
-
{ label: "Preview enabled", value: state.preview.enabled ? "yes" : "no" },
|
|
31
|
-
{ label: "Preview URL", value: state.preview.url ?? "(none)" },
|
|
32
|
-
{ label: "GitHub token/config", value: state.auth.gh ? "configured" : "missing" },
|
|
33
|
-
{ label: "Cloudflare token/config", value: state.auth.wrangler ? "configured" : "missing" },
|
|
34
|
-
{ label: "Railway token/config", value: state.auth.railway ? "configured" : "missing" },
|
|
35
|
-
{ label: "Remote API auth", value: state.auth.remoteApi ? "ready" : "not ready" },
|
|
36
|
-
{ label: "Wrapped machine key", value: state.secrets.wrappedKeyPresent ? "present" : "missing" },
|
|
37
|
-
{ label: "Key migration", value: state.secrets.migrationRequired ? "required" : "not needed" },
|
|
38
|
-
{ label: "Key agent", value: state.secrets.keyAgentRunning ? state.secrets.keyAgentUnlocked ? "running/unlocked" : "running/locked" : "stopped" },
|
|
39
|
-
{ label: "Startup passphrase env", value: state.secrets.startupPassphraseConfigured ? "configured" : "unset" },
|
|
40
|
-
{ label: "Market project", value: state.marketConnection.projectSlug ?? state.marketConnection.projectId ?? "(not paired)" },
|
|
41
|
-
{ label: "Market team", value: state.marketConnection.teamSlug ?? state.marketConnection.teamId ?? "(not paired)" },
|
|
42
|
-
{ label: "Market mode", value: state.marketConnection.connectionMode ?? "(not paired)" },
|
|
43
|
-
{ label: "Hub mode", value: state.marketConnection.hubMode ?? "(unknown)" },
|
|
44
|
-
{ label: "Runtime mode", value: state.marketConnection.runtimeMode ?? "(unknown)" },
|
|
45
|
-
{ label: "Runtime registration", value: state.marketConnection.runtimeRegistration ?? "(none)" },
|
|
46
|
-
{ label: "Runtime ready", value: state.marketConnection.runtimeReady ? "yes" : "no" },
|
|
47
|
-
{ label: "Web cache host", value: state.webCache.webHost ?? "(none)" },
|
|
48
|
-
{ label: "Content cache host", value: state.webCache.contentHost ?? "(none)" },
|
|
49
|
-
{ label: "Source page cache", value: state.webCache.sourcePagePolicy ?? "(none)" },
|
|
50
|
-
{ label: "Content page cache", value: state.webCache.contentPagePolicy ?? "(none)" },
|
|
51
|
-
{ label: "R2 object cache", value: state.webCache.r2ObjectPolicy ?? "(none)" },
|
|
52
|
-
{ label: "Cloudflare cache rules", value: state.webCache.cloudflareRulesManaged ? "managed" : "not managed" },
|
|
53
|
-
{ label: "Last deploy purge", value: state.webCache.lastDeployPurgeAt ? `${state.webCache.lastDeployPurgeAt} (${state.webCache.lastDeployPurgeCount ?? 0} urls)` : "(none)" },
|
|
54
|
-
{ label: "Last content purge", value: state.webCache.lastContentPurgeAt ? `${state.webCache.lastContentPurgeAt} (${state.webCache.lastContentPurgeCount ?? 0} urls)` : "(none)" },
|
|
55
|
-
{ label: "Current workstream", value: state.marketConnection.currentWorkstreamId ?? "(none)" },
|
|
56
|
-
{ label: "Approval blockers", value: state.marketConnection.approvalBlockers.length > 0 ? state.marketConnection.approvalBlockers.join(" | ") : "(none)" },
|
|
57
|
-
{ label: "API service", value: state.managedServices.api.enabled ? `${state.managedServices.api.initialized ? "deployed" : "not deployed"}${state.managedServices.api.lastDeployedUrl ? ` (${state.managedServices.api.lastDeployedUrl})` : ""}` : "disabled" },
|
|
58
|
-
{ label: "Worker service", value: state.managedServices.worker.enabled ? `${state.managedServices.worker.initialized ? "deployed" : "not deployed"}${state.managedServices.worker.lastDeployedUrl ? ` (${state.managedServices.worker.lastDeployedUrl})` : ""}` : "disabled" }
|
|
101
|
+
facts: statusFacts(state, live),
|
|
102
|
+
sections: [
|
|
103
|
+
...SCOPES.map((scope) => ({
|
|
104
|
+
title: scope.label,
|
|
105
|
+
lines: environmentLines(state, scope.id)
|
|
106
|
+
})),
|
|
107
|
+
{
|
|
108
|
+
title: "Managed services",
|
|
109
|
+
lines: Object.entries(state.managedServices ?? {}).map(([name, service]) => `${name}: ${service.enabled ? service.initialized ? "deployed" : "not deployed" : "disabled"}${service.lastDeployedUrl ? ` (${service.lastDeployedUrl})` : ""}`)
|
|
110
|
+
}
|
|
59
111
|
],
|
|
60
|
-
nextSteps
|
|
61
|
-
report
|
|
62
|
-
...result,
|
|
63
|
-
state
|
|
64
|
-
}
|
|
112
|
+
nextSteps,
|
|
113
|
+
report
|
|
65
114
|
});
|
|
66
115
|
} catch (error) {
|
|
67
116
|
return workflowErrorResult(error);
|
|
@@ -6,6 +6,7 @@ const handleSwitch = async (invocation, context) => {
|
|
|
6
6
|
const result = await createWorkflowSdk(context).switchTask({
|
|
7
7
|
branch,
|
|
8
8
|
preview: invocation.args.preview === true,
|
|
9
|
+
workspaceLinks: typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0,
|
|
9
10
|
plan: invocation.args.plan === true || invocation.args.dryRun === true,
|
|
10
11
|
dryRun: invocation.args.dryRun === true
|
|
11
12
|
});
|
|
@@ -8,6 +8,10 @@ type GuidedResultOptions = {
|
|
|
8
8
|
label: string;
|
|
9
9
|
value: string | number | boolean | null | undefined;
|
|
10
10
|
}>;
|
|
11
|
+
sections?: Array<{
|
|
12
|
+
title: string;
|
|
13
|
+
lines: string[];
|
|
14
|
+
}>;
|
|
11
15
|
nextSteps?: string[];
|
|
12
16
|
report?: Record<string, unknown> | null;
|
|
13
17
|
exitCode?: number;
|
|
@@ -13,6 +13,11 @@ function guidedResult(options) {
|
|
|
13
13
|
lines.push(`${fact.label}: ${fact.value}`);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
+
for (const section of options.sections ?? []) {
|
|
17
|
+
const sectionLines = section.lines.filter((line) => line.length > 0);
|
|
18
|
+
if (sectionLines.length === 0) continue;
|
|
19
|
+
lines.push("", `${section.title}:`, ...sectionLines);
|
|
20
|
+
}
|
|
16
21
|
if ((options.nextSteps ?? []).length > 0) {
|
|
17
22
|
lines.push("", "Next steps:");
|
|
18
23
|
for (const step of options.nextSteps ?? []) {
|
|
@@ -25,12 +30,14 @@ function guidedResult(options) {
|
|
|
25
30
|
ok: (options.exitCode ?? 0) === 0,
|
|
26
31
|
summary: options.summary,
|
|
27
32
|
facts: facts.map((fact) => ({ label: fact.label, value: fact.value })),
|
|
33
|
+
sections: options.sections ?? [],
|
|
28
34
|
nextSteps: options.nextSteps ?? []
|
|
29
35
|
} : {
|
|
30
36
|
command: options.command,
|
|
31
37
|
ok: (options.exitCode ?? 0) === 0,
|
|
32
38
|
summary: options.summary,
|
|
33
39
|
facts: facts.map((fact) => ({ label: fact.label, value: fact.value })),
|
|
40
|
+
sections: options.sections ?? [],
|
|
34
41
|
nextSteps: options.nextSteps ?? []
|
|
35
42
|
};
|
|
36
43
|
return {
|
|
@@ -104,8 +104,10 @@ function renderWorkflowNextStep(step) {
|
|
|
104
104
|
switch (step.operation) {
|
|
105
105
|
case "switch":
|
|
106
106
|
return `treeseed switch ${String(input.branch ?? "feature/my-change")}${input.preview ? " --preview" : ""}`;
|
|
107
|
-
case "save":
|
|
108
|
-
|
|
107
|
+
case "save": {
|
|
108
|
+
const message = String(input.message ?? "").trim();
|
|
109
|
+
return `treeseed save${message ? ` "${message}"` : ""}${input.hotfix ? " --hotfix" : ""}`;
|
|
110
|
+
}
|
|
109
111
|
case "close":
|
|
110
112
|
return `treeseed close "${String(input.message ?? "reason")}"`;
|
|
111
113
|
case "stage":
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureLocalWorkspaceLinks,
|
|
3
|
+
findNearestTreeseedWorkspaceRoot,
|
|
4
|
+
inspectWorkspaceDependencyMode,
|
|
5
|
+
unlinkLocalWorkspaceLinks
|
|
6
|
+
} from "@treeseed/sdk/workflow-support";
|
|
7
|
+
import { guidedResult } from "./utils.js";
|
|
8
|
+
import { workflowErrorResult } from "./workflow.js";
|
|
9
|
+
const workspaceCommand = (name) => `workspace${":"}${name}`;
|
|
10
|
+
function workspaceRootOrThrow(cwd) {
|
|
11
|
+
const root = findNearestTreeseedWorkspaceRoot(cwd);
|
|
12
|
+
if (!root) {
|
|
13
|
+
throw new Error("Treeseed workspace commands must run inside a workspace with checked-out packages.");
|
|
14
|
+
}
|
|
15
|
+
return root;
|
|
16
|
+
}
|
|
17
|
+
function facts(report) {
|
|
18
|
+
return [
|
|
19
|
+
{ label: "Dependency mode", value: report.mode },
|
|
20
|
+
{ label: "Workspace links enabled", value: report.enabled ? "yes" : "no" },
|
|
21
|
+
{ label: "Links", value: String(report.links.length) },
|
|
22
|
+
{ label: "Linked", value: String(report.links.filter((link) => link.linked && link.targetMatches).length) },
|
|
23
|
+
{ label: "Created", value: String(report.created.length) },
|
|
24
|
+
{ label: "Removed", value: String(report.removed.length) },
|
|
25
|
+
{ label: "Issues", value: report.issues.length > 0 ? report.issues.join(" | ") : "(none)" }
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
const handleWorkspace = async (invocation, context) => {
|
|
29
|
+
try {
|
|
30
|
+
const root = workspaceRootOrThrow(context.cwd);
|
|
31
|
+
const linkCommand = workspaceCommand("link");
|
|
32
|
+
const unlinkCommand = workspaceCommand("unlink");
|
|
33
|
+
const statusCommand = workspaceCommand("status");
|
|
34
|
+
const report = invocation.commandName === linkCommand ? ensureLocalWorkspaceLinks(root, { env: context.env }) : invocation.commandName === unlinkCommand ? unlinkLocalWorkspaceLinks(root, { env: context.env }) : inspectWorkspaceDependencyMode(root, { env: context.env });
|
|
35
|
+
return guidedResult({
|
|
36
|
+
command: invocation.commandName || statusCommand,
|
|
37
|
+
summary: invocation.commandName === linkCommand ? "Treeseed local workspace links are ready." : invocation.commandName === unlinkCommand ? "Treeseed local workspace links were removed." : "Treeseed workspace dependency mode",
|
|
38
|
+
facts: facts(report),
|
|
39
|
+
report: {
|
|
40
|
+
ok: true,
|
|
41
|
+
command: invocation.commandName || statusCommand,
|
|
42
|
+
payload: report
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return workflowErrorResult(error);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export {
|
|
50
|
+
handleWorkspace
|
|
51
|
+
};
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
function command(overlay) {
|
|
6
6
|
return overlay;
|
|
7
7
|
}
|
|
8
|
+
const workspaceCommand = (name) => `workspace${":"}${name}`;
|
|
8
9
|
function example(commandLine, title, description, extras = {}) {
|
|
9
10
|
return {
|
|
10
11
|
command: commandLine,
|
|
@@ -133,8 +134,11 @@ function mergeHelpSpec(metadata, overlay, spec) {
|
|
|
133
134
|
const PASS_THROUGH_ARGS = (invocation) => ({ args: invocation.rawArgs });
|
|
134
135
|
const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
135
136
|
["status", command({
|
|
136
|
-
options: [
|
|
137
|
-
|
|
137
|
+
options: [
|
|
138
|
+
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" },
|
|
139
|
+
{ name: "live", flags: "--live", description: "Run read-only provider connectivity checks and include the results in status.", kind: "boolean" }
|
|
140
|
+
],
|
|
141
|
+
examples: ["treeseed status", "treeseed status --json", "treeseed status --live"],
|
|
138
142
|
help: {
|
|
139
143
|
workflowPosition: "inspect",
|
|
140
144
|
longSummary: [
|
|
@@ -147,6 +151,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
147
151
|
],
|
|
148
152
|
beforeYouRun: [
|
|
149
153
|
"Run from the workspace you want to inspect.",
|
|
154
|
+
"Use `--live` only when you want read-only provider connectivity checks in addition to saved state.",
|
|
150
155
|
"Choose `--json` when another tool or agent needs to read the status programmatically."
|
|
151
156
|
],
|
|
152
157
|
outcomes: [
|
|
@@ -156,6 +161,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
156
161
|
examples: [
|
|
157
162
|
example("treeseed status", "Check the current task state", "Show the current branch role and project health in human-readable form."),
|
|
158
163
|
example("treeseed status --json", "Feed an agent or script", "Emit structured status data for automation and external tooling."),
|
|
164
|
+
example("treeseed status --live", "Check provider connectivity", "Include read-only GitHub, Cloudflare, and Railway identity checks in the status report."),
|
|
159
165
|
example("trsd status", "Use the short alias", "Run the same status inspection path through the shorter CLI entrypoint.")
|
|
160
166
|
],
|
|
161
167
|
automationNotes: [
|
|
@@ -202,6 +208,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
202
208
|
arguments: [{ name: "branch-name", description: "Task branch to create or resume.", required: true }],
|
|
203
209
|
options: [
|
|
204
210
|
{ name: "preview", flags: "--preview", description: "Provision or refresh a branch-scoped Cloudflare preview environment.", kind: "boolean" },
|
|
211
|
+
{ name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] },
|
|
205
212
|
{ name: "plan", flags: "--plan", description: "Compute the recursive branch switch plan without mutating any repo.", kind: "boolean" },
|
|
206
213
|
{ name: "dryRun", flags: "--dry-run", description: "Alias for --plan.", kind: "boolean" },
|
|
207
214
|
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
|
|
@@ -250,15 +257,16 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
250
257
|
handlerName: "switch"
|
|
251
258
|
})],
|
|
252
259
|
["save", command({
|
|
253
|
-
arguments: [{ name: "message", description: "
|
|
260
|
+
arguments: [{ name: "message", description: "Optional hint for generated save commit messages.", required: false, kind: "message_tail" }],
|
|
254
261
|
options: [
|
|
255
262
|
{ name: "hotfix", flags: "--hotfix", description: "Allow save on main for an explicit hotfix.", kind: "boolean" },
|
|
256
263
|
{ name: "preview", flags: "--preview", description: "Create or refresh the branch preview during save.", kind: "boolean" },
|
|
264
|
+
{ name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] },
|
|
257
265
|
{ name: "plan", flags: "--plan", description: "Compute the recursive save plan without mutating any repo.", kind: "boolean" },
|
|
258
266
|
{ name: "dryRun", flags: "--dry-run", description: "Alias for --plan.", kind: "boolean" },
|
|
259
267
|
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
|
|
260
268
|
],
|
|
261
|
-
examples: ['treeseed save "
|
|
269
|
+
examples: ["treeseed save", 'treeseed save "add search filters"', "treeseed save --preview", "treeseed save --plan", 'treeseed save --hotfix "fix production form submit"'],
|
|
262
270
|
help: {
|
|
263
271
|
workflowPosition: "checkpoint work",
|
|
264
272
|
longSummary: [
|
|
@@ -272,17 +280,18 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
272
280
|
],
|
|
273
281
|
beforeYouRun: [
|
|
274
282
|
"Run from a task branch unless you intentionally mean to use the hotfix path.",
|
|
275
|
-
"
|
|
283
|
+
"Optionally provide a short hint; Treeseed generates the final commit message from the diff and hint."
|
|
276
284
|
],
|
|
277
285
|
outcomes: [
|
|
278
|
-
"Verifies and commits current work using
|
|
286
|
+
"Verifies and commits current work using a generated commit message.",
|
|
279
287
|
"Syncs and pushes branch state.",
|
|
280
288
|
"Optionally refreshes preview infrastructure if requested."
|
|
281
289
|
],
|
|
282
290
|
examples: [
|
|
283
|
-
example(
|
|
284
|
-
example('treeseed save
|
|
285
|
-
example(
|
|
291
|
+
example("treeseed save", "Generated checkpoint", "Verify, commit, and push the current task branch with a generated message."),
|
|
292
|
+
example('treeseed save "add search filters"', "Checkpoint with a hint", "Feed a short hint into commit-message generation without replacing the generated message."),
|
|
293
|
+
example("treeseed save --preview", "Checkpoint plus preview refresh", "Include preview refresh when the save should update the branch environment."),
|
|
294
|
+
example('treeseed save --hotfix "fix production form submit"', "Explicit hotfix save", "Allow a save from main when the work is a deliberate hotfix path.", { why: "Use sparingly and only when the workflow intentionally bypasses the usual task-branch rule." })
|
|
286
295
|
],
|
|
287
296
|
warnings: [
|
|
288
297
|
"`--hotfix` deliberately loosens the normal task-branch safety model. Keep it exceptional."
|
|
@@ -300,6 +309,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
300
309
|
arguments: [{ name: "message", description: "Reason for closing the task without staging it.", required: true, kind: "message_tail" }],
|
|
301
310
|
options: [
|
|
302
311
|
{ name: "plan", flags: "--plan", description: "Compute the recursive close plan without mutating any repo.", kind: "boolean" },
|
|
312
|
+
{ name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] },
|
|
303
313
|
{ name: "dryRun", flags: "--dry-run", description: "Alias for --plan.", kind: "boolean" },
|
|
304
314
|
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
|
|
305
315
|
],
|
|
@@ -338,6 +348,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
338
348
|
arguments: [{ name: "message", description: "Resolution message for the staged task.", required: true, kind: "message_tail" }],
|
|
339
349
|
options: [
|
|
340
350
|
{ name: "plan", flags: "--plan", description: "Compute the recursive staging plan without mutating any repo.", kind: "boolean" },
|
|
351
|
+
{ name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] },
|
|
341
352
|
{ name: "dryRun", flags: "--dry-run", description: "Alias for --plan.", kind: "boolean" },
|
|
342
353
|
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
|
|
343
354
|
],
|
|
@@ -420,6 +431,24 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
420
431
|
executionMode: "handler",
|
|
421
432
|
handlerName: "recover"
|
|
422
433
|
})],
|
|
434
|
+
[workspaceCommand("status"), command({
|
|
435
|
+
options: [{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }],
|
|
436
|
+
examples: ["treeseed workspace:status"],
|
|
437
|
+
executionMode: "handler",
|
|
438
|
+
handlerName: workspaceCommand("status")
|
|
439
|
+
})],
|
|
440
|
+
[workspaceCommand("link"), command({
|
|
441
|
+
options: [{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }],
|
|
442
|
+
examples: ["treeseed workspace:link"],
|
|
443
|
+
executionMode: "handler",
|
|
444
|
+
handlerName: workspaceCommand("link")
|
|
445
|
+
})],
|
|
446
|
+
[workspaceCommand("unlink"), command({
|
|
447
|
+
options: [{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }],
|
|
448
|
+
examples: ["treeseed workspace:unlink"],
|
|
449
|
+
executionMode: "handler",
|
|
450
|
+
handlerName: workspaceCommand("unlink")
|
|
451
|
+
})],
|
|
423
452
|
["rollback", command({
|
|
424
453
|
arguments: [{ name: "environment", description: "The persistent environment to roll back.", required: true }],
|
|
425
454
|
options: [
|
|
@@ -492,6 +521,40 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
492
521
|
executionMode: "handler",
|
|
493
522
|
handlerName: "doctor"
|
|
494
523
|
})],
|
|
524
|
+
["install", command({
|
|
525
|
+
options: [
|
|
526
|
+
{ name: "force", flags: "--force", description: "Repair or reinstall managed tools even when they are already present.", kind: "boolean" },
|
|
527
|
+
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
|
|
528
|
+
],
|
|
529
|
+
examples: ["treeseed install", "trsd install --json", "treeseed install --force"],
|
|
530
|
+
help: {
|
|
531
|
+
workflowPosition: "setup",
|
|
532
|
+
longSummary: [
|
|
533
|
+
"Install prepares the local Treeseed toolchain by installing or verifying Treeseed-managed dependencies. It is safe to rerun and uses the same dependency initializer that config runs during bootstrap."
|
|
534
|
+
],
|
|
535
|
+
whenToUse: [
|
|
536
|
+
"Use this on a new machine before running config, dev, or deployment workflows.",
|
|
537
|
+
"Use `--force` when a managed tool cache looks stale or corrupted."
|
|
538
|
+
],
|
|
539
|
+
outcomes: [
|
|
540
|
+
"Installs the managed GitHub CLI, verifies npm-backed Treeseed tool dependencies, and installs gh-act when Docker is available.",
|
|
541
|
+
"Reports any missing host prerequisites such as Git without modifying the operating system."
|
|
542
|
+
],
|
|
543
|
+
examples: [
|
|
544
|
+
example("treeseed install", "Install managed dependencies", "Prepare the local Treeseed dependency toolchain."),
|
|
545
|
+
example("trsd install --json", "Inspect setup from automation", "Emit a structured dependency report for scripts or agents."),
|
|
546
|
+
example("treeseed install --force", "Repair the managed cache", "Reinstall managed downloaded tools and extensions.")
|
|
547
|
+
],
|
|
548
|
+
relatedDetails: [
|
|
549
|
+
related("config", "Use `config` after install to configure project and provider values."),
|
|
550
|
+
related("doctor", "Use `doctor` when install succeeds but workflow readiness still looks wrong.")
|
|
551
|
+
]
|
|
552
|
+
},
|
|
553
|
+
executionMode: "adapter",
|
|
554
|
+
buildAdapterInput: (invocation) => ({
|
|
555
|
+
force: invocation.args.force === true
|
|
556
|
+
})
|
|
557
|
+
})],
|
|
495
558
|
["auth:login", command({
|
|
496
559
|
options: [
|
|
497
560
|
{ name: "host", flags: "--host <id>", description: "Override the configured remote host id for this login.", kind: "string" },
|
|
@@ -842,6 +905,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
842
905
|
{ name: "major", flags: "--major", description: "Bump to the next major version.", kind: "boolean" },
|
|
843
906
|
{ name: "minor", flags: "--minor", description: "Bump to the next minor version.", kind: "boolean" },
|
|
844
907
|
{ name: "patch", flags: "--patch", description: "Bump to the next patch version.", kind: "boolean" },
|
|
908
|
+
{ name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] },
|
|
845
909
|
{ name: "plan", flags: "--plan", description: "Compute the recursive release plan without mutating any repo.", kind: "boolean" },
|
|
846
910
|
{ name: "dryRun", flags: "--dry-run", description: "Alias for --plan.", kind: "boolean" },
|
|
847
911
|
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
|
|
@@ -931,6 +995,9 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
931
995
|
handlerName: "destroy"
|
|
932
996
|
})],
|
|
933
997
|
["dev", command({
|
|
998
|
+
options: [
|
|
999
|
+
{ name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] }
|
|
1000
|
+
],
|
|
934
1001
|
examples: ["treeseed dev"],
|
|
935
1002
|
help: {
|
|
936
1003
|
longSummary: ["Dev starts the unified local Treeseed runtime so you can work against the integrated web, API, and supporting local surfaces."],
|
|
@@ -944,6 +1011,9 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
944
1011
|
handlerName: "dev"
|
|
945
1012
|
})],
|
|
946
1013
|
["dev:watch", command({
|
|
1014
|
+
options: [
|
|
1015
|
+
{ name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] }
|
|
1016
|
+
],
|
|
947
1017
|
examples: ["treeseed dev:watch"],
|
|
948
1018
|
help: {
|
|
949
1019
|
longSummary: ["Dev:watch starts local development with rebuild and watch semantics so code changes are reflected continuously during active development."],
|
package/dist/cli/registry.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TreeseedCommandSpec } from './operations-types.js';
|
|
2
2
|
export declare const COMMAND_HANDLERS: {
|
|
3
|
+
readonly [x: string]: import("./operations-types.js").TreeseedCommandHandler;
|
|
3
4
|
readonly init: import("./operations-types.js").TreeseedCommandHandler;
|
|
4
5
|
readonly config: import("./operations-types.js").TreeseedCommandHandler;
|
|
5
6
|
readonly close: import("./operations-types.js").TreeseedCommandHandler;
|
package/dist/cli/registry.js
CHANGED
|
@@ -32,6 +32,8 @@ import { handleStage } from "./handlers/stage.js";
|
|
|
32
32
|
import { handleExport } from "./handlers/export.js";
|
|
33
33
|
import { handleResume } from "./handlers/resume.js";
|
|
34
34
|
import { handleRecover } from "./handlers/recover.js";
|
|
35
|
+
import { handleWorkspace } from "./handlers/workspace.js";
|
|
36
|
+
const workspaceCommand = (name) => `workspace${":"}${name}`;
|
|
35
37
|
const COMMAND_HANDLERS = {
|
|
36
38
|
init: handleInit,
|
|
37
39
|
config: handleConfig,
|
|
@@ -51,6 +53,9 @@ const COMMAND_HANDLERS = {
|
|
|
51
53
|
stage: handleStage,
|
|
52
54
|
resume: handleResume,
|
|
53
55
|
recover: handleRecover,
|
|
56
|
+
[workspaceCommand("status")]: handleWorkspace,
|
|
57
|
+
[workspaceCommand("link")]: handleWorkspace,
|
|
58
|
+
[workspaceCommand("unlink")]: handleWorkspace,
|
|
54
59
|
export: handleExport,
|
|
55
60
|
"auth:login": handleAuthLogin,
|
|
56
61
|
"auth:logout": handleAuthLogout,
|
package/dist/cli/runtime.js
CHANGED
|
@@ -54,8 +54,13 @@ function colorizeTreeseedCliOutput(output, colorEnabled = true) {
|
|
|
54
54
|
if (!colorEnabled) {
|
|
55
55
|
return output;
|
|
56
56
|
}
|
|
57
|
-
return output.replace(/^((?:\[[^\]]+\]){
|
|
57
|
+
return output.replace(/^((?:\[[^\]]+\]){2,4})(\s|$)/u, (match, prefix, suffix) => {
|
|
58
58
|
const segments = [...prefix.matchAll(/\[([^\]]+)\]/gu)].map((entry) => entry[1]);
|
|
59
|
+
if (segments.length === 2) {
|
|
60
|
+
const stage2 = segments[1] ?? "";
|
|
61
|
+
const code2 = /fail|error/iu.test(stage2) ? "31;1" : /skip/iu.test(stage2) ? "90;1" : "32;1";
|
|
62
|
+
return `\x1B[${code2}m${prefix}\x1B[0m${suffix}`;
|
|
63
|
+
}
|
|
59
64
|
const system = segments[1] ?? "";
|
|
60
65
|
const stage = segments[segments.length - 1] ?? "";
|
|
61
66
|
const code = /fail|error/iu.test(stage) ? "31;1" : /skip/iu.test(stage) ? "90;1" : colorCodeForBootstrapSystem(system);
|
|
@@ -226,20 +231,24 @@ class TreeseedOperationsSdk {
|
|
|
226
231
|
rawArgs: argv
|
|
227
232
|
};
|
|
228
233
|
const input = spec.buildAdapterInput?.(invocation, context) ?? {};
|
|
234
|
+
const adapterContext = {
|
|
235
|
+
...context,
|
|
236
|
+
outputFormat: invocation.args.json === true ? "json" : context.outputFormat ?? "human"
|
|
237
|
+
};
|
|
229
238
|
return sdkOperationsRuntime.execute(
|
|
230
239
|
{ operationName: spec.name, input },
|
|
231
240
|
{
|
|
232
|
-
cwd:
|
|
233
|
-
env:
|
|
234
|
-
write:
|
|
235
|
-
spawn:
|
|
236
|
-
outputFormat:
|
|
241
|
+
cwd: adapterContext.cwd,
|
|
242
|
+
env: adapterContext.env,
|
|
243
|
+
write: adapterContext.write,
|
|
244
|
+
spawn: adapterContext.spawn,
|
|
245
|
+
outputFormat: adapterContext.outputFormat,
|
|
237
246
|
transport: "cli"
|
|
238
247
|
}
|
|
239
|
-
).then((result) => writeTreeseedResult(result,
|
|
248
|
+
).then((result) => writeTreeseedResult(result, adapterContext)).catch((error) => writeTreeseedResult({
|
|
240
249
|
exitCode: 1,
|
|
241
250
|
stderr: [error instanceof Error ? error.message : String(error)]
|
|
242
|
-
},
|
|
251
|
+
}, adapterContext));
|
|
243
252
|
}
|
|
244
253
|
async executeAgents(argv, context) {
|
|
245
254
|
try {
|
|
@@ -321,7 +330,7 @@ function formatProjectError(spec) {
|
|
|
321
330
|
].join("\n");
|
|
322
331
|
}
|
|
323
332
|
function commandNeedsProjectRoot(spec) {
|
|
324
|
-
return spec.name !== "init" && spec.name !== "export";
|
|
333
|
+
return spec.name !== "init" && spec.name !== "export" && spec.name !== "install";
|
|
325
334
|
}
|
|
326
335
|
function resolveTreeseedCommandCwd(spec, cwd) {
|
|
327
336
|
if (!commandNeedsProjectRoot(spec)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeseed/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "Operator-facing Treeseed CLI package.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"lint": "npm run build:dist",
|
|
34
34
|
"test": "npm run build:dist && node --test --test-concurrency=1 ./scripts/treeseed-help.test.mjs ./scripts/wrapper-package.test.mjs",
|
|
35
35
|
"build:dist": "node ./scripts/run-ts.mjs ./scripts/build-dist.ts",
|
|
36
|
+
"prepare": "node ./scripts/prepare.mjs",
|
|
36
37
|
"prepack": "npm run build:dist",
|
|
37
38
|
"verify:direct": "npm run release:verify",
|
|
38
39
|
"verify:local": "node --input-type=module -e \"process.env.TREESEED_VERIFY_DRIVER='direct'; await import('./scripts/verify-driver.mjs')\"",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
|
-
"@treeseed/sdk": "
|
|
48
|
+
"@treeseed/sdk": "0.6.8",
|
|
48
49
|
"ink": "^7.0.0",
|
|
49
50
|
"react": "^19.2.5"
|
|
50
51
|
},
|