@polterware/polterbase 0.2.4 → 0.2.6
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/README.md +101 -12
- package/dist/index.js +1288 -66
- package/package.json +2 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
-
import
|
|
4
|
+
import React8 from "react";
|
|
5
5
|
import { render } from "ink";
|
|
6
6
|
|
|
7
7
|
// src/app.tsx
|
|
8
|
-
import { Box as
|
|
8
|
+
import { Box as Box14, Text as Text13, useApp } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/hooks/useNavigation.ts
|
|
11
11
|
import { useState, useCallback } from "react";
|
|
@@ -43,7 +43,7 @@ var SUPABASE_FG = "\x1B[38;2;62;207;142m";
|
|
|
43
43
|
var SUPABASE_BG = "\x1B[48;2;62;207;142m";
|
|
44
44
|
var RESET_FG = "\x1B[39m";
|
|
45
45
|
var RESET_BG = "\x1B[49m";
|
|
46
|
-
var wrapAnsi = (open, close) => (
|
|
46
|
+
var wrapAnsi = (open, close) => (input2) => pc.isColorSupported ? `${open}${input2}${close}` : input2;
|
|
47
47
|
var supabase = wrapAnsi(SUPABASE_FG, RESET_FG);
|
|
48
48
|
var supabaseBg = wrapAnsi(SUPABASE_BG, RESET_BG);
|
|
49
49
|
var VERSION = packageJson.version;
|
|
@@ -229,21 +229,21 @@ function SelectList({
|
|
|
229
229
|
}, [selectableIndexes]);
|
|
230
230
|
const selectedItemIndex = selectableIndexes[selectedSelectableIndex] ?? -1;
|
|
231
231
|
const selectedItem = selectedItemIndex >= 0 ? items[selectedItemIndex] : void 0;
|
|
232
|
-
useInput((
|
|
232
|
+
useInput((input2, key) => {
|
|
233
233
|
if (selectableIndexes.length === 0) {
|
|
234
234
|
if (key.escape && onCancel) {
|
|
235
235
|
onCancel();
|
|
236
236
|
}
|
|
237
237
|
return;
|
|
238
238
|
}
|
|
239
|
-
if (key.upArrow ||
|
|
239
|
+
if (key.upArrow || input2 === "k") {
|
|
240
240
|
setSelectedSelectableIndex((prev) => {
|
|
241
241
|
let next = prev - 1;
|
|
242
242
|
if (next < 0) next = selectableIndexes.length - 1;
|
|
243
243
|
return next;
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
|
-
if (key.downArrow ||
|
|
246
|
+
if (key.downArrow || input2 === "j") {
|
|
247
247
|
setSelectedSelectableIndex((prev) => {
|
|
248
248
|
let next = prev + 1;
|
|
249
249
|
if (next >= selectableIndexes.length) next = 0;
|
|
@@ -446,17 +446,17 @@ function getPinnedRuns() {
|
|
|
446
446
|
function setPinnedRuns(runs) {
|
|
447
447
|
config.set(RUN_PINS_KEY, runs);
|
|
448
448
|
}
|
|
449
|
-
function togglePinnedRun(
|
|
449
|
+
function togglePinnedRun(runCommand2) {
|
|
450
450
|
ensurePinsInitialized();
|
|
451
451
|
const current = getPinnedRuns();
|
|
452
|
-
if (current.includes(
|
|
453
|
-
setPinnedRuns(current.filter((run) => run !==
|
|
452
|
+
if (current.includes(runCommand2)) {
|
|
453
|
+
setPinnedRuns(current.filter((run) => run !== runCommand2));
|
|
454
454
|
return;
|
|
455
455
|
}
|
|
456
|
-
setPinnedRuns([
|
|
456
|
+
setPinnedRuns([runCommand2, ...current.filter((run) => run !== runCommand2)]);
|
|
457
457
|
}
|
|
458
|
-
function isPinnedRun(
|
|
459
|
-
return getPinnedRuns().includes(
|
|
458
|
+
function isPinnedRun(runCommand2) {
|
|
459
|
+
return getPinnedRuns().includes(runCommand2);
|
|
460
460
|
}
|
|
461
461
|
|
|
462
462
|
// src/data/commands.ts
|
|
@@ -628,14 +628,14 @@ function buildMainMenuItems({
|
|
|
628
628
|
kind: "header",
|
|
629
629
|
selectable: false
|
|
630
630
|
});
|
|
631
|
-
for (const
|
|
632
|
-
const baseCommand =
|
|
631
|
+
for (const runCommand2 of pinnedRuns) {
|
|
632
|
+
const baseCommand = runCommand2.split(" ").filter(Boolean)[0] ?? "";
|
|
633
633
|
const info = getCommandInfo(baseCommand);
|
|
634
634
|
const infoHint = info ? `${info.categoryIcon} ${info.categoryLabel} \xB7 exact pinned run` : "Exact pinned run";
|
|
635
635
|
nextItems.push({
|
|
636
|
-
id: `run:${
|
|
637
|
-
value:
|
|
638
|
-
label:
|
|
636
|
+
id: `run:${runCommand2}`,
|
|
637
|
+
value: runCommand2,
|
|
638
|
+
label: runCommand2,
|
|
639
639
|
hint: infoHint,
|
|
640
640
|
icon: "\u25B6",
|
|
641
641
|
kind: "run",
|
|
@@ -711,6 +711,14 @@ function buildMainMenuItems({
|
|
|
711
711
|
icon: "\u270F\uFE0F",
|
|
712
712
|
kind: "action"
|
|
713
713
|
});
|
|
714
|
+
nextItems.push({
|
|
715
|
+
id: "action-update",
|
|
716
|
+
value: "__action_update__",
|
|
717
|
+
label: "Update Polterbase",
|
|
718
|
+
hint: "Update the current repo install or the global install",
|
|
719
|
+
icon: "\u2B06\uFE0F",
|
|
720
|
+
kind: "action"
|
|
721
|
+
});
|
|
714
722
|
nextItems.push({
|
|
715
723
|
id: "action-exit",
|
|
716
724
|
value: "__action_exit__",
|
|
@@ -772,6 +780,10 @@ function MainMenu({
|
|
|
772
780
|
onNavigate("custom-command");
|
|
773
781
|
return;
|
|
774
782
|
}
|
|
783
|
+
if (value === "__action_update__") {
|
|
784
|
+
onNavigate("self-update");
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
775
787
|
if (value === "__action_exit__") {
|
|
776
788
|
onExit();
|
|
777
789
|
}
|
|
@@ -788,12 +800,12 @@ function MainMenu({
|
|
|
788
800
|
return;
|
|
789
801
|
}
|
|
790
802
|
if (item.kind === "run") {
|
|
791
|
-
const
|
|
792
|
-
const wasPinned = pinnedRunSet.has(
|
|
793
|
-
togglePinnedRun(
|
|
803
|
+
const runCommand2 = item.value;
|
|
804
|
+
const wasPinned = pinnedRunSet.has(runCommand2);
|
|
805
|
+
togglePinnedRun(runCommand2);
|
|
794
806
|
refreshPins();
|
|
795
807
|
setPinFeedback(
|
|
796
|
-
wasPinned ? `Unpinned exact run "${
|
|
808
|
+
wasPinned ? `Unpinned exact run "${runCommand2}"` : `Pinned exact run "${runCommand2}"`
|
|
797
809
|
);
|
|
798
810
|
}
|
|
799
811
|
};
|
|
@@ -968,8 +980,8 @@ function buildCommandArgItems({
|
|
|
968
980
|
selectable: false
|
|
969
981
|
},
|
|
970
982
|
...suggestions.map((option) => {
|
|
971
|
-
const
|
|
972
|
-
const pinHint = pinnedRunSet.has(
|
|
983
|
+
const runCommand2 = buildRunCommand(command, option.args);
|
|
984
|
+
const pinHint = pinnedRunSet.has(runCommand2) ? "pinned run" : void 0;
|
|
973
985
|
return {
|
|
974
986
|
value: `suggest:${option.value}`,
|
|
975
987
|
label: option.label,
|
|
@@ -1110,33 +1122,33 @@ function CommandArgs({
|
|
|
1110
1122
|
return;
|
|
1111
1123
|
}
|
|
1112
1124
|
if (value.startsWith("suggest:")) {
|
|
1113
|
-
const
|
|
1125
|
+
const runCommand2 = getRunCommandFromArgsSelection(
|
|
1114
1126
|
command,
|
|
1115
1127
|
value,
|
|
1116
1128
|
suggestions
|
|
1117
1129
|
);
|
|
1118
|
-
if (
|
|
1130
|
+
if (runCommand2) {
|
|
1119
1131
|
navigateWithExtraArgs(
|
|
1120
|
-
|
|
1132
|
+
runCommand2.split(" ").slice(1).filter(Boolean)
|
|
1121
1133
|
);
|
|
1122
1134
|
}
|
|
1123
1135
|
return;
|
|
1124
1136
|
}
|
|
1125
1137
|
},
|
|
1126
1138
|
onRightAction: (item) => {
|
|
1127
|
-
const
|
|
1139
|
+
const runCommand2 = getRunCommandFromArgsSelection(
|
|
1128
1140
|
command,
|
|
1129
1141
|
item.value,
|
|
1130
1142
|
suggestions
|
|
1131
1143
|
);
|
|
1132
|
-
if (!
|
|
1144
|
+
if (!runCommand2) {
|
|
1133
1145
|
return;
|
|
1134
1146
|
}
|
|
1135
|
-
const wasPinned = pinnedRuns.includes(
|
|
1136
|
-
togglePinnedRun(
|
|
1147
|
+
const wasPinned = pinnedRuns.includes(runCommand2);
|
|
1148
|
+
togglePinnedRun(runCommand2);
|
|
1137
1149
|
setPinnedRuns2(getPinnedRuns());
|
|
1138
1150
|
setPinFeedback(
|
|
1139
|
-
wasPinned ? `Unpinned exact run "${
|
|
1151
|
+
wasPinned ? `Unpinned exact run "${runCommand2}"` : `Pinned exact run "${runCommand2}"`
|
|
1140
1152
|
);
|
|
1141
1153
|
},
|
|
1142
1154
|
onCancel: onBack,
|
|
@@ -1189,14 +1201,14 @@ function FlagToggle({
|
|
|
1189
1201
|
}) {
|
|
1190
1202
|
const [cursor, setCursor] = useState6(0);
|
|
1191
1203
|
const [selected, setSelected] = useState6(/* @__PURE__ */ new Set());
|
|
1192
|
-
useInput3((
|
|
1193
|
-
if (key.upArrow ||
|
|
1204
|
+
useInput3((input2, key) => {
|
|
1205
|
+
if (key.upArrow || input2 === "k") {
|
|
1194
1206
|
setCursor((prev) => prev > 0 ? prev - 1 : flags.length - 1);
|
|
1195
1207
|
}
|
|
1196
|
-
if (key.downArrow ||
|
|
1208
|
+
if (key.downArrow || input2 === "j") {
|
|
1197
1209
|
setCursor((prev) => prev < flags.length - 1 ? prev + 1 : 0);
|
|
1198
1210
|
}
|
|
1199
|
-
if (
|
|
1211
|
+
if (input2 === " ") {
|
|
1200
1212
|
setSelected((prev) => {
|
|
1201
1213
|
const next = new Set(prev);
|
|
1202
1214
|
const flag = flags[cursor];
|
|
@@ -1314,12 +1326,12 @@ function ConfirmPrompt({
|
|
|
1314
1326
|
defaultValue = true,
|
|
1315
1327
|
onConfirm
|
|
1316
1328
|
}) {
|
|
1317
|
-
useInput4((
|
|
1318
|
-
if (
|
|
1329
|
+
useInput4((input2) => {
|
|
1330
|
+
if (input2 === "y" || input2 === "Y") {
|
|
1319
1331
|
onConfirm(true);
|
|
1320
|
-
} else if (
|
|
1332
|
+
} else if (input2 === "n" || input2 === "N") {
|
|
1321
1333
|
onConfirm(false);
|
|
1322
|
-
} else if (
|
|
1334
|
+
} else if (input2 === "\r") {
|
|
1323
1335
|
onConfirm(defaultValue);
|
|
1324
1336
|
}
|
|
1325
1337
|
});
|
|
@@ -1358,11 +1370,65 @@ import { useState as useState7, useCallback as useCallback2 } from "react";
|
|
|
1358
1370
|
|
|
1359
1371
|
// src/lib/runner.ts
|
|
1360
1372
|
import { spawn } from "child_process";
|
|
1361
|
-
|
|
1362
|
-
|
|
1373
|
+
import { existsSync } from "fs";
|
|
1374
|
+
import { delimiter, dirname, join, resolve } from "path";
|
|
1375
|
+
function getSupabaseBinaryCandidates() {
|
|
1376
|
+
if (process.platform === "win32") {
|
|
1377
|
+
return ["supabase.cmd", "supabase.exe", "supabase"];
|
|
1378
|
+
}
|
|
1379
|
+
return ["supabase"];
|
|
1380
|
+
}
|
|
1381
|
+
function hasLocalSupabaseBinary(binDir) {
|
|
1382
|
+
return getSupabaseBinaryCandidates().some(
|
|
1383
|
+
(candidate) => existsSync(join(binDir, candidate))
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
function getPathEnvKey(env) {
|
|
1387
|
+
return Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
|
1388
|
+
}
|
|
1389
|
+
function findLocalSupabaseBinDir(startDir = process.cwd()) {
|
|
1390
|
+
let currentDir = resolve(startDir);
|
|
1391
|
+
while (true) {
|
|
1392
|
+
const binDir = join(currentDir, "node_modules", ".bin");
|
|
1393
|
+
if (hasLocalSupabaseBinary(binDir)) {
|
|
1394
|
+
return binDir;
|
|
1395
|
+
}
|
|
1396
|
+
const parentDir = dirname(currentDir);
|
|
1397
|
+
if (parentDir === currentDir) {
|
|
1398
|
+
return void 0;
|
|
1399
|
+
}
|
|
1400
|
+
currentDir = parentDir;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
function resolveSupabaseCommand(startDir = process.cwd(), env = process.env) {
|
|
1404
|
+
const localBinDir = findLocalSupabaseBinDir(startDir);
|
|
1405
|
+
if (!localBinDir) {
|
|
1406
|
+
return {
|
|
1407
|
+
command: "supabase",
|
|
1408
|
+
env: { ...env },
|
|
1409
|
+
source: "path"
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
const pathKey = getPathEnvKey(env);
|
|
1413
|
+
const currentPath = env[pathKey];
|
|
1414
|
+
return {
|
|
1415
|
+
command: "supabase",
|
|
1416
|
+
env: {
|
|
1417
|
+
...env,
|
|
1418
|
+
[pathKey]: currentPath ? `${localBinDir}${delimiter}${currentPath}` : localBinDir
|
|
1419
|
+
},
|
|
1420
|
+
source: "repository",
|
|
1421
|
+
localBinDir
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
async function runCommand(execution, args, cwd = process.cwd()) {
|
|
1425
|
+
return new Promise((resolve4) => {
|
|
1363
1426
|
let stdout = "";
|
|
1364
1427
|
let stderr = "";
|
|
1365
|
-
const
|
|
1428
|
+
const resolvedExecution = typeof execution === "string" ? { command: execution } : execution;
|
|
1429
|
+
const child = spawn(resolvedExecution.command, args, {
|
|
1430
|
+
cwd,
|
|
1431
|
+
env: resolvedExecution.env,
|
|
1366
1432
|
shell: true,
|
|
1367
1433
|
stdio: ["inherit", "pipe", "pipe"]
|
|
1368
1434
|
});
|
|
@@ -1377,7 +1443,7 @@ async function runSupabaseCommand(args) {
|
|
|
1377
1443
|
process.stderr.write(text);
|
|
1378
1444
|
});
|
|
1379
1445
|
child.on("error", (err) => {
|
|
1380
|
-
|
|
1446
|
+
resolve4({
|
|
1381
1447
|
exitCode: null,
|
|
1382
1448
|
signal: null,
|
|
1383
1449
|
stdout,
|
|
@@ -1386,19 +1452,22 @@ async function runSupabaseCommand(args) {
|
|
|
1386
1452
|
});
|
|
1387
1453
|
});
|
|
1388
1454
|
child.on("exit", (code, signal) => {
|
|
1389
|
-
|
|
1455
|
+
resolve4({ exitCode: code, signal, stdout, stderr });
|
|
1390
1456
|
});
|
|
1391
1457
|
});
|
|
1392
1458
|
}
|
|
1459
|
+
async function runSupabaseCommand(args, cwd = process.cwd()) {
|
|
1460
|
+
return runCommand(resolveSupabaseCommand(cwd), args, cwd);
|
|
1461
|
+
}
|
|
1393
1462
|
|
|
1394
1463
|
// src/hooks/useCommand.ts
|
|
1395
|
-
function useCommand() {
|
|
1464
|
+
function useCommand(execution = "supabase", cwd = process.cwd()) {
|
|
1396
1465
|
const [status, setStatus] = useState7("idle");
|
|
1397
1466
|
const [result, setResult] = useState7(null);
|
|
1398
1467
|
const run = useCallback2(async (args) => {
|
|
1399
1468
|
setStatus("running");
|
|
1400
1469
|
setResult(null);
|
|
1401
|
-
const res = await runSupabaseCommand(args);
|
|
1470
|
+
const res = execution === "supabase" ? await runSupabaseCommand(args, cwd) : await runCommand(execution, args, cwd);
|
|
1402
1471
|
setResult(res);
|
|
1403
1472
|
if (res.spawnError || res.exitCode !== null && res.exitCode !== 0) {
|
|
1404
1473
|
setStatus("error");
|
|
@@ -1406,7 +1475,7 @@ function useCommand() {
|
|
|
1406
1475
|
setStatus("success");
|
|
1407
1476
|
}
|
|
1408
1477
|
return res;
|
|
1409
|
-
}, []);
|
|
1478
|
+
}, [cwd, execution]);
|
|
1410
1479
|
const reset = useCallback2(() => {
|
|
1411
1480
|
setStatus("idle");
|
|
1412
1481
|
setResult(null);
|
|
@@ -1417,19 +1486,19 @@ function useCommand() {
|
|
|
1417
1486
|
// src/lib/clipboard.ts
|
|
1418
1487
|
import { spawn as spawn2, exec } from "child_process";
|
|
1419
1488
|
async function openInBrowser(url) {
|
|
1420
|
-
return new Promise((
|
|
1489
|
+
return new Promise((resolve4) => {
|
|
1421
1490
|
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
|
|
1422
|
-
exec(cmd, () =>
|
|
1491
|
+
exec(cmd, () => resolve4());
|
|
1423
1492
|
});
|
|
1424
1493
|
}
|
|
1425
1494
|
async function copyToClipboard(text) {
|
|
1426
|
-
return new Promise((
|
|
1495
|
+
return new Promise((resolve4) => {
|
|
1427
1496
|
const cmd = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
|
|
1428
1497
|
const child = spawn2(cmd, [], { shell: true });
|
|
1429
1498
|
child.stdin?.write(text);
|
|
1430
1499
|
child.stdin?.end();
|
|
1431
|
-
child.on("exit", () =>
|
|
1432
|
-
child.on("error", () =>
|
|
1500
|
+
child.on("exit", () => resolve4());
|
|
1501
|
+
child.on("error", () => resolve4());
|
|
1433
1502
|
});
|
|
1434
1503
|
}
|
|
1435
1504
|
|
|
@@ -1445,7 +1514,7 @@ function CommandExecution({
|
|
|
1445
1514
|
const [pinMessage, setPinMessage] = useState8();
|
|
1446
1515
|
const { status, result, run, reset } = useCommand();
|
|
1447
1516
|
const cmdDisplay = `supabase ${currentArgs.join(" ")}`;
|
|
1448
|
-
const
|
|
1517
|
+
const runCommand2 = currentArgs.join(" ");
|
|
1449
1518
|
useEffect4(() => {
|
|
1450
1519
|
if (phase === "running" && status === "idle") {
|
|
1451
1520
|
run(currentArgs);
|
|
@@ -1453,7 +1522,7 @@ function CommandExecution({
|
|
|
1453
1522
|
}, [phase, status, run, currentArgs]);
|
|
1454
1523
|
useEffect4(() => {
|
|
1455
1524
|
if (phase === "running" && status === "success") {
|
|
1456
|
-
if (isPinnedRun(
|
|
1525
|
+
if (isPinnedRun(runCommand2)) {
|
|
1457
1526
|
setPhase("success");
|
|
1458
1527
|
} else {
|
|
1459
1528
|
setPhase("success-pin-offer");
|
|
@@ -1462,7 +1531,7 @@ function CommandExecution({
|
|
|
1462
1531
|
if (phase === "running" && status === "error") {
|
|
1463
1532
|
setPhase("error-menu");
|
|
1464
1533
|
}
|
|
1465
|
-
}, [phase,
|
|
1534
|
+
}, [phase, runCommand2, status]);
|
|
1466
1535
|
if (phase === "confirm") {
|
|
1467
1536
|
return /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx13(
|
|
1468
1537
|
ConfirmPrompt,
|
|
@@ -1504,8 +1573,8 @@ function CommandExecution({
|
|
|
1504
1573
|
message: "Pin this exact command?",
|
|
1505
1574
|
defaultValue: false,
|
|
1506
1575
|
onConfirm: (shouldPin) => {
|
|
1507
|
-
if (shouldPin && !isPinnedRun(
|
|
1508
|
-
togglePinnedRun(
|
|
1576
|
+
if (shouldPin && !isPinnedRun(runCommand2)) {
|
|
1577
|
+
togglePinnedRun(runCommand2);
|
|
1509
1578
|
setPinMessage("Exact command pinned to Pinned Runs.");
|
|
1510
1579
|
}
|
|
1511
1580
|
setPhase("success");
|
|
@@ -1569,7 +1638,7 @@ function CommandExecution({
|
|
|
1569
1638
|
/* @__PURE__ */ jsx13(Text11, { color: "red", children: result.spawnError })
|
|
1570
1639
|
] }),
|
|
1571
1640
|
(result.spawnError.includes("ENOENT") || result.spawnError.includes("not found")) && /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [
|
|
1572
|
-
/* @__PURE__ */ jsx13(Text11, { color: inkColors.accent, bold: true, children: "\u{1F4A1} Supabase CLI not found in PATH" }),
|
|
1641
|
+
/* @__PURE__ */ jsx13(Text11, { color: inkColors.accent, bold: true, children: "\u{1F4A1} Supabase CLI not found in this repository or PATH" }),
|
|
1573
1642
|
/* @__PURE__ */ jsxs13(Box12, { gap: 1, children: [
|
|
1574
1643
|
/* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Install it:" }),
|
|
1575
1644
|
/* @__PURE__ */ jsx13(Text11, { color: inkColors.accent, children: "https://supabase.com/docs/guides/cli" })
|
|
@@ -1634,8 +1703,244 @@ function CommandExecution({
|
|
|
1634
1703
|
] });
|
|
1635
1704
|
}
|
|
1636
1705
|
|
|
1637
|
-
// src/
|
|
1706
|
+
// src/screens/SelfUpdate.tsx
|
|
1707
|
+
import { useEffect as useEffect5, useState as useState9 } from "react";
|
|
1708
|
+
import { Box as Box13, Text as Text12 } from "ink";
|
|
1709
|
+
|
|
1710
|
+
// src/lib/packageRoot.ts
|
|
1711
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1712
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
1713
|
+
function findNearestPackageRoot(startDir = process.cwd()) {
|
|
1714
|
+
let currentDir = resolve2(startDir);
|
|
1715
|
+
while (true) {
|
|
1716
|
+
if (existsSync2(join2(currentDir, "package.json"))) {
|
|
1717
|
+
return currentDir;
|
|
1718
|
+
}
|
|
1719
|
+
const parentDir = dirname2(currentDir);
|
|
1720
|
+
if (parentDir === currentDir) {
|
|
1721
|
+
return void 0;
|
|
1722
|
+
}
|
|
1723
|
+
currentDir = parentDir;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// src/screens/SelfUpdate.tsx
|
|
1638
1728
|
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1729
|
+
var packageName = "@polterware/polterbase";
|
|
1730
|
+
var globalUpdateArgs = ["install", "-g", `${packageName}@latest`];
|
|
1731
|
+
var repositoryUpdateArgs = ["install", "-D", `${packageName}@latest`];
|
|
1732
|
+
function getUpdateArgs(target) {
|
|
1733
|
+
return target === "repository" ? repositoryUpdateArgs : globalUpdateArgs;
|
|
1734
|
+
}
|
|
1735
|
+
function SelfUpdate({
|
|
1736
|
+
onBack,
|
|
1737
|
+
onExit
|
|
1738
|
+
}) {
|
|
1739
|
+
const repositoryRoot = findNearestPackageRoot();
|
|
1740
|
+
const [target, setTarget] = useState9(
|
|
1741
|
+
repositoryRoot ? "repository" : "global"
|
|
1742
|
+
);
|
|
1743
|
+
const [phase, setPhase] = useState9(
|
|
1744
|
+
repositoryRoot ? "target" : "confirm"
|
|
1745
|
+
);
|
|
1746
|
+
const updateArgs = getUpdateArgs(target);
|
|
1747
|
+
const updateDisplay = `npm ${updateArgs.join(" ")}`;
|
|
1748
|
+
const updateCwd = target === "repository" && repositoryRoot ? repositoryRoot : process.cwd();
|
|
1749
|
+
const { status, result, run, reset } = useCommand("npm", updateCwd);
|
|
1750
|
+
useEffect5(() => {
|
|
1751
|
+
if (phase === "running" && status === "idle") {
|
|
1752
|
+
run(updateArgs);
|
|
1753
|
+
}
|
|
1754
|
+
}, [phase, run, status, updateArgs]);
|
|
1755
|
+
useEffect5(() => {
|
|
1756
|
+
if (phase === "running" && status === "success") {
|
|
1757
|
+
setPhase("success");
|
|
1758
|
+
}
|
|
1759
|
+
if (phase === "running" && status === "error") {
|
|
1760
|
+
setPhase("error");
|
|
1761
|
+
}
|
|
1762
|
+
}, [phase, status]);
|
|
1763
|
+
if (phase === "target") {
|
|
1764
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
|
|
1765
|
+
/* @__PURE__ */ jsx14(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text12, { bold: true, children: "Choose where to update Polterbase." }) }),
|
|
1766
|
+
/* @__PURE__ */ jsx14(
|
|
1767
|
+
SelectList,
|
|
1768
|
+
{
|
|
1769
|
+
items: [
|
|
1770
|
+
{
|
|
1771
|
+
value: "repository",
|
|
1772
|
+
label: "Current repository",
|
|
1773
|
+
hint: "Pin the latest version in package.json"
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
value: "global",
|
|
1777
|
+
label: "Global install",
|
|
1778
|
+
hint: "Update the shared version available in PATH"
|
|
1779
|
+
},
|
|
1780
|
+
{ value: "back", label: "\u2190 Back to menu" }
|
|
1781
|
+
],
|
|
1782
|
+
onSelect: (value) => {
|
|
1783
|
+
if (value === "back") {
|
|
1784
|
+
onBack();
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
setTarget(value);
|
|
1788
|
+
reset();
|
|
1789
|
+
setPhase("confirm");
|
|
1790
|
+
},
|
|
1791
|
+
onCancel: onBack
|
|
1792
|
+
}
|
|
1793
|
+
),
|
|
1794
|
+
repositoryRoot && /* @__PURE__ */ jsx14(Box13, { marginTop: 1, marginLeft: 2, children: /* @__PURE__ */ jsxs14(Text12, { dimColor: true, children: [
|
|
1795
|
+
"Repository root: ",
|
|
1796
|
+
repositoryRoot
|
|
1797
|
+
] }) })
|
|
1798
|
+
] });
|
|
1799
|
+
}
|
|
1800
|
+
if (phase === "confirm") {
|
|
1801
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
|
|
1802
|
+
/* @__PURE__ */ jsx14(
|
|
1803
|
+
ConfirmPrompt,
|
|
1804
|
+
{
|
|
1805
|
+
message: `Run ${updateDisplay}?`,
|
|
1806
|
+
defaultValue: true,
|
|
1807
|
+
onConfirm: (confirmed) => {
|
|
1808
|
+
if (confirmed) {
|
|
1809
|
+
reset();
|
|
1810
|
+
setPhase("running");
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
if (repositoryRoot) {
|
|
1814
|
+
setPhase("target");
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
onBack();
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
),
|
|
1821
|
+
/* @__PURE__ */ jsxs14(Box13, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [
|
|
1822
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: target === "repository" ? "This updates the dependency in the current repository." : "This updates the global npm install." }),
|
|
1823
|
+
target === "repository" && repositoryRoot && /* @__PURE__ */ jsxs14(Text12, { dimColor: true, children: [
|
|
1824
|
+
"Run location: ",
|
|
1825
|
+
repositoryRoot
|
|
1826
|
+
] })
|
|
1827
|
+
] })
|
|
1828
|
+
] });
|
|
1829
|
+
}
|
|
1830
|
+
if (phase === "running") {
|
|
1831
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
|
|
1832
|
+
/* @__PURE__ */ jsx14(Divider, {}),
|
|
1833
|
+
/* @__PURE__ */ jsxs14(Box13, { marginY: 1, gap: 1, children: [
|
|
1834
|
+
/* @__PURE__ */ jsx14(Text12, { color: inkColors.accent, bold: true, children: "\u25B6" }),
|
|
1835
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Running:" }),
|
|
1836
|
+
/* @__PURE__ */ jsx14(Text12, { children: updateDisplay })
|
|
1837
|
+
] }),
|
|
1838
|
+
/* @__PURE__ */ jsx14(Divider, {}),
|
|
1839
|
+
/* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Spinner, { label: "Updating Polterbase..." }) })
|
|
1840
|
+
] });
|
|
1841
|
+
}
|
|
1842
|
+
if (phase === "success") {
|
|
1843
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
|
|
1844
|
+
/* @__PURE__ */ jsx14(Divider, {}),
|
|
1845
|
+
/* @__PURE__ */ jsxs14(Box13, { marginY: 1, gap: 1, children: [
|
|
1846
|
+
/* @__PURE__ */ jsx14(Text12, { color: inkColors.accent, bold: true, children: "\u2713" }),
|
|
1847
|
+
/* @__PURE__ */ jsx14(Text12, { color: inkColors.accent, bold: true, children: "Update completed successfully!" })
|
|
1848
|
+
] }),
|
|
1849
|
+
/* @__PURE__ */ jsxs14(Box13, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: [
|
|
1850
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Restart Polterbase to use the latest version." }),
|
|
1851
|
+
target === "repository" && repositoryRoot && /* @__PURE__ */ jsxs14(Text12, { dimColor: true, children: [
|
|
1852
|
+
"Repository updated in: ",
|
|
1853
|
+
repositoryRoot
|
|
1854
|
+
] })
|
|
1855
|
+
] }),
|
|
1856
|
+
/* @__PURE__ */ jsx14(
|
|
1857
|
+
SelectList,
|
|
1858
|
+
{
|
|
1859
|
+
items: [
|
|
1860
|
+
{ value: "__back__", label: "\u2190 Back to menu" },
|
|
1861
|
+
{ value: "__exit__", label: "\u{1F6AA} Exit Polterbase" }
|
|
1862
|
+
],
|
|
1863
|
+
onSelect: (value) => {
|
|
1864
|
+
if (value === "__exit__") {
|
|
1865
|
+
onExit();
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
onBack();
|
|
1869
|
+
},
|
|
1870
|
+
onCancel: onBack
|
|
1871
|
+
}
|
|
1872
|
+
)
|
|
1873
|
+
] });
|
|
1874
|
+
}
|
|
1875
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
|
|
1876
|
+
/* @__PURE__ */ jsx14(Divider, {}),
|
|
1877
|
+
result?.spawnError ? /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", marginY: 1, children: [
|
|
1878
|
+
/* @__PURE__ */ jsxs14(Box13, { gap: 1, children: [
|
|
1879
|
+
/* @__PURE__ */ jsx14(Text12, { color: "red", bold: true, children: "\u2717" }),
|
|
1880
|
+
/* @__PURE__ */ jsx14(Text12, { color: "red", bold: true, children: "Failed to start update" })
|
|
1881
|
+
] }),
|
|
1882
|
+
/* @__PURE__ */ jsxs14(Box13, { marginLeft: 2, marginTop: 1, children: [
|
|
1883
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Error: " }),
|
|
1884
|
+
/* @__PURE__ */ jsx14(Text12, { color: "red", children: result.spawnError })
|
|
1885
|
+
] })
|
|
1886
|
+
] }) : /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", marginY: 1, children: [
|
|
1887
|
+
/* @__PURE__ */ jsxs14(Box13, { gap: 1, children: [
|
|
1888
|
+
/* @__PURE__ */ jsx14(Text12, { color: "red", bold: true, children: "\u2717" }),
|
|
1889
|
+
/* @__PURE__ */ jsx14(Text12, { color: "red", children: "Update failed " }),
|
|
1890
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(exit code " }),
|
|
1891
|
+
/* @__PURE__ */ jsx14(Text12, { color: "red", bold: true, children: String(result?.exitCode) }),
|
|
1892
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ")" })
|
|
1893
|
+
] }),
|
|
1894
|
+
/* @__PURE__ */ jsxs14(Box13, { marginLeft: 2, marginTop: 1, children: [
|
|
1895
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Command: " }),
|
|
1896
|
+
/* @__PURE__ */ jsx14(Text12, { children: updateDisplay })
|
|
1897
|
+
] })
|
|
1898
|
+
] }),
|
|
1899
|
+
/* @__PURE__ */ jsxs14(Box13, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: [
|
|
1900
|
+
/* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Manual fallback:" }),
|
|
1901
|
+
/* @__PURE__ */ jsx14(Text12, { color: inkColors.accent, children: updateDisplay }),
|
|
1902
|
+
target === "repository" && repositoryRoot && /* @__PURE__ */ jsxs14(Text12, { dimColor: true, children: [
|
|
1903
|
+
"Run location: ",
|
|
1904
|
+
repositoryRoot
|
|
1905
|
+
] })
|
|
1906
|
+
] }),
|
|
1907
|
+
/* @__PURE__ */ jsx14(Box13, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx14(Text12, { bold: true, children: "What would you like to do?" }) }),
|
|
1908
|
+
/* @__PURE__ */ jsx14(
|
|
1909
|
+
SelectList,
|
|
1910
|
+
{
|
|
1911
|
+
items: [
|
|
1912
|
+
{ value: "retry", label: "\u{1F504} Retry update" },
|
|
1913
|
+
...repositoryRoot ? [{ value: "target", label: "\u2194 Choose update target" }] : [],
|
|
1914
|
+
{ value: "menu", label: "\u2190 Return to main menu" },
|
|
1915
|
+
{ value: "exit", label: "\u{1F6AA} Exit Polterbase" }
|
|
1916
|
+
],
|
|
1917
|
+
onSelect: (value) => {
|
|
1918
|
+
switch (value) {
|
|
1919
|
+
case "retry":
|
|
1920
|
+
reset();
|
|
1921
|
+
setPhase("running");
|
|
1922
|
+
break;
|
|
1923
|
+
case "target":
|
|
1924
|
+
reset();
|
|
1925
|
+
setPhase("target");
|
|
1926
|
+
break;
|
|
1927
|
+
case "menu":
|
|
1928
|
+
onBack();
|
|
1929
|
+
break;
|
|
1930
|
+
case "exit":
|
|
1931
|
+
onExit();
|
|
1932
|
+
break;
|
|
1933
|
+
}
|
|
1934
|
+
},
|
|
1935
|
+
onCancel: onBack
|
|
1936
|
+
}
|
|
1937
|
+
),
|
|
1938
|
+
/* @__PURE__ */ jsx14(StatusBar, {})
|
|
1939
|
+
] });
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/app.tsx
|
|
1943
|
+
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1639
1944
|
function App() {
|
|
1640
1945
|
const { screen, params, navigate, goBack } = useNavigation();
|
|
1641
1946
|
const { exit } = useApp();
|
|
@@ -1647,9 +1952,9 @@ function App() {
|
|
|
1647
1952
|
};
|
|
1648
1953
|
switch (screen) {
|
|
1649
1954
|
case "main-menu":
|
|
1650
|
-
return /* @__PURE__ */
|
|
1955
|
+
return /* @__PURE__ */ jsx15(MainMenu, { onNavigate: navigate, onExit: handleExit });
|
|
1651
1956
|
case "command-args":
|
|
1652
|
-
return /* @__PURE__ */
|
|
1957
|
+
return /* @__PURE__ */ jsx15(
|
|
1653
1958
|
CommandArgs,
|
|
1654
1959
|
{
|
|
1655
1960
|
command: params.command ?? "",
|
|
@@ -1658,9 +1963,9 @@ function App() {
|
|
|
1658
1963
|
}
|
|
1659
1964
|
);
|
|
1660
1965
|
case "custom-command":
|
|
1661
|
-
return /* @__PURE__ */
|
|
1966
|
+
return /* @__PURE__ */ jsx15(CustomCommand, { onNavigate: navigate, onBack: goBack });
|
|
1662
1967
|
case "flag-selection":
|
|
1663
|
-
return /* @__PURE__ */
|
|
1968
|
+
return /* @__PURE__ */ jsx15(
|
|
1664
1969
|
FlagSelection,
|
|
1665
1970
|
{
|
|
1666
1971
|
args: params.args ?? [],
|
|
@@ -1670,7 +1975,7 @@ function App() {
|
|
|
1670
1975
|
);
|
|
1671
1976
|
case "confirm-execute":
|
|
1672
1977
|
case "command-execution":
|
|
1673
|
-
return /* @__PURE__ */
|
|
1978
|
+
return /* @__PURE__ */ jsx15(
|
|
1674
1979
|
CommandExecution,
|
|
1675
1980
|
{
|
|
1676
1981
|
args: params.args ?? [],
|
|
@@ -1678,13 +1983,930 @@ function App() {
|
|
|
1678
1983
|
onExit: handleExit
|
|
1679
1984
|
}
|
|
1680
1985
|
);
|
|
1986
|
+
case "self-update":
|
|
1987
|
+
return /* @__PURE__ */ jsx15(SelfUpdate, { onBack: goBack, onExit: handleExit });
|
|
1681
1988
|
default:
|
|
1682
|
-
return /* @__PURE__ */
|
|
1989
|
+
return /* @__PURE__ */ jsx15(Box14, { children: /* @__PURE__ */ jsxs15(Text13, { color: "red", children: [
|
|
1683
1990
|
"Unknown screen: ",
|
|
1684
1991
|
screen
|
|
1685
1992
|
] }) });
|
|
1686
1993
|
}
|
|
1687
1994
|
}
|
|
1688
1995
|
|
|
1996
|
+
// src/lib/cliArgs.ts
|
|
1997
|
+
function takeValue(args, index) {
|
|
1998
|
+
const token = args[index];
|
|
1999
|
+
if (!token) {
|
|
2000
|
+
return { nextIndex: index };
|
|
2001
|
+
}
|
|
2002
|
+
const eqIndex = token.indexOf("=");
|
|
2003
|
+
if (eqIndex >= 0) {
|
|
2004
|
+
return {
|
|
2005
|
+
value: token.slice(eqIndex + 1),
|
|
2006
|
+
nextIndex: index
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
return { value: args[index + 1], nextIndex: index + 1 };
|
|
2010
|
+
}
|
|
2011
|
+
function parseCliArgs(argv) {
|
|
2012
|
+
if (argv.length === 0) {
|
|
2013
|
+
return { mode: "interactive", options: {} };
|
|
2014
|
+
}
|
|
2015
|
+
if (argv[0] === "--help" || argv[0] === "help") {
|
|
2016
|
+
return { mode: "help", options: {} };
|
|
2017
|
+
}
|
|
2018
|
+
if (argv[0] !== "app") {
|
|
2019
|
+
return { mode: "interactive", options: {} };
|
|
2020
|
+
}
|
|
2021
|
+
const options = {};
|
|
2022
|
+
options.action = argv[1];
|
|
2023
|
+
options.app = argv[2];
|
|
2024
|
+
for (let index = 3; index < argv.length; index += 1) {
|
|
2025
|
+
const token = argv[index];
|
|
2026
|
+
if (token === "push" || token === "lint" || token === "reset" || token === "local-reset") {
|
|
2027
|
+
options.migrationAction = token;
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
if (token === "--yes") {
|
|
2031
|
+
options.yes = true;
|
|
2032
|
+
continue;
|
|
2033
|
+
}
|
|
2034
|
+
if (token === "--relink") {
|
|
2035
|
+
options.relink = true;
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (token === "--create-project") {
|
|
2039
|
+
options.createProject = true;
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
if (token === "--use-existing-project") {
|
|
2043
|
+
options.useExistingProject = true;
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
if (token.startsWith("--path")) {
|
|
2047
|
+
const parsed = takeValue(argv, index);
|
|
2048
|
+
options.path = parsed.value;
|
|
2049
|
+
index = parsed.nextIndex;
|
|
2050
|
+
continue;
|
|
2051
|
+
}
|
|
2052
|
+
if (token.startsWith("--platform")) {
|
|
2053
|
+
const parsed = takeValue(argv, index);
|
|
2054
|
+
options.platform = parsed.value;
|
|
2055
|
+
index = parsed.nextIndex;
|
|
2056
|
+
continue;
|
|
2057
|
+
}
|
|
2058
|
+
if (token.startsWith("--version")) {
|
|
2059
|
+
const parsed = takeValue(argv, index);
|
|
2060
|
+
options.version = parsed.value;
|
|
2061
|
+
index = parsed.nextIndex;
|
|
2062
|
+
continue;
|
|
2063
|
+
}
|
|
2064
|
+
if (token.startsWith("--artifact-url")) {
|
|
2065
|
+
const parsed = takeValue(argv, index);
|
|
2066
|
+
options.artifactUrl = parsed.value;
|
|
2067
|
+
index = parsed.nextIndex;
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
if (token.startsWith("--install-dir")) {
|
|
2071
|
+
const parsed = takeValue(argv, index);
|
|
2072
|
+
options.installDir = parsed.value;
|
|
2073
|
+
index = parsed.nextIndex;
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
return {
|
|
2078
|
+
mode: "app",
|
|
2079
|
+
options
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
function printCliHelp() {
|
|
2083
|
+
process.stdout.write(
|
|
2084
|
+
[
|
|
2085
|
+
"Polterbase",
|
|
2086
|
+
"",
|
|
2087
|
+
"Usage:",
|
|
2088
|
+
" polterbase",
|
|
2089
|
+
" polterbase app setup uru [--path <dir>] [--create-project|--use-existing-project] [--yes]",
|
|
2090
|
+
" polterbase app link uru [--path <dir>] [--relink]",
|
|
2091
|
+
" polterbase app migrate uru [push|lint|reset|local-reset] [--path <dir>] [--relink]",
|
|
2092
|
+
" polterbase app configure uru [--path <dir>] [--yes]",
|
|
2093
|
+
" polterbase app install uru --platform macos [--version <version>] [--artifact-url <url>] [--install-dir <dir>] [--yes]",
|
|
2094
|
+
"",
|
|
2095
|
+
"Notes:",
|
|
2096
|
+
" - App workflows stay separate from the generic Supabase interactive menu.",
|
|
2097
|
+
" - `install uru` resolves the latest GitHub release from polterware/uru by default.",
|
|
2098
|
+
" - Use --artifact-url or POLTERBASE_URU_MACOS_ARTIFACT_URL to override the downloaded asset.",
|
|
2099
|
+
" - Use POLTERBASE_URU_GITHUB_REPO=owner/repo to resolve releases from a different repository.",
|
|
2100
|
+
""
|
|
2101
|
+
].join("\n")
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// src/apps/runAppCli.ts
|
|
2106
|
+
import pc3 from "picocolors";
|
|
2107
|
+
|
|
2108
|
+
// src/apps/uru.ts
|
|
2109
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
2110
|
+
import { mkdtemp, readdir, stat } from "fs/promises";
|
|
2111
|
+
import { dirname as dirname3, join as join4, resolve as resolve3 } from "path";
|
|
2112
|
+
import { tmpdir } from "os";
|
|
2113
|
+
import pc2 from "picocolors";
|
|
2114
|
+
|
|
2115
|
+
// src/apps/bootstrapPaths.ts
|
|
2116
|
+
import { homedir } from "os";
|
|
2117
|
+
import { join as join3 } from "path";
|
|
2118
|
+
function getUruBootstrapPayloadPath() {
|
|
2119
|
+
const home = homedir();
|
|
2120
|
+
if (process.platform === "darwin") {
|
|
2121
|
+
return join3(
|
|
2122
|
+
home,
|
|
2123
|
+
"Library",
|
|
2124
|
+
"Application Support",
|
|
2125
|
+
"uru",
|
|
2126
|
+
"bootstrap",
|
|
2127
|
+
"supabase.json"
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
2130
|
+
if (process.platform === "win32") {
|
|
2131
|
+
const appData = process.env.APPDATA ?? join3(home, "AppData", "Roaming");
|
|
2132
|
+
return join3(appData, "uru", "bootstrap", "supabase.json");
|
|
2133
|
+
}
|
|
2134
|
+
return join3(home, ".config", "uru", "bootstrap", "supabase.json");
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// src/apps/uruRelease.ts
|
|
2138
|
+
var DEFAULT_URU_GITHUB_REPO = "polterware/uru";
|
|
2139
|
+
var DEFAULT_ARTIFACT_ENV_VAR = "POLTERBASE_URU_MACOS_ARTIFACT_URL";
|
|
2140
|
+
var DEFAULT_GITHUB_REPO_ENV_VAR = "POLTERBASE_URU_GITHUB_REPO";
|
|
2141
|
+
var GITHUB_API_BASE = "https://api.github.com/repos";
|
|
2142
|
+
function getGitHubHeaders(env) {
|
|
2143
|
+
const headers = {
|
|
2144
|
+
Accept: "application/vnd.github+json",
|
|
2145
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
2146
|
+
};
|
|
2147
|
+
const token = env.GITHUB_TOKEN?.trim() || env.GH_TOKEN?.trim();
|
|
2148
|
+
if (token) {
|
|
2149
|
+
headers.Authorization = `Bearer ${token}`;
|
|
2150
|
+
}
|
|
2151
|
+
return headers;
|
|
2152
|
+
}
|
|
2153
|
+
function getGitHubReleaseRequestLabel(repo, version) {
|
|
2154
|
+
if (!version) {
|
|
2155
|
+
return `latest release in ${repo}`;
|
|
2156
|
+
}
|
|
2157
|
+
return `release ${version} in ${repo}`;
|
|
2158
|
+
}
|
|
2159
|
+
function parseGitHubReleaseAsset(value) {
|
|
2160
|
+
if (!value || typeof value !== "object") {
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
const asset = value;
|
|
2164
|
+
const name = typeof asset.name === "string" ? asset.name.trim() : "";
|
|
2165
|
+
const browserDownloadUrl = typeof asset.browser_download_url === "string" ? asset.browser_download_url.trim() : "";
|
|
2166
|
+
const size = typeof asset.size === "number" ? asset.size : void 0;
|
|
2167
|
+
if (!name || !browserDownloadUrl) {
|
|
2168
|
+
return null;
|
|
2169
|
+
}
|
|
2170
|
+
return {
|
|
2171
|
+
name,
|
|
2172
|
+
browserDownloadUrl,
|
|
2173
|
+
size
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
async function parseGitHubReleaseResponse(response, label) {
|
|
2177
|
+
const body = await response.text();
|
|
2178
|
+
if (!response.ok) {
|
|
2179
|
+
let detail = body.trim();
|
|
2180
|
+
try {
|
|
2181
|
+
const parsed2 = JSON.parse(body);
|
|
2182
|
+
if (typeof parsed2.message === "string" && parsed2.message.trim()) {
|
|
2183
|
+
detail = parsed2.message.trim();
|
|
2184
|
+
}
|
|
2185
|
+
} catch {
|
|
2186
|
+
}
|
|
2187
|
+
throw new Error(
|
|
2188
|
+
`Unable to resolve ${label}: ${response.status} ${response.statusText}${detail ? ` (${detail})` : ""}.`
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
let parsed;
|
|
2192
|
+
try {
|
|
2193
|
+
parsed = JSON.parse(body);
|
|
2194
|
+
} catch (error) {
|
|
2195
|
+
throw new Error(
|
|
2196
|
+
`Unable to parse ${label} from GitHub: ${error instanceof Error ? error.message : String(error)}.`
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
const assets = Array.isArray(parsed.assets) ? parsed.assets.map((asset) => parseGitHubReleaseAsset(asset)).filter((asset) => asset !== null) : [];
|
|
2200
|
+
if (assets.length === 0) {
|
|
2201
|
+
throw new Error(`GitHub ${label} did not contain any downloadable assets.`);
|
|
2202
|
+
}
|
|
2203
|
+
return {
|
|
2204
|
+
tagName: typeof parsed.tag_name === "string" ? parsed.tag_name : null,
|
|
2205
|
+
assets
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
function normalizeUruReleaseVersion(version) {
|
|
2209
|
+
return version.trim().replace(/^v/i, "");
|
|
2210
|
+
}
|
|
2211
|
+
function buildVersionTagCandidates(version) {
|
|
2212
|
+
const normalized = normalizeUruReleaseVersion(version);
|
|
2213
|
+
if (!normalized) {
|
|
2214
|
+
return [];
|
|
2215
|
+
}
|
|
2216
|
+
return Array.from(/* @__PURE__ */ new Set([`v${normalized}`, normalized]));
|
|
2217
|
+
}
|
|
2218
|
+
async function fetchGitHubRelease(repo, version, fetchImpl, env) {
|
|
2219
|
+
const headers = getGitHubHeaders(env);
|
|
2220
|
+
if (version) {
|
|
2221
|
+
const tagCandidates = buildVersionTagCandidates(version);
|
|
2222
|
+
for (const tag of tagCandidates) {
|
|
2223
|
+
const response2 = await fetchImpl(
|
|
2224
|
+
`${GITHUB_API_BASE}/${repo}/releases/tags/${encodeURIComponent(tag)}`,
|
|
2225
|
+
{ headers }
|
|
2226
|
+
);
|
|
2227
|
+
if (response2.status === 404) {
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2230
|
+
return parseGitHubReleaseResponse(
|
|
2231
|
+
response2,
|
|
2232
|
+
getGitHubReleaseRequestLabel(repo, tag)
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
throw new Error(
|
|
2236
|
+
`Unable to find release ${normalizeUruReleaseVersion(version)} in ${repo}.`
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
const response = await fetchImpl(`${GITHUB_API_BASE}/${repo}/releases/latest`, {
|
|
2240
|
+
headers
|
|
2241
|
+
});
|
|
2242
|
+
return parseGitHubReleaseResponse(
|
|
2243
|
+
response,
|
|
2244
|
+
getGitHubReleaseRequestLabel(repo)
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
function getArtifactFileNameFromUrl(url, fallback = "uru-macos.zip") {
|
|
2248
|
+
try {
|
|
2249
|
+
const pathname = new URL(url).pathname;
|
|
2250
|
+
const name = decodeURIComponent(pathname.split("/").pop() ?? "").trim();
|
|
2251
|
+
return name || fallback;
|
|
2252
|
+
} catch {
|
|
2253
|
+
const name = decodeURIComponent(url.split("?")[0]?.split("/").pop() ?? "").trim();
|
|
2254
|
+
return name || fallback;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
function hasSupportedArchiveFormat(name) {
|
|
2258
|
+
const lowered = name.toLowerCase();
|
|
2259
|
+
return lowered.endsWith(".app.tar.gz") || lowered.endsWith(".zip");
|
|
2260
|
+
}
|
|
2261
|
+
function hasMacosHint(name) {
|
|
2262
|
+
const lowered = name.toLowerCase();
|
|
2263
|
+
return lowered.endsWith(".app.tar.gz") || lowered.includes(".app.zip") || lowered.includes("macos") || lowered.includes("darwin") || lowered.includes("universal");
|
|
2264
|
+
}
|
|
2265
|
+
function isSupportedUruMacosArtifactName(name) {
|
|
2266
|
+
return hasSupportedArchiveFormat(name) && hasMacosHint(name);
|
|
2267
|
+
}
|
|
2268
|
+
function getArchAliases(arch) {
|
|
2269
|
+
switch (arch) {
|
|
2270
|
+
case "arm64":
|
|
2271
|
+
case "aarch64":
|
|
2272
|
+
return ["arm64", "aarch64"];
|
|
2273
|
+
case "x64":
|
|
2274
|
+
case "x86_64":
|
|
2275
|
+
case "amd64":
|
|
2276
|
+
return ["x64", "x86_64", "amd64"];
|
|
2277
|
+
default:
|
|
2278
|
+
return [arch.toLowerCase()];
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
function hasAnyArchHint(name) {
|
|
2282
|
+
return /(arm64|aarch64|x64|x86_64|amd64)/.test(name.toLowerCase());
|
|
2283
|
+
}
|
|
2284
|
+
function getArchPriority(name, arch) {
|
|
2285
|
+
const lowered = name.toLowerCase();
|
|
2286
|
+
if (lowered.includes("universal")) {
|
|
2287
|
+
return 0;
|
|
2288
|
+
}
|
|
2289
|
+
const aliases = getArchAliases(arch);
|
|
2290
|
+
if (aliases.some((alias) => lowered.includes(alias))) {
|
|
2291
|
+
return 1;
|
|
2292
|
+
}
|
|
2293
|
+
if (hasAnyArchHint(lowered)) {
|
|
2294
|
+
return 3;
|
|
2295
|
+
}
|
|
2296
|
+
return 2;
|
|
2297
|
+
}
|
|
2298
|
+
function getFormatPriority(name) {
|
|
2299
|
+
return name.toLowerCase().endsWith(".app.tar.gz") ? 0 : 1;
|
|
2300
|
+
}
|
|
2301
|
+
function selectUruMacosReleaseAsset(assets, arch = process.arch) {
|
|
2302
|
+
const supported = assets.map((asset, index) => ({ asset, index })).filter(({ asset }) => isSupportedUruMacosArtifactName(asset.name)).sort((left, right) => {
|
|
2303
|
+
const archPriority = getArchPriority(left.asset.name, arch) - getArchPriority(right.asset.name, arch);
|
|
2304
|
+
if (archPriority !== 0) {
|
|
2305
|
+
return archPriority;
|
|
2306
|
+
}
|
|
2307
|
+
const formatPriority = getFormatPriority(left.asset.name) - getFormatPriority(right.asset.name);
|
|
2308
|
+
if (formatPriority !== 0) {
|
|
2309
|
+
return formatPriority;
|
|
2310
|
+
}
|
|
2311
|
+
return left.index - right.index;
|
|
2312
|
+
});
|
|
2313
|
+
if (supported.length === 0) {
|
|
2314
|
+
const names = assets.map((asset) => asset.name).join(", ");
|
|
2315
|
+
throw new Error(
|
|
2316
|
+
`No supported macOS archive was found in the release. Expected a .app.tar.gz or .zip asset for macOS, found: ${names}.`
|
|
2317
|
+
);
|
|
2318
|
+
}
|
|
2319
|
+
return supported[0].asset;
|
|
2320
|
+
}
|
|
2321
|
+
async function resolveUruMacosArtifact(options, env = process.env, fetchImpl = fetch, arch = process.arch) {
|
|
2322
|
+
const explicitUrl = options.artifactUrl?.trim() || env[DEFAULT_ARTIFACT_ENV_VAR]?.trim();
|
|
2323
|
+
if (explicitUrl) {
|
|
2324
|
+
return {
|
|
2325
|
+
url: explicitUrl,
|
|
2326
|
+
fileName: getArtifactFileNameFromUrl(explicitUrl),
|
|
2327
|
+
source: "explicit-url"
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
const repo = env[DEFAULT_GITHUB_REPO_ENV_VAR]?.trim() || DEFAULT_URU_GITHUB_REPO;
|
|
2331
|
+
const release = await fetchGitHubRelease(repo, options.version, fetchImpl, env);
|
|
2332
|
+
const asset = selectUruMacosReleaseAsset(release.assets, arch);
|
|
2333
|
+
return {
|
|
2334
|
+
url: asset.browserDownloadUrl,
|
|
2335
|
+
fileName: asset.name,
|
|
2336
|
+
size: asset.size,
|
|
2337
|
+
source: "github-release",
|
|
2338
|
+
repo,
|
|
2339
|
+
tagName: release.tagName
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// src/lib/prompts.ts
|
|
2344
|
+
import { createInterface } from "readline/promises";
|
|
2345
|
+
import { stdin as input, stdout as output } from "process";
|
|
2346
|
+
async function promptText(label, options = {}) {
|
|
2347
|
+
const rl = createInterface({ input, output });
|
|
2348
|
+
try {
|
|
2349
|
+
while (true) {
|
|
2350
|
+
const suffix = options.defaultValue ? ` (${options.defaultValue})` : "";
|
|
2351
|
+
const answer = (await rl.question(`${label}${suffix}: `)).trim();
|
|
2352
|
+
if (!answer && options.defaultValue) {
|
|
2353
|
+
return options.defaultValue;
|
|
2354
|
+
}
|
|
2355
|
+
if (!answer && options.required) {
|
|
2356
|
+
output.write("This value is required.\n");
|
|
2357
|
+
continue;
|
|
2358
|
+
}
|
|
2359
|
+
return answer;
|
|
2360
|
+
}
|
|
2361
|
+
} finally {
|
|
2362
|
+
rl.close();
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
async function promptConfirm(label, defaultValue = true) {
|
|
2366
|
+
const rl = createInterface({ input, output });
|
|
2367
|
+
try {
|
|
2368
|
+
const answer = (await rl.question(`${label} ${defaultValue ? "(Y/n)" : "(y/N)"}: `)).trim().toLowerCase();
|
|
2369
|
+
if (!answer) {
|
|
2370
|
+
return defaultValue;
|
|
2371
|
+
}
|
|
2372
|
+
return answer === "y" || answer === "yes";
|
|
2373
|
+
} finally {
|
|
2374
|
+
rl.close();
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
async function promptSelect(label, options, defaultValue) {
|
|
2378
|
+
const rl = createInterface({ input, output });
|
|
2379
|
+
try {
|
|
2380
|
+
output.write(`${label}
|
|
2381
|
+
`);
|
|
2382
|
+
for (const [index, option] of options.entries()) {
|
|
2383
|
+
const marker = option.value === defaultValue ? " (default)" : "";
|
|
2384
|
+
output.write(` ${index + 1}. ${option.label}${marker}
|
|
2385
|
+
`);
|
|
2386
|
+
}
|
|
2387
|
+
while (true) {
|
|
2388
|
+
const answer = (await rl.question(
|
|
2389
|
+
`Choose 1-${options.length}${defaultValue ? " (press Enter for default)" : ""}: `
|
|
2390
|
+
)).trim();
|
|
2391
|
+
if (!answer && defaultValue) {
|
|
2392
|
+
return defaultValue;
|
|
2393
|
+
}
|
|
2394
|
+
const selectedIndex = Number(answer);
|
|
2395
|
+
if (Number.isInteger(selectedIndex) && selectedIndex >= 1 && selectedIndex <= options.length) {
|
|
2396
|
+
return options[selectedIndex - 1].value;
|
|
2397
|
+
}
|
|
2398
|
+
output.write("Invalid selection.\n");
|
|
2399
|
+
}
|
|
2400
|
+
} finally {
|
|
2401
|
+
rl.close();
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// src/lib/system.ts
|
|
2406
|
+
import { execSync } from "child_process";
|
|
2407
|
+
function commandExists(command) {
|
|
2408
|
+
try {
|
|
2409
|
+
execSync(`command -v ${command}`, { stdio: "ignore" });
|
|
2410
|
+
return true;
|
|
2411
|
+
} catch {
|
|
2412
|
+
return false;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// src/apps/uru.ts
|
|
2417
|
+
var LINK_REF_FILE = join4("supabase", ".temp", "project-ref");
|
|
2418
|
+
function isUruProjectRoot(candidate) {
|
|
2419
|
+
return existsSync3(join4(candidate, "src-tauri", "tauri.conf.json")) && existsSync3(join4(candidate, "supabase", "migrations")) && existsSync3(join4(candidate, "package.json"));
|
|
2420
|
+
}
|
|
2421
|
+
function findNearestUruRoot(startDir) {
|
|
2422
|
+
let currentDir = resolve3(startDir);
|
|
2423
|
+
while (true) {
|
|
2424
|
+
if (isUruProjectRoot(currentDir)) {
|
|
2425
|
+
return currentDir;
|
|
2426
|
+
}
|
|
2427
|
+
const siblingCandidate = join4(currentDir, "uru");
|
|
2428
|
+
if (isUruProjectRoot(siblingCandidate)) {
|
|
2429
|
+
return siblingCandidate;
|
|
2430
|
+
}
|
|
2431
|
+
const parentDir = dirname3(currentDir);
|
|
2432
|
+
if (parentDir === currentDir) {
|
|
2433
|
+
return void 0;
|
|
2434
|
+
}
|
|
2435
|
+
currentDir = parentDir;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
function readEnvFile(envPath) {
|
|
2439
|
+
if (!existsSync3(envPath)) {
|
|
2440
|
+
return {};
|
|
2441
|
+
}
|
|
2442
|
+
const content = readFileSync(envPath, "utf-8");
|
|
2443
|
+
const entries = {};
|
|
2444
|
+
for (const line of content.split("\n")) {
|
|
2445
|
+
const trimmed = line.trim();
|
|
2446
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
const eqIndex = trimmed.indexOf("=");
|
|
2450
|
+
if (eqIndex === -1) {
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
2454
|
+
const value = trimmed.slice(eqIndex + 1).trim();
|
|
2455
|
+
entries[key] = value;
|
|
2456
|
+
}
|
|
2457
|
+
return entries;
|
|
2458
|
+
}
|
|
2459
|
+
function writeEnvFile(envPath, nextEnv) {
|
|
2460
|
+
const content = Object.entries(nextEnv).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
2461
|
+
writeFileSync(envPath, `${content}
|
|
2462
|
+
`);
|
|
2463
|
+
}
|
|
2464
|
+
function assertProjectRoot(projectRoot) {
|
|
2465
|
+
if (!projectRoot) {
|
|
2466
|
+
throw new Error(
|
|
2467
|
+
"Could not resolve the Uru project root. Run from the Uru repository or pass --path."
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
return projectRoot;
|
|
2471
|
+
}
|
|
2472
|
+
function getLinkedProjectRef(projectRoot) {
|
|
2473
|
+
const refPath = join4(projectRoot, LINK_REF_FILE);
|
|
2474
|
+
if (!existsSync3(refPath)) {
|
|
2475
|
+
return null;
|
|
2476
|
+
}
|
|
2477
|
+
const value = readFileSync(refPath, "utf-8").trim();
|
|
2478
|
+
return value || null;
|
|
2479
|
+
}
|
|
2480
|
+
function getDbPasswordArgs() {
|
|
2481
|
+
const password = process.env.SUPABASE_DB_PASSWORD?.trim();
|
|
2482
|
+
return password ? ["--password", password] : [];
|
|
2483
|
+
}
|
|
2484
|
+
async function ensurePrerequisites() {
|
|
2485
|
+
const checks = [
|
|
2486
|
+
{ command: "node", label: "Node.js" },
|
|
2487
|
+
{ command: "pnpm", label: "pnpm" },
|
|
2488
|
+
{ command: "supabase", label: "Supabase CLI" }
|
|
2489
|
+
];
|
|
2490
|
+
const missing = checks.filter((check) => !commandExists(check.command));
|
|
2491
|
+
if (missing.length > 0) {
|
|
2492
|
+
throw new Error(
|
|
2493
|
+
`Missing required tools: ${missing.map((item) => item.label).join(", ")}`
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
async function runOrThrow(execution, args, cwd, failureMessage) {
|
|
2498
|
+
const result = await runCommand(execution, args, cwd);
|
|
2499
|
+
if (result.spawnError || result.exitCode !== 0) {
|
|
2500
|
+
throw new Error(
|
|
2501
|
+
result.stderr.trim() || result.spawnError || failureMessage
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
async function runSupabaseOrThrow(args, cwd, failureMessage) {
|
|
2506
|
+
const result = await runSupabaseCommand(args, cwd);
|
|
2507
|
+
if (result.spawnError || result.exitCode !== 0) {
|
|
2508
|
+
throw new Error(
|
|
2509
|
+
result.stderr.trim() || result.spawnError || failureMessage
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
async function ensureSupabaseLink(projectRoot, forceRelink = false) {
|
|
2514
|
+
const linkedRef = getLinkedProjectRef(projectRoot);
|
|
2515
|
+
if (linkedRef && !forceRelink) {
|
|
2516
|
+
process.stdout.write(`${pc2.dim(`Linked project: ${linkedRef}`)}
|
|
2517
|
+
`);
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
process.stdout.write(
|
|
2521
|
+
`${pc2.dim(forceRelink ? "Relinking Supabase project..." : "Linking Supabase project...")}
|
|
2522
|
+
`
|
|
2523
|
+
);
|
|
2524
|
+
await runSupabaseOrThrow(
|
|
2525
|
+
["link", ...getDbPasswordArgs()],
|
|
2526
|
+
projectRoot,
|
|
2527
|
+
"Supabase link failed."
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
async function collectSupabaseConfig(projectRoot) {
|
|
2531
|
+
const envPath = projectRoot ? join4(projectRoot, ".env.local") : void 0;
|
|
2532
|
+
const currentEnv = envPath ? readEnvFile(envPath) : {};
|
|
2533
|
+
const currentRef = projectRoot ? getLinkedProjectRef(projectRoot) : null;
|
|
2534
|
+
const url = await promptText("Supabase URL", {
|
|
2535
|
+
defaultValue: currentEnv.VITE_SUPABASE_URL,
|
|
2536
|
+
required: true
|
|
2537
|
+
});
|
|
2538
|
+
const publishableKey = await promptText("Supabase publishable key", {
|
|
2539
|
+
defaultValue: currentEnv.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY,
|
|
2540
|
+
required: true
|
|
2541
|
+
});
|
|
2542
|
+
const projectRef = await promptText("Supabase project ref", {
|
|
2543
|
+
defaultValue: currentRef ?? "",
|
|
2544
|
+
required: true
|
|
2545
|
+
});
|
|
2546
|
+
return {
|
|
2547
|
+
url: url.trim().replace(/\/$/, ""),
|
|
2548
|
+
publishableKey: publishableKey.trim(),
|
|
2549
|
+
projectRef: projectRef.trim()
|
|
2550
|
+
};
|
|
2551
|
+
}
|
|
2552
|
+
function writeUruEnv(projectRoot, config2) {
|
|
2553
|
+
const envPath = join4(projectRoot, ".env.local");
|
|
2554
|
+
const currentEnv = readEnvFile(envPath);
|
|
2555
|
+
const nextEnv = {
|
|
2556
|
+
...currentEnv,
|
|
2557
|
+
VITE_SUPABASE_URL: config2.url,
|
|
2558
|
+
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY: config2.publishableKey
|
|
2559
|
+
};
|
|
2560
|
+
writeEnvFile(envPath, nextEnv);
|
|
2561
|
+
}
|
|
2562
|
+
function writeBootstrapPayload(config2) {
|
|
2563
|
+
const payloadPath = getUruBootstrapPayloadPath();
|
|
2564
|
+
mkdirSync(dirname3(payloadPath), { recursive: true });
|
|
2565
|
+
writeFileSync(
|
|
2566
|
+
payloadPath,
|
|
2567
|
+
JSON.stringify(
|
|
2568
|
+
{
|
|
2569
|
+
url: config2.url,
|
|
2570
|
+
publishableKey: config2.publishableKey,
|
|
2571
|
+
projectRef: config2.projectRef,
|
|
2572
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2573
|
+
source: "polterbase"
|
|
2574
|
+
},
|
|
2575
|
+
null,
|
|
2576
|
+
2
|
|
2577
|
+
)
|
|
2578
|
+
);
|
|
2579
|
+
return payloadPath;
|
|
2580
|
+
}
|
|
2581
|
+
async function promptProjectMode(options) {
|
|
2582
|
+
if (options.useExistingProject) {
|
|
2583
|
+
return "existing";
|
|
2584
|
+
}
|
|
2585
|
+
if (options.createProject) {
|
|
2586
|
+
return "create";
|
|
2587
|
+
}
|
|
2588
|
+
const selected = await promptSelect(
|
|
2589
|
+
"How should Polterbase prepare Supabase for Uru?",
|
|
2590
|
+
[
|
|
2591
|
+
{ value: "existing", label: "Use an existing Supabase project" },
|
|
2592
|
+
{ value: "create", label: "Create a new Supabase project first" }
|
|
2593
|
+
],
|
|
2594
|
+
"existing"
|
|
2595
|
+
);
|
|
2596
|
+
return selected;
|
|
2597
|
+
}
|
|
2598
|
+
async function runSetup(context) {
|
|
2599
|
+
const projectRoot = assertProjectRoot(context.projectRoot);
|
|
2600
|
+
await ensurePrerequisites();
|
|
2601
|
+
const mode = await promptProjectMode(context.options);
|
|
2602
|
+
if (mode === "create") {
|
|
2603
|
+
process.stdout.write(
|
|
2604
|
+
`${pc2.dim("Launching interactive Supabase project creation...")}
|
|
2605
|
+
`
|
|
2606
|
+
);
|
|
2607
|
+
await runSupabaseOrThrow(
|
|
2608
|
+
["projects", "create"],
|
|
2609
|
+
projectRoot,
|
|
2610
|
+
"Supabase project creation failed."
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
const config2 = await collectSupabaseConfig(projectRoot);
|
|
2614
|
+
writeUruEnv(projectRoot, config2);
|
|
2615
|
+
process.stdout.write(`${pc2.green("Saved .env.local")}
|
|
2616
|
+
`);
|
|
2617
|
+
process.stdout.write(`${pc2.dim("Installing project dependencies...")}
|
|
2618
|
+
`);
|
|
2619
|
+
await runOrThrow(
|
|
2620
|
+
"pnpm",
|
|
2621
|
+
["install", "--frozen-lockfile"],
|
|
2622
|
+
projectRoot,
|
|
2623
|
+
"Dependency installation failed."
|
|
2624
|
+
);
|
|
2625
|
+
await ensureSupabaseLink(projectRoot, context.options.relink);
|
|
2626
|
+
process.stdout.write(`${pc2.dim("Pushing migrations to linked project...")}
|
|
2627
|
+
`);
|
|
2628
|
+
await runSupabaseOrThrow(
|
|
2629
|
+
["db", "push", "--linked", ...getDbPasswordArgs()],
|
|
2630
|
+
projectRoot,
|
|
2631
|
+
"Migration push failed."
|
|
2632
|
+
);
|
|
2633
|
+
const payloadPath = writeBootstrapPayload(config2);
|
|
2634
|
+
process.stdout.write(`${pc2.green("Prepared runtime bootstrap payload")}
|
|
2635
|
+
`);
|
|
2636
|
+
process.stdout.write(`${pc2.dim(`Payload path: ${payloadPath}`)}
|
|
2637
|
+
`);
|
|
2638
|
+
return 0;
|
|
2639
|
+
}
|
|
2640
|
+
async function runLink(context) {
|
|
2641
|
+
const projectRoot = assertProjectRoot(context.projectRoot);
|
|
2642
|
+
await ensurePrerequisites();
|
|
2643
|
+
await ensureSupabaseLink(projectRoot, true);
|
|
2644
|
+
process.stdout.write(`${pc2.green("Supabase link completed")}
|
|
2645
|
+
`);
|
|
2646
|
+
return 0;
|
|
2647
|
+
}
|
|
2648
|
+
async function runMigration(context, action) {
|
|
2649
|
+
const projectRoot = assertProjectRoot(context.projectRoot);
|
|
2650
|
+
await ensurePrerequisites();
|
|
2651
|
+
switch (action) {
|
|
2652
|
+
case "local-reset":
|
|
2653
|
+
await runSupabaseOrThrow(
|
|
2654
|
+
["db", "reset", "--local"],
|
|
2655
|
+
projectRoot,
|
|
2656
|
+
"Local reset failed."
|
|
2657
|
+
);
|
|
2658
|
+
process.stdout.write(`${pc2.green("Local Supabase reset completed")}
|
|
2659
|
+
`);
|
|
2660
|
+
return 0;
|
|
2661
|
+
case "push":
|
|
2662
|
+
await ensureSupabaseLink(projectRoot, context.options.relink);
|
|
2663
|
+
await runSupabaseOrThrow(
|
|
2664
|
+
["db", "push", "--linked", ...getDbPasswordArgs()],
|
|
2665
|
+
projectRoot,
|
|
2666
|
+
"Migration push failed."
|
|
2667
|
+
);
|
|
2668
|
+
process.stdout.write(`${pc2.green("Migrations pushed")}
|
|
2669
|
+
`);
|
|
2670
|
+
return 0;
|
|
2671
|
+
case "lint":
|
|
2672
|
+
await ensureSupabaseLink(projectRoot, context.options.relink);
|
|
2673
|
+
await runSupabaseOrThrow(
|
|
2674
|
+
["db", "lint", "--linked"],
|
|
2675
|
+
projectRoot,
|
|
2676
|
+
"Migration lint failed."
|
|
2677
|
+
);
|
|
2678
|
+
process.stdout.write(`${pc2.green("Migration lint completed")}
|
|
2679
|
+
`);
|
|
2680
|
+
return 0;
|
|
2681
|
+
case "reset":
|
|
2682
|
+
await ensureSupabaseLink(projectRoot, context.options.relink);
|
|
2683
|
+
if (!context.options.yes) {
|
|
2684
|
+
const confirmed = await promptConfirm(
|
|
2685
|
+
"This will reset the linked remote database. Continue?",
|
|
2686
|
+
false
|
|
2687
|
+
);
|
|
2688
|
+
if (!confirmed) {
|
|
2689
|
+
process.stdout.write(`${pc2.yellow("Cancelled.")}
|
|
2690
|
+
`);
|
|
2691
|
+
return 0;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
await runSupabaseOrThrow(
|
|
2695
|
+
["db", "reset", "--linked", ...getDbPasswordArgs()],
|
|
2696
|
+
projectRoot,
|
|
2697
|
+
"Remote reset failed."
|
|
2698
|
+
);
|
|
2699
|
+
process.stdout.write(`${pc2.green("Remote reset completed")}
|
|
2700
|
+
`);
|
|
2701
|
+
return 0;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
async function runConfigure(context) {
|
|
2705
|
+
const projectRoot = context.projectRoot;
|
|
2706
|
+
const config2 = await collectSupabaseConfig(projectRoot);
|
|
2707
|
+
if (projectRoot) {
|
|
2708
|
+
writeUruEnv(projectRoot, config2);
|
|
2709
|
+
process.stdout.write(`${pc2.green("Updated .env.local")}
|
|
2710
|
+
`);
|
|
2711
|
+
}
|
|
2712
|
+
const payloadPath = writeBootstrapPayload(config2);
|
|
2713
|
+
process.stdout.write(`${pc2.green("Updated runtime bootstrap payload")}
|
|
2714
|
+
`);
|
|
2715
|
+
process.stdout.write(`${pc2.dim(`Payload path: ${payloadPath}`)}
|
|
2716
|
+
`);
|
|
2717
|
+
return 0;
|
|
2718
|
+
}
|
|
2719
|
+
async function downloadFile(url, destinationPath) {
|
|
2720
|
+
const response = await fetch(url);
|
|
2721
|
+
if (!response.ok) {
|
|
2722
|
+
throw new Error(`Unable to download artifact: ${response.status} ${response.statusText}`);
|
|
2723
|
+
}
|
|
2724
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
2725
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
2726
|
+
writeFileSync(destinationPath, buffer);
|
|
2727
|
+
return buffer.byteLength;
|
|
2728
|
+
}
|
|
2729
|
+
async function extractArchive(archivePath, outputDir) {
|
|
2730
|
+
if (archivePath.endsWith(".zip")) {
|
|
2731
|
+
await runOrThrow(
|
|
2732
|
+
"ditto",
|
|
2733
|
+
["-xk", archivePath, outputDir],
|
|
2734
|
+
process.cwd(),
|
|
2735
|
+
"Archive extraction failed."
|
|
2736
|
+
);
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
if (archivePath.endsWith(".tar.gz") || archivePath.endsWith(".tgz")) {
|
|
2740
|
+
await runOrThrow(
|
|
2741
|
+
"tar",
|
|
2742
|
+
["-xzf", archivePath, "-C", outputDir],
|
|
2743
|
+
process.cwd(),
|
|
2744
|
+
"Archive extraction failed."
|
|
2745
|
+
);
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
throw new Error("Unsupported artifact format. Use a .zip or .tar.gz macOS artifact.");
|
|
2749
|
+
}
|
|
2750
|
+
async function findFirstAppBundle(dir) {
|
|
2751
|
+
const entries = await readdir(dir);
|
|
2752
|
+
for (const entry of entries) {
|
|
2753
|
+
const fullPath = join4(dir, entry);
|
|
2754
|
+
const entryStat = await stat(fullPath);
|
|
2755
|
+
if (entryStat.isDirectory() && entry.endsWith(".app")) {
|
|
2756
|
+
return fullPath;
|
|
2757
|
+
}
|
|
2758
|
+
if (entryStat.isDirectory()) {
|
|
2759
|
+
const nested = await findFirstAppBundle(fullPath);
|
|
2760
|
+
if (nested) {
|
|
2761
|
+
return nested;
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
return void 0;
|
|
2766
|
+
}
|
|
2767
|
+
async function installMacosApp(context) {
|
|
2768
|
+
const artifact = await resolveUruMacosArtifact(context.options);
|
|
2769
|
+
const tempRoot = await mkdtemp(join4(tmpdir(), "polterbase-uru-"));
|
|
2770
|
+
const archivePath = join4(tempRoot, artifact.fileName);
|
|
2771
|
+
const extractDir = join4(tempRoot, "extract");
|
|
2772
|
+
mkdirSync(extractDir, { recursive: true });
|
|
2773
|
+
if (artifact.source === "github-release") {
|
|
2774
|
+
const releaseLabel = artifact.tagName ?? "latest";
|
|
2775
|
+
process.stdout.write(
|
|
2776
|
+
`${pc2.dim(`Resolved ${artifact.fileName} from ${artifact.repo} (${releaseLabel})`)}
|
|
2777
|
+
`
|
|
2778
|
+
);
|
|
2779
|
+
} else {
|
|
2780
|
+
process.stdout.write(`${pc2.dim(`Using explicit artifact URL: ${artifact.url}`)}
|
|
2781
|
+
`);
|
|
2782
|
+
}
|
|
2783
|
+
process.stdout.write(`${pc2.dim("Downloading Uru macOS artifact...")}
|
|
2784
|
+
`);
|
|
2785
|
+
const downloadedSize = await downloadFile(artifact.url, archivePath);
|
|
2786
|
+
if (artifact.size && downloadedSize !== artifact.size) {
|
|
2787
|
+
throw new Error(
|
|
2788
|
+
`Downloaded file size mismatch for ${artifact.fileName}. Expected ${artifact.size} bytes but received ${downloadedSize}.`
|
|
2789
|
+
);
|
|
2790
|
+
}
|
|
2791
|
+
process.stdout.write(`${pc2.dim("Extracting artifact...")}
|
|
2792
|
+
`);
|
|
2793
|
+
await extractArchive(archivePath, extractDir);
|
|
2794
|
+
const appBundle = await findFirstAppBundle(extractDir);
|
|
2795
|
+
if (!appBundle) {
|
|
2796
|
+
throw new Error("No .app bundle was found inside the downloaded artifact.");
|
|
2797
|
+
}
|
|
2798
|
+
const installDir = context.options.installDir ?? "/Applications";
|
|
2799
|
+
mkdirSync(installDir, { recursive: true });
|
|
2800
|
+
const destination = join4(installDir, "uru.app");
|
|
2801
|
+
if (existsSync3(destination)) {
|
|
2802
|
+
const confirmed = context.options.yes || await promptConfirm(`Replace existing installation at ${destination}?`, false);
|
|
2803
|
+
if (!confirmed) {
|
|
2804
|
+
process.stdout.write(`${pc2.yellow("Cancelled.")}
|
|
2805
|
+
`);
|
|
2806
|
+
return 0;
|
|
2807
|
+
}
|
|
2808
|
+
rmSync(destination, { recursive: true, force: true });
|
|
2809
|
+
}
|
|
2810
|
+
await runOrThrow(
|
|
2811
|
+
"cp",
|
|
2812
|
+
["-R", appBundle, destination],
|
|
2813
|
+
process.cwd(),
|
|
2814
|
+
"App copy failed."
|
|
2815
|
+
);
|
|
2816
|
+
process.stdout.write(`${pc2.green(`Installed Uru to ${destination}`)}
|
|
2817
|
+
`);
|
|
2818
|
+
await runConfigure(context);
|
|
2819
|
+
const shouldOpen = context.options.yes || await promptConfirm("Open Uru now?", true);
|
|
2820
|
+
if (shouldOpen) {
|
|
2821
|
+
await runOrThrow("open", ["-a", destination], process.cwd(), "Unable to open Uru.");
|
|
2822
|
+
}
|
|
2823
|
+
return 0;
|
|
2824
|
+
}
|
|
2825
|
+
var uruProfile = {
|
|
2826
|
+
id: "uru",
|
|
2827
|
+
displayName: "Uru",
|
|
2828
|
+
detect(startDir = process.cwd()) {
|
|
2829
|
+
return findNearestUruRoot(startDir);
|
|
2830
|
+
},
|
|
2831
|
+
resolveProjectRoot(startDir = process.cwd(), explicitPath) {
|
|
2832
|
+
if (explicitPath) {
|
|
2833
|
+
const resolved = resolve3(explicitPath);
|
|
2834
|
+
if (isUruProjectRoot(resolved)) {
|
|
2835
|
+
return resolved;
|
|
2836
|
+
}
|
|
2837
|
+
return findNearestUruRoot(resolved);
|
|
2838
|
+
}
|
|
2839
|
+
return findNearestUruRoot(startDir);
|
|
2840
|
+
},
|
|
2841
|
+
async run(action, context) {
|
|
2842
|
+
switch (action) {
|
|
2843
|
+
case "setup":
|
|
2844
|
+
return runSetup(context);
|
|
2845
|
+
case "link":
|
|
2846
|
+
return runLink(context);
|
|
2847
|
+
case "configure":
|
|
2848
|
+
return runConfigure(context);
|
|
2849
|
+
case "install":
|
|
2850
|
+
if ((context.options.platform ?? "macos") !== "macos") {
|
|
2851
|
+
throw new Error("Only --platform macos is currently supported for Uru install.");
|
|
2852
|
+
}
|
|
2853
|
+
return installMacosApp(context);
|
|
2854
|
+
case "migrate":
|
|
2855
|
+
return runMigration(
|
|
2856
|
+
context,
|
|
2857
|
+
context.options.migrationAction ?? "push"
|
|
2858
|
+
);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
};
|
|
2862
|
+
|
|
2863
|
+
// src/apps/registry.ts
|
|
2864
|
+
var profiles = [uruProfile];
|
|
2865
|
+
function getAppProfile(appId) {
|
|
2866
|
+
return profiles.find((profile) => profile.id === appId);
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
// src/apps/runAppCli.ts
|
|
2870
|
+
function assertAppCommand(options) {
|
|
2871
|
+
if (!options.action || !options.app) {
|
|
2872
|
+
throw new Error("Usage: polterbase app <setup|link|migrate|configure|install> <app>");
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
async function runAppCli(options) {
|
|
2876
|
+
assertAppCommand(options);
|
|
2877
|
+
const profile = getAppProfile(options.app);
|
|
2878
|
+
if (!profile) {
|
|
2879
|
+
throw new Error(`Unknown app profile: ${options.app}`);
|
|
2880
|
+
}
|
|
2881
|
+
const projectRoot = profile.resolveProjectRoot(process.cwd(), options.path);
|
|
2882
|
+
process.stdout.write(
|
|
2883
|
+
`${pc3.bold("Polterbase")} ${pc3.dim("app workflow")} ${pc3.bold(profile.displayName)} ${pc3.dim(`(${options.action})`)}
|
|
2884
|
+
|
|
2885
|
+
`
|
|
2886
|
+
);
|
|
2887
|
+
return profile.run(options.action, {
|
|
2888
|
+
cwd: process.cwd(),
|
|
2889
|
+
projectRoot,
|
|
2890
|
+
options
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
2893
|
+
|
|
1689
2894
|
// src/index.tsx
|
|
1690
|
-
|
|
2895
|
+
async function main() {
|
|
2896
|
+
const parsed = parseCliArgs(process.argv.slice(2));
|
|
2897
|
+
if (parsed.mode === "help") {
|
|
2898
|
+
printCliHelp();
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2901
|
+
if (parsed.mode === "app") {
|
|
2902
|
+
const exitCode = await runAppCli(parsed.options);
|
|
2903
|
+
process.exit(exitCode);
|
|
2904
|
+
}
|
|
2905
|
+
render(React8.createElement(App));
|
|
2906
|
+
}
|
|
2907
|
+
main().catch((error) => {
|
|
2908
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2909
|
+
process.stderr.write(`${message}
|
|
2910
|
+
`);
|
|
2911
|
+
process.exit(1);
|
|
2912
|
+
});
|