UNPKG

convex

Version:

Client for the Convex Cloud

404 lines (403 loc) 15.6 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var dev_exports = {}; __export(dev_exports, { dev: () => dev, nextBackoff: () => nextBackoff, watchAndPush: () => watchAndPush }); module.exports = __toCommonJS(dev_exports); var import_chalk = __toESM(require("chalk"), 1); var import_extra_typings = require("@commander-js/extra-typings"); var import_path = __toESM(require("path"), 1); var import_perf_hooks = require("perf_hooks"); var import_context = require("../bundler/context.js"); var import_configure = require("./configure.js"); var import_login = require("./lib/login.js"); var import_utils = require("./lib/utils/utils.js"); var import_watch = require("./lib/watch.js"); var import_logs = require("./lib/logs.js"); var import_run = require("./lib/run.js"); var import_usage = require("./lib/usage.js"); var import_components = require("./lib/components.js"); const dev = new import_extra_typings.Command("dev").summary("Develop against a dev deployment, watching for changes").description( "Develop against a dev deployment, watching for changes\n\n 1. Configures a new or existing project (if needed)\n 2. Updates generated types and pushes code to the configured dev deployment\n 3. Runs the provided function (if `--run` is used)\n 4. Watches for file changes, and repeats step 2\n" ).option("-v, --verbose", "Show full listing of changes").addOption( new import_extra_typings.Option( "--typecheck <mode>", `Check TypeScript files with \`tsc --noEmit\`.` ).choices(["enable", "try", "disable"]).default("try") ).addOption( new import_extra_typings.Option("--codegen <mode>", "Regenerate code in `convex/_generated/`").choices(["enable", "disable"]).default("enable") ).addOption( new import_extra_typings.Option( "--configure [choice]", "Ignore existing configuration and configure new or existing project" ).choices(["new", "existing"]) ).option("--team <team_slug>", "The team you'd like to use for this project").option( "--project <project_slug>", "The name of the project you'd like to configure" ).option( "--once", "Execute only the first 3 steps, stop on any failure", false ).option( "--until-success", "Execute only the first 3 steps, on failure watch for local and remote changes and retry steps 2 and 3", false ).option( "--run <functionName>", "The identifier of the function to run in step 3, like `init` or `dir/file:myFunction`" ).addOption( new import_extra_typings.Option( "--prod", "Develop live against this project's production deployment." ).default(false).hideHelp() ).addOption( new import_extra_typings.Option( "--tail-logs", "Tail this project's Convex logs in this terminal." ) ).addOption(new import_extra_typings.Option("--trace-events").default(false).hideHelp()).addOption(new import_extra_typings.Option("--verbose").default(false).hideHelp()).addOption(new import_extra_typings.Option("--admin-key <adminKey>").hideHelp()).addOption(new import_extra_typings.Option("--url <url>").hideHelp()).addOption(new import_extra_typings.Option("--debug-bundle-path <path>").hideHelp()).addOption(new import_extra_typings.Option("--override-auth-url <url>").hideHelp()).addOption(new import_extra_typings.Option("--override-auth-client <id>").hideHelp()).addOption(new import_extra_typings.Option("--override-auth-username <username>").hideHelp()).addOption(new import_extra_typings.Option("--override-auth-password <password>").hideHelp()).addOption( new import_extra_typings.Option("--local", "Develop live against a locally running backend.").default(false).conflicts(["--prod", "--url", "--admin-key"]).hideHelp() ).addOption(new import_extra_typings.Option("--local-cloud-port <port>").hideHelp()).addOption(new import_extra_typings.Option("--local-site-port <port>").hideHelp()).addOption(new import_extra_typings.Option("--local-backend-version <version>").hideHelp()).addOption(new import_extra_typings.Option("--local-force-upgrade").default(false).hideHelp()).showHelpAfterError().action(async (cmdOptions) => { const ctx = import_context.oneoffContext; if (cmdOptions.debugBundlePath !== void 0 && !cmdOptions.once) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: "`--debug-bundle-path` can only be used with `--once`." }); } const localOptions = { forceUpgrade: false }; if (!cmdOptions.local) { if (cmdOptions.localCloudPort !== void 0 || cmdOptions.localSitePort !== void 0 || cmdOptions.localBackendVersion !== void 0 || cmdOptions.localForceUpgrade === true) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: "`--local-*` options can only be used with `--local`." }); } } else { if (cmdOptions.localCloudPort !== void 0) { if (cmdOptions.localSitePort === void 0) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: "`--local-cloud-port` requires `--local-site-port` to be set." }); } localOptions["ports"] = { cloud: parseInt(cmdOptions.localCloudPort), site: parseInt(cmdOptions.localSitePort) }; } localOptions["backendVersion"] = cmdOptions.localBackendVersion; localOptions["forceUpgrade"] = cmdOptions.localForceUpgrade; } if (!cmdOptions.url || !cmdOptions.adminKey) { if (!await (0, import_login.checkAuthorization)(ctx, false)) { await (0, import_login.performLogin)(ctx, cmdOptions); } } const configure = cmdOptions.configure === true ? "ask" : cmdOptions.configure ?? null; const credentials = await (0, import_configure.deploymentCredentialsOrConfigure)(ctx, configure, { ...cmdOptions, localOptions }); let cleanupHandle = credentials.cleanupHandle; process.on("SIGINT", async () => { (0, import_context.logVerbose)(ctx, "Received SIGINT, cleaning up..."); if (cleanupHandle !== null) { (0, import_context.logVerbose)(ctx, "About to run cleanup handle."); const f = cleanupHandle; cleanupHandle = null; await f(); } (0, import_context.logVerbose)(ctx, "Cleaned up. Exiting."); process.exit(-2); }); await (0, import_usage.usageStateWarning)(ctx); const promises = []; if (cmdOptions.tailLogs) { promises.push( (0, import_logs.watchLogs)(ctx, credentials.url, credentials.adminKey, "stderr") ); } promises.push( watchAndPush( ctx, { ...credentials, verbose: !!cmdOptions.verbose, dryRun: false, typecheck: cmdOptions.typecheck, debug: false, debugBundlePath: cmdOptions.debugBundlePath, codegen: cmdOptions.codegen === "enable" }, cmdOptions ) ); await Promise.race(promises); }); async function watchAndPush(outerCtx, options, cmdOptions) { const watch = { watcher: void 0 }; let numFailures = 0; let pushed = false; let tableNameTriggeringRetry; let shouldRetryOnDeploymentEnvVarChange; while (true) { const start = import_perf_hooks.performance.now(); tableNameTriggeringRetry = null; shouldRetryOnDeploymentEnvVarChange = false; const ctx = new import_watch.WatchContext(cmdOptions.traceEvents); (0, import_context.showSpinner)(ctx, "Preparing Convex functions..."); try { await (0, import_components.runPush)(ctx, options); const end = import_perf_hooks.performance.now(); numFailures = 0; (0, import_context.logFinishedStep)( ctx, `${(0, import_utils.getCurrentTimeString)()} Convex functions ready! (${(0, import_utils.formatDuration)( end - start )})` ); pushed = true; } catch (e) { if (!(e instanceof import_watch.Crash) || !e.errorType) { throw e; } if (e.errorType === "fatal") { break; } if (e.errorType === "transient") { const delay = nextBackoff(numFailures); numFailures += 1; (0, import_context.logWarning)( ctx, import_chalk.default.yellow( `Failed due to network error, retrying in ${(0, import_utils.formatDuration)( delay )}...` ) ); await new Promise((resolve) => setTimeout(resolve, delay)); continue; } console.assert( e.errorType === "invalid filesystem data" || e.errorType === "invalid filesystem or env vars" || e.errorType["invalid filesystem or db data"] !== void 0 ); if (e.errorType === "invalid filesystem or env vars") { shouldRetryOnDeploymentEnvVarChange = true; } else if (e.errorType !== "invalid filesystem data" && e.errorType["invalid filesystem or db data"] !== void 0) { tableNameTriggeringRetry = e.errorType["invalid filesystem or db data"]; } if (cmdOptions.once) { await outerCtx.flushAndExit(1, e.errorType); } (0, import_context.stopSpinner)(ctx); } if (cmdOptions.once) { if (options.cleanupHandle !== null) { await options.cleanupHandle(); } return; } if (pushed && cmdOptions.untilSuccess) { if (options.cleanupHandle !== null) { await options.cleanupHandle(); } return; } const fileSystemWatch = getFileSystemWatch(ctx, watch, cmdOptions); const tableWatch = getTableWatch(ctx, options, tableNameTriggeringRetry); const envVarWatch = getDeplymentEnvVarWatch( ctx, options, shouldRetryOnDeploymentEnvVarChange ); await Promise.race([ fileSystemWatch.watch(), tableWatch.watch(), envVarWatch.watch() ]); fileSystemWatch.stop(); void tableWatch.stop(); void envVarWatch.stop(); } } function getTableWatch(ctx, credentials, tableName) { return getFunctionWatch( ctx, credentials, "_system/cli/queryTable", () => tableName !== null ? { tableName } : null ); } function getDeplymentEnvVarWatch(ctx, credentials, shouldRetryOnDeploymentEnvVarChange) { return getFunctionWatch( ctx, credentials, "_system/cli/queryEnvironmentVariables", () => shouldRetryOnDeploymentEnvVarChange ? {} : null ); } function getFunctionWatch(ctx, credentials, functionName, getArgs) { const [stopPromise, stop] = (0, import_utils.waitUntilCalled)(); return { watch: async () => { const args = getArgs(); if (args === null) { return (0, import_utils.waitForever)(); } let changes = 0; return (0, import_run.subscribe)( ctx, credentials.url, credentials.adminKey, functionName, args, stopPromise, { onChange: () => { changes++; if (changes > 1) { stop(); } } } ); }, stop: () => { stop(); } }; } function getFileSystemWatch(ctx, watch, cmdOptions) { let hasStopped = false; return { watch: async () => { const observations = ctx.fs.finalize(); if (observations === "invalidated") { (0, import_context.logMessage)(ctx, "Filesystem changed during push, retrying..."); return; } if (!watch.watcher) { watch.watcher = new import_watch.Watcher(observations); await (0, import_context.showSpinnerIfSlow)( ctx, "Preparing to watch files...", 500, async () => { await watch.watcher.ready(); } ); (0, import_context.stopSpinner)(ctx); } watch.watcher.update(observations); let anyChanges = false; do { await watch.watcher.waitForEvent(); if (hasStopped) { return; } for (const event of watch.watcher.drainEvents()) { if (cmdOptions.traceEvents) { (0, import_context.logMessage)( ctx, "Processing", event.name, import_path.default.relative("", event.absPath) ); } const result = observations.overlaps(event); if (result.overlaps) { const relPath = import_path.default.relative("", event.absPath); if (cmdOptions.traceEvents) { (0, import_context.logMessage)(ctx, `${relPath} ${result.reason}, rebuilding...`); } anyChanges = true; break; } } } while (!anyChanges); let deadline = import_perf_hooks.performance.now() + quiescenceDelay; while (true) { const now = import_perf_hooks.performance.now(); if (now >= deadline) { break; } const remaining = deadline - now; if (cmdOptions.traceEvents) { (0, import_context.logMessage)( ctx, `Waiting for ${(0, import_utils.formatDuration)(remaining)} to quiesce...` ); } const remainingWait = new Promise( (resolve) => setTimeout(() => resolve("timeout"), deadline - now) ); const result = await Promise.race([ remainingWait, watch.watcher.waitForEvent().then(() => "newEvents") ]); if (result === "newEvents") { for (const event of watch.watcher.drainEvents()) { const result2 = observations.overlaps(event); if (result2.overlaps) { if (cmdOptions.traceEvents) { (0, import_context.logMessage)( ctx, `Received an overlapping event at ${event.absPath}, delaying push.` ); } deadline = import_perf_hooks.performance.now() + quiescenceDelay; } } } else { console.assert(result === "timeout"); } } }, stop: () => { hasStopped = true; } }; } const initialBackoff = 500; const maxBackoff = 16e3; const quiescenceDelay = 500; function nextBackoff(prevFailures) { const baseBackoff = initialBackoff * Math.pow(2, prevFailures); const actualBackoff = Math.min(baseBackoff, maxBackoff); const jitter = actualBackoff * (Math.random() - 0.5); return actualBackoff + jitter; } //# sourceMappingURL=dev.js.map