@luckystack/devkit 0.13.0 → 0.15.0

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/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ requestDevServerRestart
3
+ } from "./chunk-FWE6YJ3G.js";
1
4
  import {
2
5
  validateDeploy
3
6
  } from "./chunk-BJN4XIUX.js";
@@ -64,9 +67,6 @@ var isSyncFileName = (fileName) => {
64
67
  const stem = fileName.slice(0, -3);
65
68
  return getRoutingRules().syncVersionRegex.test(stem);
66
69
  };
67
- var isRouteTestFile = (fileNameOrPath) => {
68
- return fileNameOrPath.endsWith(".tests.ts");
69
- };
70
70
  var isNonRouteDirectory = (directoryName) => directoryName.startsWith(getRoutingRules().privateFolderPrefix) || isTestDirectory(directoryName);
71
71
  var validatePagePath = (srcRelativePath) => {
72
72
  const rules2 = getRoutingRules();
@@ -129,54 +129,56 @@ var isInsidePrivateRouteSubfolder = (fullPath) => {
129
129
  return segments.slice(markerIndex + 1).some((segment) => segment.startsWith(privateFolderPrefix));
130
130
  };
131
131
  var isRouteSurfaceFile = (fullPath) => !isInsidePrivateRouteSubfolder(fullPath) && !isTestFile(fullPath);
132
- var walkRouteFiles = (dir, results = []) => {
132
+ var toSrcMarkerPath = (srcDir, filePath) => `/${normalizePath(path.relative(srcDir, filePath))}`;
133
+ var walkRouteFiles = (srcDir, dir = srcDir, results = []) => {
133
134
  const entries = fs.readdirSync(dir, { withFileTypes: true });
134
135
  const apiSeg = apiMarkerSegment();
135
136
  const syncSeg = syncMarkerSegment();
136
137
  const { ignore } = getRoutingRules();
137
138
  for (const entry of entries) {
138
139
  const fullPath = path.join(dir, entry.name);
139
- const normalizedFullPath = normalizePath(fullPath);
140
+ const srcMarkerPath = toSrcMarkerPath(srcDir, fullPath);
140
141
  if (ignore(toRel(fullPath))) continue;
141
- if (isInsidePrivateRouteSubfolder(fullPath)) continue;
142
+ if (isInsidePrivateRouteSubfolder(srcMarkerPath)) continue;
142
143
  if (entry.isDirectory()) {
143
144
  if (entry.name.startsWith(".") || entry.name === "node_modules") {
144
145
  continue;
145
146
  }
146
- walkRouteFiles(fullPath, results);
147
+ walkRouteFiles(srcDir, fullPath, results);
147
148
  continue;
148
149
  }
149
150
  if (!entry.isFile() || !entry.name.endsWith(".ts")) {
150
151
  continue;
151
152
  }
152
- if (isRouteTestFile(entry.name)) {
153
+ if (!isRouteSurfaceFile(srcMarkerPath)) {
153
154
  continue;
154
155
  }
155
- if (normalizedFullPath.includes(apiSeg) || normalizedFullPath.includes(syncSeg)) {
156
+ if (srcMarkerPath.includes(apiSeg) || srcMarkerPath.includes(syncSeg)) {
156
157
  results.push(fullPath);
157
158
  }
158
159
  }
159
160
  return results;
160
161
  };
161
162
  var getFileRouteToken = ({
162
- normalizedFilePath,
163
+ srcMarkerPath,
163
164
  marker
164
165
  }) => {
165
- const markerIndex = normalizedFilePath.indexOf(marker);
166
+ const markerIndex = srcMarkerPath.indexOf(marker);
166
167
  if (markerIndex === -1) {
167
168
  return "";
168
169
  }
169
170
  const tokenStart = markerIndex + marker.length;
170
- return normalizedFilePath.slice(tokenStart, normalizedFilePath.length - ".ts".length);
171
+ return srcMarkerPath.slice(tokenStart, srcMarkerPath.length - ".ts".length);
171
172
  };
172
- var validateRouteFilePath = (filePath) => {
173
+ var validateRouteFilePath = (srcDir, filePath) => {
173
174
  const issues = [];
174
175
  const normalizedFilePath = normalizePath(path.resolve(filePath));
176
+ const srcMarkerPath = toSrcMarkerPath(srcDir, filePath);
175
177
  const fileName = path.basename(filePath);
176
178
  const apiSeg = apiMarkerSegment();
177
179
  const syncSeg = syncMarkerSegment();
178
- if (normalizedFilePath.includes(apiSeg)) {
179
- const apiRouteToken = getFileRouteToken({ normalizedFilePath, marker: apiSeg });
180
+ if (srcMarkerPath.includes(apiSeg)) {
181
+ const apiRouteToken = getFileRouteToken({ srcMarkerPath, marker: apiSeg });
180
182
  if (apiRouteToken.includes("/")) {
181
183
  issues.push({
182
184
  kind: "api",
@@ -194,8 +196,8 @@ var validateRouteFilePath = (filePath) => {
194
196
  });
195
197
  }
196
198
  }
197
- if (normalizedFilePath.includes(syncSeg)) {
198
- const syncRouteToken = getFileRouteToken({ normalizedFilePath, marker: syncSeg });
199
+ if (srcMarkerPath.includes(syncSeg)) {
200
+ const syncRouteToken = getFileRouteToken({ srcMarkerPath, marker: syncSeg });
199
201
  if (syncRouteToken.includes("/")) {
200
202
  issues.push({
201
203
  kind: "sync",
@@ -259,14 +261,14 @@ var resolveSyncRouteKey = ({
259
261
  const kind = syncMatch[1];
260
262
  const version = `v${syncMatch[2]}`;
261
263
  const syncName = rawSyncName.replace(rules2.syncVersionRegex, "");
262
- const routeBaseKey = pageLocation ? `sync/${pageLocation}/${syncName}/${version}` : `sync/${syncName}/${version}`;
264
+ const routeBaseKey = `sync/${pageLocation || "system"}/${syncName}/${version}`;
263
265
  return `${routeBaseKey}_${kind}`;
264
266
  };
265
267
  var collectInvalidRouteNamingIssues = (srcDir) => {
266
268
  const allRouteFiles = walkRouteFiles(srcDir);
267
269
  const issues = [];
268
270
  for (const filePath of allRouteFiles) {
269
- issues.push(...validateRouteFilePath(filePath));
271
+ issues.push(...validateRouteFilePath(srcDir, filePath));
270
272
  }
271
273
  return issues.toSorted((a, b) => a.filePath.localeCompare(b.filePath));
272
274
  };
@@ -278,7 +280,8 @@ var collectDuplicateNormalizedRouteKeyIssues = (srcDir) => {
278
280
  const syncSeg = syncMarkerSegment();
279
281
  for (const filePath of allRouteFiles) {
280
282
  const normalizedFilePath = normalizePath(path.resolve(filePath));
281
- if (normalizedFilePath.includes(apiSeg)) {
283
+ const srcMarkerPath = toSrcMarkerPath(srcDir, filePath);
284
+ if (srcMarkerPath.includes(apiSeg)) {
282
285
  const routeKey = resolveApiRouteKey({ srcDir, filePath });
283
286
  if (!routeKey) {
284
287
  continue;
@@ -287,7 +290,7 @@ var collectDuplicateNormalizedRouteKeyIssues = (srcDir) => {
287
290
  routeKeyKinds.set(routeKey, "api");
288
291
  continue;
289
292
  }
290
- if (normalizedFilePath.includes(syncSeg)) {
293
+ if (srcMarkerPath.includes(syncSeg)) {
291
294
  const routeKey = resolveSyncRouteKey({ srcDir, filePath });
292
295
  if (!routeKey) {
293
296
  continue;
@@ -4079,11 +4082,15 @@ import path15 from "path";
4079
4082
  import fs9 from "fs";
4080
4083
  import path12 from "path";
4081
4084
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "url";
4082
- import { ScriptKind as ScriptKind3 } from "typescript-7-api/ast";
4085
+ import {
4086
+ ScriptKind as ScriptKind3
4087
+ } from "typescript-7-api/ast";
4083
4088
  import {
4084
4089
  isIdentifier as isIdentifier8,
4085
4090
  isInterfaceDeclaration as isInterfaceDeclaration6,
4086
4091
  isPropertySignatureDeclaration as isPropertySignatureDeclaration4,
4092
+ isStringLiteral as isStringLiteral7,
4093
+ isTypeAliasDeclaration as isTypeAliasDeclaration6,
4087
4094
  isTypeLiteralNode as isTypeLiteralNode3
4088
4095
  } from "typescript-7-api/ast/is";
4089
4096
  import { ROOT_DIR as ROOT_DIR7, getGeneratedSocketTypesPath as getGeneratedSocketTypesPath6, getSrcDir as getSrcDir4 } from "@luckystack/core";
@@ -4156,23 +4163,22 @@ var isEmptyFile = (filePath) => {
4156
4163
  return false;
4157
4164
  }
4158
4165
  };
4159
- var isCommentOnlyFile = (filePath) => {
4166
+ var INJECTED_GUIDANCE_PATTERN = /^\/\/ Invalid (?:API|sync) filename: [^\r\n]*$/;
4167
+ var isInjectedGuidanceOnlyFile = (filePath) => {
4160
4168
  try {
4161
4169
  const content = fs9.readFileSync(filePath, "utf8");
4162
- const withoutBlockComments = content.replaceAll(/\/\*[\s\S]*?\*\//g, "");
4163
- const withoutLineComments = withoutBlockComments.replaceAll(/(^|\s)\/\/.*$/gm, "$1");
4164
- return withoutLineComments.trim().length === 0;
4170
+ return INJECTED_GUIDANCE_PATTERN.test(content.trim());
4165
4171
  } catch {
4166
4172
  return false;
4167
4173
  }
4168
4174
  };
4169
4175
  var isInApiFolder = (filePath) => {
4170
4176
  const normalized = filePath.replaceAll("\\", "/");
4171
- return normalized.includes(apiMarkerSegment()) && filePath.endsWith(".ts") && !isRouteTestFile(filePath);
4177
+ return normalized.includes(apiMarkerSegment()) && filePath.endsWith(".ts") && isRouteSurfaceFile(normalized);
4172
4178
  };
4173
4179
  var isInSyncFolder = (filePath) => {
4174
4180
  const normalized = filePath.replaceAll("\\", "/");
4175
- return normalized.includes(syncMarkerSegment()) && filePath.endsWith(".ts") && !isRouteTestFile(filePath);
4181
+ return normalized.includes(syncMarkerSegment()) && filePath.endsWith(".ts") && isRouteSurfaceFile(normalized);
4176
4182
  };
4177
4183
  var isPageFile = (filePath) => {
4178
4184
  const normalized = filePath.replaceAll("\\", "/");
@@ -4318,22 +4324,10 @@ var hasPairedFile = (filePath) => {
4318
4324
  if (!pairedPath) return false;
4319
4325
  return fs9.existsSync(pairedPath);
4320
4326
  };
4321
- var extractSyncPagePath2 = (filePath) => {
4322
- const normalized = filePath.replaceAll("\\", "/");
4323
- const match = /src\/(.+?)\/_sync\//.exec(normalized);
4324
- if (match?.[1]) return match[1];
4325
- if (/(?:^|\/)src\/_sync\//.test(normalized)) return "system";
4326
- return "";
4327
- };
4328
- var extractSyncName2 = (filePath) => {
4329
- const normalized = filePath.replaceAll("\\", "/");
4330
- const match = /_sync\/(.+)\.ts$/.exec(normalized);
4331
- if (!match) {
4332
- const basename = path12.basename(filePath, ".ts");
4333
- return basename.replace(/_server_v\d+$/, "").replace(/_client_v\d+$/, "");
4334
- }
4335
- return (match[1] ?? "").replace(/_server_v\d+$/, "").replace(/_client_v\d+$/, "");
4336
- };
4327
+ var toSrcRelativeOrSelf = (filePath) => computeSrcRelativePath(filePath) ?? filePath.replaceAll("\\", "/");
4328
+ var extractSyncPagePath2 = (filePath) => extractSyncPagePath(path12.resolve(filePath).replaceAll("\\", "/"));
4329
+ var extractSyncName2 = (filePath) => extractSyncName(toSrcRelativeOrSelf(filePath));
4330
+ var extractSyncVersion2 = (filePath) => extractSyncVersion(toSrcRelativeOrSelf(filePath));
4337
4331
  var extractClientInputFromFile = (filePath) => {
4338
4332
  try {
4339
4333
  const content = fs9.readFileSync(filePath, "utf8");
@@ -4364,43 +4358,32 @@ var extractClientInputFromFile = (filePath) => {
4364
4358
  return null;
4365
4359
  }
4366
4360
  };
4367
- var extractClientInputFromGeneratedTypes = (pagePath, syncName) => {
4361
+ var propertyKeyText = (name) => isIdentifier8(name) || isStringLiteral7(name) ? name.text : null;
4362
+ var findTypeLiteralMemberType = (typeNode, key) => {
4363
+ if (!typeNode || !isTypeLiteralNode3(typeNode)) return void 0;
4364
+ for (const member of typeNode.members) {
4365
+ if (isPropertySignatureDeclaration4(member) && propertyKeyText(member.name) === key) {
4366
+ return member.type;
4367
+ }
4368
+ }
4369
+ return void 0;
4370
+ };
4371
+ var extractClientInputFromGeneratedTypes = (pagePath, syncName, version) => {
4368
4372
  try {
4369
4373
  const generatedTypesPath = getGeneratedSocketTypesPath6();
4370
4374
  const content = fs9.readFileSync(generatedTypesPath, "utf8");
4371
- const escapeRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
4372
- const escapedPagePath = escapeRegex(pagePath);
4373
- const escapedSyncName = escapeRegex(syncName);
4374
- const pageBlockRegex = new RegExp(String.raw`'${escapedPagePath}'\s*:\s*\{([\s\S]*?)\n\s{2}\};`, "m");
4375
- const pageBlockMatch = content.match(pageBlockRegex);
4376
- if (!pageBlockMatch?.[1]) {
4377
- console.log(`[TemplateInjector] Could not find page block for ${pagePath}`);
4378
- return null;
4379
- }
4380
- const pageBlock = pageBlockMatch[1];
4381
- const syncEntryPattern = new RegExp(String.raw`'${escapedSyncName}':\s*\{\s*clientInput:\s*`);
4382
- const match = pageBlock.match(syncEntryPattern);
4383
- if (!match || typeof match.index !== "number") {
4384
- console.log(`[TemplateInjector] Could not find sync entry for ${pagePath}/${syncName}`);
4375
+ const sourceFile = parseSourceText(path12.basename(generatedTypesPath), content, ScriptKind3.TS);
4376
+ const syncTypeMap = sourceFile.statements.find(
4377
+ (statement) => isTypeAliasDeclaration6(statement) && statement.name.text === "_ProjectSyncTypeMap"
4378
+ );
4379
+ const pageType = findTypeLiteralMemberType(syncTypeMap?.type, pagePath);
4380
+ const versionType = findTypeLiteralMemberType(findTypeLiteralMemberType(pageType, syncName), version);
4381
+ const clientInputType = findTypeLiteralMemberType(versionType, "clientInput");
4382
+ if (!clientInputType) {
4383
+ console.log(`[TemplateInjector] Could not find sync entry for ${pagePath}/${syncName}/${version}`);
4385
4384
  return null;
4386
4385
  }
4387
- const pageStart = content.indexOf(pageBlock);
4388
- const globalMatchIndex = pageStart + match.index;
4389
- const searchStart = globalMatchIndex + match[0].length;
4390
- const braceStart = content.indexOf("{", searchStart - 1);
4391
- if (braceStart === -1) return null;
4392
- let depth = 0;
4393
- let endIndex = -1;
4394
- for (let i = braceStart; i < content.length; i++) {
4395
- if (content[i] === "{") depth++;
4396
- else if (content[i] === "}") depth--;
4397
- if (depth === 0) {
4398
- endIndex = i;
4399
- break;
4400
- }
4401
- }
4402
- if (endIndex === -1) return null;
4403
- const extracted = content.slice(braceStart, endIndex + 1);
4386
+ const extracted = content.slice(clientInputType.getStart(sourceFile), clientInputType.getEnd());
4404
4387
  console.log(`[TemplateInjector] Extracted clientInput types: ${extracted}`);
4405
4388
  return extracted;
4406
4389
  } catch (error) {
@@ -4458,10 +4441,16 @@ var checkPagePlacement = (filePath, srcRelativePath) => {
4458
4441
  }
4459
4442
  return null;
4460
4443
  };
4444
+ var TEMPLATE_LINT_HEADER = /^(?:\/\* eslint-disable(?: unicorn\/no-abusive-eslint-disable)? \*\/[^\S\r\n]*\r?\n)+(?:[^\S\r\n]*\r?\n)?/;
4445
+ var BYTE_ORDER_MARK = String.fromCodePoint(65279);
4446
+ var stripTemplateLintHeader = (content) => {
4447
+ const unmarked = content.startsWith(BYTE_ORDER_MARK) ? content.slice(1) : content;
4448
+ return TEMPLATE_LINT_HEADER.test(unmarked) ? unmarked.replace(TEMPLATE_LINT_HEADER, "") : content;
4449
+ };
4461
4450
  var applyTemplatePlaceholders = (content, filePath, fileKind) => {
4462
4451
  const relPath = calculateRelativePath(filePath);
4463
4452
  const pragmaPattern = /\/\/\s*@ts-(?:ignore|expect-error).*\r?\n(.*)\{\{REL_PATH\}\}/g;
4464
- let result = content.replaceAll(pragmaPattern, (_, prefix) => `${prefix}${relPath}`);
4453
+ let result = stripTemplateLintHeader(content).replaceAll(pragmaPattern, (_, prefix) => `${prefix}${relPath}`);
4465
4454
  result = result.replaceAll("{{REL_PATH}}", () => relPath);
4466
4455
  if (fileKind === "sync_client" || fileKind === "sync_server") {
4467
4456
  const pagePath = extractSyncPagePath2(filePath);
@@ -4518,7 +4507,7 @@ var shouldInjectTemplate = (filePath, options = {}) => {
4518
4507
  return false;
4519
4508
  }
4520
4509
  if (isEmptyFile(filePath)) return true;
4521
- if (options.isNewFile) return isCommentOnlyFile(filePath);
4510
+ if (options.isNewFile) return isInjectedGuidanceOnlyFile(filePath);
4522
4511
  return false;
4523
4512
  };
4524
4513
  var ensureImportedTypes = (content, clientFilePath) => {
@@ -4685,11 +4674,12 @@ var injectServerTemplateWithClientInput = async (serverFilePath, clientInputType
4685
4674
  try {
4686
4675
  await ensureConsumerTemplateConfigLoaded();
4687
4676
  const relPath = calculateRelativePath(serverFilePath);
4688
- let content = resolveTemplateContent("sync_server");
4689
- if (content === null) {
4677
+ const rawContent = resolveTemplateContent("sync_server");
4678
+ if (rawContent === null) {
4690
4679
  console.error(`[TemplateInjector] No sync_server template content available for: ${serverFilePath}`);
4691
4680
  return false;
4692
4681
  }
4682
+ let content = stripTemplateLintHeader(rawContent);
4693
4683
  const pragmaPattern = /\/\/\s*@ts-(?:ignore|expect-error).*\r?\n(.*)\{\{REL_PATH\}\}/g;
4694
4684
  content = content.replaceAll(pragmaPattern, (_, prefix) => {
4695
4685
  return `${prefix}${relPath}`;
@@ -4712,6 +4702,7 @@ var injectServerTemplateWithClientInput = async (serverFilePath, clientInputType
4712
4702
  import fs10 from "fs";
4713
4703
  import path13 from "path";
4714
4704
  import { ROOT_DIR as ROOT_DIR8, getServerFunctionDirs as getServerFunctionDirs2, getSharedDir, getSrcDir as getSrcDir5 } from "@luckystack/core";
4705
+ var srcDirFromRoot = () => path13.relative(ROOT_DIR8, getSrcDir5()).replaceAll("\\", "/");
4715
4706
  var SUPPORTED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"];
4716
4707
  var normalizePath4 = (value) => path13.resolve(value).replaceAll("\\", "/");
4717
4708
  var isSupportedScriptFile = (value) => {
@@ -4829,9 +4820,12 @@ var resolveImportToFile = (importerPath, specifier) => {
4829
4820
  const relativeBase = path13.resolve(path13.dirname(importerPath), specifier);
4830
4821
  return tryResolveWithExtensions(relativeBase);
4831
4822
  }
4832
- if (specifier.startsWith("src/") || specifier.startsWith("@/")) {
4833
- const sourcePath = specifier.startsWith("@/") ? specifier.slice(2) : specifier.slice(4);
4834
- return tryResolveWithExtensions(path13.join(getSrcDir5(), sourcePath));
4823
+ const srcFromRoot = srcDirFromRoot();
4824
+ const srcAliases = ["src/", "@/"];
4825
+ if (srcFromRoot && !srcFromRoot.startsWith("..")) srcAliases.push(`${srcFromRoot}/`);
4826
+ const srcAlias = srcAliases.find((alias) => specifier.startsWith(alias));
4827
+ if (srcAlias) {
4828
+ return tryResolveWithExtensions(path13.join(getSrcDir5(), specifier.slice(srcAlias.length)));
4835
4829
  }
4836
4830
  if (specifier.startsWith("shared/")) {
4837
4831
  return tryResolveWithExtensions(path13.join(getSharedDir(), specifier.slice(7)));
@@ -4839,7 +4833,10 @@ var resolveImportToFile = (importerPath, specifier) => {
4839
4833
  return null;
4840
4834
  };
4841
4835
  var isRouteFile = (filePath) => {
4842
- return filePath.includes("/src/") && (filePath.includes("/_api/") || filePath.includes("/_sync/"));
4836
+ const relative = path13.relative(getSrcDir5(), filePath).replaceAll("\\", "/");
4837
+ if (relative.startsWith("..") || path13.isAbsolute(relative)) return false;
4838
+ const markerPath = `/${relative}`;
4839
+ return (markerPath.includes(apiMarkerSegment()) || markerPath.includes(syncMarkerSegment())) && isRouteSurfaceFile(markerPath);
4843
4840
  };
4844
4841
  var collectScopedFiles = () => {
4845
4842
  const now = Date.now();
@@ -4932,26 +4929,36 @@ var runPrismaGenerate = async () => tryCatch2(
4932
4929
  );
4933
4930
 
4934
4931
  // src/hotReload.ts
4935
- import { tryCatch as tryCatch3, getProjectConfig, getLocaleReloader, isProductionRuntime } from "@luckystack/core";
4932
+ import {
4933
+ tryCatch as tryCatch3,
4934
+ getProjectConfig,
4935
+ getLocaleReloader,
4936
+ isProductionRuntime,
4937
+ getGeneratedSocketTypesPath as getGeneratedSocketTypesPath7,
4938
+ getGeneratedApiSchemasPath as getGeneratedApiSchemasPath2,
4939
+ getGeneratedApiDocsPath as getGeneratedApiDocsPath2
4940
+ } from "@luckystack/core";
4936
4941
  var normalizeFsPath = (value) => path15.resolve(value).replaceAll("\\", "/");
4937
4942
  var isGeneratedPath = (normalizedPath) => {
4938
- return normalizedPath.includes("apiTypes.generated.ts") || normalizedPath.includes("apiDocs.generated.json");
4943
+ if (normalizedPath.includes(".generated.")) return true;
4944
+ return [getGeneratedSocketTypesPath7(), getGeneratedApiSchemasPath2(), getGeneratedApiDocsPath2()].some((generatedPath) => normalizeFsPath(generatedPath) === normalizedPath);
4939
4945
  };
4940
4946
  var createTypeMapQueue = () => {
4941
4947
  const queue = { pending: false, running: false };
4942
4948
  const run = () => {
4943
4949
  queue.running = true;
4944
4950
  queue.pending = false;
4945
- const startedAt = Date.now();
4946
4951
  setImmediate(() => {
4947
4952
  void (async () => {
4953
+ const startedAt = Date.now();
4948
4954
  const [err] = await tryCatch3(() => {
4949
4955
  generateTypeMapFile({ quiet: true });
4950
4956
  });
4957
+ const durationMs = Date.now() - startedAt;
4951
4958
  if (err) {
4952
- console.log(`[HotReload] type map regeneration failed: ${String(err)}`, "red");
4959
+ console.log(`[HotReload] type map regeneration failed after ${durationMs}ms: ${String(err)}`, "red");
4953
4960
  } else {
4954
- console.log(`[HotReload] type map ready in ${Date.now() - startedAt}ms`, "green");
4961
+ console.log(`[HotReload] type map regenerated in ${durationMs}ms (the server answered no requests meanwhile)`, "green");
4955
4962
  }
4956
4963
  queue.running = false;
4957
4964
  if (queue.pending) {
@@ -4973,7 +4980,12 @@ var createPendingChangeSets = () => ({
4973
4980
  apiUpserts: /* @__PURE__ */ new Set(),
4974
4981
  apiDeletes: /* @__PURE__ */ new Set(),
4975
4982
  syncUpserts: /* @__PURE__ */ new Set(),
4976
- syncDeletes: /* @__PURE__ */ new Set()
4983
+ syncDeletes: /* @__PURE__ */ new Set(),
4984
+ //? Deleted sync SERVER files whose paired client still needs its types
4985
+ //? inlined. A set the sync task drains, not a closure handed to the
4986
+ //? scheduler: any later sync event in the debounce window replaces the
4987
+ //? scheduled task, and a closure would be dropped with it.
4988
+ deletedSyncServers: /* @__PURE__ */ new Set()
4977
4989
  });
4978
4990
  var buildPathSegments = () => {
4979
4991
  const rules2 = getRoutingRules();
@@ -4994,10 +5006,11 @@ var buildPathSegments = () => {
4994
5006
  };
4995
5007
  };
4996
5008
  var makeIsInServerFunctionsDir = (segments) => (normalizedPath) => segments.serverFunctionsSegments.some((seg) => normalizedPath.includes(seg));
4997
- var makeIsRouteDependencyFile = (segments) => (normalizedPath) => {
5009
+ var makeIsRouteFile = (segments) => (normalizedPath) => (normalizedPath.includes(segments.apiMarkerSlash) || normalizedPath.includes(segments.syncMarkerSlash)) && isRouteSurfaceFile(normalizedPath);
5010
+ var makeIsRouteDependencyFile = (segments, isRouteFile2) => (normalizedPath) => {
4998
5011
  if (!normalizedPath.endsWith(".ts") && !normalizedPath.endsWith(".tsx")) return false;
4999
5012
  if (!normalizedPath.includes(segments.srcSegment)) return false;
5000
- if (normalizedPath.includes(segments.apiMarkerSlash) || normalizedPath.includes(segments.syncMarkerSlash)) return false;
5013
+ if (isRouteFile2(normalizedPath)) return false;
5001
5014
  if (isGeneratedPath(normalizedPath)) return false;
5002
5015
  return true;
5003
5016
  };
@@ -5006,10 +5019,10 @@ var makeIsSharedDependencyFile = (segments, isInServerFunctionsDir) => (normaliz
5006
5019
  if (normalizedPath.includes(segments.sharedSegment)) return true;
5007
5020
  return isInServerFunctionsDir(normalizedPath);
5008
5021
  };
5009
- var makeIsTypeMapRelevantFile = (segments) => (normalizedPath) => {
5022
+ var makeIsTypeMapRelevantFile = (segments, isRouteFile2) => (normalizedPath) => {
5010
5023
  if (isGeneratedPath(normalizedPath)) return false;
5011
5024
  if (!(normalizedPath.endsWith(".ts") || normalizedPath.endsWith(".tsx"))) return false;
5012
- if (normalizedPath.includes(segments.apiMarkerSlash) || normalizedPath.includes(segments.syncMarkerSlash)) return false;
5025
+ if (isRouteFile2(normalizedPath)) return false;
5013
5026
  return normalizedPath.includes(segments.srcSegment) || normalizedPath.endsWith("/config.ts") || normalizedPath.includes(segments.sharedSegment);
5014
5027
  };
5015
5028
  var makeIsLocaleFile = (segments) => (normalizedPath) => normalizedPath.includes(segments.localesSegment) && normalizedPath.endsWith(".json");
@@ -5047,9 +5060,10 @@ var setupWatchers = () => {
5047
5060
  if (!isDevMode) return;
5048
5061
  const segments = buildPathSegments();
5049
5062
  const isInServerFunctionsDir = makeIsInServerFunctionsDir(segments);
5050
- const isRouteDependencyFile = makeIsRouteDependencyFile(segments);
5063
+ const isRouteFile2 = makeIsRouteFile(segments);
5064
+ const isRouteDependencyFile = makeIsRouteDependencyFile(segments, isRouteFile2);
5051
5065
  const isSharedDependencyFile = makeIsSharedDependencyFile(segments, isInServerFunctionsDir);
5052
- const isTypeMapRelevantFile = makeIsTypeMapRelevantFile(segments);
5066
+ const isTypeMapRelevantFile = makeIsTypeMapRelevantFile(segments, isRouteFile2);
5053
5067
  const isLocaleFile = makeIsLocaleFile(segments);
5054
5068
  const typeMap = createTypeMapQueue();
5055
5069
  const pending = createPendingChangeSets();
@@ -5068,11 +5082,31 @@ var setupWatchers = () => {
5068
5082
  console.log(`[HotReload] API removed: ${deletePath}`, "yellow");
5069
5083
  }
5070
5084
  for (const upsertPath of upsertPaths) {
5085
+ const startedAt = Date.now();
5071
5086
  await upsertApiFromFile(upsertPath);
5072
- console.log(`[HotReload] API reloaded: ${upsertPath}`, "green");
5087
+ console.log(`[HotReload] API reloaded in ${Date.now() - startedAt}ms: ${upsertPath}`, "green");
5088
+ }
5089
+ };
5090
+ const migrateClientsOfDeletedSyncServers = async () => {
5091
+ const serverPaths = [...pending.deletedSyncServers];
5092
+ pending.deletedSyncServers.clear();
5093
+ for (const serverPath of serverPaths) {
5094
+ if (fs12.existsSync(serverPath)) continue;
5095
+ const clientPath = getPairedSyncFile(serverPath);
5096
+ if (!clientPath || !fs12.existsSync(clientPath)) continue;
5097
+ const clientInputTypes = extractClientInputFromGeneratedTypes(
5098
+ extractSyncPagePath2(serverPath),
5099
+ extractSyncName2(serverPath),
5100
+ extractSyncVersion2(serverPath)
5101
+ );
5102
+ await updateClientFileForDeletedServer(
5103
+ clientPath,
5104
+ clientInputTypes ?? "{\n // Types were in _server.ts - please add them here\n }"
5105
+ );
5073
5106
  }
5074
5107
  };
5075
5108
  const processPendingSyncChanges = async ({ regenerateTypeMap = false } = {}) => {
5109
+ await migrateClientsOfDeletedSyncServers();
5076
5110
  const deletePaths = [...pending.syncDeletes];
5077
5111
  const upsertPaths = [...pending.syncUpserts];
5078
5112
  pending.syncDeletes.clear();
@@ -5086,16 +5120,28 @@ var setupWatchers = () => {
5086
5120
  console.log(`[HotReload] Sync removed: ${deletePath}`, "yellow");
5087
5121
  }
5088
5122
  for (const upsertPath of upsertPaths) {
5123
+ const startedAt = Date.now();
5089
5124
  await upsertSyncFromFile(upsertPath);
5090
- console.log(`[HotReload] Sync reloaded: ${upsertPath}`, "green");
5125
+ console.log(`[HotReload] Sync reloaded in ${Date.now() - startedAt}ms: ${upsertPath}`, "green");
5091
5126
  }
5092
5127
  };
5093
5128
  const enqueueAffectedRoutesFromDependency = (changedPath) => {
5094
5129
  const affectedRoutes = findDependentRouteFiles(changedPath);
5095
5130
  if (affectedRoutes.size === 0) {
5096
5131
  console.log(`[HotReload] No API/Sync routes depend on: ${changedPath}`, "yellow");
5097
- return;
5132
+ return false;
5133
+ }
5134
+ if (requestDevServerRestart(changedPath)) {
5135
+ console.log(
5136
+ `[HotReload] Dependency changed: ${changedPath} -> restarting the dev server (${affectedRoutes.size} route(s) import it)`,
5137
+ "blue"
5138
+ );
5139
+ return true;
5098
5140
  }
5141
+ console.log(
5142
+ `[HotReload] ${changedPath} changed, but routes that import it keep running its old code until you restart the dev server (the supervised \`npm run server\` restarts automatically)`,
5143
+ "yellow"
5144
+ );
5099
5145
  let queuedApiCount = 0;
5100
5146
  let queuedSyncCount = 0;
5101
5147
  for (const routePath of affectedRoutes) {
@@ -5123,6 +5169,7 @@ var setupWatchers = () => {
5123
5169
  await processPendingSyncChanges();
5124
5170
  });
5125
5171
  }
5172
+ return false;
5126
5173
  };
5127
5174
  const handleAdd = async (filePath) => {
5128
5175
  const normalizedPath = normalizeFsPath(filePath);
@@ -5162,7 +5209,8 @@ var setupWatchers = () => {
5162
5209
  return;
5163
5210
  }
5164
5211
  }
5165
- if (normalizedPath.includes(segments.apiMarkerNoLead)) {
5212
+ const isRoute = isRouteFile2(normalizedPath);
5213
+ if (isRoute && normalizedPath.includes(segments.apiMarkerNoLead)) {
5166
5214
  pending.apiDeletes.delete(normalizedPath);
5167
5215
  pending.apiUpserts.add(normalizedPath);
5168
5216
  scheduleReload("api", async () => {
@@ -5170,7 +5218,7 @@ var setupWatchers = () => {
5170
5218
  });
5171
5219
  return;
5172
5220
  }
5173
- if (normalizedPath.includes(segments.syncMarkerNoLead)) {
5221
+ if (isRoute && normalizedPath.includes(segments.syncMarkerNoLead)) {
5174
5222
  pending.syncDeletes.delete(normalizedPath);
5175
5223
  pending.syncUpserts.add(normalizedPath);
5176
5224
  scheduleReload("sync", async () => {
@@ -5212,11 +5260,11 @@ var setupWatchers = () => {
5212
5260
  return;
5213
5261
  }
5214
5262
  if (isRouteDependencyFile(normalizedPath)) {
5263
+ if (enqueueAffectedRoutesFromDependency(normalizedPath)) return;
5215
5264
  scheduleReload("typemap", () => {
5216
5265
  console.log(`[HotReload] Route dependency changed, scheduling type map regeneration`, "blue");
5217
5266
  typeMap.request();
5218
5267
  });
5219
- enqueueAffectedRoutesFromDependency(normalizedPath);
5220
5268
  return;
5221
5269
  }
5222
5270
  if (isTypeMapRelevantFile(normalizedPath)) {
@@ -5250,13 +5298,13 @@ var setupWatchers = () => {
5250
5298
  const handleFunctionChange = (changedPath) => {
5251
5299
  const normalizedPath = normalizeFsPath(changedPath);
5252
5300
  invalidateGraphForFile(normalizedPath);
5301
+ if (isSharedDependencyFile(normalizedPath) && enqueueAffectedRoutesFromDependency(normalizedPath)) {
5302
+ return;
5303
+ }
5253
5304
  scheduleReload("functions", async () => {
5254
5305
  typeMap.request();
5255
5306
  await initializeFunctions();
5256
5307
  });
5257
- if (isSharedDependencyFile(normalizedPath)) {
5258
- enqueueAffectedRoutesFromDependency(normalizedPath);
5259
- }
5260
5308
  };
5261
5309
  const handleDelete = (filePath) => {
5262
5310
  const normalizedPath = normalizeFsPath(filePath);
@@ -5271,11 +5319,11 @@ var setupWatchers = () => {
5271
5319
  return;
5272
5320
  }
5273
5321
  if (isRouteDependencyFile(normalizedPath)) {
5322
+ if (enqueueAffectedRoutesFromDependency(normalizedPath)) return;
5274
5323
  scheduleReload("typemap", () => {
5275
5324
  console.log(`[HotReload] Route dependency deleted, scheduling type map regeneration`, "blue");
5276
5325
  typeMap.request();
5277
5326
  });
5278
- enqueueAffectedRoutesFromDependency(normalizedPath);
5279
5327
  return;
5280
5328
  }
5281
5329
  if (isTypeMapRelevantFile(normalizedPath)) {
@@ -5301,19 +5349,10 @@ var setupWatchers = () => {
5301
5349
  });
5302
5350
  pending.syncUpserts.delete(normalizedPath);
5303
5351
  pending.syncDeletes.add(normalizedPath);
5352
+ if (isSyncServerFile(normalizedPath)) {
5353
+ pending.deletedSyncServers.add(normalizedPath);
5354
+ }
5304
5355
  scheduleReload("sync", async () => {
5305
- if (isSyncServerFile(normalizedPath)) {
5306
- const clientPath = getPairedSyncFile(normalizedPath);
5307
- if (clientPath && fs12.existsSync(clientPath)) {
5308
- const pagePath = extractSyncPagePath2(normalizedPath);
5309
- const syncName = extractSyncName2(normalizedPath);
5310
- const clientInputTypes = extractClientInputFromGeneratedTypes(pagePath, syncName);
5311
- await updateClientFileForDeletedServer(
5312
- clientPath,
5313
- clientInputTypes ?? "{\n // Types were in _server.ts - please add them here\n }"
5314
- );
5315
- }
5316
- }
5317
5356
  await processPendingSyncChanges();
5318
5357
  });
5319
5358
  }