@treeseed/cli 0.6.45 → 0.6.47

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.
@@ -44,9 +44,9 @@ function formatSavePlanSections(repositoryPlan) {
44
44
  }
45
45
  const handleSave = async (invocation, context) => {
46
46
  try {
47
+ const progressWrite = context.outputFormat === "json" ? ((output) => context.write(output, "stderr")) : context.write;
47
48
  const result = await createWorkflowSdk(context, {
48
- write: context.outputFormat === "json" ? (() => {
49
- }) : context.write
49
+ write: progressWrite
50
50
  }).save({
51
51
  message: invocation.positionals.join(" ").trim(),
52
52
  hotfix: invocation.args.hotfix === true,
@@ -3,6 +3,9 @@ import {
3
3
  resolveTreeseedLaunchEnvironment,
4
4
  resolveTreeseedToolCommand
5
5
  } from "@treeseed/sdk/workflow-support";
6
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
6
9
  import { workflowErrorResult } from "./workflow.js";
7
10
  const WRAPPED_TOOLS = /* @__PURE__ */ new Set(["gh", "railway", "wrangler"]);
8
11
  const ENVIRONMENT_SCOPES = /* @__PURE__ */ new Set(["local", "staging", "prod"]);
@@ -21,7 +24,84 @@ function wrapperScope(value) {
21
24
  function railwayEnvironmentName(scope) {
22
25
  return scope === "prod" ? "production" : scope;
23
26
  }
27
+ function findRailwayProjectId(value) {
28
+ if (!value || typeof value !== "object") {
29
+ return null;
30
+ }
31
+ if (Array.isArray(value)) {
32
+ for (const entry of value) {
33
+ const found = findRailwayProjectId(entry);
34
+ if (found) {
35
+ return found;
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ const record = value;
41
+ if (typeof record.projectId === "string" && /^[0-9a-f]{8}-[0-9a-f-]{27,}$/iu.test(record.projectId.trim())) {
42
+ return record.projectId.trim();
43
+ }
44
+ if (typeof record.lastDeploymentCommand === "string") {
45
+ const match = record.lastDeploymentCommand.match(/--project\s+([0-9a-f]{8}-[0-9a-f-]{27,})/iu);
46
+ if (match?.[1]) {
47
+ return match[1];
48
+ }
49
+ }
50
+ for (const entry of Object.values(record)) {
51
+ const found = findRailwayProjectId(entry);
52
+ if (found) {
53
+ return found;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ function findRailwayProjectIdFromCommand(value) {
59
+ if (!value || typeof value !== "object") {
60
+ return null;
61
+ }
62
+ if (Array.isArray(value)) {
63
+ for (const entry of value) {
64
+ const found = findRailwayProjectIdFromCommand(entry);
65
+ if (found) {
66
+ return found;
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ const record = value;
72
+ if (typeof record.lastDeploymentCommand === "string") {
73
+ const match = record.lastDeploymentCommand.match(/--project\s+([0-9a-f]{8}-[0-9a-f-]{27,})/iu);
74
+ if (match?.[1]) {
75
+ return match[1];
76
+ }
77
+ }
78
+ for (const entry of Object.values(record)) {
79
+ const found = findRailwayProjectIdFromCommand(entry);
80
+ if (found) {
81
+ return found;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ function railwayProjectIdFromDeployState(cwd, scope) {
87
+ const stateScope = scope === "prod" ? "prod" : scope;
88
+ const statePath = join(cwd, ".treeseed", "state", "environments", stateScope, "deploy.json");
89
+ if (!existsSync(statePath)) {
90
+ return null;
91
+ }
92
+ try {
93
+ const state = JSON.parse(readFileSync(statePath, "utf8"));
94
+ return findRailwayProjectIdFromCommand(state) ?? findRailwayProjectId(state);
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ function railwayCommandUsesProjectFiles(args) {
100
+ const command = args[0] ?? "";
101
+ return ["up", "dev", "develop", "run", "local", "shell"].includes(command);
102
+ }
24
103
  const handleToolWrapper = (invocation, context) => {
104
+ let isolatedRailwayCwd = null;
25
105
  try {
26
106
  const toolName = wrappedToolName(invocation.commandName);
27
107
  const scope = wrapperScope(invocation.args.environment);
@@ -53,40 +133,91 @@ const handleToolWrapper = (invocation, context) => {
53
133
  };
54
134
  }
55
135
  const targetArgs = invocation.positionals;
56
- if (toolName === "railway" && scope !== "local") {
57
- const environmentResult = context.spawn(resolved.command, [
58
- ...resolved.argsPrefix,
59
- "environment",
60
- railwayEnvironmentName(scope),
61
- "--json"
62
- ], {
63
- cwd: context.cwd,
64
- env: managedEnv,
65
- stdio: "pipe"
66
- });
67
- if ((environmentResult.status ?? 1) !== 0) {
68
- return {
69
- exitCode: environmentResult.status ?? 1,
70
- stderr: [`Failed to select Railway environment ${railwayEnvironmentName(scope)} before running ${targetArgs.join(" ") || "railway"}.`],
71
- report: {
72
- command: toolName,
73
- ok: false,
74
- scope,
75
- executable: resolved.command,
76
- binaryPath: resolved.binaryPath,
77
- argsPrefix: resolved.argsPrefix,
78
- args: targetArgs,
79
- environmentSelection: {
80
- environment: railwayEnvironmentName(scope),
81
- status: environmentResult.status ?? 1
136
+ if (toolName === "railway" && scope !== "local" && targetArgs[0] !== "link") {
137
+ const environmentName = railwayEnvironmentName(scope);
138
+ const projectId = managedEnv.TREESEED_RAILWAY_PROJECT_ID || railwayProjectIdFromDeployState(context.cwd, scope);
139
+ const railwayCwd = railwayCommandUsesProjectFiles(targetArgs) ? context.cwd : isolatedRailwayCwd = mkdtempSync(join(tmpdir(), `treeseed-railway-${scope}-`));
140
+ const railwayEnv = isolatedRailwayCwd ? {
141
+ ...managedEnv,
142
+ HOME: isolatedRailwayCwd,
143
+ XDG_CONFIG_HOME: join(isolatedRailwayCwd, ".config")
144
+ } : managedEnv;
145
+ if (projectId) {
146
+ const linkResult = context.spawn(resolved.command, [
147
+ ...resolved.argsPrefix,
148
+ "link",
149
+ "--project",
150
+ projectId,
151
+ "--environment",
152
+ environmentName,
153
+ "--json"
154
+ ], {
155
+ cwd: railwayCwd,
156
+ env: railwayEnv,
157
+ stdio: "pipe"
158
+ });
159
+ if ((linkResult.status ?? 1) !== 0) {
160
+ return {
161
+ exitCode: linkResult.status ?? 1,
162
+ stderr: [`Failed to link Railway project ${projectId} for ${environmentName} before running ${targetArgs.join(" ") || "railway"}.`],
163
+ report: {
164
+ command: toolName,
165
+ ok: false,
166
+ scope,
167
+ executable: resolved.command,
168
+ binaryPath: resolved.binaryPath,
169
+ argsPrefix: resolved.argsPrefix,
170
+ args: targetArgs,
171
+ projectLink: {
172
+ projectId,
173
+ environment: environmentName,
174
+ status: linkResult.status ?? 1
175
+ }
82
176
  }
83
- }
84
- };
177
+ };
178
+ }
179
+ }
180
+ if (!(isolatedRailwayCwd && projectId)) {
181
+ const environmentResult = context.spawn(resolved.command, [
182
+ ...resolved.argsPrefix,
183
+ "environment",
184
+ environmentName,
185
+ "--json"
186
+ ], {
187
+ cwd: railwayCwd,
188
+ env: railwayEnv,
189
+ stdio: "pipe"
190
+ });
191
+ if ((environmentResult.status ?? 1) !== 0) {
192
+ return {
193
+ exitCode: environmentResult.status ?? 1,
194
+ stderr: [`Failed to select Railway environment ${railwayEnvironmentName(scope)} before running ${targetArgs.join(" ") || "railway"}.`],
195
+ report: {
196
+ command: toolName,
197
+ ok: false,
198
+ scope,
199
+ executable: resolved.command,
200
+ binaryPath: resolved.binaryPath,
201
+ argsPrefix: resolved.argsPrefix,
202
+ args: targetArgs,
203
+ environmentSelection: {
204
+ environment: environmentName,
205
+ status: environmentResult.status ?? 1
206
+ }
207
+ }
208
+ };
209
+ }
85
210
  }
86
211
  }
212
+ const railwayTargetCwd = toolName === "railway" && isolatedRailwayCwd ? isolatedRailwayCwd : context.cwd;
213
+ const targetEnv = toolName === "railway" && isolatedRailwayCwd ? {
214
+ ...managedEnv,
215
+ HOME: isolatedRailwayCwd,
216
+ XDG_CONFIG_HOME: join(isolatedRailwayCwd, ".config")
217
+ } : managedEnv;
87
218
  const result = context.spawn(resolved.command, [...resolved.argsPrefix, ...targetArgs], {
88
- cwd: context.cwd,
89
- env: managedEnv,
219
+ cwd: railwayTargetCwd,
220
+ env: targetEnv,
90
221
  stdio: "inherit"
91
222
  });
92
223
  return {
@@ -104,6 +235,13 @@ const handleToolWrapper = (invocation, context) => {
104
235
  };
105
236
  } catch (error) {
106
237
  return workflowErrorResult(error);
238
+ } finally {
239
+ if (isolatedRailwayCwd) {
240
+ try {
241
+ rmSync(isolatedRailwayCwd, { recursive: true, force: true });
242
+ } catch {
243
+ }
244
+ }
107
245
  }
108
246
  };
109
247
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.6.45",
3
+ "version": "0.6.47",
4
4
  "description": "Operator-facing Treeseed CLI package.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -45,7 +45,7 @@
45
45
  "release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@treeseed/sdk": "0.6.49",
48
+ "@treeseed/sdk": "0.6.51",
49
49
  "ink": "^7.0.0",
50
50
  "react": "^19.2.5"
51
51
  },