Skip to content

Commit e06dff0

Browse files
committed
add preview-deploy-or-create flag to enable updating preview environments instead of having to create them each time
1 parent 942b909 commit e06dff0

File tree

1 file changed

+94
-5
lines changed

1 file changed

+94
-5
lines changed

src/cli/deploy.ts

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import {
1717
CONVEX_SELF_HOSTED_URL_VAR_NAME,
1818
CONVEX_DEPLOYMENT_ENV_VAR_NAME,
1919
bigBrainAPI,
20+
bigBrainAPIMaybeThrows,
21+
logAndHandleFetchError,
22+
ThrowingFetchError,
2023
} from "./lib/utils/utils.js";
2124
import { runFunctionAndLog } from "./lib/run.js";
2225
import { usageStateWarning } from "./lib/usage.js";
@@ -45,7 +48,13 @@ export const deploy = new Command("deploy")
4548
new Option(
4649
"--preview-create <name>",
4750
"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.",
48-
).conflicts("preview-name"),
51+
).conflicts(["preview-name", "preview-deploy-or-create"]),
52+
)
53+
.addOption(
54+
new Option(
55+
"--preview-deploy-or-create <name>",
56+
"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.",
57+
).conflicts(["preview-name", "preview-create"]),
4958
)
5059
.addOption(
5160
new Option(
@@ -126,7 +135,8 @@ Same format as .env.local or .env files, and overrides them.`,
126135
ctx,
127136
deploymentSelection.previewDeployKey,
128137
);
129-
await deployToNewPreviewDeployment(
138+
139+
await deployToPreviewDeployment(
130140
ctx,
131141
{
132142
previewDeployKey: deploymentSelection.previewDeployKey,
@@ -145,7 +155,7 @@ Same format as .env.local or .env files, and overrides them.`,
145155
}
146156
});
147157

148-
async function deployToNewPreviewDeployment(
158+
async function deployToPreviewDeployment(
149159
ctx: Context,
150160
deploymentSelection: {
151161
previewDeployKey: string;
@@ -158,6 +168,7 @@ async function deployToNewPreviewDeployment(
158168
options: {
159169
dryRun?: boolean | undefined;
160170
previewCreate?: string | undefined;
171+
previewDeployOrCreate?: string | undefined;
161172
previewRun?: string | undefined;
162173
cmdUrlEnvVarName?: string | undefined;
163174
cmd?: string | undefined;
@@ -170,16 +181,54 @@ async function deployToNewPreviewDeployment(
170181
debugBundlePath?: string | undefined;
171182
},
172183
) {
173-
const previewName = options.previewCreate ?? gitBranchFromEnvironment();
174-
if (previewName === null) {
184+
if (options.previewCreate && options.previewDeployOrCreate) {
175185
await ctx.crash({
186+
exitCode: 1,
187+
errorType: "fatal",
188+
printedMessage:
189+
"`npx convex deploy` to a preview deployment cannot have both `--preview-create` and `--preview-deploy-or-create options`",
190+
});
191+
}
192+
193+
const { previewStrategy, previewName } = (() => {
194+
if (options.previewCreate) {
195+
return {
196+
previewStrategy: "create" as const,
197+
previewName: options.previewCreate ?? gitBranchFromEnvironment(),
198+
};
199+
}
200+
201+
if (options.previewDeployOrCreate) {
202+
return {
203+
previewStrategy: "updateOrCreate" as const,
204+
previewName:
205+
options.previewDeployOrCreate ?? gitBranchFromEnvironment(),
206+
};
207+
}
208+
209+
return {
210+
previewStrategy: null,
211+
previewName: null,
212+
};
213+
})();
214+
if (!previewStrategy) {
215+
return await ctx.crash({
216+
exitCode: 1,
217+
errorType: "fatal",
218+
printedMessage:
219+
"`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`",
220+
});
221+
}
222+
if (!previewName) {
223+
return await ctx.crash({
176224
exitCode: 1,
177225
errorType: "fatal",
178226
printedMessage:
179227
"`npx convex deploy` to a preview deployment could not determine the preview name. Provide one using `--preview-create`",
180228
});
181229
}
182230

231+
// Dry run
183232
if (options.dryRun) {
184233
logFinishedStep(
185234
`Would have claimed preview deployment for "${previewName}"`,
@@ -199,6 +248,46 @@ async function deployToNewPreviewDeployment(
199248
}
200249
return;
201250
}
251+
252+
// Try deploying to existing preview - if does not exist will fall through to preview create deployment
253+
254+
if (previewStrategy === "updateOrCreate") {
255+
try {
256+
const result = await bigBrainAPIMaybeThrows({
257+
ctx,
258+
method: "POST",
259+
url: "deployment/authorize_preview",
260+
data: {
261+
previewName: previewName,
262+
projectSelection: deploymentSelection.projectSelection,
263+
},
264+
});
265+
266+
logMessage(
267+
`Existing preview deployment found. Deploying to previous preview...`,
268+
);
269+
return await deployToExistingDeployment(ctx, {
270+
...options,
271+
adminKey: result.adminKey,
272+
url: result.url,
273+
});
274+
} catch (err) {
275+
const isNotFoundError =
276+
err instanceof ThrowingFetchError &&
277+
err.response.status === 404 &&
278+
err.serverErrorData?.code === "PreviewNotFound";
279+
280+
if (isNotFoundError) {
281+
logMessage(
282+
`Existing preview deployment NOT FOUND. Deploy to NEW preview...`,
283+
);
284+
} else {
285+
return await logAndHandleFetchError(ctx, err);
286+
}
287+
}
288+
}
289+
290+
// Create new preview deployment
202291
const data = await bigBrainAPI({
203292
ctx,
204293
method: "POST",

0 commit comments

Comments
 (0)