diff --git a/npm-packages/convex/src/bundler/index.ts b/npm-packages/convex/src/bundler/index.ts index 352a3e765..60ec80387 100644 --- a/npm-packages/convex/src/bundler/index.ts +++ b/npm-packages/convex/src/bundler/index.ts @@ -297,10 +297,25 @@ export async function bundleSchema( dir: string, extraConditions: string[], ) { - let target = path.resolve(dir, "schema.ts"); - if (!ctx.fs.exists(target)) { - target = path.resolve(dir, "schema.js"); + const candidates = [ + "schema.ts", + "schema.mjs", + "schema.js", + ]; + + let target = ""; + for (const filename of candidates) { + const candidatePath = path.resolve(dir, filename); + if (ctx.fs.exists(candidatePath)) { + target = candidatePath; + break; + } + } + + if (!target) { + return []; } + const result = await bundle( ctx, dir, @@ -419,10 +434,10 @@ export async function entryPoints( logVerbose(ctx, chalk.yellow(`Skipping dotfile ${fpath}`)); } else if (base.startsWith("#")) { logVerbose(ctx, chalk.yellow(`Skipping likely emacs tempfile ${fpath}`)); - } else if (base === "schema.ts" || base === "schema.js") { + } else if (base === "schema.mjs" || base === "schema.js" || base === "schema.ts") { logVerbose(ctx, chalk.yellow(`Skipping ${fpath}`)); } else if ((base.match(/\./g) || []).length > 1) { - // `auth.config.ts` and `convex.config.ts` are important not to bundle. + // `auth.config.*` and `convex.config.*` files are important not to bundle. // `*.test.ts` `*.spec.ts` are common in developer code. logVerbose( ctx, diff --git a/npm-packages/convex/src/cli/lib/components.ts b/npm-packages/convex/src/cli/lib/components.ts index aaefd376d..a92048efc 100644 --- a/npm-packages/convex/src/cli/lib/components.ts +++ b/npm-packages/convex/src/cli/lib/components.ts @@ -54,19 +54,25 @@ import { Reporter, Span } from "./tracing.js"; import { DEFINITION_FILENAME_JS, DEFINITION_FILENAME_TS, + DEFINITION_FILENAME_MJS, } from "./components/constants.js"; import { DeploymentSelection } from "./deploymentSelection.js"; async function findComponentRootPath(ctx: Context, functionsDir: string) { - // Default to `.ts` but fallback to `.js` if not present. - let componentRootPath = path.resolve( - path.join(functionsDir, DEFINITION_FILENAME_TS), - ); - if (!ctx.fs.exists(componentRootPath)) { - componentRootPath = path.resolve( - path.join(functionsDir, DEFINITION_FILENAME_JS), - ); + const candidates = [ + DEFINITION_FILENAME_MJS, + DEFINITION_FILENAME_JS, + DEFINITION_FILENAME_TS, + ]; + + for (const filename of candidates) { + const componentRootPath = path.resolve(path.join(functionsDir, filename)); + if (ctx.fs.exists(componentRootPath)) { + return componentRootPath; + } } - return componentRootPath; + + // Default fallback to .js for backward compatibility + return path.resolve(path.join(functionsDir, DEFINITION_FILENAME_JS)); } export async function runCodegen( diff --git a/npm-packages/convex/src/cli/lib/components/constants.ts b/npm-packages/convex/src/cli/lib/components/constants.ts index 48e0d0d53..1d01bb49f 100644 --- a/npm-packages/convex/src/cli/lib/components/constants.ts +++ b/npm-packages/convex/src/cli/lib/components/constants.ts @@ -1,2 +1,3 @@ export const DEFINITION_FILENAME_TS = "convex.config.ts"; export const DEFINITION_FILENAME_JS = "convex.config.js"; +export const DEFINITION_FILENAME_MJS = "convex.config.mjs"; diff --git a/npm-packages/convex/src/cli/lib/components/definition/bundle.ts b/npm-packages/convex/src/cli/lib/components/definition/bundle.ts index 96f5dfa36..4d2f55070 100644 --- a/npm-packages/convex/src/cli/lib/components/definition/bundle.ts +++ b/npm-packages/convex/src/cli/lib/components/definition/bundle.ts @@ -53,7 +53,7 @@ function componentPlugin({ name: `convex-${mode === "discover" ? "discover-components" : "bundle-components"}`, async setup(build) { // This regex can't be really precise since developers could import - // "convex.config", "convex.config.js", "convex.config.ts", etc. + // "convex.config", "convex.config.mjs", "convex.config.js", "convex.config.ts", etc. build.onResolve({ filter: /.*convex.config.*/ }, async (args) => { verbose && logMessage(ctx, "esbuild resolving import:", args); if (args.namespace !== "file") { @@ -83,12 +83,14 @@ function componentPlugin({ const candidates = [args.path]; const ext = path.extname(args.path); - if (ext === ".js") { - candidates.push(args.path.slice(0, -".js".length) + ".ts"); + + if (ext === ".mjs" || ext === ".js") { + candidates.push(args.path.slice(0, -ext.length) + ".ts"); } - if (ext !== ".js" && ext !== ".ts") { - candidates.push(args.path + ".js"); - candidates.push(args.path + ".ts"); + + // If no extension or unrecognized extension, try all in priority order + if (!ext || ![".mjs", ".js", ".ts"].includes(ext)) { + candidates.push(args.path + ".mjs", args.path + ".js", args.path + ".ts"); } let resolvedPath = undefined; for (const candidate of candidates) { @@ -497,11 +499,14 @@ export async function bundleImplementations( rootComponentDirectory.path, directory.path, ); + // Check for schema files in priority order: .ts, .mjs, .js + const schemaCandidates = ["schema.ts", "schema.mjs", "schema.js"]; + const schemaExists = schemaCandidates.some(filename => + ctx.fs.exists(path.resolve(resolvedPath, filename)) + ); + let schema; - if (ctx.fs.exists(path.resolve(resolvedPath, "schema.ts"))) { - schema = - (await bundleSchema(ctx, resolvedPath, extraConditions))[0] || null; - } else if (ctx.fs.exists(path.resolve(resolvedPath, "schema.js"))) { + if (schemaExists) { schema = (await bundleSchema(ctx, resolvedPath, extraConditions))[0] || null; } else { diff --git a/npm-packages/convex/src/cli/lib/components/definition/directoryStructure.ts b/npm-packages/convex/src/cli/lib/components/definition/directoryStructure.ts index 89bbc9dae..374dcdddf 100644 --- a/npm-packages/convex/src/cli/lib/components/definition/directoryStructure.ts +++ b/npm-packages/convex/src/cli/lib/components/definition/directoryStructure.ts @@ -3,6 +3,7 @@ import { Context } from "../../../../bundler/context.js"; import { DEFINITION_FILENAME_JS, DEFINITION_FILENAME_TS, + DEFINITION_FILENAME_MJS, } from "../constants.js"; import { getFunctionsDirectoryPath } from "../../config.js"; @@ -29,7 +30,7 @@ export type ComponentDirectory = { path: string; /** - * Absolute local filesystem path to the `convex.config.{ts,js}` file within the component definition. + * Absolute local filesystem path to the `convex.config.{mjs,js,ts}` file within the component definition. */ definitionPath: string; }; @@ -67,17 +68,29 @@ export function isComponentDirectory( return { kind: "err", why: `Not a directory` }; } - // Check that we have a definition file, defaulting to `.ts` but falling back to `.js`. - let filename = DEFINITION_FILENAME_TS; - let definitionPath = path.resolve(path.join(directory, filename)); - if (!ctx.fs.exists(definitionPath)) { - filename = DEFINITION_FILENAME_JS; - definitionPath = path.resolve(path.join(directory, filename)); + // Check that we have a definition file, using priority order: .ts, .mjs, .js + const candidates = [ + DEFINITION_FILENAME_TS, + DEFINITION_FILENAME_MJS, + DEFINITION_FILENAME_JS, + ]; + + let filename = ""; + let definitionPath = ""; + + for (const candidate of candidates) { + const candidatePath = path.resolve(path.join(directory, candidate)); + if (ctx.fs.exists(candidatePath)) { + filename = candidate; + definitionPath = candidatePath; + break; + } } - if (!ctx.fs.exists(definitionPath)) { + + if (!filename) { return { kind: "err", - why: `Directory doesn't contain a ${filename} file`, + why: `Directory doesn't contain any of the supported definition files: ${candidates.join(", ")}`, }; } const definitionStat = ctx.fs.stat(definitionPath); diff --git a/npm-packages/dashboard-common/scripts/build-convexServerTypes.py b/npm-packages/dashboard-common/scripts/build-convexServerTypes.py index 3f75e93c6..9927c949d 100644 --- a/npm-packages/dashboard-common/scripts/build-convexServerTypes.py +++ b/npm-packages/dashboard-common/scripts/build-convexServerTypes.py @@ -6,7 +6,7 @@ RELATIVE_PATH_TO_OUTPUT_FILE = "../src/lib/generated/convexServerTypes.json" # The entrypoints and helpers from `convex` NPM package used in Convex server functions -SERVER_ENTRYPOINTS = ["server", "values", "type_utils.d.ts"] +SERVER_ENTRYPOINTS = ["server", "values", "type_utils"] # For VS Code PATH_PREFIX = "file:///convex/" @@ -36,10 +36,32 @@ def build_entrypoint(convex_build_directory, entry_point): def find_dts_files(path, base_path): dts_files = {} if os.path.isdir(path): + # Collect all .d.ts, .d.mts files in this directory + dir_files = {} for item in os.listdir(path): item_path = os.path.join(path, item) - dts_files.update(find_dts_files(item_path, base_path)) - elif path.endswith(".d.ts"): + if os.path.isdir(item_path): + dts_files.update(find_dts_files(item_path, base_path)) + elif item.endswith((".d.mts", ".d.ts")): + # Extract base name (e.g., "a" from "a.d.mts") + if item.endswith(".d.mts"): + base_name = item[:-6] # Remove ".d.mts" + priority = 0 # Highest priority + else: # .d.ts + base_name = item[:-5] # Remove ".d.ts" + priority = 1 # Lower priority + + if base_name not in dir_files or priority < dir_files[base_name][1]: + dir_files[base_name] = (item_path, priority) + + # Process the selected files from this directory + for item_path, _ in dir_files.values(): + relative_path = os.path.relpath(item_path, base_path) + with open(item_path, "r", encoding="utf-8") as file: + dts_files[PATH_PREFIX + relative_path] = strip_source_map_suffix( + file.read() + ) + elif path.endswith((".d.mts", ".d.ts")): relative_path = os.path.relpath(path, base_path) with open(path, "r", encoding="utf-8") as file: dts_files[PATH_PREFIX + relative_path] = strip_source_map_suffix(