@lizard-build/cli 0.1.0
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.d.ts +2 -0
- package/dist/commands/add.js +72 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/connect.d.ts +2 -0
- package/dist/commands/connect.js +117 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/context.d.ts +2 -0
- package/dist/commands/context.js +71 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/deploy.d.ts +2 -0
- package/dist/commands/deploy.js +120 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/destroy.d.ts +2 -0
- package/dist/commands/destroy.js +51 -0
- package/dist/commands/destroy.js.map +1 -0
- package/dist/commands/git.d.ts +2 -0
- package/dist/commands/git.js +67 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +107 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/link.d.ts +2 -0
- package/dist/commands/link.js +50 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +92 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +16 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/projects.d.ts +2 -0
- package/dist/commands/projects.js +28 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/ps.d.ts +2 -0
- package/dist/commands/ps.js +52 -0
- package/dist/commands/ps.js.map +1 -0
- package/dist/commands/redeploy.d.ts +2 -0
- package/dist/commands/redeploy.js +69 -0
- package/dist/commands/redeploy.js.map +1 -0
- package/dist/commands/regions.d.ts +2 -0
- package/dist/commands/regions.js +23 -0
- package/dist/commands/regions.js.map +1 -0
- package/dist/commands/restart.d.ts +2 -0
- package/dist/commands/restart.js +21 -0
- package/dist/commands/restart.js.map +1 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +33 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/secrets.d.ts +2 -0
- package/dist/commands/secrets.js +138 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +51 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +41 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/version.d.ts +2 -0
- package/dist/commands/version.js +37 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +21 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +19 -0
- package/dist/lib/api.js +114 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/auth.d.ts +23 -0
- package/dist/lib/auth.js +89 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +23 -0
- package/dist/lib/config.js +77 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/format.d.ts +11 -0
- package/dist/lib/format.js +86 -0
- package/dist/lib/format.js.map +1 -0
- package/install.sh +59 -0
- package/package.json +35 -0
- package/src/commands/add.ts +100 -0
- package/src/commands/connect.ts +145 -0
- package/src/commands/context.ts +93 -0
- package/src/commands/deploy.ts +153 -0
- package/src/commands/destroy.ts +51 -0
- package/src/commands/git.ts +80 -0
- package/src/commands/init.ts +139 -0
- package/src/commands/link.ts +63 -0
- package/src/commands/login.ts +158 -0
- package/src/commands/logout.ts +17 -0
- package/src/commands/logs.ts +104 -0
- package/src/commands/open.ts +17 -0
- package/src/commands/projects.ts +45 -0
- package/src/commands/ps.ts +80 -0
- package/src/commands/redeploy.ts +74 -0
- package/src/commands/regions.ts +38 -0
- package/src/commands/restart.ts +24 -0
- package/src/commands/run.ts +43 -0
- package/src/commands/secrets.ts +175 -0
- package/src/commands/status.ts +65 -0
- package/src/commands/update.ts +44 -0
- package/src/commands/version.ts +37 -0
- package/src/commands/whoami.ts +27 -0
- package/src/index.ts +168 -0
- package/src/lib/api.ts +134 -0
- package/src/lib/auth.ts +113 -0
- package/src/lib/config.ts +93 -0
- package/src/lib/format.ts +95 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { api } from "../lib/api.js";
|
|
3
|
+
import { isJSONMode, printJSON, table } from "../lib/format.js";
|
|
4
|
+
|
|
5
|
+
interface Region {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
code: string;
|
|
9
|
+
endpoint?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerRegions(program: Command) {
|
|
13
|
+
const region = program
|
|
14
|
+
.command("region")
|
|
15
|
+
.description("Region management");
|
|
16
|
+
|
|
17
|
+
region
|
|
18
|
+
.command("list")
|
|
19
|
+
.description("List available regions")
|
|
20
|
+
.action(async () => {
|
|
21
|
+
const regions = await api.get<Region[]>("/api/regions");
|
|
22
|
+
|
|
23
|
+
if (isJSONMode()) {
|
|
24
|
+
printJSON(regions);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (regions.length === 0) {
|
|
29
|
+
console.log("No regions available.");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
table(
|
|
34
|
+
["Code", "Name"],
|
|
35
|
+
regions.map((r) => [r.code || r.id, r.name]),
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { api } from "../lib/api.js";
|
|
5
|
+
import { success, isJSONMode, printJSON } from "../lib/format.js";
|
|
6
|
+
|
|
7
|
+
export function registerRestart(program: Command) {
|
|
8
|
+
program
|
|
9
|
+
.command("restart")
|
|
10
|
+
.argument("<id>", "Service ID to restart")
|
|
11
|
+
.description("Restart a service")
|
|
12
|
+
.action(async (id: string) => {
|
|
13
|
+
const spinner = ora("Restarting...").start();
|
|
14
|
+
|
|
15
|
+
await api.post(`/api/apps/${id}/restart`);
|
|
16
|
+
|
|
17
|
+
spinner.stop();
|
|
18
|
+
if (isJSONMode()) {
|
|
19
|
+
printJSON({ id, status: "restarting" });
|
|
20
|
+
} else {
|
|
21
|
+
success(`Service ${id} restarting`);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { api } from "../lib/api.js";
|
|
4
|
+
import { resolveProjectId } from "../lib/config.js";
|
|
5
|
+
|
|
6
|
+
interface Secret {
|
|
7
|
+
key: string;
|
|
8
|
+
value: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function registerRun(program: Command) {
|
|
12
|
+
program
|
|
13
|
+
.command("run")
|
|
14
|
+
.argument("<command...>", "Command to run with project env vars")
|
|
15
|
+
.description("Run a command with project secrets as env vars")
|
|
16
|
+
.allowUnknownOption()
|
|
17
|
+
.action(async (args: string[]) => {
|
|
18
|
+
const projectId = resolveProjectId(program.opts().project);
|
|
19
|
+
|
|
20
|
+
// Fetch secrets
|
|
21
|
+
const secrets = await api.get<Secret[]>(
|
|
22
|
+
`/api/projects/${projectId}/secrets`,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Build env
|
|
26
|
+
const env: Record<string, string> = { ...process.env as Record<string, string> };
|
|
27
|
+
for (const s of secrets) {
|
|
28
|
+
env[s.key] = s.value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Run command
|
|
32
|
+
const cmd = args.join(" ");
|
|
33
|
+
try {
|
|
34
|
+
execSync(cmd, {
|
|
35
|
+
env,
|
|
36
|
+
stdio: "inherit",
|
|
37
|
+
shell: process.env.SHELL || "/bin/sh",
|
|
38
|
+
});
|
|
39
|
+
} catch (err: any) {
|
|
40
|
+
process.exit(err.status || 1);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { api } from "../lib/api.js";
|
|
4
|
+
import { resolveProjectId } from "../lib/config.js";
|
|
5
|
+
import { success, isJSONMode, printJSON, table } from "../lib/format.js";
|
|
6
|
+
|
|
7
|
+
interface Secret {
|
|
8
|
+
key: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerSecrets(program: Command) {
|
|
13
|
+
const secret = program
|
|
14
|
+
.command("secret")
|
|
15
|
+
.description("Manage project secrets");
|
|
16
|
+
|
|
17
|
+
secret
|
|
18
|
+
.command("list")
|
|
19
|
+
.description("List project secrets")
|
|
20
|
+
.option("--show", "Reveal secret values")
|
|
21
|
+
.action(async (opts) => {
|
|
22
|
+
const projectId = resolveProjectId(program.opts().project);
|
|
23
|
+
const secrets = await api.get<Secret[]>(
|
|
24
|
+
`/api/projects/${projectId}/secrets`,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (isJSONMode()) {
|
|
28
|
+
printJSON(
|
|
29
|
+
opts.show
|
|
30
|
+
? secrets
|
|
31
|
+
: secrets.map((s) => ({ key: s.key, value: "***" })),
|
|
32
|
+
);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (secrets.length === 0) {
|
|
37
|
+
console.log("No secrets. Use `lizard secret set KEY=value`.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
table(
|
|
42
|
+
["Key", "Value"],
|
|
43
|
+
secrets.map((s) => [
|
|
44
|
+
s.key,
|
|
45
|
+
opts.show ? s.value : chalk.dim("•".repeat(Math.min(s.value.length, 20))),
|
|
46
|
+
]),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
secret
|
|
51
|
+
.command("set")
|
|
52
|
+
.argument("<pairs...>", "KEY=value pairs")
|
|
53
|
+
.description("Set one or more secrets")
|
|
54
|
+
.option("--no-redeploy", "Don't trigger redeploy")
|
|
55
|
+
.action(async (pairs: string[], opts) => {
|
|
56
|
+
const projectId = resolveProjectId(program.opts().project);
|
|
57
|
+
|
|
58
|
+
// Parse KEY=value pairs
|
|
59
|
+
const newSecrets: Record<string, string> = {};
|
|
60
|
+
for (const pair of pairs) {
|
|
61
|
+
const eqIdx = pair.indexOf("=");
|
|
62
|
+
if (eqIdx < 1) {
|
|
63
|
+
throw new Error(`Invalid format: "${pair}". Use KEY=value`);
|
|
64
|
+
}
|
|
65
|
+
newSecrets[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Get existing secrets, merge, update
|
|
69
|
+
const existing = await api.get<Secret[]>(
|
|
70
|
+
`/api/projects/${projectId}/secrets`,
|
|
71
|
+
);
|
|
72
|
+
const merged: Secret[] = [];
|
|
73
|
+
const existingKeys = new Set<string>();
|
|
74
|
+
|
|
75
|
+
for (const s of existing) {
|
|
76
|
+
if (newSecrets[s.key] !== undefined) {
|
|
77
|
+
merged.push({ key: s.key, value: newSecrets[s.key] });
|
|
78
|
+
existingKeys.add(s.key);
|
|
79
|
+
} else {
|
|
80
|
+
merged.push(s);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Add new keys
|
|
84
|
+
for (const [key, value] of Object.entries(newSecrets)) {
|
|
85
|
+
if (!existingKeys.has(key)) {
|
|
86
|
+
merged.push({ key, value });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await api.put(`/api/projects/${projectId}/secrets`, merged);
|
|
91
|
+
|
|
92
|
+
if (isJSONMode()) {
|
|
93
|
+
printJSON({ updated: Object.keys(newSecrets) });
|
|
94
|
+
} else {
|
|
95
|
+
success(
|
|
96
|
+
`${Object.keys(newSecrets).length} secret(s) updated`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
secret
|
|
102
|
+
.command("delete")
|
|
103
|
+
.argument("<keys...>", "Secret keys to delete")
|
|
104
|
+
.description("Delete one or more secrets")
|
|
105
|
+
.action(async (keys: string[]) => {
|
|
106
|
+
const projectId = resolveProjectId(program.opts().project);
|
|
107
|
+
const existing = await api.get<Secret[]>(
|
|
108
|
+
`/api/projects/${projectId}/secrets`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const keysSet = new Set(keys);
|
|
112
|
+
const filtered = existing.filter((s) => !keysSet.has(s.key));
|
|
113
|
+
|
|
114
|
+
if (filtered.length === existing.length) {
|
|
115
|
+
throw new Error(`Secret(s) not found: ${keys.join(", ")}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
await api.put(`/api/projects/${projectId}/secrets`, filtered);
|
|
119
|
+
|
|
120
|
+
if (isJSONMode()) {
|
|
121
|
+
printJSON({ deleted: keys });
|
|
122
|
+
} else {
|
|
123
|
+
success(`${keys.length} secret(s) deleted`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
secret
|
|
128
|
+
.command("import")
|
|
129
|
+
.description("Import secrets from stdin (KEY=value format, one per line)")
|
|
130
|
+
.option("--no-redeploy", "Don't trigger redeploy")
|
|
131
|
+
.action(async () => {
|
|
132
|
+
const projectId = resolveProjectId(program.opts().project);
|
|
133
|
+
|
|
134
|
+
// Read stdin
|
|
135
|
+
const chunks: Buffer[] = [];
|
|
136
|
+
for await (const chunk of process.stdin) {
|
|
137
|
+
chunks.push(chunk as Buffer);
|
|
138
|
+
}
|
|
139
|
+
const input = Buffer.concat(chunks).toString("utf-8");
|
|
140
|
+
|
|
141
|
+
const newSecrets: Record<string, string> = {};
|
|
142
|
+
for (const line of input.split("\n")) {
|
|
143
|
+
const trimmed = line.trim();
|
|
144
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
145
|
+
const eqIdx = trimmed.indexOf("=");
|
|
146
|
+
if (eqIdx < 1) continue;
|
|
147
|
+
newSecrets[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (Object.keys(newSecrets).length === 0) {
|
|
151
|
+
throw new Error("No valid KEY=value pairs found in input");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Merge with existing
|
|
155
|
+
const existing = await api.get<Secret[]>(
|
|
156
|
+
`/api/projects/${projectId}/secrets`,
|
|
157
|
+
);
|
|
158
|
+
const existingMap = new Map(existing.map((s) => [s.key, s.value]));
|
|
159
|
+
for (const [k, v] of Object.entries(newSecrets)) {
|
|
160
|
+
existingMap.set(k, v);
|
|
161
|
+
}
|
|
162
|
+
const merged = Array.from(existingMap.entries()).map(([key, value]) => ({
|
|
163
|
+
key,
|
|
164
|
+
value,
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
await api.put(`/api/projects/${projectId}/secrets`, merged);
|
|
168
|
+
|
|
169
|
+
if (isJSONMode()) {
|
|
170
|
+
printJSON({ imported: Object.keys(newSecrets) });
|
|
171
|
+
} else {
|
|
172
|
+
success(`${Object.keys(newSecrets).length} secret(s) imported`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { api } from "../lib/api.js";
|
|
4
|
+
import { resolveProjectId, findProjectConfig } from "../lib/config.js";
|
|
5
|
+
import { isJSONMode, printJSON, statusColor, table } from "../lib/format.js";
|
|
6
|
+
|
|
7
|
+
export function registerStatus(program: Command) {
|
|
8
|
+
program
|
|
9
|
+
.command("status")
|
|
10
|
+
.description("Show project status")
|
|
11
|
+
.action(async () => {
|
|
12
|
+
const projectId = resolveProjectId(program.opts().project);
|
|
13
|
+
const config = findProjectConfig();
|
|
14
|
+
|
|
15
|
+
const [project, services] = await Promise.all([
|
|
16
|
+
api.get<{ id: string; name: string; slug: string }>(
|
|
17
|
+
`/api/projects/${projectId}`,
|
|
18
|
+
),
|
|
19
|
+
api.get<{ apps: any[]; addons: any[] }>(
|
|
20
|
+
`/api/projects/${projectId}/services`,
|
|
21
|
+
),
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
if (isJSONMode()) {
|
|
25
|
+
printJSON({ project, services, environment: config?.environment || "production" });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(chalk.bold(project.name) + chalk.dim(` (${project.id})`));
|
|
30
|
+
if (config?.environment) {
|
|
31
|
+
console.log(chalk.dim(`Environment: ${config.environment}`));
|
|
32
|
+
}
|
|
33
|
+
console.log();
|
|
34
|
+
|
|
35
|
+
const allServices = [
|
|
36
|
+
...(services.apps || []).map((a: any) => ({
|
|
37
|
+
name: a.name,
|
|
38
|
+
type: "app",
|
|
39
|
+
status: a.status,
|
|
40
|
+
url: a.domain ? `https://${a.domain}` : "",
|
|
41
|
+
})),
|
|
42
|
+
...(services.addons || []).map((a: any) => ({
|
|
43
|
+
name: a.name || a.addonType,
|
|
44
|
+
type: a.addonType || "addon",
|
|
45
|
+
status: a.status,
|
|
46
|
+
url: a.hostname || "",
|
|
47
|
+
})),
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
if (allServices.length === 0) {
|
|
51
|
+
console.log(chalk.dim("No services"));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
table(
|
|
56
|
+
["Name", "Type", "Status", "URL"],
|
|
57
|
+
allServices.map((s) => [
|
|
58
|
+
s.name,
|
|
59
|
+
s.type,
|
|
60
|
+
statusColor(s.status),
|
|
61
|
+
s.url || chalk.dim("—"),
|
|
62
|
+
]),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { info, isJSONMode, printJSON } from "../lib/format.js";
|
|
4
|
+
|
|
5
|
+
export function registerUpdate(program: Command) {
|
|
6
|
+
program
|
|
7
|
+
.command("update")
|
|
8
|
+
.description("Update Lizard CLI to latest version")
|
|
9
|
+
.option("--check", "Only check for updates without installing")
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
// Check npm for latest version
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch("https://registry.npmjs.org/@lizard/cli/latest");
|
|
14
|
+
if (!res.ok) throw new Error("Failed to check for updates");
|
|
15
|
+
const data = (await res.json()) as { version: string };
|
|
16
|
+
|
|
17
|
+
if (isJSONMode()) {
|
|
18
|
+
printJSON({
|
|
19
|
+
currentVersion: "0.1.0",
|
|
20
|
+
latestVersion: data.version,
|
|
21
|
+
updateAvailable: data.version !== "0.1.0",
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (data.version === "0.1.0") {
|
|
27
|
+
info("Already up to date (v0.1.0)");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (opts.check) {
|
|
32
|
+
info(`Update available: v0.1.0 → v${data.version}`);
|
|
33
|
+
info(chalk.dim(`Run \`lizard update\` to install`));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
info(`Updating to v${data.version}...`);
|
|
38
|
+
const { execSync } = await import("node:child_process");
|
|
39
|
+
execSync("npm install -g @lizard/cli@latest", { stdio: "inherit" });
|
|
40
|
+
} catch {
|
|
41
|
+
info("Could not check for updates. Run `npm update -g @lizard/cli` manually.");
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { isJSONMode, printJSON } from "../lib/format.js";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
function getVersion(): string {
|
|
8
|
+
try {
|
|
9
|
+
// Try to read from package.json
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const pkgPath = join(__dirname, "..", "..", "package.json");
|
|
12
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
13
|
+
return pkg.version || "0.1.0";
|
|
14
|
+
} catch {
|
|
15
|
+
return "0.1.0";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function registerVersion(program: Command) {
|
|
20
|
+
program
|
|
21
|
+
.command("version")
|
|
22
|
+
.description("Show CLI version")
|
|
23
|
+
.action(() => {
|
|
24
|
+
const version = getVersion();
|
|
25
|
+
if (isJSONMode()) {
|
|
26
|
+
printJSON({
|
|
27
|
+
version,
|
|
28
|
+
platform: process.platform,
|
|
29
|
+
arch: process.arch,
|
|
30
|
+
node: process.version,
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
console.log(`lizard v${version}`);
|
|
34
|
+
console.log(`${process.platform}/${process.arch} node/${process.version}`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { api } from "../lib/api.js";
|
|
4
|
+
import { isJSONMode, printJSON } from "../lib/format.js";
|
|
5
|
+
|
|
6
|
+
export function registerWhoami(program: Command) {
|
|
7
|
+
program
|
|
8
|
+
.command("whoami")
|
|
9
|
+
.description("Show current user")
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const user = await api.get<{
|
|
12
|
+
id: string;
|
|
13
|
+
username: string;
|
|
14
|
+
avatarUrl?: string;
|
|
15
|
+
hasGithubApp?: boolean;
|
|
16
|
+
}>("/api/auth/me");
|
|
17
|
+
|
|
18
|
+
if (isJSONMode()) {
|
|
19
|
+
printJSON(user);
|
|
20
|
+
} else {
|
|
21
|
+
console.log(chalk.bold(user.username));
|
|
22
|
+
if (user.hasGithubApp) {
|
|
23
|
+
console.log(chalk.dim("GitHub App: connected"));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { setJSONMode, isJSONMode, error } from "./lib/format.js";
|
|
5
|
+
import { setTokenOverride, requireAuth, isLoggedIn } from "./lib/auth.js";
|
|
6
|
+
import { setBaseURL, setAccessToken } from "./lib/api.js";
|
|
7
|
+
|
|
8
|
+
// Commands
|
|
9
|
+
import { registerLogin } from "./commands/login.js";
|
|
10
|
+
import { registerLogout } from "./commands/logout.js";
|
|
11
|
+
import { registerWhoami } from "./commands/whoami.js";
|
|
12
|
+
import { registerInit } from "./commands/init.js";
|
|
13
|
+
import { registerLink } from "./commands/link.js";
|
|
14
|
+
import { registerProjects } from "./commands/projects.js";
|
|
15
|
+
import { registerDeploy } from "./commands/deploy.js";
|
|
16
|
+
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
|
+
import { registerRedeploy } from "./commands/redeploy.js";
|
|
21
|
+
import { registerLogs } from "./commands/logs.js";
|
|
22
|
+
import { registerSecrets } from "./commands/secrets.js";
|
|
23
|
+
import { registerRegions } from "./commands/regions.js";
|
|
24
|
+
import { registerStatus } from "./commands/status.js";
|
|
25
|
+
import { registerOpen } from "./commands/open.js";
|
|
26
|
+
import { registerRun } from "./commands/run.js";
|
|
27
|
+
import { registerConnect } from "./commands/connect.js";
|
|
28
|
+
import { registerContext } from "./commands/context.js";
|
|
29
|
+
import { registerGit } from "./commands/git.js";
|
|
30
|
+
import { registerVersion } from "./commands/version.js";
|
|
31
|
+
import { registerUpdate } from "./commands/update.js";
|
|
32
|
+
|
|
33
|
+
const program = new Command();
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.name("lizard")
|
|
37
|
+
.description("Lizard CLI — deploy and manage apps on Lizard")
|
|
38
|
+
.version("0.1.0")
|
|
39
|
+
.option("--json", "Output in JSON format")
|
|
40
|
+
.option("-y, --yes", "Skip confirmation prompts")
|
|
41
|
+
.option("--workspace <id>", "Workspace name or ID")
|
|
42
|
+
.option("--project <id>", "Project ID")
|
|
43
|
+
.option("--environment <name>", "Environment name")
|
|
44
|
+
.option("--region <region>", "Region for creating services")
|
|
45
|
+
.option("--token <token>", "API token")
|
|
46
|
+
.option("--no-color", "Disable colors")
|
|
47
|
+
.option("--verbose", "Verbose output")
|
|
48
|
+
.hook("preAction", async (thisCommand, actionCommand) => {
|
|
49
|
+
const opts = thisCommand.opts();
|
|
50
|
+
|
|
51
|
+
// JSON mode: explicit flag or non-TTY stdout
|
|
52
|
+
if (opts.json || !process.stdout.isTTY) {
|
|
53
|
+
setJSONMode(true);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Token override
|
|
57
|
+
if (opts.token) {
|
|
58
|
+
setTokenOverride(opts.token);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// API URL override
|
|
62
|
+
if (process.env.LIZARD_API_URL) {
|
|
63
|
+
setBaseURL(process.env.LIZARD_API_URL);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Commands that don't need auth
|
|
67
|
+
const noAuth = new Set(["login", "logout", "version", "completion", "update", "help"]);
|
|
68
|
+
if (noAuth.has(actionCommand.name())) return;
|
|
69
|
+
|
|
70
|
+
// Require auth — auto-triggers login flow if not logged in
|
|
71
|
+
const creds = await requireAuth();
|
|
72
|
+
setAccessToken(creds.accessToken);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Register all commands
|
|
76
|
+
registerLogin(program);
|
|
77
|
+
registerLogout(program);
|
|
78
|
+
registerWhoami(program);
|
|
79
|
+
registerInit(program);
|
|
80
|
+
registerLink(program);
|
|
81
|
+
registerProjects(program);
|
|
82
|
+
registerDeploy(program);
|
|
83
|
+
registerPs(program);
|
|
84
|
+
registerAdd(program);
|
|
85
|
+
registerDestroy(program);
|
|
86
|
+
registerRestart(program);
|
|
87
|
+
registerRedeploy(program);
|
|
88
|
+
registerLogs(program);
|
|
89
|
+
registerSecrets(program);
|
|
90
|
+
registerRegions(program);
|
|
91
|
+
registerStatus(program);
|
|
92
|
+
registerOpen(program);
|
|
93
|
+
registerRun(program);
|
|
94
|
+
registerConnect(program);
|
|
95
|
+
registerContext(program);
|
|
96
|
+
registerGit(program);
|
|
97
|
+
registerVersion(program);
|
|
98
|
+
registerUpdate(program);
|
|
99
|
+
|
|
100
|
+
// Shell completion
|
|
101
|
+
program
|
|
102
|
+
.command("completion")
|
|
103
|
+
.argument("<shell>", "Shell type (bash, zsh, fish)")
|
|
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
|
+
});
|
|
119
|
+
|
|
120
|
+
// Error handling
|
|
121
|
+
program.exitOverride();
|
|
122
|
+
|
|
123
|
+
async function main() {
|
|
124
|
+
try {
|
|
125
|
+
await program.parseAsync(process.argv);
|
|
126
|
+
} catch (err: any) {
|
|
127
|
+
// Commander throws for --help, --version, etc. — ignore those
|
|
128
|
+
if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
if (err.code === "commander.help") {
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const msg = err.message || String(err);
|
|
136
|
+
|
|
137
|
+
if (isJSONMode()) {
|
|
138
|
+
console.log(
|
|
139
|
+
JSON.stringify(
|
|
140
|
+
{
|
|
141
|
+
error: {
|
|
142
|
+
code: err.code || "ERROR",
|
|
143
|
+
message: msg,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
null,
|
|
147
|
+
2,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
error(msg);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Exit codes per spec
|
|
155
|
+
if (msg.includes("Not authenticated") || msg.includes("Invalid token")) {
|
|
156
|
+
process.exit(2);
|
|
157
|
+
}
|
|
158
|
+
if (msg.includes("not found") || msg.includes("Not found")) {
|
|
159
|
+
process.exit(3);
|
|
160
|
+
}
|
|
161
|
+
if (msg.includes("timeout") || msg.includes("Timeout")) {
|
|
162
|
+
process.exit(4);
|
|
163
|
+
}
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
main();
|