@exposify/cli 0.0.4 → 0.0.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/dist/index.js +39 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1232,7 +1232,7 @@ function copyToClipboard(text, deps = {}) {
|
|
|
1232
1232
|
|
|
1233
1233
|
// src/sign-in.ts
|
|
1234
1234
|
import { spawn } from "child_process";
|
|
1235
|
-
import { existsSync as existsSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1235
|
+
import { chmodSync as chmodSync2, existsSync as existsSync2, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1236
1236
|
import { join as join3 } from "path";
|
|
1237
1237
|
import { parseEnv as parseEnv2 } from "util";
|
|
1238
1238
|
import { authStacksOf, dependencyNames } from "@exposify/core";
|
|
@@ -1265,11 +1265,16 @@ function writeKeyToEnv(root, key) {
|
|
|
1265
1265
|
const path = join3(root, ".env");
|
|
1266
1266
|
const line = `${KEY_ENV}=${key}`;
|
|
1267
1267
|
let created = false;
|
|
1268
|
+
let tightened = false;
|
|
1268
1269
|
if (existsSync2(path)) {
|
|
1269
1270
|
const text = readFileSync6(path, "utf8");
|
|
1270
1271
|
const re = new RegExp(`^\\s*(export\\s+)?${KEY_ENV}\\s*=.*$`, "m");
|
|
1271
1272
|
const next = re.test(text) ? text.replace(re, line) : `${text}${text === "" || text.endsWith("\n") ? "" : "\n"}${line}
|
|
1272
1273
|
`;
|
|
1274
|
+
if (process.platform !== "win32" && (statSync(path).mode & 63) !== 0) {
|
|
1275
|
+
chmodSync2(path, 384);
|
|
1276
|
+
tightened = true;
|
|
1277
|
+
}
|
|
1273
1278
|
writeFileSync5(path, next);
|
|
1274
1279
|
} else {
|
|
1275
1280
|
writeFileSync5(path, `${line}
|
|
@@ -1281,7 +1286,22 @@ function writeKeyToEnv(root, key) {
|
|
|
1281
1286
|
ignored = readFileSync6(join3(root, ".gitignore"), "utf8").split("\n").map((l) => l.trim()).some((l) => [".env", "/.env", ".env*", "/.env*", "*.env"].includes(l));
|
|
1282
1287
|
} catch {
|
|
1283
1288
|
}
|
|
1284
|
-
return { path, created, ignored };
|
|
1289
|
+
return { path, created, tightened, ignored };
|
|
1290
|
+
}
|
|
1291
|
+
var DASHBOARD_HOSTS = ["dashboard.agemno.com"];
|
|
1292
|
+
function isSafeVerifyUrl(verifyUrl, apiUrl) {
|
|
1293
|
+
if (/[\s&|^<>"'`%;$\\]/.test(verifyUrl)) return false;
|
|
1294
|
+
let url;
|
|
1295
|
+
try {
|
|
1296
|
+
url = new URL(verifyUrl);
|
|
1297
|
+
} catch {
|
|
1298
|
+
return false;
|
|
1299
|
+
}
|
|
1300
|
+
const isLocal = (u) => u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "[::1]";
|
|
1301
|
+
if (!(url.protocol === "https:" || url.protocol === "http:" && isLocal(url))) return false;
|
|
1302
|
+
if (url.username || url.password) return false;
|
|
1303
|
+
const api = new URL(apiUrl);
|
|
1304
|
+
return url.host === api.host || DASHBOARD_HOSTS.includes(url.host) || isLocal(url) && isLocal(api);
|
|
1285
1305
|
}
|
|
1286
1306
|
async function signInToExposify(deps) {
|
|
1287
1307
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
@@ -1302,6 +1322,13 @@ async function signInToExposify(deps) {
|
|
|
1302
1322
|
} catch (err) {
|
|
1303
1323
|
return { ok: false, reason: "unreachable", message: `couldn't reach ${deps.apiUrl} (${err.message})` };
|
|
1304
1324
|
}
|
|
1325
|
+
if (typeof started.verifyUrl !== "string" || !isSafeVerifyUrl(started.verifyUrl, deps.apiUrl)) {
|
|
1326
|
+
return {
|
|
1327
|
+
ok: false,
|
|
1328
|
+
reason: "unsafe",
|
|
1329
|
+
message: `${deps.apiUrl} sent a sign-in link that isn't on its own host or ${DASHBOARD_HOSTS.join(", ")}, so it wasn't opened`
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1305
1332
|
const opened = (deps.openUrl ?? openInBrowser)(started.verifyUrl);
|
|
1306
1333
|
deps.show({ userCode: started.userCode, verifyUrl: started.verifyUrl, opened });
|
|
1307
1334
|
const deadline = Date.now() + started.expiresIn * 1e3;
|
|
@@ -1331,9 +1358,13 @@ async function signInToExposify(deps) {
|
|
|
1331
1358
|
}
|
|
1332
1359
|
return { ok: false, reason: "expired", message: "the sign-in link expired" };
|
|
1333
1360
|
}
|
|
1361
|
+
function browserCommand(url, platform = process.platform) {
|
|
1362
|
+
if (platform === "win32") return ["rundll32", "url.dll,FileProtocolHandler", url];
|
|
1363
|
+
return platform === "darwin" ? ["open", url] : ["xdg-open", url];
|
|
1364
|
+
}
|
|
1334
1365
|
function openInBrowser(url) {
|
|
1335
1366
|
if (process.env.EXPOSIFY_NO_BROWSER === "1") return false;
|
|
1336
|
-
const [cmd, ...args] =
|
|
1367
|
+
const [cmd, ...args] = browserCommand(url);
|
|
1337
1368
|
try {
|
|
1338
1369
|
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
1339
1370
|
child.on("error", () => {
|
|
@@ -1780,7 +1811,9 @@ ${bold("Step 2/5")} \xB7 Print "[agentready] MCP ready \u2026" when your server
|
|
|
1780
1811
|
}
|
|
1781
1812
|
const alreadyExposed = new Set(result.routes.filter((r) => r.exposed).map(routeKey));
|
|
1782
1813
|
const unchanged = result.aiReady && differences(result.routes, selected, alreadyExposed).length === 0 && (current?.startupLog ?? true) === startupLog && currentNuclear === nuclear && sameActions(current?.actions);
|
|
1783
|
-
|
|
1814
|
+
const step = adapter.configStep({ result, config, current, root, readLine: readLineAt });
|
|
1815
|
+
const pendingAlso = step.apply && "plan" in step.apply ? step.apply.also?.length ?? 0 : 0;
|
|
1816
|
+
if (unchanged && pendingAlso === 0) {
|
|
1784
1817
|
console.log(`
|
|
1785
1818
|
${green("\u2713 Already set up")}: your aiReady() config already matches. Nothing to change.`);
|
|
1786
1819
|
if (tty && !args.quick) {
|
|
@@ -1791,7 +1824,7 @@ ${green("\u2713 Already set up")}: your aiReady() config already matches. Nothin
|
|
|
1791
1824
|
await offerSnapshot(args, tty, toolRows(result.routes, result.actions), root, cwd);
|
|
1792
1825
|
return 0;
|
|
1793
1826
|
}
|
|
1794
|
-
const { code, hint, apply } =
|
|
1827
|
+
const { code, hint, apply } = step;
|
|
1795
1828
|
const shouldCopy = args.copy ?? tty;
|
|
1796
1829
|
const copied = shouldCopy ? copyToClipboard(code) : null;
|
|
1797
1830
|
const copyNote = copied?.certain ? green(" (copied to clipboard \u2713)") : copied ? dim(" (sent to your terminal's clipboard, if it supports that)") : "";
|
|
@@ -1982,6 +2015,7 @@ ${bold(title)} \xB7 How your users sign in`);
|
|
|
1982
2015
|
const written = writeKeyToEnv(root, outcome.key);
|
|
1983
2016
|
process.env[KEY_ENV] = outcome.key;
|
|
1984
2017
|
console.log(green(` \u2713 ${written.created ? "Created" : "Updated"} ${relative(cwd, written.path) || written.path} with ${KEY_ENV}`) + ` (project "${outcome.projectName}").`);
|
|
2018
|
+
if (written.tightened) console.log(dim(` .env could be read by other users on this machine; it's owner-only now.`));
|
|
1985
2019
|
if (!written.ignored) console.log(yellow(` ! Add .env to .gitignore: it now holds a secret.`));
|
|
1986
2020
|
if (own) console.log(` Your app tells Exposify about its ${sb ? "Supabase Auth" : "login"} when it starts with ${KEY_ENV}: nothing to add in the dashboard.`);
|
|
1987
2021
|
else {
|