convex 1.36.0 → 1.37.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.
- package/CHANGELOG.md +50 -27
- package/dist/browser.bundle.js +1 -1
- package/dist/browser.bundle.js.map +1 -1
- package/dist/cjs/cli/codegen_templates/agentsmd.js +8 -2
- package/dist/cjs/cli/codegen_templates/agentsmd.js.map +2 -2
- package/dist/cjs/cli/codegen_templates/claudemd.js +2 -0
- package/dist/cjs/cli/codegen_templates/claudemd.js.map +2 -2
- package/dist/cjs/cli/configure.js +0 -8
- package/dist/cjs/cli/configure.js.map +2 -2
- package/dist/cjs/cli/deployment.js +2 -1
- package/dist/cjs/cli/deployment.js.map +2 -2
- package/dist/cjs/cli/deploymentToken.js +30 -0
- package/dist/cjs/cli/deploymentToken.js.map +7 -0
- package/dist/cjs/cli/deploymentTokenCreate.js +109 -0
- package/dist/cjs/cli/deploymentTokenCreate.js.map +7 -0
- package/dist/cjs/cli/deploymentTokenDelete.js +87 -0
- package/dist/cjs/cli/deploymentTokenDelete.js.map +7 -0
- package/dist/cjs/cli/envDefault.js +130 -41
- package/dist/cjs/cli/envDefault.js.map +3 -3
- package/dist/cjs/cli/generatedApi.js.map +1 -1
- package/dist/cjs/cli/lib/command.js +1 -1
- package/dist/cjs/cli/lib/command.js.map +1 -1
- package/dist/cjs/cli/lib/generatedFunctionLogsApi.js.map +1 -1
- package/dist/cjs/cli/lib/login.js +51 -0
- package/dist/cjs/cli/lib/login.js.map +3 -3
- package/dist/cjs/cli/lib/usage.js +13 -6
- package/dist/cjs/cli/lib/usage.js.map +2 -2
- package/dist/cjs/cli/lib/workos/environmentApi.js +6 -12
- package/dist/cjs/cli/lib/workos/environmentApi.js.map +3 -3
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/client.js +40 -42
- package/dist/cjs/react/client.js.map +2 -2
- package/dist/cjs/react/index.js +1 -0
- package/dist/cjs/react/index.js.map +2 -2
- package/dist/cjs/react/use_paginated_query.js +5 -46
- package/dist/cjs/react/use_paginated_query.js.map +2 -2
- package/dist/cjs/react/use_paginated_query2.js.map +2 -2
- package/dist/cjs/server/audit_logging.js +67 -0
- package/dist/cjs/server/audit_logging.js.map +7 -0
- package/dist/cjs/server/impl/meta_impl.js +27 -3
- package/dist/cjs/server/impl/meta_impl.js.map +2 -2
- package/dist/cjs/server/impl/registration_impl.js +2 -0
- package/dist/cjs/server/impl/registration_impl.js.map +2 -2
- package/dist/cjs/server/index.js +2 -0
- package/dist/cjs/server/index.js.map +2 -2
- package/dist/cjs/server/log.js +30 -0
- package/dist/cjs/server/log.js.map +7 -0
- package/dist/cjs/server/logVars.js +48 -0
- package/dist/cjs/server/logVars.js.map +7 -0
- package/dist/cjs/server/meta.js.map +1 -1
- package/dist/cjs/server/registration.js.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/agentsmd.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/claudemd.d.ts.map +1 -1
- package/dist/cjs-types/cli/configure.d.ts.map +1 -1
- package/dist/cjs-types/cli/deployment.d.ts.map +1 -1
- package/dist/cjs-types/cli/deploymentToken.d.ts +3 -0
- package/dist/cjs-types/cli/deploymentToken.d.ts.map +1 -0
- package/dist/cjs-types/cli/deploymentToken.test.d.ts +2 -0
- package/dist/cjs-types/cli/deploymentToken.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/deploymentTokenCreate.d.ts +13 -0
- package/dist/cjs-types/cli/deploymentTokenCreate.d.ts.map +1 -0
- package/dist/cjs-types/cli/deploymentTokenDelete.d.ts +11 -0
- package/dist/cjs-types/cli/deploymentTokenDelete.d.ts.map +1 -0
- package/dist/cjs-types/cli/envDefault.d.ts +2 -2
- package/dist/cjs-types/cli/envDefault.d.ts.map +1 -1
- package/dist/cjs-types/cli/envDefault.test.d.ts +2 -0
- package/dist/cjs-types/cli/envDefault.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/generatedApi.d.ts +1 -1
- package/dist/cjs-types/cli/generatedApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts +1 -0
- package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/login.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/usage.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/workos/environmentApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/workos/environmentApi.test.d.ts +2 -0
- package/dist/cjs-types/cli/lib/workos/environmentApi.test.d.ts.map +1 -0
- package/dist/cjs-types/index.d.ts +1 -1
- package/dist/cjs-types/react/client.d.ts +52 -0
- package/dist/cjs-types/react/client.d.ts.map +1 -1
- package/dist/cjs-types/react/index.d.ts +2 -2
- package/dist/cjs-types/react/index.d.ts.map +1 -1
- package/dist/cjs-types/react/use_paginated_query.d.ts.map +1 -1
- package/dist/cjs-types/react/use_paginated_query2.d.ts +63 -1
- package/dist/cjs-types/react/use_paginated_query2.d.ts.map +1 -1
- package/dist/cjs-types/server/api.intersect.test.d.ts +2 -0
- package/dist/cjs-types/server/api.intersect.test.d.ts.map +1 -0
- package/dist/cjs-types/server/audit_logging.d.ts +19 -0
- package/dist/cjs-types/server/audit_logging.d.ts.map +1 -0
- package/dist/cjs-types/server/audit_logging.test.d.ts +2 -0
- package/dist/cjs-types/server/audit_logging.test.d.ts.map +1 -0
- package/dist/cjs-types/server/impl/meta_impl.d.ts.map +1 -1
- package/dist/cjs-types/server/impl/registration_impl.d.ts.map +1 -1
- package/dist/cjs-types/server/index.d.ts +2 -2
- package/dist/cjs-types/server/index.d.ts.map +1 -1
- package/dist/cjs-types/server/log.d.ts +2 -0
- package/dist/cjs-types/server/log.d.ts.map +1 -0
- package/dist/cjs-types/server/logVars.d.ts +20 -0
- package/dist/cjs-types/server/logVars.d.ts.map +1 -0
- package/dist/cjs-types/server/meta.d.ts +40 -0
- package/dist/cjs-types/server/meta.d.ts.map +1 -1
- package/dist/cjs-types/server/registration.d.ts +5 -2
- package/dist/cjs-types/server/registration.d.ts.map +1 -1
- package/dist/cli.bundle.cjs +362 -74
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/esm/cli/codegen_templates/agentsmd.js +8 -2
- package/dist/esm/cli/codegen_templates/agentsmd.js.map +2 -2
- package/dist/esm/cli/codegen_templates/claudemd.js +2 -0
- package/dist/esm/cli/codegen_templates/claudemd.js.map +2 -2
- package/dist/esm/cli/configure.js +0 -8
- package/dist/esm/cli/configure.js.map +2 -2
- package/dist/esm/cli/deployment.js +2 -1
- package/dist/esm/cli/deployment.js.map +2 -2
- package/dist/esm/cli/deploymentToken.js +8 -0
- package/dist/esm/cli/deploymentToken.js.map +7 -0
- package/dist/esm/cli/deploymentTokenCreate.js +91 -0
- package/dist/esm/cli/deploymentTokenCreate.js.map +7 -0
- package/dist/esm/cli/deploymentTokenDelete.js +68 -0
- package/dist/esm/cli/deploymentTokenDelete.js.map +7 -0
- package/dist/esm/cli/envDefault.js +131 -42
- package/dist/esm/cli/envDefault.js.map +3 -3
- package/dist/esm/cli/lib/command.js +1 -1
- package/dist/esm/cli/lib/command.js.map +1 -1
- package/dist/esm/cli/lib/login.js +52 -0
- package/dist/esm/cli/lib/login.js.map +3 -3
- package/dist/esm/cli/lib/usage.js +15 -8
- package/dist/esm/cli/lib/usage.js.map +2 -2
- package/dist/esm/cli/lib/workos/environmentApi.js +6 -12
- package/dist/esm/cli/lib/workos/environmentApi.js.map +3 -3
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/client.js +38 -41
- package/dist/esm/react/client.js.map +2 -2
- package/dist/esm/react/index.js +4 -1
- package/dist/esm/react/index.js.map +2 -2
- package/dist/esm/react/use_paginated_query.js +5 -46
- package/dist/esm/react/use_paginated_query.js.map +2 -2
- package/dist/esm/react/use_paginated_query2.js.map +2 -2
- package/dist/esm/server/audit_logging.js +44 -0
- package/dist/esm/server/audit_logging.js.map +7 -0
- package/dist/esm/server/impl/meta_impl.js +27 -3
- package/dist/esm/server/impl/meta_impl.js.map +2 -2
- package/dist/esm/server/impl/registration_impl.js +2 -0
- package/dist/esm/server/impl/registration_impl.js.map +2 -2
- package/dist/esm/server/index.js +1 -0
- package/dist/esm/server/index.js.map +2 -2
- package/dist/esm/server/log.js +8 -0
- package/dist/esm/server/log.js.map +7 -0
- package/dist/esm/server/logVars.js +25 -0
- package/dist/esm/server/logVars.js.map +7 -0
- package/dist/esm-types/cli/codegen_templates/agentsmd.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/claudemd.d.ts.map +1 -1
- package/dist/esm-types/cli/configure.d.ts.map +1 -1
- package/dist/esm-types/cli/deployment.d.ts.map +1 -1
- package/dist/esm-types/cli/deploymentToken.d.ts +3 -0
- package/dist/esm-types/cli/deploymentToken.d.ts.map +1 -0
- package/dist/esm-types/cli/deploymentToken.test.d.ts +2 -0
- package/dist/esm-types/cli/deploymentToken.test.d.ts.map +1 -0
- package/dist/esm-types/cli/deploymentTokenCreate.d.ts +13 -0
- package/dist/esm-types/cli/deploymentTokenCreate.d.ts.map +1 -0
- package/dist/esm-types/cli/deploymentTokenDelete.d.ts +11 -0
- package/dist/esm-types/cli/deploymentTokenDelete.d.ts.map +1 -0
- package/dist/esm-types/cli/envDefault.d.ts +2 -2
- package/dist/esm-types/cli/envDefault.d.ts.map +1 -1
- package/dist/esm-types/cli/envDefault.test.d.ts +2 -0
- package/dist/esm-types/cli/envDefault.test.d.ts.map +1 -0
- package/dist/esm-types/cli/generatedApi.d.ts +1 -1
- package/dist/esm-types/cli/generatedApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts +1 -0
- package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/login.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/usage.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/workos/environmentApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/workos/environmentApi.test.d.ts +2 -0
- package/dist/esm-types/cli/lib/workos/environmentApi.test.d.ts.map +1 -0
- package/dist/esm-types/index.d.ts +1 -1
- package/dist/esm-types/react/client.d.ts +52 -0
- package/dist/esm-types/react/client.d.ts.map +1 -1
- package/dist/esm-types/react/index.d.ts +2 -2
- package/dist/esm-types/react/index.d.ts.map +1 -1
- package/dist/esm-types/react/use_paginated_query.d.ts.map +1 -1
- package/dist/esm-types/react/use_paginated_query2.d.ts +63 -1
- package/dist/esm-types/react/use_paginated_query2.d.ts.map +1 -1
- package/dist/esm-types/server/api.intersect.test.d.ts +2 -0
- package/dist/esm-types/server/api.intersect.test.d.ts.map +1 -0
- package/dist/esm-types/server/audit_logging.d.ts +19 -0
- package/dist/esm-types/server/audit_logging.d.ts.map +1 -0
- package/dist/esm-types/server/audit_logging.test.d.ts +2 -0
- package/dist/esm-types/server/audit_logging.test.d.ts.map +1 -0
- package/dist/esm-types/server/impl/meta_impl.d.ts.map +1 -1
- package/dist/esm-types/server/impl/registration_impl.d.ts.map +1 -1
- package/dist/esm-types/server/index.d.ts +2 -2
- package/dist/esm-types/server/index.d.ts.map +1 -1
- package/dist/esm-types/server/log.d.ts +2 -0
- package/dist/esm-types/server/log.d.ts.map +1 -0
- package/dist/esm-types/server/logVars.d.ts +20 -0
- package/dist/esm-types/server/logVars.d.ts.map +1 -0
- package/dist/esm-types/server/meta.d.ts +40 -0
- package/dist/esm-types/server/meta.d.ts.map +1 -1
- package/dist/esm-types/server/registration.d.ts +5 -2
- package/dist/esm-types/server/registration.d.ts.map +1 -1
- package/dist/react.bundle.js +45 -88
- package/dist/react.bundle.js.map +2 -2
- package/package.json +4 -4
- package/src/cli/codegen_templates/agentsmd.ts +8 -2
- package/src/cli/codegen_templates/claudemd.ts +2 -0
- package/src/cli/configure.ts +0 -9
- package/src/cli/deployment.ts +3 -1
- package/src/cli/deploymentToken.test.ts +372 -0
- package/src/cli/deploymentToken.ts +11 -0
- package/src/cli/deploymentTokenCreate.ts +113 -0
- package/src/cli/deploymentTokenDelete.ts +91 -0
- package/src/cli/envDefault.test.ts +495 -0
- package/src/cli/envDefault.ts +222 -107
- package/src/cli/generatedApi.ts +1 -1
- package/src/cli/lib/command.ts +1 -1
- package/src/cli/lib/generatedFunctionLogsApi.ts +1 -0
- package/src/cli/lib/login.ts +67 -0
- package/src/cli/lib/usage.ts +18 -8
- package/src/cli/lib/workos/environmentApi.test.ts +107 -0
- package/src/cli/lib/workos/environmentApi.ts +12 -19
- package/src/index.ts +1 -1
- package/src/react/client.test.tsx +10 -8
- package/src/react/client.ts +88 -96
- package/src/react/index.ts +6 -1
- package/src/react/use_paginated_query.test.tsx +215 -132
- package/src/react/use_paginated_query.ts +8 -142
- package/src/react/use_paginated_query2.ts +78 -5
- package/src/react/use_query_object_options.test.ts +8 -7
- package/src/react/use_query_result.test.ts +40 -7
- package/src/server/api.intersect.test.ts +109 -0
- package/src/server/audit_logging.test.ts +129 -0
- package/src/server/audit_logging.ts +75 -0
- package/src/server/impl/meta_impl.ts +28 -0
- package/src/server/impl/registration_impl.ts +2 -0
- package/src/server/index.ts +12 -0
- package/src/server/log.ts +16 -0
- package/src/server/logVars.ts +34 -0
- package/src/server/meta.ts +53 -1
- package/src/server/registration.ts +10 -8
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.
|
|
4
|
+
"version": "1.37.0",
|
|
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.
|
|
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.
|
|
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": "~
|
|
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",
|
|
@@ -5,9 +5,13 @@ export function convexAiMarkdownBody(convexDir: string): string {
|
|
|
5
5
|
const normalizedConvexDir = convexDir.replaceAll("\\", "/");
|
|
6
6
|
return `This project uses [Convex](https://convex.dev) as its backend.
|
|
7
7
|
|
|
8
|
-
When working on Convex code, **always read
|
|
8
|
+
When working on Convex code, **always read
|
|
9
|
+
\`${normalizedConvexDir}/_generated/ai/guidelines.md\` first** for important guidelines on
|
|
10
|
+
how to correctly use Convex APIs and patterns. The file contains rules that
|
|
11
|
+
override what you may have learned about Convex from training data.
|
|
9
12
|
|
|
10
|
-
Convex agent skills for common tasks can be installed by running
|
|
13
|
+
Convex agent skills for common tasks can be installed by running
|
|
14
|
+
\`npx convex ai-files install\`.`;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
/**
|
|
@@ -17,6 +21,8 @@ Convex agent skills for common tasks can be installed by running \`npx convex ai
|
|
|
17
21
|
*/
|
|
18
22
|
export function agentsMdConvexSection(convexDir: string): string {
|
|
19
23
|
return `${AGENTS_MD_START_MARKER}
|
|
24
|
+
|
|
20
25
|
${convexAiMarkdownBody(convexDir)}
|
|
26
|
+
|
|
21
27
|
${AGENTS_MD_END_MARKER}`;
|
|
22
28
|
}
|
package/src/cli/configure.ts
CHANGED
|
@@ -671,15 +671,6 @@ async function selectExistingProject(
|
|
|
671
671
|
|
|
672
672
|
logFinishedStep(`Reinitialized project ${chalkStderr.bold(projectSlug)}`);
|
|
673
673
|
|
|
674
|
-
const { configPath, projectConfig } = await readProjectConfig(ctx);
|
|
675
|
-
const folder = functionsDir(configPath, projectConfig);
|
|
676
|
-
await attemptSetupAiFiles({
|
|
677
|
-
ctx,
|
|
678
|
-
aiFilesConfig: projectConfig.aiFiles,
|
|
679
|
-
convexDir: path.resolve(folder),
|
|
680
|
-
projectDir: path.resolve(path.dirname(configPath)),
|
|
681
|
-
});
|
|
682
|
-
|
|
683
674
|
return { teamSlug, projectSlug, devDeployment };
|
|
684
675
|
}
|
|
685
676
|
|
package/src/cli/deployment.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Command } from "@commander-js/extra-typings";
|
|
2
2
|
import { deploymentSelect } from "./deploymentSelect.js";
|
|
3
3
|
import { deploymentCreate } from "./deploymentCreate.js";
|
|
4
|
+
import { deploymentToken } from "./deploymentToken.js";
|
|
4
5
|
|
|
5
6
|
export const deployment = new Command("deployment")
|
|
6
7
|
.summary("Manage deployments")
|
|
7
8
|
.description("Manage deployments in your project.")
|
|
8
9
|
.addCommand(deploymentSelect)
|
|
9
|
-
.addCommand(deploymentCreate)
|
|
10
|
+
.addCommand(deploymentCreate)
|
|
11
|
+
.addCommand(deploymentToken);
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { nodeFs } from "../bundler/fs.js";
|
|
4
|
+
import { deploymentTokenCreate } from "./deploymentTokenCreate.js";
|
|
5
|
+
import { deploymentTokenDelete } from "./deploymentTokenDelete.js";
|
|
6
|
+
import { typedPlatformClient } from "./lib/utils/utils.js";
|
|
7
|
+
import {
|
|
8
|
+
getDeploymentSelection,
|
|
9
|
+
initializeBigBrainAuth,
|
|
10
|
+
} from "./lib/deploymentSelection.js";
|
|
11
|
+
import { loadSelectedDeploymentCredentials } from "./lib/api.js";
|
|
12
|
+
import type { Context } from "../bundler/context.js";
|
|
13
|
+
|
|
14
|
+
vi.mock("@sentry/node", () => ({
|
|
15
|
+
captureException: vi.fn(),
|
|
16
|
+
close: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock("../bundler/fs.js", async (importOriginal) => {
|
|
20
|
+
const actual = await importOriginal<typeof import("../bundler/fs.js")>();
|
|
21
|
+
return {
|
|
22
|
+
...actual,
|
|
23
|
+
nodeFs: {
|
|
24
|
+
...actual.nodeFs,
|
|
25
|
+
exists: vi.fn(),
|
|
26
|
+
readUtf8File: vi.fn(),
|
|
27
|
+
writeUtf8File: vi.fn(),
|
|
28
|
+
mkdir: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const mockPlatformPost = vi.fn();
|
|
34
|
+
|
|
35
|
+
vi.mock("./lib/utils/utils.js", async (importOriginal) => {
|
|
36
|
+
const actual = await importOriginal<typeof import("./lib/utils/utils.js")>();
|
|
37
|
+
return {
|
|
38
|
+
...actual,
|
|
39
|
+
typedPlatformClient: vi.fn(() => ({ POST: mockPlatformPost })),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
vi.mock("./lib/deploymentSelection.js", async (importOriginal) => {
|
|
44
|
+
const actual =
|
|
45
|
+
await importOriginal<typeof import("./lib/deploymentSelection.js")>();
|
|
46
|
+
return {
|
|
47
|
+
...actual,
|
|
48
|
+
initializeBigBrainAuth: vi.fn(),
|
|
49
|
+
getDeploymentSelection: vi.fn(),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
vi.mock("./lib/api.js", async (importOriginal) => {
|
|
54
|
+
const actual = await importOriginal<typeof import("./lib/api.js")>();
|
|
55
|
+
return {
|
|
56
|
+
...actual,
|
|
57
|
+
loadSelectedDeploymentCredentials: vi.fn(),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Default in-memory FS used when not overridden by a specific test.
|
|
62
|
+
let testFiles: Map<string, string>;
|
|
63
|
+
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
64
|
+
|
|
65
|
+
function setAuthKind(
|
|
66
|
+
kind: "accessToken" | "deploymentKey" | "projectKey" | "previewDeployKey",
|
|
67
|
+
) {
|
|
68
|
+
vi.mocked(initializeBigBrainAuth).mockImplementation(async (ctx) => {
|
|
69
|
+
const fakeAuth =
|
|
70
|
+
kind === "accessToken"
|
|
71
|
+
? {
|
|
72
|
+
kind: "accessToken" as const,
|
|
73
|
+
accessToken: "test-token",
|
|
74
|
+
header: "Bearer test-token",
|
|
75
|
+
}
|
|
76
|
+
: kind === "deploymentKey"
|
|
77
|
+
? {
|
|
78
|
+
kind: "deploymentKey" as const,
|
|
79
|
+
deploymentKey: "dev:foo|secret",
|
|
80
|
+
header: "Bearer dev:foo|secret",
|
|
81
|
+
}
|
|
82
|
+
: kind === "projectKey"
|
|
83
|
+
? {
|
|
84
|
+
kind: "projectKey" as const,
|
|
85
|
+
projectKey: "project:foo|secret",
|
|
86
|
+
header: "Bearer project:foo|secret",
|
|
87
|
+
}
|
|
88
|
+
: {
|
|
89
|
+
kind: "previewDeployKey" as const,
|
|
90
|
+
previewDeployKey: "preview:t:p|secret",
|
|
91
|
+
header: "Bearer preview:t:p|secret",
|
|
92
|
+
};
|
|
93
|
+
(ctx as Context)._updateBigBrainAuth(fakeAuth);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function setNoAuth() {
|
|
98
|
+
vi.mocked(initializeBigBrainAuth).mockImplementation(async (ctx) => {
|
|
99
|
+
(ctx as Context)._updateBigBrainAuth(null);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function mockSelectedDeployment(
|
|
104
|
+
deploymentName: string,
|
|
105
|
+
deploymentType: "dev" | "prod" | "preview" | "custom" | "local" | "anonymous",
|
|
106
|
+
) {
|
|
107
|
+
vi.mocked(getDeploymentSelection).mockResolvedValue({
|
|
108
|
+
kind: "existingDeployment",
|
|
109
|
+
deploymentToActOn: {
|
|
110
|
+
url: `https://${deploymentName}.convex.cloud`,
|
|
111
|
+
adminKey: "admin-key",
|
|
112
|
+
source: "deployKey",
|
|
113
|
+
deploymentFields: {
|
|
114
|
+
deploymentName,
|
|
115
|
+
deploymentType,
|
|
116
|
+
teamSlug: "my-team",
|
|
117
|
+
projectSlug: "my-project",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
vi.mocked(loadSelectedDeploymentCredentials).mockResolvedValue({
|
|
122
|
+
url: `https://${deploymentName}.convex.cloud`,
|
|
123
|
+
adminKey: "admin-key",
|
|
124
|
+
deploymentFields: {
|
|
125
|
+
deploymentName,
|
|
126
|
+
deploymentType,
|
|
127
|
+
teamSlug: "my-team",
|
|
128
|
+
projectSlug: "my-project",
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
vi.resetAllMocks();
|
|
135
|
+
|
|
136
|
+
// Set up an in-memory FS that the env-file write/read paths can use.
|
|
137
|
+
testFiles = new Map();
|
|
138
|
+
vi.mocked(nodeFs.exists).mockImplementation((p: string) =>
|
|
139
|
+
testFiles.has(path.resolve(p)),
|
|
140
|
+
);
|
|
141
|
+
vi.mocked(nodeFs.readUtf8File).mockImplementation((p: string) => {
|
|
142
|
+
const content = testFiles.get(path.resolve(p));
|
|
143
|
+
if (content === undefined) {
|
|
144
|
+
const err: any = new Error(
|
|
145
|
+
`ENOENT: no such file or directory, open '${p}'`,
|
|
146
|
+
);
|
|
147
|
+
err.code = "ENOENT";
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
return content;
|
|
151
|
+
});
|
|
152
|
+
vi.mocked(nodeFs.writeUtf8File).mockImplementation(
|
|
153
|
+
(p: string, content: string) => {
|
|
154
|
+
testFiles.set(path.resolve(p), content);
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
vi.mocked(typedPlatformClient).mockReturnValue({
|
|
159
|
+
POST: mockPlatformPost,
|
|
160
|
+
} as any);
|
|
161
|
+
mockPlatformPost.mockReset();
|
|
162
|
+
|
|
163
|
+
vi.spyOn(process, "exit").mockImplementation((() => {
|
|
164
|
+
throw new Error("process.exit called");
|
|
165
|
+
}) as any);
|
|
166
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
167
|
+
// logOutput uses console.log, so spy on it (not stdout.write).
|
|
168
|
+
consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
afterEach(() => {
|
|
172
|
+
vi.restoreAllMocks();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("deployment token create", () => {
|
|
176
|
+
test("crashes when not authed with a personal access token", async () => {
|
|
177
|
+
setAuthKind("deploymentKey");
|
|
178
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
179
|
+
|
|
180
|
+
await expect(
|
|
181
|
+
deploymentTokenCreate.parseAsync(["my-token"], { from: "user" }),
|
|
182
|
+
).rejects.toThrow();
|
|
183
|
+
expect(process.stderr.write).toHaveBeenCalledWith(
|
|
184
|
+
expect.stringContaining(
|
|
185
|
+
"requires being logged in with a personal access token",
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
expect(mockPlatformPost).not.toHaveBeenCalled();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("crashes when no auth is configured at all", async () => {
|
|
192
|
+
setNoAuth();
|
|
193
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
194
|
+
|
|
195
|
+
await expect(
|
|
196
|
+
deploymentTokenCreate.parseAsync(["my-token"], { from: "user" }),
|
|
197
|
+
).rejects.toThrow();
|
|
198
|
+
expect(mockPlatformPost).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("rejects local deployments client-side", async () => {
|
|
202
|
+
setAuthKind("accessToken");
|
|
203
|
+
mockSelectedDeployment("local-deployment", "local");
|
|
204
|
+
|
|
205
|
+
await expect(
|
|
206
|
+
deploymentTokenCreate.parseAsync(["my-token"], { from: "user" }),
|
|
207
|
+
).rejects.toThrow();
|
|
208
|
+
expect(process.stderr.write).toHaveBeenCalledWith(
|
|
209
|
+
expect.stringContaining("Cannot create a deploy key for a local"),
|
|
210
|
+
);
|
|
211
|
+
expect(mockPlatformPost).not.toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("prints the new deploy key to stdout when --save-env is omitted", async () => {
|
|
215
|
+
setAuthKind("accessToken");
|
|
216
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
217
|
+
mockPlatformPost.mockResolvedValue({
|
|
218
|
+
data: { deployKey: "dev:joyful-capybara-123|new-token" },
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await deploymentTokenCreate.parseAsync(["my-token"], { from: "user" });
|
|
222
|
+
|
|
223
|
+
expect(mockPlatformPost).toHaveBeenCalledWith(
|
|
224
|
+
"/deployments/{deployment_name}/create_deploy_key",
|
|
225
|
+
{
|
|
226
|
+
params: { path: { deployment_name: "joyful-capybara-123" } },
|
|
227
|
+
body: { name: "my-token" },
|
|
228
|
+
},
|
|
229
|
+
);
|
|
230
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
231
|
+
"dev:joyful-capybara-123|new-token",
|
|
232
|
+
);
|
|
233
|
+
// .env.local should not have been written.
|
|
234
|
+
expect(testFiles.has(path.resolve(".env.local"))).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("--save-env writes the key to .env.local by default", async () => {
|
|
238
|
+
setAuthKind("accessToken");
|
|
239
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
240
|
+
mockPlatformPost.mockResolvedValue({
|
|
241
|
+
data: { deployKey: "dev:joyful-capybara-123|new-token" },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
await deploymentTokenCreate.parseAsync(["my-token", "--save-env"], {
|
|
245
|
+
from: "user",
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const written = testFiles.get(path.resolve(".env.local"));
|
|
249
|
+
expect(written).toContain(
|
|
250
|
+
"CONVEX_DEPLOY_KEY=dev:joyful-capybara-123|new-token",
|
|
251
|
+
);
|
|
252
|
+
// The raw deploy key shouldn't have been printed to stdout.
|
|
253
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("--save-env <path> writes to a custom env file", async () => {
|
|
257
|
+
setAuthKind("accessToken");
|
|
258
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
259
|
+
mockPlatformPost.mockResolvedValue({
|
|
260
|
+
data: { deployKey: "dev:joyful-capybara-123|new-token" },
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await deploymentTokenCreate.parseAsync(
|
|
264
|
+
["ci-token", "--save-env", ".env.production"],
|
|
265
|
+
{ from: "user" },
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
expect(testFiles.has(path.resolve(".env.local"))).toBe(false);
|
|
269
|
+
const written = testFiles.get(path.resolve(".env.production"));
|
|
270
|
+
expect(written).toContain(
|
|
271
|
+
"CONVEX_DEPLOY_KEY=dev:joyful-capybara-123|new-token",
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("--save-env replaces an existing CONVEX_DEPLOY_KEY in the file", async () => {
|
|
276
|
+
setAuthKind("accessToken");
|
|
277
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
278
|
+
mockPlatformPost.mockResolvedValue({
|
|
279
|
+
data: { deployKey: "dev:joyful-capybara-123|new-token" },
|
|
280
|
+
});
|
|
281
|
+
testFiles.set(
|
|
282
|
+
path.resolve(".env.local"),
|
|
283
|
+
"FOO=bar\nCONVEX_DEPLOY_KEY=dev:joyful-capybara-123|old-token\n",
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
await deploymentTokenCreate.parseAsync(["my-token", "--save-env"], {
|
|
287
|
+
from: "user",
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const written = testFiles.get(path.resolve(".env.local"));
|
|
291
|
+
expect(written).toContain("FOO=bar");
|
|
292
|
+
expect(written).toContain(
|
|
293
|
+
"CONVEX_DEPLOY_KEY=dev:joyful-capybara-123|new-token",
|
|
294
|
+
);
|
|
295
|
+
expect(written).not.toContain("old-token");
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe("deployment token delete", () => {
|
|
300
|
+
test("crashes when not authed with a personal access token", async () => {
|
|
301
|
+
setAuthKind("deploymentKey");
|
|
302
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
303
|
+
|
|
304
|
+
await expect(
|
|
305
|
+
deploymentTokenDelete.parseAsync(["my-token"], { from: "user" }),
|
|
306
|
+
).rejects.toThrow();
|
|
307
|
+
expect(mockPlatformPost).not.toHaveBeenCalled();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("sends the friendly name as-is when no `|` is present", async () => {
|
|
311
|
+
setAuthKind("accessToken");
|
|
312
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
313
|
+
mockPlatformPost.mockResolvedValue({ data: undefined });
|
|
314
|
+
|
|
315
|
+
await deploymentTokenDelete.parseAsync(["my-token"], { from: "user" });
|
|
316
|
+
|
|
317
|
+
expect(mockPlatformPost).toHaveBeenCalledWith(
|
|
318
|
+
"/deployments/{deployment_name}/delete_deploy_key",
|
|
319
|
+
{
|
|
320
|
+
params: { path: { deployment_name: "joyful-capybara-123" } },
|
|
321
|
+
body: { id: "my-token" },
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("strips the deploy-key prefix before sending to the server", async () => {
|
|
327
|
+
setAuthKind("accessToken");
|
|
328
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
329
|
+
mockPlatformPost.mockResolvedValue({ data: undefined });
|
|
330
|
+
|
|
331
|
+
await deploymentTokenDelete.parseAsync(
|
|
332
|
+
["dev:joyful-capybara-123|the-secret-token"],
|
|
333
|
+
{ from: "user" },
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
expect(mockPlatformPost).toHaveBeenCalledWith(
|
|
337
|
+
"/deployments/{deployment_name}/delete_deploy_key",
|
|
338
|
+
{
|
|
339
|
+
params: { path: { deployment_name: "joyful-capybara-123" } },
|
|
340
|
+
body: { id: "the-secret-token" },
|
|
341
|
+
},
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("warns and crashes when the value looks like an unquoted partial deploy key", async () => {
|
|
346
|
+
setAuthKind("accessToken");
|
|
347
|
+
mockSelectedDeployment("joyful-capybara-123", "dev");
|
|
348
|
+
|
|
349
|
+
await expect(
|
|
350
|
+
deploymentTokenDelete.parseAsync(["dev:joyful-capybara-123"], {
|
|
351
|
+
from: "user",
|
|
352
|
+
}),
|
|
353
|
+
).rejects.toThrow();
|
|
354
|
+
expect(process.stderr.write).toHaveBeenCalledWith(
|
|
355
|
+
expect.stringContaining("looks like a partial deploy key"),
|
|
356
|
+
);
|
|
357
|
+
expect(mockPlatformPost).not.toHaveBeenCalled();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("rejects local deployments client-side", async () => {
|
|
361
|
+
setAuthKind("accessToken");
|
|
362
|
+
mockSelectedDeployment("local-deployment", "local");
|
|
363
|
+
|
|
364
|
+
await expect(
|
|
365
|
+
deploymentTokenDelete.parseAsync(["my-token"], { from: "user" }),
|
|
366
|
+
).rejects.toThrow();
|
|
367
|
+
expect(process.stderr.write).toHaveBeenCalledWith(
|
|
368
|
+
expect.stringContaining("Cannot delete a deploy key for a local"),
|
|
369
|
+
);
|
|
370
|
+
expect(mockPlatformPost).not.toHaveBeenCalled();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from "@commander-js/extra-typings";
|
|
2
|
+
import { deploymentTokenCreate } from "./deploymentTokenCreate.js";
|
|
3
|
+
import { deploymentTokenDelete } from "./deploymentTokenDelete.js";
|
|
4
|
+
|
|
5
|
+
export const deploymentToken = new Command("token")
|
|
6
|
+
.summary("Manage access tokens")
|
|
7
|
+
.description(
|
|
8
|
+
"Create and delete access tokens. Currently supports deploy keys.",
|
|
9
|
+
)
|
|
10
|
+
.addCommand(deploymentTokenCreate)
|
|
11
|
+
.addCommand(deploymentTokenDelete);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Command } from "@commander-js/extra-typings";
|
|
2
|
+
import { chalkStderr } from "chalk";
|
|
3
|
+
import { oneoffContext } from "../bundler/context.js";
|
|
4
|
+
import { logFinishedStep, logOutput, showSpinner } from "../bundler/log.js";
|
|
5
|
+
import { loadSelectedDeploymentCredentials } from "./lib/api.js";
|
|
6
|
+
import { actionDescription } from "./lib/command.js";
|
|
7
|
+
import { getDeploymentSelection } from "./lib/deploymentSelection.js";
|
|
8
|
+
import { changedEnvVarFile } from "./lib/envvars.js";
|
|
9
|
+
import {
|
|
10
|
+
CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
|
|
11
|
+
ENV_VAR_FILE_PATH,
|
|
12
|
+
typedPlatformClient,
|
|
13
|
+
} from "./lib/utils/utils.js";
|
|
14
|
+
|
|
15
|
+
export const deploymentTokenCreate = new Command("create")
|
|
16
|
+
.summary("Create an access token")
|
|
17
|
+
.description(
|
|
18
|
+
`Creates a deploy key that, when set as ${chalkStderr.bold(CONVEX_DEPLOY_KEY_ENV_VAR_NAME)}, scopes all commands to the target deployment. Defaults to the current deployment if '--deployment' isn't passed\n\n` +
|
|
19
|
+
" Print a new deploy key to stdout: `npx convex deployment token create my-token`\n" +
|
|
20
|
+
` Save a new deploy key in ${ENV_VAR_FILE_PATH}: \`npx convex deployment token create my-token --save-env\`\n` +
|
|
21
|
+
" Save a new deploy key in a custom env file: `npx convex deployment token create ci-token --save-env .env.production`\n" +
|
|
22
|
+
" Create a key for the project's prod: `npx convex deployment token create ci-token --deployment prod`\n",
|
|
23
|
+
)
|
|
24
|
+
.argument("<name>", "Name for the new deploy key")
|
|
25
|
+
.allowExcessArguments(false)
|
|
26
|
+
.option(
|
|
27
|
+
"--save-env [path]",
|
|
28
|
+
`Save the new key as ${CONVEX_DEPLOY_KEY_ENV_VAR_NAME} in an env file instead of printing it. Defaults to ${ENV_VAR_FILE_PATH}.`,
|
|
29
|
+
)
|
|
30
|
+
.addDeploymentSelectionOptions(actionDescription("Create a deploy key for"))
|
|
31
|
+
.showHelpAfterError()
|
|
32
|
+
.action(async (name, options) => {
|
|
33
|
+
const ctx = await oneoffContext(options);
|
|
34
|
+
|
|
35
|
+
const auth = ctx.bigBrainAuth();
|
|
36
|
+
if (auth === null || auth.kind !== "accessToken") {
|
|
37
|
+
return await ctx.crash({
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
errorType: "fatal",
|
|
40
|
+
printedMessage: `Creating a deploy key currently requires being logged in with a personal access token. ${
|
|
41
|
+
process.env[CONVEX_DEPLOY_KEY_ENV_VAR_NAME]
|
|
42
|
+
? `Unset ${CONVEX_DEPLOY_KEY_ENV_VAR_NAME}`
|
|
43
|
+
: `Run ${chalkStderr.bold("npx convex login")}`
|
|
44
|
+
} and try again.`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const deploymentSelection = await getDeploymentSelection(ctx, options);
|
|
49
|
+
const deployment = await loadSelectedDeploymentCredentials(
|
|
50
|
+
ctx,
|
|
51
|
+
deploymentSelection,
|
|
52
|
+
{ ensureLocalRunning: false },
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (deployment.deploymentFields === null) {
|
|
56
|
+
return await ctx.crash({
|
|
57
|
+
exitCode: 1,
|
|
58
|
+
errorType: "fatal",
|
|
59
|
+
printedMessage:
|
|
60
|
+
"Cannot create a deploy key for a self-hosted deployment.",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { deploymentName, deploymentType } = deployment.deploymentFields;
|
|
65
|
+
if (deploymentType === "local" || deploymentType === "anonymous") {
|
|
66
|
+
return await ctx.crash({
|
|
67
|
+
exitCode: 1,
|
|
68
|
+
errorType: "fatal",
|
|
69
|
+
printedMessage: `Cannot create a deploy key for a ${deploymentType} deployment.`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
showSpinner(`Creating deploy key for ${deploymentName}...`);
|
|
74
|
+
const response = await typedPlatformClient(ctx).POST(
|
|
75
|
+
"/deployments/{deployment_name}/create_deploy_key",
|
|
76
|
+
{
|
|
77
|
+
params: { path: { deployment_name: deploymentName } },
|
|
78
|
+
body: { name },
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
const deployKey = response.data!.deployKey;
|
|
82
|
+
|
|
83
|
+
if (options.saveEnv === undefined) {
|
|
84
|
+
logFinishedStep(`Created deploy key "${name}" for ${deploymentName}.`);
|
|
85
|
+
logOutput(deployKey);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const envFile =
|
|
90
|
+
typeof options.saveEnv === "string" ? options.saveEnv : ENV_VAR_FILE_PATH;
|
|
91
|
+
const existingFileContent = ctx.fs.exists(envFile)
|
|
92
|
+
? ctx.fs.readUtf8File(envFile)
|
|
93
|
+
: null;
|
|
94
|
+
const updatedContent = changedEnvVarFile({
|
|
95
|
+
existingFileContent,
|
|
96
|
+
envVarName: CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
|
|
97
|
+
envVarValue: deployKey,
|
|
98
|
+
commentAfterValue: null,
|
|
99
|
+
commentOnPreviousLine: null,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (updatedContent === null) {
|
|
103
|
+
logFinishedStep(
|
|
104
|
+
`Deploy key for ${deploymentName} already present in ${envFile}; no changes made.`,
|
|
105
|
+
);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
ctx.fs.writeUtf8File(envFile, updatedContent);
|
|
110
|
+
logFinishedStep(
|
|
111
|
+
`Saved deploy key "${name}" for ${deploymentName} as ${CONVEX_DEPLOY_KEY_ENV_VAR_NAME} in ${envFile}.`,
|
|
112
|
+
);
|
|
113
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Command } from "@commander-js/extra-typings";
|
|
2
|
+
import { chalkStderr } from "chalk";
|
|
3
|
+
import { oneoffContext } from "../bundler/context.js";
|
|
4
|
+
import { logFinishedStep, showSpinner } from "../bundler/log.js";
|
|
5
|
+
import { loadSelectedDeploymentCredentials } from "./lib/api.js";
|
|
6
|
+
import { actionDescription } from "./lib/command.js";
|
|
7
|
+
import { getDeploymentSelection } from "./lib/deploymentSelection.js";
|
|
8
|
+
import {
|
|
9
|
+
CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
|
|
10
|
+
typedPlatformClient,
|
|
11
|
+
} from "./lib/utils/utils.js";
|
|
12
|
+
|
|
13
|
+
export const deploymentTokenDelete = new Command("delete")
|
|
14
|
+
.summary("Delete an access token")
|
|
15
|
+
.description(
|
|
16
|
+
"Delete an access token. Currently only deploy keys (deployment-scoped access tokens) are supported.\n\n" +
|
|
17
|
+
"The positional `<nameOrToken>` can be the unique name of the deploy key (as passed to `token create`) or the deploy key value itself. The target deployment defaults to the currently-selected one; pass `--deployment` to target a different deployment.\n\n" +
|
|
18
|
+
" Delete by name: `npx convex deployment token delete my-token`\n" +
|
|
19
|
+
" Delete by value: `npx convex deployment token delete 'dev:happy-animal-123|ey...'`\n" +
|
|
20
|
+
" Target prod: `npx convex deployment token delete ci-token --deployment prod`",
|
|
21
|
+
)
|
|
22
|
+
.argument(
|
|
23
|
+
"<nameOrToken>",
|
|
24
|
+
"The unique name of the deploy key, or the deploy key value itself.",
|
|
25
|
+
)
|
|
26
|
+
.allowExcessArguments(false)
|
|
27
|
+
.addDeploymentSelectionOptions(actionDescription("Delete a deploy key for"))
|
|
28
|
+
.showHelpAfterError()
|
|
29
|
+
.action(async (nameOrToken, options) => {
|
|
30
|
+
const ctx = await oneoffContext(options);
|
|
31
|
+
|
|
32
|
+
const auth = ctx.bigBrainAuth();
|
|
33
|
+
if (auth === null || auth.kind !== "accessToken") {
|
|
34
|
+
return await ctx.crash({
|
|
35
|
+
exitCode: 1,
|
|
36
|
+
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
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const deploymentSelection = await getDeploymentSelection(ctx, options);
|
|
42
|
+
const deployment = await loadSelectedDeploymentCredentials(
|
|
43
|
+
ctx,
|
|
44
|
+
deploymentSelection,
|
|
45
|
+
{ ensureLocalRunning: false },
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (deployment.deploymentFields === null) {
|
|
49
|
+
return await ctx.crash({
|
|
50
|
+
exitCode: 1,
|
|
51
|
+
errorType: "fatal",
|
|
52
|
+
printedMessage:
|
|
53
|
+
"Cannot delete a deploy key for a self-hosted deployment.",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { deploymentName, deploymentType } = deployment.deploymentFields;
|
|
58
|
+
if (deploymentType === "local" || deploymentType === "anonymous") {
|
|
59
|
+
return await ctx.crash({
|
|
60
|
+
exitCode: 1,
|
|
61
|
+
errorType: "fatal",
|
|
62
|
+
printedMessage: `Cannot delete a deploy key for a ${deploymentType} deployment.`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A full deploy key has the form `<type>:<deployment-name>|<token>`. If we
|
|
67
|
+
// see the prefix without the `|`, the user almost certainly forgot to
|
|
68
|
+
// quote: the shell ate `|` and everything after.
|
|
69
|
+
if (/^(dev|prod|preview|local):[^|]*$/.test(nameOrToken)) {
|
|
70
|
+
return await ctx.crash({
|
|
71
|
+
exitCode: 1,
|
|
72
|
+
errorType: "fatal",
|
|
73
|
+
printedMessage: `"${nameOrToken}" looks like a partial deploy key — your shell likely consumed the \`|\` and everything after it. Wrap the value in single quotes (e.g. ${chalkStderr.bold(`npx convex deployment token delete '${nameOrToken}|...'`)}) and try again.`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// The server matches against just the token portion, so strip the prefix
|
|
77
|
+
// when present so users can paste the value of CONVEX_DEPLOY_KEY directly.
|
|
78
|
+
const pipeIdx = nameOrToken.indexOf("|");
|
|
79
|
+
const id = pipeIdx >= 0 ? nameOrToken.slice(pipeIdx + 1) : nameOrToken;
|
|
80
|
+
|
|
81
|
+
showSpinner(`Deleting deploy key for ${deploymentName}...`);
|
|
82
|
+
await typedPlatformClient(ctx).POST(
|
|
83
|
+
"/deployments/{deployment_name}/delete_deploy_key",
|
|
84
|
+
{
|
|
85
|
+
params: { path: { deployment_name: deploymentName } },
|
|
86
|
+
body: { id },
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
logFinishedStep(`Deleted deploy key for ${deploymentName}.`);
|
|
91
|
+
});
|