convex 1.41.0 → 1.42.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 +31 -0
- package/dist/browser.bundle.js +1 -1
- package/dist/browser.bundle.js.map +1 -1
- package/dist/cjs/browser/simple_client-node.js +85 -27
- package/dist/cjs/browser/simple_client-node.js.map +3 -3
- package/dist/cjs/cli/env.js +12 -5
- package/dist/cjs/cli/env.js.map +2 -2
- package/dist/cjs/cli/envDefault.js +12 -7
- package/dist/cjs/cli/envDefault.js.map +2 -2
- package/dist/cjs/cli/insights.js +11 -1
- package/dist/cjs/cli/insights.js.map +2 -2
- package/dist/cjs/cli/lib/env.js +5 -1
- package/dist/cjs/cli/lib/env.js.map +2 -2
- package/dist/cjs/cli/lib/login.js +16 -12
- package/dist/cjs/cli/lib/login.js.map +2 -2
- package/dist/cjs/cli/lib/logs.js +4 -1
- package/dist/cjs/cli/lib/logs.js.map +2 -2
- package/dist/cjs/cli/lib/run.js +27 -2
- package/dist/cjs/cli/lib/run.js.map +2 -2
- package/dist/cjs/cli/lib/utils/utils.js +30 -0
- package/dist/cjs/cli/lib/utils/utils.js.map +2 -2
- package/dist/cjs/cli/program.js +2 -1
- package/dist/cjs/cli/program.js.map +2 -2
- package/dist/cjs/cli/project.js +27 -0
- package/dist/cjs/cli/project.js.map +7 -0
- package/dist/cjs/cli/projectCreate.js +90 -0
- package/dist/cjs/cli/projectCreate.js.map +7 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/server/database.js.map +1 -1
- package/dist/cjs/server/impl/meta_impl.js +6 -6
- package/dist/cjs/server/impl/meta_impl.js.map +2 -2
- package/dist/cjs/server/impl/registration_impl.js +9 -2
- package/dist/cjs/server/impl/registration_impl.js.map +2 -2
- package/dist/cjs/server/index.js.map +2 -2
- package/dist/cjs/server/meta.js.map +1 -1
- package/dist/cjs/server/query.js.map +1 -1
- package/dist/cjs/server/registration.js.map +1 -1
- package/dist/cjs/server/storage.js.map +1 -1
- package/dist/cjs/server/system_fields.js.map +1 -1
- package/dist/cjs-types/cli/env.d.ts.map +1 -1
- package/dist/cjs-types/cli/envDefault.d.ts.map +1 -1
- package/dist/cjs-types/cli/insights.d.ts +1 -0
- package/dist/cjs-types/cli/insights.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/env.d.ts +3 -1
- package/dist/cjs-types/cli/lib/env.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/env.test.d.ts +2 -0
- package/dist/cjs-types/cli/lib/env.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/login.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/logs.d.ts +1 -1
- package/dist/cjs-types/cli/lib/logs.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/run.d.ts +1 -1
- package/dist/cjs-types/cli/lib/run.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/utils/utils.d.ts +10 -0
- package/dist/cjs-types/cli/lib/utils/utils.d.ts.map +1 -1
- package/dist/cjs-types/cli/program.d.ts.map +1 -1
- package/dist/cjs-types/cli/project.d.ts +3 -0
- package/dist/cjs-types/cli/project.d.ts.map +1 -0
- package/dist/cjs-types/cli/projectCreate.d.ts +5 -0
- package/dist/cjs-types/cli/projectCreate.d.ts.map +1 -0
- package/dist/cjs-types/index.d.ts +1 -1
- package/dist/cjs-types/server/database.d.ts +21 -3
- package/dist/cjs-types/server/database.d.ts.map +1 -1
- 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 +1 -1
- package/dist/cjs-types/server/index.d.ts.map +1 -1
- package/dist/cjs-types/server/meta.d.ts +10 -0
- package/dist/cjs-types/server/meta.d.ts.map +1 -1
- package/dist/cjs-types/server/query.d.ts +1 -1
- package/dist/cjs-types/server/registration.d.ts +15 -3
- package/dist/cjs-types/server/registration.d.ts.map +1 -1
- package/dist/cjs-types/server/storage.d.ts +20 -3
- package/dist/cjs-types/server/storage.d.ts.map +1 -1
- package/dist/cli.bundle.cjs +1047 -619
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/esm/browser/simple_client-node.js +85 -27
- package/dist/esm/browser/simple_client-node.js.map +3 -3
- package/dist/esm/cli/env.js +12 -5
- package/dist/esm/cli/env.js.map +2 -2
- package/dist/esm/cli/envDefault.js +12 -7
- package/dist/esm/cli/envDefault.js.map +2 -2
- package/dist/esm/cli/insights.js +11 -1
- package/dist/esm/cli/insights.js.map +2 -2
- package/dist/esm/cli/lib/env.js +5 -1
- package/dist/esm/cli/lib/env.js.map +2 -2
- package/dist/esm/cli/lib/login.js +18 -13
- package/dist/esm/cli/lib/login.js.map +2 -2
- package/dist/esm/cli/lib/logs.js +5 -2
- package/dist/esm/cli/lib/logs.js.map +2 -2
- package/dist/esm/cli/lib/run.js +32 -3
- package/dist/esm/cli/lib/run.js.map +2 -2
- package/dist/esm/cli/lib/utils/utils.js +29 -0
- package/dist/esm/cli/lib/utils/utils.js.map +2 -2
- package/dist/esm/cli/program.js +2 -1
- package/dist/esm/cli/program.js.map +2 -2
- package/dist/esm/cli/project.js +5 -0
- package/dist/esm/cli/project.js.map +7 -0
- package/dist/esm/cli/projectCreate.js +68 -0
- package/dist/esm/cli/projectCreate.js.map +7 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/server/impl/meta_impl.js +6 -6
- package/dist/esm/server/impl/meta_impl.js.map +2 -2
- package/dist/esm/server/impl/registration_impl.js +9 -2
- package/dist/esm/server/impl/registration_impl.js.map +2 -2
- package/dist/esm/server/index.js.map +2 -2
- package/dist/esm-types/cli/env.d.ts.map +1 -1
- package/dist/esm-types/cli/envDefault.d.ts.map +1 -1
- package/dist/esm-types/cli/insights.d.ts +1 -0
- package/dist/esm-types/cli/insights.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/env.d.ts +3 -1
- package/dist/esm-types/cli/lib/env.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/env.test.d.ts +2 -0
- package/dist/esm-types/cli/lib/env.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/login.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/logs.d.ts +1 -1
- package/dist/esm-types/cli/lib/logs.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/run.d.ts +1 -1
- package/dist/esm-types/cli/lib/run.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/utils/utils.d.ts +10 -0
- package/dist/esm-types/cli/lib/utils/utils.d.ts.map +1 -1
- package/dist/esm-types/cli/program.d.ts.map +1 -1
- package/dist/esm-types/cli/project.d.ts +3 -0
- package/dist/esm-types/cli/project.d.ts.map +1 -0
- package/dist/esm-types/cli/projectCreate.d.ts +5 -0
- package/dist/esm-types/cli/projectCreate.d.ts.map +1 -0
- package/dist/esm-types/index.d.ts +1 -1
- package/dist/esm-types/server/database.d.ts +21 -3
- package/dist/esm-types/server/database.d.ts.map +1 -1
- 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 +1 -1
- package/dist/esm-types/server/index.d.ts.map +1 -1
- package/dist/esm-types/server/meta.d.ts +10 -0
- package/dist/esm-types/server/meta.d.ts.map +1 -1
- package/dist/esm-types/server/query.d.ts +1 -1
- package/dist/esm-types/server/registration.d.ts +15 -3
- package/dist/esm-types/server/registration.d.ts.map +1 -1
- package/dist/esm-types/server/storage.d.ts +20 -3
- package/dist/esm-types/server/storage.d.ts.map +1 -1
- package/dist/react.bundle.js +1 -1
- package/dist/react.bundle.js.map +1 -1
- package/package.json +4 -4
- package/src/cli/env.ts +16 -6
- package/src/cli/envDefault.ts +13 -7
- package/src/cli/insights.ts +12 -0
- package/src/cli/lib/env.test.ts +60 -0
- package/src/cli/lib/env.ts +9 -1
- package/src/cli/lib/login.ts +18 -18
- package/src/cli/lib/logs.ts +7 -2
- package/src/cli/lib/run.ts +37 -3
- package/src/cli/lib/utils/utils.ts +56 -0
- package/src/cli/program.ts +2 -0
- package/src/cli/project.ts +7 -0
- package/src/cli/projectCreate.ts +99 -0
- package/src/index.ts +1 -1
- package/src/server/database.ts +21 -3
- package/src/server/impl/meta_impl.ts +7 -6
- package/src/server/impl/registration_impl.ts +11 -3
- package/src/server/index.ts +1 -0
- package/src/server/meta.ts +10 -0
- package/src/server/query.ts +1 -1
- package/src/server/registration.test.ts +25 -1
- package/src/server/registration.ts +16 -1
- package/src/server/storage.ts +22 -3
- package/src/server/system_fields.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.
|
|
4
|
+
"version": "1.42.0",
|
|
5
5
|
"author": "Convex, Inc. <no-reply@convex.dev>",
|
|
6
6
|
"homepage": "https://convex.dev",
|
|
7
7
|
"repository": {
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"dependencies": {
|
|
172
172
|
"esbuild": "0.27.0",
|
|
173
173
|
"prettier": "^3.0.0",
|
|
174
|
-
"ws": "8.
|
|
174
|
+
"ws": "8.21.0"
|
|
175
175
|
},
|
|
176
176
|
"peerDependencies": {
|
|
177
177
|
"@auth0/auth0-react": "^2.0.1",
|
|
@@ -266,13 +266,13 @@
|
|
|
266
266
|
"serve-handler": "~6.1.6",
|
|
267
267
|
"shx": "~0.4.0",
|
|
268
268
|
"skott": "~0.35.4",
|
|
269
|
-
"tar": "^7.5.
|
|
269
|
+
"tar": "^7.5.16",
|
|
270
270
|
"tsx": "^4.19.4",
|
|
271
271
|
"typedoc": "^0.24.6",
|
|
272
272
|
"typescript": "~5.0.3",
|
|
273
273
|
"typescript-eslint": "~8.57.0",
|
|
274
274
|
"vitest": "^4.1.0",
|
|
275
|
-
"undici": "7.
|
|
275
|
+
"undici": "7.28.0",
|
|
276
276
|
"wait-for-expect": "~4.0.0",
|
|
277
277
|
"zod": "^3.24.0",
|
|
278
278
|
"zod-to-json-schema": "^3.24.0"
|
package/src/cli/env.ts
CHANGED
|
@@ -164,18 +164,25 @@ const envRemoveCmd = new Command("remove")
|
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
const envListCmd = new Command("list")
|
|
167
|
-
.summary("List all variables and their values")
|
|
167
|
+
.summary("List all environment variables and their values")
|
|
168
168
|
.description(
|
|
169
169
|
[
|
|
170
|
-
"• List all variables: `npx convex env list`",
|
|
170
|
+
"• List all variables and their values: `npx convex env list`",
|
|
171
|
+
"• List only variable names (no values): `npx convex env list --names-only`",
|
|
171
172
|
"• Save all variables to a file: `npx convex env list > .env.convex`",
|
|
172
173
|
"• Append to a file: `npx convex env list >> .env.convex`",
|
|
173
174
|
].join("\n"),
|
|
174
175
|
)
|
|
176
|
+
.option(
|
|
177
|
+
"--names-only",
|
|
178
|
+
"List only the names of environment variables, without their values",
|
|
179
|
+
)
|
|
175
180
|
.configureHelp({ showGlobalOptions: true })
|
|
176
181
|
.allowExcessArguments(false)
|
|
177
|
-
.action(async (
|
|
178
|
-
|
|
182
|
+
.action(async (cmdOptions, cmd) => {
|
|
183
|
+
// Note: We use `as` here because optsWithGlobals() type inference doesn't
|
|
184
|
+
// include global options from the parent command (added via addDeploymentSelectionOptions)
|
|
185
|
+
const options = cmd.optsWithGlobals() as DeploymentSelectionOptions;
|
|
179
186
|
const { ctx, deployment } = await selectEnvDeployment(options);
|
|
180
187
|
await ensureHasConvexDependency(ctx, "env list");
|
|
181
188
|
await withRunningBackend({
|
|
@@ -183,7 +190,9 @@ const envListCmd = new Command("list")
|
|
|
183
190
|
deployment,
|
|
184
191
|
action: async () => {
|
|
185
192
|
const backend = deploymentEnvBackend(ctx, deployment);
|
|
186
|
-
await envList(ctx, backend
|
|
193
|
+
await envList(ctx, backend, {
|
|
194
|
+
namesOnly: cmdOptions.namesOnly ?? false,
|
|
195
|
+
});
|
|
187
196
|
},
|
|
188
197
|
});
|
|
189
198
|
});
|
|
@@ -198,7 +207,8 @@ export const env = new Command("env")
|
|
|
198
207
|
"• Set interactively: `npx convex env set NAME`",
|
|
199
208
|
"• Set multiple from file: `npx convex env set --from-file .env`",
|
|
200
209
|
"• Unset a variable: `npx convex env remove NAME`",
|
|
201
|
-
"• List all variables: `npx convex env list`",
|
|
210
|
+
"• List all variables and their values: `npx convex env list`",
|
|
211
|
+
"• List only variable names (no values): `npx convex env list --names-only`",
|
|
202
212
|
"• Print a variable's value: `npx convex env get NAME`",
|
|
203
213
|
"",
|
|
204
214
|
"By default, this sets and views variables on your dev deployment.",
|
package/src/cli/envDefault.ts
CHANGED
|
@@ -141,24 +141,29 @@ const envDefaultRemove = addEnvDefaultOptions(
|
|
|
141
141
|
|
|
142
142
|
const envDefaultList = addEnvDefaultOptions(
|
|
143
143
|
new Command("list")
|
|
144
|
-
.summary("List all default variables")
|
|
144
|
+
.summary("List all default environment variables and their values")
|
|
145
145
|
.description(
|
|
146
146
|
[
|
|
147
|
-
"List all default variables
|
|
148
|
-
"",
|
|
149
|
-
"• `npx convex env default list`",
|
|
147
|
+
"• List all default variables and their values: `npx convex env default list`",
|
|
148
|
+
"• List only default variable names (no values): `npx convex env default list --names-only`",
|
|
150
149
|
"",
|
|
151
150
|
"The deployment type is determined by the current deployment (local maps to dev), or by --type if provided.",
|
|
152
151
|
].join("\n"),
|
|
153
152
|
)
|
|
153
|
+
.option(
|
|
154
|
+
"--names-only",
|
|
155
|
+
"List only the names of environment variables, without their values",
|
|
156
|
+
)
|
|
154
157
|
.configureHelp({ showGlobalOptions: true })
|
|
155
158
|
.allowExcessArguments(false),
|
|
156
|
-
).action(async (
|
|
159
|
+
).action(async (cmdOptions, cmd) => {
|
|
157
160
|
const options = cmd.optsWithGlobals() as DeploymentSelectionOptions &
|
|
158
161
|
EnvDefaultExtraOptions;
|
|
159
162
|
const { ctx, backend } = await resolveEnvDefaultBackend(options);
|
|
160
163
|
await ensureHasConvexDependency(ctx, "env default list");
|
|
161
|
-
await envList(ctx, backend
|
|
164
|
+
await envList(ctx, backend, {
|
|
165
|
+
namesOnly: cmdOptions.namesOnly ?? false,
|
|
166
|
+
});
|
|
162
167
|
});
|
|
163
168
|
|
|
164
169
|
export const envDefault = new Command("default")
|
|
@@ -171,7 +176,8 @@ export const envDefault = new Command("default")
|
|
|
171
176
|
"",
|
|
172
177
|
"• Set a default variable: `npx convex env default set NAME 'value'`",
|
|
173
178
|
"• Unset a default variable: `npx convex env default remove NAME`",
|
|
174
|
-
"• List all default variables: `npx convex env default list`",
|
|
179
|
+
"• List all default variables and their values: `npx convex env default list`",
|
|
180
|
+
"• List only default variable names (no values): `npx convex env default list --names-only`",
|
|
175
181
|
"• Print a default variable's value: `npx convex env default get NAME`",
|
|
176
182
|
].join("\n"),
|
|
177
183
|
)
|
package/src/cli/insights.ts
CHANGED
|
@@ -101,6 +101,7 @@ export const insights = new Command("insights")
|
|
|
101
101
|
)
|
|
102
102
|
.allowExcessArguments(false)
|
|
103
103
|
.option("--details", "Show recent events for each insight", false)
|
|
104
|
+
.option("--json", "Output insights as JSON", false)
|
|
104
105
|
.addDeploymentSelectionOptions(actionDescription("Show insights for"))
|
|
105
106
|
.showHelpAfterError()
|
|
106
107
|
.action(async (cmdOptions) => {
|
|
@@ -145,6 +146,17 @@ export const insights = new Command("insights")
|
|
|
145
146
|
"?view=insights",
|
|
146
147
|
);
|
|
147
148
|
|
|
149
|
+
if (cmdOptions.json) {
|
|
150
|
+
logOutput(
|
|
151
|
+
JSON.stringify(
|
|
152
|
+
{ deploymentName, dashboardUrl, insights: insightsList },
|
|
153
|
+
null,
|
|
154
|
+
2,
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
148
160
|
if (insightsList.length === 0) {
|
|
149
161
|
logOutput(
|
|
150
162
|
chalk.green(
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import type { Context } from "../../bundler/context.js";
|
|
3
|
+
import { envList, type EnvVar, type EnvVarBackend } from "./env.js";
|
|
4
|
+
|
|
5
|
+
const logMocks = vi.hoisted(() => ({
|
|
6
|
+
logFailure: vi.fn(),
|
|
7
|
+
logFinishedStep: vi.fn(),
|
|
8
|
+
logMessage: vi.fn(),
|
|
9
|
+
logOutput: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock("../../bundler/log.js", () => ({
|
|
13
|
+
logFailure: logMocks.logFailure,
|
|
14
|
+
logFinishedStep: logMocks.logFinishedStep,
|
|
15
|
+
logMessage: logMocks.logMessage,
|
|
16
|
+
logOutput: logMocks.logOutput,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
function fakeBackend(envs: EnvVar[]): EnvVarBackend {
|
|
20
|
+
return {
|
|
21
|
+
get() {
|
|
22
|
+
return Promise.resolve(null);
|
|
23
|
+
},
|
|
24
|
+
list() {
|
|
25
|
+
return Promise.resolve(envs);
|
|
26
|
+
},
|
|
27
|
+
update() {
|
|
28
|
+
return Promise.reject(new Error("Unexpected update"));
|
|
29
|
+
},
|
|
30
|
+
notice: "",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("envList", () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("prints only names without values or warnings when namesOnly is true", async () => {
|
|
40
|
+
await envList(
|
|
41
|
+
{} as Context,
|
|
42
|
+
fakeBackend([
|
|
43
|
+
{ name: "PLAIN", value: "plain-value" },
|
|
44
|
+
{ name: "DANGEROUS", value: "secret'value#fragment" },
|
|
45
|
+
]),
|
|
46
|
+
{ namesOnly: true },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(logMocks.logOutput.mock.calls).toEqual([["PLAIN"], ["DANGEROUS"]]);
|
|
50
|
+
expect(logMocks.logMessage).not.toHaveBeenCalled();
|
|
51
|
+
|
|
52
|
+
const emitted = [
|
|
53
|
+
...logMocks.logOutput.mock.calls.flat(),
|
|
54
|
+
...logMocks.logMessage.mock.calls.flat(),
|
|
55
|
+
].join("\n");
|
|
56
|
+
expect(emitted).not.toContain("plain-value");
|
|
57
|
+
expect(emitted).not.toContain("secret'value#fragment");
|
|
58
|
+
expect(emitted).not.toContain("Warning (");
|
|
59
|
+
});
|
|
60
|
+
});
|
package/src/cli/lib/env.ts
CHANGED
|
@@ -171,13 +171,21 @@ export async function envRemove(
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
export async function envList(
|
|
174
|
+
export async function envList(
|
|
175
|
+
ctx: Context,
|
|
176
|
+
backend: EnvVarBackend,
|
|
177
|
+
options?: { namesOnly?: boolean },
|
|
178
|
+
) {
|
|
175
179
|
const envs = await backend.list();
|
|
176
180
|
if (envs.length === 0) {
|
|
177
181
|
logMessage(`No environment variables set${backend.notice}`);
|
|
178
182
|
return;
|
|
179
183
|
}
|
|
180
184
|
for (const { name, value } of envs) {
|
|
185
|
+
if (options?.namesOnly) {
|
|
186
|
+
logOutput(name);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
181
189
|
const { formatted, warning } = formatEnvValueForDotfile(value);
|
|
182
190
|
if (warning) {
|
|
183
191
|
logMessage(`Warning (${name}): ${warning}`);
|
package/src/cli/lib/login.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
logAndHandleFetchError,
|
|
6
6
|
throwingFetch,
|
|
7
7
|
isWebContainer,
|
|
8
|
+
typedPlatformClient,
|
|
8
9
|
} from "./utils/utils.js";
|
|
9
10
|
import open from "open";
|
|
10
11
|
import { chalkStderr } from "chalk";
|
|
@@ -37,12 +38,6 @@ custom.setHttpOptionsDefaults({
|
|
|
37
38
|
timeout: parseInt(process.env.OPENID_CLIENT_TIMEOUT || "10000"),
|
|
38
39
|
});
|
|
39
40
|
|
|
40
|
-
interface AuthorizeArgs {
|
|
41
|
-
authnToken: string;
|
|
42
|
-
deviceName: string;
|
|
43
|
-
anonymousId?: string | undefined;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
41
|
export async function checkAuthorization(
|
|
47
42
|
ctx: Context,
|
|
48
43
|
acceptOptIns: boolean,
|
|
@@ -390,18 +385,23 @@ export async function performLogin(
|
|
|
390
385
|
});
|
|
391
386
|
}
|
|
392
387
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const data = await bigBrainAPI({
|
|
399
|
-
ctx,
|
|
400
|
-
method: "POST",
|
|
401
|
-
path: "authorize",
|
|
402
|
-
data: authorizeArgs,
|
|
388
|
+
// Exchange the WorkOS access token for a Convex personal access token.
|
|
389
|
+
ctx._updateBigBrainAuth({
|
|
390
|
+
accessToken: accessToken!,
|
|
391
|
+
kind: "accessToken",
|
|
392
|
+
header: `Bearer ${accessToken!}`,
|
|
403
393
|
});
|
|
404
|
-
const
|
|
394
|
+
const response = await typedPlatformClient(ctx).POST(
|
|
395
|
+
"/create_personal_access_token",
|
|
396
|
+
{
|
|
397
|
+
// `anonymousId` links a prior anonymous session to this account. It's
|
|
398
|
+
// intentionally absent from the generated platform API types,
|
|
399
|
+
// so cast to pass it through.
|
|
400
|
+
body: { name: deviceName, anonymousId } as { name: string },
|
|
401
|
+
},
|
|
402
|
+
);
|
|
403
|
+
const newAccessToken = response.data!.accessToken;
|
|
404
|
+
const globalConfig = { accessToken: newAccessToken };
|
|
405
405
|
try {
|
|
406
406
|
await modifyGlobalConfig(ctx, globalConfig);
|
|
407
407
|
const path = globalConfigPath();
|
|
@@ -416,7 +416,7 @@ export async function performLogin(
|
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
logVerbose(`performLogin: updating big brain auth after login`);
|
|
419
|
-
await updateBigBrainAuthAfterLogin(ctx,
|
|
419
|
+
await updateBigBrainAuthAfterLogin(ctx, newAccessToken);
|
|
420
420
|
|
|
421
421
|
logVerbose(`performLogin: checking opt ins, acceptOptIns: ${acceptOptIns}`);
|
|
422
422
|
// Do opt in to TOS and Privacy Policy stuff
|
package/src/cli/lib/logs.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { nextBackoff } from "./dev.js";
|
|
|
5
5
|
import chalk, { chalkStderr } from "chalk";
|
|
6
6
|
import { stripVTControlCharacters } from "node:util";
|
|
7
7
|
import { format } from "node:util";
|
|
8
|
-
import { deploymentFetch } from "./utils/utils.js";
|
|
8
|
+
import { deploymentFetch, ThrowingFetchError } from "./utils/utils.js";
|
|
9
9
|
import { FunctionExecution } from "./apiTypes.js";
|
|
10
10
|
|
|
11
11
|
export type LogMode = "always" | "pause-on-deploy" | "disable";
|
|
@@ -122,7 +122,12 @@ export async function watchLogs(
|
|
|
122
122
|
options?.jsonl,
|
|
123
123
|
);
|
|
124
124
|
}
|
|
125
|
-
} catch {
|
|
125
|
+
} catch (e) {
|
|
126
|
+
// A 403 means the admin key isn't authorized for this deployment.
|
|
127
|
+
// Retrying won't help, so surface the error and fail immediately.
|
|
128
|
+
if (e instanceof ThrowingFetchError && e.response.status === 403) {
|
|
129
|
+
return await e.handle(ctx);
|
|
130
|
+
}
|
|
126
131
|
numFailures += 1;
|
|
127
132
|
}
|
|
128
133
|
// Handle backoff
|
package/src/cli/lib/run.ts
CHANGED
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
import { Value, convexToJson, jsonToConvex } from "../../values/value.js";
|
|
12
12
|
import { Context, OneoffCtx } from "../../bundler/context.js";
|
|
13
13
|
import { logFinishedStep, logMessage, logOutput } from "../../bundler/log.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
permissionDeniedErrorMessage,
|
|
16
|
+
waitForever,
|
|
17
|
+
waitUntilCalled,
|
|
18
|
+
} from "./utils/utils.js";
|
|
15
19
|
import JSON5 from "json5";
|
|
16
20
|
import path from "path";
|
|
17
21
|
import { readProjectConfig } from "./config.js";
|
|
@@ -59,6 +63,15 @@ export async function runFunctionAndLog(
|
|
|
59
63
|
} catch (err) {
|
|
60
64
|
const errorMessage = (err as Error).toString().trim();
|
|
61
65
|
|
|
66
|
+
const permissionError = permissionDeniedErrorMessage(errorMessage);
|
|
67
|
+
if (permissionError !== null) {
|
|
68
|
+
return await ctx.crash({
|
|
69
|
+
exitCode: 1,
|
|
70
|
+
errorType: "fatal",
|
|
71
|
+
printedMessage: chalkStderr.red(permissionError),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
if (errorMessage.includes("Could not find function")) {
|
|
63
76
|
const functions = (await runSystemQuery(ctx, {
|
|
64
77
|
deploymentUrl: args.deploymentUrl,
|
|
@@ -377,7 +390,7 @@ export async function subscribeAndLog(
|
|
|
377
390
|
}
|
|
378
391
|
|
|
379
392
|
export async function subscribe(
|
|
380
|
-
|
|
393
|
+
ctx: Context,
|
|
381
394
|
args: {
|
|
382
395
|
deploymentUrl: string;
|
|
383
396
|
adminKey: string;
|
|
@@ -399,7 +412,28 @@ export async function subscribe(
|
|
|
399
412
|
args.deploymentUrl,
|
|
400
413
|
(updatedQueries) => {
|
|
401
414
|
for (const queryToken of updatedQueries) {
|
|
402
|
-
|
|
415
|
+
let result: Value;
|
|
416
|
+
try {
|
|
417
|
+
result = client.localQueryResultByToken(queryToken)!;
|
|
418
|
+
} catch (err) {
|
|
419
|
+
// A failed query (e.g. a permission error) throws here. Surface
|
|
420
|
+
// permission errors cleanly instead of an uncaught stack trace, and
|
|
421
|
+
// let any other error propagate as before.
|
|
422
|
+
const permissionError = permissionDeniedErrorMessage(
|
|
423
|
+
(err as Error).toString(),
|
|
424
|
+
);
|
|
425
|
+
if (permissionError !== null) {
|
|
426
|
+
void ctx.crash({
|
|
427
|
+
exitCode: 1,
|
|
428
|
+
errorType: "fatal",
|
|
429
|
+
printedMessage: chalkStderr.red(permissionError),
|
|
430
|
+
});
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
434
|
+
throw err;
|
|
435
|
+
}
|
|
436
|
+
args.callbacks?.onChange?.(result);
|
|
403
437
|
}
|
|
404
438
|
},
|
|
405
439
|
{
|
|
@@ -101,6 +101,52 @@ export function readDeployKeyFromEnv(
|
|
|
101
101
|
return fromDeployKey || fromToken || undefined;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// The prefix the backend's `require_operation` checks use when an identity
|
|
105
|
+
// lacks a permission. The full message names the missing permission, e.g.
|
|
106
|
+
// `You do not have permission to perform this operation (deployment:data:view).`
|
|
107
|
+
const PERMISSION_DENIED_MESSAGE_PREFIX =
|
|
108
|
+
"You do not have permission to perform this operation";
|
|
109
|
+
|
|
110
|
+
function deployKeyPermissionAdvice(): string | null {
|
|
111
|
+
// Name whichever env var is actually set. `CONVEX_DEPLOY_KEY` takes
|
|
112
|
+
// precedence over `CONVEX_DEPLOYMENT_TOKEN` when both are present.
|
|
113
|
+
const envVarName = process.env[CONVEX_DEPLOY_KEY_ENV_VAR_NAME]
|
|
114
|
+
? CONVEX_DEPLOY_KEY_ENV_VAR_NAME
|
|
115
|
+
: process.env[CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME]
|
|
116
|
+
? CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME
|
|
117
|
+
: null;
|
|
118
|
+
if (envVarName === null) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return `This is determined by the permissions granted to ${envVarName}.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Detects a server-side authorization failure (a `require_operation` error,
|
|
126
|
+
* surfaced both as an HTTP 403 and as an error from a system UDF query) and
|
|
127
|
+
* produces a clean, single-line message naming the missing permission. When a
|
|
128
|
+
* deploy key is in use, appends advice for configuring its permissions.
|
|
129
|
+
*
|
|
130
|
+
* Returns `null` if `errorMessage` is not a permission error, so callers can
|
|
131
|
+
* fall back to their normal error handling.
|
|
132
|
+
*/
|
|
133
|
+
export function permissionDeniedErrorMessage(
|
|
134
|
+
errorMessage: string,
|
|
135
|
+
): string | null {
|
|
136
|
+
const index = errorMessage.indexOf(PERMISSION_DENIED_MESSAGE_PREFIX);
|
|
137
|
+
if (index === -1) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
// Drop any wrapping context (e.g. the `[CONVEX Q(...)]` prefix) and stack
|
|
141
|
+
// trace, keeping just the permission sentence.
|
|
142
|
+
let msg = errorMessage.slice(index).split("\n")[0].trim();
|
|
143
|
+
const advice = deployKeyPermissionAdvice();
|
|
144
|
+
if (advice) {
|
|
145
|
+
msg = `${msg}\n${advice}`;
|
|
146
|
+
}
|
|
147
|
+
return msg;
|
|
148
|
+
}
|
|
149
|
+
|
|
104
150
|
export function parsePositiveInteger(value: string) {
|
|
105
151
|
const parsedValue = parseInteger(value);
|
|
106
152
|
if (parsedValue <= 0) {
|
|
@@ -177,6 +223,16 @@ export class ThrowingFetchError extends Error {
|
|
|
177
223
|
} else if (this.response.status === 401) {
|
|
178
224
|
error_type = "fatal";
|
|
179
225
|
msg = `${msg}\nAuthenticate with \`npx convex dev\``;
|
|
226
|
+
} else if (this.response.status === 403) {
|
|
227
|
+
error_type = "fatal";
|
|
228
|
+
// Surface the missing permission cleanly (e.g. the server includes the
|
|
229
|
+
// required action like `deployment:data:view`) instead of the noisy
|
|
230
|
+
// "Error fetching ..." wrapper.
|
|
231
|
+
msg = this.serverErrorData?.message ?? msg;
|
|
232
|
+
const advice = deployKeyPermissionAdvice();
|
|
233
|
+
if (advice) {
|
|
234
|
+
msg = `${msg}\n${advice}`;
|
|
235
|
+
}
|
|
180
236
|
} else if (this.response.status === 404) {
|
|
181
237
|
error_type = "fatal";
|
|
182
238
|
msg = `${msg}: ${this.response.url}`;
|
package/src/cli/program.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { logout } from "./logout.js";
|
|
|
21
21
|
import { logs } from "./logs.js";
|
|
22
22
|
import { mcp } from "./mcp.js";
|
|
23
23
|
import { networkTest } from "./network_test.js";
|
|
24
|
+
import { project } from "./project.js";
|
|
24
25
|
import { reinit } from "./reinit.js";
|
|
25
26
|
import { run } from "./run.js";
|
|
26
27
|
import { typecheck } from "./typecheck.js";
|
|
@@ -51,6 +52,7 @@ export function buildProgram() {
|
|
|
51
52
|
.addCommand(env)
|
|
52
53
|
.addCommand(data)
|
|
53
54
|
.addCommand(deployment)
|
|
55
|
+
.addCommand(project)
|
|
54
56
|
.addCommand(codegen)
|
|
55
57
|
.addCommand(update)
|
|
56
58
|
.addCommand(logout)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Command, Option } from "@commander-js/extra-typings";
|
|
2
|
+
import { oneoffContext } from "../bundler/context.js";
|
|
3
|
+
import { logFinishedStep, logMessage, showSpinner } from "../bundler/log.js";
|
|
4
|
+
import { chalkStderr } from "chalk";
|
|
5
|
+
import { createProject } from "./lib/api.js";
|
|
6
|
+
import { ensureAuthCanCreateDeployment } from "./lib/deploymentSelection.js";
|
|
7
|
+
import { validateOrSelectTeam } from "./lib/utils/utils.js";
|
|
8
|
+
import { promptString } from "./lib/utils/prompts.js";
|
|
9
|
+
import { projectDashboardUrl } from "./lib/dashboard.js";
|
|
10
|
+
|
|
11
|
+
type ProjectCreateOptions = {
|
|
12
|
+
team?: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
async function runProjectCreate(
|
|
16
|
+
nameArg: string | undefined,
|
|
17
|
+
options: ProjectCreateOptions,
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
const ctx = await oneoffContext({
|
|
20
|
+
url: undefined,
|
|
21
|
+
adminKey: undefined,
|
|
22
|
+
envFile: undefined,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Creating a project goes through the platform API, which accepts personal
|
|
26
|
+
// access tokens and project keys but not deployment/preview deploy keys.
|
|
27
|
+
// Fail fast (and clearly) for the unsupported keys rather than surfacing an
|
|
28
|
+
// opaque error later.
|
|
29
|
+
await ensureAuthCanCreateDeployment(ctx);
|
|
30
|
+
|
|
31
|
+
const { team } = await validateOrSelectTeam(ctx, options.team, "Team:");
|
|
32
|
+
|
|
33
|
+
let projectName = nameArg;
|
|
34
|
+
if (!projectName) {
|
|
35
|
+
if (!process.stdin.isTTY) {
|
|
36
|
+
return await ctx.crash({
|
|
37
|
+
exitCode: 1,
|
|
38
|
+
errorType: "fatal",
|
|
39
|
+
printedMessage:
|
|
40
|
+
"Specify a project name:\n" + " `npx convex project create my-app`",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
projectName = await promptString(ctx, {
|
|
44
|
+
message: "Project name:",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
showSpinner(`Creating project ${projectName}...`);
|
|
49
|
+
// `project create` only creates the project. Provisioning a deployment is a
|
|
50
|
+
// separate, explicit step (`npx convex deployment create`) so this command
|
|
51
|
+
// doesn't have to mirror every deployment setting (region, class, ...).
|
|
52
|
+
const { projectSlug } = await createProject(ctx, {
|
|
53
|
+
teamId: team.id,
|
|
54
|
+
projectName,
|
|
55
|
+
deploymentToProvision: null,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
logFinishedStep(
|
|
59
|
+
`Created project ${chalkStderr.bold(projectSlug)} in team ` +
|
|
60
|
+
`${chalkStderr.bold(team.slug)}, manage it at ${chalkStderr.bold(
|
|
61
|
+
projectDashboardUrl(team.slug, projectSlug),
|
|
62
|
+
)}`,
|
|
63
|
+
);
|
|
64
|
+
logMessage(
|
|
65
|
+
chalkStderr.gray(
|
|
66
|
+
`Next, add a deployment with ` +
|
|
67
|
+
`\`npx convex deployment create ${team.slug}:${projectSlug}:dev --type dev\` ` +
|
|
68
|
+
`(pass \`--region us\` to choose a region).`,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const projectCreate = new Command("create")
|
|
74
|
+
.summary("Create a new project")
|
|
75
|
+
.description(
|
|
76
|
+
[
|
|
77
|
+
"Create a new project.",
|
|
78
|
+
"",
|
|
79
|
+
"Provisioning a deployment is a separate step — after creating the",
|
|
80
|
+
"project, run `npx convex deployment create` to add one.",
|
|
81
|
+
"",
|
|
82
|
+
"• Create a project in your only team: `npx convex project create my-app`",
|
|
83
|
+
"• Pick the team: `npx convex project create my-app --team my-team`",
|
|
84
|
+
].join("\n"),
|
|
85
|
+
)
|
|
86
|
+
.allowExcessArguments(false)
|
|
87
|
+
.argument(
|
|
88
|
+
"[name]",
|
|
89
|
+
"The name of the new project. Prompted for when omitted in an " +
|
|
90
|
+
"interactive terminal; required otherwise.",
|
|
91
|
+
)
|
|
92
|
+
.addOption(
|
|
93
|
+
new Option(
|
|
94
|
+
"--team <team_slug>",
|
|
95
|
+
"The team to create the project in. Defaults to your only team, or " +
|
|
96
|
+
"prompts when you belong to several.",
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
.action(runProjectCreate);
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "1.
|
|
1
|
+
export const version = "1.42.0";
|
package/src/server/database.ts
CHANGED
|
@@ -14,7 +14,13 @@ import {
|
|
|
14
14
|
|
|
15
15
|
interface BaseDatabaseReader<DataModel extends GenericDataModel> {
|
|
16
16
|
/**
|
|
17
|
-
* Fetch a single document from the database by
|
|
17
|
+
* Fetch a single document from the database by table name and
|
|
18
|
+
* {@link values.GenericId}.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const user = await ctx.db.get("users", userId);
|
|
23
|
+
* ```
|
|
18
24
|
*
|
|
19
25
|
* @param table - The name of the table to fetch the document from.
|
|
20
26
|
* @param id - The {@link values.GenericId} of the document to fetch from the database.
|
|
@@ -28,6 +34,9 @@ interface BaseDatabaseReader<DataModel extends GenericDataModel> {
|
|
|
28
34
|
/**
|
|
29
35
|
* Fetch a single document from the database by its {@link values.GenericId}.
|
|
30
36
|
*
|
|
37
|
+
* Supported for backwards compatibility. Prefer `db.get(tableName, id)` in
|
|
38
|
+
* new code, or `db.system.get(tableName, id)` for system tables.
|
|
39
|
+
*
|
|
31
40
|
* @param id - The {@link values.GenericId} of the document to fetch from the database.
|
|
32
41
|
* @returns - The {@link GenericDocument} of the document at the given {@link values.GenericId}, or `null` if it no longer exists.
|
|
33
42
|
*/
|
|
@@ -55,7 +64,7 @@ interface BaseDatabaseReader<DataModel extends GenericDataModel> {
|
|
|
55
64
|
* This accepts the string ID format as well as the `.toString()` representation
|
|
56
65
|
* of the legacy class-based ID format.
|
|
57
66
|
*
|
|
58
|
-
* This does not guarantee that the ID exists (i.e. `db.get(id)` may return `null`).
|
|
67
|
+
* This does not guarantee that the ID exists (i.e. `db.get(tableName, id)` may return `null`).
|
|
59
68
|
*
|
|
60
69
|
* @param tableName - The name of the table.
|
|
61
70
|
* @param id - The ID string.
|
|
@@ -109,7 +118,7 @@ export interface BaseTableReader<
|
|
|
109
118
|
*
|
|
110
119
|
* The two entry points are:
|
|
111
120
|
* - {@link GenericDatabaseReader.get}, which fetches a single document
|
|
112
|
-
* by
|
|
121
|
+
* by table name and {@link values.GenericId}.
|
|
113
122
|
* - {@link GenericDatabaseReader.query}, which starts building a query.
|
|
114
123
|
*
|
|
115
124
|
* @example
|
|
@@ -272,6 +281,9 @@ export interface GenericDatabaseWriter<DataModel extends GenericDataModel>
|
|
|
272
281
|
*
|
|
273
282
|
* This method will throw if the document does not exist.
|
|
274
283
|
*
|
|
284
|
+
* Supported for backwards compatibility. Prefer `db.patch(tableName, id, value)`
|
|
285
|
+
* in new code.
|
|
286
|
+
*
|
|
275
287
|
* @param id - The {@link values.GenericId} of the document to patch.
|
|
276
288
|
* @param value - The partial document to merge into the existing document.
|
|
277
289
|
*/
|
|
@@ -316,6 +328,9 @@ export interface GenericDatabaseWriter<DataModel extends GenericDataModel>
|
|
|
316
328
|
* Unlike `patch`, which does a shallow merge, `replace` overwrites the
|
|
317
329
|
* entire document.
|
|
318
330
|
*
|
|
331
|
+
* Supported for backwards compatibility. Prefer `db.replace(tableName, id, value)`
|
|
332
|
+
* in new code.
|
|
333
|
+
*
|
|
319
334
|
* @param id - The {@link values.GenericId} of the document to replace.
|
|
320
335
|
* @param value - The new document. System fields can be omitted.
|
|
321
336
|
*/
|
|
@@ -343,6 +358,9 @@ export interface GenericDatabaseWriter<DataModel extends GenericDataModel>
|
|
|
343
358
|
/**
|
|
344
359
|
* Delete an existing document.
|
|
345
360
|
*
|
|
361
|
+
* Supported for backwards compatibility. Prefer `db.delete(tableName, id)` in
|
|
362
|
+
* new code.
|
|
363
|
+
*
|
|
346
364
|
* **Note:** Convex queries do not support `.delete()` directly on query
|
|
347
365
|
* results. To delete multiple documents, `.collect()` them first, then
|
|
348
366
|
* delete each one individually.
|