convex
Version:
Client for the Convex Cloud
582 lines (581 loc) • 19.8 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var api_exports = {};
__export(api_exports, {
checkAccessToSelectedProject: () => checkAccessToSelectedProject,
createProject: () => createProject,
deploymentSelectionWithinProjectFromOptions: () => deploymentSelectionWithinProjectFromOptions,
deploymentSelectionWithinProjectSchema: () => deploymentSelectionWithinProjectSchema,
fetchDeploymentCredentialsProvisioningDevOrProdMaybeThrows: () => fetchDeploymentCredentialsProvisioningDevOrProdMaybeThrows,
fetchTeamAndProject: () => fetchTeamAndProject,
fetchTeamAndProjectForKey: () => fetchTeamAndProjectForKey,
loadSelectedDeploymentCredentials: () => loadSelectedDeploymentCredentials,
validateDeploymentSelectionForExistingDeployment: () => validateDeploymentSelectionForExistingDeployment
});
module.exports = __toCommonJS(api_exports);
var import_context = require("../../bundler/context.js");
var import_deployment = require("./deployment.js");
var import_run = require("./localDeployment/run.js");
var import_utils = require("./utils/utils.js");
var import_zod = require("zod");
var import_localDeployment = require("./localDeployment/localDeployment.js");
var import_anonymous = require("./localDeployment/anonymous.js");
async function createProject(ctx, {
teamSlug: selectedTeamSlug,
projectName,
partitionId,
deploymentTypeToProvision
}) {
const provisioningArgs = {
team: selectedTeamSlug,
projectName,
// TODO: Consider allowing projects with no deployments, or consider switching
// to provisioning prod on creation.
deploymentType: deploymentTypeToProvision,
partitionId
};
const data = await (0, import_utils.bigBrainAPI)({
ctx,
method: "POST",
url: "create_project",
data: provisioningArgs
});
const { projectSlug, teamSlug, projectsRemaining } = data;
if (projectSlug === void 0 || teamSlug === void 0 || projectsRemaining === void 0) {
const error = "Unexpected response during provisioning: " + JSON.stringify(data);
return await ctx.crash({
exitCode: 1,
errorType: "transient",
errForSentry: error,
printedMessage: error
});
}
return {
projectSlug,
teamSlug,
projectsRemaining
};
}
const deploymentSelectionWithinProjectSchema = import_zod.z.discriminatedUnion(
"kind",
[
import_zod.z.object({ kind: import_zod.z.literal("previewName"), previewName: import_zod.z.string() }),
import_zod.z.object({ kind: import_zod.z.literal("deploymentName"), deploymentName: import_zod.z.string() }),
import_zod.z.object({ kind: import_zod.z.literal("prod"), partitionId: import_zod.z.number().optional() }),
import_zod.z.object({
kind: import_zod.z.literal("implicitProd"),
partitionId: import_zod.z.number().optional()
}),
import_zod.z.object({ kind: import_zod.z.literal("ownDev"), partitionId: import_zod.z.number().optional() })
]
);
async function deploymentSelectionWithinProjectFromOptions(ctx, options) {
if (options.previewName !== void 0) {
return { kind: "previewName", previewName: options.previewName };
}
if (options.deploymentName !== void 0) {
return { kind: "deploymentName", deploymentName: options.deploymentName };
}
const partitionId = options.partitionId ? parseInt(options.partitionId) : void 0;
if (options.prod) {
return { kind: "prod", partitionId };
}
if (options.implicitProd) {
return { kind: "implicitProd", partitionId };
}
return { kind: "ownDev", partitionId };
}
async function validateDeploymentSelectionForExistingDeployment(ctx, deploymentSelection, source) {
if (deploymentSelection.kind === "ownDev" || deploymentSelection.kind === "implicitProd") {
return;
}
switch (source) {
case "selfHosted":
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "The `--prod`, `--preview-name`, and `--deployment-name` flags cannot be used with a self-hosted deployment."
});
case "deployKey":
(0, import_context.logWarning)(
ctx,
"Ignoring `--prod`, `--preview-name`, or `--deployment-name` flags and using deployment from CONVEX_DEPLOY_KEY"
);
break;
case "cliArgs":
(0, import_context.logWarning)(
ctx,
"Ignoring `--prod`, `--preview-name`, or `--deployment-name` flags since this command was run with --url and --admin-key"
);
break;
}
}
async function hasAccessToProject(ctx, selector) {
try {
await (0, import_utils.bigBrainAPIMaybeThrows)({
ctx,
url: `/api/teams/${selector.teamSlug}/projects/${selector.projectSlug}/deployments`,
method: "GET"
});
return true;
} catch (err) {
if (err instanceof import_utils.ThrowingFetchError && (err.serverErrorData?.code === "TeamNotFound" || err.serverErrorData?.code === "ProjectNotFound")) {
return false;
}
return (0, import_utils.logAndHandleFetchError)(ctx, err);
}
}
async function checkAccessToSelectedProject(ctx, projectSelection) {
switch (projectSelection.kind) {
case "deploymentName": {
const result = await getTeamAndProjectSlugForDeployment(ctx, {
deploymentName: projectSelection.deploymentName
});
if (result === null) {
return { kind: "noAccess" };
}
return {
kind: "hasAccess",
teamSlug: result.teamSlug,
projectSlug: result.projectSlug
};
}
case "teamAndProjectSlugs": {
const hasAccess = await hasAccessToProject(ctx, {
teamSlug: projectSelection.teamSlug,
projectSlug: projectSelection.projectSlug
});
if (!hasAccess) {
return { kind: "noAccess" };
}
return {
kind: "hasAccess",
teamSlug: projectSelection.teamSlug,
projectSlug: projectSelection.projectSlug
};
}
case "projectDeployKey":
return { kind: "unknown" };
default: {
const _exhaustivenessCheck = projectSelection;
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: `Invalid project selection: ${projectSelection.kind}`
});
}
}
}
async function getTeamAndProjectSlugForDeployment(ctx, selector) {
try {
const body = await (0, import_utils.bigBrainAPIMaybeThrows)({
ctx,
url: `/api/deployment/${selector.deploymentName}/team_and_project`,
method: "GET"
});
return { teamSlug: body.team, projectSlug: body.project };
} catch (err) {
if (err instanceof import_utils.ThrowingFetchError && (err.serverErrorData?.code === "DeploymentNotFound" || err.serverErrorData?.code === "ProjectNotFound")) {
return null;
}
return (0, import_utils.logAndHandleFetchError)(ctx, err);
}
}
async function fetchDeploymentCredentialsProvisioningDevOrProdMaybeThrows(ctx, projectSelection, deploymentType, partitionId) {
if (projectSelection.kind === "projectDeployKey") {
const auth = ctx.bigBrainAuth();
const doesAuthMatch = auth !== null && auth.kind === "projectKey" && auth.projectKey === projectSelection.projectDeployKey;
if (!doesAuthMatch) {
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
errForSentry: new Error(
"Expected project deploy key to match the big brain auth header"
),
printedMessage: "Unexpected error when loading the Convex deployment"
});
}
}
let data;
try {
data = await (0, import_utils.bigBrainAPIMaybeThrows)({
ctx,
method: "POST",
url: "deployment/provision_and_authorize",
data: {
teamSlug: projectSelection.kind === "teamAndProjectSlugs" ? projectSelection.teamSlug : null,
projectSlug: projectSelection.kind === "teamAndProjectSlugs" ? projectSelection.projectSlug : null,
deploymentType: deploymentType === "prod" ? "prod" : "dev",
partitionId
}
});
} catch (error) {
const msg = "Unknown error during authorization: " + error;
return await ctx.crash({
exitCode: 1,
errorType: "transient",
errForSentry: new Error(msg),
printedMessage: msg
});
}
const adminKey = data.adminKey;
const url = data.url;
const deploymentName = data.deploymentName;
if (adminKey === void 0 || url === void 0) {
const msg = "Unknown error during authorization: " + JSON.stringify(data);
return await ctx.crash({
exitCode: 1,
errorType: "transient",
errForSentry: new Error(msg),
printedMessage: msg
});
}
return { adminKey, deploymentUrl: url, deploymentName };
}
async function fetchExistingDevDeploymentCredentialsOrCrash(ctx, deploymentName) {
const slugs = await fetchTeamAndProject(ctx, deploymentName);
const credentials = await fetchDeploymentCredentialsProvisioningDevOrProdMaybeThrows(
ctx,
{
kind: "teamAndProjectSlugs",
teamSlug: slugs.team,
projectSlug: slugs.project
},
"dev",
void 0
);
return {
deploymentName: credentials.deploymentName,
adminKey: credentials.adminKey,
url: credentials.deploymentUrl,
deploymentType: "dev"
};
}
async function handleOwnDev(ctx, projectSelection, partitionId) {
switch (projectSelection.kind) {
case "deploymentName": {
if (projectSelection.deploymentType === "local") {
const credentials = await (0, import_localDeployment.loadLocalDeploymentCredentials)(
ctx,
projectSelection.deploymentName
);
return {
deploymentName: projectSelection.deploymentName,
adminKey: credentials.adminKey,
url: credentials.deploymentUrl,
deploymentType: "local"
};
}
return await fetchExistingDevDeploymentCredentialsOrCrash(
ctx,
projectSelection.deploymentName
);
}
case "teamAndProjectSlugs":
case "projectDeployKey": {
const credentials = await fetchDeploymentCredentialsProvisioningDevOrProdMaybeThrows(
ctx,
projectSelection,
"dev",
partitionId
);
return {
url: credentials.deploymentUrl,
adminKey: credentials.adminKey,
deploymentName: credentials.deploymentName,
deploymentType: "dev"
};
}
}
}
async function handleProd(ctx, projectSelection, partitionId) {
switch (projectSelection.kind) {
case "deploymentName": {
const credentials = await (0, import_utils.bigBrainAPI)({
ctx,
method: "POST",
url: "deployment/authorize_prod",
data: {
deploymentName: projectSelection.deploymentName,
partitionId
}
});
return credentials;
}
case "teamAndProjectSlugs":
case "projectDeployKey": {
const credentials = await fetchDeploymentCredentialsProvisioningDevOrProdMaybeThrows(
ctx,
projectSelection,
"prod",
partitionId
);
return {
url: credentials.deploymentUrl,
adminKey: credentials.adminKey,
deploymentName: credentials.deploymentName,
deploymentType: "prod"
};
}
}
}
async function handlePreview(ctx, previewName, projectSelection) {
switch (projectSelection.kind) {
case "deploymentName":
case "teamAndProjectSlugs":
return await (0, import_utils.bigBrainAPI)({
ctx,
method: "POST",
url: "deployment/authorize_preview",
data: {
previewName,
projectSelection
}
});
case "projectDeployKey":
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "Project deploy keys are not supported for preview deployments"
});
}
}
async function handleDeploymentName(ctx, deploymentName, projectSelection) {
switch (projectSelection.kind) {
case "deploymentName":
case "teamAndProjectSlugs":
return await (0, import_utils.bigBrainAPI)({
ctx,
method: "POST",
url: "deployment/authorize_within_current_project",
data: {
selectedDeploymentName: deploymentName,
projectSelection
}
});
case "projectDeployKey":
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "Project deploy keys are not supported with the --deployment-name flag"
});
}
}
async function fetchDeploymentCredentialsWithinCurrentProject(ctx, projectSelection, deploymentSelection) {
switch (deploymentSelection.kind) {
case "ownDev": {
return await handleOwnDev(
ctx,
projectSelection,
deploymentSelection.partitionId
);
}
case "implicitProd":
case "prod": {
return await handleProd(
ctx,
projectSelection,
deploymentSelection.partitionId
);
}
case "previewName":
return await handlePreview(
ctx,
deploymentSelection.previewName,
projectSelection
);
case "deploymentName":
return await handleDeploymentName(
ctx,
deploymentSelection.deploymentName,
projectSelection
);
default: {
const _exhaustivenessCheck = deploymentSelection;
return ctx.crash({
exitCode: 1,
errorType: "fatal",
// This should be unreachable, so don't bother with a printed message.
printedMessage: null,
errForSentry: `Unexpected deployment selection: ${deploymentSelection}`
});
}
}
}
async function _loadExistingDeploymentCredentialsForProject(ctx, targetProject, deploymentSelection, { ensureLocalRunning } = { ensureLocalRunning: true }) {
const accessResult = await checkAccessToSelectedProject(ctx, targetProject);
if (accessResult.kind === "noAccess") {
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "You don't have access to the selected project. Run `npx convex dev` to select a different project."
});
}
const result = await fetchDeploymentCredentialsWithinCurrentProject(
ctx,
targetProject,
deploymentSelection
);
(0, import_context.logVerbose)(
ctx,
`Deployment URL: ${result.url}, Deployment Name: ${result.deploymentName}, Deployment Type: ${result.deploymentType}`
);
if (ensureLocalRunning && result.deploymentType === "local") {
await (0, import_run.assertLocalBackendRunning)(ctx, {
url: result.url,
deploymentName: result.deploymentName
});
}
return {
...result,
deploymentFields: {
deploymentName: result.deploymentName,
deploymentType: result.deploymentType,
projectSlug: accessResult.kind === "hasAccess" ? accessResult.projectSlug : null,
teamSlug: accessResult.kind === "hasAccess" ? accessResult.teamSlug : null
}
};
}
async function loadSelectedDeploymentCredentials(ctx, deploymentSelection, selectionWithinProject, { ensureLocalRunning } = { ensureLocalRunning: true }) {
switch (deploymentSelection.kind) {
case "existingDeployment":
await validateDeploymentSelectionForExistingDeployment(
ctx,
selectionWithinProject,
deploymentSelection.deploymentToActOn.source
);
(0, import_context.logVerbose)(
ctx,
`Deployment URL: ${deploymentSelection.deploymentToActOn.url}, Deployment Name: ${deploymentSelection.deploymentToActOn.deploymentFields?.deploymentName ?? "unknown"}, Deployment Type: ${deploymentSelection.deploymentToActOn.deploymentFields?.deploymentType ?? "unknown"}`
);
return {
adminKey: deploymentSelection.deploymentToActOn.adminKey,
url: deploymentSelection.deploymentToActOn.url,
deploymentFields: deploymentSelection.deploymentToActOn.deploymentFields
};
case "chooseProject":
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "No CONVEX_DEPLOYMENT set, run `npx convex dev` to configure a Convex project"
});
case "preview": {
const slugs = await (0, import_deployment.getTeamAndProjectFromPreviewAdminKey)(
ctx,
deploymentSelection.previewDeployKey
);
return await _loadExistingDeploymentCredentialsForProject(
ctx,
{
kind: "teamAndProjectSlugs",
teamSlug: slugs.teamSlug,
projectSlug: slugs.projectSlug
},
selectionWithinProject,
{ ensureLocalRunning }
);
}
case "deploymentWithinProject": {
return await _loadExistingDeploymentCredentialsForProject(
ctx,
deploymentSelection.targetProject,
selectionWithinProject,
{ ensureLocalRunning }
);
}
case "anonymous": {
if (deploymentSelection.deploymentName === null) {
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "No CONVEX_DEPLOYMENT set, run `npx convex dev` to configure a Convex project"
});
}
const config = await (0, import_anonymous.loadAnonymousDeployment)(
ctx,
deploymentSelection.deploymentName
);
const url = (0, import_run.localDeploymentUrl)(config.ports.cloud);
if (ensureLocalRunning) {
await (0, import_run.assertLocalBackendRunning)(ctx, {
url,
deploymentName: deploymentSelection.deploymentName
});
}
return {
adminKey: config.adminKey,
url,
deploymentFields: {
deploymentName: deploymentSelection.deploymentName,
deploymentType: "anonymous",
projectSlug: null,
teamSlug: null
}
};
}
default: {
const _exhaustivenessCheck = deploymentSelection;
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "Unknown deployment type"
});
}
}
}
async function fetchTeamAndProject(ctx, deploymentName) {
const data = await (0, import_utils.bigBrainAPI)({
ctx,
method: "GET",
url: `deployment/${deploymentName}/team_and_project`
});
const { team, project } = data;
if (team === void 0 || project === void 0) {
const msg = "Unknown error when fetching team and project: " + JSON.stringify(data);
return await ctx.crash({
exitCode: 1,
errorType: "transient",
errForSentry: new Error(msg),
printedMessage: msg
});
}
return data;
}
async function fetchTeamAndProjectForKey(ctx, deployKey) {
const data = await (0, import_utils.bigBrainAPI)({
ctx,
method: "POST",
url: `deployment/team_and_project_for_key`,
data: {
deployKey
}
});
const { team, project } = data;
if (team === void 0 || project === void 0) {
const msg = "Unknown error when fetching team and project: " + JSON.stringify(data);
return await ctx.crash({
exitCode: 1,
errorType: "transient",
errForSentry: new Error(msg),
printedMessage: msg
});
}
return data;
}
//# sourceMappingURL=api.js.map