@luckystack/devkit 0.1.9 → 0.2.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/CHANGELOG.md +14 -14
- package/CLAUDE.md +125 -111
- package/LICENSE +21 -21
- package/README.md +44 -44
- package/dist/chunk-YYWCJ7VZ.js +170 -0
- package/dist/chunk-YYWCJ7VZ.js.map +1 -0
- package/dist/cli/validateDeploy.js +3 -168
- package/dist/cli/validateDeploy.js.map +1 -1
- package/dist/index.js +719 -599
- package/dist/index.js.map +1 -1
- package/dist/supervisor.js +187 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/templates/api.template.ts +54 -54
- package/dist/templates/sync_client.template.ts +40 -40
- package/dist/templates/sync_client_paired.template.ts +38 -38
- package/dist/templates/sync_client_standalone.template.ts +36 -36
- package/dist/templates/sync_server.template.ts +64 -64
- package/docs/supervisor.md +292 -292
- package/docs/template-customization.md +136 -136
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateDeploy
|
|
3
|
+
} from "./chunk-YYWCJ7VZ.js";
|
|
4
|
+
|
|
1
5
|
// src/typeMap/discovery.ts
|
|
2
6
|
import fs from "fs";
|
|
3
7
|
import path from "path";
|
|
@@ -68,7 +72,15 @@ var toForwardSlashRelative = (absolute) => {
|
|
|
68
72
|
const rel = path.relative(ROOT_DIR, absolute);
|
|
69
73
|
return rel.replaceAll("\\", "/");
|
|
70
74
|
};
|
|
71
|
-
var walkFiles = (dir, matcher, results = []) => {
|
|
75
|
+
var walkFiles = (dir, matcher, results = [], visited = /* @__PURE__ */ new Set()) => {
|
|
76
|
+
let realDir;
|
|
77
|
+
try {
|
|
78
|
+
realDir = fs.realpathSync(dir);
|
|
79
|
+
} catch {
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
if (visited.has(realDir)) return results;
|
|
83
|
+
visited.add(realDir);
|
|
72
84
|
const { ignore } = getRoutingRules();
|
|
73
85
|
try {
|
|
74
86
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -76,9 +88,14 @@ var walkFiles = (dir, matcher, results = []) => {
|
|
|
76
88
|
const fullPath = path.join(dir, entry.name);
|
|
77
89
|
const relativePath = toForwardSlashRelative(fullPath);
|
|
78
90
|
if (ignore(relativePath)) continue;
|
|
79
|
-
if (entry.isDirectory()) {
|
|
91
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
80
92
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
81
|
-
|
|
93
|
+
try {
|
|
94
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
95
|
+
walkFiles(fullPath, matcher, results, visited);
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
82
99
|
continue;
|
|
83
100
|
}
|
|
84
101
|
if (entry.isFile() && matcher(fullPath, entry.name)) {
|
|
@@ -93,27 +110,28 @@ var walkFiles = (dir, matcher, results = []) => {
|
|
|
93
110
|
var findAllApiFiles = (srcDir) => {
|
|
94
111
|
const apiSegment = apiMarkerSegment();
|
|
95
112
|
return walkFiles(srcDir, (fullPath, entryName) => {
|
|
96
|
-
const normalized = fullPath.
|
|
113
|
+
const normalized = fullPath.replaceAll("\\", "/");
|
|
97
114
|
return isApiFileName(entryName) && normalized.includes(apiSegment);
|
|
98
115
|
});
|
|
99
116
|
};
|
|
100
117
|
var findAllSyncServerFiles = (srcDir) => {
|
|
101
118
|
const syncSegment = syncMarkerSegment();
|
|
102
119
|
return walkFiles(srcDir, (fullPath, entryName) => {
|
|
103
|
-
const normalized = fullPath.
|
|
120
|
+
const normalized = fullPath.replaceAll("\\", "/");
|
|
104
121
|
return isSyncServerFileName(entryName) && normalized.includes(syncSegment);
|
|
105
122
|
});
|
|
106
123
|
};
|
|
107
124
|
var findAllSyncClientFiles = (srcDir) => {
|
|
108
125
|
const syncSegment = syncMarkerSegment();
|
|
109
126
|
return walkFiles(srcDir, (fullPath, entryName) => {
|
|
110
|
-
const normalized = fullPath.
|
|
127
|
+
const normalized = fullPath.replaceAll("\\", "/");
|
|
111
128
|
return isSyncClientFileName(entryName) && normalized.includes(syncSegment);
|
|
112
129
|
});
|
|
113
130
|
};
|
|
114
131
|
|
|
115
132
|
// src/typeMap/routeMeta.ts
|
|
116
133
|
import path2 from "path";
|
|
134
|
+
import { getSrcDir } from "@luckystack/core";
|
|
117
135
|
|
|
118
136
|
// src/routeConventions.ts
|
|
119
137
|
var API_VERSION_TOKEN_REGEX = /_v(\d+)$/;
|
|
@@ -139,71 +157,76 @@ var stripVersionSuffix = (name) => {
|
|
|
139
157
|
return name.replace(VERSION_SUFFIX_REGEX, "");
|
|
140
158
|
};
|
|
141
159
|
var extractVersionFromName = (name) => {
|
|
142
|
-
const match =
|
|
160
|
+
const match = VERSION_SUFFIX_REGEX.exec(name);
|
|
143
161
|
if (!match) return null;
|
|
144
162
|
return `v${match[1]}`;
|
|
145
163
|
};
|
|
146
164
|
var extractPagePath = (filePath) => {
|
|
147
|
-
const normalized = filePath.
|
|
148
|
-
const
|
|
165
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
166
|
+
const srcDirNormalized = getSrcDir().replaceAll("\\", "/");
|
|
167
|
+
const rel = path2.posix.relative(srcDirNormalized, normalized);
|
|
168
|
+
if (rel.startsWith("..")) {
|
|
169
|
+
throw new Error(`[routeMeta] file is outside srcDir \u2014 cannot extract page path: ${filePath}`);
|
|
170
|
+
}
|
|
171
|
+
const match = /^(?:(.+?)\/)_api\//.exec(rel);
|
|
149
172
|
if (match) {
|
|
150
|
-
return match[1]
|
|
173
|
+
return match[1] ?? "system";
|
|
151
174
|
}
|
|
152
|
-
if (
|
|
175
|
+
if (rel.startsWith("_api/")) {
|
|
153
176
|
return "system";
|
|
154
177
|
}
|
|
155
178
|
return "";
|
|
156
179
|
};
|
|
157
180
|
var extractApiName = (filePath) => {
|
|
158
|
-
const normalized = filePath.
|
|
159
|
-
const match =
|
|
181
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
182
|
+
const match = /_api\/(.+)\.ts$/.exec(normalized);
|
|
160
183
|
const rawName = match?.[1] ?? path2.basename(filePath, ".ts");
|
|
161
184
|
return stripVersionSuffix(rawName);
|
|
162
185
|
};
|
|
163
186
|
var extractApiVersion = (filePath) => {
|
|
164
|
-
const normalized = filePath.
|
|
165
|
-
const match =
|
|
187
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
188
|
+
const match = /_api\/(.+)\.ts$/.exec(normalized);
|
|
166
189
|
const rawName = match?.[1] ?? path2.basename(filePath, ".ts");
|
|
167
|
-
return extractVersionFromName(rawName)
|
|
190
|
+
return extractVersionFromName(rawName) ?? "v1";
|
|
168
191
|
};
|
|
169
192
|
var extractSyncPagePath = (filePath) => {
|
|
170
|
-
const normalized = filePath.
|
|
171
|
-
const
|
|
193
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
194
|
+
const srcDirNormalized = getSrcDir().replaceAll("\\", "/");
|
|
195
|
+
const rel = path2.posix.relative(srcDirNormalized, normalized);
|
|
196
|
+
const match = /^(?:(.+?)\/)_sync\//.exec(rel);
|
|
172
197
|
if (match) {
|
|
173
|
-
return match[1]
|
|
198
|
+
return match[1] ?? "system";
|
|
174
199
|
}
|
|
175
|
-
if (
|
|
176
|
-
return "
|
|
200
|
+
if (rel.startsWith("_sync/")) {
|
|
201
|
+
return "system";
|
|
177
202
|
}
|
|
178
203
|
return "";
|
|
179
204
|
};
|
|
180
205
|
var extractSyncName = (filePath) => {
|
|
181
|
-
const normalized = filePath.
|
|
182
|
-
const match =
|
|
206
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
207
|
+
const match = /_sync\/(.+)\.ts$/.exec(normalized);
|
|
183
208
|
if (!match) {
|
|
184
209
|
const basename = path2.basename(filePath, ".ts");
|
|
185
|
-
|
|
186
|
-
return rawName2;
|
|
210
|
+
return basename.replace(SYNC_VERSION_TOKEN_REGEX, "");
|
|
187
211
|
}
|
|
188
|
-
|
|
189
|
-
return rawName;
|
|
212
|
+
return (match[1] ?? "").replace(SYNC_VERSION_TOKEN_REGEX, "");
|
|
190
213
|
};
|
|
191
214
|
var extractSyncVersion = (filePath) => {
|
|
192
|
-
const normalized = filePath.
|
|
193
|
-
const match =
|
|
215
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
216
|
+
const match = /_sync\/(.+)\.ts$/.exec(normalized);
|
|
194
217
|
if (!match) {
|
|
195
218
|
const basename = path2.basename(filePath, ".ts");
|
|
196
|
-
const versionMatch2 =
|
|
219
|
+
const versionMatch2 = SYNC_VERSION_TOKEN_REGEX.exec(basename);
|
|
197
220
|
return versionMatch2 ? `v${versionMatch2[2] ?? "1"}` : "v1";
|
|
198
221
|
}
|
|
199
|
-
const versionMatch = (match[1] ?? "")
|
|
222
|
+
const versionMatch = SYNC_VERSION_TOKEN_REGEX.exec(match[1] ?? "");
|
|
200
223
|
return versionMatch ? `v${versionMatch[2] ?? "1"}` : "v1";
|
|
201
224
|
};
|
|
202
225
|
|
|
203
226
|
// src/typeMap/apiMeta.ts
|
|
204
227
|
import * as ts from "typescript";
|
|
205
228
|
import fs2 from "fs";
|
|
206
|
-
import { inferHttpMethod } from "@luckystack/core";
|
|
229
|
+
import { inferHttpMethod, tryCatchSync } from "@luckystack/core";
|
|
207
230
|
var findExportedConst = (sourceFile, name) => {
|
|
208
231
|
for (const statement of sourceFile.statements) {
|
|
209
232
|
if (!ts.isVariableStatement(statement)) continue;
|
|
@@ -223,22 +246,24 @@ var unwrapExpression = (node) => {
|
|
|
223
246
|
return current;
|
|
224
247
|
};
|
|
225
248
|
var extractHttpMethod = (filePath, apiName) => {
|
|
226
|
-
|
|
249
|
+
const [error, method] = tryCatchSync(() => {
|
|
227
250
|
const content = fs2.readFileSync(filePath, "utf8");
|
|
228
251
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
229
252
|
const decl = findExportedConst(sourceFile, "httpMethod");
|
|
230
253
|
const initializer = decl?.initializer ? unwrapExpression(decl.initializer) : void 0;
|
|
231
254
|
if (initializer && ts.isStringLiteral(initializer)) {
|
|
232
|
-
const
|
|
233
|
-
if (["GET", "POST", "PUT", "DELETE"].includes(
|
|
255
|
+
const candidate = initializer.text.toUpperCase();
|
|
256
|
+
if (["GET", "POST", "PUT", "DELETE"].includes(candidate)) return candidate;
|
|
234
257
|
}
|
|
235
|
-
|
|
258
|
+
return void 0;
|
|
259
|
+
});
|
|
260
|
+
if (error) {
|
|
236
261
|
console.error(`[TypeMapGenerator] Error extracting httpMethod from ${filePath}:`, error);
|
|
237
262
|
}
|
|
238
|
-
return inferHttpMethod(apiName);
|
|
263
|
+
return method ?? inferHttpMethod(apiName);
|
|
239
264
|
};
|
|
240
265
|
var extractRateLimit = (filePath) => {
|
|
241
|
-
|
|
266
|
+
const [error, rateLimit] = tryCatchSync(() => {
|
|
242
267
|
const content = fs2.readFileSync(filePath, "utf8");
|
|
243
268
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
244
269
|
const decl = findExportedConst(sourceFile, "rateLimit");
|
|
@@ -247,10 +272,12 @@ var extractRateLimit = (filePath) => {
|
|
|
247
272
|
if (initializer.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
248
273
|
if (ts.isNumericLiteral(initializer)) return Number(initializer.text);
|
|
249
274
|
}
|
|
250
|
-
|
|
275
|
+
return void 0;
|
|
276
|
+
});
|
|
277
|
+
if (error) {
|
|
251
278
|
console.error(`[TypeMapGenerator] Error extracting rateLimit from ${filePath}:`, error);
|
|
252
279
|
}
|
|
253
|
-
return void 0;
|
|
280
|
+
return rateLimit ?? void 0;
|
|
254
281
|
};
|
|
255
282
|
var readPrimitive = (rawNode) => {
|
|
256
283
|
const node = unwrapExpression(rawNode);
|
|
@@ -269,23 +296,23 @@ var parseAdditionalItem = (objectLiteral) => {
|
|
|
269
296
|
}
|
|
270
297
|
return item.key ? item : null;
|
|
271
298
|
};
|
|
299
|
+
var commentToString = (commentValue) => {
|
|
300
|
+
if (typeof commentValue === "string") return commentValue;
|
|
301
|
+
if (Array.isArray(commentValue)) {
|
|
302
|
+
return commentValue.map((part) => {
|
|
303
|
+
if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
|
|
304
|
+
return part.text;
|
|
305
|
+
}
|
|
306
|
+
return "";
|
|
307
|
+
}).join("");
|
|
308
|
+
}
|
|
309
|
+
return "";
|
|
310
|
+
};
|
|
272
311
|
var extractDocsMeta = (filePath) => {
|
|
273
|
-
|
|
312
|
+
const [error, docsMeta] = tryCatchSync(() => {
|
|
274
313
|
const content = fs2.readFileSync(filePath, "utf8");
|
|
275
314
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
276
315
|
const result = {};
|
|
277
|
-
const commentToString = (commentValue) => {
|
|
278
|
-
if (typeof commentValue === "string") return commentValue;
|
|
279
|
-
if (Array.isArray(commentValue)) {
|
|
280
|
-
return commentValue.map((part) => {
|
|
281
|
-
if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
|
|
282
|
-
return part.text;
|
|
283
|
-
}
|
|
284
|
-
return "";
|
|
285
|
-
}).join("");
|
|
286
|
-
}
|
|
287
|
-
return "";
|
|
288
|
-
};
|
|
289
316
|
const consumeTag = (tag) => {
|
|
290
317
|
if (tag.tagName.text !== "docs") return;
|
|
291
318
|
const commentText = commentToString(tag.comment).trim();
|
|
@@ -311,19 +338,21 @@ var extractDocsMeta = (filePath) => {
|
|
|
311
338
|
return void 0;
|
|
312
339
|
}
|
|
313
340
|
return result;
|
|
314
|
-
}
|
|
341
|
+
});
|
|
342
|
+
if (error) {
|
|
315
343
|
console.error(`[TypeMapGenerator] Error extracting @docs metadata from ${filePath}:`, error);
|
|
316
344
|
return void 0;
|
|
317
345
|
}
|
|
346
|
+
return docsMeta ?? void 0;
|
|
318
347
|
};
|
|
319
348
|
var extractAuth = (filePath) => {
|
|
320
|
-
|
|
349
|
+
const [, auth] = tryCatchSync(() => {
|
|
321
350
|
const content = fs2.readFileSync(filePath, "utf8");
|
|
322
351
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
323
352
|
const decl = findExportedConst(sourceFile, "auth");
|
|
324
353
|
const authInitializer = decl?.initializer ? unwrapExpression(decl.initializer) : void 0;
|
|
325
|
-
if (!authInitializer || !ts.isObjectLiteralExpression(authInitializer)) return { login:
|
|
326
|
-
let login =
|
|
354
|
+
if (!authInitializer || !ts.isObjectLiteralExpression(authInitializer)) return { login: false };
|
|
355
|
+
let login = false;
|
|
327
356
|
let additional;
|
|
328
357
|
for (const prop of authInitializer.properties) {
|
|
329
358
|
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;
|
|
@@ -341,9 +370,8 @@ var extractAuth = (filePath) => {
|
|
|
341
370
|
}
|
|
342
371
|
}
|
|
343
372
|
return additional && additional.length > 0 ? { login, additional } : { login };
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
return { login: true };
|
|
373
|
+
});
|
|
374
|
+
return auth ?? { login: false };
|
|
347
375
|
};
|
|
348
376
|
|
|
349
377
|
// src/typeMap/emitterArtifacts.ts
|
|
@@ -454,7 +482,7 @@ var convertTypeNode = (node) => {
|
|
|
454
482
|
}
|
|
455
483
|
return `${JSON.stringify(name)}: ${schema}`;
|
|
456
484
|
}).filter((entry) => entry !== null);
|
|
457
|
-
return `z.object({ ${entries.join(", ")} })`;
|
|
485
|
+
return `z.object({ ${entries.join(", ")} }).strict()`;
|
|
458
486
|
}
|
|
459
487
|
if (ts2.isParenthesizedTypeNode(node)) {
|
|
460
488
|
return convertTypeNode(node.type);
|
|
@@ -604,6 +632,37 @@ var validateGeneratedTypeIdentifiers = (content) => {
|
|
|
604
632
|
throw new Error(`[TypeMapGenerator] Generated type map has unresolved type identifiers: ${unknown.join(", ")}`);
|
|
605
633
|
}
|
|
606
634
|
};
|
|
635
|
+
var collectFallbacks = (typesByPage, syncTypesByPage) => {
|
|
636
|
+
const entries = [];
|
|
637
|
+
const flagField = (route, kind, field, value) => {
|
|
638
|
+
if (value === "{ }" || value === "{ status: string }") {
|
|
639
|
+
entries.push({ route, kind, field, fallback: value, reason: "default-fallback" });
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const zodSrc = typeTextToZodSource(value);
|
|
643
|
+
if (zodSrc?.includes("z.any()")) {
|
|
644
|
+
entries.push({ route, kind, field, fallback: value.slice(0, 80), reason: "zod-any-fallback" });
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
for (const [pagePath, apis] of typesByPage) {
|
|
648
|
+
for (const [apiKey, entry] of apis) {
|
|
649
|
+
const { name, version } = splitVersionedKey(apiKey);
|
|
650
|
+
const route = `${pagePath}/${name}@${version}`;
|
|
651
|
+
flagField(route, "api", "input", entry.input);
|
|
652
|
+
flagField(route, "api", "output", entry.output);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
for (const [pagePath, syncs] of syncTypesByPage) {
|
|
656
|
+
for (const [syncKey, entry] of syncs) {
|
|
657
|
+
const { name, version } = splitVersionedKey(syncKey);
|
|
658
|
+
const route = `${pagePath}/${name}@${version}`;
|
|
659
|
+
flagField(route, "sync", "clientInput", entry.clientInput);
|
|
660
|
+
flagField(route, "sync", "serverOutput", entry.serverOutput);
|
|
661
|
+
flagField(route, "sync", "clientOutput", entry.clientOutput);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return entries;
|
|
665
|
+
};
|
|
607
666
|
var buildTypeMapArtifacts = ({
|
|
608
667
|
typesByPage,
|
|
609
668
|
syncTypesByPage,
|
|
@@ -701,7 +760,7 @@ type _ProjectApiTypeMap = {
|
|
|
701
760
|
`;
|
|
702
761
|
content += ` input: ${indentStr(entry.input, " ")};
|
|
703
762
|
`;
|
|
704
|
-
content += ` output: ${indentStr(entry.output, " ")};
|
|
763
|
+
content += ` output: ${indentStr(entry.output, " ")} | { status: 'error'; errorCode: string; [key: string]: unknown };
|
|
705
764
|
`;
|
|
706
765
|
content += ` stream: ${indentStr(entry.stream, " ")};
|
|
707
766
|
`;
|
|
@@ -844,16 +903,21 @@ type _ProjectSyncTypeMap = {
|
|
|
844
903
|
clientOutput: entry.clientOutput,
|
|
845
904
|
serverStream: entry.serverStream,
|
|
846
905
|
clientStream: entry.clientStream,
|
|
847
|
-
|
|
906
|
+
//? `extractSyncPagePath` returns the `'system'` sentinel (NOT
|
|
907
|
+
//? `'root'`) for a src-root sync, matching the loader's runtime key
|
|
908
|
+
//? (`sync/system/<name>/<version>`) AND the wire name the typed
|
|
909
|
+
//? `syncRequest` sends. The old `pagePath === 'root'` branch is now
|
|
910
|
+
//? dead and would emit a non-matching path, so it is removed.
|
|
911
|
+
path: `sync/${pagePath}/${syncName}/${version}`,
|
|
848
912
|
...entry.meta ? { meta: entry.meta } : {}
|
|
849
913
|
});
|
|
850
914
|
content += ` '${version}': {
|
|
851
915
|
`;
|
|
852
916
|
content += ` clientInput: ${indentStr(entry.clientInput, " ")};
|
|
853
917
|
`;
|
|
854
|
-
content += ` serverOutput: ${indentStr(entry.serverOutput, " ")};
|
|
918
|
+
content += ` serverOutput: ${indentStr(entry.serverOutput, " ")} | { status: 'error'; errorCode: string; [key: string]: unknown };
|
|
855
919
|
`;
|
|
856
|
-
content += ` clientOutput: ${indentStr(entry.clientOutput, " ")};
|
|
920
|
+
content += ` clientOutput: ${indentStr(entry.clientOutput, " ")} | { status: 'error'; errorCode: string; [key: string]: unknown };
|
|
857
921
|
`;
|
|
858
922
|
content += ` serverStream: ${indentStr(entry.serverStream, " ")};
|
|
859
923
|
`;
|
|
@@ -897,7 +961,15 @@ declare module '@luckystack/core/typemap' {
|
|
|
897
961
|
`;
|
|
898
962
|
validateGeneratedTypeIdentifiers(content);
|
|
899
963
|
const schemasContent = buildSchemasContent({ typesByPage });
|
|
900
|
-
|
|
964
|
+
const fallbacks = collectFallbacks(typesByPage, syncTypesByPage);
|
|
965
|
+
const totalRoutes = [...typesByPage.values()].reduce((n, m) => n + m.size, 0) + [...syncTypesByPage.values()].reduce((n, m) => n + m.size, 0);
|
|
966
|
+
const diagnosticsData = {
|
|
967
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
968
|
+
totalRoutes,
|
|
969
|
+
fallbackCount: fallbacks.length,
|
|
970
|
+
fallbacks
|
|
971
|
+
};
|
|
972
|
+
return { content, docsData, schemasContent, diagnosticsData };
|
|
901
973
|
};
|
|
902
974
|
var buildSchemasContent = ({
|
|
903
975
|
typesByPage
|
|
@@ -954,7 +1026,8 @@ export const getApiInputSchema = (
|
|
|
954
1026
|
var writeTypeMapArtifacts = ({
|
|
955
1027
|
content,
|
|
956
1028
|
docsData,
|
|
957
|
-
schemasContent
|
|
1029
|
+
schemasContent,
|
|
1030
|
+
diagnosticsData
|
|
958
1031
|
}) => {
|
|
959
1032
|
try {
|
|
960
1033
|
const outputPath = getGeneratedSocketTypesPath();
|
|
@@ -978,6 +1051,17 @@ var writeTypeMapArtifacts = ({
|
|
|
978
1051
|
if (hasUpdatedDocs) {
|
|
979
1052
|
console.log("[TypeMapGenerator] Generated apiDocs.generated.json");
|
|
980
1053
|
}
|
|
1054
|
+
if (diagnosticsData !== void 0) {
|
|
1055
|
+
const diagnosticsPath = path3.join(path3.dirname(docsPath), "apiTypeDiagnostics.generated.json");
|
|
1056
|
+
const diagnosticsContent = JSON.stringify(diagnosticsData, null, 2);
|
|
1057
|
+
const hasUpdatedDiagnostics = writeFileIfChanged(diagnosticsPath, diagnosticsContent);
|
|
1058
|
+
if (hasUpdatedDiagnostics) {
|
|
1059
|
+
console.log("[TypeMapGenerator] Generated apiTypeDiagnostics.generated.json");
|
|
1060
|
+
}
|
|
1061
|
+
if (diagnosticsData.fallbackCount > 0) {
|
|
1062
|
+
console.warn(`[TypeMapGenerator] ${diagnosticsData.fallbackCount} route(s) have degraded type extraction (see apiTypeDiagnostics.generated.json)`);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
981
1065
|
} catch (error) {
|
|
982
1066
|
console.error("[TypeMapGenerator] Error writing type map or docs:", error);
|
|
983
1067
|
}
|
|
@@ -990,13 +1074,13 @@ import path5 from "path";
|
|
|
990
1074
|
// src/typeMap/tsProgram.ts
|
|
991
1075
|
import * as ts4 from "typescript";
|
|
992
1076
|
import path4 from "path";
|
|
993
|
-
import { ROOT_DIR as ROOT_DIR2 } from "@luckystack/core";
|
|
1077
|
+
import { ROOT_DIR as ROOT_DIR2, getGeneratedSocketTypesPath as getGeneratedSocketTypesPath2 } from "@luckystack/core";
|
|
994
1078
|
var cachedProgram = null;
|
|
995
1079
|
var getServerProgram = () => {
|
|
996
1080
|
if (cachedProgram) return cachedProgram;
|
|
997
|
-
const tsconfigPath = ts4.findConfigFile(ROOT_DIR2, ts4.sys.fileExists, "tsconfig.server.json");
|
|
1081
|
+
const tsconfigPath = ts4.findConfigFile(ROOT_DIR2, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.server.json");
|
|
998
1082
|
if (!tsconfigPath) throw new Error("[TypeProgram] tsconfig.server.json not found");
|
|
999
|
-
const { config } = ts4.readConfigFile(tsconfigPath, ts4.sys.readFile);
|
|
1083
|
+
const { config } = ts4.readConfigFile(tsconfigPath, ts4.sys.readFile.bind(ts4.sys));
|
|
1000
1084
|
const { options, fileNames } = ts4.parseJsonConfigFileContent(
|
|
1001
1085
|
config,
|
|
1002
1086
|
ts4.sys,
|
|
@@ -1032,8 +1116,8 @@ var SKIP_EXPANSION = /* @__PURE__ */ new Set([
|
|
|
1032
1116
|
"ReadonlyArray"
|
|
1033
1117
|
]);
|
|
1034
1118
|
var isJsonLikeType = (type, checker) => {
|
|
1035
|
-
const symbolName = type.symbol?.name
|
|
1036
|
-
const aliasName = type.aliasSymbol?.name
|
|
1119
|
+
const symbolName = type.symbol?.name ?? "";
|
|
1120
|
+
const aliasName = type.aliasSymbol?.name ?? "";
|
|
1037
1121
|
if (JSON_TYPE_NAMES.has(symbolName) || JSON_TYPE_NAMES.has(aliasName)) return true;
|
|
1038
1122
|
const rendered = checker.typeToString(type);
|
|
1039
1123
|
return /(\bPrisma\.)?(Input)?Json(Value|Object|Array)\b/.test(rendered);
|
|
@@ -1084,7 +1168,7 @@ var getLiteralTypeFromPropertySymbol = (symbol, checker, depth) => {
|
|
|
1084
1168
|
return null;
|
|
1085
1169
|
};
|
|
1086
1170
|
var normalizeImportPath = (targetFilePath) => {
|
|
1087
|
-
const fromDir = path4.
|
|
1171
|
+
const fromDir = path4.dirname(getGeneratedSocketTypesPath2());
|
|
1088
1172
|
const from = fromDir.replaceAll("\\", "/");
|
|
1089
1173
|
const to = targetFilePath.replaceAll("\\", "/");
|
|
1090
1174
|
const normalized = path4.posix.relative(from, to).replaceAll("\\", "/");
|
|
@@ -1177,7 +1261,7 @@ var expandTypeDetailed = (type, checker, depth = 0, state) => {
|
|
|
1177
1261
|
}
|
|
1178
1262
|
if (objectType.objectFlags & ts4.ObjectFlags.Reference) {
|
|
1179
1263
|
const refType = objectType;
|
|
1180
|
-
const targetName = refType.target
|
|
1264
|
+
const targetName = refType.target.symbol.name;
|
|
1181
1265
|
if (targetName === "Array" || targetName === "ReadonlyArray") {
|
|
1182
1266
|
const typeArgs = checker.getTypeArguments(refType);
|
|
1183
1267
|
const firstArg = typeArgs[0];
|
|
@@ -1194,7 +1278,7 @@ var expandTypeDetailed = (type, checker, depth = 0, state) => {
|
|
|
1194
1278
|
return { text: checker.typeToString(type), unresolvedSymbols: [] };
|
|
1195
1279
|
}
|
|
1196
1280
|
}
|
|
1197
|
-
const symbolName = type.symbol
|
|
1281
|
+
const symbolName = type.symbol.name || (type.aliasSymbol?.name ?? "");
|
|
1198
1282
|
if (SKIP_EXPANSION.has(symbolName)) {
|
|
1199
1283
|
return { text: checker.typeToString(type), unresolvedSymbols: [] };
|
|
1200
1284
|
}
|
|
@@ -1249,7 +1333,7 @@ var expandType = (type, checker, depth = 0) => {
|
|
|
1249
1333
|
};
|
|
1250
1334
|
|
|
1251
1335
|
// src/typeMap/extractors.ts
|
|
1252
|
-
import {
|
|
1336
|
+
import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath3 } from "@luckystack/core";
|
|
1253
1337
|
var TYPE_NAME_PATTERN = /\b[A-Z][A-Za-z0-9_]*\b/g;
|
|
1254
1338
|
var KNOWN_GLOBAL_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
1255
1339
|
"String",
|
|
@@ -1283,7 +1367,7 @@ var KNOWN_GLOBAL_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
|
1283
1367
|
"JsonPrimitive"
|
|
1284
1368
|
]);
|
|
1285
1369
|
var normalizeImportPath2 = (targetFilePath) => {
|
|
1286
|
-
const fromDir = path5.
|
|
1370
|
+
const fromDir = path5.dirname(getGeneratedSocketTypesPath3());
|
|
1287
1371
|
const from = fromDir.replaceAll("\\", "/");
|
|
1288
1372
|
const to = targetFilePath.replaceAll("\\", "/");
|
|
1289
1373
|
const normalized = path5.posix.relative(from, to).replaceAll("\\", "/");
|
|
@@ -1338,7 +1422,7 @@ var findInterface = (sourceFile, name) => {
|
|
|
1338
1422
|
};
|
|
1339
1423
|
var getInterfacePropertyType = (iface, propertyName, checker) => {
|
|
1340
1424
|
for (const member of iface.members) {
|
|
1341
|
-
if (ts5.isPropertySignature(member) &&
|
|
1425
|
+
if (ts5.isPropertySignature(member) && ts5.isIdentifier(member.name) && member.name.text === propertyName && member.type) {
|
|
1342
1426
|
return checker.getTypeFromTypeNode(member.type);
|
|
1343
1427
|
}
|
|
1344
1428
|
}
|
|
@@ -1422,7 +1506,10 @@ var getInputTypeDetailsFromFile = (filePath) => {
|
|
|
1422
1506
|
try {
|
|
1423
1507
|
const program = getServerProgram();
|
|
1424
1508
|
const sourceFile = program.getSourceFile(filePath);
|
|
1425
|
-
if (!sourceFile)
|
|
1509
|
+
if (!sourceFile) {
|
|
1510
|
+
console.warn(`[TypeMapGenerator] getSourceFile miss for ${filePath} \u2014 input types will default to { }`);
|
|
1511
|
+
return { text: DEFAULT, unresolvedSymbols: [] };
|
|
1512
|
+
}
|
|
1426
1513
|
const checker = program.getTypeChecker();
|
|
1427
1514
|
const iface = findInterface(sourceFile, "ApiParams");
|
|
1428
1515
|
if (!iface) return { text: DEFAULT, unresolvedSymbols: [] };
|
|
@@ -1561,10 +1648,10 @@ import path7 from "path";
|
|
|
1561
1648
|
// src/typeMap/typeContext.ts
|
|
1562
1649
|
import * as ts6 from "typescript";
|
|
1563
1650
|
import path6 from "path";
|
|
1564
|
-
import { getGeneratedSocketTypesPath as
|
|
1651
|
+
import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath4 } from "@luckystack/core";
|
|
1565
1652
|
var toGeneratedImportPath = (source, filePath) => {
|
|
1566
1653
|
if (!source.startsWith(".")) return source;
|
|
1567
|
-
const outputDir = path6.dirname(
|
|
1654
|
+
const outputDir = path6.dirname(getGeneratedSocketTypesPath4());
|
|
1568
1655
|
const absoluteSource = path6.resolve(path6.dirname(filePath), source);
|
|
1569
1656
|
let relPath = path6.relative(outputDir, absoluteSource).replaceAll("\\", "/");
|
|
1570
1657
|
relPath = relPath.replace(/\.tsx?$/, "");
|
|
@@ -1651,7 +1738,7 @@ var sanitizeTypeAndCollectImports = ({
|
|
|
1651
1738
|
};
|
|
1652
1739
|
|
|
1653
1740
|
// src/typeMap/functionsMeta.ts
|
|
1654
|
-
import { getGeneratedSocketTypesPath as
|
|
1741
|
+
import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath5, getServerFunctionDirs } from "@luckystack/core";
|
|
1655
1742
|
var stripDefaultValues = (params) => {
|
|
1656
1743
|
return params.replaceAll(/\s*=(?!>)[^,)]+/g, "");
|
|
1657
1744
|
};
|
|
@@ -1679,7 +1766,7 @@ var simplifyInferredType = (value) => {
|
|
|
1679
1766
|
if (/\bRedis\b/.test(value)) return "Redis";
|
|
1680
1767
|
return value;
|
|
1681
1768
|
};
|
|
1682
|
-
var getGeneratedFileDir = () => path7.dirname(
|
|
1769
|
+
var getGeneratedFileDir = () => path7.dirname(getGeneratedSocketTypesPath5());
|
|
1683
1770
|
var relativizeModuleSpecifier = (specifier, sourceFilePath) => {
|
|
1684
1771
|
if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
|
|
1685
1772
|
return specifier;
|
|
@@ -2041,16 +2128,16 @@ var generateServerFunctions = (collectors) => {
|
|
|
2041
2128
|
};
|
|
2042
2129
|
|
|
2043
2130
|
// src/typeMapGenerator.ts
|
|
2044
|
-
import { getSrcDir } from "@luckystack/core";
|
|
2131
|
+
import { getSrcDir as getSrcDir2 } from "@luckystack/core";
|
|
2045
2132
|
|
|
2046
2133
|
// src/routeNamingValidation.ts
|
|
2047
2134
|
import fs5 from "fs";
|
|
2048
2135
|
import path8 from "path";
|
|
2049
|
-
import { ROOT_DIR as
|
|
2136
|
+
import { ROOT_DIR as ROOT_DIR3, validatePagePath } from "@luckystack/core";
|
|
2050
2137
|
var normalizePath = (value) => {
|
|
2051
2138
|
return value.replaceAll("\\", "/");
|
|
2052
2139
|
};
|
|
2053
|
-
var toRel = (absolute) => path8.relative(
|
|
2140
|
+
var toRel = (absolute) => path8.relative(ROOT_DIR3, absolute).replaceAll("\\", "/");
|
|
2054
2141
|
var walkRouteFiles = (dir, results = []) => {
|
|
2055
2142
|
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
2056
2143
|
const apiSeg = apiMarkerSegment();
|
|
@@ -2308,7 +2395,7 @@ var collectDuplicatePageRoutes = (srcDir) => {
|
|
|
2308
2395
|
const result = validatePagePath(relative);
|
|
2309
2396
|
if (!result.valid || !result.route) continue;
|
|
2310
2397
|
const list = routeToFiles.get(result.route) ?? [];
|
|
2311
|
-
list.push(normalizePath(path8.relative(
|
|
2398
|
+
list.push(normalizePath(path8.relative(ROOT_DIR3, absoluteFilePath)));
|
|
2312
2399
|
routeToFiles.set(result.route, list);
|
|
2313
2400
|
}
|
|
2314
2401
|
const issues = [];
|
|
@@ -2347,26 +2434,9 @@ var assertNoDuplicatePageRoutes = ({
|
|
|
2347
2434
|
// src/typeMapGenerator.ts
|
|
2348
2435
|
var namedImports = /* @__PURE__ */ new Map();
|
|
2349
2436
|
var defaultImports = /* @__PURE__ */ new Map();
|
|
2350
|
-
var
|
|
2351
|
-
const {
|
|
2352
|
-
assertValidRouteNaming({
|
|
2353
|
-
srcDir: getSrcDir(),
|
|
2354
|
-
context: "generating API/sync type maps"
|
|
2355
|
-
});
|
|
2356
|
-
assertNoDuplicateNormalizedRouteKeys({
|
|
2357
|
-
srcDir: getSrcDir(),
|
|
2358
|
-
context: "generating API/sync type maps"
|
|
2359
|
-
});
|
|
2360
|
-
assertNoDuplicatePageRoutes({
|
|
2361
|
-
srcDir: getSrcDir(),
|
|
2362
|
-
context: "generating API/sync type maps"
|
|
2363
|
-
});
|
|
2364
|
-
invalidateProgramCache();
|
|
2365
|
-
namedImports.clear();
|
|
2366
|
-
defaultImports.clear();
|
|
2367
|
-
const apiFiles = findAllApiFiles(getSrcDir());
|
|
2437
|
+
var collectApiTypes = (apiFiles, collectors) => {
|
|
2438
|
+
const { namedImports: namedImports2, unresolvedTypeAliases, quiet } = collectors;
|
|
2368
2439
|
const typesByPage = /* @__PURE__ */ new Map();
|
|
2369
|
-
const unresolvedTypeAliases = /* @__PURE__ */ new Set();
|
|
2370
2440
|
if (!quiet) {
|
|
2371
2441
|
console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
2372
2442
|
console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
@@ -2393,15 +2463,17 @@ var generateTypeMapFile = (options = {}) => {
|
|
|
2393
2463
|
console.error(`[TypeMapGenerator] Unresolved API type (${pagePath}/${apiName}/${apiVersion}): ${symbol.name}`);
|
|
2394
2464
|
continue;
|
|
2395
2465
|
}
|
|
2396
|
-
getOrInit(
|
|
2466
|
+
getOrInit(namedImports2, symbol.importPath, () => /* @__PURE__ */ new Set()).add(symbol.name);
|
|
2397
2467
|
}
|
|
2398
2468
|
if (!quiet) {
|
|
2399
2469
|
console.log(`[TypeMapGenerator] API: ${pagePath}/${apiName}/${apiVersion} (${httpMethod}${rateLimit === void 0 ? "" : `, rateLimit: ${rateLimit}`})`);
|
|
2400
2470
|
}
|
|
2401
2471
|
getOrInit(typesByPage, pagePath, () => /* @__PURE__ */ new Map()).set(`${apiName}@${apiVersion}`, { input: inputType, output: outputType, stream: streamType, method: httpMethod, rateLimit, auth, version: apiVersion, ...meta ? { meta } : {} });
|
|
2402
2472
|
}
|
|
2403
|
-
|
|
2404
|
-
|
|
2473
|
+
return typesByPage;
|
|
2474
|
+
};
|
|
2475
|
+
var collectSyncTypes = (syncServerFiles, syncClientFiles, collectors) => {
|
|
2476
|
+
const { namedImports: namedImports2, unresolvedTypeAliases, quiet } = collectors;
|
|
2405
2477
|
const syncTypesByPage = /* @__PURE__ */ new Map();
|
|
2406
2478
|
if (!quiet) {
|
|
2407
2479
|
console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
|
|
@@ -2454,7 +2526,7 @@ var generateTypeMapFile = (options = {}) => {
|
|
|
2454
2526
|
console.error(`[TypeMapGenerator] Unresolved Sync type (${pagePath}/${syncName}/${syncVersion}): ${symbol.name}`);
|
|
2455
2527
|
continue;
|
|
2456
2528
|
}
|
|
2457
|
-
getOrInit(
|
|
2529
|
+
getOrInit(namedImports2, symbol.importPath, () => /* @__PURE__ */ new Set()).add(symbol.name);
|
|
2458
2530
|
}
|
|
2459
2531
|
if (!quiet) {
|
|
2460
2532
|
console.log(`[TypeMapGenerator] Sync: ${pagePath}/${syncName}/${syncVersion} (server: ${!!serverFile}, client: ${!!clientFile})`);
|
|
@@ -2470,19 +2542,45 @@ var generateTypeMapFile = (options = {}) => {
|
|
|
2470
2542
|
...syncMeta ? { meta: syncMeta } : {}
|
|
2471
2543
|
});
|
|
2472
2544
|
}
|
|
2545
|
+
return syncTypesByPage;
|
|
2546
|
+
};
|
|
2547
|
+
var generateTypeMapFile = (options = {}) => {
|
|
2548
|
+
const { quiet = false } = options;
|
|
2549
|
+
assertValidRouteNaming({
|
|
2550
|
+
srcDir: getSrcDir2(),
|
|
2551
|
+
context: "generating API/sync type maps"
|
|
2552
|
+
});
|
|
2553
|
+
assertNoDuplicateNormalizedRouteKeys({
|
|
2554
|
+
srcDir: getSrcDir2(),
|
|
2555
|
+
context: "generating API/sync type maps"
|
|
2556
|
+
});
|
|
2557
|
+
assertNoDuplicatePageRoutes({
|
|
2558
|
+
srcDir: getSrcDir2(),
|
|
2559
|
+
context: "generating API/sync type maps"
|
|
2560
|
+
});
|
|
2561
|
+
invalidateProgramCache();
|
|
2562
|
+
namedImports.clear();
|
|
2563
|
+
defaultImports.clear();
|
|
2564
|
+
const unresolvedTypeAliases = /* @__PURE__ */ new Set();
|
|
2565
|
+
const collectors = { namedImports, unresolvedTypeAliases, quiet };
|
|
2566
|
+
const apiFiles = findAllApiFiles(getSrcDir2());
|
|
2567
|
+
const typesByPage = collectApiTypes(apiFiles, collectors);
|
|
2568
|
+
const syncServerFiles = findAllSyncServerFiles(getSrcDir2());
|
|
2569
|
+
const syncClientFiles = findAllSyncClientFiles(getSrcDir2());
|
|
2570
|
+
const syncTypesByPage = collectSyncTypes(syncServerFiles, syncClientFiles, collectors);
|
|
2473
2571
|
const functionsInterface = generateServerFunctions({ namedImports, defaultImports });
|
|
2474
2572
|
if (unresolvedTypeAliases.size > 0) {
|
|
2475
2573
|
const unresolvedList = [...unresolvedTypeAliases].toSorted().join(", ");
|
|
2476
2574
|
throw new Error(`[TypeMapGenerator] Aborting generation because unresolved type symbols were found: ${unresolvedList}`);
|
|
2477
2575
|
}
|
|
2478
|
-
const { content, docsData, schemasContent } = buildTypeMapArtifacts({
|
|
2576
|
+
const { content, docsData, schemasContent, diagnosticsData } = buildTypeMapArtifacts({
|
|
2479
2577
|
typesByPage,
|
|
2480
2578
|
syncTypesByPage,
|
|
2481
2579
|
namedImports,
|
|
2482
2580
|
defaultImports,
|
|
2483
2581
|
functionsInterface
|
|
2484
2582
|
});
|
|
2485
|
-
writeTypeMapArtifacts({ content, docsData, schemasContent });
|
|
2583
|
+
writeTypeMapArtifacts({ content, docsData, schemasContent, diagnosticsData });
|
|
2486
2584
|
};
|
|
2487
2585
|
|
|
2488
2586
|
// src/templateRegistry.ts
|
|
@@ -2560,7 +2658,7 @@ registerDefaultTemplateRules();
|
|
|
2560
2658
|
import fs6 from "fs";
|
|
2561
2659
|
import path9 from "path";
|
|
2562
2660
|
import { pathToFileURL } from "url";
|
|
2563
|
-
import { tryCatch, getServerFunctionDirs as getServerFunctionDirs2, getSrcDir as
|
|
2661
|
+
import { tryCatch, getServerFunctionDirs as getServerFunctionDirs2, getSrcDir as getSrcDir3 } from "@luckystack/core";
|
|
2564
2662
|
|
|
2565
2663
|
// src/runtimeTypeResolver.ts
|
|
2566
2664
|
import * as ts8 from "typescript";
|
|
@@ -2610,8 +2708,19 @@ var splitTopLevel = (value, splitter) => {
|
|
|
2610
2708
|
let depthBrace = 0;
|
|
2611
2709
|
let depthBracket = 0;
|
|
2612
2710
|
let depthAngle = 0;
|
|
2711
|
+
let quote = null;
|
|
2613
2712
|
let token = "";
|
|
2614
2713
|
for (const char of value) {
|
|
2714
|
+
if (quote !== null) {
|
|
2715
|
+
if (char === quote) quote = null;
|
|
2716
|
+
token += char;
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
2719
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
2720
|
+
quote = char;
|
|
2721
|
+
token += char;
|
|
2722
|
+
continue;
|
|
2723
|
+
}
|
|
2615
2724
|
if (char === "(") depthParen += 1;
|
|
2616
2725
|
if (char === ")") depthParen -= 1;
|
|
2617
2726
|
if (char === "{") depthBrace += 1;
|
|
@@ -2619,7 +2728,7 @@ var splitTopLevel = (value, splitter) => {
|
|
|
2619
2728
|
if (char === "[") depthBracket += 1;
|
|
2620
2729
|
if (char === "]") depthBracket -= 1;
|
|
2621
2730
|
if (char === "<") depthAngle += 1;
|
|
2622
|
-
if (char === ">") depthAngle
|
|
2731
|
+
if (char === ">") depthAngle = Math.max(0, depthAngle - 1);
|
|
2623
2732
|
if (char === splitter && depthParen === 0 && depthBrace === 0 && depthBracket === 0 && depthAngle === 0) {
|
|
2624
2733
|
if (token.trim()) items.push(token.trim());
|
|
2625
2734
|
token = "";
|
|
@@ -2929,17 +3038,20 @@ var devSyncs = {};
|
|
|
2929
3038
|
var devFunctions = {};
|
|
2930
3039
|
var normalizePath2 = (value) => value.replaceAll("\\", "/");
|
|
2931
3040
|
var mapApiPageLocation = (pageLocation) => {
|
|
2932
|
-
return pageLocation
|
|
3041
|
+
return pageLocation || "system";
|
|
3042
|
+
};
|
|
3043
|
+
var mapSyncPageLocation = (pageLocation) => {
|
|
3044
|
+
return pageLocation || "system";
|
|
2933
3045
|
};
|
|
2934
3046
|
var resolveApiRouteMetaFromPath = (filePath) => {
|
|
2935
3047
|
const absolutePath = path9.resolve(filePath);
|
|
2936
3048
|
const normalizedAbsolutePath = normalizePath2(absolutePath);
|
|
2937
|
-
const normalizedSrcDir = normalizePath2(
|
|
3049
|
+
const normalizedSrcDir = normalizePath2(getSrcDir3());
|
|
2938
3050
|
if (!normalizedAbsolutePath.startsWith(normalizedSrcDir) || !normalizedAbsolutePath.endsWith(".ts")) {
|
|
2939
3051
|
return null;
|
|
2940
3052
|
}
|
|
2941
3053
|
const rules2 = getRoutingRules();
|
|
2942
|
-
const relativePath = normalizePath2(path9.relative(
|
|
3054
|
+
const relativePath = normalizePath2(path9.relative(getSrcDir3(), absolutePath));
|
|
2943
3055
|
const segments = relativePath.split("/");
|
|
2944
3056
|
const apiIndex = segments.indexOf(rules2.apiMarker);
|
|
2945
3057
|
if (apiIndex === -1 || apiIndex === segments.length - 1) {
|
|
@@ -2961,12 +3073,12 @@ var resolveApiRouteMetaFromPath = (filePath) => {
|
|
|
2961
3073
|
var resolveSyncRouteMetaFromPath = (filePath) => {
|
|
2962
3074
|
const absolutePath = path9.resolve(filePath);
|
|
2963
3075
|
const normalizedAbsolutePath = normalizePath2(absolutePath);
|
|
2964
|
-
const normalizedSrcDir = normalizePath2(
|
|
3076
|
+
const normalizedSrcDir = normalizePath2(getSrcDir3());
|
|
2965
3077
|
if (!normalizedAbsolutePath.startsWith(normalizedSrcDir) || !normalizedAbsolutePath.endsWith(".ts")) {
|
|
2966
3078
|
return null;
|
|
2967
3079
|
}
|
|
2968
3080
|
const rules2 = getRoutingRules();
|
|
2969
|
-
const relativePath = normalizePath2(path9.relative(
|
|
3081
|
+
const relativePath = normalizePath2(path9.relative(getSrcDir3(), absolutePath));
|
|
2970
3082
|
const segments = relativePath.split("/");
|
|
2971
3083
|
const syncIndex = segments.indexOf(rules2.syncMarker);
|
|
2972
3084
|
if (syncIndex === -1 || syncIndex === segments.length - 1) {
|
|
@@ -2982,7 +3094,8 @@ var resolveSyncRouteMetaFromPath = (filePath) => {
|
|
|
2982
3094
|
const kind = match[1];
|
|
2983
3095
|
const version = `v${match[2]}`;
|
|
2984
3096
|
const syncName = rawSyncName.replace(rules2.syncVersionRegex, "");
|
|
2985
|
-
const
|
|
3097
|
+
const mappedPageLocation = mapSyncPageLocation(pageLocation);
|
|
3098
|
+
const routeBaseKey = `sync/${mappedPageLocation}/${syncName}/${version}`;
|
|
2986
3099
|
return {
|
|
2987
3100
|
routeKey: `${routeBaseKey}_${kind}`,
|
|
2988
3101
|
kind,
|
|
@@ -2991,10 +3104,10 @@ var resolveSyncRouteMetaFromPath = (filePath) => {
|
|
|
2991
3104
|
};
|
|
2992
3105
|
var initializeAll = async () => {
|
|
2993
3106
|
assertValidRouteNaming({
|
|
2994
|
-
srcDir:
|
|
3107
|
+
srcDir: getSrcDir3(),
|
|
2995
3108
|
context: "starting dev server (npm run server)"
|
|
2996
3109
|
});
|
|
2997
|
-
const pageRouteIssues = collectDuplicatePageRoutes(
|
|
3110
|
+
const pageRouteIssues = collectDuplicatePageRoutes(getSrcDir3());
|
|
2998
3111
|
if (pageRouteIssues.length > 0) {
|
|
2999
3112
|
console.warn(formatDuplicatePageRouteIssues({
|
|
3000
3113
|
issues: pageRouteIssues,
|
|
@@ -3011,6 +3124,7 @@ var collectTsFiles = (dir, relativeTo = "") => {
|
|
|
3011
3124
|
const results = [];
|
|
3012
3125
|
const entries = fs6.readdirSync(dir);
|
|
3013
3126
|
for (const entry of entries) {
|
|
3127
|
+
if (entry === "node_modules") continue;
|
|
3014
3128
|
const entryPath = path9.join(dir, entry);
|
|
3015
3129
|
const relPath = relativeTo ? `${relativeTo}/${entry}` : entry;
|
|
3016
3130
|
if (fs6.statSync(entryPath).isDirectory()) {
|
|
@@ -3044,7 +3158,7 @@ var resolveFunctionModule = (loadedModule, fileName) => {
|
|
|
3044
3158
|
var initializeApis = async () => {
|
|
3045
3159
|
for (const key of Object.keys(devApis)) delete devApis[key];
|
|
3046
3160
|
clearRuntimeTypeResolverCache();
|
|
3047
|
-
const srcFolder = fs6.readdirSync(
|
|
3161
|
+
const srcFolder = fs6.readdirSync(getSrcDir3());
|
|
3048
3162
|
for (const file of srcFolder) {
|
|
3049
3163
|
await scanApiFolder(file);
|
|
3050
3164
|
}
|
|
@@ -3100,7 +3214,7 @@ var removeApiFromFile = (filePath) => {
|
|
|
3100
3214
|
delete devApis[routeMeta.routeKey];
|
|
3101
3215
|
};
|
|
3102
3216
|
var scanApiFolder = async (file, basePath = "") => {
|
|
3103
|
-
const fullPath = path9.join(
|
|
3217
|
+
const fullPath = path9.join(getSrcDir3(), basePath, file);
|
|
3104
3218
|
if (!fs6.statSync(fullPath).isDirectory()) return;
|
|
3105
3219
|
if (!file.toLowerCase().endsWith("api")) {
|
|
3106
3220
|
const subFolders = fs6.readdirSync(fullPath);
|
|
@@ -3155,7 +3269,7 @@ var scanApiFolder = async (file, basePath = "") => {
|
|
|
3155
3269
|
var initializeSyncs = async () => {
|
|
3156
3270
|
for (const key of Object.keys(devSyncs)) delete devSyncs[key];
|
|
3157
3271
|
clearRuntimeTypeResolverCache();
|
|
3158
|
-
const srcFolder = fs6.readdirSync(
|
|
3272
|
+
const srcFolder = fs6.readdirSync(getSrcDir3());
|
|
3159
3273
|
for (const file of srcFolder) {
|
|
3160
3274
|
await scanSyncFolder(file);
|
|
3161
3275
|
}
|
|
@@ -3190,7 +3304,13 @@ var upsertSyncFromFile = async (filePath) => {
|
|
|
3190
3304
|
main: resolvedSyncModule.main,
|
|
3191
3305
|
auth: resolvedSyncModule.auth || {},
|
|
3192
3306
|
inputType,
|
|
3193
|
-
inputTypeFilePath: routeMeta.absolutePath
|
|
3307
|
+
inputTypeFilePath: routeMeta.absolutePath,
|
|
3308
|
+
//? Forward the per-route `validation` toggle + `errorFormatter` so the dev
|
|
3309
|
+
//? loader's sync entry shape matches the prod generator's. Dropping them
|
|
3310
|
+
//? made `validation: { input: 'skip' }` / a per-route errorFormatter work
|
|
3311
|
+
//? in prod but silently no-op in dev (QUA-013 / QUA-044).
|
|
3312
|
+
validation: resolvedSyncModule.validation,
|
|
3313
|
+
errorFormatter: resolvedSyncModule.errorFormatter
|
|
3194
3314
|
};
|
|
3195
3315
|
return;
|
|
3196
3316
|
}
|
|
@@ -3210,7 +3330,7 @@ var removeSyncFromFile = (filePath) => {
|
|
|
3210
3330
|
delete devSyncs[routeMeta.routeKey];
|
|
3211
3331
|
};
|
|
3212
3332
|
var scanSyncFolder = async (file, basePath = "") => {
|
|
3213
|
-
const fullPath = path9.join(
|
|
3333
|
+
const fullPath = path9.join(getSrcDir3(), basePath, file);
|
|
3214
3334
|
if (!fs6.statSync(fullPath).isDirectory()) return;
|
|
3215
3335
|
if (!file.toLowerCase().endsWith("sync")) {
|
|
3216
3336
|
const subFolders = fs6.readdirSync(fullPath);
|
|
@@ -3235,7 +3355,8 @@ var scanSyncFolder = async (file, basePath = "") => {
|
|
|
3235
3355
|
const kind = syncMatch[1];
|
|
3236
3356
|
const version = `v${syncMatch[2]}`;
|
|
3237
3357
|
const syncName = rawSyncFileName.replace(syncRules.syncVersionRegex, "");
|
|
3238
|
-
const
|
|
3358
|
+
const mappedPageLocation = mapSyncPageLocation(pageLocation);
|
|
3359
|
+
const routeBaseKey = `sync/${mappedPageLocation}/${syncName}/${version}`;
|
|
3239
3360
|
const filePath = path9.resolve(path9.join(fullPath, relFile));
|
|
3240
3361
|
const [fileError, fileResult] = await tryCatch(async () => importFile(filePath));
|
|
3241
3362
|
if (fileError) {
|
|
@@ -3249,7 +3370,11 @@ var scanSyncFolder = async (file, basePath = "") => {
|
|
|
3249
3370
|
main: resolvedSyncModule.main,
|
|
3250
3371
|
auth: resolvedSyncModule.auth || {},
|
|
3251
3372
|
inputType,
|
|
3252
|
-
inputTypeFilePath: filePath
|
|
3373
|
+
inputTypeFilePath: filePath,
|
|
3374
|
+
//? Forward `validation` + `errorFormatter` so the dev loader matches the
|
|
3375
|
+
//? prod generator's sync entry shape (QUA-013 / QUA-044).
|
|
3376
|
+
validation: resolvedSyncModule.validation,
|
|
3377
|
+
errorFormatter: resolvedSyncModule.errorFormatter
|
|
3253
3378
|
};
|
|
3254
3379
|
} else {
|
|
3255
3380
|
devSyncs[`${routeBaseKey}_client`] = resolvedSyncModule.main;
|
|
@@ -3322,11 +3447,12 @@ import path12 from "path";
|
|
|
3322
3447
|
import fs7 from "fs";
|
|
3323
3448
|
import path10 from "path";
|
|
3324
3449
|
import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "url";
|
|
3325
|
-
import
|
|
3450
|
+
import * as ts9 from "typescript";
|
|
3451
|
+
import { ROOT_DIR as ROOT_DIR4, getGeneratedSocketTypesPath as getGeneratedSocketTypesPath6, getSrcDir as getSrcDir4, validatePagePath as validatePagePath2 } from "@luckystack/core";
|
|
3326
3452
|
var __filename = fileURLToPath(import.meta.url);
|
|
3327
3453
|
var __dirname = path10.dirname(__filename);
|
|
3328
3454
|
var templatesDir = path10.join(__dirname, "templates");
|
|
3329
|
-
var getConsumerTemplatesDir = () => path10.join(
|
|
3455
|
+
var getConsumerTemplatesDir = () => path10.join(ROOT_DIR4, ".luckystack", "templates");
|
|
3330
3456
|
var templateContentFilenames = (kind) => {
|
|
3331
3457
|
const builtIn = BUILT_IN_TEMPLATE_FILENAMES[kind];
|
|
3332
3458
|
if (builtIn) return [builtIn];
|
|
@@ -3420,7 +3546,7 @@ var isVersionedSyncFile = (filePath) => {
|
|
|
3420
3546
|
return isVersionedSyncFileName(filePath);
|
|
3421
3547
|
};
|
|
3422
3548
|
var getFileName = (filePath) => {
|
|
3423
|
-
return path10.basename(filePath.
|
|
3549
|
+
return path10.basename(filePath.replaceAll("\\", "/"));
|
|
3424
3550
|
};
|
|
3425
3551
|
var stripTsExtension = (fileName) => {
|
|
3426
3552
|
return fileName.replace(/\.ts$/, "");
|
|
@@ -3464,7 +3590,7 @@ var getSyncFilenameReason = (fileName) => {
|
|
|
3464
3590
|
return "Missing required sync kind and version suffix.";
|
|
3465
3591
|
};
|
|
3466
3592
|
var getRouteFilenameValidationMessage = (filePath) => {
|
|
3467
|
-
const normalized = filePath.
|
|
3593
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
3468
3594
|
const fileName = getFileName(normalized);
|
|
3469
3595
|
if (isInApiFolder(normalized) && !isVersionedApiFile(normalized)) {
|
|
3470
3596
|
const base = toApiBaseName(fileName);
|
|
@@ -3489,13 +3615,13 @@ var getRouteFilenameValidationMessage = (filePath) => {
|
|
|
3489
3615
|
return null;
|
|
3490
3616
|
};
|
|
3491
3617
|
var getInvalidVersionMessage = (filePath) => {
|
|
3492
|
-
const validationMessage = getRouteFilenameValidationMessage(filePath)
|
|
3618
|
+
const validationMessage = getRouteFilenameValidationMessage(filePath) ?? "Invalid route filename.";
|
|
3493
3619
|
return `// ${validationMessage}
|
|
3494
3620
|
`;
|
|
3495
3621
|
};
|
|
3496
3622
|
var computeSrcRelativePath = (filePath) => {
|
|
3497
3623
|
try {
|
|
3498
|
-
const srcDir =
|
|
3624
|
+
const srcDir = getSrcDir4();
|
|
3499
3625
|
const absolute = path10.resolve(filePath);
|
|
3500
3626
|
const normalizedSrc = srcDir.replaceAll("\\", "/");
|
|
3501
3627
|
const normalizedAbs = absolute.replaceAll("\\", "/");
|
|
@@ -3549,7 +3675,9 @@ var hasPairedFile = (filePath) => {
|
|
|
3549
3675
|
var extractSyncPagePath2 = (filePath) => {
|
|
3550
3676
|
const normalized = filePath.replaceAll("\\", "/");
|
|
3551
3677
|
const match = /src\/(.+?)\/_sync\//.exec(normalized);
|
|
3552
|
-
|
|
3678
|
+
if (match?.[1]) return match[1];
|
|
3679
|
+
if (/(?:^|\/)src\/_sync\//.test(normalized)) return "system";
|
|
3680
|
+
return "";
|
|
3553
3681
|
};
|
|
3554
3682
|
var extractSyncName2 = (filePath) => {
|
|
3555
3683
|
const normalized = filePath.replaceAll("\\", "/");
|
|
@@ -3565,11 +3693,16 @@ var extractClientInputFromFile = (filePath) => {
|
|
|
3565
3693
|
const content = fs7.readFileSync(filePath, "utf8");
|
|
3566
3694
|
const syncParamsMatch = /interface\s+SyncParams\s*\{/.exec(content);
|
|
3567
3695
|
if (!syncParamsMatch) return null;
|
|
3568
|
-
const
|
|
3696
|
+
const searchRegion = content.slice(syncParamsMatch.index + syncParamsMatch[0].length);
|
|
3697
|
+
const clientInputMatch = /clientInput\s*:\s*\{/.exec(searchRegion);
|
|
3569
3698
|
if (!clientInputMatch) return null;
|
|
3570
|
-
const startIndex = content.indexOf(
|
|
3699
|
+
const startIndex = content.indexOf(
|
|
3700
|
+
"{",
|
|
3701
|
+
syncParamsMatch.index + syncParamsMatch[0].length + clientInputMatch.index
|
|
3702
|
+
);
|
|
3703
|
+
if (startIndex === -1) return null;
|
|
3571
3704
|
let depth = 0;
|
|
3572
|
-
let endIndex =
|
|
3705
|
+
let endIndex = -1;
|
|
3573
3706
|
for (let i = startIndex; i < content.length; i++) {
|
|
3574
3707
|
if (content[i] === "{") depth++;
|
|
3575
3708
|
else if (content[i] === "}") depth--;
|
|
@@ -3578,7 +3711,8 @@ var extractClientInputFromFile = (filePath) => {
|
|
|
3578
3711
|
break;
|
|
3579
3712
|
}
|
|
3580
3713
|
}
|
|
3581
|
-
|
|
3714
|
+
if (endIndex === -1) return null;
|
|
3715
|
+
return content.slice(startIndex, endIndex + 1);
|
|
3582
3716
|
} catch (error) {
|
|
3583
3717
|
console.error(`[TemplateInjector] Error extracting clientInput from ${filePath}:`, error);
|
|
3584
3718
|
return null;
|
|
@@ -3586,7 +3720,7 @@ var extractClientInputFromFile = (filePath) => {
|
|
|
3586
3720
|
};
|
|
3587
3721
|
var extractClientInputFromGeneratedTypes = (pagePath, syncName) => {
|
|
3588
3722
|
try {
|
|
3589
|
-
const generatedTypesPath =
|
|
3723
|
+
const generatedTypesPath = getGeneratedSocketTypesPath6();
|
|
3590
3724
|
const content = fs7.readFileSync(generatedTypesPath, "utf8");
|
|
3591
3725
|
const escapeRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
3592
3726
|
const escapedPagePath = escapeRegex(pagePath);
|
|
@@ -3610,7 +3744,7 @@ var extractClientInputFromGeneratedTypes = (pagePath, syncName) => {
|
|
|
3610
3744
|
const braceStart = content.indexOf("{", searchStart - 1);
|
|
3611
3745
|
if (braceStart === -1) return null;
|
|
3612
3746
|
let depth = 0;
|
|
3613
|
-
let endIndex =
|
|
3747
|
+
let endIndex = -1;
|
|
3614
3748
|
for (let i = braceStart; i < content.length; i++) {
|
|
3615
3749
|
if (content[i] === "{") depth++;
|
|
3616
3750
|
else if (content[i] === "}") depth--;
|
|
@@ -3619,7 +3753,8 @@ var extractClientInputFromGeneratedTypes = (pagePath, syncName) => {
|
|
|
3619
3753
|
break;
|
|
3620
3754
|
}
|
|
3621
3755
|
}
|
|
3622
|
-
|
|
3756
|
+
if (endIndex === -1) return null;
|
|
3757
|
+
const extracted = content.slice(braceStart, endIndex + 1);
|
|
3623
3758
|
console.log(`[TemplateInjector] Extracted clientInput types: ${extracted}`);
|
|
3624
3759
|
return extracted;
|
|
3625
3760
|
} catch (error) {
|
|
@@ -3629,6 +3764,18 @@ var extractClientInputFromGeneratedTypes = (pagePath, syncName) => {
|
|
|
3629
3764
|
};
|
|
3630
3765
|
var calculateRelativePath = (filePath) => {
|
|
3631
3766
|
const normalized = filePath.replaceAll("\\", "/");
|
|
3767
|
+
const srcRelative = computeSrcRelativePath(filePath);
|
|
3768
|
+
if (srcRelative !== null) {
|
|
3769
|
+
try {
|
|
3770
|
+
const srcDepthFromRoot = path10.relative(ROOT_DIR4, getSrcDir4()).replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0).length;
|
|
3771
|
+
const dirSegmentsAfterSrc = Math.max(
|
|
3772
|
+
0,
|
|
3773
|
+
srcRelative.split("/").filter((segment) => segment.length > 0).length - 1
|
|
3774
|
+
);
|
|
3775
|
+
return "../".repeat(srcDepthFromRoot + dirSegmentsAfterSrc);
|
|
3776
|
+
} catch {
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3632
3779
|
const srcIndex = normalized.indexOf("src/");
|
|
3633
3780
|
if (srcIndex === -1) {
|
|
3634
3781
|
console.warn(`[TemplateInjector] Could not find /src/ in path: ${filePath}`);
|
|
@@ -3638,58 +3785,68 @@ var calculateRelativePath = (filePath) => {
|
|
|
3638
3785
|
const segments = relativePath.split("/").filter((s) => s.length > 0).length;
|
|
3639
3786
|
return "../".repeat(segments);
|
|
3640
3787
|
};
|
|
3641
|
-
var
|
|
3642
|
-
let fileKind;
|
|
3643
|
-
let hasPairedServer = false;
|
|
3644
|
-
let srcRelativePath = null;
|
|
3788
|
+
var classifyFile = (filePath) => {
|
|
3645
3789
|
if (isInApiFolder(filePath)) {
|
|
3646
|
-
fileKind
|
|
3647
|
-
}
|
|
3790
|
+
return { fileKind: "api", hasPairedServer: false, srcRelativePath: null };
|
|
3791
|
+
}
|
|
3792
|
+
if (isInSyncFolder(filePath)) {
|
|
3648
3793
|
if (isSyncServerFile(filePath)) {
|
|
3649
|
-
fileKind
|
|
3650
|
-
} else if (isSyncClientFile(filePath)) {
|
|
3651
|
-
fileKind = "sync_client";
|
|
3652
|
-
hasPairedServer = hasPairedFile(filePath);
|
|
3653
|
-
} else {
|
|
3654
|
-
console.log(`[TemplateInjector] Unknown sync file type: ${filePath}`);
|
|
3655
|
-
return null;
|
|
3794
|
+
return { fileKind: "sync_server", hasPairedServer: false, srcRelativePath: null };
|
|
3656
3795
|
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
srcRelativePath = computeSrcRelativePath(filePath);
|
|
3660
|
-
if (srcRelativePath !== null) {
|
|
3661
|
-
const placement = validatePagePath2(srcRelativePath);
|
|
3662
|
-
if (!placement.valid) {
|
|
3663
|
-
return getInvalidPagePlacementMessage(filePath, placement.reason ?? "invalid placement", srcRelativePath);
|
|
3664
|
-
}
|
|
3796
|
+
if (isSyncClientFile(filePath)) {
|
|
3797
|
+
return { fileKind: "sync_client", hasPairedServer: hasPairedFile(filePath), srcRelativePath: null };
|
|
3665
3798
|
}
|
|
3666
|
-
|
|
3799
|
+
console.log(`[TemplateInjector] Unknown sync file type: ${filePath}`);
|
|
3667
3800
|
return null;
|
|
3668
3801
|
}
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3802
|
+
if (isPageFile(filePath)) {
|
|
3803
|
+
return { fileKind: "page", hasPairedServer: false, srcRelativePath: computeSrcRelativePath(filePath) };
|
|
3804
|
+
}
|
|
3805
|
+
return null;
|
|
3806
|
+
};
|
|
3807
|
+
var checkPagePlacement = (filePath, srcRelativePath) => {
|
|
3808
|
+
if (srcRelativePath === null) return null;
|
|
3809
|
+
const placement = validatePagePath2(srcRelativePath);
|
|
3810
|
+
if (!placement.valid) {
|
|
3811
|
+
return getInvalidPagePlacementMessage(filePath, placement.reason ?? "invalid placement", srcRelativePath);
|
|
3674
3812
|
}
|
|
3675
|
-
|
|
3676
|
-
|
|
3813
|
+
return null;
|
|
3814
|
+
};
|
|
3815
|
+
var applyTemplatePlaceholders = (content, filePath, fileKind) => {
|
|
3677
3816
|
const relPath = calculateRelativePath(filePath);
|
|
3678
3817
|
const pragmaPattern = /\/\/\s*@ts-(?:ignore|expect-error).*\r?\n(.*)\{\{REL_PATH\}\}/g;
|
|
3679
|
-
|
|
3680
|
-
|
|
3818
|
+
let result = content.replaceAll(pragmaPattern, (_, prefix) => `${prefix}${relPath}`);
|
|
3819
|
+
result = result.replaceAll("{{REL_PATH}}", relPath);
|
|
3681
3820
|
if (fileKind === "sync_client" || fileKind === "sync_server") {
|
|
3682
3821
|
const pagePath = extractSyncPagePath2(filePath);
|
|
3683
3822
|
const syncName = extractSyncName2(filePath);
|
|
3684
|
-
|
|
3685
|
-
|
|
3823
|
+
result = result.replaceAll("{{PAGE_PATH}}", pagePath);
|
|
3824
|
+
result = result.replaceAll("{{SYNC_NAME}}", syncName);
|
|
3686
3825
|
}
|
|
3687
|
-
return
|
|
3826
|
+
return result;
|
|
3827
|
+
};
|
|
3828
|
+
var getTemplate = (filePath) => {
|
|
3829
|
+
const classification = classifyFile(filePath);
|
|
3830
|
+
if (!classification) return null;
|
|
3831
|
+
const { fileKind, hasPairedServer, srcRelativePath } = classification;
|
|
3832
|
+
if (fileKind === "page") {
|
|
3833
|
+
const placementWarning = checkPagePlacement(filePath, srcRelativePath);
|
|
3834
|
+
if (placementWarning !== null) return placementWarning;
|
|
3835
|
+
}
|
|
3836
|
+
const ctx = { filePath, fileKind, hasPairedServer, srcRelativePath };
|
|
3837
|
+
const templateKind = resolveTemplateKind(ctx);
|
|
3838
|
+
if (!templateKind) {
|
|
3839
|
+
console.log(`[TemplateInjector] No template rule matched for ${filePath} (fileKind=${fileKind})`);
|
|
3840
|
+
return null;
|
|
3841
|
+
}
|
|
3842
|
+
const rawContent = resolveTemplateContent(templateKind);
|
|
3843
|
+
if (rawContent === null) return null;
|
|
3844
|
+
return applyTemplatePlaceholders(rawContent, filePath, fileKind);
|
|
3688
3845
|
};
|
|
3689
3846
|
var injectTemplate = async (filePath) => {
|
|
3690
3847
|
await ensureConsumerTemplateConfigLoaded();
|
|
3691
3848
|
if (getRouteFilenameValidationMessage(filePath)) {
|
|
3692
|
-
fs7.writeFileSync(filePath, getInvalidVersionMessage(filePath), "
|
|
3849
|
+
fs7.writeFileSync(filePath, getInvalidVersionMessage(filePath), "utf8");
|
|
3693
3850
|
console.log(`[TemplateInjector] Invalid route filename, injected guidance: ${filePath}`);
|
|
3694
3851
|
return true;
|
|
3695
3852
|
}
|
|
@@ -3698,7 +3855,7 @@ var injectTemplate = async (filePath) => {
|
|
|
3698
3855
|
return false;
|
|
3699
3856
|
}
|
|
3700
3857
|
try {
|
|
3701
|
-
fs7.writeFileSync(filePath, template, "
|
|
3858
|
+
fs7.writeFileSync(filePath, template, "utf8");
|
|
3702
3859
|
console.log(`[TemplateInjector] Injected template into: ${filePath}`);
|
|
3703
3860
|
return true;
|
|
3704
3861
|
} catch (error) {
|
|
@@ -3706,62 +3863,142 @@ var injectTemplate = async (filePath) => {
|
|
|
3706
3863
|
return false;
|
|
3707
3864
|
}
|
|
3708
3865
|
};
|
|
3709
|
-
var shouldInjectTemplate = (filePath) => {
|
|
3866
|
+
var shouldInjectTemplate = (filePath, options = {}) => {
|
|
3710
3867
|
const { disableTemplateInjection } = getRoutingRules();
|
|
3711
|
-
if (disableTemplateInjection
|
|
3868
|
+
if (disableTemplateInjection?.(filePath)) {
|
|
3712
3869
|
return false;
|
|
3713
3870
|
}
|
|
3714
3871
|
if (!(isInApiFolder(filePath) || isInSyncFolder(filePath) || isPageFile(filePath))) {
|
|
3715
3872
|
return false;
|
|
3716
3873
|
}
|
|
3717
|
-
|
|
3874
|
+
if (isEmptyFile(filePath)) return true;
|
|
3875
|
+
if (options.isNewFile) return isCommentOnlyFile(filePath);
|
|
3876
|
+
return false;
|
|
3718
3877
|
};
|
|
3719
|
-
var
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
content = content.replace(
|
|
3726
|
-
/import \{([^}]+)\} from ['"]([^'"]*apiTypes\.generated)['"]/,
|
|
3727
|
-
(_match, imports, path13) => {
|
|
3728
|
-
return `import {${imports}, SyncClientInput, SyncServerOutput } from '${path13}'`;
|
|
3729
|
-
}
|
|
3730
|
-
);
|
|
3878
|
+
var ensureImportedTypes = (content, clientFilePath) => {
|
|
3879
|
+
if (content.includes("SyncClientInput")) return content;
|
|
3880
|
+
let result = content.replace(
|
|
3881
|
+
/import \{([^}]+)\} from ['"]([^'"]*apiTypes\.generated)['"]/,
|
|
3882
|
+
(_match, imports, importPath) => {
|
|
3883
|
+
return `import {${imports}, SyncClientInput, SyncServerOutput } from '${importPath}'`;
|
|
3731
3884
|
}
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3885
|
+
);
|
|
3886
|
+
if (!result.includes("SyncClientInput")) {
|
|
3887
|
+
const relPath = calculateRelativePath(clientFilePath);
|
|
3888
|
+
result = `import { SyncClientInput, SyncServerOutput } from '${relPath}src/_sockets/apiTypes.generated';
|
|
3889
|
+
` + result;
|
|
3890
|
+
}
|
|
3891
|
+
return result;
|
|
3892
|
+
};
|
|
3893
|
+
var insertTypeAliases = (content, pagePath, syncName) => {
|
|
3894
|
+
if (content.includes("type PagePath")) return content;
|
|
3895
|
+
const importEndMatch = content.match(/import .+?;[\r\n]+/g);
|
|
3896
|
+
if (!importEndMatch) return content;
|
|
3897
|
+
const lastImport = importEndMatch.at(-1);
|
|
3898
|
+
if (!lastImport) return content;
|
|
3899
|
+
const lastImportEnd = content.lastIndexOf(lastImport) + lastImport.length;
|
|
3900
|
+
const typeAliases = `
|
|
3741
3901
|
// Types are imported from the generated file based on the _server.ts definition
|
|
3742
3902
|
type PagePath = '${pagePath}';
|
|
3743
3903
|
type SyncName = '${syncName}';
|
|
3744
3904
|
`;
|
|
3745
|
-
|
|
3746
|
-
|
|
3905
|
+
return content.slice(0, lastImportEnd) + typeAliases + content.slice(lastImportEnd);
|
|
3906
|
+
};
|
|
3907
|
+
var rewriteClientInputType = (content) => {
|
|
3908
|
+
return content.replace(
|
|
3909
|
+
/^(\s*)clientInput:\s*\{[^}]*\}/m,
|
|
3910
|
+
"$1clientInput: SyncClientInput<PagePath, SyncName>"
|
|
3911
|
+
);
|
|
3912
|
+
};
|
|
3913
|
+
var rewriteClientInputTypeAst = (content, sourceFileName) => {
|
|
3914
|
+
const parseSource = (src) => ts9.createSourceFile(
|
|
3915
|
+
sourceFileName,
|
|
3916
|
+
src,
|
|
3917
|
+
ts9.ScriptTarget.Latest,
|
|
3918
|
+
/* setParentNodes */
|
|
3919
|
+
true,
|
|
3920
|
+
ts9.ScriptKind.TS
|
|
3921
|
+
);
|
|
3922
|
+
const sourceFile = parseSource(content);
|
|
3923
|
+
let syncParamsInterface;
|
|
3924
|
+
for (const stmt of sourceFile.statements) {
|
|
3925
|
+
if (ts9.isInterfaceDeclaration(stmt) && stmt.name.text === "SyncParams") {
|
|
3926
|
+
syncParamsInterface = stmt;
|
|
3927
|
+
break;
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
if (!syncParamsInterface) return null;
|
|
3931
|
+
let clientInputMember;
|
|
3932
|
+
for (const member of syncParamsInterface.members) {
|
|
3933
|
+
if (ts9.isPropertySignature(member) && ts9.isIdentifier(member.name) && member.name.text === "clientInput") {
|
|
3934
|
+
clientInputMember = member;
|
|
3935
|
+
break;
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
if (!clientInputMember?.type) return null;
|
|
3939
|
+
if (!ts9.isTypeLiteralNode(clientInputMember.type)) return null;
|
|
3940
|
+
const typeNode = clientInputMember.type;
|
|
3941
|
+
const start = typeNode.getStart(sourceFile);
|
|
3942
|
+
const end = typeNode.getEnd();
|
|
3943
|
+
const rewritten = `${content.slice(0, start)}SyncClientInput<PagePath, SyncName>${content.slice(end)}`;
|
|
3944
|
+
const reparsedFile = parseSource(rewritten);
|
|
3945
|
+
const verifyProgram = ts9.createProgram({
|
|
3946
|
+
rootNames: [sourceFileName],
|
|
3947
|
+
options: { noResolve: true, skipLibCheck: true },
|
|
3948
|
+
// Supply the in-memory source so `ts.createProgram` never touches the disk.
|
|
3949
|
+
host: {
|
|
3950
|
+
...ts9.createCompilerHost({}),
|
|
3951
|
+
getSourceFile: (name) => name === sourceFileName ? reparsedFile : void 0,
|
|
3952
|
+
fileExists: (name) => name === sourceFileName,
|
|
3953
|
+
readFile: (name) => name === sourceFileName ? rewritten : void 0
|
|
3747
3954
|
}
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3955
|
+
});
|
|
3956
|
+
const syntaxErrors = verifyProgram.getSyntacticDiagnostics(reparsedFile);
|
|
3957
|
+
if (syntaxErrors.length > 0) {
|
|
3958
|
+
console.log(
|
|
3959
|
+
`[TemplateInjector] AST rewrite produced invalid TypeScript for ${sourceFileName} \u2014 leaving untouched`,
|
|
3960
|
+
"yellow"
|
|
3751
3961
|
);
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3962
|
+
return null;
|
|
3963
|
+
}
|
|
3964
|
+
return rewritten;
|
|
3965
|
+
};
|
|
3966
|
+
var addServerOutputToParams = (content) => {
|
|
3967
|
+
if (content.includes("serverOutput:")) return content;
|
|
3968
|
+
return content.replace(
|
|
3969
|
+
/^(\s*)(clientInput:\s*SyncClientInput<PagePath, SyncName>);?\s*$/m,
|
|
3970
|
+
"$1$2;\n$1serverOutput: SyncServerOutput<PagePath, SyncName>;"
|
|
3971
|
+
);
|
|
3972
|
+
};
|
|
3973
|
+
var addServerOutputToDestructuring = (content) => {
|
|
3974
|
+
if (!content.includes("main") || /\{\s*[^}]*serverOutput[^}]*\}\s*:\s*SyncParams/.test(content)) {
|
|
3975
|
+
return content;
|
|
3976
|
+
}
|
|
3977
|
+
return content.replace(
|
|
3978
|
+
/\{\s*([^}]*?clientInput)([^}]*)\}\s*:\s*SyncParams/,
|
|
3979
|
+
"{ $1, serverOutput$2 }: SyncParams"
|
|
3980
|
+
);
|
|
3981
|
+
};
|
|
3982
|
+
var updateClientFileForPairedServer = async (clientFilePath) => {
|
|
3983
|
+
try {
|
|
3984
|
+
const pagePath = extractSyncPagePath2(clientFilePath);
|
|
3985
|
+
const syncName = extractSyncName2(clientFilePath);
|
|
3986
|
+
let content = fs7.readFileSync(clientFilePath, "utf8");
|
|
3987
|
+
content = ensureImportedTypes(content, clientFilePath);
|
|
3988
|
+
content = insertTypeAliases(content, pagePath, syncName);
|
|
3989
|
+
const astResult = rewriteClientInputTypeAst(content, path10.basename(clientFilePath));
|
|
3990
|
+
if (astResult === null) {
|
|
3991
|
+
console.log(
|
|
3992
|
+
`[TemplateInjector] AST clientInput rewrite unavailable for ${clientFilePath} \u2014 using regex fallback`,
|
|
3993
|
+
"yellow"
|
|
3762
3994
|
);
|
|
3995
|
+
content = rewriteClientInputType(content);
|
|
3996
|
+
} else {
|
|
3997
|
+
content = astResult;
|
|
3763
3998
|
}
|
|
3764
|
-
|
|
3999
|
+
content = addServerOutputToParams(content);
|
|
4000
|
+
content = addServerOutputToDestructuring(content);
|
|
4001
|
+
fs7.writeFileSync(clientFilePath, content, "utf8");
|
|
3765
4002
|
console.log(`[TemplateInjector] Updated client file to use paired types (preserved code): ${clientFilePath}`);
|
|
3766
4003
|
return true;
|
|
3767
4004
|
} catch (error) {
|
|
@@ -3769,34 +4006,39 @@ type SyncName = '${syncName}';
|
|
|
3769
4006
|
return false;
|
|
3770
4007
|
}
|
|
3771
4008
|
};
|
|
4009
|
+
var inlineClientInputType = (content, clientInputTypes) => {
|
|
4010
|
+
return content.replace(
|
|
4011
|
+
/^(\s*)clientInput:\s*SyncClientInput<[^>]+>/m,
|
|
4012
|
+
(_match, indent) => `${indent}clientInput: ${clientInputTypes}`
|
|
4013
|
+
).replace(
|
|
4014
|
+
/^(\s*)clientInput:\s*\{[^}]*\}/m,
|
|
4015
|
+
(_match, indent) => `${indent}clientInput: ${clientInputTypes}`
|
|
4016
|
+
);
|
|
4017
|
+
};
|
|
4018
|
+
var removeServerOutputFromParams = (content) => {
|
|
4019
|
+
return content.replace(/^[ \t]*serverOutput:\s*SyncServerOutput<[^>]+>;?\s*\r?\n?/m, "").replace(/^[ \t]*serverOutput:\s*\{[^}]*\};?\s*\r?\n?/m, "");
|
|
4020
|
+
};
|
|
4021
|
+
var removeServerOutputFromDestructuring = (content) => {
|
|
4022
|
+
return content.replaceAll(/,\s*serverOutput(?=\s*[,}])/g, "").replaceAll(/serverOutput\s*,\s*/g, "");
|
|
4023
|
+
};
|
|
4024
|
+
var removeGeneratedTypeImports = (content) => {
|
|
4025
|
+
return content.replaceAll(/,\s*SyncClientInput(?=\s*[,}])/g, "").replaceAll(/,\s*SyncServerOutput(?=\s*[,}])/g, "");
|
|
4026
|
+
};
|
|
4027
|
+
var removeTypeAliases = (content) => {
|
|
4028
|
+
let result = content.replaceAll(/\/\/\s*Types are imported.*\n?/g, "");
|
|
4029
|
+
result = result.replaceAll(/type PagePath = '[^']*';\s*\n?/g, "");
|
|
4030
|
+
return result.replaceAll(/type SyncName = '[^']*';\s*\n?/g, "");
|
|
4031
|
+
};
|
|
3772
4032
|
var updateClientFileForDeletedServer = async (clientFilePath, clientInputTypes) => {
|
|
3773
4033
|
try {
|
|
3774
4034
|
let content = fs7.readFileSync(clientFilePath, "utf8");
|
|
3775
|
-
content = content
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
);
|
|
3779
|
-
content = content
|
|
3780
|
-
/^(\s*)clientInput:\s*\{[^}]*\}/m,
|
|
3781
|
-
`$1clientInput: ${clientInputTypes}`
|
|
3782
|
-
);
|
|
3783
|
-
content = content.replace(
|
|
3784
|
-
/^[ \t]*serverOutput:\s*SyncServerOutput<[^>]+>;?\s*\r?\n?/m,
|
|
3785
|
-
""
|
|
3786
|
-
);
|
|
3787
|
-
content = content.replace(
|
|
3788
|
-
/^[ \t]*serverOutput:\s*\{[^}]*\};?\s*\r?\n?/m,
|
|
3789
|
-
""
|
|
3790
|
-
);
|
|
3791
|
-
content = content.replaceAll(/,\s*serverOutput(?=\s*[,}])/g, "");
|
|
3792
|
-
content = content.replaceAll(/serverOutput\s*,\s*/g, "");
|
|
3793
|
-
content = content.replaceAll(/,\s*SyncClientInput(?=\s*[,}])/g, "");
|
|
3794
|
-
content = content.replaceAll(/,\s*SyncServerOutput(?=\s*[,}])/g, "");
|
|
3795
|
-
content = content.replaceAll(/\/\/\s*Types are imported.*\n?/g, "");
|
|
3796
|
-
content = content.replaceAll(/type PagePath = '[^']*';\s*\n?/g, "");
|
|
3797
|
-
content = content.replaceAll(/type SyncName = '[^']*';\s*\n?/g, "");
|
|
4035
|
+
content = inlineClientInputType(content, clientInputTypes);
|
|
4036
|
+
content = removeServerOutputFromParams(content);
|
|
4037
|
+
content = removeServerOutputFromDestructuring(content);
|
|
4038
|
+
content = removeGeneratedTypeImports(content);
|
|
4039
|
+
content = removeTypeAliases(content);
|
|
3798
4040
|
content = content.replaceAll(/\n{3,}/g, "\n\n");
|
|
3799
|
-
fs7.writeFileSync(clientFilePath, content, "
|
|
4041
|
+
fs7.writeFileSync(clientFilePath, content, "utf8");
|
|
3800
4042
|
console.log(`[TemplateInjector] Updated client file for deleted server (preserved code): ${clientFilePath}`);
|
|
3801
4043
|
return true;
|
|
3802
4044
|
} catch (error) {
|
|
@@ -3820,9 +4062,9 @@ var injectServerTemplateWithClientInput = async (serverFilePath, clientInputType
|
|
|
3820
4062
|
content = content.replaceAll("{{REL_PATH}}", relPath);
|
|
3821
4063
|
content = content.replace(
|
|
3822
4064
|
/clientInput:\s*\{[^}]*\}/s,
|
|
3823
|
-
`clientInput: ${clientInputTypes}`
|
|
4065
|
+
(_match) => `clientInput: ${clientInputTypes}`
|
|
3824
4066
|
);
|
|
3825
|
-
fs7.writeFileSync(serverFilePath, content, "
|
|
4067
|
+
fs7.writeFileSync(serverFilePath, content, "utf8");
|
|
3826
4068
|
console.log(`[TemplateInjector] Injected server template with clientInput: ${serverFilePath}`);
|
|
3827
4069
|
return true;
|
|
3828
4070
|
} catch (error) {
|
|
@@ -3834,7 +4076,7 @@ var injectServerTemplateWithClientInput = async (serverFilePath, clientInputType
|
|
|
3834
4076
|
// src/importDependencyGraph.ts
|
|
3835
4077
|
import fs8 from "fs";
|
|
3836
4078
|
import path11 from "path";
|
|
3837
|
-
import { ROOT_DIR as
|
|
4079
|
+
import { ROOT_DIR as ROOT_DIR5, getServerFunctionDirs as getServerFunctionDirs3, getSharedDir, getSrcDir as getSrcDir5 } from "@luckystack/core";
|
|
3838
4080
|
var SUPPORTED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"];
|
|
3839
4081
|
var normalizePath3 = (value) => path11.resolve(value).replaceAll("\\", "/");
|
|
3840
4082
|
var isSupportedScriptFile = (value) => {
|
|
@@ -3854,15 +4096,26 @@ var existsDirectory = (value) => {
|
|
|
3854
4096
|
return false;
|
|
3855
4097
|
}
|
|
3856
4098
|
};
|
|
3857
|
-
var collectScriptFiles = (dir, output) => {
|
|
4099
|
+
var collectScriptFiles = (dir, output, visited = /* @__PURE__ */ new Set()) => {
|
|
3858
4100
|
if (!existsDirectory(dir)) {
|
|
3859
4101
|
return;
|
|
3860
4102
|
}
|
|
3861
|
-
|
|
4103
|
+
let realDir;
|
|
4104
|
+
try {
|
|
4105
|
+
realDir = fs8.realpathSync(dir);
|
|
4106
|
+
} catch {
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
if (visited.has(realDir)) return;
|
|
4110
|
+
visited.add(realDir);
|
|
4111
|
+
const entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
3862
4112
|
for (const entry of entries) {
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
4113
|
+
if (entry.name === "node_modules") continue;
|
|
4114
|
+
const fullPath = path11.join(dir, entry.name);
|
|
4115
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
4116
|
+
if (existsDirectory(fullPath)) {
|
|
4117
|
+
collectScriptFiles(fullPath, output, visited);
|
|
4118
|
+
}
|
|
3866
4119
|
continue;
|
|
3867
4120
|
}
|
|
3868
4121
|
const normalizedPath = normalizePath3(fullPath);
|
|
@@ -3889,8 +4142,8 @@ var extractImportSpecifiers = (filePath) => {
|
|
|
3889
4142
|
try {
|
|
3890
4143
|
const source = fs8.readFileSync(filePath, "utf8");
|
|
3891
4144
|
const specifiers = /* @__PURE__ */ new Set();
|
|
3892
|
-
const importExportRegex = /(?:import|export)\s+(?:[
|
|
3893
|
-
const dynamicImportRegex = /import\(\s*['"]([^'"
|
|
4145
|
+
const importExportRegex = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
4146
|
+
const dynamicImportRegex = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
3894
4147
|
let match = null;
|
|
3895
4148
|
while ((match = importExportRegex.exec(source)) !== null) {
|
|
3896
4149
|
if (match[1]) specifiers.add(match[1]);
|
|
@@ -3935,7 +4188,7 @@ var resolveImportToFile = (importerPath, specifier) => {
|
|
|
3935
4188
|
return null;
|
|
3936
4189
|
}
|
|
3937
4190
|
if (specifier === "config") {
|
|
3938
|
-
return tryResolveWithExtensions(path11.join(
|
|
4191
|
+
return tryResolveWithExtensions(path11.join(ROOT_DIR5, "config"));
|
|
3939
4192
|
}
|
|
3940
4193
|
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
3941
4194
|
const relativeBase = path11.resolve(path11.dirname(importerPath), specifier);
|
|
@@ -3943,7 +4196,7 @@ var resolveImportToFile = (importerPath, specifier) => {
|
|
|
3943
4196
|
}
|
|
3944
4197
|
if (specifier.startsWith("src/") || specifier.startsWith("@/")) {
|
|
3945
4198
|
const sourcePath = specifier.startsWith("@/") ? specifier.slice(2) : specifier.slice(4);
|
|
3946
|
-
return tryResolveWithExtensions(path11.join(
|
|
4199
|
+
return tryResolveWithExtensions(path11.join(getSrcDir5(), sourcePath));
|
|
3947
4200
|
}
|
|
3948
4201
|
if (specifier.startsWith("shared/")) {
|
|
3949
4202
|
return tryResolveWithExtensions(path11.join(getSharedDir(), specifier.slice(7)));
|
|
@@ -3959,12 +4212,12 @@ var collectScopedFiles = () => {
|
|
|
3959
4212
|
return scopedFilesCache.files;
|
|
3960
4213
|
}
|
|
3961
4214
|
const files = /* @__PURE__ */ new Set();
|
|
3962
|
-
collectScriptFiles(
|
|
4215
|
+
collectScriptFiles(getSrcDir5(), files);
|
|
3963
4216
|
collectScriptFiles(getSharedDir(), files);
|
|
3964
4217
|
for (const dir of getServerFunctionDirs3()) {
|
|
3965
4218
|
collectScriptFiles(dir, files);
|
|
3966
4219
|
}
|
|
3967
|
-
const configFile = tryResolveWithExtensions(path11.join(
|
|
4220
|
+
const configFile = tryResolveWithExtensions(path11.join(ROOT_DIR5, "config"));
|
|
3968
4221
|
if (configFile) {
|
|
3969
4222
|
files.add(configFile);
|
|
3970
4223
|
}
|
|
@@ -4013,31 +4266,15 @@ var findDependentRouteFiles = (changedFilePath) => {
|
|
|
4013
4266
|
|
|
4014
4267
|
// src/hotReload.ts
|
|
4015
4268
|
import { tryCatch as tryCatch2, getProjectConfig, getLocaleReloader } from "@luckystack/core";
|
|
4016
|
-
var
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
const
|
|
4022
|
-
const
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
const sharedSegment = `/${pathsCfg.sharedDir.replaceAll("\\", "/")}/`;
|
|
4026
|
-
const serverFunctionsSegments = (pathsCfg.serverFunctionDirs ?? [pathsCfg.serverFunctionsDir]).map(
|
|
4027
|
-
(dir) => `/${dir.replaceAll("\\", "/")}/`
|
|
4028
|
-
);
|
|
4029
|
-
const isInServerFunctionsDir = (normalizedPath) => serverFunctionsSegments.some((segment) => normalizedPath.includes(segment));
|
|
4030
|
-
const localesSegment = `${srcSegment}_locales/`;
|
|
4031
|
-
const reloadTimers = /* @__PURE__ */ new Map();
|
|
4032
|
-
const pendingApiUpserts = /* @__PURE__ */ new Set();
|
|
4033
|
-
const pendingApiDeletes = /* @__PURE__ */ new Set();
|
|
4034
|
-
const pendingSyncUpserts = /* @__PURE__ */ new Set();
|
|
4035
|
-
const pendingSyncDeletes = /* @__PURE__ */ new Set();
|
|
4036
|
-
const normalizeFsPath = (value) => path12.resolve(value).replaceAll("\\", "/");
|
|
4037
|
-
const typeMapQueue = { pending: false, running: false };
|
|
4038
|
-
const runTypeMapRegeneration = () => {
|
|
4039
|
-
typeMapQueue.running = true;
|
|
4040
|
-
typeMapQueue.pending = false;
|
|
4269
|
+
var normalizeFsPath = (value) => path12.resolve(value).replaceAll("\\", "/");
|
|
4270
|
+
var isGeneratedPath = (normalizedPath) => {
|
|
4271
|
+
return normalizedPath.includes("apiTypes.generated.ts") || normalizedPath.includes("apiDocs.generated.json");
|
|
4272
|
+
};
|
|
4273
|
+
var createTypeMapQueue = () => {
|
|
4274
|
+
const queue = { pending: false, running: false };
|
|
4275
|
+
const run = () => {
|
|
4276
|
+
queue.running = true;
|
|
4277
|
+
queue.pending = false;
|
|
4041
4278
|
const startedAt = Date.now();
|
|
4042
4279
|
setImmediate(() => {
|
|
4043
4280
|
void (async () => {
|
|
@@ -4049,39 +4286,142 @@ var setupWatchers = () => {
|
|
|
4049
4286
|
} else {
|
|
4050
4287
|
console.log(`[HotReload] type map ready in ${Date.now() - startedAt}ms`, "green");
|
|
4051
4288
|
}
|
|
4052
|
-
|
|
4053
|
-
if (
|
|
4054
|
-
|
|
4289
|
+
queue.running = false;
|
|
4290
|
+
if (queue.pending) {
|
|
4291
|
+
run();
|
|
4055
4292
|
}
|
|
4056
4293
|
})();
|
|
4057
4294
|
});
|
|
4058
4295
|
};
|
|
4059
|
-
const
|
|
4060
|
-
if (
|
|
4061
|
-
|
|
4296
|
+
const request = () => {
|
|
4297
|
+
if (queue.running) {
|
|
4298
|
+
queue.pending = true;
|
|
4062
4299
|
return;
|
|
4063
4300
|
}
|
|
4064
|
-
|
|
4301
|
+
run();
|
|
4065
4302
|
};
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4303
|
+
return { request };
|
|
4304
|
+
};
|
|
4305
|
+
var createPendingChangeSets = () => ({
|
|
4306
|
+
apiUpserts: /* @__PURE__ */ new Set(),
|
|
4307
|
+
apiDeletes: /* @__PURE__ */ new Set(),
|
|
4308
|
+
syncUpserts: /* @__PURE__ */ new Set(),
|
|
4309
|
+
syncDeletes: /* @__PURE__ */ new Set()
|
|
4310
|
+
});
|
|
4311
|
+
var buildPathSegments = () => {
|
|
4312
|
+
const rules2 = getRoutingRules();
|
|
4313
|
+
const pathsCfg = getProjectConfig().paths;
|
|
4314
|
+
const srcSegment = `/${pathsCfg.srcDir.replaceAll("\\", "/")}/`;
|
|
4315
|
+
const sharedSegment = `/${pathsCfg.sharedDir.replaceAll("\\", "/")}/`;
|
|
4316
|
+
return {
|
|
4317
|
+
apiMarkerSlash: `/${rules2.apiMarker}/`,
|
|
4318
|
+
syncMarkerSlash: `/${rules2.syncMarker}/`,
|
|
4319
|
+
apiMarkerNoLead: `${rules2.apiMarker}/`,
|
|
4320
|
+
syncMarkerNoLead: `${rules2.syncMarker}/`,
|
|
4321
|
+
srcSegment,
|
|
4322
|
+
sharedSegment,
|
|
4323
|
+
localesSegment: `${srcSegment}_locales/`,
|
|
4324
|
+
serverFunctionsSegments: pathsCfg.serverFunctionDirs.map(
|
|
4325
|
+
(dir) => `/${dir.replaceAll("\\", "/")}/`
|
|
4326
|
+
)
|
|
4327
|
+
};
|
|
4328
|
+
};
|
|
4329
|
+
var makeIsInServerFunctionsDir = (segments) => (normalizedPath) => segments.serverFunctionsSegments.some((seg) => normalizedPath.includes(seg));
|
|
4330
|
+
var makeIsRouteDependencyFile = (segments) => (normalizedPath) => {
|
|
4331
|
+
if (!normalizedPath.endsWith(".ts") && !normalizedPath.endsWith(".tsx")) return false;
|
|
4332
|
+
if (!normalizedPath.includes(segments.srcSegment)) return false;
|
|
4333
|
+
if (normalizedPath.includes(segments.apiMarkerSlash) || normalizedPath.includes(segments.syncMarkerSlash)) return false;
|
|
4334
|
+
if (isGeneratedPath(normalizedPath)) return false;
|
|
4335
|
+
return true;
|
|
4336
|
+
};
|
|
4337
|
+
var makeIsSharedDependencyFile = (segments, isInServerFunctionsDir) => (normalizedPath) => {
|
|
4338
|
+
if (!(normalizedPath.endsWith(".ts") || normalizedPath.endsWith(".tsx"))) return false;
|
|
4339
|
+
if (normalizedPath.includes(segments.sharedSegment)) return true;
|
|
4340
|
+
return isInServerFunctionsDir(normalizedPath);
|
|
4341
|
+
};
|
|
4342
|
+
var makeIsTypeMapRelevantFile = (segments) => (normalizedPath) => {
|
|
4343
|
+
if (isGeneratedPath(normalizedPath)) return false;
|
|
4344
|
+
if (!(normalizedPath.endsWith(".ts") || normalizedPath.endsWith(".tsx"))) return false;
|
|
4345
|
+
if (normalizedPath.includes(segments.apiMarkerSlash) || normalizedPath.includes(segments.syncMarkerSlash)) return false;
|
|
4346
|
+
return normalizedPath.includes(segments.srcSegment) || normalizedPath.endsWith("/config.ts") || normalizedPath.includes(segments.sharedSegment);
|
|
4347
|
+
};
|
|
4348
|
+
var makeIsLocaleFile = (segments) => (normalizedPath) => normalizedPath.includes(segments.localesSegment) && normalizedPath.endsWith(".json");
|
|
4349
|
+
var createReloadScheduler = (debounceMs) => {
|
|
4350
|
+
const timers = /* @__PURE__ */ new Map();
|
|
4351
|
+
return (key, task, delay = debounceMs()) => {
|
|
4352
|
+
const active = timers.get(key);
|
|
4353
|
+
if (active) clearTimeout(active);
|
|
4354
|
+
const timer = setTimeout(() => {
|
|
4355
|
+
timers.delete(key);
|
|
4356
|
+
Promise.resolve(task()).catch((error) => {
|
|
4357
|
+
console.log(`[HotReload] Scheduled task threw an error: ${String(error)}`, "red");
|
|
4358
|
+
});
|
|
4359
|
+
}, delay);
|
|
4360
|
+
timers.set(key, timer);
|
|
4361
|
+
};
|
|
4362
|
+
};
|
|
4363
|
+
var mountWatchers = (onAdd, onChange, onDelete, onFunctionChange) => {
|
|
4364
|
+
const devConfig = getProjectConfig().dev;
|
|
4365
|
+
const pathsConfig = getProjectConfig().paths;
|
|
4366
|
+
watch(pathsConfig.srcDir, {
|
|
4367
|
+
ignoreInitial: true,
|
|
4368
|
+
awaitWriteFinish: {
|
|
4369
|
+
stabilityThreshold: devConfig.watcherStabilityThresholdMs,
|
|
4370
|
+
pollInterval: devConfig.watcherPollIntervalMs
|
|
4069
4371
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4372
|
+
}).on("add", onAdd).on("change", onChange).on("unlink", onDelete);
|
|
4373
|
+
for (const dir of pathsConfig.serverFunctionDirs) {
|
|
4374
|
+
watch(dir, { ignoreInitial: true }).on("add", onFunctionChange).on("change", onFunctionChange).on("unlink", onFunctionChange);
|
|
4375
|
+
}
|
|
4376
|
+
watch(pathsConfig.sharedDir, { ignoreInitial: true }).on("add", onFunctionChange).on("change", onFunctionChange).on("unlink", onFunctionChange);
|
|
4377
|
+
};
|
|
4378
|
+
var setupWatchers = () => {
|
|
4379
|
+
const isDevMode = process.env.NODE_ENV !== "production";
|
|
4380
|
+
if (!isDevMode) return;
|
|
4381
|
+
const segments = buildPathSegments();
|
|
4382
|
+
const isInServerFunctionsDir = makeIsInServerFunctionsDir(segments);
|
|
4383
|
+
const isRouteDependencyFile = makeIsRouteDependencyFile(segments);
|
|
4384
|
+
const isSharedDependencyFile = makeIsSharedDependencyFile(segments, isInServerFunctionsDir);
|
|
4385
|
+
const isTypeMapRelevantFile = makeIsTypeMapRelevantFile(segments);
|
|
4386
|
+
const isLocaleFile = makeIsLocaleFile(segments);
|
|
4387
|
+
const typeMap = createTypeMapQueue();
|
|
4388
|
+
const pending = createPendingChangeSets();
|
|
4389
|
+
const scheduleReload = createReloadScheduler(() => getProjectConfig().dev.hotReloadDebounceMs);
|
|
4390
|
+
const processPendingApiChanges = async ({ regenerateTypeMap = false } = {}) => {
|
|
4391
|
+
const deletePaths = [...pending.apiDeletes];
|
|
4392
|
+
const upsertPaths = [...pending.apiUpserts];
|
|
4393
|
+
pending.apiDeletes.clear();
|
|
4394
|
+
pending.apiUpserts.clear();
|
|
4395
|
+
if (regenerateTypeMap) {
|
|
4396
|
+
console.log(`[HotReload] API routes changed (add/delete), scheduling type map regeneration`, "blue");
|
|
4397
|
+
typeMap.request();
|
|
4072
4398
|
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4399
|
+
for (const deletePath of deletePaths) {
|
|
4400
|
+
removeApiFromFile(deletePath);
|
|
4401
|
+
console.log(`[HotReload] API removed: ${deletePath}`, "yellow");
|
|
4075
4402
|
}
|
|
4076
|
-
|
|
4077
|
-
|
|
4403
|
+
for (const upsertPath of upsertPaths) {
|
|
4404
|
+
await upsertApiFromFile(upsertPath);
|
|
4405
|
+
console.log(`[HotReload] API reloaded: ${upsertPath}`, "green");
|
|
4078
4406
|
}
|
|
4079
|
-
return true;
|
|
4080
4407
|
};
|
|
4081
|
-
const
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4408
|
+
const processPendingSyncChanges = async ({ regenerateTypeMap = false } = {}) => {
|
|
4409
|
+
const deletePaths = [...pending.syncDeletes];
|
|
4410
|
+
const upsertPaths = [...pending.syncUpserts];
|
|
4411
|
+
pending.syncDeletes.clear();
|
|
4412
|
+
pending.syncUpserts.clear();
|
|
4413
|
+
if (regenerateTypeMap) {
|
|
4414
|
+
console.log(`[HotReload] Sync routes changed (add/delete), scheduling type map regeneration`, "blue");
|
|
4415
|
+
typeMap.request();
|
|
4416
|
+
}
|
|
4417
|
+
for (const deletePath of deletePaths) {
|
|
4418
|
+
removeSyncFromFile(deletePath);
|
|
4419
|
+
console.log(`[HotReload] Sync removed: ${deletePath}`, "yellow");
|
|
4420
|
+
}
|
|
4421
|
+
for (const upsertPath of upsertPaths) {
|
|
4422
|
+
await upsertSyncFromFile(upsertPath);
|
|
4423
|
+
console.log(`[HotReload] Sync reloaded: ${upsertPath}`, "green");
|
|
4424
|
+
}
|
|
4085
4425
|
};
|
|
4086
4426
|
const enqueueAffectedRoutesFromDependency = (changedPath) => {
|
|
4087
4427
|
const affectedRoutes = findDependentRouteFiles(changedPath);
|
|
@@ -4092,13 +4432,13 @@ var setupWatchers = () => {
|
|
|
4092
4432
|
let queuedApiCount = 0;
|
|
4093
4433
|
let queuedSyncCount = 0;
|
|
4094
4434
|
for (const routePath of affectedRoutes) {
|
|
4095
|
-
if (routePath.includes(apiMarkerSlash)) {
|
|
4096
|
-
|
|
4097
|
-
|
|
4435
|
+
if (routePath.includes(segments.apiMarkerSlash)) {
|
|
4436
|
+
pending.apiDeletes.delete(routePath);
|
|
4437
|
+
pending.apiUpserts.add(routePath);
|
|
4098
4438
|
queuedApiCount += 1;
|
|
4099
|
-
} else if (routePath.includes(syncMarkerSlash)) {
|
|
4100
|
-
|
|
4101
|
-
|
|
4439
|
+
} else if (routePath.includes(segments.syncMarkerSlash)) {
|
|
4440
|
+
pending.syncDeletes.delete(routePath);
|
|
4441
|
+
pending.syncUpserts.add(routePath);
|
|
4102
4442
|
queuedSyncCount += 1;
|
|
4103
4443
|
}
|
|
4104
4444
|
}
|
|
@@ -4117,36 +4457,13 @@ var setupWatchers = () => {
|
|
|
4117
4457
|
});
|
|
4118
4458
|
}
|
|
4119
4459
|
};
|
|
4120
|
-
const
|
|
4121
|
-
|
|
4122
|
-
};
|
|
4123
|
-
const isTypeMapRelevantFile = (normalizedPath) => {
|
|
4124
|
-
if (isGeneratedPath(normalizedPath)) return false;
|
|
4125
|
-
if (!(normalizedPath.endsWith(".ts") || normalizedPath.endsWith(".tsx"))) return false;
|
|
4126
|
-
if (normalizedPath.includes(apiMarkerSlash) || normalizedPath.includes(syncMarkerSlash)) return false;
|
|
4127
|
-
return normalizedPath.includes(srcSegment) || normalizedPath.endsWith("/config.ts") || normalizedPath.includes(sharedSegment);
|
|
4128
|
-
};
|
|
4129
|
-
const isLocaleFile = (normalizedPath) => {
|
|
4130
|
-
return normalizedPath.includes(localesSegment) && normalizedPath.endsWith(".json");
|
|
4131
|
-
};
|
|
4132
|
-
const scheduleReload = (key, task, delay = getProjectConfig().dev.hotReloadDebounceMs) => {
|
|
4133
|
-
const activeTimer = reloadTimers.get(key);
|
|
4134
|
-
if (activeTimer) {
|
|
4135
|
-
clearTimeout(activeTimer);
|
|
4136
|
-
}
|
|
4137
|
-
const timer = setTimeout(() => {
|
|
4138
|
-
reloadTimers.delete(key);
|
|
4139
|
-
void task();
|
|
4140
|
-
}, delay);
|
|
4141
|
-
reloadTimers.set(key, timer);
|
|
4142
|
-
};
|
|
4143
|
-
const handleAdd = async (path13) => {
|
|
4144
|
-
const normalizedPath = normalizeFsPath(path13);
|
|
4460
|
+
const handleAdd = async (filePath) => {
|
|
4461
|
+
const normalizedPath = normalizeFsPath(filePath);
|
|
4145
4462
|
invalidateGraphForFile(normalizedPath);
|
|
4146
4463
|
const routeValidationMessage = getRouteFilenameValidationMessage(normalizedPath);
|
|
4147
4464
|
if (routeValidationMessage) {
|
|
4148
|
-
if (shouldInjectTemplate(
|
|
4149
|
-
const injected = await injectTemplate(
|
|
4465
|
+
if (shouldInjectTemplate(filePath, { isNewFile: true })) {
|
|
4466
|
+
const injected = await injectTemplate(filePath);
|
|
4150
4467
|
if (injected) {
|
|
4151
4468
|
return;
|
|
4152
4469
|
}
|
|
@@ -4154,87 +4471,57 @@ var setupWatchers = () => {
|
|
|
4154
4471
|
console.log(`[HotReload] ${routeValidationMessage}`, "yellow");
|
|
4155
4472
|
return;
|
|
4156
4473
|
}
|
|
4157
|
-
if (shouldInjectTemplate(
|
|
4474
|
+
if (shouldInjectTemplate(filePath, { isNewFile: true })) {
|
|
4158
4475
|
if (isSyncServerFile(normalizedPath)) {
|
|
4159
4476
|
const clientPath = getPairedSyncFile(normalizedPath);
|
|
4160
4477
|
if (clientPath && fs9.existsSync(clientPath) && !isEmptyFile(clientPath)) {
|
|
4161
4478
|
const clientInputTypes = extractClientInputFromFile(clientPath);
|
|
4162
4479
|
if (clientInputTypes) {
|
|
4163
|
-
await injectServerTemplateWithClientInput(
|
|
4164
|
-
|
|
4165
|
-
await updateClientFileForPairedServer(clientPath);
|
|
4166
|
-
|
|
4480
|
+
await injectServerTemplateWithClientInput(filePath, clientInputTypes);
|
|
4481
|
+
typeMap.request();
|
|
4482
|
+
const clientRewritten = await updateClientFileForPairedServer(clientPath);
|
|
4483
|
+
if (!clientRewritten) {
|
|
4484
|
+
console.log(`[HotReload] Paired client rewrite failed, skipping upsert for: ${clientPath}`, "yellow");
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
await upsertSyncFromFile(filePath);
|
|
4167
4488
|
await upsertSyncFromFile(clientPath);
|
|
4168
4489
|
return;
|
|
4169
4490
|
}
|
|
4170
4491
|
}
|
|
4171
4492
|
}
|
|
4172
|
-
const injected = await injectTemplate(
|
|
4493
|
+
const injected = await injectTemplate(filePath);
|
|
4173
4494
|
if (injected) {
|
|
4174
4495
|
return;
|
|
4175
4496
|
}
|
|
4176
4497
|
}
|
|
4177
|
-
if (normalizedPath.includes(apiMarkerNoLead)) {
|
|
4178
|
-
|
|
4179
|
-
|
|
4498
|
+
if (normalizedPath.includes(segments.apiMarkerNoLead)) {
|
|
4499
|
+
pending.apiDeletes.delete(normalizedPath);
|
|
4500
|
+
pending.apiUpserts.add(normalizedPath);
|
|
4180
4501
|
scheduleReload("api", async () => {
|
|
4181
4502
|
await processPendingApiChanges({ regenerateTypeMap: true });
|
|
4182
4503
|
});
|
|
4183
4504
|
return;
|
|
4184
4505
|
}
|
|
4185
|
-
if (normalizedPath.includes(syncMarkerNoLead)) {
|
|
4186
|
-
|
|
4187
|
-
|
|
4506
|
+
if (normalizedPath.includes(segments.syncMarkerNoLead)) {
|
|
4507
|
+
pending.syncDeletes.delete(normalizedPath);
|
|
4508
|
+
pending.syncUpserts.add(normalizedPath);
|
|
4188
4509
|
scheduleReload("sync", async () => {
|
|
4189
4510
|
await processPendingSyncChanges({ regenerateTypeMap: true });
|
|
4190
4511
|
});
|
|
4191
4512
|
return;
|
|
4192
4513
|
}
|
|
4193
|
-
handleChange(
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
const deletePaths = [...pendingApiDeletes];
|
|
4197
|
-
const upsertPaths = [...pendingApiUpserts];
|
|
4198
|
-
pendingApiDeletes.clear();
|
|
4199
|
-
pendingApiUpserts.clear();
|
|
4200
|
-
if (regenerateTypeMap) {
|
|
4201
|
-
console.log(`[HotReload] API routes changed (add/delete), scheduling type map regeneration`, "blue");
|
|
4202
|
-
requestTypeMapRegeneration();
|
|
4203
|
-
}
|
|
4204
|
-
for (const deletePath of deletePaths) {
|
|
4205
|
-
removeApiFromFile(deletePath);
|
|
4206
|
-
console.log(`[HotReload] API removed: ${deletePath}`, "yellow");
|
|
4207
|
-
}
|
|
4208
|
-
for (const upsertPath of upsertPaths) {
|
|
4209
|
-
await upsertApiFromFile(upsertPath);
|
|
4210
|
-
console.log(`[HotReload] API reloaded: ${upsertPath}`, "green");
|
|
4211
|
-
}
|
|
4212
|
-
};
|
|
4213
|
-
const processPendingSyncChanges = async ({ regenerateTypeMap = false } = {}) => {
|
|
4214
|
-
const deletePaths = [...pendingSyncDeletes];
|
|
4215
|
-
const upsertPaths = [...pendingSyncUpserts];
|
|
4216
|
-
pendingSyncDeletes.clear();
|
|
4217
|
-
pendingSyncUpserts.clear();
|
|
4218
|
-
if (regenerateTypeMap) {
|
|
4219
|
-
console.log(`[HotReload] Sync routes changed (add/delete), scheduling type map regeneration`, "blue");
|
|
4220
|
-
requestTypeMapRegeneration();
|
|
4221
|
-
}
|
|
4222
|
-
for (const deletePath of deletePaths) {
|
|
4223
|
-
removeSyncFromFile(deletePath);
|
|
4224
|
-
console.log(`[HotReload] Sync removed: ${deletePath}`, "yellow");
|
|
4225
|
-
}
|
|
4226
|
-
for (const upsertPath of upsertPaths) {
|
|
4227
|
-
await upsertSyncFromFile(upsertPath);
|
|
4228
|
-
console.log(`[HotReload] Sync reloaded: ${upsertPath}`, "green");
|
|
4229
|
-
}
|
|
4514
|
+
handleChange(filePath).catch((error) => {
|
|
4515
|
+
console.log(`[HotReload] handleChange threw an error: ${String(error)}`, "red");
|
|
4516
|
+
});
|
|
4230
4517
|
};
|
|
4231
|
-
const handleChange = async (
|
|
4232
|
-
const normalizedPath = normalizeFsPath(
|
|
4518
|
+
const handleChange = async (filePath) => {
|
|
4519
|
+
const normalizedPath = normalizeFsPath(filePath);
|
|
4233
4520
|
invalidateGraphForFile(normalizedPath);
|
|
4234
4521
|
const routeValidationMessage = getRouteFilenameValidationMessage(normalizedPath);
|
|
4235
4522
|
if (routeValidationMessage) {
|
|
4236
|
-
if (shouldInjectTemplate(
|
|
4237
|
-
const injected = await injectTemplate(
|
|
4523
|
+
if (shouldInjectTemplate(filePath)) {
|
|
4524
|
+
const injected = await injectTemplate(filePath);
|
|
4238
4525
|
if (injected) {
|
|
4239
4526
|
return;
|
|
4240
4527
|
}
|
|
@@ -4242,8 +4529,8 @@ var setupWatchers = () => {
|
|
|
4242
4529
|
console.log(`[HotReload] ${routeValidationMessage}`, "yellow");
|
|
4243
4530
|
return;
|
|
4244
4531
|
}
|
|
4245
|
-
if (shouldInjectTemplate(
|
|
4246
|
-
const injected = await injectTemplate(
|
|
4532
|
+
if (shouldInjectTemplate(filePath)) {
|
|
4533
|
+
const injected = await injectTemplate(filePath);
|
|
4247
4534
|
if (injected) {
|
|
4248
4535
|
return;
|
|
4249
4536
|
}
|
|
@@ -4260,7 +4547,7 @@ var setupWatchers = () => {
|
|
|
4260
4547
|
if (isRouteDependencyFile(normalizedPath)) {
|
|
4261
4548
|
scheduleReload("typemap", () => {
|
|
4262
4549
|
console.log(`[HotReload] Route dependency changed, scheduling type map regeneration`, "blue");
|
|
4263
|
-
|
|
4550
|
+
typeMap.request();
|
|
4264
4551
|
});
|
|
4265
4552
|
enqueueAffectedRoutesFromDependency(normalizedPath);
|
|
4266
4553
|
return;
|
|
@@ -4268,26 +4555,26 @@ var setupWatchers = () => {
|
|
|
4268
4555
|
if (isTypeMapRelevantFile(normalizedPath)) {
|
|
4269
4556
|
scheduleReload("typemap", () => {
|
|
4270
4557
|
console.log(`[HotReload] Route dependency changed, scheduling type map regeneration`, "blue");
|
|
4271
|
-
|
|
4558
|
+
typeMap.request();
|
|
4272
4559
|
});
|
|
4273
4560
|
}
|
|
4274
|
-
if (normalizedPath.includes(apiMarkerNoLead)) {
|
|
4561
|
+
if (normalizedPath.includes(segments.apiMarkerNoLead)) {
|
|
4275
4562
|
scheduleReload("typemap", () => {
|
|
4276
4563
|
console.log(`[HotReload] API changed, scheduling type map regeneration`, "blue");
|
|
4277
|
-
|
|
4564
|
+
typeMap.request();
|
|
4278
4565
|
});
|
|
4279
|
-
|
|
4280
|
-
|
|
4566
|
+
pending.apiDeletes.delete(normalizedPath);
|
|
4567
|
+
pending.apiUpserts.add(normalizedPath);
|
|
4281
4568
|
scheduleReload("api", async () => {
|
|
4282
4569
|
await processPendingApiChanges();
|
|
4283
4570
|
});
|
|
4284
|
-
} else if (normalizedPath.includes(syncMarkerNoLead)) {
|
|
4571
|
+
} else if (normalizedPath.includes(segments.syncMarkerNoLead)) {
|
|
4285
4572
|
scheduleReload("typemap", () => {
|
|
4286
4573
|
console.log(`[HotReload] Sync changed, scheduling type map regeneration`, "blue");
|
|
4287
|
-
|
|
4574
|
+
typeMap.request();
|
|
4288
4575
|
});
|
|
4289
|
-
|
|
4290
|
-
|
|
4576
|
+
pending.syncDeletes.delete(normalizedPath);
|
|
4577
|
+
pending.syncUpserts.add(normalizedPath);
|
|
4291
4578
|
scheduleReload("sync", async () => {
|
|
4292
4579
|
await processPendingSyncChanges();
|
|
4293
4580
|
});
|
|
@@ -4297,15 +4584,15 @@ var setupWatchers = () => {
|
|
|
4297
4584
|
const normalizedPath = normalizeFsPath(changedPath);
|
|
4298
4585
|
invalidateGraphForFile(normalizedPath);
|
|
4299
4586
|
scheduleReload("functions", async () => {
|
|
4300
|
-
|
|
4587
|
+
typeMap.request();
|
|
4301
4588
|
await initializeFunctions();
|
|
4302
4589
|
});
|
|
4303
4590
|
if (isSharedDependencyFile(normalizedPath)) {
|
|
4304
4591
|
enqueueAffectedRoutesFromDependency(normalizedPath);
|
|
4305
4592
|
}
|
|
4306
4593
|
};
|
|
4307
|
-
const handleDelete =
|
|
4308
|
-
const normalizedPath = normalizeFsPath(
|
|
4594
|
+
const handleDelete = (filePath) => {
|
|
4595
|
+
const normalizedPath = normalizeFsPath(filePath);
|
|
4309
4596
|
invalidateGraphForFile(normalizedPath);
|
|
4310
4597
|
if (isGeneratedPath(normalizedPath)) {
|
|
4311
4598
|
return;
|
|
@@ -4319,7 +4606,7 @@ var setupWatchers = () => {
|
|
|
4319
4606
|
if (isRouteDependencyFile(normalizedPath)) {
|
|
4320
4607
|
scheduleReload("typemap", () => {
|
|
4321
4608
|
console.log(`[HotReload] Route dependency deleted, scheduling type map regeneration`, "blue");
|
|
4322
|
-
|
|
4609
|
+
typeMap.request();
|
|
4323
4610
|
});
|
|
4324
4611
|
enqueueAffectedRoutesFromDependency(normalizedPath);
|
|
4325
4612
|
return;
|
|
@@ -4327,26 +4614,26 @@ var setupWatchers = () => {
|
|
|
4327
4614
|
if (isTypeMapRelevantFile(normalizedPath)) {
|
|
4328
4615
|
scheduleReload("typemap", () => {
|
|
4329
4616
|
console.log(`[HotReload] Route dependency deleted, scheduling type map regeneration`, "blue");
|
|
4330
|
-
|
|
4617
|
+
typeMap.request();
|
|
4331
4618
|
});
|
|
4332
4619
|
}
|
|
4333
|
-
if (normalizedPath.includes(apiMarkerNoLead)) {
|
|
4620
|
+
if (normalizedPath.includes(segments.apiMarkerNoLead)) {
|
|
4334
4621
|
scheduleReload("typemap", () => {
|
|
4335
4622
|
console.log(`[HotReload] API deleted, scheduling type map regeneration`, "blue");
|
|
4336
|
-
|
|
4623
|
+
typeMap.request();
|
|
4337
4624
|
});
|
|
4338
|
-
|
|
4339
|
-
|
|
4625
|
+
pending.apiUpserts.delete(normalizedPath);
|
|
4626
|
+
pending.apiDeletes.add(normalizedPath);
|
|
4340
4627
|
scheduleReload("api", async () => {
|
|
4341
4628
|
await processPendingApiChanges();
|
|
4342
4629
|
});
|
|
4343
|
-
} else if (normalizedPath.includes(syncMarkerNoLead)) {
|
|
4630
|
+
} else if (normalizedPath.includes(segments.syncMarkerNoLead)) {
|
|
4344
4631
|
scheduleReload("typemap", () => {
|
|
4345
4632
|
console.log(`[HotReload] Sync deleted, scheduling type map regeneration`, "blue");
|
|
4346
|
-
|
|
4633
|
+
typeMap.request();
|
|
4347
4634
|
});
|
|
4348
|
-
|
|
4349
|
-
|
|
4635
|
+
pending.syncUpserts.delete(normalizedPath);
|
|
4636
|
+
pending.syncDeletes.add(normalizedPath);
|
|
4350
4637
|
scheduleReload("sync", async () => {
|
|
4351
4638
|
if (isSyncServerFile(normalizedPath)) {
|
|
4352
4639
|
const clientPath = getPairedSyncFile(normalizedPath);
|
|
@@ -4354,31 +4641,30 @@ var setupWatchers = () => {
|
|
|
4354
4641
|
const pagePath = extractSyncPagePath2(normalizedPath);
|
|
4355
4642
|
const syncName = extractSyncName2(normalizedPath);
|
|
4356
4643
|
const clientInputTypes = extractClientInputFromGeneratedTypes(pagePath, syncName);
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
}
|
|
4644
|
+
await updateClientFileForDeletedServer(
|
|
4645
|
+
clientPath,
|
|
4646
|
+
clientInputTypes ?? "{\n // Types were in _server.ts - please add them here\n }"
|
|
4647
|
+
);
|
|
4362
4648
|
}
|
|
4363
4649
|
}
|
|
4364
4650
|
await processPendingSyncChanges();
|
|
4365
4651
|
});
|
|
4366
4652
|
}
|
|
4367
4653
|
};
|
|
4368
|
-
const
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
}
|
|
4381
|
-
|
|
4654
|
+
const safeHandleAdd = (p) => {
|
|
4655
|
+
void handleAdd(p).catch((error) => {
|
|
4656
|
+
console.log(`[HotReload] handleAdd threw an error: ${String(error)}`, "red");
|
|
4657
|
+
});
|
|
4658
|
+
};
|
|
4659
|
+
const safeHandleChange = (p) => {
|
|
4660
|
+
void handleChange(p).catch((error) => {
|
|
4661
|
+
console.log(`[HotReload] handleChange threw an error: ${String(error)}`, "red");
|
|
4662
|
+
});
|
|
4663
|
+
};
|
|
4664
|
+
const safeHandleDelete = (p) => {
|
|
4665
|
+
handleDelete(p);
|
|
4666
|
+
};
|
|
4667
|
+
mountWatchers(safeHandleAdd, safeHandleChange, safeHandleDelete, handleFunctionChange);
|
|
4382
4668
|
setImmediate(() => {
|
|
4383
4669
|
void (async () => {
|
|
4384
4670
|
const [err] = await tryCatch2(() => {
|
|
@@ -4392,172 +4678,6 @@ var setupWatchers = () => {
|
|
|
4392
4678
|
})();
|
|
4393
4679
|
});
|
|
4394
4680
|
};
|
|
4395
|
-
|
|
4396
|
-
// src/validateDeploy.ts
|
|
4397
|
-
var validateDeploy = ({
|
|
4398
|
-
services,
|
|
4399
|
-
deploy,
|
|
4400
|
-
env = process.env
|
|
4401
|
-
}) => {
|
|
4402
|
-
const findings = [];
|
|
4403
|
-
const serviceNames = new Set(Object.keys(services.services));
|
|
4404
|
-
const servicesInPresets = /* @__PURE__ */ new Map();
|
|
4405
|
-
for (const [presetKey, preset] of Object.entries(services.presets)) {
|
|
4406
|
-
for (const service of preset.services) {
|
|
4407
|
-
const existing = servicesInPresets.get(service) ?? [];
|
|
4408
|
-
existing.push(presetKey);
|
|
4409
|
-
servicesInPresets.set(service, existing);
|
|
4410
|
-
}
|
|
4411
|
-
}
|
|
4412
|
-
for (const service of serviceNames) {
|
|
4413
|
-
const owners = servicesInPresets.get(service) ?? [];
|
|
4414
|
-
if (owners.length === 0) {
|
|
4415
|
-
findings.push({
|
|
4416
|
-
severity: "error",
|
|
4417
|
-
code: "service-unassigned",
|
|
4418
|
-
message: `Service "${service}" is declared in services.services but not assigned to any preset. Every service must belong to exactly one preset.`,
|
|
4419
|
-
location: `services.config.ts > services.${service}`
|
|
4420
|
-
});
|
|
4421
|
-
} else if (owners.length > 1) {
|
|
4422
|
-
findings.push({
|
|
4423
|
-
severity: "error",
|
|
4424
|
-
code: "service-in-multiple-presets",
|
|
4425
|
-
message: `Service "${service}" is in multiple presets: ${owners.join(", ")}. A service may belong to exactly one preset.`,
|
|
4426
|
-
location: `services.config.ts > services.${service}`
|
|
4427
|
-
});
|
|
4428
|
-
}
|
|
4429
|
-
}
|
|
4430
|
-
for (const [presetKey, preset] of Object.entries(services.presets)) {
|
|
4431
|
-
for (const service of preset.services) {
|
|
4432
|
-
if (!serviceNames.has(service)) {
|
|
4433
|
-
findings.push({
|
|
4434
|
-
severity: "error",
|
|
4435
|
-
code: "preset-references-unknown-service",
|
|
4436
|
-
message: `Preset "${presetKey}" references unknown service "${service}". Add it to services.services or remove the reference.`,
|
|
4437
|
-
location: `services.config.ts > presets.${presetKey}.services`
|
|
4438
|
-
});
|
|
4439
|
-
}
|
|
4440
|
-
}
|
|
4441
|
-
}
|
|
4442
|
-
const environments = deploy.environments ?? {};
|
|
4443
|
-
const resourceNames = new Set(Object.keys(deploy.resources));
|
|
4444
|
-
for (const [envKey, envDef] of Object.entries(environments)) {
|
|
4445
|
-
for (const [bindingService, bindingUrl] of Object.entries(envDef.bindings)) {
|
|
4446
|
-
if (!serviceNames.has(bindingService)) {
|
|
4447
|
-
findings.push({
|
|
4448
|
-
severity: "error",
|
|
4449
|
-
code: "binding-references-unknown-service",
|
|
4450
|
-
message: `Environment "${envKey}" binds unknown service "${bindingService}". Either add it to services.services or remove the binding.`,
|
|
4451
|
-
location: `deploy.config.ts > environments.${envKey}.bindings`
|
|
4452
|
-
});
|
|
4453
|
-
}
|
|
4454
|
-
let parsed = null;
|
|
4455
|
-
try {
|
|
4456
|
-
parsed = new URL(bindingUrl);
|
|
4457
|
-
} catch {
|
|
4458
|
-
findings.push({
|
|
4459
|
-
severity: "error",
|
|
4460
|
-
code: "binding-invalid-url",
|
|
4461
|
-
message: `Environment "${envKey}" service "${bindingService}" binding is not a valid URL: "${bindingUrl}".`,
|
|
4462
|
-
location: `deploy.config.ts > environments.${envKey}.bindings.${bindingService}`
|
|
4463
|
-
});
|
|
4464
|
-
}
|
|
4465
|
-
if (parsed && !parsed.port) {
|
|
4466
|
-
findings.push({
|
|
4467
|
-
severity: "error",
|
|
4468
|
-
code: "binding-missing-port",
|
|
4469
|
-
message: `Environment "${envKey}" service "${bindingService}" binding "${bindingUrl}" is missing an explicit port. Set one to avoid the URL-spec default (80/443) silently winning.`,
|
|
4470
|
-
location: `deploy.config.ts > environments.${envKey}.bindings.${bindingService}`
|
|
4471
|
-
});
|
|
4472
|
-
}
|
|
4473
|
-
}
|
|
4474
|
-
if (!resourceNames.has(envDef.redis)) {
|
|
4475
|
-
findings.push({
|
|
4476
|
-
severity: "error",
|
|
4477
|
-
code: "unknown-redis-resource",
|
|
4478
|
-
message: `Environment "${envKey}" references unknown redis resource "${envDef.redis}". Add it to deploy.resources.`,
|
|
4479
|
-
location: `deploy.config.ts > environments.${envKey}.redis`
|
|
4480
|
-
});
|
|
4481
|
-
}
|
|
4482
|
-
if (!resourceNames.has(envDef.mongo)) {
|
|
4483
|
-
findings.push({
|
|
4484
|
-
severity: "error",
|
|
4485
|
-
code: "unknown-mongo-resource",
|
|
4486
|
-
message: `Environment "${envKey}" references unknown mongo resource "${envDef.mongo}". Add it to deploy.resources.`,
|
|
4487
|
-
location: `deploy.config.ts > environments.${envKey}.mongo`
|
|
4488
|
-
});
|
|
4489
|
-
}
|
|
4490
|
-
if (envDef.fallback) {
|
|
4491
|
-
const fallbackEnv = environments[envDef.fallback];
|
|
4492
|
-
if (fallbackEnv) {
|
|
4493
|
-
if (fallbackEnv.redis !== envDef.redis) {
|
|
4494
|
-
findings.push({
|
|
4495
|
-
severity: "error",
|
|
4496
|
-
code: "fallback-redis-mismatch",
|
|
4497
|
-
message: `Environment "${envKey}" and its fallback "${envDef.fallback}" reference different redis resources ("${envDef.redis}" vs "${fallbackEnv.redis}"). Fallback envs must share the same redis resource key to keep the shared-Redis invariant.`,
|
|
4498
|
-
location: `deploy.config.ts > environments.${envKey}.fallback`
|
|
4499
|
-
});
|
|
4500
|
-
}
|
|
4501
|
-
if (fallbackEnv.mongo !== envDef.mongo) {
|
|
4502
|
-
findings.push({
|
|
4503
|
-
severity: "error",
|
|
4504
|
-
code: "fallback-mongo-mismatch",
|
|
4505
|
-
message: `Environment "${envKey}" and its fallback "${envDef.fallback}" reference different mongo resources ("${envDef.mongo}" vs "${fallbackEnv.mongo}"). Fallback envs must share the same mongo resource key.`,
|
|
4506
|
-
location: `deploy.config.ts > environments.${envKey}.fallback`
|
|
4507
|
-
});
|
|
4508
|
-
}
|
|
4509
|
-
} else {
|
|
4510
|
-
findings.push({
|
|
4511
|
-
severity: "error",
|
|
4512
|
-
code: "unknown-fallback-env",
|
|
4513
|
-
message: `Environment "${envKey}" has fallback "${envDef.fallback}" which does not exist in deploy.environments.`,
|
|
4514
|
-
location: `deploy.config.ts > environments.${envKey}.fallback`
|
|
4515
|
-
});
|
|
4516
|
-
}
|
|
4517
|
-
}
|
|
4518
|
-
}
|
|
4519
|
-
for (const [resourceKey, resource] of Object.entries(deploy.resources)) {
|
|
4520
|
-
const urlValue = env[resource.urlEnvKey];
|
|
4521
|
-
if (urlValue === void 0 || urlValue === "") {
|
|
4522
|
-
findings.push({
|
|
4523
|
-
severity: "warning",
|
|
4524
|
-
code: "missing-resource-env-var",
|
|
4525
|
-
message: `Resource "${resourceKey}" expects env var "${resource.urlEnvKey}" but it is unset. The framework will still boot if the env is provided at runtime, but config-time tooling cannot verify the value.`,
|
|
4526
|
-
location: `deploy.config.ts > resources.${resourceKey}.urlEnvKey`
|
|
4527
|
-
});
|
|
4528
|
-
}
|
|
4529
|
-
for (const synchronizedKey of resource.synchronizedEnvKeys ?? []) {
|
|
4530
|
-
const value = env[synchronizedKey];
|
|
4531
|
-
if (value === void 0 || value === "") {
|
|
4532
|
-
findings.push({
|
|
4533
|
-
severity: "warning",
|
|
4534
|
-
code: "missing-synchronized-env-var",
|
|
4535
|
-
message: `Resource "${resourceKey}" requires synchronized env var "${synchronizedKey}" but it is unset. Boot will compute an empty hash and fall back to warning-only.`,
|
|
4536
|
-
location: `deploy.config.ts > resources.${resourceKey}.synchronizedEnvKeys`
|
|
4537
|
-
});
|
|
4538
|
-
}
|
|
4539
|
-
}
|
|
4540
|
-
}
|
|
4541
|
-
for (const service of serviceNames) {
|
|
4542
|
-
const boundIn = Object.entries(environments).filter(([, env2]) => service in env2.bindings);
|
|
4543
|
-
if (boundIn.length === 0) {
|
|
4544
|
-
findings.push({
|
|
4545
|
-
severity: "warning",
|
|
4546
|
-
code: "service-bound-in-no-environment",
|
|
4547
|
-
message: `Service "${service}" is in services.services and assigned to a preset but never bound in any environment. Requests for this service will fall through to the missing-service handler.`,
|
|
4548
|
-
location: `services.config.ts > services.${service}`
|
|
4549
|
-
});
|
|
4550
|
-
}
|
|
4551
|
-
}
|
|
4552
|
-
const errorCount = findings.filter((f) => f.severity === "error").length;
|
|
4553
|
-
const warningCount = findings.filter((f) => f.severity === "warning").length;
|
|
4554
|
-
return {
|
|
4555
|
-
ok: errorCount === 0,
|
|
4556
|
-
findings,
|
|
4557
|
-
errorCount,
|
|
4558
|
-
warningCount
|
|
4559
|
-
};
|
|
4560
|
-
};
|
|
4561
4681
|
export {
|
|
4562
4682
|
API_VERSION_TOKEN_REGEX,
|
|
4563
4683
|
BUILT_IN_TEMPLATE_FILENAMES,
|