@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.
Files changed (2) hide show
  1. package/dist/cli.js +76 -18
  2. 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 = body && typeof body === "object" && (body.error || body.message) || `HTTP ${r.status}`;
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
- console.error(colors.red(e.message));
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 api(url, "/platform/instances", {
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 c = requireAuth();
182
- const url = opts.server ? serverUrl(opts) : c.serverUrl;
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 c = requireAuth();
195
- const url = opts.server ? serverUrl(opts) : c.serverUrl;
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 c = requireAuth();
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 api(url, `/platform/instances/${encodeURIComponent(name)}`, { method: "DELETE", token: c.token });
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 res = await fetch(`${url}/platform/instances/${encodeURIComponent(name)}/logs?tail=${tail}`, {
249
- headers: { authorization: `Bearer ${c.token}` }
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
- "name": "@uetuluk/create-cli",
3
- "version": "0.0.1",
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": ["teenybase", "platform", "cli", "ritsdev"],
20
- "author": "uetuluk",
21
- "license": "MIT",
22
- "repository": {
23
- "type": "git",
24
- "url": "git+https://github.com/uetuluk/create-ritsdev-platform.git",
25
- "directory": "cli"
26
- },
27
- "homepage": "https://github.com/uetuluk/create-ritsdev-platform/tree/main/cli#readme",
28
- "bugs": {
29
- "url": "https://github.com/uetuluk/create-ritsdev-platform/issues"
30
- },
31
- "scripts": {
32
- "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",
33
- "check": "tsc -p tsconfig.json --noEmit",
34
- "dev": "tsx src/cli.ts",
35
- "prepublishOnly": "npm run check && npm run build"
36
- },
37
- "dependencies": {
38
- "cac": "^6.7.14",
39
- "picocolors": "^1.1.1",
40
- "prompts": "^2.4.2"
41
- },
42
- "devDependencies": {
43
- "@types/node": "^22.0.0",
44
- "@types/prompts": "^2.4.9",
45
- "esbuild": "^0.24.0",
46
- "tsx": "^4.19.0",
47
- "typescript": "^5.6.0"
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
  }