@polterware/polterbase 0.2.5 → 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 +58 -0
- package/dist/index.js +936 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ Polterbase is a productivity layer on top of the official `supabase` CLI. Instea
|
|
|
21
21
|
- **Built-in Self-Update**: Update Polterbase for the current repository or globally through npm
|
|
22
22
|
- **Shell Execution**: Resolves `supabase` from the current repository first, then falls back to `PATH`
|
|
23
23
|
- **TypeScript-based CLI**: Strongly typed internal implementation
|
|
24
|
+
- **App Workflows**: Explicit `polterbase app ...` flows for repository-aware setup, linking, migrations, runtime configuration, and app installation
|
|
24
25
|
|
|
25
26
|
---
|
|
26
27
|
|
|
@@ -102,6 +103,39 @@ Install Supabase CLI (official docs):
|
|
|
102
103
|
|
|
103
104
|
## Quick Reference
|
|
104
105
|
|
|
106
|
+
### App Workflows
|
|
107
|
+
|
|
108
|
+
Polterbase keeps generic Supabase execution separate from app-specific automation.
|
|
109
|
+
Use the `app` namespace when you want project-aware workflows:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
polterbase app setup uru --path .
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
polterbase app link uru --path .
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
polterbase app migrate uru push --path .
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
polterbase app configure uru --path .
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
polterbase app install uru --platform macos
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`setup uru` installs dependencies, collects Supabase connection data, links the project, pushes migrations, and writes the runtime bootstrap payload used by the desktop app.
|
|
132
|
+
|
|
133
|
+
`configure uru` refreshes the runtime connection payload without reinstalling the app.
|
|
134
|
+
|
|
135
|
+
`install uru` is currently macOS-only. By default it resolves the latest GitHub release from `polterware/uru`, accepts `--version <version>` to pin a release, and still supports `--artifact-url` or `POLTERBASE_URU_MACOS_ARTIFACT_URL` as manual overrides.
|
|
136
|
+
|
|
137
|
+
Use `POLTERBASE_URU_GITHUB_REPO=owner/repo` when you need to resolve releases from a fork or a different repository.
|
|
138
|
+
|
|
105
139
|
### Execution Model
|
|
106
140
|
|
|
107
141
|
Polterbase executes workflow commands as:
|
|
@@ -220,6 +254,30 @@ Pins are persisted locally using OS-level app config storage.
|
|
|
220
254
|
|
|
221
255
|
## Usage Examples
|
|
222
256
|
|
|
257
|
+
### Bootstrap Uru from source
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
polterbase app setup uru --path /absolute/path/to/uru
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Reconfigure an installed Uru app
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
polterbase app configure uru
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Install the latest released Uru app
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
polterbase app install uru --platform macos
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Install a specific release:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
polterbase app install uru --platform macos --version 1.0.0
|
|
279
|
+
```
|
|
280
|
+
|
|
223
281
|
### Check Supabase CLI version
|
|
224
282
|
|
|
225
283
|
Interactive path:
|
package/dist/index.js
CHANGED
|
@@ -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;
|
|
@@ -1201,14 +1201,14 @@ function FlagToggle({
|
|
|
1201
1201
|
}) {
|
|
1202
1202
|
const [cursor, setCursor] = useState6(0);
|
|
1203
1203
|
const [selected, setSelected] = useState6(/* @__PURE__ */ new Set());
|
|
1204
|
-
useInput3((
|
|
1205
|
-
if (key.upArrow ||
|
|
1204
|
+
useInput3((input2, key) => {
|
|
1205
|
+
if (key.upArrow || input2 === "k") {
|
|
1206
1206
|
setCursor((prev) => prev > 0 ? prev - 1 : flags.length - 1);
|
|
1207
1207
|
}
|
|
1208
|
-
if (key.downArrow ||
|
|
1208
|
+
if (key.downArrow || input2 === "j") {
|
|
1209
1209
|
setCursor((prev) => prev < flags.length - 1 ? prev + 1 : 0);
|
|
1210
1210
|
}
|
|
1211
|
-
if (
|
|
1211
|
+
if (input2 === " ") {
|
|
1212
1212
|
setSelected((prev) => {
|
|
1213
1213
|
const next = new Set(prev);
|
|
1214
1214
|
const flag = flags[cursor];
|
|
@@ -1326,12 +1326,12 @@ function ConfirmPrompt({
|
|
|
1326
1326
|
defaultValue = true,
|
|
1327
1327
|
onConfirm
|
|
1328
1328
|
}) {
|
|
1329
|
-
useInput4((
|
|
1330
|
-
if (
|
|
1329
|
+
useInput4((input2) => {
|
|
1330
|
+
if (input2 === "y" || input2 === "Y") {
|
|
1331
1331
|
onConfirm(true);
|
|
1332
|
-
} else if (
|
|
1332
|
+
} else if (input2 === "n" || input2 === "N") {
|
|
1333
1333
|
onConfirm(false);
|
|
1334
|
-
} else if (
|
|
1334
|
+
} else if (input2 === "\r") {
|
|
1335
1335
|
onConfirm(defaultValue);
|
|
1336
1336
|
}
|
|
1337
1337
|
});
|
|
@@ -1422,7 +1422,7 @@ function resolveSupabaseCommand(startDir = process.cwd(), env = process.env) {
|
|
|
1422
1422
|
};
|
|
1423
1423
|
}
|
|
1424
1424
|
async function runCommand(execution, args, cwd = process.cwd()) {
|
|
1425
|
-
return new Promise((
|
|
1425
|
+
return new Promise((resolve4) => {
|
|
1426
1426
|
let stdout = "";
|
|
1427
1427
|
let stderr = "";
|
|
1428
1428
|
const resolvedExecution = typeof execution === "string" ? { command: execution } : execution;
|
|
@@ -1443,7 +1443,7 @@ async function runCommand(execution, args, cwd = process.cwd()) {
|
|
|
1443
1443
|
process.stderr.write(text);
|
|
1444
1444
|
});
|
|
1445
1445
|
child.on("error", (err) => {
|
|
1446
|
-
|
|
1446
|
+
resolve4({
|
|
1447
1447
|
exitCode: null,
|
|
1448
1448
|
signal: null,
|
|
1449
1449
|
stdout,
|
|
@@ -1452,7 +1452,7 @@ async function runCommand(execution, args, cwd = process.cwd()) {
|
|
|
1452
1452
|
});
|
|
1453
1453
|
});
|
|
1454
1454
|
child.on("exit", (code, signal) => {
|
|
1455
|
-
|
|
1455
|
+
resolve4({ exitCode: code, signal, stdout, stderr });
|
|
1456
1456
|
});
|
|
1457
1457
|
});
|
|
1458
1458
|
}
|
|
@@ -1486,19 +1486,19 @@ function useCommand(execution = "supabase", cwd = process.cwd()) {
|
|
|
1486
1486
|
// src/lib/clipboard.ts
|
|
1487
1487
|
import { spawn as spawn2, exec } from "child_process";
|
|
1488
1488
|
async function openInBrowser(url) {
|
|
1489
|
-
return new Promise((
|
|
1489
|
+
return new Promise((resolve4) => {
|
|
1490
1490
|
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
|
|
1491
|
-
exec(cmd, () =>
|
|
1491
|
+
exec(cmd, () => resolve4());
|
|
1492
1492
|
});
|
|
1493
1493
|
}
|
|
1494
1494
|
async function copyToClipboard(text) {
|
|
1495
|
-
return new Promise((
|
|
1495
|
+
return new Promise((resolve4) => {
|
|
1496
1496
|
const cmd = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
|
|
1497
1497
|
const child = spawn2(cmd, [], { shell: true });
|
|
1498
1498
|
child.stdin?.write(text);
|
|
1499
1499
|
child.stdin?.end();
|
|
1500
|
-
child.on("exit", () =>
|
|
1501
|
-
child.on("error", () =>
|
|
1500
|
+
child.on("exit", () => resolve4());
|
|
1501
|
+
child.on("error", () => resolve4());
|
|
1502
1502
|
});
|
|
1503
1503
|
}
|
|
1504
1504
|
|
|
@@ -1993,5 +1993,920 @@ function App() {
|
|
|
1993
1993
|
}
|
|
1994
1994
|
}
|
|
1995
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
|
+
|
|
1996
2894
|
// src/index.tsx
|
|
1997
|
-
|
|
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
|
+
});
|