kitcn
Version:
kitcn - React Query integration and CLI tools for Convex
159 lines (157 loc) • 6.37 kB
JavaScript
import { Dt as generateMeta, F as resolveRunDeps, Ot as getConvexConfig, j as resolveConfiguredBackend, kt as logger, q as withLocalCodegenEnv } from "./backend-core-cwf87w_T.mjs";
import path from "node:path";
import { fileURLToPath } from "node:url";
//#region src/cli/watcher.ts
function getWatchRoots(functionsDir) {
const convexDir = path.dirname(functionsDir);
return [functionsDir, path.join(convexDir, "routers")];
}
function parseWatcherBackendEnv(value) {
if (value === "convex" || value === "concave") return value;
}
function resolveWatcherCliCommand(currentFilename = fileURLToPath(import.meta.url)) {
const currentDir = path.dirname(currentFilename);
const isTs = currentFilename.endsWith(".ts");
return {
cliPath: isTs ? path.join(currentDir, "cli.ts") : path.join(currentDir, "cli.mjs"),
runtime: isTs ? "bun" : "node"
};
}
function parseTrimSegmentsEnv(value) {
if (!value) return;
const parseFromArray = (segments) => {
const normalized = [...new Set(segments.map((segment) => segment.trim()).filter((segment) => segment))];
return normalized.length > 0 ? normalized : void 0;
};
const trimmed = value.trim();
if (!trimmed) return;
if (trimmed.startsWith("[")) try {
const parsed = JSON.parse(trimmed);
if (Array.isArray(parsed) && parsed.every((segment) => typeof segment === "string")) return parseFromArray(parsed);
} catch {}
return parseFromArray(trimmed.split(","));
}
function shouldIgnoreWatchPath(watchedPath, functionsDir, outputFile) {
const normalizedPath = path.resolve(watchedPath);
const normalizedFunctionsDir = path.resolve(functionsDir);
const normalizedOutputFile = path.resolve(outputFile);
const convexGeneratedDir = path.join(normalizedFunctionsDir, "_generated");
const generatedDir = path.join(normalizedFunctionsDir, "generated");
const generatedFile = path.join(normalizedFunctionsDir, "generated.ts");
if (normalizedPath === normalizedOutputFile) return true;
if (normalizedPath === generatedFile) return true;
if (normalizedPath === convexGeneratedDir || normalizedPath.startsWith(`${convexGeneratedDir}${path.sep}`)) return true;
if (normalizedPath === generatedDir || normalizedPath.startsWith(`${generatedDir}${path.sep}`)) return true;
return normalizedPath.endsWith(".runtime.ts") || normalizedPath.endsWith(".kitcn-parse.ts");
}
async function runWatcherCodegen(params, deps = {}) {
const resolveRunDepsFn = deps.resolveRunDeps ?? resolveRunDeps;
const resolveConfiguredBackendFn = deps.resolveConfiguredBackendFn ?? resolveConfiguredBackend;
const { execa: execaFn, generateMeta: generateMetaFn, loadCliConfig: loadCliConfigFn } = resolveRunDepsFn();
const config = loadCliConfigFn(params.configPath);
const backend = resolveConfiguredBackendFn({
backendArg: params.backendArg,
config
});
if (backend !== "concave") {
await withLocalCodegenEnv(params.sharedDir, backend, async () => {
await generateMetaFn(params.sharedDir, {
debug: params.debug,
silent: true,
scope: params.scope,
trimSegments: params.trimSegments
});
});
return;
}
const { cliPath, runtime } = resolveWatcherCliCommand();
const args = [
cliPath,
"codegen",
"--backend",
"concave",
"--scope",
params.scope
];
if (params.sharedDir) args.push("--api", params.sharedDir);
if (params.configPath) args.push("--config", params.configPath);
if (params.debug) args.push("--debug");
const result = await execaFn(runtime, args, {
cwd: process.cwd(),
reject: false,
stdio: "pipe"
});
if (result.exitCode !== 0) throw new Error(`Watcher codegen failed with exit code ${result.exitCode}.\n${`${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim()}`);
}
async function startWatcher(opts) {
const backendArg = opts?.backend ?? parseWatcherBackendEnv(process.env.KITCN_BACKEND);
const configPath = opts?.configPath ?? process.env.KITCN_CONFIG_PATH;
const sharedDir = opts?.sharedDir ?? (process.env.KITCN_API_OUTPUT_DIR || void 0);
const debug = opts?.debug ?? process.env.KITCN_DEBUG === "1";
const scope = opts?.scope ?? process.env.KITCN_CODEGEN_SCOPE ?? "all";
const trimSegments = opts?.trimSegments ?? parseTrimSegmentsEnv(process.env.KITCN_CODEGEN_TRIM_SEGMENTS);
const debounceMs = opts?.debounceMs ?? 100;
const resolveConfig = opts?.getConvexConfig ?? getConvexConfig;
const runGenerateMeta = opts?.generateMeta ?? generateMeta;
const runWatchCodegen = opts?.runWatcherCodegen ?? runWatcherCodegen;
const { functionsDir, outputFile } = resolveConfig(sharedDir);
const watchRoots = getWatchRoots(functionsDir);
const watch = opts?.watch ?? (await import("chokidar")).watch;
let debounceTimer = null;
let generateMetaInFlight = false;
let generateMetaQueued = false;
const createGenerateOptions = () => {
const generateOptions = {
debug,
silent: true,
scope
};
if (trimSegments && trimSegments.length > 0) generateOptions.trimSegments = trimSegments;
return generateOptions;
};
const runGenerateMetaSafely = async () => {
if (generateMetaInFlight) {
generateMetaQueued = true;
return;
}
generateMetaInFlight = true;
try {
if (backendArg || configPath) await runWatchCodegen({
backendArg,
configPath,
debug,
scope,
sharedDir,
trimSegments
});
else await runGenerateMeta(sharedDir, createGenerateOptions());
logger.success("Convex api updated");
} catch (error) {
logger.error("Watch codegen error:", error);
} finally {
generateMetaInFlight = false;
if (generateMetaQueued) {
generateMetaQueued = false;
scheduleGenerateMeta();
}
}
};
const scheduleGenerateMeta = () => {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
debounceTimer = null;
runGenerateMetaSafely();
}, debounceMs);
};
return watch(watchRoots, {
ignoreInitial: true,
ignored: (watchedPath) => shouldIgnoreWatchPath(watchedPath, functionsDir, outputFile)
}).on("add", scheduleGenerateMeta).on("change", scheduleGenerateMeta).on("unlink", scheduleGenerateMeta).on("error", (err) => logger.error("Watch error:", err));
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) startWatcher().catch((error) => {
logger.error("Watch error:", error);
process.exitCode = 1;
});
//#endregion
export { getWatchRoots, runWatcherCodegen, shouldIgnoreWatchPath, startWatcher };