Skip to content

Commit f31ed78

Browse files
ldanilekConvex, Inc.
authored and
Convex, Inc.
committed
npx convex self-host deploy (#34039)
create a distilled version of `npx convex deploy` that only has the flags and behavior necessary for self-hosting. 1. refactored deployment-agnostic options into the Command interface 2. refactor out `deployToDeployment`, which is the stuff that `npx convex deploy` runs after it knows what deployment it wants to talk to. 3. create command `npx convex self-host deploy` that deploys to a self-hosted deployment note the `handleManuallySetUrlAndAdminKey` writes VITE_CONVEX_URL to .env.local, which I think only `npx convex self-host dev` wants to do. The flow I imagine is similar to existing `npx convex dev` and `npx convex deploy`: while developing locally you run `npx convex self-host dev`, and it uses env variables from .env.local. Then when you git push, Vercel (or whatever hosting provider) runs `npx convex self-host deploy`, which uses env variables from vercel, and pushes to potentially a different self-hosted deployment. The point of these commands is they talk to a deployment directly via url and admin key, and never have to talk to big-brain. GitOrigin-RevId: e0d5793528f1a7a61e05567fe916605744476d3a
1 parent 51264c3 commit f31ed78

File tree

4 files changed

+208
-121
lines changed

4 files changed

+208
-121
lines changed

npm-packages/convex/src/cli/deploy.ts

Lines changed: 3 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
getConfiguredDeployment,
2626
readAdminKeyFromEnvVar,
2727
} from "./lib/utils/utils.js";
28-
import { spawnSync } from "child_process";
2928
import { runFunctionAndLog } from "./lib/run.js";
3029
import { usageStateWarning } from "./lib/usage.js";
3130
import {
@@ -35,6 +34,7 @@ import {
3534
} from "./lib/deployment.js";
3635
import { runPush } from "./lib/components.js";
3736
import { promptYesNo } from "./lib/utils/prompts.js";
37+
import { deployToDeployment, runCommand } from "./lib/deploy2.js";
3838

3939
export const deploy = new Command("deploy")
4040
.summary("Deploy to your prod deployment")
@@ -43,45 +43,7 @@ export const deploy = new Command("deploy")
4343
"Deploys to a preview deployment if the `CONVEX_DEPLOY_KEY` environment variable is set to a Preview Deploy Key.",
4444
)
4545
.allowExcessArguments(false)
46-
.option("-v, --verbose", "Show full listing of changes")
47-
.option(
48-
"--dry-run",
49-
"Print out the generated configuration without deploying to your Convex deployment",
50-
)
51-
.option("-y, --yes", "Skip confirmation prompt when running locally")
52-
.addOption(
53-
new Option(
54-
"--typecheck <mode>",
55-
`Whether to check TypeScript files with \`tsc --noEmit\` before deploying.`,
56-
)
57-
.choices(["enable", "try", "disable"] as const)
58-
.default("try" as const),
59-
)
60-
.option(
61-
"--typecheck-components",
62-
"Check TypeScript files within component implementations with `tsc --noEmit`.",
63-
false,
64-
)
65-
.addOption(
66-
new Option(
67-
"--codegen <mode>",
68-
"Whether to regenerate code in `convex/_generated/` before pushing.",
69-
)
70-
.choices(["enable", "disable"] as const)
71-
.default("enable" as const),
72-
)
73-
.addOption(
74-
new Option(
75-
"--cmd <command>",
76-
"Command to run as part of deploying your app (e.g. `vite build`). This command can depend on the environment variables specified in `--cmd-url-env-var-name` being set.",
77-
),
78-
)
79-
.addOption(
80-
new Option(
81-
"--cmd-url-env-var-name <name>",
82-
"Environment variable name to set Convex deployment URL (e.g. `VITE_CONVEX_URL`) when using `--cmd`",
83-
),
84-
)
46+
.addDeployOptions()
8547
.addOption(
8648
new Option(
8749
"--preview-run <functionName>",
@@ -103,12 +65,9 @@ export const deploy = new Command("deploy")
10365
.default("enable" as const)
10466
.hideHelp(),
10567
)
106-
.addOption(new Option("--debug-bundle-path <path>").hideHelp())
107-
.addOption(new Option("--debug").hideHelp())
10868
// Hidden options to pass in admin key and url for tests and local development
10969
.addOption(new Option("--admin-key <adminKey>").hideHelp())
11070
.addOption(new Option("--url <url>").hideHelp())
111-
.addOption(new Option("--write-push-request <writePushRequest>").hideHelp()) // Option used for tests in backend
11271
.addOption(
11372
new Option(
11473
"--preview-name <name>",
@@ -117,7 +76,6 @@ export const deploy = new Command("deploy")
11776
.hideHelp()
11877
.conflicts("preview-create"),
11978
)
120-
.addOption(new Option("--live-component-sources").hideHelp())
12179
.addOption(new Option("--partition-id <id>").hideHelp())
12280
.showHelpAfterError()
12381
.action(async (cmdOptions) => {
@@ -330,77 +288,7 @@ async function deployToExistingDeployment(
330288
}
331289
}
332290

333-
await runCommand(ctx, { ...options, url });
334-
335-
const pushOptions: PushOptions = {
336-
adminKey,
337-
verbose: !!options.verbose,
338-
dryRun: !!options.dryRun,
339-
typecheck: options.typecheck,
340-
typecheckComponents: options.typecheckComponents,
341-
debug: !!options.debug,
342-
debugBundlePath: options.debugBundlePath,
343-
codegen: options.codegen === "enable",
344-
url,
345-
writePushRequest: options.writePushRequest,
346-
liveComponentSources: !!options.liveComponentSources,
347-
};
348-
showSpinner(
349-
ctx,
350-
`Deploying to ${url}...${options.dryRun ? " [dry run]" : ""}`,
351-
);
352-
await runPush(ctx, pushOptions);
353-
logFinishedStep(
354-
ctx,
355-
`${
356-
options.dryRun ? "Would have deployed" : "Deployed"
357-
} Convex functions to ${url}`,
358-
);
359-
}
360-
361-
async function runCommand(
362-
ctx: Context,
363-
options: {
364-
cmdUrlEnvVarName?: string | undefined;
365-
cmd?: string | undefined;
366-
dryRun?: boolean | undefined;
367-
url: string;
368-
},
369-
) {
370-
if (options.cmd === undefined) {
371-
return;
372-
}
373-
374-
const urlVar =
375-
options.cmdUrlEnvVarName ?? (await suggestedEnvVarName(ctx)).envVar;
376-
showSpinner(
377-
ctx,
378-
`Running '${options.cmd}' with environment variable "${urlVar}" set...${
379-
options.dryRun ? " [dry run]" : ""
380-
}`,
381-
);
382-
if (!options.dryRun) {
383-
const env = { ...process.env };
384-
env[urlVar] = options.url;
385-
const result = spawnSync(options.cmd, {
386-
env,
387-
stdio: "inherit",
388-
shell: true,
389-
});
390-
if (result.status !== 0) {
391-
await ctx.crash({
392-
exitCode: 1,
393-
errorType: "invalid filesystem data",
394-
printedMessage: `'${options.cmd}' failed`,
395-
});
396-
}
397-
}
398-
logFinishedStep(
399-
ctx,
400-
`${options.dryRun ? "Would have run" : "Ran"} "${
401-
options.cmd
402-
}" with environment variable "${urlVar}" set`,
403-
);
291+
await deployToDeployment(ctx, { url, adminKey }, options);
404292
}
405293

406294
async function askToConfirmPush(

npm-packages/convex/src/cli/lib/command.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,27 @@ declare module "@commander-js/extra-typings" {
5151
liveComponentSources?: boolean;
5252
}
5353
>;
54+
55+
/**
56+
* Adds common options for deploy-related commands.
57+
*/
58+
addDeployOptions(): Command<
59+
Args,
60+
Opts & {
61+
verbose?: boolean;
62+
dryRun?: boolean;
63+
yes?: boolean;
64+
typecheck: "enable" | "try" | "disable";
65+
typecheckComponents: boolean;
66+
codegen: "enable" | "disable";
67+
cmd?: string;
68+
cmdUrlEnvVarName?: string;
69+
debugBundlePath?: string;
70+
debug?: boolean;
71+
writePushRequest?: string;
72+
liveComponentSources?: boolean;
73+
}
74+
>;
5475
}
5576
}
5677

@@ -188,3 +209,49 @@ export async function normalizeDevOptions(
188209
liveComponentSources: !!cmdOptions.liveComponentSources,
189210
};
190211
}
212+
213+
Command.prototype.addDeployOptions = function () {
214+
return this.option("-v, --verbose", "Show full listing of changes")
215+
.option(
216+
"--dry-run",
217+
"Print out the generated configuration without deploying to your Convex deployment",
218+
)
219+
.option("-y, --yes", "Skip confirmation prompt when running locally")
220+
.addOption(
221+
new Option(
222+
"--typecheck <mode>",
223+
`Whether to check TypeScript files with \`tsc --noEmit\` before deploying.`,
224+
)
225+
.choices(["enable", "try", "disable"] as const)
226+
.default("try" as const),
227+
)
228+
.option(
229+
"--typecheck-components",
230+
"Check TypeScript files within component implementations with `tsc --noEmit`.",
231+
false,
232+
)
233+
.addOption(
234+
new Option(
235+
"--codegen <mode>",
236+
"Whether to regenerate code in `convex/_generated/` before pushing.",
237+
)
238+
.choices(["enable", "disable"] as const)
239+
.default("enable" as const),
240+
)
241+
.addOption(
242+
new Option(
243+
"--cmd <command>",
244+
"Command to run as part of deploying your app (e.g. `vite build`). This command can depend on the environment variables specified in `--cmd-url-env-var-name` being set.",
245+
),
246+
)
247+
.addOption(
248+
new Option(
249+
"--cmd-url-env-var-name <name>",
250+
"Environment variable name to set Convex deployment URL (e.g. `VITE_CONVEX_URL`) when using `--cmd`",
251+
),
252+
)
253+
.addOption(new Option("--debug-bundle-path <path>").hideHelp())
254+
.addOption(new Option("--debug").hideHelp())
255+
.addOption(new Option("--write-push-request <writePushRequest>").hideHelp())
256+
.addOption(new Option("--live-component-sources").hideHelp());
257+
};

npm-packages/convex/src/cli/lib/deploy2.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import {
33
Context,
44
logError,
55
logFailure,
6+
logFinishedStep,
67
logVerbose,
8+
showSpinner,
79
} from "../../bundler/context.js";
10+
import { spawnSync } from "child_process";
811
import {
912
deploymentFetch,
1013
ErrorData,
@@ -29,6 +32,9 @@ import { finishPushDiff, FinishPushDiff } from "./deployApi/finishPush.js";
2932
import { Reporter, Span } from "./tracing.js";
3033
import { promisify } from "node:util";
3134
import zlib from "node:zlib";
35+
import { PushOptions } from "./push.js";
36+
import { runPush } from "./components.js";
37+
import { suggestedEnvVarName } from "./envvars.js";
3238

3339
const brotli = promisify(zlib.brotliCompress);
3440

@@ -299,3 +305,100 @@ export async function reportPushCompleted(
299305
);
300306
}
301307
}
308+
309+
export async function deployToDeployment(
310+
ctx: Context,
311+
credentials: {
312+
url: string;
313+
adminKey: string;
314+
},
315+
options: {
316+
verbose?: boolean | undefined;
317+
dryRun?: boolean | undefined;
318+
yes?: boolean | undefined;
319+
typecheck: "enable" | "try" | "disable";
320+
typecheckComponents: boolean;
321+
codegen: "enable" | "disable";
322+
cmd?: string | undefined;
323+
cmdUrlEnvVarName?: string | undefined;
324+
325+
debugBundlePath?: string | undefined;
326+
debug?: boolean | undefined;
327+
writePushRequest?: string | undefined;
328+
liveComponentSources?: boolean | undefined;
329+
partitionId?: string | undefined;
330+
},
331+
) {
332+
const { url, adminKey } = credentials;
333+
await runCommand(ctx, { ...options, url });
334+
335+
const pushOptions: PushOptions = {
336+
adminKey,
337+
verbose: !!options.verbose,
338+
dryRun: !!options.dryRun,
339+
typecheck: options.typecheck,
340+
typecheckComponents: options.typecheckComponents,
341+
debug: !!options.debug,
342+
debugBundlePath: options.debugBundlePath,
343+
codegen: options.codegen === "enable",
344+
url,
345+
writePushRequest: options.writePushRequest,
346+
liveComponentSources: !!options.liveComponentSources,
347+
};
348+
showSpinner(
349+
ctx,
350+
`Deploying to ${url}...${options.dryRun ? " [dry run]" : ""}`,
351+
);
352+
await runPush(ctx, pushOptions);
353+
logFinishedStep(
354+
ctx,
355+
`${
356+
options.dryRun ? "Would have deployed" : "Deployed"
357+
} Convex functions to ${url}`,
358+
);
359+
}
360+
361+
export async function runCommand(
362+
ctx: Context,
363+
options: {
364+
cmdUrlEnvVarName?: string | undefined;
365+
cmd?: string | undefined;
366+
dryRun?: boolean | undefined;
367+
url: string;
368+
},
369+
) {
370+
if (options.cmd === undefined) {
371+
return;
372+
}
373+
374+
const urlVar =
375+
options.cmdUrlEnvVarName ?? (await suggestedEnvVarName(ctx)).envVar;
376+
showSpinner(
377+
ctx,
378+
`Running '${options.cmd}' with environment variable "${urlVar}" set...${
379+
options.dryRun ? " [dry run]" : ""
380+
}`,
381+
);
382+
if (!options.dryRun) {
383+
const env = { ...process.env };
384+
env[urlVar] = options.url;
385+
const result = spawnSync(options.cmd, {
386+
env,
387+
stdio: "inherit",
388+
shell: true,
389+
});
390+
if (result.status !== 0) {
391+
await ctx.crash({
392+
exitCode: 1,
393+
errorType: "invalid filesystem data",
394+
printedMessage: `'${options.cmd}' failed`,
395+
});
396+
}
397+
}
398+
logFinishedStep(
399+
ctx,
400+
`${options.dryRun ? "Would have run" : "Ran"} "${
401+
options.cmd
402+
}" with environment variable "${urlVar}" set`,
403+
);
404+
}

0 commit comments

Comments
 (0)