UNPKG

convex

Version:

Client for the Convex Cloud

641 lines (638 loc) 24.7 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 config_exports = {}; __export(config_exports, { configFilepath: () => configFilepath, configFromProjectConfig: () => configFromProjectConfig, configName: () => configName, debugIsolateEndpointBundles: () => debugIsolateEndpointBundles, diffConfig: () => diffConfig, getAuthKitConfig: () => getAuthKitConfig, getAuthKitEnvironmentConfig: () => getAuthKitEnvironmentConfig, getFunctionsDirectoryPath: () => getFunctionsDirectoryPath, handlePushConfigError: () => handlePushConfigError, parseProjectConfig: () => parseProjectConfig, productionProvisionHost: () => import_utils2.productionProvisionHost, provisionHost: () => import_utils2.provisionHost, pullConfig: () => pullConfig, readConfig: () => readConfig, readProjectConfig: () => readProjectConfig, removedExistingConfig: () => removedExistingConfig, resetUnknownKeyWarnings: () => resetUnknownKeyWarnings, usesComponentApiImports: () => usesComponentApiImports, usesTypeScriptCodegen: () => usesTypeScriptCodegen, writeProjectConfig: () => writeProjectConfig }); module.exports = __toCommonJS(config_exports); var import_chalk = require("chalk"); var import_path = __toESM(require("path"), 1); var import_zod = require("zod"); var import_log = require("../../bundler/log.js"); var import_bundler = require("../../bundler/index.js"); var import_version = require("../version.js"); var import_dashboard = require("./dashboard.js"); var import_utils = require("./utils/utils.js"); var import_fsUtils = require("./fsUtils.js"); var import_errors = require("./localDeployment/errors.js"); var import_debugBundle = require("../../bundler/debugBundle.js"); var import_utils2 = require("./utils/utils.js"); const DEFAULT_FUNCTIONS_PATH = "convex/"; function usesTypeScriptCodegen(projectConfig) { return projectConfig.codegen.fileType === "ts"; } function usesComponentApiImports(projectConfig) { return projectConfig.codegen.legacyComponentApi === false; } async function getAuthKitConfig(ctx, projectConfig) { if ("authKit" in projectConfig) { return projectConfig.authKit; } const homepage = await (0, import_utils.currentPackageHomepage)(ctx); const isOldWorkOSTemplate = !!(homepage && [ "https://github.com/workos/template-convex-nextjs-authkit/#readme", "https://github.com/workos/template-convex-react-vite-authkit/#readme", "https://github.com:workos/template-convex-react-vite-authkit/#readme", "https://github.com/workos/template-convex-tanstack-start-authkit/#readme" ].includes(homepage)); if (isOldWorkOSTemplate) { (0, import_log.logWarning)( "The template this project is based on has been updated to work with this version of Convex." ); (0, import_log.logWarning)( "Please copy the convex.json from the latest template version or add an 'authKit' section." ); (0, import_log.logMessage)("Learn more at https://docs.convex.dev/auth/authkit"); } } async function getAuthKitEnvironmentConfig(ctx, projectConfig, deploymentType) { const authKitConfig = await getAuthKitConfig(ctx, projectConfig); return authKitConfig?.[deploymentType]; } class ParseError extends Error { } const AuthKitConfigureSchema = import_zod.z.union([ import_zod.z.literal(false), import_zod.z.object({ redirectUris: import_zod.z.array(import_zod.z.string()).optional(), appHomepageUrl: import_zod.z.string().optional(), corsOrigins: import_zod.z.array(import_zod.z.string()).optional() }) ]); const AuthKitLocalEnvVarsSchema = import_zod.z.union([ import_zod.z.literal(false), import_zod.z.record(import_zod.z.string()) ]); const AuthKitEnvironmentConfigSchema = import_zod.z.object({ environmentType: import_zod.z.enum(["development", "staging", "production"]).optional(), configure: AuthKitConfigureSchema.optional(), localEnvVars: AuthKitLocalEnvVarsSchema.optional() }); const AuthKitConfigSchema = import_zod.z.object({ dev: AuthKitEnvironmentConfigSchema.optional(), preview: AuthKitEnvironmentConfigSchema.optional(), prod: AuthKitEnvironmentConfigSchema.optional() }).refine( (data) => { const devEnvType = data.dev?.environmentType; const previewEnvType = data.preview?.environmentType; if (devEnvType || previewEnvType) { return false; } return true; }, { message: "authKit.environmentType is only allowed in the prod section", path: ["environmentType"] } ).refine( (data) => { if (data.preview?.localEnvVars !== void 0 && data.preview?.localEnvVars !== false) { return false; } if (data.prod?.localEnvVars !== void 0 && data.prod?.localEnvVars !== false) { return false; } return true; }, { message: "authKit.localEnvVars is only supported for dev deployments. Preview and prod deployments must configure environment variables directly in the deployment platform.", path: ["localEnvVars"] } ); const NodeSchema = import_zod.z.object({ externalPackages: import_zod.z.array(import_zod.z.string()).default([]).describe( "list of npm packages to install at deploy time instead of bundling. Packages with binaries should be added here." ), nodeVersion: import_zod.z.string().optional().describe("The Node.js version to use for Node.js functions") }); const CodegenSchema = import_zod.z.object({ staticApi: import_zod.z.boolean().default(false).describe( "Use Convex function argument validators and return value validators to generate a typed API object" ), staticDataModel: import_zod.z.boolean().default(false), // These optional fields have no defaults - their presence/absence is meaningful legacyComponentApi: import_zod.z.boolean().optional(), fileType: import_zod.z.enum(["ts", "js/dts"]).optional() }); const BundlerSchema = import_zod.z.object({ includeSourcesContent: import_zod.z.boolean().default(false).describe( "Whether to include original source code in source maps. Set to false to reduce bundle size." ) }); const refineToObject = (schema) => schema.refine((val) => val !== null && !Array.isArray(val), { message: "Expected `convex.json` to contain an object" }); const createProjectConfigSchema = (strict) => { const nodeSchema = strict ? NodeSchema.strict() : NodeSchema.passthrough(); const codegenSchema = strict ? CodegenSchema.strict() : CodegenSchema.passthrough(); const bundlerSchema = strict ? BundlerSchema.strict() : BundlerSchema.passthrough(); const baseObject = import_zod.z.object({ functions: import_zod.z.string().default(DEFAULT_FUNCTIONS_PATH).describe("Relative file path to the convex directory"), node: nodeSchema.default({ externalPackages: [] }), codegen: codegenSchema.default({ staticApi: false, staticDataModel: false }), bundler: bundlerSchema.default({ includeSourcesContent: false }).optional(), generateCommonJSApi: import_zod.z.boolean().default(false), typescriptCompiler: import_zod.z.enum(["tsc", "tsgo"]).optional().describe( "TypeScript compiler to use for typechecking (`@typescript/native-preview` must be installed to use `tsgo`)" ), // Optional $schema field for JSON schema validation in editors $schema: import_zod.z.string().optional(), // WorkOS AuthKit integration configuration authKit: AuthKitConfigSchema.optional(), // Deprecated fields that have been deprecated for years, only here so we // know it's safe to delete them. project: import_zod.z.string().optional(), team: import_zod.z.string().optional(), prodUrl: import_zod.z.string().optional() }); const withStrictness = strict ? baseObject.strict() : baseObject.passthrough(); return withStrictness.refine( (data) => { if (data.generateCommonJSApi && data.codegen.fileType === "ts") { return false; } return true; }, { message: 'Cannot use `generateCommonJSApi: true` with `codegen.fileType: "ts"`. CommonJS modules require JavaScript generation. Either set `codegen.fileType: "js/dts"` or remove `generateCommonJSApi`.', path: ["generateCommonJSApi"] } ); }; const ProjectConfigSchema = refineToObject(createProjectConfigSchema(false)); const ProjectConfigSchemaStrict = refineToObject( createProjectConfigSchema(true) ); const warnedUnknownKeys = /* @__PURE__ */ new Set(); function resetUnknownKeyWarnings() { warnedUnknownKeys.clear(); } async function parseProjectConfig(ctx, obj) { if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: "Expected `convex.json` to contain an object" }); } try { return ProjectConfigSchemaStrict.parse(obj); } catch (error) { if (error instanceof import_zod.z.ZodError) { const unknownKeyIssues = error.issues.filter( (issue) => issue.code === "unrecognized_keys" ); if (unknownKeyIssues.length > 0 && unknownKeyIssues.length === error.issues.length) { for (const issue of unknownKeyIssues) { if (issue.code === "unrecognized_keys") { const pathPrefix = issue.path.length > 0 ? issue.path.join(".") + "." : ""; const unknownKeys = issue.keys; const newUnknownKeys = unknownKeys.filter( (key) => !warnedUnknownKeys.has(pathPrefix + key) ); if (newUnknownKeys.length > 0) { const fullPath = issue.path.length > 0 ? `\`${issue.path.join(".")}\`` : "`convex.json`"; (0, import_log.logMessage)( import_chalk.chalkStderr.yellow( `Warning: Unknown ${newUnknownKeys.length === 1 ? "property" : "properties"} in ${fullPath}: ${newUnknownKeys.map((k) => `\`${k}\``).join(", ")}` ) ); (0, import_log.logMessage)( import_chalk.chalkStderr.gray( " These properties will be preserved but are not recognized by this version of Convex." ) ); newUnknownKeys.forEach( (key) => warnedUnknownKeys.add(pathPrefix + key) ); } } } return ProjectConfigSchema.parse(obj); } if (error instanceof import_zod.z.ZodError) { const issue = error.issues[0]; const pathStr = issue.path.join("."); const message = pathStr ? `\`${pathStr}\` in \`convex.json\`: ${issue.message}` : `\`convex.json\`: ${issue.message}`; return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: message }); } } return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: error.toString() }); } } function parseBackendConfig(obj) { function throwParseError(message) { throw new ParseError(message); } if (typeof obj !== "object") { throwParseError("Expected an object"); } const { functions, nodeVersion } = obj; if (typeof functions !== "string") { throwParseError("Expected functions to be a string"); } if (typeof nodeVersion !== "undefined" && typeof nodeVersion !== "string") { throwParseError("Expected nodeVersion to be a string"); } return { functions, ...(nodeVersion ?? null) !== null ? { nodeVersion } : {} }; } function configName() { return "convex.json"; } async function configFilepath(ctx) { const configFn = configName(); const preferredLocation = configFn; const wrongLocation = import_path.default.join("src", configFn); const preferredLocationExists = ctx.fs.exists(preferredLocation); const wrongLocationExists = ctx.fs.exists(wrongLocation); if (preferredLocationExists && wrongLocationExists) { const message = `${import_chalk.chalkStderr.red(`Error: both ${preferredLocation} and ${wrongLocation} files exist!`)} Consolidate these and remove ${wrongLocation}.`; return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: message }); } if (!preferredLocationExists && wrongLocationExists) { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `Error: Please move ${wrongLocation} to the root of your project` }); } return preferredLocation; } async function getFunctionsDirectoryPath(ctx) { const { projectConfig, configPath } = await readProjectConfig(ctx); return (0, import_utils.functionsDir)(configPath, projectConfig); } async function readProjectConfig(ctx) { if (!ctx.fs.exists("convex.json")) { const packages = await (0, import_utils.loadPackageJson)(ctx); const isCreateReactApp = "react-scripts" in packages; return { projectConfig: { functions: isCreateReactApp ? `src/${DEFAULT_FUNCTIONS_PATH}` : DEFAULT_FUNCTIONS_PATH, node: { externalPackages: [] }, generateCommonJSApi: false, codegen: { staticApi: false, staticDataModel: false } }, configPath: configName() }; } let projectConfig; const configPath = await configFilepath(ctx); try { projectConfig = await parseProjectConfig( ctx, JSON.parse(ctx.fs.readUtf8File(configPath)) ); } catch (err) { if (err instanceof ParseError || err instanceof SyntaxError) { (0, import_log.logError)(import_chalk.chalkStderr.red(`Error: Parsing "${configPath}" failed`)); (0, import_log.logMessage)(import_chalk.chalkStderr.gray(err.toString())); } else { (0, import_log.logFailure)( `Error: Unable to read project config file "${configPath}" Are you running this command from the root directory of a Convex project? If so, run \`npx convex dev\` first.` ); if (err instanceof Error) { (0, import_log.logError)(import_chalk.chalkStderr.red(err.message)); } } return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", errForSentry: err, // TODO -- move the logging above in here printedMessage: null }); } return { projectConfig, configPath }; } async function configFromProjectConfig(ctx, projectConfig, configPath, verbose) { const baseDir = (0, import_utils.functionsDir)(configPath, projectConfig); const entryPoints = await (0, import_bundler.entryPointsByEnvironment)(ctx, baseDir); if (verbose) { (0, import_log.showSpinner)("Bundling modules for Convex's runtime..."); } const convexResult = await (0, import_bundler.bundle)({ ctx, dir: baseDir, entryPoints: entryPoints.isolate, generateSourceMaps: true, platform: "browser" }); if (verbose) { (0, import_log.logMessage)( "Convex's runtime modules: ", convexResult.modules.map((m) => m.path) ); } if (verbose && entryPoints.node.length !== 0) { (0, import_log.showSpinner)("Bundling modules for Node.js runtime..."); } const nodeResult = await (0, import_bundler.bundle)({ ctx, dir: baseDir, entryPoints: entryPoints.node, generateSourceMaps: true, platform: "node", chunksFolder: import_path.default.join("_deps", "node"), externalPackagesAllowList: projectConfig.node.externalPackages }); if (verbose && entryPoints.node.length !== 0) { (0, import_log.logMessage)( "Node.js runtime modules: ", nodeResult.modules.map((m) => m.path) ); if (projectConfig.node.externalPackages.length > 0) { (0, import_log.logMessage)( "Node.js runtime external dependencies (to be installed on the server): ", [...nodeResult.externalDependencies.entries()].map( (a) => `${a[0]}: ${a[1]}` ) ); } } const modules = convexResult.modules; modules.push(...nodeResult.modules); modules.push(...await (0, import_bundler.bundleAuthConfig)(ctx, baseDir)); const nodeDependencies = []; for (const [moduleName, moduleVersion] of nodeResult.externalDependencies) { nodeDependencies.push({ name: moduleName, version: moduleVersion }); } const bundledModuleInfos = Array.from( convexResult.bundledModuleNames.keys() ).map((moduleName) => { return { name: moduleName, platform: "convex" }; }); bundledModuleInfos.push( ...Array.from(nodeResult.bundledModuleNames.keys()).map( (moduleName) => { return { name: moduleName, platform: "node" }; } ) ); return { config: { projectConfig, modules, nodeDependencies, // We're just using the version this CLI is running with for now. // This could be different than the version of `convex` the app runs with // if the CLI is installed globally. udfServerVersion: import_version.version, nodeVersion: projectConfig.node.nodeVersion }, bundledModuleInfos }; } async function debugIsolateEndpointBundles(ctx, projectConfig, configPath) { const baseDir = (0, import_utils.functionsDir)(configPath, projectConfig); const entryPoints = await (0, import_bundler.entryPointsByEnvironment)(ctx, baseDir); if (entryPoints.isolate.length === 0) { (0, import_log.logFinishedStep)("No non-'use node' modules found."); } await (0, import_debugBundle.debugIsolateBundlesSerially)(ctx, { entryPoints: entryPoints.isolate, extraConditions: [], dir: baseDir }); } async function readConfig(ctx, verbose) { const { projectConfig, configPath } = await readProjectConfig(ctx); const { config, bundledModuleInfos } = await configFromProjectConfig( ctx, projectConfig, configPath, verbose ); return { config, configPath, bundledModuleInfos }; } async function writeProjectConfig(ctx, projectConfig) { const configPath = await configFilepath(ctx); ctx.fs.mkdir((0, import_utils.functionsDir)(configPath, projectConfig), { allowExisting: true }); } function removedExistingConfig(ctx, configPath, options) { if (!options.allowExistingConfig) { return false; } (0, import_fsUtils.recursivelyDelete)(ctx, configPath); (0, import_log.logFinishedStep)(`Removed existing ${configPath}`); return true; } async function pullConfig(ctx, project, team, origin, adminKey) { const fetch = (0, import_utils.deploymentFetch)(ctx, { deploymentUrl: origin, adminKey }); (0, import_log.changeSpinner)("Downloading current deployment state..."); try { const res = await fetch("/api/get_config_hashes", { method: "POST", body: JSON.stringify({ version: import_version.version, adminKey }) }); (0, import_utils.deprecationCheckWarning)(ctx, res); const data = await res.json(); const backendConfig = parseBackendConfig(data.config); const projectConfig = { ...backendConfig, node: { // This field is not stored in the backend, which is ok since it is also // not used to diff configs. externalPackages: [], nodeVersion: data.nodeVersion }, // This field is not stored in the backend, it only affects the client. generateCommonJSApi: false, // This field is also not stored in the backend, it only affects the client. codegen: { staticApi: false, staticDataModel: false }, project, team, prodUrl: origin }; return { projectConfig, moduleHashes: data.moduleHashes, // TODO(presley): Add this to diffConfig(). nodeDependencies: data.nodeDependencies, udfServerVersion: data.udfServerVersion }; } catch (err) { (0, import_log.logFailure)(`Error: Unable to pull deployment config from ${origin}`); return await (0, import_utils.logAndHandleFetchError)(ctx, err); } } function diffConfig(oldConfig, newConfig) { let diff = ""; let versionMessage = ""; const matches = oldConfig.udfServerVersion === newConfig.udfServerVersion; if (oldConfig.udfServerVersion && (!newConfig.udfServerVersion || !matches)) { versionMessage += `[-] ${oldConfig.udfServerVersion} `; } if (newConfig.udfServerVersion && (!oldConfig.udfServerVersion || !matches)) { versionMessage += `[+] ${newConfig.udfServerVersion} `; } if (versionMessage) { diff += "Change the server's function version:\n"; diff += versionMessage; } if (oldConfig.projectConfig.node.nodeVersion !== newConfig.nodeVersion) { diff += "Change the server's version for Node.js actions:\n"; if (oldConfig.projectConfig.node.nodeVersion) { diff += `[-] ${oldConfig.projectConfig.node.nodeVersion} `; } if (newConfig.nodeVersion) { diff += `[+] ${newConfig.nodeVersion} `; } } return { diffString: diff }; } async function handlePushConfigError(ctx, error, defaultMessage, deploymentName, deployment, _deploymentType) { const data = error instanceof import_utils.ThrowingFetchError ? error.serverErrorData : void 0; if (data?.code === "AuthConfigMissingEnvironmentVariable") { const errorMessage = data.message || "(no error message given)"; const [, variableName] = errorMessage.match(/Environment variable (\S+)/i) ?? []; if (variableName === "WORKOS_CLIENT_ID" && deploymentName && deployment) { (0, import_log.logWarning)( "WORKOS_CLIENT_ID is not set; you can set it manually on the deployment or for hosted Convex deployments, use auto-provisioning." ); (0, import_log.logMessage)( "Learn more at https://docs.convex.dev/auth/authkit/auto-provision" ); (0, import_log.logMessage)(""); } const envVarMessage = `Environment variable ${import_chalk.chalkStderr.bold( variableName )} is used in auth config file but its value was not set.`; let setEnvVarInstructions = "Go set it in the dashboard or using `npx convex env set`"; if (deploymentName !== null) { const variableQuery = variableName !== void 0 ? `?var=${variableName}` : ""; const dashboardUrl = (0, import_dashboard.deploymentDashboardUrlPage)( deploymentName, `/settings/environment-variables${variableQuery}` ); setEnvVarInstructions = `Go to: ${import_chalk.chalkStderr.bold( dashboardUrl )} to set it up. `; } await ctx.crash({ exitCode: 1, errorType: "invalid filesystem or env vars", errForSentry: error, printedMessage: envVarMessage + "\n" + setEnvVarInstructions }); } if (data?.code === "RaceDetected") { const message = data.message || "Schema or environment variables changed during push"; return await ctx.crash({ exitCode: 1, errorType: "transient", errForSentry: error, printedMessage: import_chalk.chalkStderr.yellow(message) }); } if (data?.code === "InternalServerError") { if (deploymentName?.startsWith("local-")) { (0, import_errors.printLocalDeploymentOnError)(); return ctx.crash({ exitCode: 1, errorType: "fatal", errForSentry: new import_errors.LocalDeploymentError( "InternalServerError while pushing to local deployment" ), printedMessage: defaultMessage }); } } (0, import_log.logFailure)(defaultMessage); return await (0, import_utils.logAndHandleFetchError)(ctx, error); } //# sourceMappingURL=config.js.map