@uetuluk/create-cli 0.0.1 → 0.0.3
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/cli.js +76 -18
- package/package.json +52 -47
package/dist/cli.js
CHANGED
|
@@ -25,11 +25,27 @@ async function api(serverUrl2, path, init = {}) {
|
|
|
25
25
|
} catch {
|
|
26
26
|
}
|
|
27
27
|
if (!r.ok) {
|
|
28
|
-
const msg =
|
|
28
|
+
const msg = formatErrorMessage(body, r.status);
|
|
29
29
|
throw new ApiError(r.status, msg, body);
|
|
30
30
|
}
|
|
31
31
|
return body;
|
|
32
32
|
}
|
|
33
|
+
function formatErrorMessage(body, status) {
|
|
34
|
+
if (!body || typeof body !== "object") return `HTTP ${status}`;
|
|
35
|
+
const b = body;
|
|
36
|
+
const outer = typeof b.error === "string" && b.error || typeof b.message === "string" && b.message || "";
|
|
37
|
+
const inner = innerDetail(b.data);
|
|
38
|
+
if (outer && inner && inner !== outer) return `${outer}: ${inner}`;
|
|
39
|
+
return outer || inner || `HTTP ${status}`;
|
|
40
|
+
}
|
|
41
|
+
function innerDetail(data) {
|
|
42
|
+
if (!data || typeof data !== "object") return "";
|
|
43
|
+
const d = data;
|
|
44
|
+
for (const v of [d.error, d.detail, d.message]) {
|
|
45
|
+
if (typeof v === "string" && v) return v;
|
|
46
|
+
}
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
33
49
|
|
|
34
50
|
// src/store.ts
|
|
35
51
|
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -101,6 +117,33 @@ function requireAuth() {
|
|
|
101
117
|
}
|
|
102
118
|
return c;
|
|
103
119
|
}
|
|
120
|
+
async function apiAuth(path, init = {}) {
|
|
121
|
+
const creds = requireAuth();
|
|
122
|
+
const url = init.server ?? creds.serverUrl;
|
|
123
|
+
const { server, ...rest } = init;
|
|
124
|
+
try {
|
|
125
|
+
return await api(url, path, { ...rest, token: creds.token });
|
|
126
|
+
} catch (e) {
|
|
127
|
+
if (!(e instanceof ApiError) || e.status !== 401 || !creds.refreshToken) throw e;
|
|
128
|
+
let refreshed;
|
|
129
|
+
try {
|
|
130
|
+
refreshed = await api(url, "/auth/refresh-token", {
|
|
131
|
+
method: "POST",
|
|
132
|
+
body: JSON.stringify({ refresh_token: creds.refreshToken })
|
|
133
|
+
});
|
|
134
|
+
} catch {
|
|
135
|
+
throw e;
|
|
136
|
+
}
|
|
137
|
+
if (url === creds.serverUrl) {
|
|
138
|
+
saveCredentials({
|
|
139
|
+
...creds,
|
|
140
|
+
token: refreshed.token,
|
|
141
|
+
refreshToken: refreshed.refresh_token
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return await api(url, path, { ...rest, token: refreshed.token });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
104
147
|
function wrap(fn) {
|
|
105
148
|
return async (...args) => {
|
|
106
149
|
try {
|
|
@@ -109,7 +152,15 @@ function wrap(fn) {
|
|
|
109
152
|
if (e instanceof ApiError) {
|
|
110
153
|
console.error(colors.red(`${e.status} ${e.message}`));
|
|
111
154
|
} else {
|
|
112
|
-
|
|
155
|
+
const err = e;
|
|
156
|
+
let msg = err.message || String(e);
|
|
157
|
+
if (msg === "fetch failed" && err.cause) {
|
|
158
|
+
const cause = err.cause;
|
|
159
|
+
const code = cause.code ? ` [${cause.code}]` : "";
|
|
160
|
+
msg = `fetch failed: ${cause.message ?? cause}${code}`;
|
|
161
|
+
}
|
|
162
|
+
console.error(colors.red(msg));
|
|
163
|
+
if (process.env.TEENY_DEBUG) console.error(err.stack ?? err);
|
|
113
164
|
}
|
|
114
165
|
process.exit(1);
|
|
115
166
|
}
|
|
@@ -168,19 +219,15 @@ cli.command("whoami", "show current user").action(wrap(async () => {
|
|
|
168
219
|
cli.command("create", "provision a new instance").action(wrap(async (opts) => {
|
|
169
220
|
const c = requireAuth();
|
|
170
221
|
const url = opts.server ? serverUrl(opts) : c.serverUrl;
|
|
171
|
-
const r = await
|
|
172
|
-
method: "POST",
|
|
173
|
-
token: c.token
|
|
174
|
-
});
|
|
222
|
+
const r = await apiAuth("/platform/instances", { method: "POST" });
|
|
175
223
|
saveInstance({ name: r.name, url: r.url, adminToken: r.admin_token, serverUrl: url });
|
|
176
224
|
console.log(colors.green(`created ${colors.bold(r.name)}`));
|
|
177
225
|
console.log(` url: ${r.url}`);
|
|
178
226
|
console.log(` admin token saved to local store (only shown once)`);
|
|
179
227
|
}));
|
|
180
228
|
cli.command("list", "list your instances").action(wrap(async (opts) => {
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
const r = await api(url, "/platform/instances", { token: c.token });
|
|
229
|
+
const url = opts.server ? serverUrl(opts) : void 0;
|
|
230
|
+
const r = await apiAuth("/platform/instances", { server: url });
|
|
184
231
|
if (!r.result.length) {
|
|
185
232
|
console.log("no instances. run `teeny create`");
|
|
186
233
|
return;
|
|
@@ -191,16 +238,14 @@ cli.command("list", "list your instances").action(wrap(async (opts) => {
|
|
|
191
238
|
}
|
|
192
239
|
}));
|
|
193
240
|
cli.command("status <name>", "show instance details").action(wrap(async (name, opts) => {
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const r = await api(url, `/platform/instances/${encodeURIComponent(name)}`, { token: c.token });
|
|
241
|
+
const url = opts.server ? serverUrl(opts) : void 0;
|
|
242
|
+
const r = await apiAuth(`/platform/instances/${encodeURIComponent(name)}`, { server: url });
|
|
197
243
|
console.log(colors.bold(r.result.name));
|
|
198
244
|
console.log(` url: ${r.result.url}`);
|
|
199
245
|
console.log(` created: ${r.result.created_at}`);
|
|
200
246
|
}));
|
|
201
247
|
cli.command("delete <name>", "tear down an instance").option("-y, --yes", "[boolean] skip confirmation").action(wrap(async (name, opts) => {
|
|
202
|
-
const
|
|
203
|
-
const url = opts.server ? serverUrl(opts) : c.serverUrl;
|
|
248
|
+
const url = opts.server ? serverUrl(opts) : void 0;
|
|
204
249
|
if (!opts.yes) {
|
|
205
250
|
const r = await prompts({ type: "confirm", name: "v", message: `delete ${name}? this is permanent`, initial: false });
|
|
206
251
|
if (!r.v) {
|
|
@@ -208,7 +253,7 @@ cli.command("delete <name>", "tear down an instance").option("-y, --yes", "[bool
|
|
|
208
253
|
return;
|
|
209
254
|
}
|
|
210
255
|
}
|
|
211
|
-
await
|
|
256
|
+
await apiAuth(`/platform/instances/${encodeURIComponent(name)}`, { method: "DELETE", server: url });
|
|
212
257
|
deleteInstance(name);
|
|
213
258
|
console.log(colors.green(`deleted ${name}`));
|
|
214
259
|
}));
|
|
@@ -245,9 +290,22 @@ cli.command("logs <name>", "stream live logs from an instance").option("--tail <
|
|
|
245
290
|
const c = requireAuth();
|
|
246
291
|
const url = opts.server ? serverUrl(opts) : c.serverUrl;
|
|
247
292
|
const tail = opts.tail || "200";
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
293
|
+
const streamUrl = `${url}/platform/instances/${encodeURIComponent(name)}/logs?tail=${tail}`;
|
|
294
|
+
const open = (token) => fetch(streamUrl, { headers: { authorization: `Bearer ${token}` } });
|
|
295
|
+
let res = await open(c.token);
|
|
296
|
+
if (res.status === 401 && c.refreshToken) {
|
|
297
|
+
try {
|
|
298
|
+
const refreshed = await api(url, "/auth/refresh-token", {
|
|
299
|
+
method: "POST",
|
|
300
|
+
body: JSON.stringify({ refresh_token: c.refreshToken })
|
|
301
|
+
});
|
|
302
|
+
if (url === c.serverUrl) {
|
|
303
|
+
saveCredentials({ ...c, token: refreshed.token, refreshToken: refreshed.refresh_token });
|
|
304
|
+
}
|
|
305
|
+
res = await open(refreshed.token);
|
|
306
|
+
} catch {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
251
309
|
if (!res.ok || !res.body) {
|
|
252
310
|
console.error(colors.red(`failed to stream: ${res.status}`));
|
|
253
311
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,49 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
2
|
+
"name": "@uetuluk/create-cli",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "CLI for the create.ritsdev.top platform — register, provision, and deploy teenybase instances.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"teeny": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"teenybase",
|
|
21
|
+
"platform",
|
|
22
|
+
"cli",
|
|
23
|
+
"ritsdev"
|
|
24
|
+
],
|
|
25
|
+
"author": "uetuluk",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/uetuluk/create-ritsdev-platform.git",
|
|
30
|
+
"directory": "cli"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/uetuluk/create-ritsdev-platform/tree/main/cli#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/uetuluk/create-ritsdev-platform/issues"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "esbuild src/cli.ts --bundle --outfile=dist/cli.js --platform=node --format=esm --packages=external --banner:js=\"#!/usr/bin/env node\" && chmod +x dist/cli.js",
|
|
38
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
39
|
+
"dev": "tsx src/cli.ts",
|
|
40
|
+
"prepublishOnly": "npm run check && npm run build"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"cac": "^6.7.14",
|
|
44
|
+
"picocolors": "^1.1.1",
|
|
45
|
+
"prompts": "^2.4.2"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^22.0.0",
|
|
49
|
+
"@types/prompts": "^2.4.9",
|
|
50
|
+
"esbuild": "^0.24.0",
|
|
51
|
+
"tsx": "^4.19.0",
|
|
52
|
+
"typescript": "^5.6.0"
|
|
53
|
+
}
|
|
49
54
|
}
|