UNPKG

convex

Version:

Client for the Convex Cloud

467 lines (466 loc) 17.2 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 bundler_exports = {}; __export(bundler_exports, { RecordingFs: () => import_fs2.RecordingFs, actionsDir: () => actionsDir, bundle: () => bundle, bundleAuthConfig: () => bundleAuthConfig, bundleSchema: () => bundleSchema, doesImportConvexHttpRouter: () => doesImportConvexHttpRouter, entryPoints: () => entryPoints, entryPointsByEnvironment: () => entryPointsByEnvironment, mustBeIsolate: () => mustBeIsolate, nodeFs: () => import_fs2.nodeFs, useNodeDirectiveRegex: () => useNodeDirectiveRegex, walkDir: () => walkDir }); module.exports = __toCommonJS(bundler_exports); var import_path = __toESM(require("path"), 1); var import_chalk = require("chalk"); var import_parser = require("@babel/parser"); var Sentry = __toESM(require("@sentry/node"), 1); var import_fs = require("./fs.js"); var import_log = require("./log.js"); var import_wasm = require("./wasm.js"); var import_external = require("./external.js"); var import_debugBundle = require("./debugBundle.js"); var import_fs2 = require("./fs.js"); const actionsDir = "actions"; function* walkDir(fs, dirPath, shouldSkipDir, depth) { depth = depth ?? 0; for (const dirEntry of fs.listDir(dirPath).sort(import_fs.consistentPathSort)) { const childPath = import_path.default.join(dirPath, dirEntry.name); if (dirEntry.isDirectory()) { if (shouldSkipDir && shouldSkipDir(childPath)) { continue; } yield { isDir: true, path: childPath, depth }; yield* walkDir(fs, childPath, shouldSkipDir, depth + 1); } else if (dirEntry.isFile()) { yield { isDir: false, path: childPath, depth }; } } } async function doEsbuild({ ctx, dir, entryPoints: entryPoints2, generateSourceMaps, platform, chunksFolder, externalPackages, extraConditions, includeSourcesContent, splitting }) { const external = (0, import_external.createExternalPlugin)(ctx, externalPackages); try { const result = await (0, import_debugBundle.innerEsbuild)({ entryPoints: entryPoints2, platform, generateSourceMaps, chunksFolder, extraConditions, dir, // The wasmPlugin should be last so it doesn't run on external modules. plugins: [external.plugin, import_wasm.wasmPlugin], includeSourcesContent, splitting }); for (const [relPath, input] of Object.entries(result.metafile.inputs)) { if (relPath.indexOf("(disabled):") !== -1 || relPath.startsWith("wasm-binary:") || relPath.startsWith("wasm-stub:")) { continue; } const absPath = import_path.default.resolve(relPath); const st = ctx.fs.stat(absPath); if (st.size !== input.bytes) { (0, import_log.logWarning)( `Bundled file ${absPath} changed right after esbuild invocation` ); return await ctx.crash({ exitCode: 1, errorType: "transient", printedMessage: null }); } ctx.fs.registerPath(absPath, st); } return { ...result, externalModuleNames: external.externalModuleNames, bundledModuleNames: external.bundledModuleNames }; } catch (e) { let recommendUseNode = false; if ((0, import_debugBundle.isEsbuildBuildError)(e)) { for (const error of e.errors) { if (error.location) { const absPath = import_path.default.resolve(error.location.file); const st = ctx.fs.stat(absPath); ctx.fs.registerPath(absPath, st); } if (platform !== "node" && !recommendUseNode && error.notes.some( (note) => note.text.includes("Are you trying to bundle for node?") )) { recommendUseNode = true; } } } return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", // We don't print any error because esbuild already printed // all the relevant information. printedMessage: recommendUseNode ? ` It looks like you are using Node APIs from a file without the "use node" directive. Split out actions using Node.js APIs like this into a new file only containing actions that uses "use node" so these actions will run in a Node.js environment. For more information see https://docs.convex.dev/functions/runtimes#nodejs-runtime ` : null }); } } async function bundle({ ctx, dir, entryPoints: entryPoints2, generateSourceMaps, platform, chunksFolder = "_deps", externalPackagesAllowList = [], extraConditions = [], includeSourcesContent = false, splitting }) { const availableExternalPackages = await (0, import_external.computeExternalPackages)( ctx, externalPackagesAllowList ); const result = await doEsbuild({ ctx, dir, entryPoints: entryPoints2, generateSourceMaps, platform, chunksFolder, externalPackages: availableExternalPackages, extraConditions, includeSourcesContent, splitting }); if (result.errors.length) { const errorMessage = result.errors.map((e) => `esbuild error: ${e.text}`).join("\n"); return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: errorMessage }); } for (const warning of result.warnings) { (0, import_log.logWarning)(import_chalk.chalkStderr.yellow(`esbuild warning: ${warning.text}`)); } const sourceMaps = /* @__PURE__ */ new Map(); const modules = []; const environment = platform === "node" ? "node" : "isolate"; for (const outputFile of result.outputFiles) { const relPath = import_path.default.relative(import_path.default.normalize("out"), outputFile.path); if (import_path.default.extname(relPath) === ".map") { sourceMaps.set(relPath, outputFile.text); continue; } const posixRelPath = relPath.split(import_path.default.sep).join(import_path.default.posix.sep); modules.push({ path: posixRelPath, source: outputFile.text, environment }); } for (const module2 of modules) { const sourceMapPath = module2.path + ".map"; const sourceMap = sourceMaps.get(sourceMapPath); if (sourceMap) { module2.sourceMap = sourceMap; } } return { modules, externalDependencies: await externalPackageVersions( ctx, availableExternalPackages, result.externalModuleNames ), bundledModuleNames: result.bundledModuleNames }; } async function externalPackageVersions(ctx, availableExternalPackages, referencedPackages) { const versions = /* @__PURE__ */ new Map(); const referencedPackagesQueue = Array.from(referencedPackages.keys()); for (let i = 0; i < referencedPackagesQueue.length; i++) { const moduleName = referencedPackagesQueue[i]; const modulePath = availableExternalPackages.get(moduleName).path; const { version, peerAndOptionalDependencies } = await (0, import_external.findExactVersionAndDependencies)(ctx, moduleName, modulePath); versions.set(moduleName, version); for (const dependency of peerAndOptionalDependencies) { if (availableExternalPackages.has(dependency) && !referencedPackages.has(dependency)) { referencedPackagesQueue.push(dependency); referencedPackages.add(dependency); } } } return versions; } async function bundleSchema(ctx, dir, extraConditions) { let target = import_path.default.resolve(dir, "schema.ts"); if (!ctx.fs.exists(target)) { target = import_path.default.resolve(dir, "schema.js"); } const result = await bundle({ ctx, dir, entryPoints: [target], generateSourceMaps: true, platform: "browser", extraConditions }); return result.modules; } async function bundleAuthConfig(ctx, dir) { const authConfigPath = import_path.default.resolve(dir, "auth.config.js"); const authConfigTsPath = import_path.default.resolve(dir, "auth.config.ts"); if (ctx.fs.exists(authConfigPath) && ctx.fs.exists(authConfigTsPath)) { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `Found both ${authConfigPath} and ${authConfigTsPath}, choose one.` }); } const chosenPath = ctx.fs.exists(authConfigTsPath) ? authConfigTsPath : authConfigPath; if (!ctx.fs.exists(chosenPath)) { (0, import_log.logVerbose)( import_chalk.chalkStderr.yellow( `Found no auth config file at ${authConfigTsPath} or ${authConfigPath} so there are no configured auth providers` ) ); return []; } (0, import_log.logVerbose)(import_chalk.chalkStderr.yellow(`Bundling auth config found at ${chosenPath}`)); const result = await bundle({ ctx, dir, entryPoints: [chosenPath], generateSourceMaps: true, platform: "browser", // The auth config must be one module splitting: false }); return result.modules; } async function doesImportConvexHttpRouter(source) { try { const ast = (0, import_parser.parse)(source, { sourceType: "module", plugins: ["typescript"] }); return ast.program.body.some((node) => { if (node.type !== "ImportDeclaration") return false; return node.specifiers.some((s) => { const specifier = s; const imported = specifier.imported; return imported.name === "httpRouter"; }); }); } catch { return source.match( /import\s*\{\s*httpRouter.*\}\s*from\s*"\s*convex\/server\s*"/ ) !== null; } } const ENTRY_POINT_EXTENSIONS = [ // ESBuild js loader ".js", ".mjs", ".cjs", // ESBuild ts loader ".ts", ".tsx", ".mts", ".cts", // ESBuild jsx loader ".jsx" // ESBuild supports css, text, json, and more but these file types are not // allowed to define entry points. ]; async function entryPoints(ctx, dir) { const entryPoints2 = []; const looksLikeNestedComponent = (dirPath) => { const config = import_path.default.join(dirPath, "convex.config.ts"); const isComponentDefinition = ctx.fs.exists(config); if (isComponentDefinition) { (0, import_log.logVerbose)(import_chalk.chalkStderr.yellow(`Skipping component directory ${dirPath}`)); } return isComponentDefinition; }; for (const { isDir, path: fpath, depth } of walkDir( ctx.fs, dir, looksLikeNestedComponent )) { if (isDir) { continue; } const relPath = import_path.default.relative(dir, fpath); const parsedPath = import_path.default.parse(fpath); const base = parsedPath.base; const extension = parsedPath.ext.toLowerCase(); if (relPath.startsWith("_deps" + import_path.default.sep)) { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `The path "${fpath}" is within the "_deps" directory, which is reserved for dependencies. Please move your code to another directory.` }); } if (depth === 0 && base.toLowerCase().startsWith("https.")) { const source = ctx.fs.readUtf8File(fpath); if (await doesImportConvexHttpRouter(source)) (0, import_log.logWarning)( import_chalk.chalkStderr.yellow( `Found ${fpath}. HTTP action routes will not be imported from this file. Did you mean to include http${extension}?` ) ); Sentry.captureMessage( `User code top level directory contains file ${base} which imports httpRouter.`, "warning" ); } if (!ENTRY_POINT_EXTENSIONS.some((ext) => relPath.endsWith(ext))) { (0, import_log.logVerbose)(import_chalk.chalkStderr.yellow(`Skipping non-JS file ${fpath}`)); } else if (relPath.startsWith("_generated" + import_path.default.sep)) { (0, import_log.logVerbose)(import_chalk.chalkStderr.yellow(`Skipping ${fpath}`)); } else if (base.startsWith(".")) { (0, import_log.logVerbose)(import_chalk.chalkStderr.yellow(`Skipping dotfile ${fpath}`)); } else if (base.startsWith("#")) { (0, import_log.logVerbose)(import_chalk.chalkStderr.yellow(`Skipping likely emacs tempfile ${fpath}`)); } else if (base === "schema.ts" || base === "schema.js") { (0, import_log.logVerbose)(import_chalk.chalkStderr.yellow(`Skipping ${fpath}`)); } else if ((base.match(/\./g) || []).length > 1) { (0, import_log.logVerbose)( import_chalk.chalkStderr.yellow(`Skipping ${fpath} that contains multiple dots`) ); } else if (relPath.includes(" ")) { (0, import_log.logVerbose)( import_chalk.chalkStderr.yellow(`Skipping ${relPath} because it contains a space`) ); } else { (0, import_log.logVerbose)(import_chalk.chalkStderr.green(`Preparing ${fpath}`)); entryPoints2.push(fpath); } } const nonEmptyEntryPoints = entryPoints2.filter((fpath) => { if (!fpath.endsWith(".ts") && !fpath.endsWith(".tsx")) { return true; } const contents = ctx.fs.readUtf8File(fpath); if (/^\s{0,100}(import|export)/m.test(contents)) { return true; } (0, import_log.logVerbose)( import_chalk.chalkStderr.yellow( `Skipping ${fpath} because it has no export or import to make it a valid TypeScript module` ) ); }); return nonEmptyEntryPoints; } const useNodeDirectiveRegex = /^\s*("|')use node("|');?\s*$/; function hasUseNodeDirective(ctx, fpath) { const source = ctx.fs.readUtf8File(fpath); if (source.indexOf("use node") === -1) { return false; } try { const ast = (0, import_parser.parse)(source, { // parse in strict mode and allow module declarations sourceType: "module", // esbuild supports jsx and typescript by default. Allow the same plugins // here too. plugins: ["jsx", "typescript"] }); return ast.program.directives.map((d) => d.value.value).includes("use node"); } catch (error) { let lineMatches = false; for (const line of source.split("\n")) { if (line.match(useNodeDirectiveRegex)) { lineMatches = true; break; } } (0, import_log.logVerbose)( `Failed to parse ${fpath}. Use node is set to ${lineMatches} based on regex. Parse error: ${error.toString()}.` ); return lineMatches; } } function mustBeIsolate(relPath) { return ["http", "crons", "schema", "auth.config"].includes( relPath.replace(/\.[^/.]+$/, "") ); } async function determineEnvironment(ctx, dir, fpath) { const relPath = import_path.default.relative(dir, fpath); const useNodeDirectiveFound = hasUseNodeDirective(ctx, fpath); if (useNodeDirectiveFound) { if (mustBeIsolate(relPath)) { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `"use node" directive is not allowed for ${relPath}.` }); } return "node"; } const actionsPrefix = actionsDir + import_path.default.sep; if (relPath.startsWith(actionsPrefix)) { return await ctx.crash({ exitCode: 1, errorType: "invalid filesystem data", printedMessage: `${relPath} is in /actions subfolder but has no "use node"; directive. You can now define actions in any folder and indicate they should run in node by adding "use node" directive. /actions is a deprecated way to choose Node.js environment, and we require "use node" for all files within that folder to avoid unexpected errors during the migration. See https://docs.convex.dev/functions/actions for more details` }); } return "isolate"; } async function entryPointsByEnvironment(ctx, dir) { const isolate = []; const node = []; for (const entryPoint of await entryPoints(ctx, dir)) { const environment = await determineEnvironment(ctx, dir, entryPoint); if (environment === "node") { node.push(entryPoint); } else { isolate.push(entryPoint); } } return { isolate, node }; } //# sourceMappingURL=index.js.map