convex
Version:
Client for the Convex Cloud
528 lines (505 loc) • 16 kB
text/typescript
import { Command, Option } from "@commander-js/extra-typings";
import { OneoffCtx } from "../../bundler/context.js";
import { LogMode } from "./logs.js";
import {
CONVEX_DEPLOYMENT_ENV_VAR_NAME,
CONVEX_SELF_HOSTED_ADMIN_KEY_VAR_NAME,
CONVEX_SELF_HOSTED_URL_VAR_NAME,
parseInteger,
parsePositiveInteger,
} from "./utils/utils.js";
declare module "@commander-js/extra-typings" {
interface Command<Args extends any[] = [], Opts extends OptionValues = {}> {
/**
* For a command that talks to the configured dev deployment by default,
* add flags for talking to prod, preview, or other deployment in the same
* project.
*
* These flags are added to the end of `command` (ordering matters for `--help`
* output). `action` should look like "Import data into" because it is prefixed
* onto help strings.
*
* The options can be passed to `deploymentSelectionFromOptions`.
*
* NOTE: This method only exists at runtime if this file is imported.
* To help avoid this bug, this method takes in an `ActionDescription` which
* can only be constructed via `actionDescription` from this file.
*/
addDeploymentSelectionOptions(action: ActionDescription): Command<
Args,
Opts & {
envFile?: string;
url?: string;
adminKey?: string;
prod?: boolean;
previewName?: string;
deploymentName?: string;
}
>;
/**
* Adds options for the `deploy` command.
*/
addDeployOptions(): Command<
Args,
Opts & {
verbose?: boolean;
dryRun?: boolean;
yes?: boolean;
typecheck: "enable" | "try" | "disable";
typecheckComponents: boolean;
codegen: "enable" | "disable";
cmd?: string;
cmdUrlEnvVarName?: string;
debugBundlePath?: string;
debug?: boolean;
writePushRequest?: string;
liveComponentSources?: boolean;
}
>;
/**
* Adds options for `self-host` subcommands.
*/
addSelfHostOptions(): Command<
Args,
Opts & {
url?: string;
adminKey?: string;
env?: string;
}
>;
/**
* Adds options and arguments for the `run` command.
*/
addRunOptions(): Command<
[...Args, string, string | undefined],
Opts & {
watch?: boolean;
push?: boolean;
identity?: string;
typecheck: "enable" | "try" | "disable";
typecheckComponents: boolean;
codegen: "enable" | "disable";
component?: string;
liveComponentSources?: boolean;
}
>;
/**
* Adds options for the `import` command.
*/
addImportOptions(): Command<
[...Args, string],
Opts & {
table?: string;
format?: "csv" | "jsonLines" | "jsonArray" | "zip";
replace?: boolean;
append?: boolean;
replaceAll?: boolean;
yes?: boolean;
component?: string;
}
>;
/**
* Adds options for the `export` command.
*/
addExportOptions(): Command<
Args,
Opts & {
path: string;
includeFileStorage?: boolean;
}
>;
/**
* Adds options for the `data` command.
*/
addDataOptions(): Command<
[...Args, string | undefined],
Opts & {
limit: number;
order: "asc" | "desc";
component?: string;
}
>;
/**
* Adds options for the `logs` command.
*/
addLogsOptions(): Command<
Args,
Opts & {
history: number;
success: boolean;
}
>;
/**
* Adds options for the `network-test` command.
*/
addNetworkTestOptions(): Command<
Args,
Opts & {
timeout?: string;
ipFamily?: string;
speedTest?: boolean;
}
>;
}
}
Command.prototype.addDeploymentSelectionOptions = function (
action: ActionDescription,
) {
return this.addOption(
new Option("--url <url>")
.conflicts(["--prod", "--preview-name", "--deployment-name"])
.hideHelp(),
)
.addOption(new Option("--admin-key <adminKey>").hideHelp())
.addOption(
new Option(
"--env-file <envFile>",
`Path to a custom file of environment variables, for choosing the \
deployment, e.g. ${CONVEX_DEPLOYMENT_ENV_VAR_NAME} or ${CONVEX_SELF_HOSTED_URL_VAR_NAME}. \
Same format as .env.local or .env files, and overrides them.`,
),
)
.addOption(
new Option(
"--prod",
action + " this project's production deployment.",
).conflicts(["--preview-name", "--deployment-name", "--url"]),
)
.addOption(
new Option(
"--preview-name <previewName>",
action + " the preview deployment with the given name.",
).conflicts(["--prod", "--deployment-name", "--url"]),
)
.addOption(
new Option(
"--deployment-name <deploymentName>",
action + " the specified deployment.",
).conflicts(["--prod", "--preview-name", "--url"]),
) as any;
};
declare const tag: unique symbol;
type ActionDescription = string & { readonly [tag]: "noop" };
export function actionDescription(action: string): ActionDescription {
return action as any;
}
export async function normalizeDevOptions(
ctx: OneoffCtx,
cmdOptions: {
verbose?: boolean;
typecheck: "enable" | "try" | "disable";
typecheckComponents?: boolean;
codegen: "enable" | "disable";
once?: boolean;
untilSuccess: boolean;
run?: string;
runSh?: string;
runComponent?: string;
tailLogs?: string | true;
traceEvents: boolean;
debugBundlePath?: string;
debugNodeApis?: boolean;
liveComponentSources?: boolean;
while?: string;
},
): Promise<{
verbose: boolean;
typecheck: "enable" | "try" | "disable";
typecheckComponents: boolean;
codegen: boolean;
once: boolean;
untilSuccess: boolean;
run?:
| { kind: "function"; name: string; component?: string }
| { kind: "shell"; command: string };
tailLogs: LogMode;
traceEvents: boolean;
debugBundlePath?: string;
debugNodeApis: boolean;
liveComponentSources: boolean;
}> {
if (cmdOptions.runComponent && !cmdOptions.run) {
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "Can't specify `--run-component` option without `--run`",
});
}
if (cmdOptions.debugBundlePath !== undefined && !cmdOptions.once) {
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "`--debug-bundle-path` can only be used with `--once`.",
});
}
if (cmdOptions.debugNodeApis && !cmdOptions.once) {
return await ctx.crash({
exitCode: 1,
errorType: "fatal",
printedMessage: "`--debug-node-apis` can only be used with `--once`.",
});
}
return {
verbose: !!cmdOptions.verbose,
typecheck: cmdOptions.typecheck,
typecheckComponents: !!cmdOptions.typecheckComponents,
codegen: cmdOptions.codegen === "enable",
once: !!cmdOptions.once,
untilSuccess: cmdOptions.untilSuccess,
run:
cmdOptions.run !== undefined
? {
kind: "function",
name: cmdOptions.run,
component: cmdOptions.runComponent,
}
: cmdOptions.runSh !== undefined
? {
kind: "shell",
command: cmdOptions.runSh,
}
: undefined,
tailLogs:
typeof cmdOptions.tailLogs === "string"
? (cmdOptions.tailLogs as LogMode)
: "pause-on-deploy",
traceEvents: cmdOptions.traceEvents,
debugBundlePath: cmdOptions.debugBundlePath,
debugNodeApis: !!cmdOptions.debugNodeApis,
liveComponentSources: !!cmdOptions.liveComponentSources,
};
}
Command.prototype.addDeployOptions = function () {
return this.option("-v, --verbose", "Show full listing of changes")
.option(
"--dry-run",
"Print out the generated configuration without deploying to your Convex deployment",
)
.option("-y, --yes", "Skip confirmation prompt when running locally")
.addOption(
new Option(
"--typecheck <mode>",
`Whether to check TypeScript files with \`tsc --noEmit\` before deploying.`,
)
.choices(["enable", "try", "disable"] as const)
.default("try" as const),
)
.option(
"--typecheck-components",
"Check TypeScript files within component implementations with `tsc --noEmit`.",
false,
)
.addOption(
new Option(
"--codegen <mode>",
"Whether to regenerate code in `convex/_generated/` before pushing.",
)
.choices(["enable", "disable"] as const)
.default("enable" as const),
)
.addOption(
new Option(
"--cmd <command>",
"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.",
),
)
.addOption(
new Option(
"--cmd-url-env-var-name <name>",
"Environment variable name to set Convex deployment URL (e.g. `VITE_CONVEX_URL`) when using `--cmd`",
),
)
.addOption(new Option("--debug-bundle-path <path>").hideHelp())
.addOption(new Option("--debug").hideHelp())
.addOption(new Option("--write-push-request <writePushRequest>").hideHelp())
.addOption(new Option("--live-component-sources").hideHelp());
};
Command.prototype.addSelfHostOptions = function () {
return this.option(
"--admin-key <adminKey>",
`An admin key for the deployment. Can alternatively be set as \`${CONVEX_SELF_HOSTED_ADMIN_KEY_VAR_NAME}\` environment variable.`,
)
.option(
"--url <url>",
`The url of the deployment. Can alternatively be set as \`${CONVEX_SELF_HOSTED_URL_VAR_NAME}\` environment variable.`,
)
.option(
"--env <env>",
`Path to a custom file of environment variables, containing \`${CONVEX_SELF_HOSTED_URL_VAR_NAME}\` and \`${CONVEX_SELF_HOSTED_ADMIN_KEY_VAR_NAME}\`.`,
);
};
Command.prototype.addRunOptions = function () {
return (
this.argument(
"functionName",
"identifier of the function to run, like `listMessages` or `dir/file:myFunction`",
)
.argument(
"[args]",
"JSON-formatted arguments object to pass to the function.",
)
.option(
"-w, --watch",
"Watch a query, printing its result if the underlying data changes. Given function must be a query.",
)
.option("--push", "Push code to deployment before running the function.")
.addOption(
new Option(
"--identity <identity>",
'JSON-formatted UserIdentity object, e.g. \'{ name: "John", address: "0x123" }\'',
),
)
// For backwards compatibility we still support --no-push which is a noop
.addOption(new Option("--no-push").hideHelp())
// Options for the deploy that --push does
.addOption(
new Option(
"--typecheck <mode>",
`Whether to check TypeScript files with \`tsc --noEmit\`.`,
)
.choices(["enable", "try", "disable"] as const)
.default("try" as const),
)
.option(
"--typecheck-components",
"Check TypeScript files within component implementations with `tsc --noEmit`.",
false,
)
.addOption(
new Option(
"--codegen <mode>",
"Regenerate code in `convex/_generated/`",
)
.choices(["enable", "disable"] as const)
.default("enable" as const),
)
.addOption(
new Option(
"--component <path>",
"Path to the component in the component tree defined in convex.config.ts. " +
"Components are a beta feature. This flag is unstable and may change in subsequent releases.",
),
)
.addOption(new Option("--live-component-sources").hideHelp())
);
};
Command.prototype.addImportOptions = function () {
return this.argument("<path>", "Path to the input file")
.addOption(
new Option(
"--table <table>",
"Destination table name. Required if format is csv, jsonLines, or jsonArray. Not supported if format is zip.",
),
)
.addOption(
new Option(
"--replace",
"Replace all existing data in any of the imported tables",
)
.conflicts("--append")
.conflicts("--replace-all"),
)
.addOption(
new Option("--append", "Append imported data to any existing tables")
.conflicts("--replace-all")
.conflicts("--replace"),
)
.addOption(
new Option(
"--replace-all",
"Replace all existing data in the deployment with the imported tables,\n" +
" deleting tables that don't appear in the import file or the schema,\n" +
" and clearing tables that appear in the schema but not in the import file",
)
.conflicts("--append")
.conflicts("--replace"),
)
.option(
"-y, --yes",
"Skip confirmation prompt when import leads to deleting existing documents",
)
.addOption(
new Option(
"--format <format>",
"Input file format. This flag is only required if the filename is missing an extension.\n" +
"- CSV files must have a header, and each row's entries are interpreted either as a (floating point) number or a string.\n" +
"- JSON files must be an array of JSON objects.\n" +
"- JSONLines files must have a JSON object per line.\n" +
"- ZIP files must have one directory per table, containing <table>/documents.jsonl. Snapshot exports from the Convex dashboard have this format.",
).choices(["csv", "jsonLines", "jsonArray", "zip"]),
)
.addOption(
new Option(
"--component <path>",
"Path to the component in the component tree defined in convex.config.ts",
),
);
};
Command.prototype.addExportOptions = function () {
return this.requiredOption(
"--path <zipFilePath>",
"Exports data into a ZIP file at this path, which may be a directory or unoccupied .zip path",
).addOption(
new Option(
"--include-file-storage",
"Includes stored files (https://dashboard.convex.dev/deployment/files) in a _storage folder within the ZIP file",
),
);
};
Command.prototype.addDataOptions = function () {
return this.addOption(
new Option(
"--limit <n>",
"List only the `n` the most recently created documents.",
)
.default(100)
.argParser(parsePositiveInteger),
)
.addOption(
new Option(
"--order <choice>",
"Order the documents by their `_creationTime`.",
)
.choices(["asc", "desc"])
.default("desc"),
)
.addOption(
new Option(
"--component <path>",
"Path to the component in the component tree defined in convex.config.ts.\n" +
" By default, inspects data in the root component",
).hideHelp(),
)
.argument("[table]", "If specified, list documents in this table.");
};
Command.prototype.addLogsOptions = function () {
return this.option(
"--history [n]",
"Show `n` most recent logs. Defaults to showing all available logs.",
parseInteger,
).option(
"--success",
"Print a log line for every successful function execution",
false,
);
};
Command.prototype.addNetworkTestOptions = function () {
return this.addOption(
new Option(
"--timeout <timeout>",
"Timeout in seconds for the network test (default: 30).",
),
)
.addOption(
new Option(
"--ip-family <ipFamily>",
"IP family to use (ipv4, ipv6, or auto)",
),
)
.addOption(
new Option(
"--speed-test",
"Perform a large echo test to measure network speed.",
),
);
};