convex 1.47.0 → 1.48.0-alpha.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/dist/browser.bundle.js +1 -1
- package/dist/browser.bundle.js.map +1 -1
- package/dist/cjs/bundler/context.js +7 -5
- package/dist/cjs/bundler/context.js.map +2 -2
- package/dist/cjs/bundler/fs.js +47 -6
- package/dist/cjs/bundler/fs.js.map +2 -2
- package/dist/cjs/cli/account.js +237 -0
- package/dist/cjs/cli/account.js.map +7 -0
- package/dist/cjs/cli/configure.js +15 -2
- package/dist/cjs/cli/configure.js.map +2 -2
- package/dist/cjs/cli/lib/deployKeyWarning.js +43 -0
- package/dist/cjs/cli/lib/deployKeyWarning.js.map +7 -0
- package/dist/cjs/cli/lib/deployment.js +2 -0
- package/dist/cjs/cli/lib/deployment.js.map +2 -2
- package/dist/cjs/cli/lib/deploymentSelection.js +27 -18
- package/dist/cjs/cli/lib/deploymentSelection.js.map +2 -2
- package/dist/cjs/cli/lib/envvars.js +32 -0
- package/dist/cjs/cli/lib/envvars.js.map +2 -2
- package/dist/cjs/cli/lib/fsUtils.js +1 -1
- package/dist/cjs/cli/lib/fsUtils.js.map +2 -2
- package/dist/cjs/cli/lib/generateDocs.js +1 -1
- package/dist/cjs/cli/lib/generateDocs.js.map +1 -1
- package/dist/cjs/cli/lib/login.js +28 -4
- package/dist/cjs/cli/lib/login.js.map +2 -2
- package/dist/cjs/cli/lib/mcp/requestContext.js +5 -1
- package/dist/cjs/cli/lib/mcp/requestContext.js.map +2 -2
- package/dist/cjs/cli/lib/profileManagement.js +196 -0
- package/dist/cjs/cli/lib/profileManagement.js.map +7 -0
- package/dist/cjs/cli/lib/profiles.js +333 -0
- package/dist/cjs/cli/lib/profiles.js.map +7 -0
- package/dist/cjs/cli/lib/utils/utils.js +18 -9
- package/dist/cjs/cli/lib/utils/utils.js.map +2 -2
- package/dist/cjs/cli/login.js +213 -114
- package/dist/cjs/cli/login.js.map +3 -3
- package/dist/cjs/cli/logout.js +113 -12
- package/dist/cjs/cli/logout.js.map +3 -3
- package/dist/cjs/cli/mcp.js +43 -10
- package/dist/cjs/cli/mcp.js.map +3 -3
- package/dist/cjs/cli/program.js +2 -1
- package/dist/cjs/cli/program.js.map +2 -2
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs-types/bundler/context.d.ts +5 -0
- package/dist/cjs-types/bundler/context.d.ts.map +1 -1
- package/dist/cjs-types/bundler/fs.d.ts +7 -2
- package/dist/cjs-types/bundler/fs.d.ts.map +1 -1
- package/dist/cjs-types/cli/account.d.ts +6 -0
- package/dist/cjs-types/cli/account.d.ts.map +1 -0
- package/dist/cjs-types/cli/configure.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/deployKeyWarning.d.ts +3 -0
- package/dist/cjs-types/cli/lib/deployKeyWarning.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/deployment.d.ts +2 -0
- package/dist/cjs-types/cli/lib/deployment.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/deploymentSelection.d.ts +2 -1
- package/dist/cjs-types/cli/lib/deploymentSelection.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/envvars.d.ts +6 -0
- package/dist/cjs-types/cli/lib/envvars.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/fsUtils.d.ts +1 -0
- package/dist/cjs-types/cli/lib/fsUtils.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/login.d.ts +3 -1
- package/dist/cjs-types/cli/lib/login.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/mcp/requestContext.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/mcp/tools/insights.d.ts +2 -2
- package/dist/cjs-types/cli/lib/profileManagement.d.ts +10 -0
- package/dist/cjs-types/cli/lib/profileManagement.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/profiles.d.ts +59 -0
- package/dist/cjs-types/cli/lib/profiles.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/utils/utils.d.ts +5 -0
- package/dist/cjs-types/cli/lib/utils/utils.d.ts.map +1 -1
- package/dist/cjs-types/cli/login.d.ts +21 -0
- package/dist/cjs-types/cli/login.d.ts.map +1 -1
- package/dist/cjs-types/cli/logout.d.ts +8 -1
- package/dist/cjs-types/cli/logout.d.ts.map +1 -1
- package/dist/cjs-types/cli/mcp.d.ts.map +1 -1
- package/dist/cjs-types/cli/program.d.ts.map +1 -1
- package/dist/cjs-types/index.d.ts +1 -1
- package/dist/cjs-types/index.d.ts.map +1 -1
- package/dist/cli.bundle.cjs +2118 -1096
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/esm/bundler/context.js +7 -5
- package/dist/esm/bundler/context.js.map +2 -2
- package/dist/esm/bundler/fs.js +47 -6
- package/dist/esm/bundler/fs.js.map +2 -2
- package/dist/esm/cli/account.js +221 -0
- package/dist/esm/cli/account.js.map +7 -0
- package/dist/esm/cli/configure.js +15 -2
- package/dist/esm/cli/configure.js.map +2 -2
- package/dist/esm/cli/lib/deployKeyWarning.js +27 -0
- package/dist/esm/cli/lib/deployKeyWarning.js.map +7 -0
- package/dist/esm/cli/lib/deployment.js +2 -2
- package/dist/esm/cli/lib/deployment.js.map +2 -2
- package/dist/esm/cli/lib/deploymentSelection.js +35 -20
- package/dist/esm/cli/lib/deploymentSelection.js.map +2 -2
- package/dist/esm/cli/lib/envvars.js +35 -1
- package/dist/esm/cli/lib/envvars.js.map +2 -2
- package/dist/esm/cli/lib/fsUtils.js +1 -1
- package/dist/esm/cli/lib/fsUtils.js.map +2 -2
- package/dist/esm/cli/lib/generateDocs.js +1 -1
- package/dist/esm/cli/lib/generateDocs.js.map +1 -1
- package/dist/esm/cli/lib/login.js +36 -4
- package/dist/esm/cli/lib/login.js.map +2 -2
- package/dist/esm/cli/lib/mcp/requestContext.js +5 -1
- package/dist/esm/cli/lib/mcp/requestContext.js.map +2 -2
- package/dist/esm/cli/lib/profileManagement.js +179 -0
- package/dist/esm/cli/lib/profileManagement.js.map +7 -0
- package/dist/esm/cli/lib/profiles.js +288 -0
- package/dist/esm/cli/lib/profiles.js.map +7 -0
- package/dist/esm/cli/lib/utils/utils.js +16 -9
- package/dist/esm/cli/lib/utils/utils.js.map +2 -2
- package/dist/esm/cli/login.js +225 -112
- package/dist/esm/cli/login.js.map +3 -3
- package/dist/esm/cli/logout.js +110 -13
- package/dist/esm/cli/logout.js.map +2 -2
- package/dist/esm/cli/mcp.js +34 -10
- package/dist/esm/cli/mcp.js.map +2 -2
- package/dist/esm/cli/program.js +2 -1
- package/dist/esm/cli/program.js.map +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm-types/bundler/context.d.ts +5 -0
- package/dist/esm-types/bundler/context.d.ts.map +1 -1
- package/dist/esm-types/bundler/fs.d.ts +7 -2
- package/dist/esm-types/bundler/fs.d.ts.map +1 -1
- package/dist/esm-types/cli/account.d.ts +6 -0
- package/dist/esm-types/cli/account.d.ts.map +1 -0
- package/dist/esm-types/cli/configure.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/deployKeyWarning.d.ts +3 -0
- package/dist/esm-types/cli/lib/deployKeyWarning.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/deployment.d.ts +2 -0
- package/dist/esm-types/cli/lib/deployment.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/deploymentSelection.d.ts +2 -1
- package/dist/esm-types/cli/lib/deploymentSelection.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/envvars.d.ts +6 -0
- package/dist/esm-types/cli/lib/envvars.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/fsUtils.d.ts +1 -0
- package/dist/esm-types/cli/lib/fsUtils.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/login.d.ts +3 -1
- package/dist/esm-types/cli/lib/login.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/mcp/requestContext.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/mcp/tools/insights.d.ts +2 -2
- package/dist/esm-types/cli/lib/profileManagement.d.ts +10 -0
- package/dist/esm-types/cli/lib/profileManagement.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/profiles.d.ts +59 -0
- package/dist/esm-types/cli/lib/profiles.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/utils/utils.d.ts +5 -0
- package/dist/esm-types/cli/lib/utils/utils.d.ts.map +1 -1
- package/dist/esm-types/cli/login.d.ts +21 -0
- package/dist/esm-types/cli/login.d.ts.map +1 -1
- package/dist/esm-types/cli/logout.d.ts +8 -1
- package/dist/esm-types/cli/logout.d.ts.map +1 -1
- package/dist/esm-types/cli/mcp.d.ts.map +1 -1
- package/dist/esm-types/cli/program.d.ts.map +1 -1
- package/dist/esm-types/index.d.ts +1 -1
- package/dist/esm-types/index.d.ts.map +1 -1
- package/dist/react.bundle.js +1 -1
- package/dist/react.bundle.js.map +1 -1
- package/package.json +1 -1
- package/src/bundler/context.ts +9 -5
- package/src/bundler/fs.ts +54 -5
- package/src/cli/account.ts +320 -0
- package/src/cli/configure.ts +18 -2
- package/src/cli/lib/deployKeyWarning.ts +43 -0
- package/src/cli/lib/deployment.ts +2 -2
- package/src/cli/lib/deploymentSelection.ts +37 -18
- package/src/cli/lib/envvars.ts +43 -1
- package/src/cli/lib/fsUtils.ts +5 -2
- package/src/cli/lib/generateDocs.ts +1 -1
- package/src/cli/lib/login.ts +47 -3
- package/src/cli/lib/mcp/requestContext.ts +7 -1
- package/src/cli/lib/profileManagement.ts +262 -0
- package/src/cli/lib/profiles.ts +398 -0
- package/src/cli/lib/utils/utils.ts +19 -15
- package/src/cli/login.ts +293 -168
- package/src/cli/logout.ts +142 -17
- package/src/cli/mcp.ts +48 -11
- package/src/cli/program.ts +2 -0
- package/src/index.ts +1 -1
|
@@ -31,18 +31,26 @@ import {
|
|
|
31
31
|
targetProjectForLocalSelector,
|
|
32
32
|
} from "./localDeployment/projectMismatch.js";
|
|
33
33
|
import { chalkStderr } from "chalk";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
getBuildEnvironment,
|
|
36
|
+
loadProjectEnvironmentVariables,
|
|
37
|
+
} from "./envvars.js";
|
|
35
38
|
import { readGlobalConfig } from "./utils/globalConfig.js";
|
|
39
|
+
import {
|
|
40
|
+
getProfileSelection,
|
|
41
|
+
ProfileSelection,
|
|
42
|
+
readProfileAuth,
|
|
43
|
+
} from "./profiles.js";
|
|
36
44
|
import {
|
|
37
45
|
CONVEX_DEPLOYMENT_ENV_VAR_NAME,
|
|
38
46
|
CONVEX_DEPLOYMENT_TOKEN_ENV_VAR_NAME,
|
|
39
47
|
CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
|
|
40
48
|
CONVEX_SELF_HOSTED_ADMIN_KEY_VAR_NAME,
|
|
41
49
|
CONVEX_SELF_HOSTED_URL_VAR_NAME,
|
|
42
|
-
ENV_VAR_FILE_PATH,
|
|
43
50
|
bigBrainAPI,
|
|
44
51
|
processDeployKeyValue,
|
|
45
52
|
readDeployKeyFromEnv,
|
|
53
|
+
readDeployKeyEnvVar,
|
|
46
54
|
typedPlatformClient,
|
|
47
55
|
} from "./utils/utils.js";
|
|
48
56
|
import * as dotenv from "dotenv";
|
|
@@ -83,10 +91,11 @@ export async function initializeBigBrainAuth(
|
|
|
83
91
|
if (initialArgs.url !== undefined && initialArgs.adminKey !== undefined) {
|
|
84
92
|
// Do not check any env vars if `url` and `adminKey` are specified via CLI
|
|
85
93
|
ctx._updateBigBrainAuth(
|
|
86
|
-
getBigBrainAuth(ctx, {
|
|
94
|
+
await getBigBrainAuth(ctx, {
|
|
87
95
|
previewDeployKey: null,
|
|
88
96
|
projectKey: null,
|
|
89
97
|
deploymentKey: null,
|
|
98
|
+
skipProfile: true,
|
|
90
99
|
}),
|
|
91
100
|
);
|
|
92
101
|
return;
|
|
@@ -106,30 +115,31 @@ export async function initializeBigBrainAuth(
|
|
|
106
115
|
const rawDeployKey = readDeployKeyFromEnv((name) => config[name]);
|
|
107
116
|
const deployKey = await processDeployKeyValue(ctx, rawDeployKey);
|
|
108
117
|
if (deployKey !== undefined) {
|
|
109
|
-
const bigBrainAuth = getBigBrainAuth(ctx, {
|
|
118
|
+
const bigBrainAuth = await getBigBrainAuth(ctx, {
|
|
110
119
|
previewDeployKey: isPreviewDeployKey(deployKey) ? deployKey : null,
|
|
111
120
|
projectKey: isProjectKey(deployKey) ? deployKey : null,
|
|
112
121
|
deploymentKey: isDeploymentKey(deployKey) ? deployKey : null,
|
|
122
|
+
envFile: initialArgs.envFile,
|
|
113
123
|
});
|
|
114
124
|
ctx._updateBigBrainAuth(bigBrainAuth);
|
|
115
125
|
return;
|
|
116
126
|
}
|
|
117
127
|
// No deploy key was found in the env file, so fall back on using the global config
|
|
118
128
|
ctx._updateBigBrainAuth(
|
|
119
|
-
getBigBrainAuth(ctx, {
|
|
129
|
+
await getBigBrainAuth(ctx, {
|
|
120
130
|
previewDeployKey: null,
|
|
121
131
|
projectKey: null,
|
|
122
132
|
deploymentKey: null,
|
|
133
|
+
envFile: initialArgs.envFile,
|
|
123
134
|
}),
|
|
124
135
|
);
|
|
125
136
|
return;
|
|
126
137
|
}
|
|
127
|
-
|
|
128
|
-
dotenv.config();
|
|
138
|
+
loadProjectEnvironmentVariables();
|
|
129
139
|
const rawDeployKey = readDeployKeyFromEnv((name) => process.env[name]);
|
|
130
140
|
const deployKey = await processDeployKeyValue(ctx, rawDeployKey);
|
|
131
141
|
if (deployKey !== undefined) {
|
|
132
|
-
const bigBrainAuth = getBigBrainAuth(ctx, {
|
|
142
|
+
const bigBrainAuth = await getBigBrainAuth(ctx, {
|
|
133
143
|
previewDeployKey: isPreviewDeployKey(deployKey) ? deployKey : null,
|
|
134
144
|
projectKey: isProjectKey(deployKey) ? deployKey : null,
|
|
135
145
|
deploymentKey: isDeploymentKey(deployKey) ? deployKey : null,
|
|
@@ -138,7 +148,7 @@ export async function initializeBigBrainAuth(
|
|
|
138
148
|
return;
|
|
139
149
|
}
|
|
140
150
|
ctx._updateBigBrainAuth(
|
|
141
|
-
getBigBrainAuth(ctx, {
|
|
151
|
+
await getBigBrainAuth(ctx, {
|
|
142
152
|
previewDeployKey: null,
|
|
143
153
|
projectKey: null,
|
|
144
154
|
deploymentKey: null,
|
|
@@ -150,6 +160,7 @@ export async function initializeBigBrainAuth(
|
|
|
150
160
|
export async function updateBigBrainAuthAfterLogin(
|
|
151
161
|
ctx: Context,
|
|
152
162
|
accessToken: string,
|
|
163
|
+
profile?: ProfileSelection,
|
|
153
164
|
) {
|
|
154
165
|
const existingAuth = ctx.bigBrainAuth();
|
|
155
166
|
if (existingAuth !== null && existingAuth.kind === "projectKey") {
|
|
@@ -162,6 +173,7 @@ export async function updateBigBrainAuthAfterLogin(
|
|
|
162
173
|
accessToken: accessToken,
|
|
163
174
|
kind: "accessToken",
|
|
164
175
|
header: `Bearer ${accessToken}`,
|
|
176
|
+
...(profile ? { profile } : {}),
|
|
165
177
|
});
|
|
166
178
|
}
|
|
167
179
|
|
|
@@ -169,19 +181,22 @@ export async function clearBigBrainAuth(ctx: Context) {
|
|
|
169
181
|
ctx._updateBigBrainAuth(null);
|
|
170
182
|
}
|
|
171
183
|
|
|
172
|
-
function getBigBrainAuth(
|
|
184
|
+
async function getBigBrainAuth(
|
|
173
185
|
ctx: Context,
|
|
174
186
|
opts: {
|
|
175
187
|
previewDeployKey: string | null;
|
|
176
188
|
projectKey: string | null;
|
|
177
189
|
deploymentKey: string | null;
|
|
190
|
+
envFile?: string;
|
|
191
|
+
skipProfile?: boolean;
|
|
178
192
|
},
|
|
179
|
-
): BigBrainAuth | null {
|
|
193
|
+
): Promise<BigBrainAuth | null> {
|
|
180
194
|
if (process.env.CONVEX_OVERRIDE_ACCESS_TOKEN) {
|
|
181
195
|
return {
|
|
182
196
|
accessToken: process.env.CONVEX_OVERRIDE_ACCESS_TOKEN,
|
|
183
197
|
kind: "accessToken",
|
|
184
198
|
header: `Bearer ${process.env.CONVEX_OVERRIDE_ACCESS_TOKEN}`,
|
|
199
|
+
profile: null,
|
|
185
200
|
};
|
|
186
201
|
}
|
|
187
202
|
if (opts.projectKey !== null) {
|
|
@@ -200,12 +215,19 @@ function getBigBrainAuth(
|
|
|
200
215
|
deploymentKey: opts.deploymentKey,
|
|
201
216
|
};
|
|
202
217
|
}
|
|
218
|
+
const profile = opts.skipProfile
|
|
219
|
+
? null
|
|
220
|
+
: await getProfileSelection(ctx, { envFile: opts.envFile });
|
|
221
|
+
if (profile !== null) {
|
|
222
|
+
return await readProfileAuth(ctx, profile);
|
|
223
|
+
}
|
|
203
224
|
const globalConfig = readGlobalConfig(ctx);
|
|
204
225
|
if (globalConfig) {
|
|
205
226
|
return {
|
|
206
227
|
kind: "accessToken",
|
|
207
228
|
header: `Bearer ${globalConfig.accessToken}`,
|
|
208
229
|
accessToken: globalConfig.accessToken,
|
|
230
|
+
profile: null,
|
|
209
231
|
};
|
|
210
232
|
}
|
|
211
233
|
if (opts.previewDeployKey !== null) {
|
|
@@ -272,9 +294,9 @@ export async function ensureAuthCanCreateDeployment(
|
|
|
272
294
|
)} and try again.`,
|
|
273
295
|
});
|
|
274
296
|
}
|
|
275
|
-
const envVar =
|
|
276
|
-
|
|
277
|
-
|
|
297
|
+
const envVar =
|
|
298
|
+
readDeployKeyEnvVar((name) => process.env[name])?.name ??
|
|
299
|
+
CONVEX_DEPLOY_KEY_ENV_VAR_NAME;
|
|
278
300
|
return await ctx.crash({
|
|
279
301
|
exitCode: 1,
|
|
280
302
|
errorType: "fatal",
|
|
@@ -569,10 +591,7 @@ async function resolveBaseDeploymentSelection(
|
|
|
569
591
|
}
|
|
570
592
|
return result.metadata;
|
|
571
593
|
}
|
|
572
|
-
|
|
573
|
-
dotenv.config({ path: ENV_VAR_FILE_PATH });
|
|
574
|
-
// for variables not already set, use .env values
|
|
575
|
-
dotenv.config();
|
|
594
|
+
loadProjectEnvironmentVariables();
|
|
576
595
|
const result = await getDeploymentSelectionFromEnv(
|
|
577
596
|
ctx,
|
|
578
597
|
selectionWithinProject,
|
package/src/cli/lib/envvars.ts
CHANGED
|
@@ -6,7 +6,49 @@ import * as dotenv from "dotenv";
|
|
|
6
6
|
|
|
7
7
|
import { Context } from "../../bundler/context.js";
|
|
8
8
|
import { logWarning } from "../../bundler/log.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
CONVEX_DEPLOY_KEY_ENV_VAR_NAME,
|
|
11
|
+
readDeployKeyEnvVar,
|
|
12
|
+
ENV_VAR_FILE_PATH,
|
|
13
|
+
loadPackageJson,
|
|
14
|
+
} from "./utils/utils.js";
|
|
15
|
+
|
|
16
|
+
export const CONVEX_ACCOUNT_ENV_VAR = "CONVEX_ACCOUNT";
|
|
17
|
+
|
|
18
|
+
const loadedEnvironment = new Map<string, { value: string; source: string }>();
|
|
19
|
+
|
|
20
|
+
export function loadProjectEnvironmentVariables() {
|
|
21
|
+
// Only remove values supplied by our previous load; later shell overrides win.
|
|
22
|
+
for (const [name, { value }] of loadedEnvironment) {
|
|
23
|
+
if (process.env[name] === value) delete process.env[name];
|
|
24
|
+
}
|
|
25
|
+
loadedEnvironment.clear();
|
|
26
|
+
const environment: Record<string, string> = {};
|
|
27
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
28
|
+
if (value !== undefined) environment[name] = value;
|
|
29
|
+
}
|
|
30
|
+
for (const file of [ENV_VAR_FILE_PATH, ".env"]) {
|
|
31
|
+
dotenv.config({ path: file, processEnv: environment });
|
|
32
|
+
for (const [name, value] of Object.entries(environment)) {
|
|
33
|
+
// Account selection reads the current project's files directly.
|
|
34
|
+
if (name !== CONVEX_ACCOUNT_ENV_VAR && process.env[name] === undefined) {
|
|
35
|
+
process.env[name] = value;
|
|
36
|
+
loadedEnvironment.set(name, { value, source: file });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getDeployKeySource(key: string) {
|
|
43
|
+
const envVarName =
|
|
44
|
+
readDeployKeyEnvVar((name) => process.env[name])?.name ??
|
|
45
|
+
CONVEX_DEPLOY_KEY_ENV_VAR_NAME;
|
|
46
|
+
const loaded = loadedEnvironment.get(envVarName);
|
|
47
|
+
return {
|
|
48
|
+
envVarName,
|
|
49
|
+
source: loaded?.value === key ? loaded.source : "shell environment",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
10
52
|
|
|
11
53
|
const _FRAMEWORKS = [
|
|
12
54
|
"create-react-app",
|
package/src/cli/lib/fsUtils.ts
CHANGED
|
@@ -6,12 +6,15 @@ import { NodeFs } from "../../bundler/fs.js";
|
|
|
6
6
|
export function recursivelyDelete(
|
|
7
7
|
ctx: Context,
|
|
8
8
|
deletePath: string,
|
|
9
|
-
opts?: { force?: boolean; dryRun?: boolean },
|
|
9
|
+
opts?: { force?: boolean; dryRun?: boolean; followSymlinks?: boolean },
|
|
10
10
|
) {
|
|
11
11
|
const dryRun = !!opts?.dryRun;
|
|
12
12
|
let st;
|
|
13
13
|
try {
|
|
14
|
-
st =
|
|
14
|
+
st =
|
|
15
|
+
opts?.followSymlinks === false
|
|
16
|
+
? ctx.fs.lstat(deletePath)
|
|
17
|
+
: ctx.fs.stat(deletePath);
|
|
15
18
|
} catch (err: any) {
|
|
16
19
|
if (err.code === "ENOENT" && opts?.force) {
|
|
17
20
|
return;
|
|
@@ -299,7 +299,7 @@ function renderCopyableCommands(text: string): string {
|
|
|
299
299
|
.map((line) =>
|
|
300
300
|
/^- /.test(line)
|
|
301
301
|
? line.replace(
|
|
302
|
-
/`(npx convex [^`]*)`/g,
|
|
302
|
+
/`(npx convex(?:@[^\s`]+)? [^`]*)`/g,
|
|
303
303
|
(_, command) =>
|
|
304
304
|
`<CodeWithCopyButton text={${JSON.stringify(command)}} />`,
|
|
305
305
|
)
|
package/src/cli/lib/login.ts
CHANGED
|
@@ -32,6 +32,17 @@ import {
|
|
|
32
32
|
modifyGlobalConfig,
|
|
33
33
|
} from "./utils/globalConfig.js";
|
|
34
34
|
import { updateBigBrainAuthAfterLogin } from "./deploymentSelection.js";
|
|
35
|
+
import {
|
|
36
|
+
getProfileSelection,
|
|
37
|
+
ProfileSelection,
|
|
38
|
+
profileConfigPath,
|
|
39
|
+
writeProfileCredentials,
|
|
40
|
+
readProfileCredentials,
|
|
41
|
+
} from "./profiles.js";
|
|
42
|
+
import {
|
|
43
|
+
profileLoginMetadata,
|
|
44
|
+
revokeReplacedProfileToken,
|
|
45
|
+
} from "./profileManagement.js";
|
|
35
46
|
|
|
36
47
|
// Per https://github.com/panva/node-openid-client/tree/main/docs#customizing
|
|
37
48
|
custom.setHttpOptionsDefaults({
|
|
@@ -291,6 +302,7 @@ export async function performLogin(
|
|
|
291
302
|
anonymousId,
|
|
292
303
|
vercel,
|
|
293
304
|
vercelOverride,
|
|
305
|
+
profile: selectedProfile,
|
|
294
306
|
}: {
|
|
295
307
|
overrideAuthUrl?: string | undefined;
|
|
296
308
|
overrideAuthClient?: string | undefined;
|
|
@@ -307,8 +319,16 @@ export async function performLogin(
|
|
|
307
319
|
anonymousId?: string | undefined;
|
|
308
320
|
vercel?: boolean | undefined;
|
|
309
321
|
vercelOverride?: string | undefined;
|
|
322
|
+
profile?: ProfileSelection | null;
|
|
310
323
|
} = {},
|
|
311
324
|
) {
|
|
325
|
+
const auth = ctx.bigBrainAuth();
|
|
326
|
+
const profile =
|
|
327
|
+
selectedProfile !== undefined
|
|
328
|
+
? selectedProfile
|
|
329
|
+
: auth?.kind === "accessToken" && auth.profile !== undefined
|
|
330
|
+
? auth.profile
|
|
331
|
+
: await getProfileSelection(ctx);
|
|
312
332
|
loginFlow = loginFlow || "auto";
|
|
313
333
|
// Get access token from big-brain
|
|
314
334
|
// Default the device name to the hostname, but allow the user to change this if the terminal is interactive.
|
|
@@ -400,6 +420,12 @@ export async function performLogin(
|
|
|
400
420
|
}
|
|
401
421
|
|
|
402
422
|
// Exchange the WorkOS access token for a Convex personal access token.
|
|
423
|
+
const previous =
|
|
424
|
+
profile === null ? null : await readProfileCredentials(ctx, profile, true);
|
|
425
|
+
const metadata =
|
|
426
|
+
profile === null
|
|
427
|
+
? {}
|
|
428
|
+
: await profileLoginMetadata(ctx, profile, accessToken, previous);
|
|
403
429
|
ctx._updateBigBrainAuth({
|
|
404
430
|
accessToken: accessToken,
|
|
405
431
|
kind: "accessToken",
|
|
@@ -417,8 +443,19 @@ export async function performLogin(
|
|
|
417
443
|
const newAccessToken = response.data!.accessToken;
|
|
418
444
|
const globalConfig = { accessToken: newAccessToken };
|
|
419
445
|
try {
|
|
420
|
-
|
|
421
|
-
|
|
446
|
+
if (profile !== null) {
|
|
447
|
+
await writeProfileCredentials(
|
|
448
|
+
ctx,
|
|
449
|
+
profile.name,
|
|
450
|
+
newAccessToken,
|
|
451
|
+
metadata,
|
|
452
|
+
previous,
|
|
453
|
+
);
|
|
454
|
+
} else {
|
|
455
|
+
await modifyGlobalConfig(ctx, globalConfig);
|
|
456
|
+
}
|
|
457
|
+
const path =
|
|
458
|
+
profile !== null ? profileConfigPath(profile.name) : globalConfigPath();
|
|
422
459
|
logFinishedStep(`Saved credentials to ${formatPathForPrinting(path)}`);
|
|
423
460
|
} catch (err: unknown) {
|
|
424
461
|
return await ctx.crash({
|
|
@@ -429,8 +466,15 @@ export async function performLogin(
|
|
|
429
466
|
});
|
|
430
467
|
}
|
|
431
468
|
|
|
469
|
+
if (profile !== null) {
|
|
470
|
+
await revokeReplacedProfileToken(
|
|
471
|
+
ctx,
|
|
472
|
+
previous?.accessToken,
|
|
473
|
+
newAccessToken,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
432
476
|
logVerbose(`performLogin: updating big brain auth after login`);
|
|
433
|
-
await updateBigBrainAuthAfterLogin(ctx, newAccessToken);
|
|
477
|
+
await updateBigBrainAuthAfterLogin(ctx, newAccessToken, profile ?? undefined);
|
|
434
478
|
|
|
435
479
|
logVerbose(`performLogin: checking opt ins, acceptOptIns: ${acceptOptIns}`);
|
|
436
480
|
// Do opt in to TOS and Privacy Policy stuff
|
|
@@ -149,7 +149,13 @@ export function encodeDeploymentSelector(
|
|
|
149
149
|
projectDir,
|
|
150
150
|
deployment,
|
|
151
151
|
};
|
|
152
|
-
|
|
152
|
+
// ASCII JSON escapes support Unicode paths with the existing Latin-1 decoder.
|
|
153
|
+
const serialized = JSON.stringify(payload).replace(
|
|
154
|
+
/[\u0080-\uffff]/g,
|
|
155
|
+
(character) =>
|
|
156
|
+
`\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`,
|
|
157
|
+
);
|
|
158
|
+
return `${deployment.kind}:${btoa(serialized)}`;
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
const payloadSchema = z.object({
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { ProjectSelection } from "./deploymentSelection.js";
|
|
2
|
+
import { Context } from "../../bundler/context.js";
|
|
3
|
+
import { logMessage, logWarning } from "../../bundler/log.js";
|
|
4
|
+
import { provisionHost } from "./config.js";
|
|
5
|
+
import { version } from "../version.js";
|
|
6
|
+
import {
|
|
7
|
+
isAnonymousDeployment,
|
|
8
|
+
getDeploymentTypeFromConfiguredDeployment,
|
|
9
|
+
stripDeploymentTypePrefix,
|
|
10
|
+
} from "./deployment.js";
|
|
11
|
+
import { CONVEX_DEPLOYMENT_ENV_VAR_NAME } from "./utils/utils.js";
|
|
12
|
+
import { promptYesNo } from "./utils/prompts.js";
|
|
13
|
+
import { loadProjectEnvironmentVariables } from "./envvars.js";
|
|
14
|
+
import {
|
|
15
|
+
ProfileCredentials,
|
|
16
|
+
ProfileSelection,
|
|
17
|
+
readProfileCredentials,
|
|
18
|
+
readProfileSettings,
|
|
19
|
+
readProfileToken,
|
|
20
|
+
savedProfileNames,
|
|
21
|
+
writeProfileDirectory,
|
|
22
|
+
} from "./profiles.js";
|
|
23
|
+
|
|
24
|
+
export async function profileApi(
|
|
25
|
+
token: string,
|
|
26
|
+
pathname: string,
|
|
27
|
+
body?: unknown,
|
|
28
|
+
) {
|
|
29
|
+
return await fetch(`${provisionHost}${pathname}`, {
|
|
30
|
+
method: body === undefined ? "GET" : "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
33
|
+
"Convex-Client": `npm-cli-${version}`,
|
|
34
|
+
...(body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
35
|
+
},
|
|
36
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
37
|
+
signal: AbortSignal.timeout(10_000),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function deploymentAccess(
|
|
42
|
+
token: string,
|
|
43
|
+
deploymentName: string,
|
|
44
|
+
): Promise<"yes" | "no" | "unknown"> {
|
|
45
|
+
try {
|
|
46
|
+
const response = await profileApi(
|
|
47
|
+
token,
|
|
48
|
+
`/v1/deployments/${encodeURIComponent(deploymentName)}`,
|
|
49
|
+
);
|
|
50
|
+
if (response.ok) return "yes";
|
|
51
|
+
return [401, 403, 404].includes(response.status) ? "no" : "unknown";
|
|
52
|
+
} catch {
|
|
53
|
+
return "unknown";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function projectAccess(
|
|
58
|
+
token: string,
|
|
59
|
+
target: ProjectSelection,
|
|
60
|
+
): Promise<"yes" | "no" | "unknown"> {
|
|
61
|
+
if (target.kind === "deploymentName")
|
|
62
|
+
return deploymentAccess(token, target.deploymentName);
|
|
63
|
+
if (target.kind !== "teamAndProjectSlugs") return "unknown";
|
|
64
|
+
try {
|
|
65
|
+
const response = await profileApi(
|
|
66
|
+
token,
|
|
67
|
+
`/v1/teams/${encodeURIComponent(target.teamSlug)}/projects/${encodeURIComponent(target.projectSlug)}`,
|
|
68
|
+
);
|
|
69
|
+
if (response.ok) return "yes";
|
|
70
|
+
return [401, 403, 404].includes(response.status) ? "no" : "unknown";
|
|
71
|
+
} catch {
|
|
72
|
+
return "unknown";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function configuredCloudDeployment(): string | null {
|
|
77
|
+
const configured = process.env[CONVEX_DEPLOYMENT_ENV_VAR_NAME]?.trim();
|
|
78
|
+
if (
|
|
79
|
+
!configured ||
|
|
80
|
+
getDeploymentTypeFromConfiguredDeployment(configured) === "local"
|
|
81
|
+
)
|
|
82
|
+
return null;
|
|
83
|
+
const name = stripDeploymentTypePrefix(configured);
|
|
84
|
+
return isAnonymousDeployment(name) ? null : name;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function bindProfileToDirectory(
|
|
88
|
+
ctx: Context,
|
|
89
|
+
name: string,
|
|
90
|
+
force = false,
|
|
91
|
+
target?: ProjectSelection,
|
|
92
|
+
): Promise<boolean> {
|
|
93
|
+
const credentials = await readProfileCredentials(ctx, {
|
|
94
|
+
name,
|
|
95
|
+
source: "account selection",
|
|
96
|
+
});
|
|
97
|
+
loadProjectEnvironmentVariables();
|
|
98
|
+
const deployment = configuredCloudDeployment();
|
|
99
|
+
const selectedTarget =
|
|
100
|
+
target ??
|
|
101
|
+
(deployment === null
|
|
102
|
+
? null
|
|
103
|
+
: {
|
|
104
|
+
kind: "deploymentName" as const,
|
|
105
|
+
deploymentName: deployment,
|
|
106
|
+
deploymentType: null,
|
|
107
|
+
});
|
|
108
|
+
if (!force && selectedTarget !== null) {
|
|
109
|
+
const label =
|
|
110
|
+
selectedTarget.kind === "teamAndProjectSlugs"
|
|
111
|
+
? `project "${selectedTarget.teamSlug}/${selectedTarget.projectSlug}"`
|
|
112
|
+
: `deployment "${selectedTarget.kind === "deploymentName" ? selectedTarget.deploymentName : "configured"}"`;
|
|
113
|
+
const access = await projectAccess(
|
|
114
|
+
credentials!.accessToken,
|
|
115
|
+
selectedTarget,
|
|
116
|
+
);
|
|
117
|
+
if (access !== "yes") {
|
|
118
|
+
logWarning(
|
|
119
|
+
access === "no"
|
|
120
|
+
? `Account "${name}" cannot access ${label}.`
|
|
121
|
+
: `Could not verify whether account "${name}" can access ${label}.`,
|
|
122
|
+
);
|
|
123
|
+
if (
|
|
124
|
+
!process.stdin.isTTY ||
|
|
125
|
+
!(await promptYesNo(ctx, {
|
|
126
|
+
message: `Use account "${name}" here anyway?`,
|
|
127
|
+
default: false,
|
|
128
|
+
}))
|
|
129
|
+
) {
|
|
130
|
+
logMessage(
|
|
131
|
+
`Directory selection unchanged. To select this account anyway, run \`npx convex account select ${name} --force\`.`,
|
|
132
|
+
);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await writeProfileDirectory(ctx, name);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function profileLoginMetadata(
|
|
142
|
+
ctx: Context,
|
|
143
|
+
profile: ProfileSelection,
|
|
144
|
+
loginToken: string,
|
|
145
|
+
previous: ProfileCredentials | null,
|
|
146
|
+
): Promise<Omit<ProfileCredentials, "accessToken">> {
|
|
147
|
+
let identity: { memberId: string; email: string } | null = null;
|
|
148
|
+
try {
|
|
149
|
+
const response = await profileApi(loginToken, "/api/dashboard/profile");
|
|
150
|
+
if (response.ok) {
|
|
151
|
+
const data = await response.json();
|
|
152
|
+
if (
|
|
153
|
+
(typeof data.id === "number" || typeof data.id === "string") &&
|
|
154
|
+
typeof data.email === "string"
|
|
155
|
+
) {
|
|
156
|
+
identity = { memberId: String(data.id), email: data.email };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Older authentication tokens can be valid without access to the dashboard profile API.
|
|
161
|
+
}
|
|
162
|
+
const sameAccount =
|
|
163
|
+
previous !== null && (previous.memberId === undefined || identity === null)
|
|
164
|
+
? await tokensBelongToSameAccount(previous.accessToken, loginToken)
|
|
165
|
+
: null;
|
|
166
|
+
if (
|
|
167
|
+
previous !== null &&
|
|
168
|
+
(previous.memberId === undefined || identity === null
|
|
169
|
+
? sameAccount !== true
|
|
170
|
+
: identity.memberId !== previous.memberId)
|
|
171
|
+
) {
|
|
172
|
+
return await ctx.crash({
|
|
173
|
+
exitCode: 1,
|
|
174
|
+
errorType: "fatal",
|
|
175
|
+
printedMessage:
|
|
176
|
+
sameAccount === null &&
|
|
177
|
+
(previous.memberId === undefined || identity === null)
|
|
178
|
+
? `Could not verify the identity of saved account "${profile.name}". To replace this saved login, run \`npx convex logout --account ${profile.name}\` followed by \`npx convex login --account ${profile.name}\`. Choose the intended account when logging in.`
|
|
179
|
+
: `The login does not match saved account "${profile.name}". Log in with the matching account, or choose another name with \`npx convex login --account <name>\`.`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
...(identity ??
|
|
184
|
+
(previous?.memberId === undefined
|
|
185
|
+
? {}
|
|
186
|
+
: { memberId: previous.memberId, email: previous.email })),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function tokensBelongToSameAccount(
|
|
191
|
+
older: string,
|
|
192
|
+
newer: string,
|
|
193
|
+
): Promise<boolean | null> {
|
|
194
|
+
try {
|
|
195
|
+
const responses = await Promise.all(
|
|
196
|
+
[older, newer].map((token) =>
|
|
197
|
+
profileApi(token, "/v1/list_personal_access_tokens?limit=100"),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
if (responses.some((response) => !response.ok)) return null;
|
|
201
|
+
const lists = await Promise.all(
|
|
202
|
+
responses.map((response) => response.json()),
|
|
203
|
+
);
|
|
204
|
+
if (
|
|
205
|
+
lists.some(
|
|
206
|
+
(list) => !Array.isArray(list.items) || list.items.length === 0,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
return null;
|
|
210
|
+
const ids = new Set(
|
|
211
|
+
lists[0].items.map((token: { id: unknown }) => String(token.id)),
|
|
212
|
+
);
|
|
213
|
+
if (
|
|
214
|
+
lists[1].items.some((token: { id: unknown }) => ids.has(String(token.id)))
|
|
215
|
+
)
|
|
216
|
+
return true;
|
|
217
|
+
// Disjoint partial pages cannot establish that the accounts differ.
|
|
218
|
+
return lists.some((list) => list.pagination?.hasMore !== false)
|
|
219
|
+
? null
|
|
220
|
+
: false;
|
|
221
|
+
} catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function revokeReplacedProfileToken(
|
|
227
|
+
ctx: Context,
|
|
228
|
+
previous: string | undefined,
|
|
229
|
+
accessToken: string,
|
|
230
|
+
) {
|
|
231
|
+
if (previous === undefined || previous === accessToken) return;
|
|
232
|
+
const settings = await readProfileSettings(ctx);
|
|
233
|
+
if (
|
|
234
|
+
settings.revokeReplacedTokens === false ||
|
|
235
|
+
settings.accessToken === previous
|
|
236
|
+
)
|
|
237
|
+
return;
|
|
238
|
+
for (const name of savedProfileNames(ctx)) {
|
|
239
|
+
const token = readProfileToken(ctx, name);
|
|
240
|
+
if (token === null) {
|
|
241
|
+
logWarning(
|
|
242
|
+
`The new login was saved, but account "${name}" could not be read. Skipping revocation of the replaced token.`,
|
|
243
|
+
);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (token === previous) return;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
// The API verifies token ownership using the newly authenticated account.
|
|
250
|
+
const response = await profileApi(
|
|
251
|
+
accessToken,
|
|
252
|
+
"/v1/delete_personal_access_token",
|
|
253
|
+
{ id: previous },
|
|
254
|
+
);
|
|
255
|
+
if (response.ok) return;
|
|
256
|
+
} catch {
|
|
257
|
+
// The new credentials are already saved, so a failed revocation can be retried in the dashboard.
|
|
258
|
+
}
|
|
259
|
+
logWarning(
|
|
260
|
+
"The new login was saved, but the replaced token could not be revoked. You can remove it in the dashboard's account settings.",
|
|
261
|
+
);
|
|
262
|
+
}
|