@lizard-build/cli 4.0.48 → 4.0.50
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/README.md +2 -0
- package/dist/commands/login.js +15 -12
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/pause.d.ts +30 -0
- package/dist/commands/pause.js +62 -0
- package/dist/commands/pause.js.map +1 -0
- package/dist/commands/projects.js +20 -0
- package/dist/commands/projects.js.map +1 -1
- package/dist/commands/whoami.js +19 -9
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/workspace.js +19 -0
- package/dist/commands/workspace.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/format.js +1 -0
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/signin.d.ts +17 -2
- package/dist/lib/signin.js +21 -1
- package/dist/lib/signin.js.map +1 -1
- package/dist/lib/skills-data.generated.js +1 -1
- package/dist/lib/skills-data.generated.js.map +1 -1
- package/dist/lib/updater.d.ts +1 -1
- package/dist/lib/updater.js +1 -1
- package/package.json +1 -1
- package/skill-data/core/SKILL.md +5 -0
- package/src/commands/login.ts +16 -12
- package/src/commands/pause.ts +72 -0
- package/src/commands/projects.ts +18 -0
- package/src/commands/whoami.ts +20 -21
- package/src/commands/workspace.ts +16 -0
- package/src/index.ts +2 -0
- package/src/lib/format.ts +1 -0
- package/src/lib/signin.ts +36 -3
- package/src/lib/updater.ts +1 -1
- package/test/unit/describe-me.test.ts +23 -0
- package/test/unit/pause.test.ts +37 -0
|
@@ -4,6 +4,7 @@ import { Command } from "commander";
|
|
|
4
4
|
import { api, withQuery, type Workspace } from "../lib/api.js";
|
|
5
5
|
import { fetchWorkspaces, resolveWorkspace } from "../lib/picker.js";
|
|
6
6
|
import { isJSONMode, printJSON, table, success, info, isTTY } from "../lib/format.js";
|
|
7
|
+
import { printBulkResult, type BulkPauseResult } from "./pause.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* `lizard workspace` — create, list, and delete workspaces.
|
|
@@ -61,6 +62,21 @@ export function registerWorkspace(program: Command) {
|
|
|
61
62
|
);
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
for (const op of ["pause", "resume"] as const) {
|
|
66
|
+
ws.command(op)
|
|
67
|
+
.argument("<name-or-id>", "Workspace name, slug, or ID")
|
|
68
|
+
.description(op === "pause"
|
|
69
|
+
? "Pause every service in every project of a workspace (owner only; keeps URLs, passwords, settings and data)"
|
|
70
|
+
: "Resume the services you paused in a workspace (owner only)")
|
|
71
|
+
.action(async (nameOrId: string) => {
|
|
72
|
+
const workspace = await resolveWorkspace(nameOrId);
|
|
73
|
+
if (workspace.role !== "owner") throw new Error(`Only the workspace owner can ${op} all of it.`);
|
|
74
|
+
const result = await api.post<BulkPauseResult>(`/api/workspaces/${workspace.id}/${op}`);
|
|
75
|
+
if (isJSONMode()) { printJSON({ id: workspace.id, ...result }); return; }
|
|
76
|
+
printBulkResult(op, `workspace ${chalk.bold(workspace.name)}`, result);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
ws.command("rm")
|
|
65
81
|
.alias("delete")
|
|
66
82
|
.argument("<name-or-id>", "Workspace name, slug, or ID")
|
package/src/index.ts
CHANGED
|
@@ -82,6 +82,7 @@ import { registerProjects } from "./commands/projects.js";
|
|
|
82
82
|
import { registerPs } from "./commands/ps.js";
|
|
83
83
|
import { registerRedeploy } from "./commands/redeploy.js";
|
|
84
84
|
import { registerRegions } from "./commands/regions.js";
|
|
85
|
+
import { registerPause } from "./commands/pause.js";
|
|
85
86
|
import { registerRestart } from "./commands/restart.js";
|
|
86
87
|
import { registerRun } from "./commands/run.js";
|
|
87
88
|
import { registerS3 } from "./commands/s3.js";
|
|
@@ -178,6 +179,7 @@ registerProjects(program);
|
|
|
178
179
|
registerPs(program);
|
|
179
180
|
registerRedeploy(program);
|
|
180
181
|
registerRegions(program);
|
|
182
|
+
registerPause(program);
|
|
181
183
|
registerRestart(program);
|
|
182
184
|
registerRun(program);
|
|
183
185
|
registerS3(program);
|
package/src/lib/format.ts
CHANGED
package/src/lib/signin.ts
CHANGED
|
@@ -48,11 +48,44 @@ export interface CheckResponse {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
/** One project or workspace a scoped API key reaches, by name. */
|
|
52
|
+
export interface KeyScope {
|
|
53
|
+
type: "workspace" | "project";
|
|
52
54
|
id: string;
|
|
53
|
-
|
|
55
|
+
name?: string | null;
|
|
56
|
+
workspaceId?: string | null;
|
|
57
|
+
workspaceName?: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Me {
|
|
61
|
+
// Absent for a scoped API key: the platform never tells a scoped key whose account it
|
|
62
|
+
// is (it is handed to people who are not the account's members).
|
|
63
|
+
id?: string;
|
|
64
|
+
username?: string;
|
|
54
65
|
email?: string | null;
|
|
55
66
|
scoped?: boolean;
|
|
67
|
+
scopes?: KeyScope[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** "Project X (workspace Y)" for one scope; the id when the name is unknown. */
|
|
71
|
+
export function describeScope(sc: KeyScope): string {
|
|
72
|
+
if (sc.type === "project") {
|
|
73
|
+
const ws = sc.workspaceName ? ` (workspace ${sc.workspaceName})` : "";
|
|
74
|
+
return `project ${sc.name || sc.id}${ws}`;
|
|
75
|
+
}
|
|
76
|
+
return `workspace ${sc.name || sc.id}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Who a login is, for "Signed in as ...": the username for an account, and the
|
|
80
|
+
* project/workspace for a scoped key -- never the account owner's name, even from an
|
|
81
|
+
* older platform that still sends it. */
|
|
82
|
+
export function describeMe(me: Me): string {
|
|
83
|
+
if (me.scoped) {
|
|
84
|
+
const scopes = me.scopes ?? [];
|
|
85
|
+
if (scopes.length === 0) return "a scoped API key";
|
|
86
|
+
return `a key for ${scopes.map(describeScope).join(", ")}`;
|
|
87
|
+
}
|
|
88
|
+
return me.username ?? "unknown";
|
|
56
89
|
}
|
|
57
90
|
|
|
58
91
|
export interface Pending {
|
|
@@ -315,6 +348,6 @@ export async function signInInteractive(flag?: AuthProvider): Promise<Me> {
|
|
|
315
348
|
saveSessionLogin({ accessToken: result.accessToken, user: result.user });
|
|
316
349
|
spin.stop("Authorized");
|
|
317
350
|
const me = await api.get<Me>("/api/auth/me");
|
|
318
|
-
p.log.success(`Signed in as ${chalk.bold(me
|
|
351
|
+
p.log.success(`Signed in as ${chalk.bold(describeMe(me))}`);
|
|
319
352
|
return me;
|
|
320
353
|
}
|
package/src/lib/updater.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { join, dirname } from "node:path";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
|
|
8
|
-
export const CURRENT_VERSION = "4.0.
|
|
8
|
+
export const CURRENT_VERSION = "4.0.50";
|
|
9
9
|
const RELEASES_API = "https://api.github.com/repos/lizard-build/lizard-cli/releases/latest";
|
|
10
10
|
const RELEASE_BASE = "https://github.com/lizard-build/lizard-cli/releases/latest/download";
|
|
11
11
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { describeMe } from "../../src/lib/signin.js";
|
|
3
|
+
|
|
4
|
+
describe("describeMe", () => {
|
|
5
|
+
it("names an account by its username", () => {
|
|
6
|
+
expect(describeMe({ id: "u1", username: "alice" })).toBe("alice");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("names a project-scoped key by its project and workspace, never the owner", () => {
|
|
10
|
+
const text = describeMe({
|
|
11
|
+
// An older platform still sent the owner for a scoped key.
|
|
12
|
+
id: "u1", username: "alice-the-owner", scoped: true,
|
|
13
|
+
scopes: [{ type: "project", id: "p1", name: "Shop", workspaceId: "w1", workspaceName: "Acme" }],
|
|
14
|
+
});
|
|
15
|
+
expect(text).toBe("a key for project Shop (workspace Acme)");
|
|
16
|
+
expect(text).not.toContain("alice");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("names a workspace-scoped key by its workspace, falling back to the id", () => {
|
|
20
|
+
expect(describeMe({ scoped: true, scopes: [{ type: "workspace", id: "w1", name: "Acme" }, { type: "workspace", id: "w2" }] }))
|
|
21
|
+
.toBe("a key for workspace Acme, workspace w2");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, test, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { servicePausePath, printBulkResult } from "../../src/commands/pause.js";
|
|
3
|
+
|
|
4
|
+
afterEach(() => vi.restoreAllMocks());
|
|
5
|
+
|
|
6
|
+
describe("servicePausePath", () => {
|
|
7
|
+
test("an app pauses and resumes through /api/apps/:id", () => {
|
|
8
|
+
expect(servicePausePath("pause", { id: "a1", kind: "app" }, "p1", {})).toBe("/api/apps/a1/pause");
|
|
9
|
+
expect(servicePausePath("resume", { id: "a1", kind: "app" }, "p1", { workspaceId: "w1" })).toBe("/api/apps/a1/resume?workspaceId=w1");
|
|
10
|
+
});
|
|
11
|
+
test("a database (addon) pauses through its project", () => {
|
|
12
|
+
expect(servicePausePath("pause", { id: "d1", kind: "addon" }, "p1", { workspaceId: "w1" }))
|
|
13
|
+
.toBe("/api/projects/p1/addons/d1/pause?workspaceId=w1");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("printBulkResult", () => {
|
|
18
|
+
test("lists what was paused and what was skipped with why", () => {
|
|
19
|
+
const out: string[] = [];
|
|
20
|
+
vi.spyOn(process.stderr, "write").mockImplementation((chunk: any) => { out.push(String(chunk)); return true; });
|
|
21
|
+
printBulkResult("pause", "project demo", {
|
|
22
|
+
done: [{ kind: "app", id: "a1", name: "web" }, { kind: "database", id: "d1", name: "postgres" }],
|
|
23
|
+
skipped: [{ kind: "app", id: "a2", name: "worker", reason: "already paused" }],
|
|
24
|
+
});
|
|
25
|
+
const text = out.join("\n");
|
|
26
|
+
expect(text).toContain("Paused 2 services in project demo");
|
|
27
|
+
expect(text).toContain("web");
|
|
28
|
+
expect(text).toContain("worker");
|
|
29
|
+
expect(text).toContain("already paused");
|
|
30
|
+
});
|
|
31
|
+
test("says when there was nothing to do", () => {
|
|
32
|
+
const out: string[] = [];
|
|
33
|
+
vi.spyOn(process.stderr, "write").mockImplementation((chunk: any) => { out.push(String(chunk)); return true; });
|
|
34
|
+
printBulkResult("resume", "workspace team", { done: [], skipped: [] });
|
|
35
|
+
expect(out.join("\n")).toContain("nothing to resume");
|
|
36
|
+
});
|
|
37
|
+
});
|