@treeseed/cli 0.4.7 → 0.4.9

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.
@@ -1,54 +1,124 @@
1
- import readline from "node:readline/promises";
2
- import { stdin as input, stdout as output } from "node:process";
3
- import { guidedResult } from "./utils.js";
1
+ import {
2
+ applyTreeseedSafeRepairs,
3
+ collectTreeseedConfigContext,
4
+ findNearestTreeseedRoot
5
+ } from "@treeseed/sdk/workflow-support";
6
+ import { fail, guidedResult } from "./utils.js";
7
+ import { buildCliConfigPages, runCliConfigEditor } from "./config-ui.js";
4
8
  import { createWorkflowSdk, renderWorkflowNextSteps, workflowErrorResult } from "./workflow.js";
9
+ function normalizeConfigScopes(value) {
10
+ const requested = Array.isArray(value) ? value.map(String) : typeof value === "string" ? [value] : ["all"];
11
+ if (requested.includes("all")) {
12
+ return ["local", "staging", "prod"];
13
+ }
14
+ return ["local", "staging", "prod"].filter((scope) => requested.includes(scope));
15
+ }
16
+ function formatPrintEnvReports(payload) {
17
+ const lines = [];
18
+ for (const report of payload.reports ?? []) {
19
+ lines.push(`Resolved environment values for ${report.scope}`);
20
+ lines.push(payload.secretsRevealed ? "Secrets are shown." : "Secret values are masked.");
21
+ for (const entry of report.environment?.entries ?? []) {
22
+ lines.push(`${entry.id}=${entry.displayValue} (${entry.source})`);
23
+ }
24
+ lines.push("");
25
+ lines.push(`Provider connection checks for ${report.scope}`);
26
+ for (const check of report.provider?.checks ?? []) {
27
+ const status = check.ready ? "ready" : check.skipped ? "skipped" : "failed";
28
+ lines.push(`${check.provider}: ${status} - ${check.detail}`);
29
+ }
30
+ lines.push("");
31
+ }
32
+ return lines.filter((line, index, all) => !(line === "" && all[index - 1] === ""));
33
+ }
34
+ function renderConfigResult(commandName, result) {
35
+ const payload = result.payload;
36
+ const toolHealth = payload.toolHealth;
37
+ const summary = payload.mode === "print-env-only" ? "Treeseed config environment report completed." : payload.mode === "rotate-machine-key" ? "Treeseed machine key rotated successfully." : "Treeseed config completed successfully.";
38
+ return guidedResult({
39
+ command: commandName,
40
+ summary,
41
+ facts: [
42
+ { label: "Mode", value: payload.mode },
43
+ { label: "Scopes", value: Array.isArray(payload.scopes) ? payload.scopes.join(", ") : "(none)" },
44
+ { label: "Sync", value: payload.sync ?? "all" },
45
+ { label: "Safe repairs", value: Array.isArray(payload.repairs) ? payload.repairs.length : 0 },
46
+ { label: "Machine config", value: payload.configPath },
47
+ { label: "Machine key", value: payload.keyPath },
48
+ { label: "GitHub CLI", value: toolHealth?.githubCli?.available ? "ready" : "missing" },
49
+ { label: "gh act", value: toolHealth?.ghActExtension?.available ? "ready" : "missing" },
50
+ { label: "Docker", value: toolHealth?.dockerDaemon?.available ? "ready" : "missing" },
51
+ { label: "ACT verify", value: toolHealth?.actVerificationReady ? "ready" : "not ready" }
52
+ ],
53
+ nextSteps: renderWorkflowNextSteps(result),
54
+ report: payload
55
+ });
56
+ }
5
57
  const handleConfig = async (invocation, context) => {
6
- const rl = readline.createInterface({ input, output });
7
58
  try {
8
59
  const workflow = createWorkflowSdk(context, {
9
60
  write: context.outputFormat === "json" ? (() => {
10
- }) : context.write,
11
- prompt: async (message) => {
12
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
13
- return "";
14
- }
15
- return rl.question(message);
16
- }
61
+ }) : context.write
17
62
  });
63
+ const scopes = normalizeConfigScopes(invocation.args.environment);
64
+ const sync = invocation.args.sync;
65
+ const interactive = context.outputFormat !== "json" && process.stdin.isTTY && process.stdout.isTTY;
66
+ if (interactive && invocation.args.printEnvOnly !== true && invocation.args.rotateMachineKey !== true) {
67
+ const tenantRoot = findNearestTreeseedRoot(context.cwd) ?? context.cwd;
68
+ if (!tenantRoot) {
69
+ return fail("Treeseed config requires a Treeseed project. Run the command from inside a tenant or initialize one first.");
70
+ }
71
+ applyTreeseedSafeRepairs(tenantRoot);
72
+ const configContext = collectTreeseedConfigContext({
73
+ tenantRoot,
74
+ scopes,
75
+ env: context.env
76
+ });
77
+ const editorResult = await runCliConfigEditor(configContext, {
78
+ initialViewMode: invocation.args.full === true ? "full" : "startup"
79
+ });
80
+ if (editorResult === null) {
81
+ return fail("Treeseed config canceled.");
82
+ }
83
+ const updates = buildCliConfigPages(configContext, "all", editorResult.overrides, "full").map((page) => ({
84
+ scope: page.scope,
85
+ entryId: page.entry.id,
86
+ value: page.finalValue,
87
+ reused: !(page.key in editorResult.overrides)
88
+ }));
89
+ const result2 = await workflow.config({
90
+ environment: scopes,
91
+ sync,
92
+ printEnv: invocation.args.printEnv === true,
93
+ showSecrets: invocation.args.showSecrets === true,
94
+ nonInteractive: true,
95
+ updates
96
+ });
97
+ return renderConfigResult(invocation.commandName || "config", result2);
98
+ }
18
99
  const result = await workflow.config({
19
100
  environment: invocation.args.environment,
20
- sync: invocation.args.sync,
101
+ sync,
21
102
  printEnv: invocation.args.printEnv === true,
22
103
  printEnvOnly: invocation.args.printEnvOnly === true,
23
104
  showSecrets: invocation.args.showSecrets === true,
24
105
  rotateMachineKey: invocation.args.rotateMachineKey === true,
25
106
  nonInteractive: context.outputFormat === "json"
26
107
  });
27
- const payload = result.payload;
28
- const toolHealth = payload.toolHealth;
29
- const summary = payload.mode === "print-env-only" ? "Treeseed config environment report completed." : payload.mode === "rotate-machine-key" ? "Treeseed machine key rotated successfully." : "Treeseed config completed successfully.";
30
- return guidedResult({
31
- command: invocation.commandName || "config",
32
- summary,
33
- facts: [
34
- { label: "Mode", value: payload.mode },
35
- { label: "Scopes", value: Array.isArray(payload.scopes) ? payload.scopes.join(", ") : "(none)" },
36
- { label: "Sync", value: payload.sync ?? "all" },
37
- { label: "Safe repairs", value: Array.isArray(payload.repairs) ? payload.repairs.length : 0 },
38
- { label: "Machine config", value: payload.configPath },
39
- { label: "Machine key", value: payload.keyPath },
40
- { label: "GitHub CLI", value: toolHealth?.githubCli?.available ? "ready" : "missing" },
41
- { label: "gh act", value: toolHealth?.ghActExtension?.available ? "ready" : "missing" },
42
- { label: "Docker", value: toolHealth?.dockerDaemon?.available ? "ready" : "missing" },
43
- { label: "ACT verify", value: toolHealth?.actVerificationReady ? "ready" : "not ready" }
44
- ],
45
- nextSteps: renderWorkflowNextSteps(result),
46
- report: payload
47
- });
108
+ if (context.outputFormat !== "json" && result.payload.mode === "print-env-only") {
109
+ return {
110
+ exitCode: 0,
111
+ stdout: formatPrintEnvReports(result.payload),
112
+ report: {
113
+ command: invocation.commandName || "config",
114
+ ok: true,
115
+ ...result.payload
116
+ }
117
+ };
118
+ }
119
+ return renderConfigResult(invocation.commandName || "config", result);
48
120
  } catch (error) {
49
121
  return workflowErrorResult(error);
50
- } finally {
51
- rl.close();
52
122
  }
53
123
  };
54
124
  export {
@@ -0,0 +1,2 @@
1
+ import type { TreeseedCommandHandler } from '../types.js';
2
+ export declare const handleExport: TreeseedCommandHandler;
@@ -0,0 +1,28 @@
1
+ import { guidedResult } from "./utils.js";
2
+ import { createWorkflowSdk, workflowErrorResult } from "./workflow.js";
3
+ const handleExport = async (invocation, context) => {
4
+ try {
5
+ const directory = typeof invocation.positionals[0] === "string" && invocation.positionals[0].trim().length > 0 ? invocation.positionals[0] : void 0;
6
+ const result = await createWorkflowSdk(context).export({ directory });
7
+ const exported = result.payload;
8
+ return guidedResult({
9
+ command: "export",
10
+ summary: "Treeseed export completed successfully.",
11
+ facts: [
12
+ { label: "Directory", value: exported.directory },
13
+ { label: "Output", value: exported.outputPath },
14
+ { label: "Branch", value: exported.branch },
15
+ { label: "Timestamp", value: exported.timestamp },
16
+ { label: "Files", value: exported.summary?.totalFiles },
17
+ { label: "Tokens", value: exported.summary?.totalTokens },
18
+ { label: "Bundled paths", value: Array.isArray(exported.includedBundlePaths) ? exported.includedBundlePaths.length : 0 }
19
+ ],
20
+ report: exported
21
+ });
22
+ } catch (error) {
23
+ return workflowErrorResult(error);
24
+ }
25
+ };
26
+ export {
27
+ handleExport
28
+ };
@@ -14,7 +14,9 @@ const handleRelease = async (invocation, context) => {
14
14
  { label: "Release level", value: payload.level },
15
15
  { label: "Root version", value: payload.rootVersion },
16
16
  { label: "Release tag", value: payload.releaseTag },
17
- { label: "Updated packages", value: payload.touchedPackages.length }
17
+ { label: "Released commit", value: payload.releasedCommit.slice(0, 12) },
18
+ { label: "Updated packages", value: payload.touchedPackages.length },
19
+ { label: "Final branch", value: payload.finalBranch }
18
20
  ],
19
21
  nextSteps: renderWorkflowNextSteps(result),
20
22
  report: payload
@@ -4,18 +4,20 @@ const handleSave = async (invocation, context) => {
4
4
  try {
5
5
  const result = await createWorkflowSdk(context).save({
6
6
  message: invocation.positionals.join(" ").trim(),
7
- hotfix: invocation.args.hotfix === true
7
+ hotfix: invocation.args.hotfix === true,
8
+ preview: invocation.args.preview === true
8
9
  });
9
10
  const payload = result.payload;
10
11
  return guidedResult({
11
12
  command: invocation.commandName || "save",
12
- summary: "Treeseed save completed successfully.",
13
+ summary: payload.noChanges ? "Treeseed save found no new changes and confirmed branch sync." : "Treeseed save completed successfully.",
13
14
  facts: [
14
15
  { label: "Branch", value: payload.branch },
15
16
  { label: "Environment scope", value: payload.scope },
16
17
  { label: "Hotfix", value: payload.hotfix ? "yes" : "no" },
17
18
  { label: "Commit", value: payload.commitSha.slice(0, 12) },
18
- { label: "Preview refreshed", value: payload.previewRefresh ? "yes" : "no" }
19
+ { label: "Commit created", value: payload.commitCreated ? "yes" : "no" },
20
+ { label: "Preview action", value: payload.previewAction?.status ?? "skipped" }
19
21
  ],
20
22
  nextSteps: renderWorkflowNextSteps(result),
21
23
  report: payload
@@ -12,9 +12,11 @@ const handleStage = async (invocation, context) => {
12
12
  facts: [
13
13
  { label: "Merged branch", value: payload.branchName },
14
14
  { label: "Merge target", value: payload.mergeTarget },
15
+ { label: "Auto-saved", value: payload.autoSaved ? "yes" : "no" },
15
16
  { label: "Deprecated tag", value: payload.deprecatedTag.tagName },
16
17
  { label: "Staging wait", value: payload.stagingWait.status },
17
- { label: "Preview cleanup", value: payload.previewCleanup.performed ? "performed" : "not needed" }
18
+ { label: "Preview cleanup", value: payload.previewCleanup.performed ? "performed" : "not needed" },
19
+ { label: "Final branch", value: payload.finalBranch }
18
20
  ],
19
21
  nextSteps: renderWorkflowNextSteps(result),
20
22
  report: payload
@@ -0,0 +1,3 @@
1
+ import type { TreeseedCommandContext } from './operations-types.js';
2
+ export declare function renderTreeseedHelpInk(commandName: string | null | undefined, context?: Pick<TreeseedCommandContext, 'outputFormat' | 'interactiveUi'>): Promise<number | null>;
3
+ export declare function shouldUseInkHelp(context: Pick<TreeseedCommandContext, 'outputFormat' | 'interactiveUi'>): boolean;