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