@treeseed/cli 0.8.14 → 0.8.15
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.
|
@@ -7,6 +7,26 @@ import { createMarketClientForInvocation, marketAuthRoot } from "./market-utils.
|
|
|
7
7
|
function sleep(ms) {
|
|
8
8
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
9
|
}
|
|
10
|
+
function localWebApprovalUrlFromApiUrl(value, profileId) {
|
|
11
|
+
const explicit = process.env.TREESEED_SITE_URL?.trim() || process.env.BETTER_AUTH_URL?.trim();
|
|
12
|
+
if (explicit && profileId === "local") {
|
|
13
|
+
const url2 = new URL(value);
|
|
14
|
+
return new URL(`${url2.pathname}${url2.search}${url2.hash}`, explicit.replace(/\/+$/u, "")).toString();
|
|
15
|
+
}
|
|
16
|
+
const url = new URL(value);
|
|
17
|
+
if (profileId === "local" && (url.hostname === "127.0.0.1" || url.hostname === "localhost") && url.port === "3000") {
|
|
18
|
+
url.port = "4321";
|
|
19
|
+
return url.toString();
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function approvalUrlForDisplay(value, profileId) {
|
|
24
|
+
try {
|
|
25
|
+
return localWebApprovalUrlFromApiUrl(value, profileId);
|
|
26
|
+
} catch {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
10
30
|
const handleAuthLogin = async (invocation, context) => {
|
|
11
31
|
try {
|
|
12
32
|
const tenantRoot = marketAuthRoot(context);
|
|
@@ -15,8 +35,9 @@ const handleAuthLogin = async (invocation, context) => {
|
|
|
15
35
|
clientName: "treeseed-cli",
|
|
16
36
|
scopes: ["auth:me", "market"]
|
|
17
37
|
});
|
|
38
|
+
const approvalUrl = approvalUrlForDisplay(started.verificationUriComplete, profile.id);
|
|
18
39
|
if (context.outputFormat !== "json") {
|
|
19
|
-
context.write(`Open ${
|
|
40
|
+
context.write(`Open ${approvalUrl}`, "stdout");
|
|
20
41
|
context.write(`User code: ${started.userCode}`, "stdout");
|
|
21
42
|
context.write("Waiting for approval...", "stdout");
|
|
22
43
|
}
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { pathToFileURL } from "node:url";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { formatSeedDiagnostics, formatSeedPlan, loadAndPlanSeed } from "@treeseed/sdk/seeds";
|
|
5
5
|
import { MarketApiError } from "@treeseed/sdk/market-client";
|
|
6
|
-
import { createMarketClientForInvocation } from "./market-utils.js";
|
|
6
|
+
import { createMarketClientForInvocation, marketAuthRoot, marketSelector } from "./market-utils.js";
|
|
7
|
+
import { resolveMarketProfile, resolveMarketSession } from "@treeseed/sdk/market-client";
|
|
7
8
|
async function loadLocalSeedModule(projectRoot) {
|
|
8
|
-
const
|
|
9
|
+
const apiModulePath = resolve(projectRoot, "src", "lib", "market", "seeds", "local-api.js");
|
|
10
|
+
const moduleUrl = pathToFileURL(existsSync(apiModulePath) ? apiModulePath : resolve(projectRoot, "src", "lib", "market", "seeds", "apply.js")).href;
|
|
9
11
|
return await import(moduleUrl);
|
|
10
12
|
}
|
|
13
|
+
function requireLocalSeedSession(invocation, context) {
|
|
14
|
+
const selector = marketSelector(invocation) ?? "local";
|
|
15
|
+
const profile = resolveMarketProfile(selector);
|
|
16
|
+
const session = resolveMarketSession(marketAuthRoot(context), profile.id);
|
|
17
|
+
if (!session?.accessToken) {
|
|
18
|
+
throw new Error(`Not logged in to market "${profile.id}". Run treeseed auth:login --market ${profile.id}.`);
|
|
19
|
+
}
|
|
20
|
+
return { profile, session };
|
|
21
|
+
}
|
|
11
22
|
function seedRequestBody(invocation) {
|
|
12
23
|
return {
|
|
13
24
|
...typeof invocation.args.environments === "string" ? { environments: invocation.args.environments.split(",").map((entry) => entry.trim()).filter(Boolean) } : {},
|
|
@@ -116,17 +127,20 @@ async function handleSeedExport(invocation, context) {
|
|
|
116
127
|
const { client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
|
|
117
128
|
payload = await client.exportSeed(team, body);
|
|
118
129
|
} else {
|
|
130
|
+
const { session } = requireLocalSeedSession(invocation, context);
|
|
119
131
|
const localModule = await loadLocalSeedModule(context.cwd);
|
|
120
|
-
|
|
132
|
+
const exporter = localModule.exportLocalSeedViaApiFromCli ?? localModule.exportSeedFromCli;
|
|
133
|
+
if (typeof exporter !== "function") {
|
|
121
134
|
throw new Error("Local seed export service is not available in this market project.");
|
|
122
135
|
}
|
|
123
|
-
payload = await
|
|
136
|
+
payload = await exporter({
|
|
124
137
|
projectRoot: context.cwd,
|
|
125
138
|
seedName,
|
|
126
139
|
team,
|
|
127
140
|
environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
|
|
128
141
|
includePrivate: invocation.args.includePrivate === true,
|
|
129
142
|
includeArtifacts: invocation.args.includeArtifacts === true,
|
|
143
|
+
accessToken: session.accessToken,
|
|
130
144
|
env: context.env
|
|
131
145
|
});
|
|
132
146
|
}
|
|
@@ -187,22 +201,37 @@ const handleSeed = async (invocation, context) => {
|
|
|
187
201
|
}
|
|
188
202
|
};
|
|
189
203
|
}
|
|
204
|
+
const localAuth = planned.plan.environments.every((environment) => environment === "local") && !wantsValidate ? (() => {
|
|
205
|
+
try {
|
|
206
|
+
return requireLocalSeedSession(invocation, context);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return { error };
|
|
209
|
+
}
|
|
210
|
+
})() : null;
|
|
211
|
+
if (localAuth && "error" in localAuth) {
|
|
212
|
+
return remoteSeedError(localAuth.error, "seed");
|
|
213
|
+
}
|
|
190
214
|
if (!wantsApply && !wantsValidate && planned.plan.environments.length === 1 && planned.plan.environments[0] === "local") {
|
|
191
215
|
try {
|
|
192
216
|
const localModule = await loadLocalSeedModule(context.cwd);
|
|
193
|
-
|
|
194
|
-
|
|
217
|
+
const localPlanner = localModule.planLocalSeedViaApiFromCli ?? localModule.planLocalSeedFromCli;
|
|
218
|
+
if (typeof localPlanner === "function") {
|
|
219
|
+
const localPlanned = await localPlanner({
|
|
195
220
|
projectRoot: context.cwd,
|
|
196
221
|
seedName,
|
|
197
222
|
environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
|
|
198
223
|
mode,
|
|
224
|
+
accessToken: localAuth?.session.accessToken,
|
|
199
225
|
env: context.env
|
|
200
226
|
});
|
|
201
227
|
if (localPlanned.plan) {
|
|
202
228
|
planned.plan = localPlanned.plan;
|
|
203
229
|
}
|
|
204
230
|
}
|
|
205
|
-
} catch {
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (error instanceof Error && /not logged in|authentication|permission denied/iu.test(error.message)) {
|
|
233
|
+
return remoteSeedError(error, "seed");
|
|
234
|
+
}
|
|
206
235
|
planned.plan.diagnostics.push({
|
|
207
236
|
severity: "warning",
|
|
208
237
|
code: "seed.local_state_unavailable",
|
|
@@ -222,15 +251,16 @@ const handleSeed = async (invocation, context) => {
|
|
|
222
251
|
}
|
|
223
252
|
}
|
|
224
253
|
const localModule = await loadLocalSeedModule(context.cwd);
|
|
225
|
-
|
|
254
|
+
const runner = localModule.applyLocalSeedViaApiFromCli ?? localModule.applyLocalSeedFromCli;
|
|
255
|
+
if (typeof runner !== "function") {
|
|
226
256
|
throw new Error("Local seed apply service is not available in this market project.");
|
|
227
257
|
}
|
|
228
|
-
const runner = localModule.applyLocalSeedFromCli;
|
|
229
258
|
const applied = await runner({
|
|
230
259
|
projectRoot: context.cwd,
|
|
231
260
|
seedName,
|
|
232
261
|
environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
|
|
233
262
|
plan: planned.plan,
|
|
263
|
+
accessToken: localAuth?.session.accessToken,
|
|
234
264
|
env: context.env
|
|
235
265
|
});
|
|
236
266
|
const message = "Local seed apply completed.";
|
|
@@ -30,7 +30,7 @@ const DEV_RUNTIME_OPTIONS = [
|
|
|
30
30
|
{ name: "managerPort", flags: "--manager-port <port>", description: "Port used for the local manager service URL.", kind: "string" },
|
|
31
31
|
{ name: "setup", flags: "--setup <mode>", description: "Control automatic local runtime setup.", kind: "enum", values: ["auto", "check", "off"] },
|
|
32
32
|
{ name: "feedback", flags: "--feedback <mode>", description: "Control live feedback, service restarts, and browser reload stamps.", kind: "enum", values: ["live", "restart", "off"] },
|
|
33
|
-
{ name: "open", flags: "--open <mode>", description: "Control whether dev opens the browser after readiness.", kind: "enum", values: ["auto", "on", "off"] },
|
|
33
|
+
{ name: "open", flags: "--open <mode>", description: "Control whether dev opens the browser after readiness. Defaults to off; use --open on to launch it.", kind: "enum", values: ["auto", "on", "off"] },
|
|
34
34
|
{ name: "plan", flags: "--plan", description: "Print the dev runtime plan and exit without starting services.", kind: "boolean" },
|
|
35
35
|
{ name: "reset", flags: "--reset", description: "Clear local dev runtime state before setup, migrations, and service startup.", kind: "boolean" },
|
|
36
36
|
{ name: "json", flags: "--json", description: "Emit structured JSON or newline-delimited dev events.", kind: "boolean" },
|
|
@@ -726,6 +726,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
726
726
|
longSummary: ["Auth:login authenticates the CLI against the configured Treeseed API so later provider-aware and remote-aware workflows can run without missing-credential failures."],
|
|
727
727
|
examples: [
|
|
728
728
|
example("treeseed auth:login", "Log in with the default host", "Authenticate the CLI against the configured default Treeseed API host."),
|
|
729
|
+
example("treeseed auth:login --market local", "Log in to local dev", "Authenticate against the local Treeseed API at TREESEED_MARKET_API_BASE_URL or http://127.0.0.1:3000."),
|
|
729
730
|
example("treeseed auth:login --host production", "Target a specific host id", "Override the configured default host for this login session."),
|
|
730
731
|
example("treeseed auth:login --json", "Automate auth workflows", "Emit structured auth results where supported for scripts and agents.")
|
|
731
732
|
]
|
|
@@ -1171,7 +1172,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
1171
1172
|
})],
|
|
1172
1173
|
["dev", command({
|
|
1173
1174
|
options: DEV_RUNTIME_OPTIONS,
|
|
1174
|
-
examples: ["treeseed dev", "treeseed dev --reset", "treeseed dev --reset --plan --json", "treeseed dev --surfaces web,api --plan --json", "treeseed dev --surface web --port 4322
|
|
1175
|
+
examples: ["treeseed dev", "treeseed dev --reset", "treeseed dev --reset --plan --json", "treeseed dev --surfaces web,api --plan --json", "treeseed dev --surface web --port 4322"],
|
|
1175
1176
|
help: {
|
|
1176
1177
|
longSummary: [
|
|
1177
1178
|
"Dev starts the unified local Treeseed runtime as a foreground supervisor so you can work against the integrated web, API, manager, worker, and supporting local surfaces.",
|
|
@@ -1181,6 +1182,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
1181
1182
|
"Run from the tenant or workspace root you want to develop.",
|
|
1182
1183
|
"Use `--plan --json` when you want to inspect selected surfaces, commands, setup steps, readiness checks, watched paths, and restart policy without starting services.",
|
|
1183
1184
|
"Use `--reset` when you want a fresh local D1 database, Mailpit inbox, generated worker bundle, and Wrangler temp output without deleting configuration.",
|
|
1185
|
+
"Dev prints the local web URL after readiness; it does not open a browser unless you pass `--open on` or `--open auto`.",
|
|
1184
1186
|
"Keep the foreground process running while you test. Press Ctrl+C to stop the supervised stack and free the local ports."
|
|
1185
1187
|
],
|
|
1186
1188
|
examples: [
|
|
@@ -1191,12 +1193,13 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
1191
1193
|
example("treeseed dev --surfaces web,api,worker --plan --json", "Inspect selected surfaces", "Plan a comma-separated set of local surfaces without starting the supervisor."),
|
|
1192
1194
|
example("treeseed dev --surface api --plan --json", "Inspect the API surface", "Plan the local API runtime without the web UI."),
|
|
1193
1195
|
example("treeseed dev --surfaces integrated,agents", "Opt into agents diagnostics", "Start the integrated local platform plus the agents diagnostic loop."),
|
|
1194
|
-
example("treeseed dev --surface web --port 4322
|
|
1196
|
+
example("treeseed dev --surface web --port 4322", "Run only the web surface", "Start the Astro UI on a specific port and print the URL when ready."),
|
|
1197
|
+
example("treeseed dev --open on", "Open the browser explicitly", "Start the integrated runtime and launch the local web URL after readiness."),
|
|
1195
1198
|
example("trsd dev", "Use the short alias", "Start the same local runtime through the shorter entrypoint."),
|
|
1196
1199
|
example("treeseed dev --json", "Stream dev events", "Emit newline-delimited events while the long-running dev process supervises local services.")
|
|
1197
1200
|
],
|
|
1198
1201
|
outcomes: [
|
|
1199
|
-
"Starts the selected local surfaces, waits for readiness, and then remains attached as the live supervisor.",
|
|
1202
|
+
"Starts the selected local surfaces, waits for readiness, prints the local URL, and then remains attached as the live supervisor.",
|
|
1200
1203
|
"Restarts required crashed surfaces with capped exponential backoff and keeps setup/readiness failures alive for retry.",
|
|
1201
1204
|
"Stops watchers first and then terminates service process groups when the foreground command exits."
|
|
1202
1205
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeseed/cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.15",
|
|
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.8.
|
|
48
|
+
"@treeseed/sdk": "0.8.15",
|
|
49
49
|
"ink": "^7.0.0",
|
|
50
50
|
"react": "^19.2.5"
|
|
51
51
|
},
|