@lizard-build/cli 0.3.38 → 0.3.40
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/commands/add.js +2 -2
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/git.js +7 -6
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/login.js +5 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logs.js +4 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/redeploy.js +8 -6
- package/dist/commands/redeploy.js.map +1 -1
- package/dist/commands/secrets.js +5 -0
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/ssh.js +24 -18
- package/dist/commands/ssh.js.map +1 -1
- package/dist/commands/up.js +26 -16
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/upgrade.js +20 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +8 -2
- package/dist/lib/api.js +28 -5
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/auth.d.ts +8 -1
- package/dist/lib/auth.js +33 -3
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.js +14 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/updater.d.ts +22 -4
- package/dist/lib/updater.js +137 -49
- package/dist/lib/updater.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/add.ts +2 -2
- package/src/commands/git.ts +11 -6
- package/src/commands/login.ts +5 -0
- package/src/commands/logs.ts +12 -5
- package/src/commands/redeploy.ts +12 -6
- package/src/commands/secrets.ts +8 -0
- package/src/commands/ssh.ts +24 -25
- package/src/commands/up.ts +26 -17
- package/src/commands/upgrade.ts +21 -1
- package/src/index.ts +12 -1
- package/src/lib/api.ts +25 -4
- package/src/lib/auth.ts +33 -3
- package/src/lib/config.ts +13 -2
- package/src/lib/updater.ts +130 -45
- package/test/unit/config.test.ts +29 -0
package/dist/lib/updater.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { createWriteStream, existsSync, renameSync, chmodSync } from "node:fs";
|
|
1
|
+
import { createWriteStream, existsSync, renameSync, chmodSync, unlinkSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { pipeline } from "node:stream/promises";
|
|
3
3
|
import { Readable } from "node:stream";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
export const CURRENT_VERSION = "0.3.40";
|
|
7
8
|
const RELEASES_API = "https://api.github.com/repos/lizard-build/lizard-cli/releases/latest";
|
|
8
9
|
const RELEASE_BASE = "https://github.com/lizard-build/lizard-cli/releases/latest/download";
|
|
10
|
+
/** Minimum gap between background update checks. */
|
|
11
|
+
const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6h
|
|
9
12
|
function getBinaryName() {
|
|
10
13
|
const os = process.platform;
|
|
11
14
|
const arch = process.arch;
|
|
@@ -19,6 +22,21 @@ function getBinaryName() {
|
|
|
19
22
|
return "lizard-linux-arm64";
|
|
20
23
|
return null;
|
|
21
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* True only when running as the Bun-compiled standalone binary. Under
|
|
27
|
+
* npm/node, `process.execPath` is the *node* executable — self-update would
|
|
28
|
+
* overwrite the user's Node.js install with the lizard binary.
|
|
29
|
+
*/
|
|
30
|
+
export function isStandaloneBinary() {
|
|
31
|
+
return typeof globalThis.Bun !== "undefined";
|
|
32
|
+
}
|
|
33
|
+
function stateDir() {
|
|
34
|
+
return process.env.LIZARD_HOME
|
|
35
|
+
? join(process.env.LIZARD_HOME, ".lizard")
|
|
36
|
+
: join(os.homedir(), ".lizard");
|
|
37
|
+
}
|
|
38
|
+
const checkStampFile = () => join(stateDir(), "update-check.json");
|
|
39
|
+
const updateNoticeFile = () => join(stateDir(), "update-notice.json");
|
|
22
40
|
export async function getLatestVersion() {
|
|
23
41
|
try {
|
|
24
42
|
const res = await fetch(RELEASES_API, {
|
|
@@ -39,64 +57,134 @@ export async function getLatestVersion() {
|
|
|
39
57
|
return { kind: "error" };
|
|
40
58
|
}
|
|
41
59
|
}
|
|
60
|
+
export function isNewerVersion(latest, current) {
|
|
61
|
+
const [maj, min, pat] = latest.split(".").map(Number);
|
|
62
|
+
const [cmaj, cmin, cpat] = current.split(".").map(Number);
|
|
63
|
+
if (![maj, min, pat, cmaj, cmin, cpat].every(Number.isFinite))
|
|
64
|
+
return false;
|
|
65
|
+
return maj > cmaj || (maj === cmaj && min > cmin) || (maj === cmaj && min === cmin && pat > cpat);
|
|
66
|
+
}
|
|
42
67
|
export async function selfUpdate(onProgress) {
|
|
43
68
|
const binaryName = getBinaryName();
|
|
44
69
|
if (!binaryName)
|
|
45
70
|
return false;
|
|
46
|
-
//
|
|
71
|
+
// Refuse to replace anything that isn't the standalone lizard binary —
|
|
72
|
+
// under npm the execPath is the user's node executable.
|
|
73
|
+
if (!isStandaloneBinary())
|
|
74
|
+
return false;
|
|
47
75
|
const currentBin = process.execPath;
|
|
48
76
|
if (!existsSync(currentBin))
|
|
49
77
|
return false;
|
|
50
78
|
const url = `${RELEASE_BASE}/${binaryName}`;
|
|
51
|
-
|
|
79
|
+
// Download next to the target binary: rename() must stay on one filesystem
|
|
80
|
+
// (tmpdir is often tmpfs on Linux → EXDEV).
|
|
81
|
+
const tmp = join(dirname(currentBin), `.lizard-update-${process.pid}`);
|
|
52
82
|
onProgress?.(`Downloading ${binaryName}...`);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(60000) });
|
|
85
|
+
if (!res.ok)
|
|
86
|
+
throw new Error(`Download failed: ${res.status}`);
|
|
87
|
+
const writer = createWriteStream(tmp);
|
|
88
|
+
await pipeline(Readable.fromWeb(res.body), writer);
|
|
89
|
+
chmodSync(tmp, 0o755);
|
|
90
|
+
onProgress?.("Installing...");
|
|
91
|
+
renameSync(tmp, currentBin);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
try {
|
|
96
|
+
unlinkSync(tmp);
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
62
101
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
function readJSON(file) {
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function writeJSON(file, data) {
|
|
111
|
+
try {
|
|
112
|
+
mkdirSync(stateDir(), { recursive: true });
|
|
113
|
+
writeFileSync(file, JSON.stringify(data));
|
|
114
|
+
}
|
|
115
|
+
catch { }
|
|
116
|
+
}
|
|
117
|
+
function autoUpdateDisabled() {
|
|
118
|
+
return Boolean(process.env.LIZARD_NO_UPDATE || process.env.CI);
|
|
119
|
+
}
|
|
120
|
+
/** Print (once) the notice left behind by a completed background update. */
|
|
121
|
+
function flushUpdateNotice() {
|
|
122
|
+
const notice = readJSON(updateNoticeFile());
|
|
123
|
+
if (!notice?.to)
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
unlinkSync(updateNoticeFile());
|
|
127
|
+
}
|
|
128
|
+
catch { }
|
|
129
|
+
// We are already running the replaced binary, so notice.to should match.
|
|
130
|
+
if (notice.to === CURRENT_VERSION && notice.from !== CURRENT_VERSION) {
|
|
131
|
+
process.stderr.write(` lizard auto-updated: v${notice.from} → v${notice.to}\n`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Kick off an update check without delaying the current command.
|
|
136
|
+
*
|
|
137
|
+
* The check+download runs in a *detached child process* (`lizard
|
|
138
|
+
* __lizard-update`): an in-process fetch would keep the event loop alive and
|
|
139
|
+
* make every command linger until GitHub answers. Checks are throttled via a
|
|
140
|
+
* stamp file (6h), disabled with LIZARD_NO_UPDATE/CI, and only run for the
|
|
141
|
+
* standalone binary — npm installs upgrade through npm.
|
|
142
|
+
*/
|
|
66
143
|
export function checkForUpdateInBackground() {
|
|
67
|
-
// Only auto-update in TTY; skip CI / piped output
|
|
68
144
|
if (!process.stdout.isTTY)
|
|
69
145
|
return;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
146
|
+
if (autoUpdateDisabled())
|
|
147
|
+
return;
|
|
148
|
+
flushUpdateNotice();
|
|
149
|
+
if (!isStandaloneBinary())
|
|
150
|
+
return;
|
|
151
|
+
const stamp = readJSON(checkStampFile());
|
|
152
|
+
if (stamp?.lastCheckAt && Date.now() - stamp.lastCheckAt < CHECK_INTERVAL_MS)
|
|
153
|
+
return;
|
|
154
|
+
// Stamp before spawning so parallel commands don't pile up children.
|
|
155
|
+
writeJSON(checkStampFile(), { lastCheckAt: Date.now(), lastVersion: CURRENT_VERSION });
|
|
156
|
+
try {
|
|
157
|
+
const child = spawn(process.execPath, ["__lizard-update"], {
|
|
158
|
+
detached: true,
|
|
159
|
+
stdio: "ignore",
|
|
160
|
+
});
|
|
161
|
+
child.unref();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// never break the actual command over an update check
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Body of the hidden `__lizard-update` command: check the latest release and
|
|
169
|
+
* install it, leaving a notice file for the next foreground run.
|
|
170
|
+
*/
|
|
171
|
+
export async function runBackgroundUpdate() {
|
|
172
|
+
if (autoUpdateDisabled() || !isStandaloneBinary())
|
|
173
|
+
return;
|
|
174
|
+
const r = await getLatestVersion();
|
|
175
|
+
writeJSON(checkStampFile(), { lastCheckAt: Date.now(), lastVersion: r.kind === "ok" ? r.version : CURRENT_VERSION });
|
|
176
|
+
if (r.kind !== "ok")
|
|
177
|
+
return;
|
|
178
|
+
if (!isNewerVersion(r.version, CURRENT_VERSION))
|
|
179
|
+
return;
|
|
180
|
+
try {
|
|
181
|
+
const ok = await selfUpdate();
|
|
182
|
+
if (ok) {
|
|
183
|
+
writeJSON(updateNoticeFile(), { from: CURRENT_VERSION, to: r.version, at: Date.now() });
|
|
92
184
|
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
98
|
-
// Don't block process exit
|
|
99
|
-
if (typeof promise.unref === "function")
|
|
100
|
-
promise.unref();
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// silent — retried after the next throttle window
|
|
188
|
+
}
|
|
101
189
|
}
|
|
102
190
|
//# sourceMappingURL=updater.js.map
|
package/dist/lib/updater.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updater.js","sourceRoot":"","sources":["../../src/lib/updater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"updater.js","sourceRoot":"","sources":["../../src/lib/updater.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/commands/add.ts
CHANGED
|
@@ -318,7 +318,7 @@ async function runAdd(input: AddInput): Promise<void> {
|
|
|
318
318
|
? opts.repo
|
|
319
319
|
: `https://github.com/${opts.repo}`,
|
|
320
320
|
region,
|
|
321
|
-
variables,
|
|
321
|
+
envVars: variables,
|
|
322
322
|
...(detectedPort ? { containerPort: detectedPort } : {}),
|
|
323
323
|
...(input.noDeploy ? { skipInitialDeploy: true } : {}),
|
|
324
324
|
},
|
|
@@ -349,7 +349,7 @@ async function runAdd(input: AddInput): Promise<void> {
|
|
|
349
349
|
{
|
|
350
350
|
name: opts.service,
|
|
351
351
|
region,
|
|
352
|
-
variables,
|
|
352
|
+
envVars: variables,
|
|
353
353
|
},
|
|
354
354
|
);
|
|
355
355
|
if (isJSONMode()) printJSON(app);
|
package/src/commands/git.ts
CHANGED
|
@@ -112,22 +112,27 @@ export function registerGit(program: Command) {
|
|
|
112
112
|
});
|
|
113
113
|
spinner.succeed(`Branch set to ${chalk.cyan(branch)}`);
|
|
114
114
|
|
|
115
|
-
// Trigger redeploy
|
|
115
|
+
// Trigger redeploy — the endpoint returns the freshly created Build
|
|
116
|
+
// record; using its id avoids racing against a previous build.
|
|
116
117
|
const deploySpinner = ora("Starting redeploy...").start();
|
|
117
|
-
await api.post
|
|
118
|
+
const build = await api.post<{ id?: string }>(
|
|
119
|
+
withScope(`/api/apps/${serviceId}/redeploy`, scope),
|
|
120
|
+
undefined,
|
|
121
|
+
{ "X-Deploy-Source": "cli" },
|
|
122
|
+
);
|
|
118
123
|
deploySpinner.stop();
|
|
119
124
|
|
|
120
125
|
if (opts.detach || isJSONMode()) {
|
|
121
|
-
if (isJSONMode()) printJSON({ id: serviceId, branch, status: "deploying" });
|
|
126
|
+
if (isJSONMode()) printJSON({ id: serviceId, buildId: build?.id, branch, status: "deploying" });
|
|
122
127
|
else success(`Redeploy started on branch ${chalk.cyan(branch)}`);
|
|
123
128
|
return;
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
info(`Redeploying ${chalk.bold(serviceName)} on ${chalk.cyan(branch)}...`);
|
|
127
132
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (let i = 0; i < 30; i++) {
|
|
133
|
+
let buildId: string | null = build?.id ?? null;
|
|
134
|
+
// Fallback for older servers that respond without a Build record.
|
|
135
|
+
for (let i = 0; !buildId && i < 30; i++) {
|
|
131
136
|
await new Promise((r) => setTimeout(r, 2000));
|
|
132
137
|
try {
|
|
133
138
|
const app = await api.get<{ builds?: Array<{ id: string; status: string }> }>(`/api/apps/${serviceId}`);
|
package/src/commands/login.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Command } from "commander";
|
|
|
4
4
|
import {
|
|
5
5
|
saveCredentials,
|
|
6
6
|
openURL,
|
|
7
|
+
jwtExpiryMs,
|
|
7
8
|
type Credentials,
|
|
8
9
|
} from "../lib/auth.js";
|
|
9
10
|
import { getBaseURL } from "../lib/api.js";
|
|
@@ -86,9 +87,11 @@ export async function performLogin(): Promise<Credentials> {
|
|
|
86
87
|
|
|
87
88
|
if (result.status === "complete" && result.accessToken && result.user) {
|
|
88
89
|
spinner.stop();
|
|
90
|
+
const expMs = jwtExpiryMs(result.accessToken);
|
|
89
91
|
const creds: Credentials = {
|
|
90
92
|
accessToken: result.accessToken,
|
|
91
93
|
refreshToken: result.refreshToken,
|
|
94
|
+
expiresAt: expMs ? new Date(expMs).toISOString() : undefined,
|
|
92
95
|
userId: result.user.id,
|
|
93
96
|
username: result.user.username,
|
|
94
97
|
email: result.user.email,
|
|
@@ -139,8 +142,10 @@ export function registerLogin(program: Command) {
|
|
|
139
142
|
});
|
|
140
143
|
if (!res.ok) throw new Error("Invalid token");
|
|
141
144
|
const user = (await res.json()) as any;
|
|
145
|
+
const expMs = jwtExpiryMs(token);
|
|
142
146
|
saveCredentials({
|
|
143
147
|
accessToken: token,
|
|
148
|
+
expiresAt: expMs ? new Date(expMs).toISOString() : undefined,
|
|
144
149
|
userId: user.id,
|
|
145
150
|
username: user.username,
|
|
146
151
|
email: user.email,
|
package/src/commands/logs.ts
CHANGED
|
@@ -347,12 +347,19 @@ async function showBuildLogs(
|
|
|
347
347
|
info(chalk.dim(`Build ${buildId}\n`));
|
|
348
348
|
|
|
349
349
|
if (tailN !== undefined) {
|
|
350
|
+
// Snapshot semantics: the server replays history immediately; if the
|
|
351
|
+
// build is still running the stream would otherwise follow it forever.
|
|
352
|
+
// Stop after 3s without new events and print what we have.
|
|
350
353
|
const lines: string[] = [];
|
|
351
|
-
await streamSSE(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
await streamSSE(
|
|
355
|
+
`/api/builds/${buildId}/logs`,
|
|
356
|
+
(event, data) => {
|
|
357
|
+
if (event === "done" || event === "error") return false;
|
|
358
|
+
lines.push(data);
|
|
359
|
+
return true;
|
|
360
|
+
},
|
|
361
|
+
{ idleTimeoutMs: 3000 },
|
|
362
|
+
);
|
|
356
363
|
for (const line of lines.slice(-tailN)) printLogLine(line);
|
|
357
364
|
return;
|
|
358
365
|
}
|
package/src/commands/redeploy.ts
CHANGED
|
@@ -54,24 +54,30 @@ export function registerRedeploy(program: Command) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const spinner = ora("Starting redeploy...").start();
|
|
57
|
-
|
|
57
|
+
// The endpoint pre-creates and returns the Build record — use its id
|
|
58
|
+
// instead of polling builds[0], which races against a previous build.
|
|
59
|
+
const build = await api.post<{ id?: string; status?: string }>(
|
|
60
|
+
`/api/apps/${id}/redeploy`,
|
|
61
|
+
undefined,
|
|
62
|
+
{ "X-Deploy-Source": "cli" },
|
|
63
|
+
);
|
|
58
64
|
spinner.stop();
|
|
59
65
|
|
|
60
66
|
if (opts.detach || isJSONMode()) {
|
|
61
67
|
if (isJSONMode()) {
|
|
62
|
-
printJSON({ id, status: "deploying" });
|
|
68
|
+
printJSON({ id, buildId: build?.id, status: "deploying" });
|
|
63
69
|
} else {
|
|
64
70
|
success("Redeploy started");
|
|
65
|
-
info(chalk.dim(` Check status: lizard
|
|
71
|
+
info(chalk.dim(` Check status: lizard up status ${id}`));
|
|
66
72
|
}
|
|
67
73
|
return;
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
info("Redeploying...");
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
for (let i = 0; i < 30; i++) {
|
|
78
|
+
let buildId: string | null = build?.id ?? null;
|
|
79
|
+
// Fallback for older servers that respond without a Build record.
|
|
80
|
+
for (let i = 0; !buildId && i < 30; i++) {
|
|
75
81
|
await new Promise((r) => setTimeout(r, 2000));
|
|
76
82
|
try {
|
|
77
83
|
const app = await api.get<{ builds?: Array<{ id: string; status: string }> }>(
|
package/src/commands/secrets.ts
CHANGED
|
@@ -278,6 +278,14 @@ Notes:
|
|
|
278
278
|
.option("-s, --service <name>", "Service to scope to (overrides linked)")
|
|
279
279
|
.option("-p, --project <id>", "Project to scope to")
|
|
280
280
|
.action(async (opts, sub) => {
|
|
281
|
+
// Reading a TTY stdin waits for Ctrl+D forever — fail fast instead.
|
|
282
|
+
if (process.stdin.isTTY) {
|
|
283
|
+
throw new Error(
|
|
284
|
+
"secrets import reads KEY=value lines from stdin. Pipe a file:\n" +
|
|
285
|
+
" lizard secrets import < .env",
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
281
289
|
const inherited = sub.parent?.opts() || {};
|
|
282
290
|
const scope = await resolveScope(
|
|
283
291
|
opts.project ?? inherited.project,
|
package/src/commands/ssh.ts
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import * as p from "@clack/prompts";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { api, getBaseURL, streamSSE, withScope } from "../lib/api.js";
|
|
5
|
-
import { resolveProjectScope } from "../lib/resolve.js";
|
|
5
|
+
import { resolveProjectScope, resolveService } from "../lib/resolve.js";
|
|
6
6
|
import { error, isTTY } from "../lib/format.js";
|
|
7
7
|
import { getToken } from "../lib/auth.js";
|
|
8
8
|
import * as https from "node:https";
|
|
@@ -22,10 +22,19 @@ Examples:
|
|
|
22
22
|
lizard ssh -s my-app -- bash -c "ps aux | head"`)
|
|
23
23
|
.action(async (cmdArgs: string[], opts) => {
|
|
24
24
|
const { projectId, scope } = await resolveProjectScope(opts.project);
|
|
25
|
-
let serviceId
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
let serviceId: string | undefined;
|
|
26
|
+
|
|
27
|
+
if (opts.service) {
|
|
28
|
+
// Resolve by name or ID through the shared resolver — guessing
|
|
29
|
+
// "looks like an ID" by length breaks for long service names.
|
|
30
|
+
const svc = await resolveService(projectId, opts.service);
|
|
31
|
+
if (svc.kind !== "app") {
|
|
32
|
+
error(`"${svc.name}" is an addon — ssh works only for app services.`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
serviceId = svc.id;
|
|
36
|
+
} else {
|
|
37
|
+
// Resolve service interactively if not given
|
|
29
38
|
const data = await api.get<{ apps: Array<{ id: string; name: string; status: string }> }>(
|
|
30
39
|
withScope(`/api/projects/${projectId}/services`, scope),
|
|
31
40
|
);
|
|
@@ -49,17 +58,6 @@ Examples:
|
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
// Resolve service ID if a name was given (lookup by name)
|
|
53
|
-
if (serviceId && !serviceId.match(/^[A-Za-z0-9_-]{20,}$/)) {
|
|
54
|
-
const data = await api.get<{ apps: Array<{ id: string; name: string; serviceName?: string }> }>(
|
|
55
|
-
withScope(`/api/projects/${projectId}/services`, scope),
|
|
56
|
-
);
|
|
57
|
-
const match = (data.apps || []).find(
|
|
58
|
-
(a) => a.name === serviceId || a.serviceName === serviceId || a.id === serviceId,
|
|
59
|
-
);
|
|
60
|
-
if (match) serviceId = match.id;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
61
|
if (cmdArgs.length === 0) {
|
|
64
62
|
error("No command given. Usage: lizard ssh -s <service> -- <cmd> [args...]");
|
|
65
63
|
process.exit(1);
|
|
@@ -71,29 +69,29 @@ Examples:
|
|
|
71
69
|
const cmd = cmdArgs.map(shellQuote).join(" ");
|
|
72
70
|
process.stdout.write(chalk.dim(`$ ${cmd}\n`));
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
await execStream(serviceId!, cmd, (stream, line) => {
|
|
72
|
+
const exitCode = await execStream(serviceId!, cmd, (stream, line) => {
|
|
77
73
|
if (stream === "stderr") {
|
|
78
74
|
process.stderr.write(line + "\n");
|
|
79
75
|
} else {
|
|
80
76
|
process.stdout.write(line + "\n");
|
|
81
77
|
}
|
|
82
|
-
}, (code) => {
|
|
83
|
-
exitCode = code;
|
|
84
78
|
});
|
|
85
79
|
|
|
86
80
|
process.exit(exitCode);
|
|
87
81
|
});
|
|
88
82
|
}
|
|
89
83
|
|
|
84
|
+
/** Run a command on the VM, streaming output. Resolves with the exit code:
|
|
85
|
+
* the remote command's code from the `exit` event, or 1 when the server
|
|
86
|
+
* reported an `error` event without one. */
|
|
90
87
|
function execStream(
|
|
91
88
|
appId: string,
|
|
92
89
|
cmd: string,
|
|
93
90
|
onLine: (stream: string, line: string) => void,
|
|
94
|
-
|
|
95
|
-
): Promise<void> {
|
|
91
|
+
): Promise<number> {
|
|
96
92
|
return new Promise((resolve, reject) => {
|
|
93
|
+
let exitCode: number | null = null;
|
|
94
|
+
let sawError = false;
|
|
97
95
|
const baseURL = getBaseURL();
|
|
98
96
|
const url = new URL(`${baseURL}/api/apps/${appId}/exec`);
|
|
99
97
|
const token = getToken();
|
|
@@ -141,8 +139,9 @@ function execStream(
|
|
|
141
139
|
} else if (trimmed.startsWith("data:")) {
|
|
142
140
|
const data = trimmed.slice(5).trimStart();
|
|
143
141
|
if (currentEvent === "exit") {
|
|
144
|
-
try {
|
|
142
|
+
try { exitCode = JSON.parse(data).exitCode ?? 0; } catch {}
|
|
145
143
|
} else if (currentEvent === "error") {
|
|
144
|
+
sawError = true;
|
|
146
145
|
error(data);
|
|
147
146
|
} else {
|
|
148
147
|
try {
|
|
@@ -156,7 +155,7 @@ function execStream(
|
|
|
156
155
|
}
|
|
157
156
|
});
|
|
158
157
|
|
|
159
|
-
res.on("end", resolve);
|
|
158
|
+
res.on("end", () => resolve(exitCode ?? (sawError ? 1 : 0)));
|
|
160
159
|
res.on("error", reject);
|
|
161
160
|
},
|
|
162
161
|
);
|
package/src/commands/up.ts
CHANGED
|
@@ -198,7 +198,7 @@ async function deployFromLocal(args: {
|
|
|
198
198
|
: success(`Deploy started ${chalk.dim(`lizard up status ${newApp.id}`)}`);
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
|
-
await streamBuildLogs(newApp.id, args.opts.ci);
|
|
201
|
+
await streamBuildLogs(newApp.id, args.opts.ci, newApp.buildId);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -260,12 +260,16 @@ const EXCLUDE_DIRS = new Set([
|
|
|
260
260
|
".turbo",
|
|
261
261
|
".vercel",
|
|
262
262
|
]);
|
|
263
|
-
const EXCLUDE_EXT = new Set([".pyc", ".pyo", ".log"
|
|
263
|
+
const EXCLUDE_EXT = new Set([".pyc", ".pyo", ".log"]);
|
|
264
|
+
// Matched by full name — `path.extname(".DS_Store")` is "" (dotfile), so
|
|
265
|
+
// extension matching never catches these.
|
|
266
|
+
const EXCLUDE_FILES = new Set([".DS_Store"]);
|
|
264
267
|
|
|
265
268
|
function collectFilesManually(root: string, dir: string): string[] {
|
|
266
269
|
const results: string[] = [];
|
|
267
270
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
268
271
|
if (EXCLUDE_DIRS.has(entry.name)) continue;
|
|
272
|
+
if (EXCLUDE_FILES.has(entry.name)) continue;
|
|
269
273
|
if (EXCLUDE_EXT.has(path.extname(entry.name))) continue;
|
|
270
274
|
const full = path.join(dir, entry.name);
|
|
271
275
|
if (entry.isDirectory()) results.push(...collectFilesManually(root, full));
|
|
@@ -324,23 +328,28 @@ function prompt(question: string): Promise<string> {
|
|
|
324
328
|
});
|
|
325
329
|
}
|
|
326
330
|
|
|
327
|
-
async function streamBuildLogs(appId: string, ciMode: boolean = false) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
331
|
+
async function streamBuildLogs(appId: string, ciMode: boolean = false, knownBuildId?: string) {
|
|
332
|
+
// Prefer the buildId returned by the upload/redeploy response — polling
|
|
333
|
+
// builds[0] races against a still-running previous build and can attach
|
|
334
|
+
// to the wrong one.
|
|
335
|
+
let buildId: string | null = knownBuildId ?? null;
|
|
336
|
+
if (!buildId) {
|
|
337
|
+
const spinner = ora("Waiting for build...").start();
|
|
338
|
+
for (let i = 0; i < 30; i++) {
|
|
339
|
+
await sleep(2000);
|
|
340
|
+
try {
|
|
341
|
+
const app = await api.get<App>(`/api/apps/${appId}`);
|
|
342
|
+
if (app.builds?.length) {
|
|
343
|
+
const latest = app.builds[0];
|
|
344
|
+
if (["building", "deploying", "running", "failed"].includes(latest.status)) {
|
|
345
|
+
buildId = latest.id;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
339
348
|
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
349
|
+
} catch {}
|
|
350
|
+
}
|
|
351
|
+
spinner.stop();
|
|
342
352
|
}
|
|
343
|
-
spinner.stop();
|
|
344
353
|
if (!buildId) {
|
|
345
354
|
info(chalk.dim("No build found. Check `lizard up status <id>`."));
|
|
346
355
|
return;
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { info, success, isJSONMode, printJSON } from "../lib/format.js";
|
|
4
|
-
import { CURRENT_VERSION, getLatestVersion, selfUpdate } from "../lib/updater.js";
|
|
4
|
+
import { CURRENT_VERSION, getLatestVersion, selfUpdate, isStandaloneBinary } from "../lib/updater.js";
|
|
5
5
|
|
|
6
6
|
export function registerUpgrade(program: Command) {
|
|
7
7
|
program
|
|
@@ -67,6 +67,26 @@ export function registerUpgrade(program: Command) {
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// npm install — self-replacing process.execPath would overwrite the
|
|
71
|
+
// user's node binary. Point at npm instead.
|
|
72
|
+
if (!isStandaloneBinary()) {
|
|
73
|
+
if (isJSONMode()) {
|
|
74
|
+
printJSON({
|
|
75
|
+
currentVersion: CURRENT_VERSION,
|
|
76
|
+
latestVersion: latest,
|
|
77
|
+
updateAvailable: true,
|
|
78
|
+
upgraded: false,
|
|
79
|
+
method: "npm",
|
|
80
|
+
hint: "npm install -g @lizard-build/cli@latest",
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
info(`Update available: v${CURRENT_VERSION} → ${chalk.green("v" + latest)}`);
|
|
85
|
+
info(`This copy was installed via npm. Upgrade with:`);
|
|
86
|
+
info(` ${chalk.cyan("npm install -g @lizard-build/cli@latest")}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
70
90
|
info(`Upgrading v${CURRENT_VERSION} → ${chalk.green("v" + latest)}...`);
|
|
71
91
|
|
|
72
92
|
try {
|