convex 1.37.0 → 1.38.0

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 (218) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/browser.bundle.js +1 -1
  3. package/dist/browser.bundle.js.map +1 -1
  4. package/dist/cjs/bundler/debugBundle.js +2 -1
  5. package/dist/cjs/bundler/debugBundle.js.map +2 -2
  6. package/dist/cjs/bundler/index.js +6 -3
  7. package/dist/cjs/bundler/index.js.map +2 -2
  8. package/dist/cjs/bundler/serverOnly.js +37 -0
  9. package/dist/cjs/bundler/serverOnly.js.map +7 -0
  10. package/dist/cjs/cli/configure.js +5 -32
  11. package/dist/cjs/cli/configure.js.map +2 -2
  12. package/dist/cjs/cli/deploy.js +1 -1
  13. package/dist/cjs/cli/deploy.js.map +1 -1
  14. package/dist/cjs/cli/deploymentCreate.js +21 -9
  15. package/dist/cjs/cli/deploymentCreate.js.map +2 -2
  16. package/dist/cjs/cli/deploymentSelect.js +25 -0
  17. package/dist/cjs/cli/deploymentSelect.js.map +2 -2
  18. package/dist/cjs/cli/deploymentTokenCreate.js +1 -1
  19. package/dist/cjs/cli/deploymentTokenCreate.js.map +2 -2
  20. package/dist/cjs/cli/deploymentTokenDelete.js +1 -1
  21. package/dist/cjs/cli/deploymentTokenDelete.js.map +2 -2
  22. package/dist/cjs/cli/index.js +5 -2
  23. package/dist/cjs/cli/index.js.map +2 -2
  24. package/dist/cjs/cli/lib/deploymentSelection.js +4 -7
  25. package/dist/cjs/cli/lib/deploymentSelection.js.map +2 -2
  26. package/dist/cjs/cli/lib/env.js +1 -0
  27. package/dist/cjs/cli/lib/env.js.map +2 -2
  28. package/dist/cjs/cli/lib/localDeployment/anonymous.js +4 -4
  29. package/dist/cjs/cli/lib/localDeployment/anonymous.js.map +2 -2
  30. package/dist/cjs/cli/lib/localDeployment/errors.js +1 -1
  31. package/dist/cjs/cli/lib/localDeployment/errors.js.map +2 -2
  32. package/dist/cjs/cli/lib/localDeployment/localDeployment.js +64 -9
  33. package/dist/cjs/cli/lib/localDeployment/localDeployment.js.map +2 -2
  34. package/dist/cjs/cli/lib/localDeployment/utils.js +19 -7
  35. package/dist/cjs/cli/lib/localDeployment/utils.js.map +3 -3
  36. package/dist/cjs/cli/lib/utils/globalConfig.js +1 -2
  37. package/dist/cjs/cli/lib/utils/globalConfig.js.map +2 -2
  38. package/dist/cjs/cli/lib/utils/utils.js +8 -0
  39. package/dist/cjs/cli/lib/utils/utils.js.map +2 -2
  40. package/dist/cjs/index.js +1 -1
  41. package/dist/cjs/index.js.map +1 -1
  42. package/dist/cjs/server/audit_logging.js +3 -1
  43. package/dist/cjs/server/audit_logging.js.map +2 -2
  44. package/dist/cjs/server/components/index.js +1 -12
  45. package/dist/cjs/server/components/index.js.map +2 -2
  46. package/dist/cjs/server/impl/registration_impl.js +8 -5
  47. package/dist/cjs/server/impl/registration_impl.js.map +2 -2
  48. package/dist/cjs/server/index.js.map +2 -2
  49. package/dist/cjs/server/log.js.map +2 -2
  50. package/dist/cjs/server/logVars.js.map +2 -2
  51. package/dist/cjs/server/meta.js.map +1 -1
  52. package/dist/cjs-types/bundler/debugBundle.d.ts.map +1 -1
  53. package/dist/cjs-types/bundler/index.d.ts.map +1 -1
  54. package/dist/cjs-types/bundler/serverOnly.d.ts +3 -0
  55. package/dist/cjs-types/bundler/serverOnly.d.ts.map +1 -0
  56. package/dist/cjs-types/cli/configure.d.ts.map +1 -1
  57. package/dist/cjs-types/cli/deploymentCreate.d.ts +4 -0
  58. package/dist/cjs-types/cli/deploymentCreate.d.ts.map +1 -1
  59. package/dist/cjs-types/cli/deploymentSelect.d.ts.map +1 -1
  60. package/dist/cjs-types/cli/deploymentTokenCreate.d.ts.map +1 -1
  61. package/dist/cjs-types/cli/deploymentTokenDelete.d.ts.map +1 -1
  62. package/dist/cjs-types/cli/lib/deployApi/componentDefinition.d.ts +6 -6
  63. package/dist/cjs-types/cli/lib/deployApi/modules.d.ts +6 -6
  64. package/dist/cjs-types/cli/lib/deployApi/startPush.d.ts +8 -8
  65. package/dist/cjs-types/cli/lib/deploymentSelection.d.ts.map +1 -1
  66. package/dist/cjs-types/cli/lib/env.d.ts.map +1 -1
  67. package/dist/cjs-types/cli/lib/localDeployment/anonymous.d.ts.map +1 -1
  68. package/dist/cjs-types/cli/lib/localDeployment/errors.d.ts.map +1 -1
  69. package/dist/cjs-types/cli/lib/localDeployment/localDeployment.d.ts +8 -0
  70. package/dist/cjs-types/cli/lib/localDeployment/localDeployment.d.ts.map +1 -1
  71. package/dist/cjs-types/cli/lib/localDeployment/utils.d.ts +13 -4
  72. package/dist/cjs-types/cli/lib/localDeployment/utils.d.ts.map +1 -1
  73. package/dist/cjs-types/cli/lib/utils/globalConfig.d.ts +0 -1
  74. package/dist/cjs-types/cli/lib/utils/globalConfig.d.ts.map +1 -1
  75. package/dist/cjs-types/cli/lib/utils/utils.d.ts +7 -0
  76. package/dist/cjs-types/cli/lib/utils/utils.d.ts.map +1 -1
  77. package/dist/cjs-types/index.d.ts +1 -1
  78. package/dist/cjs-types/server/audit_logging.d.ts +1 -0
  79. package/dist/cjs-types/server/audit_logging.d.ts.map +1 -1
  80. package/dist/cjs-types/server/components/index.d.ts.map +1 -1
  81. package/dist/cjs-types/server/impl/registration_impl.d.ts.map +1 -1
  82. package/dist/cjs-types/server/index.d.ts +2 -0
  83. package/dist/cjs-types/server/index.d.ts.map +1 -1
  84. package/dist/cjs-types/server/log.d.ts +28 -0
  85. package/dist/cjs-types/server/log.d.ts.map +1 -1
  86. package/dist/cjs-types/server/logVars.d.ts +1 -0
  87. package/dist/cjs-types/server/logVars.d.ts.map +1 -1
  88. package/dist/cjs-types/server/meta.d.ts +2 -0
  89. package/dist/cjs-types/server/meta.d.ts.map +1 -1
  90. package/dist/cli.bundle.cjs +1583 -1520
  91. package/dist/cli.bundle.cjs.map +4 -4
  92. package/dist/esm/bundler/debugBundle.js +2 -1
  93. package/dist/esm/bundler/debugBundle.js.map +2 -2
  94. package/dist/esm/bundler/index.js +6 -3
  95. package/dist/esm/bundler/index.js.map +2 -2
  96. package/dist/esm/bundler/serverOnly.js +15 -0
  97. package/dist/esm/bundler/serverOnly.js.map +7 -0
  98. package/dist/esm/cli/configure.js +5 -32
  99. package/dist/esm/cli/configure.js.map +2 -2
  100. package/dist/esm/cli/deploy.js +1 -1
  101. package/dist/esm/cli/deploy.js.map +1 -1
  102. package/dist/esm/cli/deploymentCreate.js +21 -10
  103. package/dist/esm/cli/deploymentCreate.js.map +2 -2
  104. package/dist/esm/cli/deploymentSelect.js +25 -0
  105. package/dist/esm/cli/deploymentSelect.js.map +2 -2
  106. package/dist/esm/cli/deploymentTokenCreate.js +2 -1
  107. package/dist/esm/cli/deploymentTokenCreate.js.map +2 -2
  108. package/dist/esm/cli/deploymentTokenDelete.js +2 -1
  109. package/dist/esm/cli/deploymentTokenDelete.js.map +2 -2
  110. package/dist/esm/cli/index.js +5 -2
  111. package/dist/esm/cli/index.js.map +2 -2
  112. package/dist/esm/cli/lib/deploymentSelection.js +5 -7
  113. package/dist/esm/cli/lib/deploymentSelection.js.map +2 -2
  114. package/dist/esm/cli/lib/env.js +2 -0
  115. package/dist/esm/cli/lib/env.js.map +2 -2
  116. package/dist/esm/cli/lib/localDeployment/anonymous.js +4 -4
  117. package/dist/esm/cli/lib/localDeployment/anonymous.js.map +2 -2
  118. package/dist/esm/cli/lib/localDeployment/errors.js +1 -1
  119. package/dist/esm/cli/lib/localDeployment/errors.js.map +2 -2
  120. package/dist/esm/cli/lib/localDeployment/localDeployment.js +70 -11
  121. package/dist/esm/cli/lib/localDeployment/localDeployment.js.map +2 -2
  122. package/dist/esm/cli/lib/localDeployment/utils.js +19 -7
  123. package/dist/esm/cli/lib/localDeployment/utils.js.map +3 -3
  124. package/dist/esm/cli/lib/utils/globalConfig.js +1 -2
  125. package/dist/esm/cli/lib/utils/globalConfig.js.map +2 -2
  126. package/dist/esm/cli/lib/utils/utils.js +6 -0
  127. package/dist/esm/cli/lib/utils/utils.js.map +2 -2
  128. package/dist/esm/index.js +1 -1
  129. package/dist/esm/index.js.map +1 -1
  130. package/dist/esm/server/audit_logging.js +3 -1
  131. package/dist/esm/server/audit_logging.js.map +2 -2
  132. package/dist/esm/server/components/index.js +1 -12
  133. package/dist/esm/server/components/index.js.map +2 -2
  134. package/dist/esm/server/impl/registration_impl.js +8 -5
  135. package/dist/esm/server/impl/registration_impl.js.map +2 -2
  136. package/dist/esm/server/index.js.map +2 -2
  137. package/dist/esm/server/log.js.map +2 -2
  138. package/dist/esm/server/logVars.js.map +2 -2
  139. package/dist/esm-types/bundler/debugBundle.d.ts.map +1 -1
  140. package/dist/esm-types/bundler/index.d.ts.map +1 -1
  141. package/dist/esm-types/bundler/serverOnly.d.ts +3 -0
  142. package/dist/esm-types/bundler/serverOnly.d.ts.map +1 -0
  143. package/dist/esm-types/cli/configure.d.ts.map +1 -1
  144. package/dist/esm-types/cli/deploymentCreate.d.ts +4 -0
  145. package/dist/esm-types/cli/deploymentCreate.d.ts.map +1 -1
  146. package/dist/esm-types/cli/deploymentSelect.d.ts.map +1 -1
  147. package/dist/esm-types/cli/deploymentTokenCreate.d.ts.map +1 -1
  148. package/dist/esm-types/cli/deploymentTokenDelete.d.ts.map +1 -1
  149. package/dist/esm-types/cli/lib/deployApi/componentDefinition.d.ts +6 -6
  150. package/dist/esm-types/cli/lib/deployApi/modules.d.ts +6 -6
  151. package/dist/esm-types/cli/lib/deployApi/startPush.d.ts +8 -8
  152. package/dist/esm-types/cli/lib/deploymentSelection.d.ts.map +1 -1
  153. package/dist/esm-types/cli/lib/env.d.ts.map +1 -1
  154. package/dist/esm-types/cli/lib/localDeployment/anonymous.d.ts.map +1 -1
  155. package/dist/esm-types/cli/lib/localDeployment/errors.d.ts.map +1 -1
  156. package/dist/esm-types/cli/lib/localDeployment/localDeployment.d.ts +8 -0
  157. package/dist/esm-types/cli/lib/localDeployment/localDeployment.d.ts.map +1 -1
  158. package/dist/esm-types/cli/lib/localDeployment/utils.d.ts +13 -4
  159. package/dist/esm-types/cli/lib/localDeployment/utils.d.ts.map +1 -1
  160. package/dist/esm-types/cli/lib/utils/globalConfig.d.ts +0 -1
  161. package/dist/esm-types/cli/lib/utils/globalConfig.d.ts.map +1 -1
  162. package/dist/esm-types/cli/lib/utils/utils.d.ts +7 -0
  163. package/dist/esm-types/cli/lib/utils/utils.d.ts.map +1 -1
  164. package/dist/esm-types/index.d.ts +1 -1
  165. package/dist/esm-types/server/audit_logging.d.ts +1 -0
  166. package/dist/esm-types/server/audit_logging.d.ts.map +1 -1
  167. package/dist/esm-types/server/components/index.d.ts.map +1 -1
  168. package/dist/esm-types/server/impl/registration_impl.d.ts.map +1 -1
  169. package/dist/esm-types/server/index.d.ts +2 -0
  170. package/dist/esm-types/server/index.d.ts.map +1 -1
  171. package/dist/esm-types/server/log.d.ts +28 -0
  172. package/dist/esm-types/server/log.d.ts.map +1 -1
  173. package/dist/esm-types/server/logVars.d.ts +1 -0
  174. package/dist/esm-types/server/logVars.d.ts.map +1 -1
  175. package/dist/esm-types/server/meta.d.ts +2 -0
  176. package/dist/esm-types/server/meta.d.ts.map +1 -1
  177. package/dist/react.bundle.js +1 -1
  178. package/dist/react.bundle.js.map +1 -1
  179. package/package.json +1 -1
  180. package/schemas/convex.schema.json +1 -1
  181. package/src/bundler/debugBundle.ts +2 -1
  182. package/src/bundler/index.ts +7 -3
  183. package/src/bundler/serverOnly.ts +18 -0
  184. package/src/cli/configure.ts +2 -35
  185. package/src/cli/deploy.ts +1 -1
  186. package/src/cli/deploymentCreate.test.ts +4 -0
  187. package/src/cli/deploymentCreate.ts +23 -9
  188. package/src/cli/deploymentSelect.test.ts +60 -6
  189. package/src/cli/deploymentSelect.ts +34 -0
  190. package/src/cli/deploymentSelection.test.ts +72 -19
  191. package/src/cli/deploymentTokenCreate.ts +4 -1
  192. package/src/cli/deploymentTokenDelete.ts +9 -1
  193. package/src/cli/index.ts +6 -2
  194. package/src/cli/lib/deploymentSelection.ts +5 -7
  195. package/src/cli/lib/env.ts +2 -0
  196. package/src/cli/lib/localDeployment/anonymous.ts +5 -4
  197. package/src/cli/lib/localDeployment/errors.ts +1 -3
  198. package/src/cli/lib/localDeployment/localDeployment.ts +85 -10
  199. package/src/cli/lib/localDeployment/utils.ts +31 -7
  200. package/src/cli/lib/utils/globalConfig.ts +0 -3
  201. package/src/cli/lib/utils/utils.ts +15 -0
  202. package/src/index.ts +1 -1
  203. package/src/server/audit_logging.ts +2 -3
  204. package/src/server/components/index.ts +3 -15
  205. package/src/server/impl/registration_impl.ts +13 -7
  206. package/src/server/index.ts +0 -6
  207. package/src/server/log.ts +21 -3
  208. package/src/server/logVars.ts +0 -3
  209. package/src/server/meta.ts +0 -7
  210. package/dist/cjs/cli/disableLocalDev.js +0 -121
  211. package/dist/cjs/cli/disableLocalDev.js.map +0 -7
  212. package/dist/cjs-types/cli/disableLocalDev.d.ts +0 -6
  213. package/dist/cjs-types/cli/disableLocalDev.d.ts.map +0 -1
  214. package/dist/esm/cli/disableLocalDev.js +0 -105
  215. package/dist/esm/cli/disableLocalDev.js.map +0 -7
  216. package/dist/esm-types/cli/disableLocalDev.d.ts +0 -6
  217. package/dist/esm-types/cli/disableLocalDev.d.ts.map +0 -1
  218. package/src/cli/disableLocalDev.ts +0 -134
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "convex",
3
3
  "description": "Client for the Convex Cloud",
4
- "version": "1.37.0",
4
+ "version": "1.38.0",
5
5
  "author": "Convex, Inc. <no-reply@convex.dev>",
6
6
  "homepage": "https://convex.dev",
7
7
  "repository": {
@@ -123,7 +123,7 @@
123
123
  },
124
124
  "nodeVersion": {
125
125
  "type": "string",
126
- "description": "The Node.js version to use for Node.js actions. The currently supported values are '18', '20', and '22'.\n\nDocumentation: https://docs.convex.dev/production/project-configuration#configuring-the-nodejs-version",
126
+ "description": "The Node.js version to use for Node.js actions. The currently supported values are '20', '22', and '24'.\n\nDocumentation: https://docs.convex.dev/production/project-configuration#configuring-the-nodejs-version",
127
127
  "default": "20"
128
128
  }
129
129
  }
@@ -9,6 +9,7 @@ import {
9
9
  logMessage,
10
10
  } from "./log.js";
11
11
  import { wasmPlugin } from "./wasm.js";
12
+ import { serverOnlyPlugin } from "./serverOnly.js";
12
13
  import dependencyTrackerPlugin from "./depgraph.js";
13
14
 
14
15
  export async function innerEsbuild({
@@ -111,7 +112,7 @@ export async function debugIsolateBundlesSerially(
111
112
  chunksFolder: "_deps",
112
113
  extraConditions,
113
114
  dir,
114
- plugins: [plugin, wasmPlugin],
115
+ plugins: [serverOnlyPlugin, plugin, wasmPlugin],
115
116
  logLevel: "silent",
116
117
  });
117
118
  } catch (error) {
@@ -8,6 +8,7 @@ import { Filesystem, consistentPathSort } from "./fs.js";
8
8
  import { Context } from "./context.js";
9
9
  import { logVerbose, logWarning } from "./log.js";
10
10
  import { wasmPlugin } from "./wasm.js";
11
+ import { serverOnlyPlugin } from "./serverOnly.js";
11
12
  import {
12
13
  ExternalPackage,
13
14
  computeExternalPackages,
@@ -100,8 +101,10 @@ async function doEsbuild({
100
101
  chunksFolder,
101
102
  extraConditions,
102
103
  dir,
103
- // The wasmPlugin should be last so it doesn't run on external modules.
104
- plugins: [external.plugin, wasmPlugin],
104
+ // serverOnlyPlugin runs first so `server-only` is always stubbed,
105
+ // even if it appears in the external packages list.
106
+ // wasmPlugin runs last so it doesn't run on external modules.
107
+ plugins: [serverOnlyPlugin, external.plugin, wasmPlugin],
105
108
  includeSourcesContent,
106
109
  splitting,
107
110
  });
@@ -112,7 +115,8 @@ async function doEsbuild({
112
115
  if (
113
116
  relPath.indexOf("(disabled):") !== -1 ||
114
117
  relPath.startsWith("wasm-binary:") ||
115
- relPath.startsWith("wasm-stub:")
118
+ relPath.startsWith("wasm-stub:") ||
119
+ relPath.startsWith("server-only-stub:")
116
120
  ) {
117
121
  continue;
118
122
  }
@@ -0,0 +1,18 @@
1
+ import { Plugin } from "esbuild";
2
+
3
+ // Stub `import "server-only"` to an empty module so user code that uses
4
+ // Next.js's server-only can be bundled and analyzed correctly:
5
+ // https://nextjs.org/docs/app/getting-started/server-and-client-components#preventing-environment-poisoning
6
+ export const serverOnlyPlugin: Plugin = {
7
+ name: "convex-server-only",
8
+ setup(build) {
9
+ build.onResolve({ filter: /^server-only$/ }, (args) => ({
10
+ path: args.path,
11
+ namespace: "server-only-stub",
12
+ }));
13
+ build.onLoad({ filter: /.*/, namespace: "server-only-stub" }, () => ({
14
+ contents: "",
15
+ loader: "js",
16
+ }));
17
+ },
18
+ };
@@ -43,7 +43,6 @@ import {
43
43
  promptString,
44
44
  promptYesNo,
45
45
  } from "./lib/utils/prompts.js";
46
- import { readGlobalConfig } from "./lib/utils/globalConfig.js";
47
46
  import { attemptSetupAiFiles } from "./lib/aiFiles/index.js";
48
47
  import {
49
48
  DeploymentSelection,
@@ -175,17 +174,6 @@ export async function _deploymentCredentialsOrConfigure(
175
174
  } | null;
176
175
  }
177
176
  > {
178
- const config = readGlobalConfig(ctx);
179
- const globallyForceCloud = !!config?.optOutOfLocalDevDeploymentsUntilBetaOver;
180
- if (globallyForceCloud && cmdOptions.local) {
181
- return await ctx.crash({
182
- exitCode: 1,
183
- errorType: "fatal",
184
- printedMessage:
185
- "Can't specify --local when local deployments are disabled on this machine. Run `npx convex disable-local-deployments --undo-global` to allow use of --local.",
186
- });
187
- }
188
-
189
177
  switch (deploymentSelection.kind) {
190
178
  case "existingDeployment":
191
179
  return {
@@ -205,9 +193,6 @@ export async function _deploymentCredentialsOrConfigure(
205
193
  ctx,
206
194
  chosenConfiguration,
207
195
  deploymentSelection.selectionWithinProject,
208
- {
209
- globallyForceCloud,
210
- },
211
196
  cmdOptions,
212
197
  );
213
198
  }
@@ -222,7 +207,6 @@ export async function _deploymentCredentialsOrConfigure(
222
207
  chosenConfiguration,
223
208
  deploymentSelection,
224
209
  cmdOptions,
225
- globallyForceCloud,
226
210
  });
227
211
  }
228
212
  case "anonymous": {
@@ -244,9 +228,6 @@ export async function _deploymentCredentialsOrConfigure(
244
228
  ctx,
245
229
  chosenConfiguration,
246
230
  deploymentSelection.selectionWithinProject,
247
- {
248
- globallyForceCloud,
249
- },
250
231
  cmdOptions,
251
232
  );
252
233
  }
@@ -300,9 +281,6 @@ export async function _deploymentCredentialsOrConfigure(
300
281
  ctx,
301
282
  chosenConfiguration,
302
283
  deploymentSelection.selectionWithinProject,
303
- {
304
- globallyForceCloud,
305
- },
306
284
  cmdOptions,
307
285
  );
308
286
  }
@@ -315,14 +293,12 @@ async function handleDeploymentWithinProject(
315
293
  chosenConfiguration,
316
294
  deploymentSelection,
317
295
  cmdOptions,
318
- globallyForceCloud,
319
296
  }: {
320
297
  chosenConfiguration: ChosenConfiguration;
321
298
  deploymentSelection: DeploymentSelection & {
322
299
  kind: "deploymentWithinProject";
323
300
  };
324
301
  cmdOptions: ConfigureCmdOptions;
325
- globallyForceCloud: boolean;
326
302
  },
327
303
  ) {
328
304
  const hasAuth = ctx.bigBrainAuth() !== null;
@@ -343,9 +319,6 @@ async function handleDeploymentWithinProject(
343
319
  ctx,
344
320
  chosenConfiguration,
345
321
  deploymentSelection.selectionWithinProject,
346
- {
347
- globallyForceCloud,
348
- },
349
322
  cmdOptions,
350
323
  );
351
324
  return result;
@@ -361,9 +334,6 @@ async function handleDeploymentWithinProject(
361
334
  ctx,
362
335
  chosenConfiguration,
363
336
  deploymentSelection.selectionWithinProject,
364
- {
365
- globallyForceCloud,
366
- },
367
337
  cmdOptions,
368
338
  );
369
339
  return result;
@@ -405,9 +375,6 @@ async function handleChooseProject(
405
375
  ctx: Context,
406
376
  chosenConfiguration: ChosenConfiguration,
407
377
  selectionWithinProject: DeploymentSelectionWithinProject,
408
- args: {
409
- globallyForceCloud: boolean;
410
- },
411
378
  cmdOptions: ConfigureCmdOptions,
412
379
  ): Promise<
413
380
  DeploymentCredentials & {
@@ -429,8 +396,8 @@ async function handleChooseProject(
429
396
  team: cmdOptions.team,
430
397
  project: cmdOptions.project,
431
398
  devDeployment: cmdOptions.devDeployment,
432
- local: args.globallyForceCloud ? false : cmdOptions.local,
433
- cloud: args.globallyForceCloud ? true : cmdOptions.cloud,
399
+ local: cmdOptions.local,
400
+ cloud: cmdOptions.cloud,
434
401
  });
435
402
  // TODO complain about any non-default cmdOptions.localOptions here
436
403
  // because we're ignoring them if this isn't a local development.
package/src/cli/deploy.ts CHANGED
@@ -227,7 +227,7 @@ async function deployToNewPreviewDeployment(
227
227
  exitCode: 1,
228
228
  errorType: "fatal",
229
229
  printedMessage:
230
- "`npx convex deploy` to a preview deployment could not determine the preview name. Provide one using `--preview-create`",
230
+ "`npx convex deploy` to a preview deployment could not determine the preview name. Provide one using `--preview-name`",
231
231
  });
232
232
  }
233
233
 
@@ -217,6 +217,8 @@ describe("non-interactive create flow", () => {
217
217
  deploymentName: "local-test-123",
218
218
  adminKey: "test-key",
219
219
  });
220
+ setupPlatformClient();
221
+ mockPlatformGet.mockResolvedValue({ data: { items: [] } });
220
222
 
221
223
  await deploymentCreate.parseAsync(["local"], { from: "user" });
222
224
 
@@ -263,6 +265,8 @@ describe("non-interactive create flow", () => {
263
265
  deploymentName: "local-test-123",
264
266
  adminKey: "test-key",
265
267
  });
268
+ setupPlatformClient();
269
+ mockPlatformGet.mockResolvedValue({ data: { items: [] } });
266
270
 
267
271
  await deploymentCreate.parseAsync(["local", "--select"], {
268
272
  from: "user",
@@ -40,6 +40,8 @@ import {
40
40
  LOCAL_BACKEND_INSTANCE_SECRET,
41
41
  } from "./lib/localDeployment/utils.js";
42
42
  import { bigBrainStart } from "./lib/localDeployment/bigBrain.js";
43
+ import { importDefaultEnvVars } from "./lib/localDeployment/localDeployment.js";
44
+ import { localDeploymentUrl } from "./lib/localDeployment/run.js";
43
45
 
44
46
  const SUPPORTED_TYPES = ["dev", "prod", "preview"] as const;
45
47
 
@@ -70,7 +72,10 @@ export const deploymentCreate = new Command("create")
70
72
  "--expiration <value>",
71
73
  'When the deployment expires (e.g. "none", "in 7 days", "2026-04-01T00:00:00Z", or a UNIX timestamp in seconds or milliseconds)',
72
74
  )
75
+ .addOption(new Option("--expiry <value>").hideHelp())
76
+ .addOption(new Option("--expires <value>").hideHelp())
73
77
  .action(async (refParam, options) => {
78
+ const expiration = options.expiration ?? options.expiry ?? options.expires;
74
79
  const ctx = await oneoffContext({
75
80
  url: undefined,
76
81
  adminKey: undefined,
@@ -86,13 +91,7 @@ export const deploymentCreate = new Command("create")
86
91
  // Handle `deployment create local`
87
92
  if (refParam !== undefined) {
88
93
  if (refParam === "local") {
89
- const cloudOnlyFlags = [
90
- "type",
91
- "region",
92
- "class",
93
- "default",
94
- "expiration",
95
- ] as const;
94
+ const cloudOnlyFlags = ["type", "region", "class", "default"] as const;
96
95
  for (const flag of cloudOnlyFlags) {
97
96
  if (options[flag]) {
98
97
  return await ctx.crash({
@@ -102,6 +101,13 @@ export const deploymentCreate = new Command("create")
102
101
  });
103
102
  }
104
103
  }
104
+ if (expiration !== undefined) {
105
+ return await ctx.crash({
106
+ exitCode: 1,
107
+ errorType: "fatal",
108
+ printedMessage: `--expiration cannot be used when creating a local deployment`,
109
+ });
110
+ }
105
111
  await createLocalDeployment(
106
112
  ctx,
107
113
  currentDeployment,
@@ -111,7 +117,7 @@ export const deploymentCreate = new Command("create")
111
117
  }
112
118
  }
113
119
 
114
- const expiresAt = await resolveExpiresAtOrCrash(ctx, options.expiration);
120
+ const expiresAt = await resolveExpiresAtOrCrash(ctx, expiration);
115
121
 
116
122
  const {
117
123
  ref,
@@ -212,7 +218,7 @@ export const deploymentCreate = new Command("create")
212
218
  }
213
219
  });
214
220
 
215
- async function createLocalDeployment(
221
+ export async function createLocalDeployment(
216
222
  ctx: Context,
217
223
  currentDeployment: DeploymentSelection,
218
224
  select: boolean,
@@ -255,6 +261,14 @@ async function createLocalDeployment(
255
261
 
256
262
  logFinishedStep("Created local deployment.");
257
263
 
264
+ await importDefaultEnvVars(ctx, {
265
+ teamSlug,
266
+ projectSlug,
267
+ deploymentName,
268
+ deploymentUrl: localDeploymentUrl(cloudPort),
269
+ adminKey,
270
+ });
271
+
258
272
  if (select) {
259
273
  const selection: DeploymentSelection = {
260
274
  kind: "deploymentWithinProject",
@@ -1,4 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ // @inquirer/testing/vitest must be imported before modules that use @inquirer/*
3
+ import { screen } from "@inquirer/testing/vitest";
2
4
  import path from "path";
3
5
  import { nodeFs } from "../bundler/fs.js";
4
6
  import { deploymentSelect } from "./deploymentSelect.js";
@@ -8,6 +10,9 @@ import { globalConfigPath } from "./lib/utils/globalConfig.js";
8
10
  // Mock GET functions — can be configured per test
9
11
  const mockPlatformGet = vi.fn();
10
12
  const mockDeploymentGet = vi.fn();
13
+ const { mockCreateLocalDeployment } = vi.hoisted(() => ({
14
+ mockCreateLocalDeployment: vi.fn(),
15
+ }));
11
16
 
12
17
  // In-memory filesystem — populated in beforeEach, written to by real configure code
13
18
  let testFiles: Map<string, string>;
@@ -50,6 +55,14 @@ vi.mock("@sentry/node", () => ({
50
55
  close: vi.fn(),
51
56
  }));
52
57
 
58
+ vi.mock("./deploymentCreate.js", async (importOriginal) => {
59
+ const actual = await importOriginal<typeof import("./deploymentCreate.js")>();
60
+ return {
61
+ ...actual,
62
+ createLocalDeployment: mockCreateLocalDeployment,
63
+ };
64
+ });
65
+
53
66
  vi.mock("./lib/localDeployment/run.js", async (importOriginal) => {
54
67
  const actual =
55
68
  await importOriginal<typeof import("./lib/localDeployment/run.js")>();
@@ -501,14 +514,38 @@ describe("npx convex select", () => {
501
514
  expect(envContent).not.toContain("CONVEX_SITE_URL");
502
515
  });
503
516
 
504
- it("fails with 'No local deployment found' when no local config exists", async () => {
505
- await expect(
506
- deploymentSelect.parseAsync(["local"], { from: "user" }),
507
- ).rejects.toThrow();
517
+ it("creates a local deployment when user approves the 'Create one now?' prompt", async () => {
518
+ mockCreateLocalDeployment.mockResolvedValue(undefined);
508
519
 
509
- expect(process.stderr.write).toHaveBeenCalledWith(
510
- expect.stringContaining("No local deployment found"),
520
+ const promise = deploymentSelect.parseAsync(["local"], { from: "user" });
521
+
522
+ await screen.next();
523
+ expect(screen.getScreen()).toContain(
524
+ "No local deployment found. Create one now?",
511
525
  );
526
+ screen.keypress("y");
527
+ screen.keypress("enter");
528
+
529
+ await promise;
530
+
531
+ expect(mockCreateLocalDeployment).toHaveBeenCalledTimes(1);
532
+ });
533
+
534
+ it("fails with 'No local deployment found' when no local config exists and stdin is not a TTY", async () => {
535
+ const previousIsTTY = process.stdin.isTTY;
536
+ process.stdin.isTTY = false as any;
537
+ try {
538
+ await expect(
539
+ deploymentSelect.parseAsync(["local"], { from: "user" }),
540
+ ).rejects.toThrow();
541
+
542
+ expect(process.stderr.write).toHaveBeenCalledWith(
543
+ expect.stringContaining("No local deployment found"),
544
+ );
545
+ expect(mockCreateLocalDeployment).not.toHaveBeenCalled();
546
+ } finally {
547
+ process.stdin.isTTY = previousIsTTY as any;
548
+ }
512
549
  });
513
550
  });
514
551
 
@@ -610,6 +647,23 @@ describe("npx convex select", () => {
610
647
  expect(envContent).toContain("CONVEX_DEPLOYMENT=dev:clever-otter-890");
611
648
  });
612
649
 
650
+ it("fails with 'No project configured' for 'local' when no local deployment exists yet", async () => {
651
+ // No `.convex/local/default/config.json` exists, so we'd normally prompt
652
+ // "Create one now?". But since creating a local deployment requires a
653
+ // project, we should fail up-front instead.
654
+ await expect(
655
+ deploymentSelect.parseAsync(["local"], { from: "user" }),
656
+ ).rejects.toThrow();
657
+
658
+ expect(process.stderr.write).toHaveBeenCalledWith(
659
+ expect.stringContaining("No project configured"),
660
+ );
661
+ expect(process.stderr.write).not.toHaveBeenCalledWith(
662
+ expect.stringContaining("Create one now?"),
663
+ );
664
+ expect(mockCreateLocalDeployment).not.toHaveBeenCalled();
665
+ });
666
+
613
667
  it("selects local deployment without project configured", async () => {
614
668
  testFiles.set(
615
669
  path.resolve(".convex/local/default/config.json"),
@@ -9,6 +9,9 @@ import {
9
9
  import { parseDeploymentSelector } from "./lib/deploymentSelector.js";
10
10
  import { updateEnvAndConfigForDeploymentSelection } from "./configure.js";
11
11
  import { fetchDeploymentCanonicalUrls } from "./lib/deploy2.js";
12
+ import { loadProjectLocalConfig } from "./lib/localDeployment/filePaths.js";
13
+ import { promptYesNo } from "./lib/utils/prompts.js";
14
+ import { createLocalDeployment } from "./deploymentCreate.js";
12
15
  import { chalkStderr } from "chalk";
13
16
 
14
17
  export const deploymentSelect = new Command("select")
@@ -50,6 +53,37 @@ export const deploymentSelect = new Command("select")
50
53
  });
51
54
  }
52
55
 
56
+ // If selecting `local` and no local deployment exists, offer to create one interactively.
57
+ if (
58
+ parsed.kind === "local" &&
59
+ process.stdin.isTTY &&
60
+ loadProjectLocalConfig(ctx) === null
61
+ ) {
62
+ // Creating a local deployment requires a configured project, so bail out
63
+ // before prompting the user if there isn't one.
64
+ if (currentSelection.kind === "chooseProject") {
65
+ return await ctx.crash({
66
+ exitCode: 1,
67
+ errorType: "fatal",
68
+ printedMessage: `No project configured. Run \`npx convex dev\` to set up a project first.`,
69
+ });
70
+ }
71
+
72
+ const wantsToCreate = await promptYesNo(ctx, {
73
+ message: "No local deployment found. Create one now?",
74
+ default: true,
75
+ });
76
+ if (!wantsToCreate) {
77
+ return await ctx.crash({
78
+ exitCode: 1,
79
+ errorType: "fatal",
80
+ printedMessage: `No local deployment found. Run ${chalkStderr.bold("npx convex deployment create local")} to create one.`,
81
+ });
82
+ }
83
+ await createLocalDeployment(ctx, currentSelection, true);
84
+ return;
85
+ }
86
+
53
87
  // Resolve the new deployment using the selector relative to the current project
54
88
  const newSelection = await getDeploymentSelection(ctx, {
55
89
  url: undefined,
@@ -394,6 +394,65 @@ describe("deployment selection flows", () => {
394
394
  );
395
395
  });
396
396
 
397
+ it("resolves CONVEX_DEPLOYMENT_TOKEN as an alias for CONVEX_DEPLOY_KEY", async () => {
398
+ process.env.CONVEX_DEPLOYMENT_TOKEN =
399
+ "prod:joyful-capybara-123|secretkey";
400
+
401
+ setupBigBrainRoutes({
402
+ "deployment/url_for_key": () =>
403
+ "https://joyful-capybara-123.eu-west-1.convex.cloud",
404
+ "deployment/team_and_project_for_key": () => ({
405
+ team: "my-team",
406
+ project: "my-project",
407
+ teamId: 1,
408
+ projectId: 1,
409
+ }),
410
+ });
411
+
412
+ const mockFetch = vi.fn().mockResolvedValue({ ok: true });
413
+ vi.mocked(deploymentFetch).mockReturnValue(mockFetch as any);
414
+
415
+ await env.parseAsync(["set", "ABC", "DEF"], { from: "user" });
416
+
417
+ expect(deploymentFetch).toHaveBeenCalledWith(
418
+ expect.anything(),
419
+ expect.objectContaining({
420
+ deploymentUrl: "https://joyful-capybara-123.eu-west-1.convex.cloud",
421
+ adminKey: "prod:joyful-capybara-123|secretkey",
422
+ }),
423
+ );
424
+ });
425
+
426
+ it("CONVEX_DEPLOY_KEY takes precedence over CONVEX_DEPLOYMENT_TOKEN", async () => {
427
+ process.env.CONVEX_DEPLOY_KEY = "prod:joyful-capybara-123|secretkey";
428
+ process.env.CONVEX_DEPLOYMENT_TOKEN =
429
+ "prod:other-deployment-456|otherkey";
430
+
431
+ setupBigBrainRoutes({
432
+ "deployment/url_for_key": () =>
433
+ "https://joyful-capybara-123.eu-west-1.convex.cloud",
434
+ "deployment/team_and_project_for_key": () => ({
435
+ team: "my-team",
436
+ project: "my-project",
437
+ teamId: 1,
438
+ projectId: 1,
439
+ }),
440
+ });
441
+
442
+ const mockFetch = vi.fn().mockResolvedValue({ ok: true });
443
+ vi.mocked(deploymentFetch).mockReturnValue(mockFetch as any);
444
+
445
+ await env.parseAsync(["set", "ABC", "DEF"], { from: "user" });
446
+
447
+ expect(deploymentFetch).toHaveBeenCalledWith(
448
+ expect.anything(),
449
+ expect.objectContaining({
450
+ deploymentUrl: "https://joyful-capybara-123.eu-west-1.convex.cloud",
451
+ adminKey: "prod:joyful-capybara-123|secretkey",
452
+ }),
453
+ );
454
+ });
455
+
397
456
  it("resolves CONVEX_DEPLOY_KEY with project deploy key to dev deployment by default", async () => {
398
457
  process.env.CONVEX_DEPLOY_KEY = "project:identifier|secretkey";
399
458
 
@@ -1662,6 +1721,18 @@ describe("deployment selection flows", () => {
1662
1721
  expect(runPush).toHaveBeenCalled();
1663
1722
  });
1664
1723
 
1724
+ it("suggests --preview-name when preview name cannot be determined", async () => {
1725
+ process.env.CONVEX_DEPLOY_KEY = "preview:my-team:my-project|secretkey";
1726
+
1727
+ await expect(deploy.parseAsync([], { from: "user" })).rejects.toThrow();
1728
+
1729
+ expect(process.stderr.write).toHaveBeenCalledWith(
1730
+ expect.stringContaining("Provide one using `--preview-name`"),
1731
+ );
1732
+ expect(bigBrainAPI).not.toHaveBeenCalled();
1733
+ expect(runPush).not.toHaveBeenCalled();
1734
+ });
1735
+
1665
1736
  it("deploys to existing preview with CONVEX_DEPLOYMENT and --preview-name", async () => {
1666
1737
  process.env.CONVEX_DEPLOYMENT = "dev:joyful-capybara-123";
1667
1738
  vi.mocked(readGlobalConfig).mockReturnValue({
@@ -1746,7 +1817,7 @@ describe("deployment selection flows", () => {
1746
1817
  it("dev --local uses local deployment when local is allowed", async () => {
1747
1818
  vi.mocked(readGlobalConfig).mockReturnValue({
1748
1819
  accessToken: "test-token",
1749
- }); // no optOutOfLocalDevDeploymentsUntilBetaOver
1820
+ });
1750
1821
  vi.mocked(validateOrSelectTeam).mockResolvedValue({
1751
1822
  team: { slug: "my-team", id: 1, name: "My Team" } as any,
1752
1823
  chosen: false,
@@ -1829,24 +1900,6 @@ describe("deployment selection flows", () => {
1829
1900
  );
1830
1901
  });
1831
1902
 
1832
- it("dev --local crashes when local deployments are globally disabled", async () => {
1833
- vi.mocked(readGlobalConfig).mockReturnValue({
1834
- accessToken: "test-token",
1835
- optOutOfLocalDevDeploymentsUntilBetaOver: true,
1836
- });
1837
-
1838
- await expect(
1839
- dev.parseAsync(["--skip-push", "--local"], { from: "user" }),
1840
- ).rejects.toThrow();
1841
-
1842
- expect(process.stderr.write).toHaveBeenCalledWith(
1843
- expect.stringContaining(
1844
- "Can't specify --local when local deployments are disabled on this machine",
1845
- ),
1846
- );
1847
- expect(devAgainstDeployment).not.toHaveBeenCalled();
1848
- });
1849
-
1850
1903
  it("resolves CONVEX_DEPLOYMENT to the configured deployment", async () => {
1851
1904
  process.env.CONVEX_DEPLOYMENT = "dev:joyful-capybara-123";
1852
1905
  vi.mocked(readGlobalConfig).mockReturnValue({
@@ -7,6 +7,7 @@ import { actionDescription } from "./lib/command.js";
7
7
  import { getDeploymentSelection } from "./lib/deploymentSelection.js";
8
8
  import { changedEnvVarFile } from "./lib/envvars.js";
9
9
  import {
10
+ CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME,
10
11
  CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
11
12
  ENV_VAR_FILE_PATH,
12
13
  typedPlatformClient,
@@ -40,7 +41,9 @@ export const deploymentTokenCreate = new Command("create")
40
41
  printedMessage: `Creating a deploy key currently requires being logged in with a personal access token. ${
41
42
  process.env[CONVEX_DEPLOY_KEY_ENV_VAR_NAME]
42
43
  ? `Unset ${CONVEX_DEPLOY_KEY_ENV_VAR_NAME}`
43
- : `Run ${chalkStderr.bold("npx convex login")}`
44
+ : process.env[CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME]
45
+ ? `Unset ${CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME}`
46
+ : `Run ${chalkStderr.bold("npx convex login")}`
44
47
  } and try again.`,
45
48
  });
46
49
  }
@@ -6,6 +6,7 @@ import { loadSelectedDeploymentCredentials } from "./lib/api.js";
6
6
  import { actionDescription } from "./lib/command.js";
7
7
  import { getDeploymentSelection } from "./lib/deploymentSelection.js";
8
8
  import {
9
+ CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME,
9
10
  CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
10
11
  typedPlatformClient,
11
12
  } from "./lib/utils/utils.js";
@@ -34,7 +35,14 @@ export const deploymentTokenDelete = new Command("delete")
34
35
  return await ctx.crash({
35
36
  exitCode: 1,
36
37
  errorType: "fatal",
37
- printedMessage: `Deleting a deploy key requires being logged in with a personal access token. ${auth === null ? "Run " : `Unset ${CONVEX_DEPLOY_KEY_ENV_VAR_NAME} and run `}${chalkStderr.bold("npx convex login")} and try again.`,
38
+ printedMessage: `Deleting a deploy key requires being logged in with a personal access token. ${
39
+ auth === null
40
+ ? "Run "
41
+ : process.env[CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME] &&
42
+ !process.env[CONVEX_DEPLOY_KEY_ENV_VAR_NAME]
43
+ ? `Unset ${CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME} and run `
44
+ : `Unset ${CONVEX_DEPLOY_KEY_ENV_VAR_NAME} and run `
45
+ }${chalkStderr.bold("npx convex login")} and try again.`,
38
46
  });
39
47
  }
40
48
 
package/src/cli/index.ts CHANGED
@@ -26,7 +26,6 @@ import { data } from "./data.js";
26
26
  import { format } from "util";
27
27
  import { functionSpec } from "./functionSpec.js";
28
28
  import { insights } from "./insights.js";
29
- import { disableLocalDeployments } from "./disableLocalDev.js";
30
29
  import { mcp } from "./mcp.js";
31
30
  import { deployment } from "./deployment.js";
32
31
  import { aiFiles } from "./aiFiles.js";
@@ -161,7 +160,6 @@ async function main() {
161
160
  .addCommand(integration, { hidden: true })
162
161
  .addCommand(functionSpec)
163
162
  .addCommand(insights)
164
- .addCommand(disableLocalDeployments)
165
163
  .addCommand(mcp)
166
164
  .addCommand(aiFiles)
167
165
  .helpCommand("help <command>", "Show help for given <command>")
@@ -183,6 +181,12 @@ async function main() {
183
181
  } finally {
184
182
  await Sentry.close();
185
183
  }
184
+ // When stdout is a pipe, Node buffers writes and `process.exit()` does not
185
+ // wait for them to flush — drain first so piped output isn't truncated.
186
+ await Promise.all([
187
+ new Promise<void>((resolve) => process.stdout.write("", () => resolve())),
188
+ new Promise<void>((resolve) => process.stderr.write("", () => resolve())),
189
+ ]);
186
190
  process.exit();
187
191
  }
188
192
  void main();