@suronai/cli 0.1.39 → 0.1.41
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/package.json +1 -1
- package/src/commands.js +351 -64
- package/src/index.js +2 -1
- package/src/utils/api.js +4 -2
package/package.json
CHANGED
package/src/commands.js
CHANGED
|
@@ -1,19 +1,235 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
2
|
+
import { spinner, select, text, isCancel } from "@clack/prompts";
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
import {
|
|
4
|
+
import { join, basename, relative } from "path";
|
|
5
|
+
import { spawn, execSync } from "child_process";
|
|
6
6
|
import ui from "./utils/colors.js";
|
|
7
7
|
import { getToken, requireToken, saveConfig, clearToken } from "./utils/config.js";
|
|
8
8
|
import { api, BASE_URL } from "./utils/api.js";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// ── Shared helpers ────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
function cancel(msg, hint) {
|
|
11
13
|
console.log(ui.blank());
|
|
12
|
-
console.log(ui.errBlock(msg));
|
|
14
|
+
console.log(ui.errBlock(msg, hint));
|
|
13
15
|
console.log(ui.blank());
|
|
14
16
|
process.exit(1);
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
function sanitiseName(name) {
|
|
20
|
+
return name.replace(/[^a-zA-Z0-9]/g, "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function openBrowser(url) {
|
|
24
|
+
if (process.platform === "win32") {
|
|
25
|
+
spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
|
|
26
|
+
} else if (process.platform === "darwin") {
|
|
27
|
+
spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
28
|
+
} else {
|
|
29
|
+
spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function waitForEnter(prompt) {
|
|
34
|
+
process.stdout.write(prompt);
|
|
35
|
+
return new Promise(resolve => {
|
|
36
|
+
const handler = (buf) => {
|
|
37
|
+
if (buf.toString().includes("\n")) {
|
|
38
|
+
process.stdin.removeListener("data", handler);
|
|
39
|
+
process.stdin.pause();
|
|
40
|
+
resolve();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
process.stdin.resume();
|
|
44
|
+
process.stdin.setEncoding("utf-8");
|
|
45
|
+
process.stdin.on("data", handler);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function detectPackageManager(cwd) {
|
|
50
|
+
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
51
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
52
|
+
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
53
|
+
return "npm";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function pmAddCmd(pm) {
|
|
57
|
+
return pm === "npm" ? "install" : "add";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── .gitignore ────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
const GITIGNORE_ENTRIES = [".env", ".suron.json"];
|
|
63
|
+
|
|
64
|
+
function ensureGitignore(cwd) {
|
|
65
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
66
|
+
if (!existsSync(gitignorePath)) {
|
|
67
|
+
writeFileSync(gitignorePath, GITIGNORE_ENTRIES.join("\n") + "\n", "utf-8");
|
|
68
|
+
return "created";
|
|
69
|
+
}
|
|
70
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
71
|
+
const existing = new Set(content.split("\n").map(l => l.trim()));
|
|
72
|
+
const missing = GITIGNORE_ENTRIES.filter(e => !existing.has(e));
|
|
73
|
+
if (missing.length === 0) return "ok";
|
|
74
|
+
const sep = content.endsWith("\n") ? "" : "\n";
|
|
75
|
+
writeFileSync(gitignorePath, content + sep + missing.join("\n") + "\n", "utf-8");
|
|
76
|
+
return `added ${missing.join(", ")}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── SDK install ───────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
function installSdk(cwd) {
|
|
82
|
+
const pkgJsonPath = join(cwd, "package.json");
|
|
83
|
+
if (!existsSync(pkgJsonPath)) return { status: "skip", note: "no package.json" };
|
|
84
|
+
|
|
85
|
+
let alreadyInstalled = false;
|
|
86
|
+
let isEsm = false;
|
|
87
|
+
try {
|
|
88
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
89
|
+
alreadyInstalled = !!(pkg.dependencies?.["@suronai/sdk"] || pkg.devDependencies?.["@suronai/sdk"]);
|
|
90
|
+
isEsm = pkg.type === "module";
|
|
91
|
+
} catch {
|
|
92
|
+
return { status: "skip", note: "could not read package.json" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (alreadyInstalled) return { status: "skip", note: "already installed", isEsm };
|
|
96
|
+
|
|
97
|
+
const pm = detectPackageManager(cwd);
|
|
98
|
+
try {
|
|
99
|
+
execSync(`${pm} ${pmAddCmd(pm)} @suronai/sdk`, { cwd, stdio: "pipe" });
|
|
100
|
+
return { status: "done", isEsm };
|
|
101
|
+
} catch {
|
|
102
|
+
return { status: "fail", note: `run: npm install @suronai/sdk`, isEsm };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Entry-point patching ──────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
async function patchEntryPoint(cwd, isEsm) {
|
|
109
|
+
const candidates = isEsm
|
|
110
|
+
? ["index.js", "index.mjs", "src/index.js", "src/index.mjs"]
|
|
111
|
+
: ["index.js", "index.cjs", "src/index.js", "src/index.cjs"];
|
|
112
|
+
|
|
113
|
+
let entryPath = null;
|
|
114
|
+
for (const rel of candidates) {
|
|
115
|
+
const abs = join(cwd, rel);
|
|
116
|
+
if (existsSync(abs)) { entryPath = abs; break; }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!entryPath) { printSnippet(isEsm); return; }
|
|
120
|
+
|
|
121
|
+
let src;
|
|
122
|
+
try { src = readFileSync(entryPath, "utf-8"); } catch { return; }
|
|
123
|
+
|
|
124
|
+
const lines = src.split("\n");
|
|
125
|
+
const toReplace = [];
|
|
126
|
+
const seenIndices = new Set();
|
|
127
|
+
|
|
128
|
+
for (let i = 0; i < lines.length; i++) {
|
|
129
|
+
const trimmed = lines[i].trim();
|
|
130
|
+
const indent = lines[i].match(/^(\s*)/)[1];
|
|
131
|
+
|
|
132
|
+
if (lines[i].includes("dotenv")) {
|
|
133
|
+
seenIndices.add(i);
|
|
134
|
+
let replacement = null;
|
|
135
|
+
if (isEsm) {
|
|
136
|
+
if (trimmed.startsWith("import")) {
|
|
137
|
+
replacement = indent + trimmed.replace(/(from\s+)['"]dotenv(?:\/config)?['"]/, '$1"@suronai/sdk"');
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
if (trimmed.includes("require")) {
|
|
141
|
+
replacement = indent + 'const { config } = require("@suronai/sdk");\n' + indent + "await config();";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
toReplace.push({ index: i, content: lines[i], replacement });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (isEsm) {
|
|
149
|
+
for (let i = 0; i < lines.length; i++) {
|
|
150
|
+
if (seenIndices.has(i)) continue;
|
|
151
|
+
const trimmed = lines[i].trim();
|
|
152
|
+
const indent = lines[i].match(/^(\s*)/)[1];
|
|
153
|
+
if (/^config\s*\(.*\);?$/.test(trimmed) && !trimmed.startsWith("//")) {
|
|
154
|
+
toReplace.push({
|
|
155
|
+
index: i,
|
|
156
|
+
content: lines[i],
|
|
157
|
+
replacement: indent + trimmed.replace(/^config\s*\(/, "await config("),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
toReplace.sort((a, b) => a.index - b.index);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (toReplace.length === 0) { printSnippet(isEsm); return; }
|
|
165
|
+
|
|
166
|
+
const relEntry = relative(cwd, entryPath);
|
|
167
|
+
|
|
168
|
+
console.log(ui.kv("ENTRY", relEntry, 8));
|
|
169
|
+
console.log(ui.blank());
|
|
170
|
+
|
|
171
|
+
for (const { content, replacement } of toReplace) {
|
|
172
|
+
console.log(ui.diff("remove", content.trim()));
|
|
173
|
+
if (replacement !== null) {
|
|
174
|
+
for (const l of replacement.split("\n")) console.log(ui.diff("add", l.trim()));
|
|
175
|
+
} else {
|
|
176
|
+
console.log(ui.diff("unknown", ""));
|
|
177
|
+
}
|
|
178
|
+
console.log(ui.blank());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await waitForEnter(ui.promptLine("apply patch? [Y/n]"));
|
|
182
|
+
const answer = "";
|
|
183
|
+
const confirmed = answer === "" || /^y(es)?$/i.test(answer);
|
|
184
|
+
console.log(ui.blank());
|
|
185
|
+
console.log(ui.hr());
|
|
186
|
+
console.log(ui.blank());
|
|
187
|
+
|
|
188
|
+
if (!confirmed) { printSnippet(isEsm); return; }
|
|
189
|
+
|
|
190
|
+
const indexMap = new Map(toReplace.map(r => [r.index, r]));
|
|
191
|
+
const outLines = lines.map((line, i) => {
|
|
192
|
+
const r = indexMap.get(i);
|
|
193
|
+
return (r && r.replacement !== null) ? r.replacement : line;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (isEsm) {
|
|
197
|
+
const callLineIndex = outLines.findIndex(l => /^\s*await config\s*\(/.test(l));
|
|
198
|
+
const lastImportIndex = outLines.reduce((last, l, i) => l.trimStart().startsWith("import ") ? i : last, -1);
|
|
199
|
+
if (callLineIndex !== -1 && callLineIndex > lastImportIndex + 2) {
|
|
200
|
+
const callLine = outLines[callLineIndex];
|
|
201
|
+
outLines.splice(callLineIndex, 1);
|
|
202
|
+
if (outLines[callLineIndex]?.trim() === "") outLines.splice(callLineIndex, 1);
|
|
203
|
+
const newLastImport = outLines.reduce((last, l, i) => l.trimStart().startsWith("import ") ? i : last, -1);
|
|
204
|
+
outLines.splice(newLastImport + 1, 0, "", callLine, "");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
writeFileSync(entryPath, outLines.join("\n"), "utf-8");
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error(ui.errBlock("could not write " + relEntry, err.message));
|
|
212
|
+
printSnippet(isEsm);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function printSnippet(isEsm) {
|
|
217
|
+
console.log(ui.infoBlock("add to your entry point:"));
|
|
218
|
+
console.log(ui.blank());
|
|
219
|
+
if (isEsm) {
|
|
220
|
+
console.log(ui.INDENT + " " + ui.amber('import { config } from "@suronai/sdk"'));
|
|
221
|
+
console.log(ui.INDENT + " " + ui.amber("await config()"));
|
|
222
|
+
} else {
|
|
223
|
+
console.log(ui.INDENT + " " + ui.amber('const { config } = require("@suronai/sdk")'));
|
|
224
|
+
console.log(ui.INDENT + " " + ui.amber("await config()"));
|
|
225
|
+
}
|
|
226
|
+
console.log(ui.blank());
|
|
227
|
+
console.log(ui.hr());
|
|
228
|
+
console.log(ui.blank());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
232
|
+
|
|
17
233
|
export const loginCommand = new Command("login")
|
|
18
234
|
.description("Authenticate via browser")
|
|
19
235
|
.option("--force", "Re-authenticate even if already logged in")
|
|
@@ -42,12 +258,14 @@ export const loginCommand = new Command("login")
|
|
|
42
258
|
|
|
43
259
|
const loginUrl = `${BASE_URL}/clilogin?code=${code}`;
|
|
44
260
|
|
|
45
|
-
console.log(ui.infoBlock("
|
|
261
|
+
console.log(ui.infoBlock("press Enter to open login in your browser"));
|
|
46
262
|
console.log(ui.blank());
|
|
47
263
|
console.log(ui.kv("URL", loginUrl, 6));
|
|
48
264
|
console.log(ui.blank());
|
|
49
265
|
|
|
50
|
-
|
|
266
|
+
await waitForEnter(ui.promptLine("open browser"));
|
|
267
|
+
openBrowser(loginUrl);
|
|
268
|
+
console.log(ui.blank());
|
|
51
269
|
|
|
52
270
|
const s = spinner();
|
|
53
271
|
s.start("Waiting for approval");
|
|
@@ -90,9 +308,7 @@ export const logoutCommand = new Command("logout")
|
|
|
90
308
|
.description("Clear local session token")
|
|
91
309
|
.action(async () => {
|
|
92
310
|
clearToken();
|
|
93
|
-
try {
|
|
94
|
-
await api("/auth/logout", { method: "POST" });
|
|
95
|
-
} catch {}
|
|
311
|
+
try { await api("/auth/logout", { method: "POST" }); } catch {}
|
|
96
312
|
console.log(ui.blank());
|
|
97
313
|
console.log(ui.okBlock("logged out"));
|
|
98
314
|
console.log(ui.blank());
|
|
@@ -100,19 +316,21 @@ export const logoutCommand = new Command("logout")
|
|
|
100
316
|
|
|
101
317
|
export const initCommand = new Command("init")
|
|
102
318
|
.description("Initialize Suron in this project")
|
|
103
|
-
.
|
|
104
|
-
|
|
319
|
+
.option("--name <n>", "App name (skips interactive prompt)")
|
|
320
|
+
.action(async (opts) => {
|
|
321
|
+
requireToken();
|
|
322
|
+
const cwd = process.cwd();
|
|
105
323
|
|
|
106
|
-
if (existsSync(".suron.json")) {
|
|
324
|
+
if (existsSync(join(cwd, ".suron.json"))) {
|
|
107
325
|
console.log(ui.blank());
|
|
108
326
|
console.log(ui.errBlock(".suron.json already exists", "use suron up to push a new version"));
|
|
109
327
|
console.log(ui.blank());
|
|
110
328
|
process.exit(1);
|
|
111
329
|
}
|
|
112
330
|
|
|
113
|
-
if (!existsSync(".env")) {
|
|
331
|
+
if (!existsSync(join(cwd, ".env"))) {
|
|
114
332
|
console.log(ui.blank());
|
|
115
|
-
console.log(ui.errBlock(".env not found", "create a .env file first"));
|
|
333
|
+
console.log(ui.errBlock(".env not found", "create a .env file with your secrets first"));
|
|
116
334
|
console.log(ui.blank());
|
|
117
335
|
process.exit(1);
|
|
118
336
|
}
|
|
@@ -122,40 +340,49 @@ export const initCommand = new Command("init")
|
|
|
122
340
|
console.log(ui.hr());
|
|
123
341
|
console.log(ui.blank());
|
|
124
342
|
|
|
343
|
+
// ── App selection ─────────────────────────────────────────────────────────
|
|
344
|
+
|
|
125
345
|
const mode = await select({
|
|
126
346
|
message: "How do you want to link this project?",
|
|
127
347
|
options: [
|
|
128
|
-
{ value: "new",
|
|
348
|
+
{ value: "new", label: "Create new app" },
|
|
129
349
|
{ value: "existing", label: "Link to existing app" },
|
|
130
350
|
],
|
|
131
351
|
});
|
|
132
|
-
|
|
133
352
|
if (isCancel(mode)) cancel("cancelled");
|
|
134
353
|
|
|
135
354
|
let app_id, app_name;
|
|
136
355
|
|
|
137
356
|
if (mode === "new") {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
357
|
+
const suggested = sanitiseName(basename(cwd) || "myapp");
|
|
358
|
+
let rawName;
|
|
359
|
+
|
|
360
|
+
if (opts.name) {
|
|
361
|
+
rawName = opts.name;
|
|
362
|
+
} else {
|
|
363
|
+
rawName = await text({
|
|
364
|
+
message: `App name [${suggested}]`,
|
|
365
|
+
placeholder: suggested,
|
|
366
|
+
validate: v => {
|
|
367
|
+
const s = sanitiseName(v || suggested);
|
|
368
|
+
return s.length ? undefined : "Alphanumeric only";
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
if (isCancel(rawName)) cancel("cancelled");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const name = sanitiseName(rawName || suggested);
|
|
143
375
|
|
|
144
376
|
const result = await api("/apps", {
|
|
145
377
|
method: "POST",
|
|
146
378
|
body: JSON.stringify({ name }),
|
|
147
379
|
}).catch(err => cancel(err.message));
|
|
148
380
|
|
|
149
|
-
app_id
|
|
381
|
+
app_id = result.app_id;
|
|
150
382
|
app_name = result.name;
|
|
151
383
|
} else {
|
|
152
384
|
const apps = await api("/apps").catch(err => cancel(err.message));
|
|
153
|
-
|
|
154
|
-
if (!apps.length) {
|
|
155
|
-
console.log(ui.blank());
|
|
156
|
-
console.log(ui.errBlock("no apps found", "use 'Create new app' instead"));
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
385
|
+
if (!apps.length) cancel("no apps found", "use 'Create new app' instead");
|
|
159
386
|
|
|
160
387
|
const chosen = await select({
|
|
161
388
|
message: "Select an app",
|
|
@@ -164,59 +391,119 @@ export const initCommand = new Command("init")
|
|
|
164
391
|
if (isCancel(chosen)) cancel("cancelled");
|
|
165
392
|
|
|
166
393
|
const found = apps.find(a => a.app_id === chosen);
|
|
167
|
-
app_id
|
|
394
|
+
app_id = found.app_id;
|
|
168
395
|
app_name = found.name;
|
|
169
396
|
}
|
|
170
397
|
|
|
171
|
-
|
|
398
|
+
console.log(ui.blank());
|
|
399
|
+
console.log(ui.kv("APP", app_name));
|
|
400
|
+
console.log(ui.kv("DIR", ui.slate(cwd)));
|
|
401
|
+
console.log(ui.blank());
|
|
402
|
+
console.log(ui.hr());
|
|
403
|
+
console.log(ui.blank());
|
|
404
|
+
|
|
405
|
+
// ── Step tracker ──────────────────────────────────────────────────────────
|
|
172
406
|
|
|
173
|
-
const
|
|
174
|
-
|
|
407
|
+
const steps = {
|
|
408
|
+
upload: { label: "upload .env", status: "pending" },
|
|
409
|
+
gitignore: { label: "gitignore .gitignore", status: "pending" },
|
|
410
|
+
sdk: { label: "sdk @suronai/sdk", status: "pending" },
|
|
411
|
+
patch: { label: "patch entry point", status: "pending" },
|
|
412
|
+
};
|
|
175
413
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
414
|
+
const printSteps = () => {
|
|
415
|
+
for (const s of Object.values(steps)) {
|
|
416
|
+
console.log(ui.step(s.status, s.label, s.note));
|
|
417
|
+
}
|
|
418
|
+
};
|
|
180
419
|
|
|
181
|
-
|
|
420
|
+
// ── Upload ────────────────────────────────────────────────────────────────
|
|
182
421
|
|
|
183
|
-
|
|
184
|
-
|
|
422
|
+
steps.upload.status = "run";
|
|
423
|
+
const env_plaintext = readFileSync(join(cwd, ".env"), "utf-8");
|
|
185
424
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
425
|
+
let uploadResult;
|
|
426
|
+
try {
|
|
427
|
+
uploadResult = await api(`/apps/${app_id}/versions`, {
|
|
428
|
+
method: "POST",
|
|
429
|
+
body: JSON.stringify({ env_plaintext }),
|
|
430
|
+
});
|
|
431
|
+
steps.upload.status = "done";
|
|
432
|
+
steps.upload.note = `v${uploadResult.version}`;
|
|
433
|
+
} catch (err) {
|
|
434
|
+
steps.upload.status = "fail";
|
|
435
|
+
printSteps();
|
|
436
|
+
console.log(ui.blank());
|
|
437
|
+
cancel(err.message);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── .suron.json ───────────────────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
writeFileSync(
|
|
443
|
+
join(cwd, ".suron.json"),
|
|
444
|
+
JSON.stringify({ app_name, app_id, version: uploadResult.version }, null, 2) + "\n",
|
|
445
|
+
"utf-8"
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// ── .gitignore ────────────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
steps.gitignore.status = "run";
|
|
451
|
+
const gitResult = ensureGitignore(cwd);
|
|
452
|
+
steps.gitignore.status = "done";
|
|
453
|
+
steps.gitignore.note = gitResult;
|
|
454
|
+
|
|
455
|
+
// ── SDK install ───────────────────────────────────────────────────────────
|
|
456
|
+
|
|
457
|
+
steps.sdk.status = "run";
|
|
458
|
+
const sdkResult = installSdk(cwd);
|
|
459
|
+
steps.sdk.status = sdkResult.status;
|
|
460
|
+
steps.sdk.note = sdkResult.note;
|
|
461
|
+
|
|
462
|
+
// ── Print steps before patch (patch is interactive) ───────────────────────
|
|
463
|
+
|
|
464
|
+
steps.patch.status = "run";
|
|
465
|
+
printSteps();
|
|
190
466
|
console.log(ui.blank());
|
|
191
467
|
console.log(ui.hr());
|
|
192
468
|
console.log(ui.blank());
|
|
193
|
-
|
|
469
|
+
|
|
470
|
+
// ── Entry-point patch ─────────────────────────────────────────────────────
|
|
471
|
+
|
|
472
|
+
await patchEntryPoint(cwd, sdkResult.isEsm ?? false);
|
|
473
|
+
steps.patch.status = "done";
|
|
474
|
+
|
|
475
|
+
// ── Ready ─────────────────────────────────────────────────────────────────
|
|
476
|
+
|
|
477
|
+
console.log(ui.readyBlock([
|
|
478
|
+
{ label: ".env", note: "uploaded · keep out of git" },
|
|
479
|
+
{ label: ".suron.json", note: "safe to commit" },
|
|
480
|
+
{ label: ".gitignore", note: gitResult },
|
|
481
|
+
]));
|
|
194
482
|
console.log(ui.blank());
|
|
195
483
|
});
|
|
196
484
|
|
|
197
|
-
export const
|
|
198
|
-
.description("
|
|
485
|
+
export const whoamiCommand = new Command("whoami")
|
|
486
|
+
.description("Show the current logged-in user")
|
|
199
487
|
.action(async () => {
|
|
200
488
|
requireToken();
|
|
489
|
+
const user = await api("/auth/me").catch(err => cancel(err.message));
|
|
490
|
+
console.log(ui.blank());
|
|
491
|
+
console.log(ui.kv("EMAIL", user.email));
|
|
492
|
+
console.log(ui.kv("ROLE", user.role));
|
|
493
|
+
console.log(ui.blank());
|
|
494
|
+
});
|
|
201
495
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (!existsSync(".env")) {
|
|
210
|
-
console.log(ui.blank());
|
|
211
|
-
console.log(ui.errBlock(".env not found"));
|
|
212
|
-
console.log(ui.blank());
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
496
|
+
export const upCommand = new Command("up")
|
|
497
|
+
.action(async () => {
|
|
498
|
+
requireToken();
|
|
499
|
+
const cwd = process.cwd();
|
|
215
500
|
|
|
216
|
-
|
|
217
|
-
|
|
501
|
+
if (!existsSync(join(cwd, ".suron.json"))) cancel(".suron.json not found", "run: suron init");
|
|
502
|
+
if (!existsSync(join(cwd, ".env"))) cancel(".env not found");
|
|
218
503
|
|
|
219
|
-
const
|
|
504
|
+
const suronJson = JSON.parse(readFileSync(join(cwd, ".suron.json"), "utf-8"));
|
|
505
|
+
const { app_id } = suronJson;
|
|
506
|
+
const env_plaintext = readFileSync(join(cwd, ".env"), "utf-8");
|
|
220
507
|
|
|
221
508
|
const s = spinner();
|
|
222
509
|
s.start("Pushing new version");
|
|
@@ -229,7 +516,7 @@ export const upCommand = new Command("up")
|
|
|
229
516
|
s.stop("Done");
|
|
230
517
|
|
|
231
518
|
suronJson.version = result.version;
|
|
232
|
-
writeFileSync(".suron.json", JSON.stringify(suronJson, null, 2) + "\n");
|
|
519
|
+
writeFileSync(join(cwd, ".suron.json"), JSON.stringify(suronJson, null, 2) + "\n");
|
|
233
520
|
|
|
234
521
|
console.log(ui.blank());
|
|
235
522
|
console.log(ui.step("done", `version ${result.version} pushed`));
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { loginCommand, logoutCommand, initCommand, upCommand } from "./commands.js";
|
|
5
|
+
import { loginCommand, logoutCommand, initCommand, upCommand, whoamiCommand } from "./commands.js";
|
|
6
6
|
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
8
|
const { version } = require("../package.json");
|
|
@@ -16,6 +16,7 @@ program
|
|
|
16
16
|
|
|
17
17
|
program.addCommand(loginCommand);
|
|
18
18
|
program.addCommand(logoutCommand);
|
|
19
|
+
program.addCommand(whoamiCommand);
|
|
19
20
|
program.addCommand(initCommand);
|
|
20
21
|
program.addCommand(upCommand);
|
|
21
22
|
|
package/src/utils/api.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { getToken } from "./config.js";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const WEB_URL = process.env.SURON_WEB_URL ?? "https://suron.vercel.app";
|
|
4
|
+
const API_URL = `${WEB_URL}/api`;
|
|
5
|
+
const BASE_URL = WEB_URL;
|
|
4
6
|
|
|
5
7
|
export async function api(path, options = {}) {
|
|
6
8
|
const token = getToken();
|
|
7
9
|
const headers = { "Content-Type": "application/json", ...options.headers };
|
|
8
10
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
9
11
|
|
|
10
|
-
const res = await fetch(`${
|
|
12
|
+
const res = await fetch(`${API_URL}${path}`, { ...options, headers });
|
|
11
13
|
|
|
12
14
|
if (!res.ok) {
|
|
13
15
|
const data = await res.json().catch(() => ({}));
|