void 0.7.2 → 0.7.4

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.
@@ -1,5 +1,6 @@
1
1
  import { a as isVoidSocialAuthProvider, r as getVoidAuthProviderEnvNames } from "./providers-B3aMxWzP.mjs";
2
2
  import { authContext } from "./runtime/auth.mjs";
3
+ import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
3
4
  import { betterAuth } from "better-auth";
4
5
  //#region src/runtime/better-auth-shared.ts
5
6
  function isLocalHost(hostname) {
@@ -17,8 +18,47 @@ function readEnvString(env, ...keys) {
17
18
  if (typeof value === "string" && value.length > 0) return value;
18
19
  }
19
20
  }
21
+ const PASSWORD_SALT_LENGTH = 16;
22
+ const SCRYPT_KEY_LENGTH = 64;
23
+ const SCRYPT_OPTIONS = {
24
+ N: 16384,
25
+ maxmem: 128 * 16384 * 16 * 2,
26
+ p: 1,
27
+ r: 16
28
+ };
29
+ function toHex(bytes) {
30
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
31
+ }
32
+ function derivePasswordKey(password, salt) {
33
+ return new Promise((resolve, reject) => {
34
+ scrypt(password.normalize("NFKC"), salt, SCRYPT_KEY_LENGTH, SCRYPT_OPTIONS, (error, key) => {
35
+ if (error) {
36
+ reject(error);
37
+ return;
38
+ }
39
+ resolve(key);
40
+ });
41
+ });
42
+ }
43
+ const voidAuthPassword = {
44
+ async hash(password) {
45
+ const salt = toHex(randomBytes(PASSWORD_SALT_LENGTH));
46
+ return `${salt}:${(await derivePasswordKey(password, salt)).toString("hex")}`;
47
+ },
48
+ async verify({ hash, password }) {
49
+ const [salt, key, ...extra] = hash.split(":");
50
+ if (!salt || !key || extra.length > 0) return false;
51
+ const targetKey = await derivePasswordKey(password, salt);
52
+ const storedKey = Buffer.from(key, "hex");
53
+ if (storedKey.length !== targetKey.length) return false;
54
+ return timingSafeEqual(targetKey, storedKey);
55
+ }
56
+ };
20
57
  function resolveEmailAndPassword(configuredProviders) {
21
- if (!configuredProviders || configuredProviders.includes("email")) return { enabled: true };
58
+ if (!configuredProviders || configuredProviders.includes("email")) return {
59
+ enabled: true,
60
+ password: voidAuthPassword
61
+ };
22
62
  }
23
63
  function resolveSocialProviderCredentials(env, provider) {
24
64
  const envNames = getVoidAuthProviderEnvNames(provider);
@@ -54,17 +94,27 @@ function appendAuthHeaders(target, source) {
54
94
  });
55
95
  }
56
96
  function resolveUserConfig(userConfig, defaults, request, env, dialect) {
97
+ const mergeEmailAndPasswordDefaults = (options) => {
98
+ if (!defaults.emailAndPassword?.password || !options.emailAndPassword) return options;
99
+ return {
100
+ ...options,
101
+ emailAndPassword: {
102
+ ...options.emailAndPassword,
103
+ password: options.emailAndPassword.password ?? defaults.emailAndPassword.password
104
+ }
105
+ };
106
+ };
57
107
  if (!userConfig) return defaults;
58
- if (typeof userConfig === "function") return userConfig({
108
+ if (typeof userConfig === "function") return mergeEmailAndPasswordDefaults(userConfig({
59
109
  defaults,
60
110
  request,
61
111
  env,
62
112
  dialect
63
- });
64
- return {
113
+ }));
114
+ return mergeEmailAndPasswordDefaults({
65
115
  ...defaults,
66
116
  ...userConfig
67
- };
117
+ });
68
118
  }
69
119
  function assertRuntimeDialect(runtime, dialect) {
70
120
  if (runtime.dialect !== dialect) throw new Error(`auth: Runtime mismatch. Expected ${dialect}, received '${runtime.dialect}'.`);
package/dist/cli/cli.mjs CHANGED
@@ -1503,7 +1503,7 @@ else if (command === "deploy") {
1503
1503
  R.error(err instanceof Error ? err.message : String(err));
1504
1504
  process.exit(1);
1505
1505
  }
1506
- import("../deploy-BkjqNk9U.mjs").then((n) => n.t).then(({ runDeploy }) => {
1506
+ import("../deploy-CRIz77mw.mjs").then((n) => n.t).then(({ runDeploy }) => {
1507
1507
  runDeploy(root, {
1508
1508
  projectSlug: deployArgs.projectSlug,
1509
1509
  dir: deployArgs.dir,
@@ -1519,7 +1519,7 @@ else if (command === "deploy") {
1519
1519
  else if (command === "project") runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (root, parsed) => import("../project-cmd-DKiQYdSd.mjs").then(({ runProjectCommand }) => runProjectCommand(root, parsed)));
1520
1520
  else if (command === "secret") runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (root, parsed) => import("../secret-BXHx515u.mjs").then(({ runSecretCommand }) => runSecretCommand(root, parsed)));
1521
1521
  else if (command === "domain") runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (root, parsed) => import("../domain-CDQhvYNZ.mjs").then(({ runDomainCommand }) => runDomainCommand(root, parsed)));
1522
- else if (command === "db") runSubcommandGroup("db", dbSubcommands, parseDbArgs, (root, parsed) => import("../db-ClNu7vYQ.mjs").then(({ runDbCommand }) => runDbCommand(root, parsed)));
1522
+ else if (command === "db") runSubcommandGroup("db", dbSubcommands, parseDbArgs, (root, parsed) => import("../db-RqxhOT2E.mjs").then(({ runDbCommand }) => runDbCommand(root, parsed)));
1523
1523
  else if (command === "env") runSubcommandGroup("env", envSubcommands, parseEnvArgs, (root, parsed) => import("../env-CnrQY2b6.mjs").then(({ runEnvCommand }) => runEnvCommand(root, parsed)));
1524
1524
  else if (command === "gen") runSubcommandGroup("gen", genSubcommands, parseGenArgs, (root, parsed) => import("../gen-C0EY2k27.mjs").then(({ runGenCommand }) => runGenCommand(root, parsed)));
1525
1525
  else if (command === "prepare") {
@@ -1531,7 +1531,7 @@ else if (command === "prepare") {
1531
1531
  R.error(err instanceof Error ? err.message : String(err));
1532
1532
  process.exit(1);
1533
1533
  }
1534
- import("../prepare-DKkx-2Kt.mjs").then(({ runPrepareCommand }) => {
1534
+ import("../prepare-PqAbhLte.mjs").then(({ runPrepareCommand }) => {
1535
1535
  runPrepareCommand(root, prepareArgs).catch((err) => {
1536
1536
  R.error(err.message ?? err);
1537
1537
  process.exit(1);
@@ -1539,7 +1539,7 @@ else if (command === "prepare") {
1539
1539
  });
1540
1540
  } else if (command === "init") {
1541
1541
  const root = process.cwd();
1542
- import("../init-CPny6w9D.mjs").then(({ runInitCommand, parseInitArgs }) => {
1542
+ import("../init-FMZ29UZW.mjs").then(({ runInitCommand, parseInitArgs }) => {
1543
1543
  let initArgs;
1544
1544
  try {
1545
1545
  initArgs = parseInitArgs(args.slice(1));
@@ -1628,12 +1628,12 @@ else if (command === "-v" || command === "--version") {
1628
1628
  const root = process.cwd();
1629
1629
  switch (cmd) {
1630
1630
  case "init": {
1631
- const { runInitCommand, parseInitArgs } = await import("../init-CPny6w9D.mjs");
1631
+ const { runInitCommand, parseInitArgs } = await import("../init-FMZ29UZW.mjs");
1632
1632
  await runInitCommand(root, parseInitArgs([]));
1633
1633
  return;
1634
1634
  }
1635
1635
  case "deploy": {
1636
- const { runDeploy } = await import("../deploy-BkjqNk9U.mjs").then((n) => n.t);
1636
+ const { runDeploy } = await import("../deploy-CRIz77mw.mjs").then((n) => n.t);
1637
1637
  await runDeploy(root);
1638
1638
  return;
1639
1639
  }
@@ -1646,11 +1646,11 @@ else if (command === "-v" || command === "--version") {
1646
1646
  case "project": return runSubcommandGroup("project", projectSubcommands, parseProjectArgs, (r, p) => import("../project-cmd-DKiQYdSd.mjs").then(({ runProjectCommand }) => runProjectCommand(r, p)));
1647
1647
  case "secret": return runSubcommandGroup("secret", secretSubcommands, parseSecretArgs, (r, p) => import("../secret-BXHx515u.mjs").then(({ runSecretCommand }) => runSecretCommand(r, p)));
1648
1648
  case "domain": return runSubcommandGroup("domain", domainSubcommands, parseDomainArgs, (r, p) => import("../domain-CDQhvYNZ.mjs").then(({ runDomainCommand }) => runDomainCommand(r, p)));
1649
- case "db": return runSubcommandGroup("db", dbSubcommands, parseDbArgs, (r, p) => import("../db-ClNu7vYQ.mjs").then(({ runDbCommand }) => runDbCommand(r, p)));
1649
+ case "db": return runSubcommandGroup("db", dbSubcommands, parseDbArgs, (r, p) => import("../db-RqxhOT2E.mjs").then(({ runDbCommand }) => runDbCommand(r, p)));
1650
1650
  case "env": return runSubcommandGroup("env", envSubcommands, parseEnvArgs, (r, p) => import("../env-CnrQY2b6.mjs").then(({ runEnvCommand }) => runEnvCommand(r, p)));
1651
1651
  case "gen": return runSubcommandGroup("gen", genSubcommands, parseGenArgs, (r, p) => import("../gen-C0EY2k27.mjs").then(({ runGenCommand }) => runGenCommand(r, p)));
1652
1652
  case "prepare": {
1653
- const { runPrepareCommand } = await import("../prepare-DKkx-2Kt.mjs");
1653
+ const { runPrepareCommand } = await import("../prepare-PqAbhLte.mjs");
1654
1654
  await runPrepareCommand(root, parsePrepareArgs([]));
1655
1655
  return;
1656
1656
  }
@@ -4,7 +4,7 @@ import { c as R, g as ge, y as ye } from "./dist-Dayj3gCK.mjs";
4
4
  import { r as readProjectConfig } from "./project-TqORyHn8.mjs";
5
5
  import { c as getToken, n as PlatformClient } from "./client-DCqnMpDt.mjs";
6
6
  import { c as getDatabaseDialect, f as readConfig } from "./config-BIa9HwVX.mjs";
7
- import { n as detectFramework } from "./plugin-inference-oZ6Ybu2_.mjs";
7
+ import { r as detectFramework } from "./plugin-inference-CCtRkt9O.mjs";
8
8
  import { a as writeJournal, i as stripSqlExt, r as readJournal, t as collectMigrations } from "./collect-CjeZgz5D.mjs";
9
9
  import { n as listDrizzleArtifacts, r as validateMigrations, t as assertJournalCoherence } from "./validate-CaMavMxu.mjs";
10
10
  import { formatTimestamp } from "./gen-C0EY2k27.mjs";
@@ -6,8 +6,8 @@ import { c as R, g as ge, u as Se, v as ue, x as q, y as ye } from "./dist-Dayj3
6
6
  import { a as writeProjectConfig, r as readProjectConfig } from "./project-TqORyHn8.mjs";
7
7
  import { a as isExpiredTokenError, c as getToken, i as isCliOutdatedError, l as getTokenSource, n as PlatformClient, o as parsePlatformErrorBody, t as PlatformApiError, u as isStagingMode } from "./client-DCqnMpDt.mjs";
8
8
  import { c as getDatabaseDialect, f as readConfig, l as isNodeTarget, p as resolveBindingNames } from "./config-BIa9HwVX.mjs";
9
- import { i as scanJobsSync, n as scanWebSocketRoutesSync, r as scanQueuesSync, t as scanRoutes } from "./scan-C6HMEIdW.mjs";
10
- import { c as validateSsrEntry, n as detectFramework, r as inferProjectBindings, t as FRAMEWORK_SCAN_DIRS } from "./plugin-inference-oZ6Ybu2_.mjs";
9
+ import { i as scanJobsSync, n as scanWebSocketRoutesSync, r as scanQueuesSync, t as scanRoutes } from "./scan-QtRTdh94.mjs";
10
+ import { i as inferProjectBindings, l as validateSsrEntry, n as FRAMEWORK_SCAN_DIRS, r as detectFramework } from "./plugin-inference-CCtRkt9O.mjs";
11
11
  import { i as voidWarn, r as voidLog } from "./log-DXdqnmhF.mjs";
12
12
  import { t as discoverSchema } from "./discover-B7FkXBLB.mjs";
13
13
  import { t as findEnvFile } from "./env-types-DknSA4SO.mjs";
@@ -20,15 +20,15 @@ import { n as promptProjectSelection, r as promptProjectSetupAction, t as prompt
20
20
  import { r as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-DdjLQ2tB.mjs";
21
21
  import { n as lintDuplicateSources, r as mergeRoutingRules, t as lintDestinationSplats } from "./headers-YVkHjOyq.mjs";
22
22
  import { t as promptForLoginToken } from "./login-B5HHT32i.mjs";
23
- import { i as resolveProjectCommand, n as detectPreset, r as formatProjectCommand, t as FRAMEWORK_PRESETS } from "./preset-DFvePt0l.mjs";
23
+ import { i as resolveProjectCommand, n as detectPreset, r as formatProjectCommand, t as FRAMEWORK_PRESETS } from "./preset-jKn1hQwP.mjs";
24
24
  import { createRequire } from "node:module";
25
- import { copyFileSync, cpSync, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
25
+ import { copyFileSync, cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
26
26
  import { fileURLToPath, pathToFileURL } from "node:url";
27
27
  import { build, loadEnv } from "vite";
28
- import { homedir, tmpdir } from "node:os";
29
- import { parse } from "jsonc-parser";
30
- import { execFile, execFileSync, execSync } from "node:child_process";
28
+ import { homedir } from "node:os";
31
29
  import { createHash } from "node:crypto";
30
+ import { parse } from "jsonc-parser";
31
+ import { execFile, execFileSync, execSync, spawnSync } from "node:child_process";
32
32
  import { hash } from "blake3-jit";
33
33
  import ignore from "ignore";
34
34
  const SANDBOX_MIGRATION_TAG = "void-sandbox-v1";
@@ -77,9 +77,77 @@ function resolveSandboxImage(image, root) {
77
77
  //#region src/cli/wrangler.ts
78
78
  const COMPAT_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
79
79
  const NODEJS_ALS_FLAG = "nodejs_als";
80
+ const NODEJS_COMPAT_FLAG = "nodejs_compat";
80
81
  const VOID_CONFIG_FILE = "void.json";
82
+ const DEFAULT_LIVE_BINDING = "VOID_LIVE";
83
+ const DEFAULT_LIVE_CLASS = "VoidLiveStreamDurableObject";
84
+ const LIVE_MIGRATION_TAG = "live-v1";
81
85
  /** Latest compatibility date the Void test suite is pinned against. */
82
86
  const LATEST_KNOWN_COMPAT_DATE = "2026-02-24";
87
+ function normalizeWranglerCompatibilityFlags(flags, options) {
88
+ const existingFlags = flags ?? [];
89
+ if (options?.needsNodejsCompat && !existingFlags.includes(NODEJS_COMPAT_FLAG)) return [...existingFlags.filter((flag) => flag !== NODEJS_ALS_FLAG), NODEJS_COMPAT_FLAG];
90
+ if (existingFlags.length === 0 || !existingFlags.includes(NODEJS_ALS_FLAG) && !existingFlags.includes(NODEJS_COMPAT_FLAG)) return [...existingFlags, NODEJS_ALS_FLAG];
91
+ return flags;
92
+ }
93
+ function hasDurableObjectSqliteMigration(migrations, className) {
94
+ return migrations.some((migration) => migration.new_sqlite_classes?.includes(className));
95
+ }
96
+ function nextDurableObjectMigrationTag(migrations, baseTag) {
97
+ const existingTags = new Set(migrations.map((migration) => migration.tag).filter(Boolean));
98
+ if (!existingTags.has(baseTag)) return baseTag;
99
+ let index = 2;
100
+ while (existingTags.has(`${baseTag}-${index}`)) index++;
101
+ return `${baseTag}-${index}`;
102
+ }
103
+ function resolveLiveBindingConfig(root, options) {
104
+ const durableObjectBindings = [...readWranglerDurableObjectBindings(root), ...options?.existingBindings ?? []];
105
+ const generated = findGeneratedLiveBinding(durableObjectBindings);
106
+ if (generated) return {
107
+ bindingName: generated.name,
108
+ className: generated.class_name
109
+ };
110
+ const usedBindings = new Set([...durableObjectBindings.map((binding) => binding.name), ...(options?.reservedBindings ?? []).filter((name) => typeof name === "string")]);
111
+ const usedClasses = new Set([...durableObjectBindings.map((binding) => binding.class_name), ...(options?.reservedClasses ?? []).filter((name) => typeof name === "string")]);
112
+ return {
113
+ bindingName: resolveGeneratedName(DEFAULT_LIVE_BINDING, usedBindings, "binding"),
114
+ className: resolveGeneratedName(DEFAULT_LIVE_CLASS, usedClasses, "class")
115
+ };
116
+ }
117
+ function readExistingLiveBindingConfig(root) {
118
+ const generated = findGeneratedLiveBinding(readWranglerDurableObjectBindings(root));
119
+ return generated ? {
120
+ bindingName: generated.name,
121
+ className: generated.class_name
122
+ } : void 0;
123
+ }
124
+ function readWranglerDurableObjectBindings(root) {
125
+ for (const name of ["wrangler.jsonc", "wrangler.json"]) try {
126
+ const bindings = parseJsonc(readFileSync(join(root, name), "utf-8")).durable_objects?.bindings;
127
+ if (!Array.isArray(bindings)) return [];
128
+ return bindings.filter((binding) => typeof binding.name === "string" && typeof binding.class_name === "string");
129
+ } catch {}
130
+ return [];
131
+ }
132
+ function isGeneratedLiveName(name, base) {
133
+ return name === base || name.match(generatedLiveNamePattern(base)) !== null;
134
+ }
135
+ function generatedLiveNamePattern(base) {
136
+ return new RegExp(`^${escapeRegExp(base)}_[0-9a-f]{8}$`);
137
+ }
138
+ function escapeRegExp(value) {
139
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
140
+ }
141
+ function findGeneratedLiveBinding(bindings) {
142
+ return bindings.find((binding) => isGeneratedLiveName(binding.name, "VOID_LIVE") && isGeneratedLiveName(binding.class_name, "VoidLiveStreamDurableObject"));
143
+ }
144
+ function resolveGeneratedName(base, used, kind) {
145
+ if (!used.has(base)) return base;
146
+ for (let attempt = 0;; attempt++) {
147
+ const candidate = `${base}_${createHash("sha256").update(`void/live:${kind}:${base}:${attempt}`).digest("hex").slice(0, 8)}`;
148
+ if (!used.has(candidate)) return candidate;
149
+ }
150
+ }
83
151
  /**
84
152
  * Read compatibility settings from `wrangler.jsonc`/`wrangler.json` and
85
153
  * `void.json`, with priority: void.json > wrangler.jsonc > build output.
@@ -271,6 +339,29 @@ function syncWranglerBindings(root, bindings, options) {
271
339
  modified = true;
272
340
  }
273
341
  }
342
+ if (bindings.needsLive || options?.live) {
343
+ const existingDurableObjectBindings = parsed.durable_objects?.bindings ?? [];
344
+ const liveConfig = typeof options?.live === "object" ? options.live : resolveLiveBindingConfig(root, {
345
+ existingBindings: existingDurableObjectBindings,
346
+ reservedBindings: options?.websockets?.map((route) => route.bindingName),
347
+ reservedClasses: [...options?.websockets?.map((route) => route.className) ?? [], options?.sandbox?.className]
348
+ });
349
+ if (!existingDurableObjectBindings.some((binding) => binding.name === liveConfig.bindingName)) {
350
+ parsed.durable_objects = { bindings: [...existingDurableObjectBindings, {
351
+ name: liveConfig.bindingName,
352
+ class_name: liveConfig.className
353
+ }] };
354
+ modified = true;
355
+ }
356
+ const migrations = Array.isArray(parsed.migrations) ? parsed.migrations : [];
357
+ if (!hasDurableObjectSqliteMigration(migrations, liveConfig.className)) {
358
+ parsed.migrations = [...migrations, {
359
+ tag: nextDurableObjectMigrationTag(migrations, LIVE_MIGRATION_TAG),
360
+ new_sqlite_classes: [liveConfig.className]
361
+ }];
362
+ modified = true;
363
+ }
364
+ }
274
365
  if (created || modified) writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n");
275
366
  return created || modified;
276
367
  }
@@ -1259,6 +1350,7 @@ function toSlash(p) {
1259
1350
  //#endregion
1260
1351
  //#region src/cli/package.ts
1261
1352
  const textEncoder = new TextEncoder();
1353
+ const FRAMEWORK_LIVE_UNSUPPORTED_MESSAGE = "deploy: void/live is currently supported in Void apps only.";
1262
1354
  /**
1263
1355
  * Strip dev-only / internal-only fields from each rule before shipping the
1264
1356
  * manifest. The resulting entries contain only `{ source, destination, status }`.
@@ -1528,6 +1620,20 @@ function toSandboxManifest(sandbox) {
1528
1620
  ...sandbox.maxInstances != null && { maxInstances: sandbox.maxInstances }
1529
1621
  };
1530
1622
  }
1623
+ function toLiveManifest(live) {
1624
+ const bindingName = live?.bindingName ?? "VOID_LIVE";
1625
+ const className = live?.className ?? "VoidLiveStreamDurableObject";
1626
+ return {
1627
+ bindingName,
1628
+ className,
1629
+ storage: "sqlite",
1630
+ classes: [className],
1631
+ migrations: [{
1632
+ tag: LIVE_MIGRATION_TAG,
1633
+ newSqliteClasses: [className]
1634
+ }]
1635
+ };
1636
+ }
1531
1637
  /**
1532
1638
  * If the user configured `routing.fallbacks` on a non-SPA app, the platform
1533
1639
  * still applies the rules but no `/* -> /index.html` SPA shell is synthesised
@@ -1570,6 +1676,7 @@ function buildVoidManifest(opts) {
1570
1676
  binding: options.bindingNames?.sandbox ?? options.sandbox.binding
1571
1677
  });
1572
1678
  }
1679
+ if (bindings.needsLive) manifest.live = toLiveManifest(options?.live);
1573
1680
  if (migrations && migrations.length > 0) manifest.migrations = {
1574
1681
  dialect: dialect ?? "sqlite",
1575
1682
  pending: migrations.length
@@ -1663,6 +1770,7 @@ function buildFrameworkManifest(opts) {
1663
1770
  if (opts.bindings.needsKV) manifest.bindings.kv = [po?.bindingNames?.kv ?? "KV"];
1664
1771
  if (opts.bindings.needsR2) manifest.bindings.r2 = [po?.bindingNames?.r2 ?? "STORAGE"];
1665
1772
  if (opts.bindings.needsAI) manifest.bindings.ai = true;
1773
+ if (opts.bindings.needsLive) throw new Error(FRAMEWORK_LIVE_UNSUPPORTED_MESSAGE);
1666
1774
  if (opts.migrations && opts.migrations.length > 0) manifest.migrations = {
1667
1775
  dialect: opts.dialect ?? "sqlite",
1668
1776
  pending: opts.migrations.length
@@ -1958,9 +2066,11 @@ function generateHandlersSource(root, features) {
1958
2066
  //#region src/cli/deploy.ts
1959
2067
  var deploy_exports = /* @__PURE__ */ __exportAll({
1960
2068
  applyDeployEvent: () => applyDeployEvent,
2069
+ ensurePreviouslyDeployedLiveBindingConfig: () => ensurePreviouslyDeployedLiveBindingConfig,
1961
2070
  hasDeploySchemaDrift: () => hasDeploySchemaDrift,
1962
2071
  promptDeployProjectConfig: () => promptDeployProjectConfig,
1963
2072
  resolveAssetConfig: () => resolveAssetConfig,
2073
+ resolveDeployLiveBindingConfig: () => resolveDeployLiveBindingConfig,
1964
2074
  resolveDeployWranglerCompat: () => resolveDeployWranglerCompat,
1965
2075
  resolveDistDir: () => resolveDistDir,
1966
2076
  resolveStaticBuildCommand: () => resolveStaticBuildCommand,
@@ -1972,9 +2082,55 @@ var deploy_exports = /* @__PURE__ */ __exportAll({
1972
2082
  const drizzleKitBin = join(fileURLToPath(import.meta.resolve("drizzle-kit")), "..", "bin.cjs");
1973
2083
  const deployDriftCheckDir = join(".void", "deploy-drift-check");
1974
2084
  var DeployEventError = class extends Error {};
2085
+ const EMPTY_BINDINGS = {
2086
+ needsD1: false,
2087
+ needsKV: false,
2088
+ needsR2: false,
2089
+ needsAuth: false,
2090
+ needsQueues: false,
2091
+ needsAI: false,
2092
+ needsSandbox: false
2093
+ };
1975
2094
  function getErrorMessage(error) {
1976
2095
  return error instanceof Error ? error.message : String(error);
1977
2096
  }
2097
+ function readLiveBindingConfigFromDeployment(deployment) {
2098
+ if (!deployment?.manifest) return;
2099
+ let manifest;
2100
+ try {
2101
+ manifest = JSON.parse(deployment.manifest);
2102
+ } catch {
2103
+ return;
2104
+ }
2105
+ if (typeof manifest.live?.bindingName !== "string" || typeof manifest.live.className !== "string") return;
2106
+ return {
2107
+ bindingName: manifest.live.bindingName,
2108
+ className: manifest.live.className
2109
+ };
2110
+ }
2111
+ async function ensurePreviouslyDeployedLiveBindingConfig(root, client, projectId, cliLog) {
2112
+ if (readExistingLiveBindingConfig(root)) return false;
2113
+ const { deployments, activeDeploymentId } = await client.listDeployments(projectId);
2114
+ const live = readLiveBindingConfigFromDeployment(deployments.find((deployment) => deployment.id === activeDeploymentId));
2115
+ if (!live) return false;
2116
+ const synced = syncWranglerBindings(root, {
2117
+ ...EMPTY_BINDINGS,
2118
+ needsLive: true
2119
+ }, { live });
2120
+ if (synced) cliLog?.info("live_binding_restored", {
2121
+ bindingName: live.bindingName,
2122
+ className: live.className
2123
+ });
2124
+ return synced;
2125
+ }
2126
+ async function resolveDeployLiveBindingConfig(root, client, projectId, opts) {
2127
+ if (opts.isFrameworkMode || !opts.needsLive) return;
2128
+ await ensurePreviouslyDeployedLiveBindingConfig(root, client, projectId, opts.cliLog);
2129
+ return resolveLiveBindingConfig(root, {
2130
+ reservedBindings: opts.webSockets.map((route) => route.bindingName),
2131
+ reservedClasses: [...opts.webSockets.map((route) => route.className), opts.sandboxClassName]
2132
+ });
2133
+ }
1978
2134
  /**
1979
2135
  * Resolve the Cloudflare Workers Static Assets `not_found_handling` config.
1980
2136
  *
@@ -1997,8 +2153,14 @@ function getExitCode(error) {
1997
2153
  if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") return error.status;
1998
2154
  return 1;
1999
2155
  }
2000
- function resolveDeployWranglerCompat(root, config, buildOutputDir) {
2001
- return isNodeTarget(config.target) ? void 0 : readWranglerCompat(root, config, buildOutputDir);
2156
+ function resolveDeployWranglerCompat(root, config, buildOutputDir, requirements) {
2157
+ if (isNodeTarget(config.target)) return;
2158
+ const compat = readWranglerCompat(root, config, buildOutputDir);
2159
+ const compatibilityFlags = normalizeWranglerCompatibilityFlags(compat.compatibilityFlags, { needsNodejsCompat: requirements?.authEnabled === true || requirements?.dialect === "postgresql" || requirements?.needsSandbox === true });
2160
+ return compatibilityFlags === compat.compatibilityFlags ? compat : {
2161
+ ...compat,
2162
+ compatibilityFlags
2163
+ };
2002
2164
  }
2003
2165
  /**
2004
2166
  * Run the full deploy pipeline:
@@ -2489,6 +2651,10 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
2489
2651
  R.error("deploy: Void-managed Better Auth is only supported in Void apps. Use Better Auth's official integration for this framework instead.");
2490
2652
  process.exit(1);
2491
2653
  }
2654
+ if (bindings.needsLive) {
2655
+ R.error("deploy: void/live is currently supported in Void apps only.");
2656
+ process.exit(1);
2657
+ }
2492
2658
  const migrationsDir = join(root, "db", "migrations");
2493
2659
  assertDeploySchemaDriftFree(root, dialect);
2494
2660
  const rawMigrations = collectMigrations(migrationsDir);
@@ -2548,7 +2714,7 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
2548
2714
  await generateWrapper(actualWorkerDir, actualWorkerMain, root, wrapperFeatures);
2549
2715
  R.info("Wrapper generated");
2550
2716
  }
2551
- const wranglerCompat = readWranglerCompat(root, voidConfig, workerDir);
2717
+ const wranglerCompat = resolveDeployWranglerCompat(root, voidConfig, workerDir, { dialect });
2552
2718
  const revalidate = voidConfig.routing?.revalidate;
2553
2719
  const prerender = voidConfig.routing?.prerender;
2554
2720
  const schedules = rawJobs.flatMap((job) => job.crons.map((cron) => ({
@@ -2587,8 +2753,8 @@ async function runFrameworkDeploy(root, config, client, detected, fwPreset, void
2587
2753
  const packageOptions = {
2588
2754
  bindingNames: resolveBindingNames(voidConfig.inference?.bindings),
2589
2755
  workerMain: actualWorkerMain,
2590
- compatibilityDate: wranglerCompat.compatibilityDate,
2591
- compatibilityFlags: wranglerCompat.compatibilityFlags
2756
+ compatibilityDate: wranglerCompat?.compatibilityDate,
2757
+ compatibilityFlags: wranglerCompat?.compatibilityFlags
2592
2758
  };
2593
2759
  const manifest = buildFrameworkManifest({
2594
2760
  frameworkName: detected.name,
@@ -2683,6 +2849,10 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
2683
2849
  R.error("deploy: Cloudflare Sandboxes are currently supported in Void apps only.");
2684
2850
  process.exit(1);
2685
2851
  }
2852
+ if (isFrameworkMode && bindings.needsLive) {
2853
+ R.error("deploy: void/live is currently supported in Void apps only.");
2854
+ process.exit(1);
2855
+ }
2686
2856
  const sandboxConfig = bindings.needsSandbox ? resolveSandboxConfig(deployConfig, root) : void 0;
2687
2857
  if (bindings.needsSandbox && requiresSandboxPlatformImage(deployConfig)) {
2688
2858
  R.error("deploy: `sandbox.image` points at a local Dockerfile. Add `sandbox.platformImage` with a pushed registry image for `void deploy`, or deploy this sandbox directly with Wrangler.");
@@ -2740,6 +2910,13 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
2740
2910
  })));
2741
2911
  const routeScan = await scanRoutes(root, { output: deployConfig.output });
2742
2912
  const webSockets = routeScan.websockets ?? scanWebSocketRoutesSync(root);
2913
+ const liveConfig = await resolveDeployLiveBindingConfig(root, client, config.projectId, {
2914
+ isFrameworkMode,
2915
+ needsLive: effectiveBindings.needsLive,
2916
+ webSockets,
2917
+ sandboxClassName: sandboxConfig?.className,
2918
+ cliLog
2919
+ });
2743
2920
  const queues = scanQueuesSync(root).map((q) => ({
2744
2921
  name: q.name,
2745
2922
  bindingName: q.bindingName,
@@ -2763,7 +2940,7 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
2763
2940
  else revalidate = perPageRules;
2764
2941
  }
2765
2942
  if (deployConfig.output === "static" && pageScan && !isSsr) {
2766
- if (pageScan.pages.every((p) => p.prerender && (p.params.length === 0 && !p.catchAll || p.hasGetPrerenderPaths)) && !(hasRoutes || hasMiddleware || hasWebSockets || authEnabled) && schedules.length === 0 && queues.length === 0) {
2943
+ if (pageScan.pages.every((p) => p.prerender && (p.params.length === 0 && !p.catchAll || p.hasGetPrerenderPaths)) && !(hasRoutes || hasMiddleware || hasWebSockets || authEnabled || liveConfig !== void 0) && schedules.length === 0 && queues.length === 0) {
2767
2944
  R.info("All pages prerendered — deploying as static site");
2768
2945
  cliLog?.info("full_to_static_redirect", { reason: "all_pages_prerendered" });
2769
2946
  return runStaticDeploy(root, config, client, {
@@ -2785,7 +2962,11 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
2785
2962
  remoteMode: resolveRemoteMode(deployConfig.remote),
2786
2963
  projectId: config.projectId
2787
2964
  });
2788
- const wranglerCompat = resolveDeployWranglerCompat(root, deployConfig, join(distDir, workerDirName));
2965
+ const wranglerCompat = resolveDeployWranglerCompat(root, deployConfig, join(distDir, workerDirName), {
2966
+ authEnabled,
2967
+ dialect,
2968
+ needsSandbox: effectiveBindings.needsSandbox
2969
+ });
2789
2970
  const envVars = loadDeployEnv(root);
2790
2971
  await assertEnvSchemaValid(root, client, config.projectId);
2791
2972
  await applySchemaDefaultsToVars(root, envVars);
@@ -2826,6 +3007,7 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
2826
3007
  compatibilityDate: wranglerCompat?.compatibilityDate,
2827
3008
  compatibilityFlags: wranglerCompat?.compatibilityFlags,
2828
3009
  ...sandboxConfig && { sandbox: sandboxConfig },
3010
+ ...liveConfig && { live: liveConfig },
2829
3011
  revalidateQueryAllowlist: deployConfig.routing?.revalidateQueryAllowlist
2830
3012
  };
2831
3013
  const fallbackWarning = detectNonSpaFallbackWarning(assetConfig, fallbackRules);
@@ -2834,7 +3016,10 @@ async function runFullDeploy(root, config, client, fwPreset, skipBuild, gitMetad
2834
3016
  console.warn(`[void] ${fallbackWarning}`);
2835
3017
  }
2836
3018
  const manifest = buildVoidManifest({
2837
- bindings: effectiveBindings,
3019
+ bindings: liveConfig ? {
3020
+ ...effectiveBindings,
3021
+ needsLive: true
3022
+ } : effectiveBindings,
2838
3023
  migrations: validatedMigrations,
2839
3024
  schedules,
2840
3025
  ssr: isSsr || hasRoutes || hasMiddleware || hasWebSockets || authEnabled,
@@ -2918,28 +3103,16 @@ function assertDeploySchemaDriftFree(root, dialect) {
2918
3103
  }
2919
3104
  function hasDeploySchemaDrift(root, dialect) {
2920
3105
  if (!discoverSchema(root)) return false;
2921
- const driftRoot = mkDeployDriftCheckRoot();
3106
+ const driftRoot = mkDeployDriftCheckRoot(root);
2922
3107
  const driftProjectRoot = join(driftRoot, "project");
2923
- rmSync(driftRoot, {
2924
- recursive: true,
2925
- force: true
2926
- });
2927
- mkdirSync(driftRoot, { recursive: true });
2928
3108
  createDeployDriftProject(root, driftProjectRoot);
2929
3109
  try {
2930
3110
  const migrationsDir = join(driftProjectRoot, "db", "migrations");
2931
3111
  const before = snapshotDir(migrationsDir);
2932
- const driftConfigPath = writeDrizzleConfig(driftProjectRoot, dialect === "postgresql" ? "postgresql://dummy" : ":memory:", dialect);
2933
- execFileSync(process.execPath, [
2934
- drizzleKitBin,
2935
- "generate",
2936
- "--config",
2937
- driftConfigPath
2938
- ], {
2939
- cwd: driftProjectRoot,
2940
- stdio: "pipe"
2941
- });
2942
- return !migrationSnapshotsEqual(before, snapshotDir(migrationsDir));
3112
+ const output = runDrizzleKitGenerate(driftProjectRoot, writeDrizzleConfig(driftProjectRoot, dialect === "postgresql" ? "postgresql://dummy" : ":memory:", dialect));
3113
+ const snapshotsEqual = migrationSnapshotsEqual(before, snapshotDir(migrationsDir));
3114
+ if (snapshotsEqual && !output.includes("No schema changes, nothing to migrate")) throw new Error(output || "drizzle-kit generate finished without reporting schema status.");
3115
+ return !snapshotsEqual;
2943
3116
  } catch (error) {
2944
3117
  throw new Error(formatDeploySchemaDriftCheckError(error));
2945
3118
  } finally {
@@ -2947,8 +3120,26 @@ function hasDeploySchemaDrift(root, dialect) {
2947
3120
  recursive: true,
2948
3121
  force: true
2949
3122
  });
3123
+ cleanupDeployDriftCheckRoot(root);
2950
3124
  }
2951
3125
  }
3126
+ function runDrizzleKitGenerate(root, configPath) {
3127
+ const result = spawnSync(process.execPath, [
3128
+ drizzleKitBin,
3129
+ "generate",
3130
+ "--config",
3131
+ configPath
3132
+ ], {
3133
+ cwd: root,
3134
+ encoding: "utf-8"
3135
+ });
3136
+ const stderr = bufferToString(result.stderr);
3137
+ const output = [stderr, bufferToString(result.stdout)].filter(Boolean).join("\n").trim();
3138
+ if (result.error) throw result.error;
3139
+ if (stderr) throw new Error(output);
3140
+ if (result.status !== 0) throw new Error(output || `drizzle-kit generate exited with code ${result.status}.`);
3141
+ return output;
3142
+ }
2952
3143
  function getDeploySchemaDriftMessage() {
2953
3144
  return ["Schema drift detected between your Drizzle schema and db/migrations/.", "Run `void db generate`, review the generated migration, commit it yourself, then rerun `void deploy`."].join("\n");
2954
3145
  }
@@ -2967,6 +3158,8 @@ function extractExecErrorOutput(error) {
2967
3158
  const stdout = "stdout" in error ? bufferToString(error.stdout) : "";
2968
3159
  if (stderr) details.push(stderr);
2969
3160
  if (stdout) details.push(stdout);
3161
+ const message = "message" in error ? bufferToString(error.message) : "";
3162
+ if (details.length === 0 && message) details.push(message);
2970
3163
  return details.join("\n").trim();
2971
3164
  }
2972
3165
  function bufferToString(value) {
@@ -2979,32 +3172,26 @@ function createDeployDriftProject(root, driftProjectRoot) {
2979
3172
  for (const entry of readdirSync(root, { withFileTypes: true })) {
2980
3173
  if (entry.name === ".git" || entry.name === ".void" || entry.name === "node_modules") continue;
2981
3174
  if (entry.name === "dist" || entry.name === "build" || entry.name === ".wrangler" || entry.name === ".turbo" || entry.name === ".next" || entry.name === ".svelte-kit" || entry.name === ".output") continue;
2982
- const source = join(root, entry.name);
2983
- const target = join(driftProjectRoot, entry.name);
2984
- if (entry.name === "db") {
2985
- cpSync(source, target, { recursive: true });
2986
- continue;
2987
- }
2988
- symlinkSync(source, target, entry.isDirectory() ? symlinkDirType() : "file");
2989
- }
2990
- const nodeModules = findNearestNodeModules(root);
2991
- if (nodeModules) symlinkSync(nodeModules, join(driftProjectRoot, "node_modules"), symlinkDirType());
2992
- }
2993
- function findNearestNodeModules(root) {
2994
- let current = root;
2995
- while (true) {
2996
- const candidate = join(current, "node_modules");
2997
- if (existsSync(candidate)) return candidate;
2998
- const parent = resolve(current, "..");
2999
- if (parent === current) return null;
3000
- current = parent;
3175
+ cpSync(join(root, entry.name), join(driftProjectRoot, entry.name), { recursive: true });
3001
3176
  }
3002
3177
  }
3003
- function mkDeployDriftCheckRoot() {
3004
- return join(tmpdir(), `${deployDriftCheckDir.replaceAll("/", "-").replaceAll("\\", "-")}-${Date.now()}`);
3178
+ function mkDeployDriftCheckRoot(root) {
3179
+ const driftCheckDir = join(root, deployDriftCheckDir);
3180
+ rmSync(driftCheckDir, {
3181
+ recursive: true,
3182
+ force: true
3183
+ });
3184
+ mkdirSync(driftCheckDir, { recursive: true });
3185
+ return mkdtempSync(join(driftCheckDir, "check-"));
3005
3186
  }
3006
- function symlinkDirType() {
3007
- return process.platform === "win32" ? "junction" : "dir";
3187
+ function cleanupDeployDriftCheckRoot(root) {
3188
+ const driftCheckDir = join(root, deployDriftCheckDir);
3189
+ try {
3190
+ if (existsSync(driftCheckDir) && readdirSync(driftCheckDir).length === 0) rmSync(driftCheckDir, {
3191
+ recursive: true,
3192
+ force: true
3193
+ });
3194
+ } catch {}
3008
3195
  }
3009
3196
  function snapshotDir(root) {
3010
3197
  if (!existsSync(root)) return {};
@@ -3124,4 +3311,4 @@ function resolveWorkerDirName(distDir, fwPreset, buildCommand) {
3124
3311
  throw new Error("deploy: Worker output not found. Expected dist/ssr/ or dist/rsc/.");
3125
3312
  }
3126
3313
  //#endregion
3127
- export { resolveSandboxConfig as _, prerenderPagesNode as a, filterLoadedEnv as c, ensureWranglerCompatibilityDate as d, ensureWranglerNodejsAlsFlag as f, isSandboxEnabled as g, SANDBOX_MIGRATION_TAG as h, prerenderPages as i, resolveRemoteMode as l, syncWranglerBindings as m, resolveDistDir as n, DEFAULT_PROXY_URL as o, readWranglerCompat as p, resolveWorkerDirName as r, STAGING_PROXY_URL as s, deploy_exports as t, stripInternalEnvKeys as u };
3314
+ export { resolveSandboxConfig as C, isSandboxEnabled as S, readExistingLiveBindingConfig as _, prerenderPagesNode as a, syncWranglerBindings as b, filterLoadedEnv as c, LIVE_MIGRATION_TAG as d, ensureWranglerCompatibilityDate as f, normalizeWranglerCompatibilityFlags as g, nextDurableObjectMigrationTag as h, prerenderPages as i, resolveRemoteMode as l, hasDurableObjectSqliteMigration as m, resolveDistDir as n, DEFAULT_PROXY_URL as o, ensureWranglerNodejsAlsFlag as p, resolveWorkerDirName as r, STAGING_PROXY_URL as s, deploy_exports as t, stripInternalEnvKeys as u, readWranglerCompat as v, SANDBOX_MIGRATION_TAG as x, resolveLiveBindingConfig as y };