convex
Version:
Client for the Convex Cloud
404 lines (403 loc) • 15.6 kB
JavaScript
;
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