dineway 0.1.26 → 0.1.28

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.
Files changed (71) hide show
  1. package/dist/api/route-utils.d.mts +1 -1
  2. package/dist/api/schemas/index.d.mts +1 -1
  3. package/dist/{api-CXQfCGRX.mjs → api-B1tPdBKN.mjs} +1 -1
  4. package/dist/astro/index.d.mts +1 -1
  5. package/dist/astro/index.mjs +1 -1
  6. package/dist/astro/middleware/auth.d.mts +1 -1
  7. package/dist/astro/middleware/seed.mjs +1 -1
  8. package/dist/astro/middleware.mjs +2 -2
  9. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +1 -1
  10. package/dist/astro/routes/api/admin/api-tokens/index.mjs +1 -1
  11. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +1 -1
  12. package/dist/astro/routes/api/admin/comments/_id_.mjs +1 -1
  13. package/dist/astro/routes/api/admin/comments/bulk.mjs +1 -1
  14. package/dist/astro/routes/api/admin/comments/counts.mjs +1 -1
  15. package/dist/astro/routes/api/admin/comments/index.mjs +1 -1
  16. package/dist/astro/routes/api/admin/context/_id_/history.mjs +1 -1
  17. package/dist/astro/routes/api/admin/context/_id_/index.mjs +1 -1
  18. package/dist/astro/routes/api/admin/context/_id_/review.mjs +2 -2
  19. package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +2 -2
  20. package/dist/astro/routes/api/admin/context/diff.mjs +1 -1
  21. package/dist/astro/routes/api/admin/context/index.mjs +2 -2
  22. package/dist/astro/routes/api/admin/context/stale.mjs +1 -1
  23. package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +1 -1
  24. package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +1 -1
  25. package/dist/astro/routes/api/admin/hitl-requests/index.mjs +1 -1
  26. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +1 -1
  27. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +1 -1
  28. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +1 -1
  29. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +1 -1
  30. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +1 -1
  31. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +1 -1
  32. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +1 -1
  33. package/dist/astro/routes/api/admin/plugins/index.mjs +1 -1
  34. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +1 -1
  35. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +1 -1
  36. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +1 -1
  37. package/dist/astro/routes/api/admin/plugins/updates.mjs +1 -1
  38. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +1 -1
  39. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +1 -1
  40. package/dist/astro/routes/api/auth/setup-token/index.mjs +4 -4
  41. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +1 -1
  42. package/dist/astro/routes/api/health.mjs +1 -1
  43. package/dist/astro/routes/api/manifest.mjs +1 -1
  44. package/dist/astro/routes/api/mcp.mjs +1 -1
  45. package/dist/astro/routes/api/openapi.json.mjs +1 -1
  46. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +1 -1
  47. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +1 -1
  48. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +1 -1
  49. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +1 -1
  50. package/dist/astro/routes/api/schema/collections/index.mjs +1 -1
  51. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +1 -1
  52. package/dist/astro/routes/api/schema/orphans/index.mjs +1 -1
  53. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  54. package/dist/astro/types.d.mts +1 -1
  55. package/dist/{bylines-D9TSSOe0.d.mts → bylines-Cf_VHc-Y.d.mts} +7 -7
  56. package/dist/cli/index.mjs +286 -247
  57. package/dist/client/index.d.mts +7 -1
  58. package/dist/client/index.mjs +4 -0
  59. package/dist/index.d.mts +1 -1
  60. package/dist/index.mjs +1 -1
  61. package/dist/media/local-runtime.d.mts +1 -1
  62. package/dist/plugins/adapt-sandbox-entry.d.mts +1 -1
  63. package/dist/runtime.d.mts +1 -1
  64. package/dist/version-B9lKhZ7w.mjs +6 -0
  65. package/package.json +1 -1
  66. package/dist/version-C-Dcb5bX.mjs +0 -6
  67. /package/dist/{auth-control-guard-1FHtkDP6.mjs → auth-control-guard-C-6ysFEy.mjs} +0 -0
  68. /package/dist/{comments-yTbeIYc2.mjs → comments-85tbgZQN.mjs} +0 -0
  69. /package/dist/{context-DgqEfcWz.mjs → context-cudavuns.mjs} +0 -0
  70. /package/dist/{context-route-helpers-Dq5U_AXp.mjs → context-route-helpers--PJWR-5R.mjs} +0 -0
  71. /package/dist/{hitl-requests-DaRuZ7tb.mjs → hitl-requests-BQiAzKqo.mjs} +0 -0
@@ -23,8 +23,8 @@ import "../ssrf-KAIQS48_.mjs";
23
23
  import { t as validateSeed } from "../validate-JE-WfUQ5.mjs";
24
24
  import { t as applySeed } from "../apply-TIoQ00aH.mjs";
25
25
  import { n as fingerprintKey, r as generateEncryptionKey, t as DinewaySecretsError } from "../secrets-DfeNNoLa.mjs";
26
- import { o as convertDataForRead } from "../transport-B7kO-4ee.mjs";
27
26
  import { createHeaderAwareFetch, customHeadersInterceptor, isRedirectResponse, resolveCustomHeaders } from "../client/external-auth-headers.mjs";
27
+ import { o as convertDataForRead } from "../transport-B7kO-4ee.mjs";
28
28
  import { DinewayClient } from "../client/index.mjs";
29
29
  import { LocalStorage } from "../storage/local.mjs";
30
30
  import { imageSize } from "image-size";
@@ -35,50 +35,13 @@ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from
35
35
  import { defineCommand, runCommand, runMain } from "citty";
36
36
  import consola, { consola as consola$1 } from "consola";
37
37
  import pc from "picocolors";
38
- import { access, chmod, copyFile, mkdir, readFile, readdir, rm, stat, symlink, writeFile } from "node:fs/promises";
39
38
  import { homedir } from "node:os";
39
+ import { access, chmod, copyFile, mkdir, readFile, readdir, rm, stat, symlink, writeFile } from "node:fs/promises";
40
40
  import { createInterface } from "node:readline/promises";
41
41
  import { spawn } from "node:child_process";
42
42
  import { pipeline } from "node:stream/promises";
43
43
  import { packTar } from "modern-tar/fs";
44
44
 
45
- //#region src/cli/commands/auth.ts
46
- /**
47
- * Auth CLI commands
48
- */
49
- /**
50
- * Generate a cryptographically secure auth secret
51
- */
52
- function generateAuthSecret() {
53
- const bytes = new Uint8Array(32);
54
- crypto.getRandomValues(bytes);
55
- return encodeBase64url(bytes);
56
- }
57
- const secretCommand = defineCommand({
58
- meta: {
59
- name: "secret",
60
- description: "Generate a secure auth secret"
61
- },
62
- run() {
63
- const secret = generateAuthSecret();
64
- consola$1.log("");
65
- consola$1.log(pc.bold("Generated auth secret:"));
66
- consola$1.log("");
67
- consola$1.log(` ${pc.cyan("DINEWAY_AUTH_SECRET")}=${pc.green(secret)}`);
68
- consola$1.log("");
69
- consola$1.log(pc.dim("Add this to your environment variables."));
70
- consola$1.log("");
71
- }
72
- });
73
- const authCommand = defineCommand({
74
- meta: {
75
- name: "auth",
76
- description: "Authentication utilities"
77
- },
78
- subCommands: { secret: secretCommand }
79
- });
80
-
81
- //#endregion
82
45
  //#region src/cli/credentials.ts
83
46
  /**
84
47
  * Credential storage for CLI auth tokens.
@@ -223,6 +186,79 @@ function removeMarketplaceCredential(registryUrl) {
223
186
  return false;
224
187
  }
225
188
 
189
+ //#endregion
190
+ //#region src/cli/project-env.ts
191
+ const DEFAULT_DINEWAY_URL = "http://localhost:4321";
192
+ const ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
193
+ const LINE_SPLIT_PATTERN = /\r?\n/;
194
+ const NEWLINE_PATTERN = /\r?\n/g;
195
+ function parseDotenvValue(value) {
196
+ const trimmed = value.trim();
197
+ if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
198
+ return trimmed;
199
+ }
200
+ function parseDotenv(text) {
201
+ const env = {};
202
+ for (const line of text.split(LINE_SPLIT_PATTERN)) {
203
+ const trimmed = line.trim();
204
+ if (!trimmed || trimmed.startsWith("#")) continue;
205
+ const normalized = trimmed.startsWith("export ") ? trimmed.slice(7).trim() : trimmed;
206
+ const equalsIndex = normalized.indexOf("=");
207
+ if (equalsIndex <= 0) continue;
208
+ const key = normalized.slice(0, equalsIndex).trim();
209
+ if (!ENV_KEY_PATTERN.test(key)) continue;
210
+ env[key] = parseDotenvValue(normalized.slice(equalsIndex + 1));
211
+ }
212
+ return env;
213
+ }
214
+ function readProjectEnv(cwd = process.cwd()) {
215
+ const envPath = join(cwd, ".env");
216
+ if (!existsSync(envPath)) return {};
217
+ return parseDotenv(readFileSync(envPath, "utf-8"));
218
+ }
219
+ function getProjectEnvValue(name, cwd = process.cwd()) {
220
+ return process.env[name] ?? readProjectEnv(cwd)[name];
221
+ }
222
+ function resolveDinewayBaseUrl(url, cwd = process.cwd()) {
223
+ if (url && url !== DEFAULT_DINEWAY_URL) return url;
224
+ const projectEnv = readProjectEnv(cwd);
225
+ return process.env["DINEWAY_URL"] ?? process.env["DINEWAY_SITE_URL"] ?? projectEnv["DINEWAY_SITE_URL"] ?? projectEnv["DINEWAY_URL"] ?? url ?? DEFAULT_DINEWAY_URL;
226
+ }
227
+ function resolveDinewayToken(token, cwd = process.cwd()) {
228
+ return token ?? getProjectEnvValue("DINEWAY_TOKEN", cwd);
229
+ }
230
+ function formatEnvValue(value) {
231
+ return value.replace(NEWLINE_PATTERN, "");
232
+ }
233
+ async function upsertProjectEnv(cwd, values) {
234
+ const envPath = join(cwd, ".env");
235
+ const existing = await readFile(envPath, "utf-8").catch(() => "");
236
+ const lines = existing ? existing.split(LINE_SPLIT_PATTERN) : [];
237
+ const seen = /* @__PURE__ */ new Set();
238
+ const nextLines = lines.map((line) => {
239
+ const trimmed = line.trim();
240
+ const normalized = trimmed.startsWith("export ") ? trimmed.slice(7).trim() : trimmed;
241
+ const equalsIndex = normalized.indexOf("=");
242
+ if (equalsIndex <= 0) return line;
243
+ const key = normalized.slice(0, equalsIndex).trim();
244
+ const value = values[key];
245
+ if (!(key in values) || value === void 0) return line;
246
+ seen.add(key);
247
+ return `${key}=${formatEnvValue(value)}`;
248
+ });
249
+ for (const [key, value] of Object.entries(values)) {
250
+ if (value === void 0 || seen.has(key)) continue;
251
+ nextLines.push(`${key}=${formatEnvValue(value)}`);
252
+ }
253
+ await writeFile(envPath, `${nextLines.filter((line, index) => line || index < nextLines.length - 1).join("\n")}\n`, "utf-8");
254
+ }
255
+ async function ensureProjectGitignoreEntry(cwd, entry) {
256
+ const gitignorePath = join(cwd, ".gitignore");
257
+ const existing = await readFile(gitignorePath, "utf-8").catch(() => "");
258
+ if (existing.split(LINE_SPLIT_PATTERN).map((line) => line.trim()).includes(entry)) return;
259
+ await writeFile(gitignorePath, `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${entry}\n`, "utf-8");
260
+ }
261
+
226
262
  //#endregion
227
263
  //#region src/cli/client-factory.ts
228
264
  var client_factory_exports = /* @__PURE__ */ __exportAll({
@@ -238,7 +274,7 @@ const connectionArgs = {
238
274
  type: "string",
239
275
  alias: "u",
240
276
  description: "Dineway instance URL",
241
- default: "http://localhost:4321"
277
+ default: DEFAULT_DINEWAY_URL
242
278
  },
243
279
  token: {
244
280
  type: "string",
@@ -270,8 +306,8 @@ const connectionArgs = {
270
306
  * 3. --header CLI flags
271
307
  */
272
308
  function createClientFromArgs(args) {
273
- const baseUrl = args.url || process.env["DINEWAY_URL"] || "http://localhost:4321";
274
- let token = args.token || process.env["DINEWAY_TOKEN"];
309
+ const baseUrl = resolveDinewayBaseUrl(args.url);
310
+ let token = resolveDinewayToken(args.token);
275
311
  const isLocal = baseUrl.includes("localhost") || baseUrl.includes("127.0.0.1");
276
312
  const cred = !token ? getCredentials(baseUrl) : null;
277
313
  const customHeaders = {
@@ -367,6 +403,69 @@ function prettyPrint(data, indent = 0) {
367
403
  consola$1.log(typeof data === "string" ? data : JSON.stringify(data));
368
404
  }
369
405
 
406
+ //#endregion
407
+ //#region src/cli/commands/auth.ts
408
+ /**
409
+ * Auth CLI commands
410
+ */
411
+ /**
412
+ * Generate a cryptographically secure auth secret
413
+ */
414
+ function generateAuthSecret() {
415
+ const bytes = new Uint8Array(32);
416
+ crypto.getRandomValues(bytes);
417
+ return encodeBase64url(bytes);
418
+ }
419
+ const secretCommand = defineCommand({
420
+ meta: {
421
+ name: "secret",
422
+ description: "Generate a secure auth secret"
423
+ },
424
+ run() {
425
+ const secret = generateAuthSecret();
426
+ consola$1.log("");
427
+ consola$1.log(pc.bold("Generated auth secret:"));
428
+ consola$1.log("");
429
+ consola$1.log(` ${pc.cyan("DINEWAY_AUTH_SECRET")}=${pc.green(secret)}`);
430
+ consola$1.log("");
431
+ consola$1.log(pc.dim("Add this to your environment variables."));
432
+ consola$1.log("");
433
+ }
434
+ });
435
+ const setupLinkCommand = defineCommand({
436
+ meta: {
437
+ name: "setup-link",
438
+ description: "Create a one-time admin setup link"
439
+ },
440
+ args: connectionArgs,
441
+ async run({ args }) {
442
+ configureOutputMode(args);
443
+ try {
444
+ const result = await createClientFromArgs(args).createSetupLink();
445
+ if (args.json || !process.stdout.isTTY) {
446
+ output(result, args);
447
+ return;
448
+ }
449
+ consola$1.success("Created one-time setup link");
450
+ consola$1.info(`Expires: ${result.expiresAt}`);
451
+ consola$1.log(result.setupUrl);
452
+ } catch (error) {
453
+ consola$1.error(error instanceof Error ? error.message : "Failed to create setup link");
454
+ process.exit(2);
455
+ }
456
+ }
457
+ });
458
+ const authCommand = defineCommand({
459
+ meta: {
460
+ name: "auth",
461
+ description: "Authentication utilities"
462
+ },
463
+ subCommands: {
464
+ secret: secretCommand,
465
+ "setup-link": setupLinkCommand
466
+ }
467
+ });
468
+
370
469
  //#endregion
371
470
  //#region src/cli/commands/content.ts
372
471
  /**
@@ -1541,17 +1640,6 @@ async function writeForgewayCredentials(credentials, cwd) {
1541
1640
  store.credentials = credentials;
1542
1641
  await writeStore(store, cwd);
1543
1642
  }
1544
- async function readForgewayProjectSecret(projectId, cwd) {
1545
- return (await readStore(cwd)).projects?.[projectId] ?? null;
1546
- }
1547
- async function writeForgewayProjectSecret(projectId, secret, cwd) {
1548
- const store = await readStore(cwd);
1549
- store.projects = {
1550
- ...store.projects,
1551
- [projectId]: secret
1552
- };
1553
- await writeStore(store, cwd);
1554
- }
1555
1643
  function shadowGrantKey(platformApiUrl, placeId) {
1556
1644
  return `${platformApiUrl.replace(TRAILING_SLASH_PATTERN$1, "")}#${placeId}`;
1557
1645
  }
@@ -1577,7 +1665,6 @@ async function writeForgewayShadowGrant(grant, cwd) {
1577
1665
  //#endregion
1578
1666
  //#region src/cli/commands/deploy/targets/forgeway.ts
1579
1667
  const DEFAULT_PLATFORM_API_URL = "https://api.dineway.ai";
1580
- const DEFAULT_SITE_BASE_DOMAIN = "dineway.site";
1581
1668
  const DATABASE_ENV_VAR_NAMES = ["DINEWAY_DATABASE_URL", "DINEWAY_DATABASE_AUTH_TOKEN"];
1582
1669
  const POLL_INTERVAL_MS$1 = 5e3;
1583
1670
  const POLL_TIMEOUT_MS$1 = 3e5;
@@ -1594,6 +1681,7 @@ const BACKSLASH_PATTERN = /\\/g;
1594
1681
  const DIACRITICS_PATTERN = /[\u0300-\u036f]/g;
1595
1682
  const NON_COMPARE_PATTERN = /[^\p{Letter}\p{Number}]+/gu;
1596
1683
  const LOCATION_PARTS_PATTERN = /[,;|/]+|\s+-\s+/u;
1684
+ const SHADOW_EMAIL_PATTERN = /^shadow_[a-f0-9]{16}@dineway\.ai$/i;
1597
1685
  const EXCLUDE_PATTERNS = [
1598
1686
  "node_modules",
1599
1687
  ".git",
@@ -1634,7 +1722,7 @@ function normalizeForgewayUser(user) {
1634
1722
  name: user.profile?.name ?? user.name ?? user.email,
1635
1723
  email: user.email,
1636
1724
  avatarUrl: user.profile?.avatar_url ?? user.avatarUrl ?? user.avatar_url ?? null,
1637
- emailVerified: user.emailVerified ?? user.email_verified ?? true
1725
+ emailVerified: user.emailVerified ?? user.email_verified ?? false
1638
1726
  };
1639
1727
  }
1640
1728
  function getFetch(deps) {
@@ -1676,79 +1764,35 @@ async function parseErrorResponse(response) {
1676
1764
  }
1677
1765
  return `Forgeway request failed: ${response.status}`;
1678
1766
  }
1679
- async function loginWithEmail(platformApiUrl, deps) {
1680
- const email = getEnv("FORGEWAY_EMAIL");
1681
- const password = getEnv("FORGEWAY_PASSWORD");
1682
- if (!email || !password) throw new Error("FORGEWAY_EMAIL and FORGEWAY_PASSWORD are required for admin login.");
1683
- const response = await forgewayRequest(`${platformApiUrl}/api/auth/admin/sessions`, {
1684
- method: "POST",
1685
- headers: { "Content-Type": "application/json" },
1686
- body: JSON.stringify({
1687
- email,
1688
- password
1689
- })
1690
- }, deps);
1691
- if (!response.ok) throw new Error(await parseErrorResponse(response));
1692
- const payload = await response.json();
1693
- if (!payload.accessToken || !payload.user) throw new Error("Forgeway login response was missing accessToken or user.");
1694
- return {
1695
- platformApiUrl,
1696
- accessToken: payload.accessToken,
1697
- refreshToken: payload.refreshToken,
1698
- user: normalizeForgewayUser(payload.user)
1699
- };
1767
+ function normalizeEmail(email) {
1768
+ return email.trim().toLowerCase();
1700
1769
  }
1701
- async function resolveCredentials(cwd, platformApiUrl, deps, _dryRun) {
1702
- const envToken = getEnv("FORGEWAY_ACCESS_TOKEN");
1703
- if (envToken) return {
1704
- platformApiUrl,
1705
- accessToken: envToken
1706
- };
1707
- const stored = await (deps.readCredentials ?? readForgewayCredentials)(cwd);
1708
- if (stored?.accessToken) return {
1709
- ...stored,
1710
- platformApiUrl
1711
- };
1712
- if (!getEnv("FORGEWAY_EMAIL") || !getEnv("FORGEWAY_PASSWORD")) return null;
1713
- const credentials = await loginWithEmail(platformApiUrl, deps);
1714
- await (deps.writeCredentials ?? writeForgewayCredentials)(credentials, cwd);
1715
- return credentials;
1770
+ function isShadowEmail(email) {
1771
+ return Boolean(email && SHADOW_EMAIL_PATTERN.test(email));
1716
1772
  }
1717
- async function platformFetch(path, context, deps, options = {}) {
1718
- const request = async (accessToken) => await forgewayRequest(`${context.platformApiUrl}${path}`, {
1719
- ...options,
1720
- headers: {
1721
- "Content-Type": "application/json",
1722
- Authorization: `Bearer ${accessToken}`,
1723
- ...options.headers
1724
- }
1725
- }, deps);
1726
- let response = await request(context.credentials.accessToken);
1727
- if (response.status === 401 && context.credentials.refreshToken) {
1728
- const refreshed = await refreshAccessToken(context.projectDir, context.platformApiUrl, context.credentials, deps);
1729
- context.credentials.accessToken = refreshed;
1730
- response = await request(refreshed);
1731
- }
1732
- if (!response.ok) throw new ForgewayApiError(await parseErrorResponse(response), response.status);
1733
- return await response.json();
1734
- }
1735
- async function refreshAccessToken(cwd, platformApiUrl, credentials, deps) {
1736
- if (!credentials.refreshToken) throw new Error("Forgeway refresh token is missing. Log in again.");
1737
- const response = await forgewayRequest(`${platformApiUrl}/api/auth/refresh?client_type=server`, {
1773
+ async function refreshFormalAccessToken(context, deps) {
1774
+ if (!context.refreshToken) throw new Error("Forgeway formal account refresh token is missing. Run dineway deploy again.");
1775
+ const response = await forgewayRequest(`${context.platformApiUrl}/api/auth/refresh?client_type=server`, {
1738
1776
  method: "POST",
1739
1777
  headers: { "Content-Type": "application/json" },
1740
- body: JSON.stringify({ refreshToken: credentials.refreshToken })
1778
+ body: JSON.stringify({ refreshToken: context.refreshToken })
1741
1779
  }, deps);
1742
- if (!response.ok) throw new Error("Failed to refresh Forgeway access token. Log in again.");
1780
+ if (!response.ok) throw new Error("Failed to refresh Forgeway formal account credentials. Run dineway deploy again.");
1743
1781
  const payload = await response.json();
1744
1782
  if (!payload.accessToken) throw new Error("Forgeway refresh response was missing accessToken.");
1783
+ const user = assertVerifiedFormalAccountUser(payload.user ? normalizeForgewayUser(payload.user) : context.user, context.user?.email);
1745
1784
  const updated = {
1746
- ...credentials,
1747
- platformApiUrl,
1785
+ platformApiUrl: context.platformApiUrl,
1748
1786
  accessToken: payload.accessToken,
1749
- refreshToken: payload.refreshToken ?? credentials.refreshToken
1787
+ refreshToken: payload.refreshToken ?? context.refreshToken,
1788
+ ...user ? { user } : {},
1789
+ source: "shadow-email-upgrade",
1790
+ ...context.placeId ? { placeId: context.placeId } : {}
1750
1791
  };
1751
- await (deps.writeCredentials ?? writeForgewayCredentials)(updated, cwd);
1792
+ context.accessToken = updated.accessToken;
1793
+ context.refreshToken = updated.refreshToken;
1794
+ context.user = updated.user;
1795
+ await (deps.writeCredentials ?? writeForgewayCredentials)(updated, context.projectDir);
1752
1796
  return payload.accessToken;
1753
1797
  }
1754
1798
  async function refreshShadowAccessToken(context, deps) {
@@ -1794,89 +1838,84 @@ async function promptRequired(question, initialValue, deps, errorMessage) {
1794
1838
  if (!answer) throw new Error(errorMessage);
1795
1839
  return answer;
1796
1840
  }
1797
- async function chooseSingleOrPrompt(label, items, deps) {
1798
- if (items.length === 0) throw new Error(`No Forgeway ${label}s found.`);
1799
- if (items.length === 1) return items[0];
1800
- if (!process.stdin.isTTY && !deps.promptText) throw new Error(`Multiple Forgeway ${label}s found. Set FORGEWAY_${label.toUpperCase()}_ID.`);
1801
- consola.info(`Available Forgeway ${label}s:`);
1802
- items.forEach((item, index) => consola.info(` ${index + 1}. ${item.name} (${item.id})`));
1803
- const answer = await promptRequired(`Choose ${label}: `, "1", deps, `${label} is required.`);
1804
- const index = Number(answer);
1805
- if (!Number.isInteger(index) || index < 1 || index > items.length) throw new Error(`Invalid Forgeway ${label} selection: ${answer}`);
1806
- return items[index - 1];
1807
- }
1808
1841
  function getSavedForgewayMetadata(pkg) {
1809
1842
  return pkg.dineway?.deploy?.forgeway ?? {};
1810
1843
  }
1811
- async function resolveAdminProjectContext(cwd, options, deps) {
1812
- const saved = getSavedForgewayMetadata(await readDeployPackageJson(cwd));
1813
- const platformApiUrl = normalizePlatformApiUrl(getEnv("FORGEWAY_API_URL") ?? (typeof saved.platformApiUrl === "string" ? saved.platformApiUrl : void 0));
1814
- const envApiKey = getEnv("FORGEWAY_PROJECT_API_KEY");
1815
- const envOssHost = getEnv("FORGEWAY_OSS_HOST");
1816
- if (envApiKey && envOssHost) return {
1817
- projectDir: cwd,
1818
- platformApiUrl,
1819
- authKind: "project-api-key",
1820
- projectId: getEnv("FORGEWAY_PROJECT_ID") ?? saved.projectId,
1821
- projectName: saved.projectName,
1822
- orgId: saved.orgId,
1823
- appkey: saved.appkey,
1824
- region: saved.region,
1825
- apiKey: envApiKey,
1826
- ossHost: envOssHost
1827
- };
1828
- const credentials = await resolveCredentials(cwd, platformApiUrl, deps, Boolean(options.dryRun));
1829
- if (!credentials) return null;
1830
- const platformContext = {
1831
- platformApiUrl,
1832
- projectDir: cwd,
1833
- credentials
1834
- };
1835
- let projectId = getEnv("FORGEWAY_PROJECT_ID") ?? (typeof saved.projectId === "string" ? saved.projectId : void 0);
1836
- let project = null;
1837
- if (!projectId) {
1838
- const orgId = getEnv("FORGEWAY_ORG_ID");
1839
- project = await chooseSingleOrPrompt("project", await listProjects((orgId !== void 0 ? {
1840
- id: orgId,
1841
- name: orgId
1842
- } : await chooseSingleOrPrompt("org", await listOrganizations(platformContext, deps), deps)).id, platformContext, deps), deps);
1843
- projectId = project.id;
1844
- }
1845
- if (!project) {
1846
- if (!projectId) throw new Error("Forgeway project id is required");
1847
- project = unwrapProjectPayload(await platformFetch(`/projects/v1/${encodeURIComponent(projectId)}`, platformContext, deps));
1848
- }
1849
- const savedSecret = await (deps.readProjectSecret ?? readForgewayProjectSecret)(projectId, cwd) ?? void 0;
1850
- const apiKey = getEnv("FORGEWAY_PROJECT_API_KEY") ?? savedSecret?.apiKey ?? await fetchProjectApiKey(projectId, platformContext, deps);
1851
- const orgId = project.organization_id ?? project.organizationId ?? (typeof saved.orgId === "string" ? saved.orgId : "unknown");
1852
- const ossHost = getEnv("FORGEWAY_OSS_HOST") ?? savedSecret?.ossHost ?? (typeof saved.ossHost === "string" ? saved.ossHost : void 0) ?? `https://${project.appkey}.${project.region}.${DEFAULT_SITE_BASE_DOMAIN}`;
1853
- if (!options.dryRun) {
1854
- await (deps.writeProjectSecret ?? writeForgewayProjectSecret)(projectId, {
1855
- apiKey,
1856
- ossHost
1857
- }, cwd);
1858
- await writeForgewayDeployMetadata(cwd, {
1859
- platformApiUrl,
1860
- projectId,
1861
- projectName: project.name,
1862
- orgId,
1863
- appkey: project.appkey,
1864
- region: project.region,
1865
- ossHost
1866
- });
1867
- }
1844
+ async function resolveForgewayAccountEmail(options, grant, cwd, deps) {
1845
+ const stored = await (deps.readCredentials ?? readForgewayCredentials)(cwd);
1846
+ const storedEmail = stored?.source === "shadow-email-upgrade" && stored.placeId === grant.placeId && stored.user?.emailVerified === true && !isShadowEmail(stored.user?.email) ? stored.user?.email : void 0;
1847
+ const email = normalizeEmail(options.email ?? getEnv("DINEWAY_EMAIL") ?? getEnv("FORGEWAY_EMAIL") ?? storedEmail ?? await promptRequired("Dineway account email: ", void 0, deps, "Dineway account email is required. Pass --email or set DINEWAY_EMAIL."));
1848
+ if (!email || isShadowEmail(email)) throw new Error("Dineway account email must be a real owner email, not a shadow user email.");
1849
+ return email;
1850
+ }
1851
+ function isStoredFormalCredentialForGrant(stored, grant, email) {
1852
+ if (!stored?.accessToken || stored.source !== "shadow-email-upgrade" || stored.placeId !== grant.placeId || !stored.user) return false;
1853
+ if (normalizeEmail(stored.user.email) !== email) return false;
1854
+ if (stored.user.emailVerified !== true) return false;
1855
+ if (isShadowEmail(stored.user.email)) return false;
1856
+ if (grant.user?.id && stored.user.id !== grant.user.id) return false;
1857
+ return true;
1858
+ }
1859
+ function assertVerifiedFormalAccountUser(user, expectedEmail) {
1860
+ if (!user || user.emailVerified !== true || isShadowEmail(user.email)) throw new Error("Forgeway formal account credentials require a verified formal account.");
1861
+ if (expectedEmail && normalizeEmail(user.email) !== normalizeEmail(expectedEmail)) throw new Error("Forgeway refresh returned credentials for a different email.");
1862
+ return user;
1863
+ }
1864
+ async function shadowUpgradeFetch(context, path, deps, options) {
1865
+ const request = async (accessToken) => await forgewayRequest(`${context.platformApiUrl}${path}`, {
1866
+ ...options,
1867
+ headers: {
1868
+ "Content-Type": "application/json",
1869
+ Authorization: `Bearer ${accessToken}`,
1870
+ ...options.headers
1871
+ }
1872
+ }, deps);
1873
+ if (!context.accessToken) throw new Error("Dineway shadow deploy grant is missing its access token.");
1874
+ let response = await request(context.accessToken);
1875
+ if (response.status === 401) response = await request(await refreshShadowAccessToken(context, deps));
1876
+ if (!response.ok) throw new ForgewayApiError(await parseErrorResponse(response), response.status);
1877
+ return await response.json();
1878
+ }
1879
+ async function startShadowEmailUpgrade(context, email, deps) {
1880
+ const payload = await shadowUpgradeFetch(context, "/api/auth/users/shadow/upgrade/email/start", deps, {
1881
+ method: "POST",
1882
+ body: JSON.stringify({ email })
1883
+ });
1884
+ if (payload.email && normalizeEmail(payload.email) !== email) throw new Error("Forgeway email verification response returned a different email.");
1885
+ return payload;
1886
+ }
1887
+ async function verifyShadowEmailUpgrade(context, email, otp, deps) {
1888
+ const payload = await shadowUpgradeFetch(context, "/api/auth/users/shadow/upgrade/email/verify", deps, {
1889
+ method: "POST",
1890
+ body: JSON.stringify({
1891
+ email,
1892
+ otp
1893
+ })
1894
+ });
1895
+ if (!payload.accessToken || !payload.refreshToken || !payload.user) throw new Error("Forgeway email upgrade response was missing credentials.");
1896
+ const user = normalizeForgewayUser(payload.user);
1897
+ if (normalizeEmail(user.email) !== email) throw new Error("Forgeway email upgrade returned credentials for a different email.");
1898
+ if (user.emailVerified !== true || isShadowEmail(user.email)) throw new Error("Forgeway email upgrade did not return a verified formal account.");
1868
1899
  return {
1869
- projectDir: cwd,
1870
- platformApiUrl,
1871
- authKind: "project-api-key",
1872
- projectId,
1873
- projectName: project.name,
1874
- orgId,
1875
- appkey: project.appkey,
1876
- region: project.region,
1877
- apiKey,
1878
- ossHost
1900
+ platformApiUrl: context.platformApiUrl,
1901
+ accessToken: payload.accessToken,
1902
+ refreshToken: payload.refreshToken,
1903
+ user,
1904
+ source: "shadow-email-upgrade",
1905
+ ...context.placeId ? { placeId: context.placeId } : {}
1906
+ };
1907
+ }
1908
+ async function resolveFormalAccountCredentials(context, grant, email, deps) {
1909
+ const stored = await (deps.readCredentials ?? readForgewayCredentials)(context.projectDir);
1910
+ if (isStoredFormalCredentialForGrant(stored, grant, email)) return {
1911
+ ...stored,
1912
+ platformApiUrl: context.platformApiUrl
1879
1913
  };
1914
+ await startShadowEmailUpgrade(context, email, deps);
1915
+ consola.info(`Sent Forgeway email verification code to ${email}`);
1916
+ const credentials = await verifyShadowEmailUpgrade(context, email, await promptRequired("Email verification code: ", void 0, deps, "Email verification code is required."), deps);
1917
+ await (deps.writeCredentials ?? writeForgewayCredentials)(credentials, context.projectDir);
1918
+ return credentials;
1880
1919
  }
1881
1920
  async function resolveShadowProjectContext(cwd, options, deps) {
1882
1921
  const pkg = await readDeployPackageJson(cwd).catch(() => null);
@@ -1901,37 +1940,35 @@ async function resolveShadowProjectContext(cwd, options, deps) {
1901
1940
  name: options.restaurantName ?? grant.restaurantName,
1902
1941
  city: options.city ?? grant.city
1903
1942
  };
1943
+ const shadowContext = {
1944
+ projectDir: cwd,
1945
+ platformApiUrl,
1946
+ authKind: "shadow-grant",
1947
+ accessToken: grant.accessToken,
1948
+ refreshToken: grant.refreshToken,
1949
+ user: grant.user,
1950
+ ossHost: platformApiUrl,
1951
+ placeId: grant.placeId,
1952
+ restaurantName: restaurant.name,
1953
+ city: restaurant.city
1954
+ };
1955
+ const accountEmail = await resolveForgewayAccountEmail(options, grant, cwd, deps);
1956
+ const credentials = await resolveFormalAccountCredentials(shadowContext, grant, accountEmail, deps);
1904
1957
  return {
1905
1958
  context: {
1906
- projectDir: cwd,
1907
- platformApiUrl,
1908
- authKind: "shadow-grant",
1909
- accessToken: grant.accessToken,
1910
- refreshToken: grant.refreshToken,
1911
- ossHost: platformApiUrl,
1912
- placeId: grant.placeId,
1913
- restaurantName: restaurant.name,
1914
- city: restaurant.city
1959
+ ...shadowContext,
1960
+ authKind: "formal-account",
1961
+ accessToken: credentials.accessToken,
1962
+ refreshToken: credentials.refreshToken,
1963
+ user: credentials.user,
1964
+ ossHost: platformApiUrl
1915
1965
  },
1916
1966
  restaurant
1917
1967
  };
1918
1968
  }
1919
1969
  async function resolveProjectContext(cwd, options, deps) {
1920
- const adminContext = await resolveAdminProjectContext(cwd, options, deps);
1921
- if (adminContext) return { context: adminContext };
1922
1970
  return await resolveShadowProjectContext(cwd, options, deps);
1923
1971
  }
1924
- function unwrapProjectPayload(payload) {
1925
- if (isRecord(payload) && "project" in payload) {
1926
- if (isForgewayProject(payload.project)) return payload.project;
1927
- throw new Error("Forgeway project response did not include a project");
1928
- }
1929
- if (isForgewayProject(payload)) return payload;
1930
- throw new Error("Forgeway project response did not include a project");
1931
- }
1932
- function isForgewayProject(value) {
1933
- return isRecord(value) && typeof value.id === "string" && typeof value.name === "string" && typeof value.appkey === "string" && typeof value.region === "string";
1934
- }
1935
1972
  function isRecord(value) {
1936
1973
  return typeof value === "object" && value !== null;
1937
1974
  }
@@ -2038,20 +2075,6 @@ async function createShadowGrant(options) {
2038
2075
  await (options.deps.writeShadowGrant ?? writeForgewayShadowGrant)(grant, options.cwd);
2039
2076
  return grant;
2040
2077
  }
2041
- async function listOrganizations(context, deps) {
2042
- const payload = await platformFetch("/organizations/v1", context, deps);
2043
- return Array.isArray(payload) ? payload : payload.organizations ?? [];
2044
- }
2045
- async function listProjects(orgId, context, deps) {
2046
- const payload = await platformFetch(`/organizations/v1/${encodeURIComponent(orgId)}/projects`, context, deps);
2047
- return Array.isArray(payload) ? payload : payload.projects ?? [];
2048
- }
2049
- async function fetchProjectApiKey(projectId, context, deps) {
2050
- const payload = await platformFetch(`/projects/v1/${encodeURIComponent(projectId)}/access-api-key`, context, deps);
2051
- const apiKey = payload.access_api_key ?? payload.apiKey;
2052
- if (!apiKey) throw new Error("Forgeway project API key response was empty.");
2053
- return apiKey;
2054
- }
2055
2078
  async function ossFetch(context, path, deps, options = {}) {
2056
2079
  const request = async (token) => await forgewayRequest(`${context.ossHost.replace(TRAILING_SLASH_PATTERN, "")}${path}`, {
2057
2080
  ...options,
@@ -2061,10 +2084,10 @@ async function ossFetch(context, path, deps, options = {}) {
2061
2084
  ...options.headers
2062
2085
  }
2063
2086
  }, deps);
2064
- const token = context.apiKey ?? context.accessToken;
2087
+ const token = context.accessToken;
2065
2088
  if (!token) throw new Error("Forgeway deploy context is missing an authorization token.");
2066
2089
  let response = await request(token);
2067
- if (response.status === 401 && context.authKind === "shadow-grant") response = await request(await refreshShadowAccessToken(context, deps));
2090
+ if (response.status === 401) response = await request(context.authKind === "formal-account" ? await refreshFormalAccessToken(context, deps) : await refreshShadowAccessToken(context, deps));
2068
2091
  if (!response.ok) throw new ForgewayApiError(await parseErrorResponse(response), response.status);
2069
2092
  return await response.json();
2070
2093
  }
@@ -2382,15 +2405,21 @@ async function deploySiteProject(options) {
2382
2405
  dinewayAdminBootstrap: createResult.dinewayAdminBootstrap
2383
2406
  };
2384
2407
  }
2385
- function formatDinewayAdminBootstrapMessage(bootstrap, siteUrl) {
2408
+ async function persistForgewayCliEnv(cwd, params) {
2409
+ await upsertProjectEnv(cwd, {
2410
+ DINEWAY_SITE_URL: params.siteUrl,
2411
+ DINEWAY_TOKEN: params.token
2412
+ });
2413
+ await ensureProjectGitignoreEntry(cwd, ".env");
2414
+ }
2415
+ function formatDinewayAdminBootstrapMessage(bootstrap) {
2386
2416
  if (!bootstrap) return "";
2387
2417
  if (bootstrap.status === "already_issued") return `Dineway admin bootstrap: ${bootstrap.message}`;
2388
2418
  return [
2389
- "Dineway admin bootstrap credentials were issued. Forgeway will not print these again.",
2390
- `Admin email: ${bootstrap.adminEmail}`,
2419
+ "Dineway admin access is ready. Local CLI defaults were saved to .env.",
2420
+ `Dineway admin email: ${bootstrap.adminEmail}`,
2391
2421
  `Setup link (expires ${bootstrap.setupTokenExpiresAt}): ${bootstrap.setupUrl}`,
2392
- `Admin API token: ${bootstrap.apiToken}`,
2393
- `CLI example: DINEWAY_TOKEN=${bootstrap.apiToken} dineway whoami --url ${siteUrl}`
2422
+ "CLI example: dineway whoami"
2394
2423
  ].join("\n");
2395
2424
  }
2396
2425
  async function deployForgeway(cwd, options, deps = {}) {
@@ -2429,7 +2458,11 @@ async function deployForgeway(cwd, options, deps = {}) {
2429
2458
  deps
2430
2459
  });
2431
2460
  const url = deployment.liveUrl ?? `https://${site.domain}`;
2432
- const bootstrapMessage = formatDinewayAdminBootstrapMessage(deployment.dinewayAdminBootstrap, url);
2461
+ await (deps.writeProjectEnv ?? persistForgewayCliEnv)(cwd, {
2462
+ siteUrl: url,
2463
+ token: deployment.dinewayAdminBootstrap?.status === "issued" ? deployment.dinewayAdminBootstrap.apiToken : void 0
2464
+ });
2465
+ const bootstrapMessage = formatDinewayAdminBootstrapMessage(deployment.dinewayAdminBootstrap);
2433
2466
  const statusMessage = deployment.isReady ? `Deploy complete: ${url}` : `Deploy started for ${site.domain}. Check Forgeway for build status.`;
2434
2467
  return {
2435
2468
  url,
@@ -2965,6 +2998,11 @@ const deployCommand = defineCommand({
2965
2998
  description: "Forgeway deployment site ID or slug",
2966
2999
  required: false
2967
3000
  },
3001
+ email: {
3002
+ type: "string",
3003
+ description: "Dineway account email for deploy authorization",
3004
+ required: false
3005
+ },
2968
3006
  "restaurant-name": {
2969
3007
  type: "string",
2970
3008
  description: "Restaurant name for first Forgeway site creation",
@@ -2995,6 +3033,7 @@ const deployCommand = defineCommand({
2995
3033
  yes: args.yes,
2996
3034
  dryRun: args["dry-run"],
2997
3035
  site: args.site,
3036
+ email: args.email,
2998
3037
  restaurantName: args["restaurant-name"],
2999
3038
  city: args.city,
3000
3039
  database: args.database,
@@ -3971,7 +4010,7 @@ const loginCommand = defineCommand({
3971
4010
  type: "string",
3972
4011
  alias: "u",
3973
4012
  description: "Dineway instance URL",
3974
- default: "http://localhost:4321"
4013
+ default: DEFAULT_DINEWAY_URL
3975
4014
  },
3976
4015
  header: {
3977
4016
  type: "string",
@@ -3980,7 +4019,7 @@ const loginCommand = defineCommand({
3980
4019
  }
3981
4020
  },
3982
4021
  async run({ args }) {
3983
- const baseUrl = args.url || "http://localhost:4321";
4022
+ const baseUrl = resolveDinewayBaseUrl(args.url);
3984
4023
  consola$1.start(`Connecting to ${baseUrl}...`);
3985
4024
  const customHeaders = resolveCustomHeaders();
3986
4025
  let headerFetch = createHeaderAwareFetch(customHeaders);
@@ -4092,10 +4131,10 @@ const logoutCommand = defineCommand({
4092
4131
  type: "string",
4093
4132
  alias: "u",
4094
4133
  description: "Dineway instance URL",
4095
- default: "http://localhost:4321"
4134
+ default: DEFAULT_DINEWAY_URL
4096
4135
  } },
4097
4136
  async run({ args }) {
4098
- const baseUrl = args.url || "http://localhost:4321";
4137
+ const baseUrl = resolveDinewayBaseUrl(args.url);
4099
4138
  const cred = getCredentials(baseUrl);
4100
4139
  if (!cred) {
4101
4140
  consola$1.info("No stored credentials found for this instance.");
@@ -4123,7 +4162,7 @@ const whoamiCommand = defineCommand({
4123
4162
  type: "string",
4124
4163
  alias: "u",
4125
4164
  description: "Dineway instance URL",
4126
- default: "http://localhost:4321"
4165
+ default: DEFAULT_DINEWAY_URL
4127
4166
  },
4128
4167
  token: {
4129
4168
  type: "string",
@@ -4137,8 +4176,8 @@ const whoamiCommand = defineCommand({
4137
4176
  },
4138
4177
  async run({ args }) {
4139
4178
  configureOutputMode(args);
4140
- const baseUrl = args.url || "http://localhost:4321";
4141
- let token = args.token || process.env["DINEWAY_TOKEN"];
4179
+ const baseUrl = resolveDinewayBaseUrl(args.url);
4180
+ let token = resolveDinewayToken(args.token);
4142
4181
  let authMethod = token ? "token" : "none";
4143
4182
  let storedHeaders = {};
4144
4183
  if (!token) {