Skip to content

feat: enhance file resolution logic for Convex #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions npm-packages/convex/src/bundler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
24 changes: 15 additions & 9 deletions npm-packages/convex/src/cli/lib/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions npm-packages/convex/src/cli/lib/components/constants.ts
Original file line number Diff line number Diff line change
@@ -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";
25 changes: 15 additions & 10 deletions npm-packages/convex/src/cli/lib/components/definition/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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;
};
Expand Down Expand Up @@ -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);
Expand Down
28 changes: 25 additions & 3 deletions npm-packages/dashboard-common/scripts/build-convexServerTypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down Expand Up @@ -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(
Expand Down