Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 99 additions & 6 deletions src/cli/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
CONVEX_SELF_HOSTED_URL_VAR_NAME,
CONVEX_DEPLOYMENT_ENV_VAR_NAME,
bigBrainAPI,
bigBrainAPIMaybeThrows,
logAndHandleFetchError,
ThrowingFetchError,
} from "./lib/utils/utils.js";
import { runFunctionAndLog } from "./lib/run.js";
import { usageStateWarning } from "./lib/usage.js";
Expand Down Expand Up @@ -44,8 +47,14 @@ export const deploy = new Command("deploy")
.addOption(
new Option(
"--preview-create <name>",
"The name to associate with this deployment if deploying to a newly created preview deployment. Defaults to the current Git branch name in Vercel, Netlify and GitHub CI. This parameter can only be used with a preview deploy key (when used with another type of key, the command will return an error).",
).conflicts("preview-name"),
"The name to associate with this deployment if deploying to a newly created preview deployment. Defaults to the current Git branch name in Vercel, Netlify and GitHub CI. This is ignored if deploying to a production deployment.",
).conflicts(["preview-name", "preview-deploy-or-create"]),
)
.addOption(
new Option(
"--preview-deploy-or-create <name>",
"Updates existing preview deployment if it exists, otherwise creates a new preview deployment. The name to associate with this deployment if deploying to a newly created preview deployment. Defaults to the current Git branch name in Vercel, Netlify and GitHub CI. This is ignored if deploying to a production deployment.",
).conflicts(["preview-name", "preview-create"]),
)
.addOption(
new Option(
Expand Down Expand Up @@ -132,7 +141,8 @@ Same format as .env.local or .env files, and overrides them.`,
ctx,
deploymentSelection.previewDeployKey,
);
await deployToNewPreviewDeployment(

await deployToPreviewDeployment(
ctx,
{
previewDeployKey: deploymentSelection.previewDeployKey,
Expand All @@ -144,6 +154,8 @@ Same format as .env.local or .env files, and overrides them.`,
},
{
...cmdOptions,
allowDeletingLargeIndexes:
cmdOptions.allowDeletingLargeIndexes ?? false,
},
);
} else {
Expand Down Expand Up @@ -171,7 +183,7 @@ Same format as .env.local or .env files, and overrides them.`,
}
});

async function deployToNewPreviewDeployment(
async function deployToPreviewDeployment(
ctx: Context,
deploymentSelection: {
previewDeployKey: string;
Expand All @@ -184,6 +196,7 @@ async function deployToNewPreviewDeployment(
options: {
dryRun?: boolean | undefined;
previewCreate?: string | undefined;
previewDeployOrCreate?: string | undefined;
previewRun?: string | undefined;
cmdUrlEnvVarName?: string | undefined;
cmd?: string | undefined;
Expand All @@ -194,18 +207,57 @@ async function deployToNewPreviewDeployment(

debug?: boolean | undefined;
debugBundlePath?: string | undefined;
allowDeletingLargeIndexes: boolean;
},
) {
const previewName = options.previewCreate ?? gitBranchFromEnvironment();
if (previewName === null) {
if (options.previewCreate && options.previewDeployOrCreate) {
await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage:
"`npx convex deploy` to a preview deployment cannot have both `--preview-create` and `--preview-deploy-or-create options`",
});
}

const { previewStrategy, previewName } = (() => {
if (options.previewCreate) {
return {
previewStrategy: "create" as const,
previewName: options.previewCreate ?? gitBranchFromEnvironment(),
};
}

if (options.previewDeployOrCreate) {
return {
previewStrategy: "updateOrCreate" as const,
previewName:
options.previewDeployOrCreate ?? gitBranchFromEnvironment(),
};
}

return {
previewStrategy: null,
previewName: null,
};
})();
if (!previewStrategy) {
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage:
"`npx convex deploy` to a preview deployment could not whether you wanted to create a preview deployment or update a previous preview deployment. Use either `--preview-create or --preview-deploy-or-create`",
});
}
if (!previewName) {
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage:
"`npx convex deploy` to a preview deployment could not determine the preview name. Provide one using `--preview-create`",
});
}

// Dry run
if (options.dryRun) {
logFinishedStep(
`Would have claimed preview deployment for "${previewName}"`,
Expand All @@ -225,6 +277,47 @@ async function deployToNewPreviewDeployment(
}
return;
}

// Try deploying to existing preview - if does not exist will fall through to preview create deployment

if (previewStrategy === "updateOrCreate") {
try {
const result = await bigBrainAPIMaybeThrows({
ctx,
method: "POST",
url: "deployment/authorize_preview",
data: {
previewName: previewName,
projectSelection: deploymentSelection.projectSelection,
},
});

logMessage(
`Existing preview deployment found. Deploying to previous preview...`,
);
return await deployToExistingDeployment(ctx, {
...options,
adminKey: result.adminKey,
url: result.url,
allowDeletingLargeIndexes: options.allowDeletingLargeIndexes,
});
} catch (err) {
const isNotFoundError =
err instanceof ThrowingFetchError &&
err.response.status === 404 &&
err.serverErrorData?.code === "PreviewNotFound";

if (isNotFoundError) {
logMessage(
`Existing preview deployment NOT FOUND. Deploy to NEW preview...`,
);
} else {
return await logAndHandleFetchError(ctx, err);
}
}
}

// Create new preview deployment
const data = await bigBrainAPI({
ctx,
method: "POST",
Expand Down