convex 1.36.0 → 1.36.1

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 (41) hide show
  1. package/CHANGELOG.md +5 -4
  2. package/dist/browser.bundle.js +1 -1
  3. package/dist/browser.bundle.js.map +1 -1
  4. package/dist/cjs/cli/envDefault.js +130 -41
  5. package/dist/cjs/cli/envDefault.js.map +3 -3
  6. package/dist/cjs/cli/lib/command.js +1 -1
  7. package/dist/cjs/cli/lib/command.js.map +1 -1
  8. package/dist/cjs/cli/lib/login.js +51 -0
  9. package/dist/cjs/cli/lib/login.js.map +3 -3
  10. package/dist/cjs/index.js +1 -1
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs-types/cli/envDefault.d.ts +2 -2
  13. package/dist/cjs-types/cli/envDefault.d.ts.map +1 -1
  14. package/dist/cjs-types/cli/envDefault.test.d.ts +2 -0
  15. package/dist/cjs-types/cli/envDefault.test.d.ts.map +1 -0
  16. package/dist/cjs-types/cli/lib/login.d.ts.map +1 -1
  17. package/dist/cjs-types/index.d.ts +1 -1
  18. package/dist/cli.bundle.cjs +186 -48
  19. package/dist/cli.bundle.cjs.map +4 -4
  20. package/dist/esm/cli/envDefault.js +131 -42
  21. package/dist/esm/cli/envDefault.js.map +3 -3
  22. package/dist/esm/cli/lib/command.js +1 -1
  23. package/dist/esm/cli/lib/command.js.map +1 -1
  24. package/dist/esm/cli/lib/login.js +52 -0
  25. package/dist/esm/cli/lib/login.js.map +3 -3
  26. package/dist/esm/index.js +1 -1
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm-types/cli/envDefault.d.ts +2 -2
  29. package/dist/esm-types/cli/envDefault.d.ts.map +1 -1
  30. package/dist/esm-types/cli/envDefault.test.d.ts +2 -0
  31. package/dist/esm-types/cli/envDefault.test.d.ts.map +1 -0
  32. package/dist/esm-types/cli/lib/login.d.ts.map +1 -1
  33. package/dist/esm-types/index.d.ts +1 -1
  34. package/dist/react.bundle.js +1 -1
  35. package/dist/react.bundle.js.map +1 -1
  36. package/package.json +4 -4
  37. package/src/cli/envDefault.test.ts +495 -0
  38. package/src/cli/envDefault.ts +222 -107
  39. package/src/cli/lib/command.ts +1 -1
  40. package/src/cli/lib/login.ts +67 -0
  41. package/src/index.ts +1 -1
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.36.0",
4
+ "version": "1.36.1",
5
5
  "author": "Convex, Inc. <no-reply@convex.dev>",
6
6
  "homepage": "https://convex.dev",
7
7
  "repository": {
@@ -178,7 +178,7 @@
178
178
  "peerDependencies": {
179
179
  "@auth0/auth0-react": "^2.0.1",
180
180
  "@clerk/clerk-react": "^4.12.8 || ^5.0.0",
181
- "@clerk/react": "^6.0.0",
181
+ "@clerk/react": "^6.4.3",
182
182
  "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0"
183
183
  },
184
184
  "peerDependenciesMeta": {
@@ -203,14 +203,14 @@
203
203
  "@auth0/auth0-react": "2.15.1",
204
204
  "@babel/parser": "^7.27.1",
205
205
  "@babel/types": "^7.27.1",
206
- "@clerk/react": "^6.0.0",
206
+ "@clerk/react": "^6.4.3",
207
207
  "@commander-js/extra-typings": "^14.0.0",
208
208
  "@convex-dev/platform": "0.1.8",
209
209
  "@eslint/compat": "~2.0.0",
210
210
  "@eslint/eslintrc": "^3",
211
211
  "@eslint/js": "~9.39.0",
212
212
  "@modelcontextprotocol/sdk": "^1.25.2",
213
- "@octokit/openapi-types": "~25.1.0",
213
+ "@octokit/openapi-types": "~27.0.0",
214
214
  "@sentry/node": "^7.23.0",
215
215
  "@sentry/tracing": "^7.23.0",
216
216
  "@swc/core": "1.15.8",
@@ -0,0 +1,495 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { nodeFs } from "../bundler/fs.js";
3
+ import { env } from "./env.js";
4
+ import { bigBrainAPI, bigBrainAPIMaybeThrows } from "./lib/utils/utils.js";
5
+ import { readGlobalConfig } from "./lib/utils/globalConfig.js";
6
+
7
+ vi.mock("../bundler/fs.js", async (importOriginal) => {
8
+ const actual = await importOriginal<typeof import("../bundler/fs.js")>();
9
+ return {
10
+ ...actual,
11
+ nodeFs: {
12
+ ...actual.nodeFs,
13
+ exists: vi.fn().mockImplementation(() => {
14
+ throw new Error("nodeFs.exists should be mocked in test");
15
+ }),
16
+ readUtf8File: vi.fn().mockImplementation(() => {
17
+ throw new Error("nodeFs.readUtf8File should be mocked in test");
18
+ }),
19
+ },
20
+ };
21
+ });
22
+
23
+ const mockPlatformGet = vi.fn();
24
+ const mockPlatformPost = vi.fn();
25
+
26
+ vi.mock("./lib/utils/utils.js", async (importOriginal) => {
27
+ const actual = await importOriginal<typeof import("./lib/utils/utils.js")>();
28
+ return {
29
+ ...actual,
30
+ deploymentFetch: vi.fn(),
31
+ ensureHasConvexDependency: vi.fn(),
32
+ bigBrainAPI: vi.fn(),
33
+ bigBrainAPIMaybeThrows: vi.fn(),
34
+ validateOrSelectTeam: vi.fn(),
35
+ validateOrSelectProject: vi.fn(),
36
+ typedPlatformClient: vi.fn(() => ({
37
+ GET: mockPlatformGet,
38
+ POST: mockPlatformPost,
39
+ })),
40
+ };
41
+ });
42
+
43
+ vi.mock("./lib/utils/globalConfig.js", async (importOriginal) => {
44
+ const actual =
45
+ await importOriginal<typeof import("./lib/utils/globalConfig.js")>();
46
+ return {
47
+ ...actual,
48
+ readGlobalConfig: vi.fn().mockReturnValue(null),
49
+ };
50
+ });
51
+
52
+ vi.mock("dotenv", async (importOriginal) => {
53
+ const actual = await importOriginal<typeof import("dotenv")>();
54
+ return {
55
+ ...actual,
56
+ config: vi.fn(),
57
+ };
58
+ });
59
+
60
+ vi.mock("@sentry/node", () => ({
61
+ captureException: vi.fn(),
62
+ close: vi.fn(),
63
+ }));
64
+
65
+ function setupBigBrainRoutes(routes: Record<string, (data?: any) => any>) {
66
+ const handler = (args: { path: string; data?: any }) => {
67
+ for (const [routePath, routeHandler] of Object.entries(routes)) {
68
+ if (args.path === routePath || args.path.startsWith(routePath)) {
69
+ return routeHandler(args.data);
70
+ }
71
+ }
72
+ throw new Error(`Unmocked Big Brain route: ${args.path}`);
73
+ };
74
+ vi.mocked(bigBrainAPI).mockImplementation(handler as any);
75
+ vi.mocked(bigBrainAPIMaybeThrows).mockImplementation(handler as any);
76
+ }
77
+
78
+ describe("env default", () => {
79
+ let savedEnv: NodeJS.ProcessEnv;
80
+ let savedIsTTY: boolean | undefined;
81
+
82
+ beforeEach(() => {
83
+ savedEnv = { ...process.env };
84
+ savedIsTTY = process.stdin.isTTY;
85
+ process.env = {};
86
+ process.stdin.isTTY = true as any;
87
+
88
+ vi.resetAllMocks();
89
+ vi.mocked(readGlobalConfig).mockReturnValue(null);
90
+ vi.mocked(nodeFs.exists).mockReturnValue(false);
91
+
92
+ mockPlatformGet.mockReset();
93
+ mockPlatformPost.mockReset();
94
+ });
95
+
96
+ afterEach(() => {
97
+ process.env = savedEnv;
98
+ process.stdin.isTTY = savedIsTTY as any;
99
+ });
100
+
101
+ beforeEach(() => {
102
+ vi.spyOn(process, "exit").mockImplementation((() => {
103
+ throw new Error("process.exit called");
104
+ }) as any);
105
+ vi.spyOn(process.stderr, "write").mockImplementation(() => true);
106
+ vi.spyOn(process.stdout, "write").mockImplementation(() => true);
107
+ });
108
+
109
+ afterEach(() => {
110
+ vi.restoreAllMocks();
111
+ });
112
+
113
+ function setupDefaultEnvPlatformMocks({
114
+ projectId = 42,
115
+ teamSlug = "my-team",
116
+ projectSlug = "my-project",
117
+ }: {
118
+ projectId?: number;
119
+ teamSlug?: string;
120
+ projectSlug?: string;
121
+ } = {}) {
122
+ mockPlatformGet.mockImplementation((path: string, _opts: any) => {
123
+ if (path === "/teams/{team_id_or_slug}/projects/{project_slug}") {
124
+ return {
125
+ data: {
126
+ id: projectId,
127
+ slug: projectSlug,
128
+ name: projectSlug,
129
+ teamId: 1,
130
+ teamSlug,
131
+ createTime: 0,
132
+ },
133
+ };
134
+ }
135
+ if (
136
+ path === "/projects/{project_id}/list_default_environment_variables"
137
+ ) {
138
+ return { data: { items: [] } };
139
+ }
140
+ throw new Error(`Unmocked platform GET: ${path}`);
141
+ });
142
+ mockPlatformPost.mockImplementation(async (path: string, _opts: any) => {
143
+ if (
144
+ path === "/projects/{project_id}/update_default_environment_variables"
145
+ ) {
146
+ return { data: {} };
147
+ }
148
+ throw new Error(`Unmocked platform POST: ${path}`);
149
+ });
150
+ }
151
+
152
+ describe("default behavior (no --type, no --project)", () => {
153
+ it("uses current deployment's project and inferred dtype", async () => {
154
+ process.env.CONVEX_DEPLOYMENT = "dev:foo-bar-123";
155
+ vi.mocked(readGlobalConfig).mockReturnValue({
156
+ accessToken: "test-token",
157
+ });
158
+ setupBigBrainRoutes({
159
+ "deployment/foo-bar-123/team_and_project": () => ({
160
+ team: "my-team",
161
+ project: "my-project",
162
+ teamId: 1,
163
+ projectId: 42,
164
+ }),
165
+ "deployment/authorize_within_current_project": () => ({
166
+ adminKey: "dev-key",
167
+ url: "https://foo-bar-123.convex.cloud",
168
+ deploymentName: "foo-bar-123",
169
+ deploymentType: "dev",
170
+ }),
171
+ });
172
+ setupDefaultEnvPlatformMocks({ projectId: 42 });
173
+
174
+ await env.parseAsync(["default", "set", "ABC", "DEF"], { from: "user" });
175
+
176
+ expect(mockPlatformPost).toHaveBeenCalledWith(
177
+ "/projects/{project_id}/update_default_environment_variables",
178
+ expect.objectContaining({
179
+ params: { path: { project_id: 42 } },
180
+ body: {
181
+ changes: [{ name: "ABC", deploymentType: "dev", value: "DEF" }],
182
+ },
183
+ }),
184
+ );
185
+ });
186
+ });
187
+
188
+ describe("--type override", () => {
189
+ it("--type prod overrides dtype, still uses current deployment's project", async () => {
190
+ process.env.CONVEX_DEPLOYMENT = "dev:foo-bar-123";
191
+ vi.mocked(readGlobalConfig).mockReturnValue({
192
+ accessToken: "test-token",
193
+ });
194
+ setupBigBrainRoutes({
195
+ "deployment/foo-bar-123/team_and_project": () => ({
196
+ team: "my-team",
197
+ project: "my-project",
198
+ teamId: 1,
199
+ projectId: 42,
200
+ }),
201
+ "deployment/authorize_within_current_project": () => ({
202
+ adminKey: "dev-key",
203
+ url: "https://foo-bar-123.convex.cloud",
204
+ deploymentName: "foo-bar-123",
205
+ deploymentType: "dev",
206
+ }),
207
+ });
208
+ setupDefaultEnvPlatformMocks({ projectId: 42 });
209
+
210
+ await env.parseAsync(["default", "set", "ABC", "DEF", "--type", "prod"], {
211
+ from: "user",
212
+ });
213
+
214
+ expect(mockPlatformPost).toHaveBeenCalledWith(
215
+ "/projects/{project_id}/update_default_environment_variables",
216
+ expect.objectContaining({
217
+ params: { path: { project_id: 42 } },
218
+ body: {
219
+ changes: [{ name: "ABC", deploymentType: "prod", value: "DEF" }],
220
+ },
221
+ }),
222
+ );
223
+ });
224
+
225
+ it("--type production aliases to prod", async () => {
226
+ process.env.CONVEX_DEPLOYMENT = "dev:foo-bar-123";
227
+ vi.mocked(readGlobalConfig).mockReturnValue({
228
+ accessToken: "test-token",
229
+ });
230
+ setupBigBrainRoutes({
231
+ "deployment/foo-bar-123/team_and_project": () => ({
232
+ team: "my-team",
233
+ project: "my-project",
234
+ teamId: 1,
235
+ projectId: 42,
236
+ }),
237
+ "deployment/authorize_within_current_project": () => ({
238
+ adminKey: "dev-key",
239
+ url: "https://foo-bar-123.convex.cloud",
240
+ deploymentName: "foo-bar-123",
241
+ deploymentType: "dev",
242
+ }),
243
+ });
244
+ setupDefaultEnvPlatformMocks({ projectId: 42 });
245
+
246
+ await env.parseAsync(["default", "list", "--type", "production"], {
247
+ from: "user",
248
+ });
249
+
250
+ expect(mockPlatformGet).toHaveBeenCalledWith(
251
+ "/projects/{project_id}/list_default_environment_variables",
252
+ expect.objectContaining({
253
+ params: {
254
+ path: { project_id: 42 },
255
+ query: { deploymentType: "prod" },
256
+ },
257
+ }),
258
+ );
259
+ });
260
+
261
+ it("--type development aliases to dev", async () => {
262
+ process.env.CONVEX_DEPLOYMENT = "prod:foo-bar-123";
263
+ vi.mocked(readGlobalConfig).mockReturnValue({
264
+ accessToken: "test-token",
265
+ });
266
+ setupBigBrainRoutes({
267
+ "deployment/foo-bar-123/team_and_project": () => ({
268
+ team: "my-team",
269
+ project: "my-project",
270
+ teamId: 1,
271
+ projectId: 42,
272
+ }),
273
+ "deployment/authorize_within_current_project": () => ({
274
+ adminKey: "dev-key",
275
+ url: "https://foo-bar-123.convex.cloud",
276
+ deploymentName: "foo-bar-123",
277
+ deploymentType: "prod",
278
+ }),
279
+ });
280
+ setupDefaultEnvPlatformMocks({ projectId: 42 });
281
+
282
+ await env.parseAsync(["default", "list", "--type", "development"], {
283
+ from: "user",
284
+ });
285
+
286
+ expect(mockPlatformGet).toHaveBeenCalledWith(
287
+ "/projects/{project_id}/list_default_environment_variables",
288
+ expect.objectContaining({
289
+ params: {
290
+ path: { project_id: 42 },
291
+ query: { deploymentType: "dev" },
292
+ },
293
+ }),
294
+ );
295
+ });
296
+ });
297
+
298
+ describe("--project", () => {
299
+ it("--project team:proj --type prod does not require a current deployment", async () => {
300
+ vi.mocked(readGlobalConfig).mockReturnValue({
301
+ accessToken: "test-token",
302
+ });
303
+ setupBigBrainRoutes({});
304
+ setupDefaultEnvPlatformMocks({
305
+ projectId: 99,
306
+ teamSlug: "my-team",
307
+ projectSlug: "my-proj",
308
+ });
309
+
310
+ await env.parseAsync(
311
+ [
312
+ "default",
313
+ "set",
314
+ "ABC",
315
+ "DEF",
316
+ "--project",
317
+ "my-team:my-proj",
318
+ "--type",
319
+ "prod",
320
+ ],
321
+ { from: "user" },
322
+ );
323
+
324
+ expect(bigBrainAPI).not.toHaveBeenCalled();
325
+ expect(mockPlatformGet).toHaveBeenCalledWith(
326
+ "/teams/{team_id_or_slug}/projects/{project_slug}",
327
+ expect.objectContaining({
328
+ params: {
329
+ path: { team_id_or_slug: "my-team", project_slug: "my-proj" },
330
+ },
331
+ }),
332
+ );
333
+ expect(mockPlatformPost).toHaveBeenCalledWith(
334
+ "/projects/{project_id}/update_default_environment_variables",
335
+ expect.objectContaining({
336
+ params: { path: { project_id: 99 } },
337
+ body: {
338
+ changes: [{ name: "ABC", deploymentType: "prod", value: "DEF" }],
339
+ },
340
+ }),
341
+ );
342
+ });
343
+
344
+ it("--project just-proj --type prod uses current deployment's team", async () => {
345
+ process.env.CONVEX_DEPLOYMENT = "dev:foo-bar-123";
346
+ vi.mocked(readGlobalConfig).mockReturnValue({
347
+ accessToken: "test-token",
348
+ });
349
+ setupBigBrainRoutes({
350
+ "deployment/foo-bar-123/team_and_project": () => ({
351
+ team: "my-team",
352
+ project: "original-project",
353
+ teamId: 1,
354
+ projectId: 42,
355
+ }),
356
+ "deployment/authorize_within_current_project": () => ({
357
+ adminKey: "dev-key",
358
+ url: "https://foo-bar-123.convex.cloud",
359
+ deploymentName: "foo-bar-123",
360
+ deploymentType: "dev",
361
+ }),
362
+ });
363
+ setupDefaultEnvPlatformMocks({
364
+ projectId: 77,
365
+ teamSlug: "my-team",
366
+ projectSlug: "just-proj",
367
+ });
368
+
369
+ await env.parseAsync(
370
+ ["default", "list", "--project", "just-proj", "--type", "prod"],
371
+ { from: "user" },
372
+ );
373
+
374
+ expect(mockPlatformGet).toHaveBeenCalledWith(
375
+ "/teams/{team_id_or_slug}/projects/{project_slug}",
376
+ expect.objectContaining({
377
+ params: {
378
+ path: { team_id_or_slug: "my-team", project_slug: "just-proj" },
379
+ },
380
+ }),
381
+ );
382
+ expect(mockPlatformGet).toHaveBeenCalledWith(
383
+ "/projects/{project_id}/list_default_environment_variables",
384
+ expect.objectContaining({
385
+ params: {
386
+ path: { project_id: 77 },
387
+ query: { deploymentType: "prod" },
388
+ },
389
+ }),
390
+ );
391
+ });
392
+
393
+ it("--project team:proj without --type exits with a fatal error", async () => {
394
+ await expect(
395
+ env.parseAsync(["default", "list", "--project", "my-team:my-proj"], {
396
+ from: "user",
397
+ }),
398
+ ).rejects.toThrow();
399
+ expect(process.stderr.write).toHaveBeenCalledWith(
400
+ expect.stringContaining("--project requires --type"),
401
+ );
402
+ });
403
+
404
+ it("--project a:b:c (too many colons) exits with a fatal error", async () => {
405
+ await expect(
406
+ env.parseAsync(
407
+ ["default", "list", "--project", "a:b:c", "--type", "prod"],
408
+ { from: "user" },
409
+ ),
410
+ ).rejects.toThrow();
411
+ expect(process.stderr.write).toHaveBeenCalledWith(
412
+ expect.stringContaining("--project must be"),
413
+ );
414
+ });
415
+ });
416
+
417
+ describe("--type forwarded values", () => {
418
+ it("invalid --type values are forwarded; backend error surfaces", async () => {
419
+ process.env.CONVEX_DEPLOYMENT = "dev:foo-bar-123";
420
+ vi.mocked(readGlobalConfig).mockReturnValue({
421
+ accessToken: "test-token",
422
+ });
423
+ setupBigBrainRoutes({
424
+ "deployment/foo-bar-123/team_and_project": () => ({
425
+ team: "my-team",
426
+ project: "my-project",
427
+ teamId: 1,
428
+ projectId: 42,
429
+ }),
430
+ "deployment/authorize_within_current_project": () => ({
431
+ adminKey: "dev-key",
432
+ url: "https://foo-bar-123.convex.cloud",
433
+ deploymentName: "foo-bar-123",
434
+ deploymentType: "dev",
435
+ }),
436
+ });
437
+ mockPlatformGet.mockImplementation((path: string) => {
438
+ if (
439
+ path === "/projects/{project_id}/list_default_environment_variables"
440
+ ) {
441
+ throw new Error("backend rejected deploymentType: invalid-type");
442
+ }
443
+ throw new Error(`Unmocked platform GET: ${path}`);
444
+ });
445
+
446
+ await expect(
447
+ env.parseAsync(["default", "list", "--type", "invalid-type"], {
448
+ from: "user",
449
+ }),
450
+ ).rejects.toThrow();
451
+
452
+ expect(mockPlatformGet).toHaveBeenCalledWith(
453
+ "/projects/{project_id}/list_default_environment_variables",
454
+ expect.objectContaining({
455
+ params: expect.objectContaining({
456
+ query: expect.objectContaining({ deploymentType: "invalid-type" }),
457
+ }),
458
+ }),
459
+ );
460
+ });
461
+ });
462
+
463
+ describe("remove subcommand", () => {
464
+ it("--project team:proj --type prod wires through", async () => {
465
+ vi.mocked(readGlobalConfig).mockReturnValue({
466
+ accessToken: "test-token",
467
+ });
468
+ setupBigBrainRoutes({});
469
+ setupDefaultEnvPlatformMocks({ projectId: 55 });
470
+
471
+ await env.parseAsync(
472
+ [
473
+ "default",
474
+ "remove",
475
+ "ABC",
476
+ "--project",
477
+ "my-team:my-proj",
478
+ "--type",
479
+ "prod",
480
+ ],
481
+ { from: "user" },
482
+ );
483
+
484
+ expect(mockPlatformPost).toHaveBeenCalledWith(
485
+ "/projects/{project_id}/update_default_environment_variables",
486
+ expect.objectContaining({
487
+ params: { path: { project_id: 55 } },
488
+ body: {
489
+ changes: [{ name: "ABC", deploymentType: "prod", value: null }],
490
+ },
491
+ }),
492
+ );
493
+ });
494
+ });
495
+ });