@lizard-build/cli 0.1.0 → 0.3.29
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/.github/workflows/release.yml +90 -0
- package/README.md +41 -0
- package/dist/commands/add.js +318 -45
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +68 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/docs.d.ts +2 -0
- package/dist/commands/docs.js +13 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/domain.d.ts +9 -0
- package/dist/commands/domain.js +195 -0
- package/dist/commands/domain.js.map +1 -0
- package/dist/commands/git.js +175 -36
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/init.d.ts +24 -0
- package/dist/commands/init.js +128 -86
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +7 -0
- package/dist/commands/link.js +104 -33
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.js +4 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logs.js +223 -30
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/open.js +3 -2
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/port.d.ts +7 -0
- package/dist/commands/port.js +49 -0
- package/dist/commands/port.js.map +1 -0
- package/dist/commands/projects.js +36 -6
- package/dist/commands/projects.js.map +1 -1
- package/dist/commands/ps.js +32 -39
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/redeploy.js +48 -8
- package/dist/commands/redeploy.js.map +1 -1
- package/dist/commands/regions.js +2 -5
- package/dist/commands/regions.js.map +1 -1
- package/dist/commands/restart.js +84 -10
- package/dist/commands/restart.js.map +1 -1
- package/dist/commands/run.d.ts +9 -0
- package/dist/commands/run.js +61 -22
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/scale.d.ts +10 -0
- package/dist/commands/scale.js +166 -0
- package/dist/commands/scale.js.map +1 -0
- package/dist/commands/secrets.js +200 -89
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/service-set.d.ts +49 -0
- package/dist/commands/service-set.js +552 -0
- package/dist/commands/service-set.js.map +1 -0
- package/dist/commands/service-show.d.ts +11 -0
- package/dist/commands/service-show.js +44 -0
- package/dist/commands/service-show.js.map +1 -0
- package/dist/commands/service.d.ts +8 -0
- package/dist/commands/service.js +262 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/ssh.d.ts +2 -0
- package/dist/commands/ssh.js +161 -0
- package/dist/commands/ssh.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +49 -38
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/unlink.d.ts +5 -0
- package/dist/commands/unlink.js +18 -0
- package/dist/commands/unlink.js.map +1 -0
- package/dist/commands/up.d.ts +9 -0
- package/dist/commands/up.js +417 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +79 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/whoami.js +26 -6
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/workspace.d.ts +8 -0
- package/dist/commands/workspace.js +36 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/index.js +204 -82
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +17 -2
- package/dist/lib/api.js +85 -51
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/auth.d.ts +3 -11
- package/dist/lib/auth.js +16 -36
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.d.ts +36 -15
- package/dist/lib/config.js +71 -58
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/format.d.ts +1 -0
- package/dist/lib/format.js +17 -4
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/name.d.ts +11 -0
- package/dist/lib/name.js +26 -0
- package/dist/lib/name.js.map +1 -0
- package/dist/lib/picker.d.ts +32 -0
- package/dist/lib/picker.js +91 -0
- package/dist/lib/picker.js.map +1 -0
- package/dist/lib/resolve.d.ts +85 -0
- package/dist/lib/resolve.js +203 -0
- package/dist/lib/resolve.js.map +1 -0
- package/dist/lib/updater.d.ts +16 -0
- package/dist/lib/updater.js +102 -0
- package/dist/lib/updater.js.map +1 -0
- package/lizard-wrapper.sh +2 -0
- package/package.json +11 -3
- package/src/commands/add.ts +388 -56
- package/src/commands/config.ts +80 -0
- package/src/commands/docs.ts +15 -0
- package/src/commands/domain.ts +248 -0
- package/src/commands/git.ts +201 -40
- package/src/commands/init.ts +149 -100
- package/src/commands/link.ts +127 -35
- package/src/commands/login.ts +4 -3
- package/src/commands/logs.ts +283 -27
- package/src/commands/open.ts +3 -2
- package/src/commands/port.ts +57 -0
- package/src/commands/projects.ts +43 -6
- package/src/commands/ps.ts +39 -60
- package/src/commands/redeploy.ts +51 -10
- package/src/commands/regions.ts +2 -6
- package/src/commands/restart.ts +84 -10
- package/src/commands/run.ts +68 -24
- package/src/commands/scale.ts +216 -0
- package/src/commands/secrets.ts +277 -100
- package/src/commands/service-set.ts +669 -0
- package/src/commands/service-show.ts +52 -0
- package/src/commands/service.ts +298 -0
- package/src/commands/ssh.ts +176 -0
- package/src/commands/status.ts +51 -46
- package/src/commands/unlink.ts +17 -0
- package/src/commands/up.ts +461 -0
- package/src/commands/upgrade.ts +87 -0
- package/src/commands/whoami.ts +34 -6
- package/src/commands/workspace.ts +44 -0
- package/src/index.ts +214 -85
- package/src/lib/api.ts +114 -51
- package/src/lib/auth.ts +22 -46
- package/src/lib/config.ts +100 -65
- package/src/lib/format.ts +18 -4
- package/src/lib/name.ts +27 -0
- package/src/lib/picker.ts +133 -0
- package/src/lib/resolve.ts +285 -0
- package/src/lib/updater.ts +106 -0
- package/test/cli.test.ts +491 -0
- package/test/fixtures/hello-app/Dockerfile +5 -0
- package/test/fixtures/hello-app/index.js +5 -0
- package/test/unit/api.test.ts +66 -0
- package/test/unit/config.test.ts +94 -0
- package/test/unit/init.test.ts +211 -0
- package/test/unit/json.test.ts +208 -0
- package/test/unit/picker.test.ts +161 -0
- package/test/unit/resolve.test.ts +124 -0
- package/test/unit/service-set.test.ts +355 -0
- package/vitest.config.ts +10 -0
- package/dist/commands/connect.d.ts +0 -2
- package/dist/commands/connect.js +0 -117
- package/dist/commands/connect.js.map +0 -1
- package/dist/commands/context.d.ts +0 -2
- package/dist/commands/context.js +0 -71
- package/dist/commands/context.js.map +0 -1
- package/dist/commands/deploy.d.ts +0 -2
- package/dist/commands/deploy.js +0 -120
- package/dist/commands/deploy.js.map +0 -1
- package/dist/commands/destroy.d.ts +0 -2
- package/dist/commands/destroy.js +0 -51
- package/dist/commands/destroy.js.map +0 -1
- package/dist/commands/update.d.ts +0 -2
- package/dist/commands/update.js +0 -41
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/version.d.ts +0 -2
- package/dist/commands/version.js +0 -37
- package/dist/commands/version.js.map +0 -1
- package/src/commands/connect.ts +0 -145
- package/src/commands/context.ts +0 -93
- package/src/commands/deploy.ts +0 -153
- package/src/commands/destroy.ts +0 -51
- package/src/commands/update.ts +0 -44
- package/src/commands/version.ts +0 -37
package/src/index.ts
CHANGED
|
@@ -1,126 +1,247 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
+
import chalk from "chalk";
|
|
4
5
|
import { setJSONMode, isJSONMode, error } from "./lib/format.js";
|
|
5
|
-
import {
|
|
6
|
-
import { setBaseURL, setAccessToken } from "./lib/api.js";
|
|
6
|
+
import { requireAuth, isLoggedIn } from "./lib/auth.js";
|
|
7
|
+
import { setBaseURL, setAccessToken, APIError } from "./lib/api.js";
|
|
8
|
+
import { checkForUpdateInBackground, CURRENT_VERSION } from "./lib/updater.js";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const BANNER = chalk.rgb(16, 185, 129)(
|
|
11
|
+
[
|
|
12
|
+
"╔═══════════════════════════════════════════════════╗",
|
|
13
|
+
"║ ║",
|
|
14
|
+
"║ ██╗ ██╗███████╗ █████╗ ██████╗ ██████╗ ║",
|
|
15
|
+
"║ ██║ ██║╚══███╔╝██╔══██╗██╔══██╗██╔══██╗ ║",
|
|
16
|
+
"║ ██║ ██║ ███╔╝ ███████║██████╔╝██║ ██║ ║",
|
|
17
|
+
"║ ██║ ██║ ███╔╝ ██╔══██║██╔══██╗██║ ██║ ║",
|
|
18
|
+
"║ ███████╗██║███████╗██║ ██║██║ ██║██████╔╝ ║",
|
|
19
|
+
"║ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ║",
|
|
20
|
+
"║ ║",
|
|
21
|
+
"╚═══════════════════════════════════════════════════╝",
|
|
22
|
+
].join("\n"),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Commands (alphabetical by command name)
|
|
26
|
+
import { registerAdd } from "./commands/add.js";
|
|
27
|
+
import { registerConfig } from "./commands/config.js";
|
|
28
|
+
import { registerDocs } from "./commands/docs.js";
|
|
29
|
+
import { registerDomain } from "./commands/domain.js";
|
|
30
|
+
import { registerGit } from "./commands/git.js";
|
|
12
31
|
import { registerInit } from "./commands/init.js";
|
|
13
32
|
import { registerLink } from "./commands/link.js";
|
|
33
|
+
import { registerLogin } from "./commands/login.js";
|
|
34
|
+
import { registerLogout } from "./commands/logout.js";
|
|
35
|
+
import { registerLogs } from "./commands/logs.js";
|
|
36
|
+
import { registerOpen } from "./commands/open.js";
|
|
37
|
+
import { registerPort } from "./commands/port.js";
|
|
14
38
|
import { registerProjects } from "./commands/projects.js";
|
|
15
|
-
import { registerDeploy } from "./commands/deploy.js";
|
|
16
39
|
import { registerPs } from "./commands/ps.js";
|
|
17
|
-
import { registerAdd } from "./commands/add.js";
|
|
18
|
-
import { registerDestroy } from "./commands/destroy.js";
|
|
19
|
-
import { registerRestart } from "./commands/restart.js";
|
|
20
40
|
import { registerRedeploy } from "./commands/redeploy.js";
|
|
21
|
-
import { registerLogs } from "./commands/logs.js";
|
|
22
|
-
import { registerSecrets } from "./commands/secrets.js";
|
|
23
41
|
import { registerRegions } from "./commands/regions.js";
|
|
24
|
-
import {
|
|
25
|
-
import { registerOpen } from "./commands/open.js";
|
|
42
|
+
import { registerRestart } from "./commands/restart.js";
|
|
26
43
|
import { registerRun } from "./commands/run.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
44
|
+
import { registerScale } from "./commands/scale.js";
|
|
45
|
+
import { registerSecrets } from "./commands/secrets.js";
|
|
46
|
+
import { registerService } from "./commands/service.js";
|
|
47
|
+
import { registerSSH } from "./commands/ssh.js";
|
|
48
|
+
import { registerStatus } from "./commands/status.js";
|
|
49
|
+
import { registerUnlink } from "./commands/unlink.js";
|
|
50
|
+
import { registerUp } from "./commands/up.js";
|
|
51
|
+
import { registerUpgrade } from "./commands/upgrade.js";
|
|
52
|
+
import { registerWhoami } from "./commands/whoami.js";
|
|
53
|
+
import { registerWorkspace } from "./commands/workspace.js";
|
|
32
54
|
|
|
33
55
|
const program = new Command();
|
|
34
56
|
|
|
35
57
|
program
|
|
36
58
|
.name("lizard")
|
|
37
59
|
.description("Lizard CLI — deploy and manage apps on Lizard")
|
|
38
|
-
.version(
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.option("--
|
|
47
|
-
.option("--verbose", "Verbose output")
|
|
60
|
+
.version(CURRENT_VERSION)
|
|
61
|
+
.addHelpText("before", BANNER + "\n")
|
|
62
|
+
.configureHelp({
|
|
63
|
+
subcommandTerm: (cmd) => {
|
|
64
|
+
const alias = cmd.aliases()[0];
|
|
65
|
+
return alias ? `${cmd.name()}|${alias}` : cmd.name();
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
.option("--json", "Output in JSON format (combine with --help to dump machine-readable command schema for agents)")
|
|
48
69
|
.hook("preAction", async (thisCommand, actionCommand) => {
|
|
49
70
|
const opts = thisCommand.opts();
|
|
50
71
|
|
|
72
|
+
// Check for updates silently in background (shows notice after command)
|
|
73
|
+
if (actionCommand.name() !== "upgrade") {
|
|
74
|
+
checkForUpdateInBackground();
|
|
75
|
+
}
|
|
76
|
+
|
|
51
77
|
// JSON mode: explicit flag or non-TTY stdout
|
|
52
78
|
if (opts.json || !process.stdout.isTTY) {
|
|
53
79
|
setJSONMode(true);
|
|
54
80
|
}
|
|
55
81
|
|
|
56
|
-
// Token override
|
|
57
|
-
if (opts.token) {
|
|
58
|
-
setTokenOverride(opts.token);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
82
|
// API URL override
|
|
62
83
|
if (process.env.LIZARD_API_URL) {
|
|
63
84
|
setBaseURL(process.env.LIZARD_API_URL);
|
|
64
85
|
}
|
|
65
86
|
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
87
|
+
// Top-level commands that don't need auth. `status` prints the local link;
|
|
88
|
+
// the optional workspace backfill silently no-ops when not authed.
|
|
89
|
+
//
|
|
90
|
+
// Match by name AND parent — otherwise leaf subcommands sharing a name
|
|
91
|
+
// (e.g. `git status`, `up status`) skip the auto-login flow and would
|
|
92
|
+
// 401 instead of prompting the user to log in.
|
|
93
|
+
const noAuth = new Set(["login", "logout", "upgrade", "help", "docs", "status"]);
|
|
94
|
+
const isTopLevel = actionCommand.parent === thisCommand;
|
|
95
|
+
if (isTopLevel && noAuth.has(actionCommand.name())) return;
|
|
69
96
|
|
|
70
97
|
// Require auth — auto-triggers login flow if not logged in
|
|
71
98
|
const creds = await requireAuth();
|
|
72
99
|
setAccessToken(creds.accessToken);
|
|
73
100
|
});
|
|
74
101
|
|
|
75
|
-
// Register all commands
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
// Register all commands (alphabetical)
|
|
103
|
+
registerAdd(program);
|
|
104
|
+
registerConfig(program);
|
|
105
|
+
registerDocs(program);
|
|
106
|
+
registerDomain(program);
|
|
107
|
+
registerGit(program);
|
|
79
108
|
registerInit(program);
|
|
80
109
|
registerLink(program);
|
|
110
|
+
registerLogin(program);
|
|
111
|
+
registerLogout(program);
|
|
112
|
+
registerLogs(program);
|
|
113
|
+
registerOpen(program);
|
|
114
|
+
registerPort(program);
|
|
81
115
|
registerProjects(program);
|
|
82
|
-
registerDeploy(program);
|
|
83
116
|
registerPs(program);
|
|
84
|
-
registerAdd(program);
|
|
85
|
-
registerDestroy(program);
|
|
86
|
-
registerRestart(program);
|
|
87
117
|
registerRedeploy(program);
|
|
88
|
-
registerLogs(program);
|
|
89
|
-
registerSecrets(program);
|
|
90
118
|
registerRegions(program);
|
|
91
|
-
|
|
92
|
-
registerOpen(program);
|
|
119
|
+
registerRestart(program);
|
|
93
120
|
registerRun(program);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
program
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.description("Generate shell completion script")
|
|
105
|
-
.action((shell: string) => {
|
|
106
|
-
// Commander doesn't have built-in completion like Cobra
|
|
107
|
-
// Point users to manual setup
|
|
108
|
-
console.log(`# Add to your .${shell}rc:`);
|
|
109
|
-
if (shell === "bash") {
|
|
110
|
-
console.log(`eval "$(lizard completion bash)"`);
|
|
111
|
-
} else if (shell === "zsh") {
|
|
112
|
-
console.log(`# lizard completion is not yet available for zsh`);
|
|
113
|
-
console.log(`# Coming soon`);
|
|
114
|
-
} else if (shell === "fish") {
|
|
115
|
-
console.log(`# lizard completion is not yet available for fish`);
|
|
116
|
-
console.log(`# Coming soon`);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
121
|
+
registerScale(program);
|
|
122
|
+
registerSecrets(program);
|
|
123
|
+
registerService(program);
|
|
124
|
+
registerSSH(program);
|
|
125
|
+
registerStatus(program);
|
|
126
|
+
registerUnlink(program);
|
|
127
|
+
registerUp(program);
|
|
128
|
+
registerUpgrade(program);
|
|
129
|
+
registerWhoami(program);
|
|
130
|
+
registerWorkspace(program);
|
|
119
131
|
|
|
120
132
|
// Error handling
|
|
121
133
|
program.exitOverride();
|
|
122
134
|
|
|
135
|
+
const EXIT_CODES: Record<string, string> = {
|
|
136
|
+
"0": "success",
|
|
137
|
+
"1": "generic error",
|
|
138
|
+
"2": "auth (401/403)",
|
|
139
|
+
"3": "not found (404)",
|
|
140
|
+
"4": "timeout (408/504)",
|
|
141
|
+
"5": "cancelled by user",
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
function isHelpJsonRequest(argv: string[]): boolean {
|
|
145
|
+
const hasHelp = argv.some((a) => a === "--help" || a === "-h");
|
|
146
|
+
const hasJson = argv.some((a) => a === "--json");
|
|
147
|
+
return hasHelp && hasJson;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function collectValueFlags(cmd: Command, acc: Set<string>) {
|
|
151
|
+
for (const opt of cmd.options as any[]) {
|
|
152
|
+
if (opt.required || opt.optional) {
|
|
153
|
+
if (opt.short) acc.add(opt.short);
|
|
154
|
+
if (opt.long) acc.add(opt.long);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const sub of cmd.commands) collectValueFlags(sub, acc);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function findTargetCommand(argv: string[], root: Command): Command {
|
|
161
|
+
const valueFlags = new Set<string>();
|
|
162
|
+
collectValueFlags(root, valueFlags);
|
|
163
|
+
|
|
164
|
+
let cur: Command = root;
|
|
165
|
+
for (let i = 2; i < argv.length; i++) {
|
|
166
|
+
const tok = argv[i];
|
|
167
|
+
if (tok === "--help" || tok === "-h" || tok === "--json") continue;
|
|
168
|
+
if (tok.startsWith("-")) {
|
|
169
|
+
if (tok.includes("=")) continue;
|
|
170
|
+
if (valueFlags.has(tok) && i + 1 < argv.length) i++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const sub = cur.commands.find(
|
|
174
|
+
(c) => c.name() === tok || c.aliases().includes(tok),
|
|
175
|
+
);
|
|
176
|
+
if (!sub) break;
|
|
177
|
+
cur = sub;
|
|
178
|
+
}
|
|
179
|
+
return cur;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function dumpOption(o: any) {
|
|
183
|
+
return {
|
|
184
|
+
flags: o.flags,
|
|
185
|
+
long: o.long ?? null,
|
|
186
|
+
short: o.short ?? null,
|
|
187
|
+
description: o.description ?? "",
|
|
188
|
+
takesValue: Boolean(o.required || o.optional),
|
|
189
|
+
valueRequired: Boolean(o.required),
|
|
190
|
+
defaultValue: o.defaultValue ?? null,
|
|
191
|
+
choices: o.argChoices ?? null,
|
|
192
|
+
negate: Boolean(o.negate),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function dumpCommand(cmd: Command): any {
|
|
197
|
+
const args = ((cmd as any).registeredArguments ?? []).map((a: any) => ({
|
|
198
|
+
name: a.name(),
|
|
199
|
+
description: a.description ?? "",
|
|
200
|
+
required: Boolean(a.required),
|
|
201
|
+
variadic: Boolean(a.variadic),
|
|
202
|
+
defaultValue: a.defaultValue ?? null,
|
|
203
|
+
choices: a.argChoices ?? null,
|
|
204
|
+
}));
|
|
205
|
+
return {
|
|
206
|
+
name: cmd.name(),
|
|
207
|
+
aliases: cmd.aliases(),
|
|
208
|
+
description: cmd.description(),
|
|
209
|
+
usage: cmd.usage(),
|
|
210
|
+
arguments: args,
|
|
211
|
+
options: (cmd.options as any[])
|
|
212
|
+
.filter((o) => !o.hidden)
|
|
213
|
+
.map(dumpOption),
|
|
214
|
+
subcommands: cmd.commands
|
|
215
|
+
.filter((c: any) => !c._hidden)
|
|
216
|
+
.map(dumpCommand),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
123
220
|
async function main() {
|
|
221
|
+
// Set JSON mode from argv *before* parseAsync so the catch block below
|
|
222
|
+
// honors --json even when commander rejects before our preAction hook
|
|
223
|
+
// fires (e.g. unknown command, malformed global flag). Non-TTY auto-mode
|
|
224
|
+
// stays in preAction — `lizard --help | less` shouldn't suddenly emit JSON.
|
|
225
|
+
if (process.argv.includes("--json")) {
|
|
226
|
+
setJSONMode(true);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (isHelpJsonRequest(process.argv)) {
|
|
230
|
+
const target = findTargetCommand(process.argv, program);
|
|
231
|
+
const isRoot = target === program;
|
|
232
|
+
const out = {
|
|
233
|
+
cli: "lizard",
|
|
234
|
+
version: CURRENT_VERSION,
|
|
235
|
+
command: dumpCommand(target),
|
|
236
|
+
globalOptions: isRoot
|
|
237
|
+
? []
|
|
238
|
+
: (program.options as any[]).filter((o) => !o.hidden).map(dumpOption),
|
|
239
|
+
exitCodes: EXIT_CODES,
|
|
240
|
+
};
|
|
241
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
|
|
124
245
|
try {
|
|
125
246
|
await program.parseAsync(process.argv);
|
|
126
247
|
} catch (err: any) {
|
|
@@ -133,14 +254,19 @@ async function main() {
|
|
|
133
254
|
}
|
|
134
255
|
|
|
135
256
|
const msg = err.message || String(err);
|
|
257
|
+
const apiErr = err instanceof APIError ? err : undefined;
|
|
258
|
+
const status = apiErr?.status;
|
|
259
|
+
const code = apiErr?.code || err.code || "ERROR";
|
|
136
260
|
|
|
137
261
|
if (isJSONMode()) {
|
|
138
262
|
console.log(
|
|
139
263
|
JSON.stringify(
|
|
140
264
|
{
|
|
141
265
|
error: {
|
|
142
|
-
code
|
|
266
|
+
code,
|
|
267
|
+
status: status ?? null,
|
|
143
268
|
message: msg,
|
|
269
|
+
body: apiErr?.body ?? null,
|
|
144
270
|
},
|
|
145
271
|
},
|
|
146
272
|
null,
|
|
@@ -151,16 +277,19 @@ async function main() {
|
|
|
151
277
|
error(msg);
|
|
152
278
|
}
|
|
153
279
|
|
|
154
|
-
// Exit codes
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
280
|
+
// Exit codes derived from APIError.status (or tagged error codes), not message text
|
|
281
|
+
const isAuth = status === 401 || status === 403 || code === "NOT_AUTHENTICATED";
|
|
282
|
+
const isNotFound = status === 404;
|
|
283
|
+
const isTimeout =
|
|
284
|
+
status === 408 ||
|
|
285
|
+
status === 504 ||
|
|
286
|
+
err.name === "AbortError" ||
|
|
287
|
+
err.code === "ETIMEDOUT" ||
|
|
288
|
+
err.code === "UND_ERR_CONNECT_TIMEOUT";
|
|
289
|
+
|
|
290
|
+
if (isAuth) process.exit(2);
|
|
291
|
+
if (isNotFound) process.exit(3);
|
|
292
|
+
if (isTimeout) process.exit(4);
|
|
164
293
|
process.exit(1);
|
|
165
294
|
}
|
|
166
295
|
}
|
package/src/lib/api.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { getToken } from "./auth.js";
|
|
2
|
+
import { CURRENT_VERSION } from "./updater.js";
|
|
3
|
+
import * as https from "node:https";
|
|
4
|
+
import * as http from "node:http";
|
|
2
5
|
|
|
3
6
|
const DEFAULT_BASE_URL = "https://lizard.build";
|
|
4
|
-
const USER_AGENT =
|
|
7
|
+
const USER_AGENT = `lizard-cli/${CURRENT_VERSION}`;
|
|
5
8
|
|
|
6
9
|
let baseURL = process.env.LIZARD_API_URL || DEFAULT_BASE_URL;
|
|
7
10
|
let _accessToken: string | null = null;
|
|
@@ -10,13 +13,56 @@ export function setBaseURL(url: string) { baseURL = url; }
|
|
|
10
13
|
export function getBaseURL() { return baseURL; }
|
|
11
14
|
export function setAccessToken(token: string) { _accessToken = token; }
|
|
12
15
|
|
|
16
|
+
// ── Scoping ───────────────────────────────────────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// Every project-scoped endpoint takes `?workspaceId=…` — mirrors
|
|
19
|
+
// lizard-client's `withScope` so server-side state is shared across
|
|
20
|
+
// CLI and browser. Build URLs through these helpers, never by hand.
|
|
21
|
+
|
|
22
|
+
export interface ResourceScope {
|
|
23
|
+
workspaceId?: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Workspace {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
slug: string;
|
|
30
|
+
role: "owner" | "member";
|
|
31
|
+
isPersonal?: boolean;
|
|
32
|
+
projectCount?: number;
|
|
33
|
+
createdAt?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function withQuery(
|
|
37
|
+
path: string,
|
|
38
|
+
params: Record<string, string | number | boolean | null | undefined>,
|
|
39
|
+
): string {
|
|
40
|
+
const search = new URLSearchParams();
|
|
41
|
+
for (const [key, value] of Object.entries(params)) {
|
|
42
|
+
if (value === null || value === undefined || value === "") continue;
|
|
43
|
+
search.set(key, String(value));
|
|
44
|
+
}
|
|
45
|
+
const query = search.toString();
|
|
46
|
+
if (!query) return path;
|
|
47
|
+
return `${path}${path.includes("?") ? "&" : "?"}${query}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function withScope(path: string, scope?: ResourceScope): string {
|
|
51
|
+
if (!scope) return path;
|
|
52
|
+
return withQuery(path, {
|
|
53
|
+
workspaceId: scope.workspaceId,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
13
57
|
export class APIError extends Error {
|
|
14
58
|
status: number;
|
|
15
59
|
code: string;
|
|
16
|
-
|
|
60
|
+
body: unknown;
|
|
61
|
+
constructor(status: number, message: string, code = "", body: unknown = null) {
|
|
17
62
|
super(message);
|
|
18
63
|
this.status = status;
|
|
19
64
|
this.code = code;
|
|
65
|
+
this.body = body;
|
|
20
66
|
}
|
|
21
67
|
}
|
|
22
68
|
|
|
@@ -32,12 +78,14 @@ async function request<T = any>(
|
|
|
32
78
|
method: string,
|
|
33
79
|
path: string,
|
|
34
80
|
body?: unknown,
|
|
81
|
+
extraHeaders: Record<string, string> = {},
|
|
35
82
|
): Promise<T> {
|
|
36
83
|
const url = baseURL + path;
|
|
37
84
|
const token = _accessToken || getToken();
|
|
38
85
|
|
|
39
86
|
const headers: Record<string, string> = {
|
|
40
87
|
"User-Agent": USER_AGENT,
|
|
88
|
+
...extraHeaders,
|
|
41
89
|
};
|
|
42
90
|
if (token) {
|
|
43
91
|
headers["Authorization"] = `Bearer ${token}`;
|
|
@@ -55,12 +103,14 @@ async function request<T = any>(
|
|
|
55
103
|
if (!res.ok) {
|
|
56
104
|
let msg = res.statusText;
|
|
57
105
|
let code = "";
|
|
106
|
+
let body: unknown = null;
|
|
58
107
|
try {
|
|
59
108
|
const j = (await res.json()) as any;
|
|
109
|
+
body = j;
|
|
60
110
|
msg = j.error || j.message || msg;
|
|
61
111
|
code = j.code || "";
|
|
62
112
|
} catch {}
|
|
63
|
-
throw new APIError(res.status, msg, code);
|
|
113
|
+
throw new APIError(res.status, msg, code, body);
|
|
64
114
|
}
|
|
65
115
|
|
|
66
116
|
const text = await res.text();
|
|
@@ -70,8 +120,8 @@ async function request<T = any>(
|
|
|
70
120
|
|
|
71
121
|
export const api = {
|
|
72
122
|
get: <T = any>(path: string) => request<T>("GET", path),
|
|
73
|
-
post: <T = any>(path: string, body?: unknown) =>
|
|
74
|
-
request<T>("POST", path, body),
|
|
123
|
+
post: <T = any>(path: string, body?: unknown, headers?: Record<string, string>) =>
|
|
124
|
+
request<T>("POST", path, body, headers),
|
|
75
125
|
put: <T = any>(path: string, body?: unknown) =>
|
|
76
126
|
request<T>("PUT", path, body),
|
|
77
127
|
patch: <T = any>(path: string, body?: unknown) =>
|
|
@@ -80,55 +130,68 @@ export const api = {
|
|
|
80
130
|
};
|
|
81
131
|
|
|
82
132
|
/** Stream SSE and call handler for each data line. Return false to stop. */
|
|
83
|
-
export
|
|
133
|
+
export function streamSSE(
|
|
84
134
|
path: string,
|
|
85
135
|
handler: (event: string, data: string) => boolean | void,
|
|
86
136
|
): Promise<void> {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const url = new URL(baseURL + path);
|
|
139
|
+
const token = _accessToken || getToken();
|
|
140
|
+
const reqHeaders: Record<string, string> = {
|
|
141
|
+
"User-Agent": USER_AGENT,
|
|
142
|
+
Accept: "text/event-stream",
|
|
143
|
+
};
|
|
144
|
+
if (token) reqHeaders["Authorization"] = `Bearer ${token}`;
|
|
145
|
+
|
|
146
|
+
const transport = url.protocol === "https:" ? https : http;
|
|
147
|
+
const req = transport.request(
|
|
148
|
+
{ hostname: url.hostname, port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
149
|
+
path: url.pathname + url.search, method: "GET", headers: reqHeaders },
|
|
150
|
+
(res) => {
|
|
151
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
152
|
+
let body = "";
|
|
153
|
+
res.on("data", (c: Buffer) => body += c.toString());
|
|
154
|
+
res.on("end", () => reject(new APIError(res.statusCode!, `SSE failed: ${body}`)));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
94
157
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
158
|
+
let buffer = "";
|
|
159
|
+
let currentEvent = "";
|
|
160
|
+
let currentData = "";
|
|
161
|
+
|
|
162
|
+
res.setEncoding("utf8");
|
|
163
|
+
res.on("data", (chunk: string) => {
|
|
164
|
+
buffer += chunk;
|
|
165
|
+
const lines = buffer.split("\n");
|
|
166
|
+
buffer = lines.pop() ?? "";
|
|
167
|
+
|
|
168
|
+
for (const line of lines) {
|
|
169
|
+
const trimmed = line.replace(/\r$/, "");
|
|
170
|
+
if (trimmed === "") {
|
|
171
|
+
if (currentData) {
|
|
172
|
+
const cont = handler(currentEvent, currentData);
|
|
173
|
+
if (cont === false) {
|
|
174
|
+
req.destroy();
|
|
175
|
+
resolve();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
currentEvent = "";
|
|
180
|
+
currentData = "";
|
|
181
|
+
} else if (trimmed.startsWith("event:")) {
|
|
182
|
+
currentEvent = trimmed.slice(6).trim();
|
|
183
|
+
} else if (trimmed.startsWith("data:")) {
|
|
184
|
+
currentData = trimmed.slice(5).trimStart();
|
|
185
|
+
}
|
|
123
186
|
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
res.on("end", resolve);
|
|
190
|
+
res.on("error", reject);
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
req.on("error", reject);
|
|
195
|
+
req.end();
|
|
196
|
+
});
|
|
134
197
|
}
|