@concavejs/cli 0.0.1-alpha.6 → 0.0.1-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4757,7 +4757,7 @@ async function loadAppDefinition(configPath, parentComponentPath, entries, visit
4757
4757
  for (const child of childComponents) {
4758
4758
  const componentPath = joinComponentPath(parentComponentPath, child.name);
4759
4759
  const definitionPath = resolveChildDefinitionPath(configPath, child.path);
4760
- const modulesDir = path.join(path.dirname(definitionPath), "convex");
4760
+ const modulesDir = await resolveComponentModulesDir(definitionPath);
4761
4761
  entries.push({
4762
4762
  componentPath,
4763
4763
  modulesDir,
@@ -4780,7 +4780,7 @@ async function loadComponentDefinition(configPath, componentPath, entries, visit
4780
4780
  for (const child of childComponents) {
4781
4781
  const nestedComponentPath = joinComponentPath(componentPath, child.name);
4782
4782
  const definitionPath = resolveChildDefinitionPath(configPath, child.path);
4783
- const modulesDir = path.join(path.dirname(definitionPath), "convex");
4783
+ const modulesDir = await resolveComponentModulesDir(definitionPath);
4784
4784
  entries.push({
4785
4785
  componentPath: nestedComponentPath,
4786
4786
  modulesDir,
@@ -4806,6 +4806,17 @@ function resolveChildDefinitionPath(parentConfigPath, childPath) {
4806
4806
  const parentDir = path.dirname(parentConfigPath);
4807
4807
  return path.resolve(parentDir, childPath);
4808
4808
  }
4809
+ async function resolveComponentModulesDir(definitionPath) {
4810
+ const definitionDir = path.dirname(definitionPath);
4811
+ const nestedConvexDir = path.join(definitionDir, "convex");
4812
+ try {
4813
+ const stats = await fs.stat(nestedConvexDir);
4814
+ if (stats.isDirectory()) {
4815
+ return nestedConvexDir;
4816
+ }
4817
+ } catch {}
4818
+ return definitionDir;
4819
+ }
4809
4820
  function joinComponentPath(parent, child) {
4810
4821
  if (!parent) {
4811
4822
  return child;
@@ -5146,6 +5157,7 @@ async function generateUserEntry(projectRoot, outDir) {
5146
5157
  const componentManifest = await scanComponents(projectRoot);
5147
5158
  await writeComponentManifest(targetDir, componentManifest.entries);
5148
5159
  await copyComponentModules(targetDir, componentManifest.entries);
5160
+ const componentLoaderBlocks = await buildComponentLoaderBlocks(targetDir, componentManifest.entries);
5149
5161
  const fallbackEntries = files.map((filePath) => {
5150
5162
  const importPath = formatImportPath(targetDir, filePath);
5151
5163
  return ` '${importPath}': () => import('${importPath}'),`;
@@ -5154,10 +5166,20 @@ async function generateUserEntry(projectRoot, outDir) {
5154
5166
  const content = `// Generated by concave CLI
5155
5167
  // Do not edit this file manually
5156
5168
 
5169
+ import { createModuleLoaderFromGlob, setConcaveModuleLoader } from "@concavejs/core/udf";
5170
+
5157
5171
  const modules = import.meta?.glob?.(["../convex/**/*.*s", "!../**/*.*.*s"]) ?? {
5158
5172
  ${fallbackEntries}
5159
5173
  };
5160
5174
 
5175
+ const componentLoaders = [];
5176
+ ${componentLoaderBlocks.join(`
5177
+
5178
+ `)}
5179
+ for (const { componentPath, loader } of componentLoaders) {
5180
+ setConcaveModuleLoader(loader, { componentPath });
5181
+ }
5182
+
5161
5183
  export { modules };
5162
5184
  `;
5163
5185
  const contentWithFunctionsPath = content.replace('const modules = import.meta?.glob?.(["../convex/**/*.*s", "!../**/*.*.*s"]) ?? {', `const modules = import.meta?.glob?.([${JSON.stringify(functionsGlobPattern)}, "!../**/*.*.*s"]) ?? {`);
@@ -6611,6 +6633,7 @@ async function bundleProject(projectRoot, _verbose) {
6611
6633
  sourcemap: false,
6612
6634
  logLevel: "silent",
6613
6635
  absWorkingDir: buildDir,
6636
+ external: ["node:*"],
6614
6637
  plugins: [componentDefinitionPlugin()]
6615
6638
  });
6616
6639
  if (buildResult.errors.length > 0) {
@@ -7386,6 +7409,8 @@ import {
7386
7409
  } from "convex/server";
7387
7410
  import type { DataModel } from "./dataModel.js";
7388
7411
 
7412
+ export { defineSchema, defineTable } from "convex/server";
7413
+
7389
7414
  /**
7390
7415
  * Define a query in this Convex app's public API.
7391
7416
  *
@@ -7527,6 +7552,8 @@ import {
7527
7552
  internalActionGeneric,
7528
7553
  internalMutationGeneric,
7529
7554
  internalQueryGeneric,
7555
+ defineSchema,
7556
+ defineTable,
7530
7557
  } from "convex/server";
7531
7558
 
7532
7559
  /**
@@ -7598,6 +7625,11 @@ export const internalAction = internalActionGeneric;
7598
7625
  * @returns The wrapped endpoint function. Route a URL path to this function in \`convex/http.js\`.
7599
7626
  */
7600
7627
  export const httpAction = httpActionGeneric;
7628
+
7629
+ /**
7630
+ * Define a table in this Convex app's schema.
7631
+ */
7632
+ export { defineTable, defineSchema };
7601
7633
  `;
7602
7634
  await fs9.writeFile(path12.join(generatedDir, "server.js"), content, "utf8");
7603
7635
  }
@@ -7734,8 +7766,59 @@ async function generateApiJs(generatedDir) {
7734
7766
  * @module
7735
7767
  */
7736
7768
 
7737
- import { anyApi } from "convex/server";
7738
- import { componentsGeneric } from "convex/server";
7769
+ const functionName = Symbol.for("functionName");
7770
+ const toReferencePath = Symbol.for("toReferencePath");
7771
+
7772
+ function createApi(pathParts = []) {
7773
+ return new Proxy(
7774
+ {},
7775
+ {
7776
+ get(_target, prop) {
7777
+ if (typeof prop === "string") {
7778
+ return createApi([...pathParts, prop]);
7779
+ }
7780
+ if (prop === functionName) {
7781
+ if (pathParts.length < 2) {
7782
+ const found = ["api", ...pathParts].join(".");
7783
+ throw new Error(
7784
+ "API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`" + found + "\`",
7785
+ );
7786
+ }
7787
+ const modulePath = pathParts.slice(0, -1).join("/");
7788
+ const exportName = pathParts[pathParts.length - 1];
7789
+ return exportName === "default" ? modulePath : modulePath + ":" + exportName;
7790
+ }
7791
+ if (prop === Symbol.toStringTag) {
7792
+ return "FunctionReference";
7793
+ }
7794
+ return undefined;
7795
+ },
7796
+ },
7797
+ );
7798
+ }
7799
+
7800
+ function createComponents(pathParts = []) {
7801
+ return new Proxy(
7802
+ {},
7803
+ {
7804
+ get(_target, prop) {
7805
+ if (typeof prop === "string") {
7806
+ return createComponents([...pathParts, prop]);
7807
+ }
7808
+ if (prop === toReferencePath) {
7809
+ if (pathParts.length < 1) {
7810
+ const found = ["components", ...pathParts].join(".");
7811
+ throw new Error(
7812
+ "API path is expected to be of the form \`components.childComponent.functionName\`. Found: \`" + found + "\`",
7813
+ );
7814
+ }
7815
+ return "_reference/childComponent/" + pathParts.join("/");
7816
+ }
7817
+ return undefined;
7818
+ },
7819
+ },
7820
+ );
7821
+ }
7739
7822
 
7740
7823
  /**
7741
7824
  * A utility for referencing Convex functions in your app's API.
@@ -7745,9 +7828,9 @@ import { componentsGeneric } from "convex/server";
7745
7828
  * const myFunctionReference = api.myModule.myFunction;
7746
7829
  * \`\`\`
7747
7830
  */
7748
- export const api = anyApi;
7749
- export const internal = anyApi;
7750
- export const components = componentsGeneric();
7831
+ export const api = createApi();
7832
+ export const internal = createApi();
7833
+ export const components = createComponents();
7751
7834
  `;
7752
7835
  await fs9.writeFile(path12.join(generatedDir, "api.js"), content, "utf8");
7753
7836
  }
@@ -8296,6 +8379,8 @@ import {
8296
8379
  } from "convex/server";
8297
8380
  import type { DataModel } from "./dataModel.js";
8298
8381
 
8382
+ export { defineSchema, defineTable } from "convex/server";
8383
+
8299
8384
  export declare const query: QueryBuilder<DataModel, "public">;
8300
8385
  export declare const internalQuery: QueryBuilder<DataModel, "internal">;
8301
8386
  export declare const mutation: MutationBuilder<DataModel, "public">;
@@ -8331,15 +8416,84 @@ import {
8331
8416
  internalActionGeneric,
8332
8417
  internalMutationGeneric,
8333
8418
  internalQueryGeneric,
8419
+ defineSchema,
8420
+ defineTable,
8334
8421
  } from "convex/server";
8335
8422
 
8423
+ /**
8424
+ * Define a query in this Convex app's public API.
8425
+ *
8426
+ * This function will be allowed to read your Convex database and will be accessible from the client.
8427
+ *
8428
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
8429
+ * @returns The wrapped query. Include this as an \`export\` to name it and make it accessible.
8430
+ */
8336
8431
  export const query = queryGeneric;
8432
+
8433
+ /**
8434
+ * Define a query that is only accessible from other Convex functions (but not from the client).
8435
+ *
8436
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
8437
+ *
8438
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
8439
+ * @returns The wrapped query. Include this as an \`export\` to name it and make it accessible.
8440
+ */
8337
8441
  export const internalQuery = internalQueryGeneric;
8442
+
8443
+ /**
8444
+ * Define a mutation in this Convex app's public API.
8445
+ *
8446
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
8447
+ *
8448
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
8449
+ * @returns The wrapped mutation. Include this as an \`export\` to name it and make it accessible.
8450
+ */
8338
8451
  export const mutation = mutationGeneric;
8452
+
8453
+ /**
8454
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
8455
+ *
8456
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
8457
+ *
8458
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
8459
+ * @returns The wrapped mutation. Include this as an \`export\` to name it and make it accessible.
8460
+ */
8339
8461
  export const internalMutation = internalMutationGeneric;
8462
+
8463
+ /**
8464
+ * Define an action in this Convex app's public API.
8465
+ *
8466
+ * An action is a function which can execute any JavaScript code, including non-deterministic
8467
+ * code and code with side-effects, like calling third-party services.
8468
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
8469
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
8470
+ *
8471
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
8472
+ * @returns The wrapped action. Include this as an \`export\` to name it and make it accessible.
8473
+ */
8340
8474
  export const action = actionGeneric;
8475
+
8476
+ /**
8477
+ * Define an action that is only accessible from other Convex functions (but not from the client).
8478
+ *
8479
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
8480
+ * @returns The wrapped function. Include this as an \`export\` to name it and make it accessible.
8481
+ */
8341
8482
  export const internalAction = internalActionGeneric;
8483
+
8484
+ /**
8485
+ * Define a Convex HTTP action.
8486
+ *
8487
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a \`Request\` object
8488
+ * as its second.
8489
+ * @returns The wrapped endpoint function. Route a URL path to this function in \`convex/http.js\`.
8490
+ */
8342
8491
  export const httpAction = httpActionGeneric;
8492
+
8493
+ /**
8494
+ * Define a table in this Convex app's schema.
8495
+ */
8496
+ export { defineTable, defineSchema };
8343
8497
  `;
8344
8498
  await fs10.writeFile(path13.join(generatedDir, "server.js"), content, "utf8");
8345
8499
  }
@@ -8382,7 +8536,59 @@ async function generateApiJs2(generatedDir) {
8382
8536
  * @module
8383
8537
  */
8384
8538
 
8385
- import { anyApi, componentsGeneric } from "convex/server";
8539
+ const functionName = Symbol.for("functionName");
8540
+ const toReferencePath = Symbol.for("toReferencePath");
8541
+
8542
+ function createApi(pathParts = []) {
8543
+ return new Proxy(
8544
+ {},
8545
+ {
8546
+ get(_target, prop) {
8547
+ if (typeof prop === "string") {
8548
+ return createApi([...pathParts, prop]);
8549
+ }
8550
+ if (prop === functionName) {
8551
+ if (pathParts.length < 2) {
8552
+ const found = ["api", ...pathParts].join(".");
8553
+ throw new Error(
8554
+ "API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`" + found + "\`",
8555
+ );
8556
+ }
8557
+ const modulePath = pathParts.slice(0, -1).join("/");
8558
+ const exportName = pathParts[pathParts.length - 1];
8559
+ return exportName === "default" ? modulePath : modulePath + ":" + exportName;
8560
+ }
8561
+ if (prop === Symbol.toStringTag) {
8562
+ return "FunctionReference";
8563
+ }
8564
+ return undefined;
8565
+ },
8566
+ },
8567
+ );
8568
+ }
8569
+
8570
+ function createComponents(pathParts = []) {
8571
+ return new Proxy(
8572
+ {},
8573
+ {
8574
+ get(_target, prop) {
8575
+ if (typeof prop === "string") {
8576
+ return createComponents([...pathParts, prop]);
8577
+ }
8578
+ if (prop === toReferencePath) {
8579
+ if (pathParts.length < 1) {
8580
+ const found = ["components", ...pathParts].join(".");
8581
+ throw new Error(
8582
+ "API path is expected to be of the form \`components.childComponent.functionName\`. Found: \`" + found + "\`",
8583
+ );
8584
+ }
8585
+ return "_reference/childComponent/" + pathParts.join("/");
8586
+ }
8587
+ return undefined;
8588
+ },
8589
+ },
8590
+ );
8591
+ }
8386
8592
 
8387
8593
  /**
8388
8594
  * A utility for referencing Convex functions in your app's API.
@@ -8392,9 +8598,9 @@ import { anyApi, componentsGeneric } from "convex/server";
8392
8598
  * const myFunctionReference = api.myModule.myFunction;
8393
8599
  * \`\`\`
8394
8600
  */
8395
- export const api = anyApi;
8396
- export const internal = anyApi;
8397
- export const components = componentsGeneric();
8601
+ export const api = createApi();
8602
+ export const internal = createApi();
8603
+ export const components = createComponents();
8398
8604
  `;
8399
8605
  await fs10.writeFile(path13.join(generatedDir, "api.js"), content, "utf8");
8400
8606
  }
@@ -10097,7 +10303,7 @@ function setupAuthEnvironment(keys, siteUrl, options = {}) {
10097
10303
  setEnv("AUTH_SKIP_VERIFICATION", "true");
10098
10304
  }
10099
10305
  // package.json
10100
- var version = "0.0.1-alpha.6";
10306
+ var version = "0.0.1-alpha.8";
10101
10307
 
10102
10308
  // src/cli/cli.ts
10103
10309
  var WATCH_IGNORE_PATTERNS = [
@@ -10248,14 +10454,6 @@ async function ensurePackageJsonWithConvexDependency(projectRoot) {
10248
10454
  };
10249
10455
  }
10250
10456
  async function canResolveConvexRuntime(projectRoot) {
10251
- const nodeModulesConvex = path15.join(projectRoot, "node_modules", "convex", "package.json");
10252
- if (existsSync2(nodeModulesConvex)) {
10253
- return true;
10254
- }
10255
- const hasYarnPnp = existsSync2(path15.join(projectRoot, ".pnp.cjs")) || existsSync2(path15.join(projectRoot, ".pnp.js"));
10256
- if (!hasYarnPnp) {
10257
- return false;
10258
- }
10259
10457
  const requireFromProject = createRequire3(path15.join(projectRoot, "__concave__.js"));
10260
10458
  try {
10261
10459
  requireFromProject.resolve("convex/values");
@@ -10291,7 +10489,7 @@ async function ensureConvexDependencyReady(projectRoot, commandName) {
10291
10489
  }
10292
10490
  if (!hasConvexDependency(packageJson)) {
10293
10491
  console.error(import_picocolors9.default.red("✗ Missing dependency: convex"));
10294
- console.error(import_picocolors9.default.dim(" Concave function files import `convex/values` and generated files import `convex/server`."));
10492
+ console.error(import_picocolors9.default.dim(" Concave function files import `convex/values` and generated types depend on `convex/server`."));
10295
10493
  console.error(import_picocolors9.default.dim(` Run: ${addCommand.display}`));
10296
10494
  process.exit(1);
10297
10495
  }
@@ -10954,17 +11152,15 @@ async function runBunDevServer(cwd, opts, artifacts, layout) {
10954
11152
  throw error;
10955
11153
  }
10956
11154
  };
10957
- const restartServer = async (reason) => {
11155
+ const reloadServer = async (reason) => {
10958
11156
  if (isRestarting)
10959
11157
  return;
10960
11158
  isRestarting = true;
10961
11159
  try {
10962
- console.log(import_picocolors9.default.yellow(`
10963
- ⟳ ${reason}`));
10964
- if (server) {
10965
- await server.close();
10966
- }
10967
- await startServer();
11160
+ console.log(import_picocolors9.default.dim(`
11161
+ ⟳ ${reason}`));
11162
+ await server.reload();
11163
+ console.log(import_picocolors9.default.dim(` ✓ Reloaded`));
10968
11164
  } catch (error) {
10969
11165
  console.error(import_picocolors9.default.red("✗ Failed to reload server:"), error.message);
10970
11166
  } finally {
@@ -10988,7 +11184,7 @@ async function runBunDevServer(cwd, opts, artifacts, layout) {
10988
11184
  try {
10989
11185
  await generateTypes(cwd, { silent: true });
10990
11186
  } catch {}
10991
- restartServer(`File changed: ${relPath}`);
11187
+ reloadServer(`File changed: ${relPath}`);
10992
11188
  }, 300);
10993
11189
  };
10994
11190
  watcher.on("add", (filePath) => scheduleReload("added", filePath)).on("change", (filePath) => scheduleReload("changed", filePath)).on("unlink", (filePath) => scheduleReload("deleted", filePath));
@@ -11095,20 +11291,136 @@ process.on("SIGTERM", async () => {
11095
11291
  `;
11096
11292
  await fs12.writeFile(tempScriptPath, scriptContent, "utf8");
11097
11293
  const bunArgs = [tempScriptPath];
11098
- const bunProcess = spawn2("bun", bunArgs, {
11099
- cwd,
11100
- stdio: "inherit"
11101
- });
11102
- bunProcess.on("exit", (code) => {
11103
- fs12.unlink(tempScriptPath).catch(() => {});
11104
- process.exit(code ?? 0);
11105
- });
11106
- process.on("SIGINT", () => {
11107
- bunProcess.kill("SIGTERM");
11108
- });
11109
- process.on("SIGTERM", () => {
11110
- bunProcess.kill("SIGTERM");
11294
+ let watcher;
11295
+ let bunProcess;
11296
+ let reloadTimer;
11297
+ let isRestarting = false;
11298
+ let isShuttingDown = false;
11299
+ const stopBunProcess = async () => {
11300
+ const activeProcess = bunProcess;
11301
+ if (!activeProcess || activeProcess.exitCode !== null) {
11302
+ return;
11303
+ }
11304
+ await new Promise((resolve3) => {
11305
+ let settled = false;
11306
+ let forceKillTimer;
11307
+ const finish = () => {
11308
+ if (settled) {
11309
+ return;
11310
+ }
11311
+ settled = true;
11312
+ if (forceKillTimer) {
11313
+ clearTimeout(forceKillTimer);
11314
+ forceKillTimer = undefined;
11315
+ }
11316
+ activeProcess.off("exit", onExit);
11317
+ resolve3();
11318
+ };
11319
+ const onExit = () => finish();
11320
+ activeProcess.once("exit", onExit);
11321
+ try {
11322
+ activeProcess.kill("SIGTERM");
11323
+ } catch {
11324
+ finish();
11325
+ return;
11326
+ }
11327
+ forceKillTimer = setTimeout(() => {
11328
+ if (settled) {
11329
+ return;
11330
+ }
11331
+ try {
11332
+ activeProcess.kill("SIGKILL");
11333
+ } catch {
11334
+ finish();
11335
+ }
11336
+ }, 2000);
11337
+ });
11338
+ };
11339
+ const cleanup = async () => {
11340
+ if (reloadTimer) {
11341
+ clearTimeout(reloadTimer);
11342
+ reloadTimer = undefined;
11343
+ }
11344
+ if (watcher) {
11345
+ await watcher.close();
11346
+ watcher = undefined;
11347
+ }
11348
+ await stopBunProcess();
11349
+ await fs12.unlink(tempScriptPath).catch(() => {});
11350
+ };
11351
+ const spawnBunProcess = () => {
11352
+ const child = spawn2("bun", bunArgs, {
11353
+ cwd,
11354
+ stdio: "inherit"
11355
+ });
11356
+ child.on("exit", (code) => {
11357
+ if (isRestarting || isShuttingDown) {
11358
+ return;
11359
+ }
11360
+ cleanup().catch(() => {}).finally(() => {
11361
+ process.exit(code ?? 0);
11362
+ });
11363
+ });
11364
+ return child;
11365
+ };
11366
+ const reloadServerViaHttp = async (reason) => {
11367
+ if (isRestarting || isShuttingDown) {
11368
+ return;
11369
+ }
11370
+ isRestarting = true;
11371
+ try {
11372
+ console.log(import_picocolors9.default.dim(`
11373
+ ⟳ ${reason}`));
11374
+ const response = await fetch(`http://127.0.0.1:${port}/_dev/reload`, {
11375
+ method: "POST",
11376
+ signal: AbortSignal.timeout(5000)
11377
+ });
11378
+ if (response.ok) {
11379
+ console.log(import_picocolors9.default.dim(` ✓ Reloaded`));
11380
+ } else {
11381
+ throw new Error(`HTTP ${response.status}`);
11382
+ }
11383
+ } catch (error) {
11384
+ console.warn(import_picocolors9.default.dim(` HTTP reload failed (${error.message}), restarting process...`));
11385
+ await stopBunProcess();
11386
+ bunProcess = spawnBunProcess();
11387
+ } finally {
11388
+ isRestarting = false;
11389
+ }
11390
+ };
11391
+ bunProcess = spawnBunProcess();
11392
+ watcher = esm_default.watch(convexDir, {
11393
+ ignoreInitial: true,
11394
+ ignored: WATCH_IGNORE_PATTERNS,
11395
+ persistent: true
11111
11396
  });
11397
+ const scheduleReload = (_event, filePath) => {
11398
+ if (reloadTimer) {
11399
+ clearTimeout(reloadTimer);
11400
+ }
11401
+ reloadTimer = setTimeout(async () => {
11402
+ reloadTimer = undefined;
11403
+ const relPath = path15.relative(cwd, filePath);
11404
+ try {
11405
+ await generateTypes(cwd, { silent: true });
11406
+ } catch {}
11407
+ await reloadServerViaHttp(`File changed: ${relPath}`);
11408
+ }, 300);
11409
+ };
11410
+ watcher.on("add", (filePath) => scheduleReload("added", filePath)).on("change", (filePath) => scheduleReload("changed", filePath)).on("unlink", (filePath) => scheduleReload("deleted", filePath));
11411
+ const shutdown = async () => {
11412
+ if (isShuttingDown) {
11413
+ return;
11414
+ }
11415
+ isShuttingDown = true;
11416
+ console.log(import_picocolors9.default.cyan(`
11417
+
11418
+ \uD83C\uDF0A Shutting down Concave dev server...`));
11419
+ await cleanup();
11420
+ process.exit(0);
11421
+ };
11422
+ process.on("SIGINT", shutdown);
11423
+ process.on("SIGTERM", shutdown);
11112
11424
  }
11113
11425
  async function ensureNodeSqliteReady() {
11114
11426
  const hasFlag = process.execArgv.includes("--experimental-sqlite");
@@ -11261,17 +11573,15 @@ async function runNodeDevServer(cwd, opts, artifacts, layout) {
11261
11573
  throw error;
11262
11574
  }
11263
11575
  };
11264
- const restartServer = async (reason) => {
11576
+ const reloadServer = async (reason) => {
11265
11577
  if (isRestarting)
11266
11578
  return;
11267
11579
  isRestarting = true;
11268
11580
  try {
11269
- console.log(import_picocolors9.default.yellow(`
11270
- ⟳ ${reason}`));
11271
- if (server) {
11272
- await server.close();
11273
- }
11274
- await startServer();
11581
+ console.log(import_picocolors9.default.dim(`
11582
+ ⟳ ${reason}`));
11583
+ await server.reload();
11584
+ console.log(import_picocolors9.default.dim(` ✓ Reloaded`));
11275
11585
  } catch (error) {
11276
11586
  console.error(import_picocolors9.default.red("✗ Failed to reload server:"), error.message);
11277
11587
  } finally {
@@ -11295,7 +11605,7 @@ async function runNodeDevServer(cwd, opts, artifacts, layout) {
11295
11605
  try {
11296
11606
  await generateTypes(cwd, { silent: true });
11297
11607
  } catch {}
11298
- restartServer(`File changed: ${relPath}`);
11608
+ reloadServer(`File changed: ${relPath}`);
11299
11609
  }, 300);
11300
11610
  };
11301
11611
  watcher.on("add", (filePath) => scheduleReload("added", filePath)).on("change", (filePath) => scheduleReload("changed", filePath)).on("unlink", (filePath) => scheduleReload("deleted", filePath));
@@ -11411,20 +11721,136 @@ process.on("SIGTERM", async () => {
11411
11721
  await fs12.writeFile(tempScriptPath, scriptContent, "utf8");
11412
11722
  const nodeArgs = ["--experimental-sqlite", "--import", tsxLoaderPath, tempScriptPath];
11413
11723
  const executable = detectHostRuntime() === "node" ? process.execPath : "node";
11414
- const nodeProcess = spawn2(executable, nodeArgs, {
11415
- cwd,
11416
- stdio: "inherit"
11417
- });
11418
- nodeProcess.on("exit", (code) => {
11419
- fs12.unlink(tempScriptPath).catch(() => {});
11420
- process.exit(code ?? 0);
11421
- });
11422
- process.on("SIGINT", () => {
11423
- nodeProcess.kill("SIGTERM");
11424
- });
11425
- process.on("SIGTERM", () => {
11426
- nodeProcess.kill("SIGTERM");
11724
+ let watcher;
11725
+ let nodeProcess;
11726
+ let reloadTimer;
11727
+ let isRestarting = false;
11728
+ let isShuttingDown = false;
11729
+ const stopNodeProcess = async () => {
11730
+ const activeProcess = nodeProcess;
11731
+ if (!activeProcess || activeProcess.exitCode !== null) {
11732
+ return;
11733
+ }
11734
+ await new Promise((resolve3) => {
11735
+ let settled = false;
11736
+ let forceKillTimer;
11737
+ const finish = () => {
11738
+ if (settled) {
11739
+ return;
11740
+ }
11741
+ settled = true;
11742
+ if (forceKillTimer) {
11743
+ clearTimeout(forceKillTimer);
11744
+ forceKillTimer = undefined;
11745
+ }
11746
+ activeProcess.off("exit", onExit);
11747
+ resolve3();
11748
+ };
11749
+ const onExit = () => finish();
11750
+ activeProcess.once("exit", onExit);
11751
+ try {
11752
+ activeProcess.kill("SIGTERM");
11753
+ } catch {
11754
+ finish();
11755
+ return;
11756
+ }
11757
+ forceKillTimer = setTimeout(() => {
11758
+ if (settled) {
11759
+ return;
11760
+ }
11761
+ try {
11762
+ activeProcess.kill("SIGKILL");
11763
+ } catch {
11764
+ finish();
11765
+ }
11766
+ }, 2000);
11767
+ });
11768
+ };
11769
+ const cleanup = async () => {
11770
+ if (reloadTimer) {
11771
+ clearTimeout(reloadTimer);
11772
+ reloadTimer = undefined;
11773
+ }
11774
+ if (watcher) {
11775
+ await watcher.close();
11776
+ watcher = undefined;
11777
+ }
11778
+ await stopNodeProcess();
11779
+ await fs12.unlink(tempScriptPath).catch(() => {});
11780
+ };
11781
+ const spawnNodeProcess = () => {
11782
+ const child = spawn2(executable, nodeArgs, {
11783
+ cwd,
11784
+ stdio: "inherit"
11785
+ });
11786
+ child.on("exit", (code) => {
11787
+ if (isRestarting || isShuttingDown) {
11788
+ return;
11789
+ }
11790
+ cleanup().catch(() => {}).finally(() => {
11791
+ process.exit(code ?? 0);
11792
+ });
11793
+ });
11794
+ return child;
11795
+ };
11796
+ const reloadServerViaHttp = async (reason) => {
11797
+ if (isRestarting || isShuttingDown) {
11798
+ return;
11799
+ }
11800
+ isRestarting = true;
11801
+ try {
11802
+ console.log(import_picocolors9.default.dim(`
11803
+ ⟳ ${reason}`));
11804
+ const response = await fetch(`http://127.0.0.1:${port}/_dev/reload`, {
11805
+ method: "POST",
11806
+ signal: AbortSignal.timeout(5000)
11807
+ });
11808
+ if (response.ok) {
11809
+ console.log(import_picocolors9.default.dim(` ✓ Reloaded`));
11810
+ } else {
11811
+ throw new Error(`HTTP ${response.status}`);
11812
+ }
11813
+ } catch (error) {
11814
+ console.warn(import_picocolors9.default.dim(` HTTP reload failed (${error.message}), restarting process...`));
11815
+ await stopNodeProcess();
11816
+ nodeProcess = spawnNodeProcess();
11817
+ } finally {
11818
+ isRestarting = false;
11819
+ }
11820
+ };
11821
+ nodeProcess = spawnNodeProcess();
11822
+ watcher = esm_default.watch(convexDir, {
11823
+ ignoreInitial: true,
11824
+ ignored: WATCH_IGNORE_PATTERNS,
11825
+ persistent: true
11427
11826
  });
11827
+ const scheduleReload = (_event, filePath) => {
11828
+ if (reloadTimer) {
11829
+ clearTimeout(reloadTimer);
11830
+ }
11831
+ reloadTimer = setTimeout(async () => {
11832
+ reloadTimer = undefined;
11833
+ const relPath = path15.relative(cwd, filePath);
11834
+ try {
11835
+ await generateTypes(cwd, { silent: true });
11836
+ } catch {}
11837
+ await reloadServerViaHttp(`File changed: ${relPath}`);
11838
+ }, 300);
11839
+ };
11840
+ watcher.on("add", (filePath) => scheduleReload("added", filePath)).on("change", (filePath) => scheduleReload("changed", filePath)).on("unlink", (filePath) => scheduleReload("deleted", filePath));
11841
+ const shutdown = async () => {
11842
+ if (isShuttingDown) {
11843
+ return;
11844
+ }
11845
+ isShuttingDown = true;
11846
+ console.log(import_picocolors9.default.cyan(`
11847
+
11848
+ \uD83C\uDF0A Shutting down Concave dev server...`));
11849
+ await cleanup();
11850
+ process.exit(0);
11851
+ };
11852
+ process.on("SIGINT", shutdown);
11853
+ process.on("SIGTERM", shutdown);
11428
11854
  }
11429
11855
  async function ensureWranglerConfig(cwd) {
11430
11856
  const userConfigs = [