@luckystack/devkit 0.8.6 → 0.8.7
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 +11 -0
- package/dist/index.js +501 -493
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,11 +3,12 @@ import {
|
|
|
3
3
|
} from "./chunk-IWGWJU4M.js";
|
|
4
4
|
|
|
5
5
|
// src/typeMap/discovery.ts
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { ROOT_DIR } from "@luckystack/core";
|
|
6
|
+
import fs2 from "fs";
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
import { ROOT_DIR as ROOT_DIR2 } from "@luckystack/core";
|
|
9
9
|
|
|
10
10
|
// src/routingRules.ts
|
|
11
|
+
import { isTestDirectory } from "@luckystack/core";
|
|
11
12
|
import { validatePagePath as corePagePath } from "@luckystack/core";
|
|
12
13
|
var DEFAULT_RULES = {
|
|
13
14
|
apiMarker: "_api",
|
|
@@ -66,24 +67,371 @@ var isSyncFileName = (fileName) => {
|
|
|
66
67
|
var isRouteTestFile = (fileNameOrPath) => {
|
|
67
68
|
return fileNameOrPath.endsWith(".tests.ts");
|
|
68
69
|
};
|
|
69
|
-
var
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
var isNonRouteDirectory = (directoryName) => directoryName.startsWith(getRoutingRules().privateFolderPrefix) || isTestDirectory(directoryName);
|
|
71
|
+
var validatePagePath = (srcRelativePath) => {
|
|
72
|
+
const rules2 = getRoutingRules();
|
|
73
|
+
const pageRules = {
|
|
74
|
+
privateFolderPrefix: rules2.privateFolderPrefix,
|
|
75
|
+
scaffoldIgnoredFolders: rules2.scaffoldIgnoredFolders
|
|
76
|
+
};
|
|
77
|
+
return corePagePath(srcRelativePath, pageRules);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/routeNamingValidation.ts
|
|
81
|
+
import fs from "fs";
|
|
82
|
+
import path from "path";
|
|
83
|
+
import { ROOT_DIR, isTestFile } from "@luckystack/core";
|
|
84
|
+
|
|
85
|
+
// src/routeConventions.ts
|
|
86
|
+
var API_VERSION_TOKEN_REGEX = /_v(\d+)$/;
|
|
87
|
+
var SYNC_VERSION_TOKEN_REGEX = /_(server|client)_v(\d+)$/;
|
|
88
|
+
var ROUTE_NAMING_RULES = {
|
|
89
|
+
api: "<name>_v<number>.ts",
|
|
90
|
+
syncServer: "<name>_server_v<number>.ts",
|
|
91
|
+
syncClient: "<name>_client_v<number>.ts"
|
|
92
|
+
};
|
|
93
|
+
var ROUTE_NAMING_EXAMPLES = {
|
|
94
|
+
api: "updateUser_v1.ts",
|
|
95
|
+
syncServer: "updateCounter_server_v1.ts",
|
|
96
|
+
syncClient: "updateCounter_client_v1.ts"
|
|
97
|
+
};
|
|
98
|
+
var isVersionedApiFileName = (fileName) => isApiFileName(fileName);
|
|
99
|
+
var isVersionedSyncFileName = (fileName) => isSyncFileName(fileName);
|
|
100
|
+
var isVersionedSyncServerFileName = (fileName) => isSyncServerFileName(fileName);
|
|
101
|
+
var isVersionedSyncClientFileName = (fileName) => isSyncClientFileName(fileName);
|
|
102
|
+
|
|
103
|
+
// src/internal/mapUtils.ts
|
|
104
|
+
var getOrInit = (map, key, factory) => {
|
|
105
|
+
const existing = map.get(key);
|
|
106
|
+
if (existing !== void 0) return existing;
|
|
107
|
+
const fresh = factory();
|
|
108
|
+
map.set(key, fresh);
|
|
109
|
+
return fresh;
|
|
110
|
+
};
|
|
111
|
+
var mustGet = (map, key, label) => {
|
|
112
|
+
const value = map.get(key);
|
|
113
|
+
if (value === void 0) {
|
|
114
|
+
throw new Error(`[devkit] invariant violation: '${String(key)}' missing from ${label}`);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/routeNamingValidation.ts
|
|
120
|
+
var normalizePath = (value) => {
|
|
121
|
+
return value.replaceAll("\\", "/");
|
|
122
|
+
};
|
|
123
|
+
var toRel = (absolute) => path.relative(ROOT_DIR, absolute).replaceAll("\\", "/");
|
|
124
|
+
var isInsidePrivateRouteSubfolder = (fullPath) => {
|
|
125
|
+
const { privateFolderPrefix, apiMarker, syncMarker } = getRoutingRules();
|
|
126
|
+
const segments = normalizePath(fullPath).split("/");
|
|
127
|
+
const markerIndex = segments.findLastIndex((segment) => segment === apiMarker || segment === syncMarker);
|
|
128
|
+
if (markerIndex === -1) return false;
|
|
129
|
+
return segments.slice(markerIndex + 1).some((segment) => segment.startsWith(privateFolderPrefix));
|
|
130
|
+
};
|
|
131
|
+
var isRouteSurfaceFile = (fullPath) => !isInsidePrivateRouteSubfolder(fullPath) && !isTestFile(fullPath);
|
|
132
|
+
var walkRouteFiles = (dir, results = []) => {
|
|
133
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
134
|
+
const apiSeg = apiMarkerSegment();
|
|
135
|
+
const syncSeg = syncMarkerSegment();
|
|
136
|
+
const { ignore } = getRoutingRules();
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
const fullPath = path.join(dir, entry.name);
|
|
139
|
+
const normalizedFullPath = normalizePath(fullPath);
|
|
140
|
+
if (ignore(toRel(fullPath))) continue;
|
|
141
|
+
if (isInsidePrivateRouteSubfolder(fullPath)) continue;
|
|
142
|
+
if (entry.isDirectory()) {
|
|
143
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
walkRouteFiles(fullPath, results);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!entry.isFile() || !entry.name.endsWith(".ts")) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (isRouteTestFile(entry.name)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (normalizedFullPath.includes(apiSeg) || normalizedFullPath.includes(syncSeg)) {
|
|
156
|
+
results.push(fullPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return results;
|
|
160
|
+
};
|
|
161
|
+
var getFileRouteToken = ({
|
|
162
|
+
normalizedFilePath,
|
|
163
|
+
marker
|
|
164
|
+
}) => {
|
|
165
|
+
const markerIndex = normalizedFilePath.indexOf(marker);
|
|
166
|
+
if (markerIndex === -1) {
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
const tokenStart = markerIndex + marker.length;
|
|
170
|
+
return normalizedFilePath.slice(tokenStart, normalizedFilePath.length - ".ts".length);
|
|
171
|
+
};
|
|
172
|
+
var validateRouteFilePath = (filePath) => {
|
|
173
|
+
const issues = [];
|
|
174
|
+
const normalizedFilePath = normalizePath(path.resolve(filePath));
|
|
175
|
+
const fileName = path.basename(filePath);
|
|
176
|
+
const apiSeg = apiMarkerSegment();
|
|
177
|
+
const syncSeg = syncMarkerSegment();
|
|
178
|
+
if (normalizedFilePath.includes(apiSeg)) {
|
|
179
|
+
const apiRouteToken = getFileRouteToken({ normalizedFilePath, marker: apiSeg });
|
|
180
|
+
if (apiRouteToken.includes("/")) {
|
|
181
|
+
issues.push({
|
|
182
|
+
kind: "api",
|
|
183
|
+
filePath: normalizedFilePath,
|
|
184
|
+
reason: 'API route token cannot contain nested path segments ("/").',
|
|
185
|
+
expected: ROUTE_NAMING_RULES.api
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (!isVersionedApiFileName(fileName)) {
|
|
189
|
+
issues.push({
|
|
190
|
+
kind: "api",
|
|
191
|
+
filePath: normalizedFilePath,
|
|
192
|
+
reason: "API filename does not match versioned naming.",
|
|
193
|
+
expected: ROUTE_NAMING_RULES.api
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (normalizedFilePath.includes(syncSeg)) {
|
|
198
|
+
const syncRouteToken = getFileRouteToken({ normalizedFilePath, marker: syncSeg });
|
|
199
|
+
if (syncRouteToken.includes("/")) {
|
|
200
|
+
issues.push({
|
|
201
|
+
kind: "sync",
|
|
202
|
+
filePath: normalizedFilePath,
|
|
203
|
+
reason: 'Sync route token cannot contain nested path segments ("/").',
|
|
204
|
+
expected: `${ROUTE_NAMING_RULES.syncServer} or ${ROUTE_NAMING_RULES.syncClient}`
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (!isVersionedSyncFileName(fileName)) {
|
|
208
|
+
issues.push({
|
|
209
|
+
kind: "sync",
|
|
210
|
+
filePath: normalizedFilePath,
|
|
211
|
+
reason: "Sync filename does not match versioned naming.",
|
|
212
|
+
expected: `${ROUTE_NAMING_RULES.syncServer} or ${ROUTE_NAMING_RULES.syncClient}`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return issues;
|
|
217
|
+
};
|
|
218
|
+
var resolveApiRouteKey = ({
|
|
219
|
+
srcDir,
|
|
220
|
+
filePath
|
|
221
|
+
}) => {
|
|
222
|
+
const rules2 = getRoutingRules();
|
|
223
|
+
const relativePath = normalizePath(path.relative(srcDir, filePath));
|
|
224
|
+
const segments = relativePath.split("/");
|
|
225
|
+
const apiIndex = segments.indexOf(rules2.apiMarker);
|
|
226
|
+
if (apiIndex === -1 || apiIndex === segments.length - 1) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
const pageLocation = segments.slice(0, apiIndex).join("/");
|
|
230
|
+
const apiFilePath = segments.slice(apiIndex + 1).join("/");
|
|
231
|
+
const rawApiName = apiFilePath.replace(/\.ts$/, "");
|
|
232
|
+
const versionMatch = rawApiName.match(rules2.apiVersionRegex);
|
|
233
|
+
if (!versionMatch) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const version = `v${versionMatch[1]}`;
|
|
237
|
+
const apiName = rawApiName.replace(rules2.apiVersionRegex, "");
|
|
238
|
+
const mappedPageLocation = pageLocation || "system";
|
|
239
|
+
return `api/${mappedPageLocation}/${apiName}/${version}`;
|
|
240
|
+
};
|
|
241
|
+
var resolveSyncRouteKey = ({
|
|
242
|
+
srcDir,
|
|
243
|
+
filePath
|
|
244
|
+
}) => {
|
|
245
|
+
const rules2 = getRoutingRules();
|
|
246
|
+
const relativePath = normalizePath(path.relative(srcDir, filePath));
|
|
247
|
+
const segments = relativePath.split("/");
|
|
248
|
+
const syncIndex = segments.indexOf(rules2.syncMarker);
|
|
249
|
+
if (syncIndex === -1 || syncIndex === segments.length - 1) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const pageLocation = segments.slice(0, syncIndex).join("/");
|
|
253
|
+
const syncFilePath = segments.slice(syncIndex + 1).join("/");
|
|
254
|
+
const rawSyncName = syncFilePath.replace(/\.ts$/, "");
|
|
255
|
+
const syncMatch = rawSyncName.match(rules2.syncVersionRegex);
|
|
256
|
+
if (!syncMatch) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const kind = syncMatch[1];
|
|
260
|
+
const version = `v${syncMatch[2]}`;
|
|
261
|
+
const syncName = rawSyncName.replace(rules2.syncVersionRegex, "");
|
|
262
|
+
const routeBaseKey = pageLocation ? `sync/${pageLocation}/${syncName}/${version}` : `sync/${syncName}/${version}`;
|
|
263
|
+
return `${routeBaseKey}_${kind}`;
|
|
264
|
+
};
|
|
265
|
+
var collectInvalidRouteNamingIssues = (srcDir) => {
|
|
266
|
+
const allRouteFiles = walkRouteFiles(srcDir);
|
|
267
|
+
const issues = [];
|
|
268
|
+
for (const filePath of allRouteFiles) {
|
|
269
|
+
issues.push(...validateRouteFilePath(filePath));
|
|
270
|
+
}
|
|
271
|
+
return issues.toSorted((a, b) => a.filePath.localeCompare(b.filePath));
|
|
272
|
+
};
|
|
273
|
+
var collectDuplicateNormalizedRouteKeyIssues = (srcDir) => {
|
|
274
|
+
const allRouteFiles = walkRouteFiles(srcDir);
|
|
275
|
+
const routeKeyToFilePaths = /* @__PURE__ */ new Map();
|
|
276
|
+
const routeKeyKinds = /* @__PURE__ */ new Map();
|
|
277
|
+
const apiSeg = apiMarkerSegment();
|
|
278
|
+
const syncSeg = syncMarkerSegment();
|
|
279
|
+
for (const filePath of allRouteFiles) {
|
|
280
|
+
const normalizedFilePath = normalizePath(path.resolve(filePath));
|
|
281
|
+
if (normalizedFilePath.includes(apiSeg)) {
|
|
282
|
+
const routeKey = resolveApiRouteKey({ srcDir, filePath });
|
|
283
|
+
if (!routeKey) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
getOrInit(routeKeyToFilePaths, routeKey, () => []).push(normalizedFilePath);
|
|
287
|
+
routeKeyKinds.set(routeKey, "api");
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (normalizedFilePath.includes(syncSeg)) {
|
|
291
|
+
const routeKey = resolveSyncRouteKey({ srcDir, filePath });
|
|
292
|
+
if (!routeKey) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
getOrInit(routeKeyToFilePaths, routeKey, () => []).push(normalizedFilePath);
|
|
296
|
+
routeKeyKinds.set(routeKey, "sync");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const issues = [];
|
|
300
|
+
for (const [routeKey, filePaths] of routeKeyToFilePaths.entries()) {
|
|
301
|
+
if (filePaths.length < 2) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
issues.push({
|
|
305
|
+
kind: routeKeyKinds.get(routeKey) ?? "api",
|
|
306
|
+
routeKey,
|
|
307
|
+
filePaths: filePaths.toSorted((a, b) => a.localeCompare(b))
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return issues.toSorted((a, b) => a.routeKey.localeCompare(b.routeKey));
|
|
311
|
+
};
|
|
312
|
+
var formatRouteNamingIssues = ({
|
|
313
|
+
issues,
|
|
314
|
+
context
|
|
315
|
+
}) => {
|
|
316
|
+
const plural = issues.length === 1 ? "" : "s";
|
|
317
|
+
const header = `[RouteNaming] Found ${issues.length} invalid API/sync route file${plural} while ${context}.`;
|
|
318
|
+
const details = issues.map((issue, index) => {
|
|
319
|
+
return `${index + 1}. [${issue.kind.toUpperCase()}] ${issue.filePath}
|
|
320
|
+
reason: ${issue.reason}
|
|
321
|
+
expected: ${issue.expected}`;
|
|
322
|
+
}).join("\n");
|
|
323
|
+
return `${header}
|
|
324
|
+
${details}`;
|
|
325
|
+
};
|
|
326
|
+
var formatDuplicateRouteKeyIssues = ({
|
|
327
|
+
issues,
|
|
328
|
+
context
|
|
329
|
+
}) => {
|
|
330
|
+
const plural = issues.length === 1 ? "" : "s";
|
|
331
|
+
const header = `[RouteNaming] Found ${issues.length} duplicate normalized route key${plural} while ${context}.`;
|
|
332
|
+
const details = issues.map((issue, index) => {
|
|
333
|
+
const fileList = issue.filePaths.map((filePath) => ` - ${filePath}`).join("\n");
|
|
334
|
+
return `${index + 1}. [${issue.kind.toUpperCase()}] ${issue.routeKey}
|
|
335
|
+
files:
|
|
336
|
+
${fileList}`;
|
|
337
|
+
}).join("\n");
|
|
338
|
+
return `${header}
|
|
339
|
+
${details}`;
|
|
340
|
+
};
|
|
341
|
+
var assertValidRouteNaming = ({
|
|
342
|
+
srcDir,
|
|
343
|
+
context
|
|
344
|
+
}) => {
|
|
345
|
+
const issues = collectInvalidRouteNamingIssues(srcDir);
|
|
346
|
+
if (issues.length === 0) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
throw new Error(formatRouteNamingIssues({ issues, context }));
|
|
350
|
+
};
|
|
351
|
+
var assertNoDuplicateNormalizedRouteKeys = ({
|
|
352
|
+
srcDir,
|
|
353
|
+
context
|
|
354
|
+
}) => {
|
|
355
|
+
const issues = collectDuplicateNormalizedRouteKeyIssues(srcDir);
|
|
356
|
+
if (issues.length === 0) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
throw new Error(formatDuplicateRouteKeyIssues({ issues, context }));
|
|
360
|
+
};
|
|
361
|
+
var walkPageFiles = (dir, results = []) => {
|
|
362
|
+
let entries;
|
|
363
|
+
try {
|
|
364
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
365
|
+
} catch {
|
|
366
|
+
return results;
|
|
367
|
+
}
|
|
368
|
+
for (const entry of entries) {
|
|
369
|
+
const fullPath = path.join(dir, entry.name);
|
|
370
|
+
if (entry.isDirectory()) {
|
|
371
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
372
|
+
walkPageFiles(fullPath, results);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (!entry.isFile()) continue;
|
|
376
|
+
if (entry.name === "page.tsx" || entry.name === "page.jsx") {
|
|
377
|
+
results.push(fullPath);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return results;
|
|
381
|
+
};
|
|
382
|
+
var collectDuplicatePageRoutes = (srcDir) => {
|
|
383
|
+
const pageFiles = walkPageFiles(srcDir);
|
|
384
|
+
const routeToFiles = /* @__PURE__ */ new Map();
|
|
385
|
+
for (const absoluteFilePath of pageFiles) {
|
|
386
|
+
const relative = normalizePath(path.relative(srcDir, absoluteFilePath));
|
|
387
|
+
const result = validatePagePath(relative);
|
|
388
|
+
if (!result.valid || !result.route) continue;
|
|
389
|
+
const list = routeToFiles.get(result.route) ?? [];
|
|
390
|
+
list.push(normalizePath(path.relative(ROOT_DIR, absoluteFilePath)));
|
|
391
|
+
routeToFiles.set(result.route, list);
|
|
392
|
+
}
|
|
393
|
+
const issues = [];
|
|
394
|
+
for (const [route, filePaths] of routeToFiles) {
|
|
395
|
+
if (filePaths.length > 1) {
|
|
396
|
+
issues.push({ route, filePaths });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return issues;
|
|
400
|
+
};
|
|
401
|
+
var formatDuplicatePageRouteIssues = ({
|
|
402
|
+
issues,
|
|
403
|
+
context
|
|
404
|
+
}) => {
|
|
405
|
+
const plural = issues.length === 1 ? "" : "s";
|
|
406
|
+
const header = `[RouteNaming] Found ${issues.length} duplicate page route${plural} while ${context}.`;
|
|
407
|
+
const details = issues.map((issue, index) => {
|
|
408
|
+
const fileList = issue.filePaths.map((filePath) => ` - ${filePath}`).join("\n");
|
|
409
|
+
return `${index + 1}. ${issue.route}
|
|
410
|
+
files:
|
|
411
|
+
${fileList}
|
|
412
|
+
fix: rename or move one of the files so only one page.tsx resolves to "${issue.route}". Remember that "_<folder>" segments are stripped from the URL (invisible-parent rule).`;
|
|
413
|
+
}).join("\n");
|
|
414
|
+
return `${header}
|
|
415
|
+
${details}`;
|
|
416
|
+
};
|
|
417
|
+
var assertNoDuplicatePageRoutes = ({
|
|
418
|
+
srcDir,
|
|
419
|
+
context
|
|
420
|
+
}) => {
|
|
421
|
+
const issues = collectDuplicatePageRoutes(srcDir);
|
|
422
|
+
if (issues.length === 0) return;
|
|
423
|
+
throw new Error(formatDuplicatePageRouteIssues({ issues, context }));
|
|
76
424
|
};
|
|
77
425
|
|
|
78
426
|
// src/typeMap/discovery.ts
|
|
79
427
|
var toForwardSlashRelative = (absolute) => {
|
|
80
|
-
const rel =
|
|
428
|
+
const rel = path2.relative(ROOT_DIR2, absolute);
|
|
81
429
|
return rel.replaceAll("\\", "/");
|
|
82
430
|
};
|
|
83
431
|
var walkFiles = (dir, matcher, results = [], visited = /* @__PURE__ */ new Set()) => {
|
|
84
432
|
let realDir;
|
|
85
433
|
try {
|
|
86
|
-
realDir =
|
|
434
|
+
realDir = fs2.realpathSync(dir);
|
|
87
435
|
} catch {
|
|
88
436
|
return results;
|
|
89
437
|
}
|
|
@@ -91,15 +439,15 @@ var walkFiles = (dir, matcher, results = [], visited = /* @__PURE__ */ new Set()
|
|
|
91
439
|
visited.add(realDir);
|
|
92
440
|
const { ignore } = getRoutingRules();
|
|
93
441
|
try {
|
|
94
|
-
const entries =
|
|
442
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
95
443
|
for (const entry of entries) {
|
|
96
|
-
const fullPath =
|
|
444
|
+
const fullPath = path2.join(dir, entry.name);
|
|
97
445
|
const relativePath = toForwardSlashRelative(fullPath);
|
|
98
446
|
if (ignore(relativePath)) continue;
|
|
99
447
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
100
448
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
101
449
|
try {
|
|
102
|
-
if (
|
|
450
|
+
if (fs2.statSync(fullPath).isDirectory()) {
|
|
103
451
|
walkFiles(fullPath, matcher, results, visited);
|
|
104
452
|
}
|
|
105
453
|
} catch {
|
|
@@ -119,47 +467,27 @@ var findAllApiFiles = (srcDir) => {
|
|
|
119
467
|
const apiSegment = apiMarkerSegment();
|
|
120
468
|
return walkFiles(srcDir, (fullPath, entryName) => {
|
|
121
469
|
const normalized = fullPath.replaceAll("\\", "/");
|
|
122
|
-
return isApiFileName(entryName) && normalized.includes(apiSegment);
|
|
470
|
+
return isApiFileName(entryName) && normalized.includes(apiSegment) && isRouteSurfaceFile(normalized);
|
|
123
471
|
});
|
|
124
472
|
};
|
|
125
473
|
var findAllSyncServerFiles = (srcDir) => {
|
|
126
474
|
const syncSegment = syncMarkerSegment();
|
|
127
475
|
return walkFiles(srcDir, (fullPath, entryName) => {
|
|
128
476
|
const normalized = fullPath.replaceAll("\\", "/");
|
|
129
|
-
return isSyncServerFileName(entryName) && normalized.includes(syncSegment);
|
|
477
|
+
return isSyncServerFileName(entryName) && normalized.includes(syncSegment) && isRouteSurfaceFile(normalized);
|
|
130
478
|
});
|
|
131
479
|
};
|
|
132
480
|
var findAllSyncClientFiles = (srcDir) => {
|
|
133
481
|
const syncSegment = syncMarkerSegment();
|
|
134
482
|
return walkFiles(srcDir, (fullPath, entryName) => {
|
|
135
483
|
const normalized = fullPath.replaceAll("\\", "/");
|
|
136
|
-
return isSyncClientFileName(entryName) && normalized.includes(syncSegment);
|
|
484
|
+
return isSyncClientFileName(entryName) && normalized.includes(syncSegment) && isRouteSurfaceFile(normalized);
|
|
137
485
|
});
|
|
138
486
|
};
|
|
139
487
|
|
|
140
488
|
// src/typeMap/routeMeta.ts
|
|
141
|
-
import
|
|
489
|
+
import path3 from "path";
|
|
142
490
|
import { getSrcDir } from "@luckystack/core";
|
|
143
|
-
|
|
144
|
-
// src/routeConventions.ts
|
|
145
|
-
var API_VERSION_TOKEN_REGEX = /_v(\d+)$/;
|
|
146
|
-
var SYNC_VERSION_TOKEN_REGEX = /_(server|client)_v(\d+)$/;
|
|
147
|
-
var ROUTE_NAMING_RULES = {
|
|
148
|
-
api: "<name>_v<number>.ts",
|
|
149
|
-
syncServer: "<name>_server_v<number>.ts",
|
|
150
|
-
syncClient: "<name>_client_v<number>.ts"
|
|
151
|
-
};
|
|
152
|
-
var ROUTE_NAMING_EXAMPLES = {
|
|
153
|
-
api: "updateUser_v1.ts",
|
|
154
|
-
syncServer: "updateCounter_server_v1.ts",
|
|
155
|
-
syncClient: "updateCounter_client_v1.ts"
|
|
156
|
-
};
|
|
157
|
-
var isVersionedApiFileName = (fileName) => isApiFileName(fileName);
|
|
158
|
-
var isVersionedSyncFileName = (fileName) => isSyncFileName(fileName);
|
|
159
|
-
var isVersionedSyncServerFileName = (fileName) => isSyncServerFileName(fileName);
|
|
160
|
-
var isVersionedSyncClientFileName = (fileName) => isSyncClientFileName(fileName);
|
|
161
|
-
|
|
162
|
-
// src/typeMap/routeMeta.ts
|
|
163
491
|
var VERSION_SUFFIX_REGEX = API_VERSION_TOKEN_REGEX;
|
|
164
492
|
var escapeRegExp = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
165
493
|
var stripVersionSuffix = (name) => {
|
|
@@ -173,7 +501,7 @@ var extractVersionFromName = (name) => {
|
|
|
173
501
|
var extractPagePath = (filePath) => {
|
|
174
502
|
const normalized = filePath.replaceAll("\\", "/");
|
|
175
503
|
const srcDirNormalized = getSrcDir().replaceAll("\\", "/");
|
|
176
|
-
const rel =
|
|
504
|
+
const rel = path3.posix.relative(srcDirNormalized, normalized);
|
|
177
505
|
if (rel.startsWith("..")) {
|
|
178
506
|
throw new Error(`[routeMeta] file is outside srcDir \u2014 cannot extract page path: ${filePath}`);
|
|
179
507
|
}
|
|
@@ -191,20 +519,20 @@ var extractApiName = (filePath) => {
|
|
|
191
519
|
const normalized = filePath.replaceAll("\\", "/");
|
|
192
520
|
const { apiMarker } = getRoutingRules();
|
|
193
521
|
const match = new RegExp(String.raw`${escapeRegExp(apiMarker)}\/(.+)\.ts$`).exec(normalized);
|
|
194
|
-
const rawName = match?.[1] ??
|
|
522
|
+
const rawName = match?.[1] ?? path3.basename(filePath, ".ts");
|
|
195
523
|
return stripVersionSuffix(rawName);
|
|
196
524
|
};
|
|
197
525
|
var extractApiVersion = (filePath) => {
|
|
198
526
|
const normalized = filePath.replaceAll("\\", "/");
|
|
199
527
|
const { apiMarker } = getRoutingRules();
|
|
200
528
|
const match = new RegExp(String.raw`${escapeRegExp(apiMarker)}\/(.+)\.ts$`).exec(normalized);
|
|
201
|
-
const rawName = match?.[1] ??
|
|
529
|
+
const rawName = match?.[1] ?? path3.basename(filePath, ".ts");
|
|
202
530
|
return extractVersionFromName(rawName) ?? "v1";
|
|
203
531
|
};
|
|
204
532
|
var extractSyncPagePath = (filePath) => {
|
|
205
533
|
const normalized = filePath.replaceAll("\\", "/");
|
|
206
534
|
const srcDirNormalized = getSrcDir().replaceAll("\\", "/");
|
|
207
|
-
const rel =
|
|
535
|
+
const rel = path3.posix.relative(srcDirNormalized, normalized);
|
|
208
536
|
const { syncMarker } = getRoutingRules();
|
|
209
537
|
const match = new RegExp(String.raw`^(?:(.+?)\/)${escapeRegExp(syncMarker)}\/`).exec(rel);
|
|
210
538
|
if (match) {
|
|
@@ -220,7 +548,7 @@ var extractSyncName = (filePath) => {
|
|
|
220
548
|
const { syncMarker } = getRoutingRules();
|
|
221
549
|
const match = new RegExp(String.raw`${escapeRegExp(syncMarker)}\/(.+)\.ts$`).exec(normalized);
|
|
222
550
|
if (!match) {
|
|
223
|
-
const basename =
|
|
551
|
+
const basename = path3.basename(filePath, ".ts");
|
|
224
552
|
return basename.replace(SYNC_VERSION_TOKEN_REGEX, "");
|
|
225
553
|
}
|
|
226
554
|
return (match[1] ?? "").replace(SYNC_VERSION_TOKEN_REGEX, "");
|
|
@@ -230,7 +558,7 @@ var extractSyncVersion = (filePath) => {
|
|
|
230
558
|
const { syncMarker } = getRoutingRules();
|
|
231
559
|
const match = new RegExp(String.raw`${escapeRegExp(syncMarker)}\/(.+)\.ts$`).exec(normalized);
|
|
232
560
|
if (!match) {
|
|
233
|
-
const basename =
|
|
561
|
+
const basename = path3.basename(filePath, ".ts");
|
|
234
562
|
const versionMatch2 = SYNC_VERSION_TOKEN_REGEX.exec(basename);
|
|
235
563
|
return versionMatch2 ? `v${versionMatch2[2] ?? "1"}` : "v1";
|
|
236
564
|
}
|
|
@@ -240,7 +568,7 @@ var extractSyncVersion = (filePath) => {
|
|
|
240
568
|
|
|
241
569
|
// src/typeMap/apiMeta.ts
|
|
242
570
|
import * as ts from "typescript";
|
|
243
|
-
import
|
|
571
|
+
import fs3 from "fs";
|
|
244
572
|
import { inferHttpMethod, tryCatchSync } from "@luckystack/core";
|
|
245
573
|
var findExportedConst = (sourceFile, name) => {
|
|
246
574
|
for (const statement of sourceFile.statements) {
|
|
@@ -262,7 +590,7 @@ var unwrapExpression = (node) => {
|
|
|
262
590
|
};
|
|
263
591
|
var extractHttpMethod = (filePath, apiName) => {
|
|
264
592
|
const [error, method] = tryCatchSync(() => {
|
|
265
|
-
const content =
|
|
593
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
266
594
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
267
595
|
const decl = findExportedConst(sourceFile, "httpMethod");
|
|
268
596
|
const initializer = decl?.initializer ? unwrapExpression(decl.initializer) : void 0;
|
|
@@ -279,7 +607,7 @@ var extractHttpMethod = (filePath, apiName) => {
|
|
|
279
607
|
};
|
|
280
608
|
var extractRateLimit = (filePath) => {
|
|
281
609
|
const [error, rateLimit] = tryCatchSync(() => {
|
|
282
|
-
const content =
|
|
610
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
283
611
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
284
612
|
const decl = findExportedConst(sourceFile, "rateLimit");
|
|
285
613
|
if (decl?.initializer) {
|
|
@@ -325,7 +653,7 @@ var commentToString = (commentValue) => {
|
|
|
325
653
|
};
|
|
326
654
|
var extractDocsMeta = (filePath) => {
|
|
327
655
|
const [error, docsMeta] = tryCatchSync(() => {
|
|
328
|
-
const content =
|
|
656
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
329
657
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
330
658
|
const result = {};
|
|
331
659
|
const consumeTag = (tag) => {
|
|
@@ -362,7 +690,7 @@ var extractDocsMeta = (filePath) => {
|
|
|
362
690
|
};
|
|
363
691
|
var extractAuth = (filePath) => {
|
|
364
692
|
const [, auth] = tryCatchSync(() => {
|
|
365
|
-
const content =
|
|
693
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
366
694
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
367
695
|
const decl = findExportedConst(sourceFile, "auth");
|
|
368
696
|
const authInitializer = decl?.initializer ? unwrapExpression(decl.initializer) : void 0;
|
|
@@ -392,27 +720,11 @@ var extractAuth = (filePath) => {
|
|
|
392
720
|
};
|
|
393
721
|
|
|
394
722
|
// src/typeMap/emitterArtifacts.ts
|
|
395
|
-
import
|
|
396
|
-
import
|
|
723
|
+
import fs4 from "fs";
|
|
724
|
+
import path4 from "path";
|
|
397
725
|
import * as ts3 from "typescript";
|
|
398
726
|
import { getGeneratedApiDocsPath, getGeneratedApiSchemasPath, getGeneratedSocketTypesPath } from "@luckystack/core";
|
|
399
727
|
|
|
400
|
-
// src/internal/mapUtils.ts
|
|
401
|
-
var getOrInit = (map, key, factory) => {
|
|
402
|
-
const existing = map.get(key);
|
|
403
|
-
if (existing !== void 0) return existing;
|
|
404
|
-
const fresh = factory();
|
|
405
|
-
map.set(key, fresh);
|
|
406
|
-
return fresh;
|
|
407
|
-
};
|
|
408
|
-
var mustGet = (map, key, label) => {
|
|
409
|
-
const value = map.get(key);
|
|
410
|
-
if (value === void 0) {
|
|
411
|
-
throw new Error(`[devkit] invariant violation: '${String(key)}' missing from ${label}`);
|
|
412
|
-
}
|
|
413
|
-
return value;
|
|
414
|
-
};
|
|
415
|
-
|
|
416
728
|
// src/typeMap/zodEmitter.ts
|
|
417
729
|
import * as ts2 from "typescript";
|
|
418
730
|
var wrapOptional = (inner) => `${inner}.optional()`;
|
|
@@ -575,13 +887,13 @@ var splitVersionedKey = (value) => {
|
|
|
575
887
|
return { name: name ?? "", version: version ?? "v1" };
|
|
576
888
|
};
|
|
577
889
|
var writeFileIfChanged = (filePath, content) => {
|
|
578
|
-
if (
|
|
579
|
-
const currentContent =
|
|
890
|
+
if (fs4.existsSync(filePath)) {
|
|
891
|
+
const currentContent = fs4.readFileSync(filePath, "utf8");
|
|
580
892
|
if (currentContent === content) {
|
|
581
893
|
return false;
|
|
582
894
|
}
|
|
583
895
|
}
|
|
584
|
-
|
|
896
|
+
fs4.writeFileSync(filePath, content, "utf8");
|
|
585
897
|
return true;
|
|
586
898
|
};
|
|
587
899
|
var indentStr = (str, indentText) => {
|
|
@@ -1139,9 +1451,9 @@ var writeTypeMapArtifacts = ({
|
|
|
1139
1451
|
}
|
|
1140
1452
|
}
|
|
1141
1453
|
const docsPath = getGeneratedApiDocsPath();
|
|
1142
|
-
const docsDir =
|
|
1143
|
-
if (!
|
|
1144
|
-
|
|
1454
|
+
const docsDir = path4.dirname(docsPath);
|
|
1455
|
+
if (!fs4.existsSync(docsDir)) {
|
|
1456
|
+
fs4.mkdirSync(docsDir, { recursive: true });
|
|
1145
1457
|
}
|
|
1146
1458
|
const docsContent = JSON.stringify(docsData, null, 2);
|
|
1147
1459
|
const hasUpdatedDocs = writeFileIfChanged(docsPath, docsContent);
|
|
@@ -1149,7 +1461,7 @@ var writeTypeMapArtifacts = ({
|
|
|
1149
1461
|
console.log("[TypeMapGenerator] Generated apiDocs.generated.json");
|
|
1150
1462
|
}
|
|
1151
1463
|
if (diagnosticsData !== void 0) {
|
|
1152
|
-
const diagnosticsPath =
|
|
1464
|
+
const diagnosticsPath = path4.join(path4.dirname(docsPath), "apiTypeDiagnostics.generated.json");
|
|
1153
1465
|
const diagnosticsContent = JSON.stringify(diagnosticsData, null, 2);
|
|
1154
1466
|
const hasUpdatedDiagnostics = writeFileIfChanged(diagnosticsPath, diagnosticsContent);
|
|
1155
1467
|
if (hasUpdatedDiagnostics) {
|
|
@@ -1166,22 +1478,22 @@ var writeTypeMapArtifacts = ({
|
|
|
1166
1478
|
|
|
1167
1479
|
// src/typeMap/extractors.ts
|
|
1168
1480
|
import * as ts5 from "typescript";
|
|
1169
|
-
import
|
|
1481
|
+
import path6 from "path";
|
|
1170
1482
|
|
|
1171
1483
|
// src/typeMap/tsProgram.ts
|
|
1172
1484
|
import * as ts4 from "typescript";
|
|
1173
|
-
import
|
|
1174
|
-
import { ROOT_DIR as
|
|
1485
|
+
import path5 from "path";
|
|
1486
|
+
import { ROOT_DIR as ROOT_DIR3, getGeneratedSocketTypesPath as getGeneratedSocketTypesPath2 } from "@luckystack/core";
|
|
1175
1487
|
var cachedProgram = null;
|
|
1176
1488
|
var getServerProgram = () => {
|
|
1177
1489
|
if (cachedProgram) return cachedProgram;
|
|
1178
|
-
const tsconfigPath = ts4.findConfigFile(
|
|
1490
|
+
const tsconfigPath = ts4.findConfigFile(ROOT_DIR3, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.server.json");
|
|
1179
1491
|
if (!tsconfigPath) throw new Error("[TypeProgram] tsconfig.server.json not found");
|
|
1180
1492
|
const { config } = ts4.readConfigFile(tsconfigPath, ts4.sys.readFile.bind(ts4.sys));
|
|
1181
1493
|
const { options, fileNames } = ts4.parseJsonConfigFileContent(
|
|
1182
1494
|
config,
|
|
1183
1495
|
ts4.sys,
|
|
1184
|
-
|
|
1496
|
+
path5.dirname(tsconfigPath)
|
|
1185
1497
|
);
|
|
1186
1498
|
cachedProgram = ts4.createProgram(fileNames, options);
|
|
1187
1499
|
return cachedProgram;
|
|
@@ -1290,10 +1602,10 @@ var getLiteralTypeFromPropertySymbol = (symbol, checker, depth) => {
|
|
|
1290
1602
|
return null;
|
|
1291
1603
|
};
|
|
1292
1604
|
var normalizeImportPath = (targetFilePath) => {
|
|
1293
|
-
const fromDir =
|
|
1605
|
+
const fromDir = path5.dirname(getGeneratedSocketTypesPath2());
|
|
1294
1606
|
const from = fromDir.replaceAll("\\", "/");
|
|
1295
1607
|
const to = targetFilePath.replaceAll("\\", "/");
|
|
1296
|
-
const normalized =
|
|
1608
|
+
const normalized = path5.posix.relative(from, to).replaceAll("\\", "/");
|
|
1297
1609
|
const withoutExtension = normalized.replace(/(\.d)?\.(ts|tsx|js|jsx)$/i, "");
|
|
1298
1610
|
if (withoutExtension.startsWith(".")) return withoutExtension;
|
|
1299
1611
|
return `./${withoutExtension}`;
|
|
@@ -1586,10 +1898,10 @@ var KNOWN_GLOBAL_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
|
1586
1898
|
"JsonPrimitive"
|
|
1587
1899
|
]);
|
|
1588
1900
|
var normalizeImportPath2 = (targetFilePath) => {
|
|
1589
|
-
const fromDir =
|
|
1901
|
+
const fromDir = path6.dirname(getGeneratedSocketTypesPath3());
|
|
1590
1902
|
const from = fromDir.replaceAll("\\", "/");
|
|
1591
1903
|
const to = targetFilePath.replaceAll("\\", "/");
|
|
1592
|
-
const normalized =
|
|
1904
|
+
const normalized = path6.posix.relative(from, to).replaceAll("\\", "/");
|
|
1593
1905
|
const withoutExtension = normalized.replace(/(\.d)?\.(ts|tsx|js|jsx)$/i, "");
|
|
1594
1906
|
if (withoutExtension.startsWith(".")) return withoutExtension;
|
|
1595
1907
|
return `./${withoutExtension}`;
|
|
@@ -1943,18 +2255,18 @@ var getSyncClientOutputTypeDetailsFromFile = (filePath) => {
|
|
|
1943
2255
|
|
|
1944
2256
|
// src/typeMap/functionsMeta.ts
|
|
1945
2257
|
import * as ts7 from "typescript";
|
|
1946
|
-
import
|
|
1947
|
-
import
|
|
2258
|
+
import fs6 from "fs";
|
|
2259
|
+
import path9 from "path";
|
|
1948
2260
|
|
|
1949
2261
|
// src/typeMap/typeContext.ts
|
|
1950
2262
|
import * as ts6 from "typescript";
|
|
1951
|
-
import
|
|
2263
|
+
import path7 from "path";
|
|
1952
2264
|
import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath4 } from "@luckystack/core";
|
|
1953
2265
|
var toGeneratedImportPath = (source, filePath) => {
|
|
1954
2266
|
if (!source.startsWith(".")) return source;
|
|
1955
|
-
const outputDir =
|
|
1956
|
-
const absoluteSource =
|
|
1957
|
-
let relPath =
|
|
2267
|
+
const outputDir = path7.dirname(getGeneratedSocketTypesPath4());
|
|
2268
|
+
const absoluteSource = path7.resolve(path7.dirname(filePath), source);
|
|
2269
|
+
let relPath = path7.relative(outputDir, absoluteSource).replaceAll("\\", "/");
|
|
1958
2270
|
relPath = relPath.replace(/\.tsx?$/, "");
|
|
1959
2271
|
if (!relPath.startsWith(".")) relPath = `./${relPath}`;
|
|
1960
2272
|
return relPath;
|
|
@@ -2038,14 +2350,14 @@ var sanitizeTypeAndCollectImports = ({
|
|
|
2038
2350
|
};
|
|
2039
2351
|
|
|
2040
2352
|
// src/typeMap/functionsMeta.ts
|
|
2041
|
-
import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath5, ROOT_DIR as
|
|
2353
|
+
import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath5, ROOT_DIR as ROOT_DIR5 } from "@luckystack/core";
|
|
2042
2354
|
|
|
2043
2355
|
// src/functionRegistry.ts
|
|
2044
|
-
import
|
|
2045
|
-
import
|
|
2046
|
-
import { ROOT_DIR as
|
|
2047
|
-
var
|
|
2048
|
-
var toWorkspaceRelative = (absolutePath) =>
|
|
2356
|
+
import fs5 from "fs";
|
|
2357
|
+
import path8 from "path";
|
|
2358
|
+
import { ROOT_DIR as ROOT_DIR4, getServerFunctionDirs, isTestFile as isTestFile2, isTestDirectory as isTestDirectory2 } from "@luckystack/core";
|
|
2359
|
+
var normalizePath2 = (value) => value.replaceAll("\\", "/");
|
|
2360
|
+
var toWorkspaceRelative = (absolutePath) => normalizePath2(path8.relative(ROOT_DIR4, absolutePath));
|
|
2049
2361
|
var formatFunctionKeyPath = (keyPath) => keyPath.join(".");
|
|
2050
2362
|
var functionModuleFileName = (keyPath) => {
|
|
2051
2363
|
const fileName = keyPath.at(-1);
|
|
@@ -2057,18 +2369,18 @@ var functionModuleFileName = (keyPath) => {
|
|
|
2057
2369
|
var formatFunctionKeyConflict = (keyPath, firstSourcePath, secondSourcePath) => `[function-injection] Conflict at \`functions.${formatFunctionKeyPath(keyPath)}\`: defined in both \`${firstSourcePath}\` and \`${secondSourcePath}\`. Delete one \u2014 \`shared/\` is the canonical location for framework re-exports.`;
|
|
2058
2370
|
var formatFunctionNamespaceConflict = (moduleKeyPath, moduleSourcePath, nestedKeyPath, nestedSourcePath) => `[function-injection] Conflict at \`functions.${formatFunctionKeyPath(moduleKeyPath)}\`: \`${moduleSourcePath}\` defines it as a module, but \`${nestedSourcePath}\` nests \`functions.${formatFunctionKeyPath(nestedKeyPath)}\` underneath it. Rename one \u2014 a key cannot be both a module and a namespace.`;
|
|
2059
2371
|
var walkFunctionRoot = (dir, rootDir, basePath, collected) => {
|
|
2060
|
-
const entries =
|
|
2372
|
+
const entries = fs5.readdirSync(dir).toSorted();
|
|
2061
2373
|
for (const entry of entries) {
|
|
2062
2374
|
if (entry === "node_modules") continue;
|
|
2063
|
-
const fullPath =
|
|
2375
|
+
const fullPath = path8.join(dir, entry);
|
|
2064
2376
|
if (getRoutingRules().ignore(toWorkspaceRelative(fullPath))) continue;
|
|
2065
|
-
if (
|
|
2066
|
-
if (
|
|
2377
|
+
if (fs5.statSync(fullPath).isDirectory()) {
|
|
2378
|
+
if (isTestDirectory2(entry)) continue;
|
|
2067
2379
|
walkFunctionRoot(fullPath, rootDir, [...basePath, entry], collected);
|
|
2068
2380
|
continue;
|
|
2069
2381
|
}
|
|
2070
2382
|
if (!entry.endsWith(".ts")) continue;
|
|
2071
|
-
if (
|
|
2383
|
+
if (isTestFile2(entry)) continue;
|
|
2072
2384
|
collected.push({
|
|
2073
2385
|
keyPath: [...basePath, entry.slice(0, -".ts".length)],
|
|
2074
2386
|
sourcePath: toWorkspaceRelative(fullPath),
|
|
@@ -2105,7 +2417,7 @@ var collectFunctionModules = (options = {}) => {
|
|
|
2105
2417
|
onConflict(message);
|
|
2106
2418
|
};
|
|
2107
2419
|
for (const root of functionRoots) {
|
|
2108
|
-
if (!
|
|
2420
|
+
if (!fs5.existsSync(root)) continue;
|
|
2109
2421
|
const fromThisRoot = [];
|
|
2110
2422
|
walkFunctionRoot(root, root, [], fromThisRoot);
|
|
2111
2423
|
for (const module of fromThisRoot) {
|
|
@@ -2202,9 +2514,9 @@ var simplifyInferredType = (value) => {
|
|
|
2202
2514
|
if (/\bRedis\b/.test(value)) return "Redis";
|
|
2203
2515
|
return value;
|
|
2204
2516
|
};
|
|
2205
|
-
var getGeneratedFileDir = () =>
|
|
2517
|
+
var getGeneratedFileDir = () => path9.dirname(getGeneratedSocketTypesPath5());
|
|
2206
2518
|
var sourceModuleSpecifier = (sourceFilePath) => {
|
|
2207
|
-
const relative =
|
|
2519
|
+
const relative = path9.relative(getGeneratedFileDir(), sourceFilePath).replaceAll("\\", "/").replace(/\.tsx?$/i, "");
|
|
2208
2520
|
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
2209
2521
|
};
|
|
2210
2522
|
var portableInferredValueType = (inferred, sourceFilePath, exportName) => {
|
|
@@ -2215,9 +2527,9 @@ var relativizeModuleSpecifier = (specifier, sourceFilePath) => {
|
|
|
2215
2527
|
if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
|
|
2216
2528
|
return specifier;
|
|
2217
2529
|
}
|
|
2218
|
-
const absolute =
|
|
2219
|
-
const rel =
|
|
2220
|
-
const normalized = rel.split(
|
|
2530
|
+
const absolute = path9.resolve(path9.dirname(sourceFilePath), specifier);
|
|
2531
|
+
const rel = path9.relative(getGeneratedFileDir(), absolute);
|
|
2532
|
+
const normalized = rel.split(path9.sep).join("/");
|
|
2221
2533
|
return normalized.startsWith(".") ? normalized : `./${normalized}`;
|
|
2222
2534
|
};
|
|
2223
2535
|
var findProgramVariableDeclaration = (sourceFile, exportName) => {
|
|
@@ -2391,7 +2703,7 @@ var findSignatureForExport = (name, sourceFile, rawContent, filePath, availableE
|
|
|
2391
2703
|
};
|
|
2392
2704
|
var parseFunctionFile = (fullPath, collectors) => {
|
|
2393
2705
|
try {
|
|
2394
|
-
const rawContent =
|
|
2706
|
+
const rawContent = fs6.readFileSync(fullPath, "utf8");
|
|
2395
2707
|
const sourceFile = ts7.createSourceFile(fullPath, rawContent, ts7.ScriptTarget.Latest, true);
|
|
2396
2708
|
const { availableExports, fileImports } = parseFileTypeContext(rawContent);
|
|
2397
2709
|
const program = getServerProgram();
|
|
@@ -2461,406 +2773,95 @@ var parseFunctionFile = (fullPath, collectors) => {
|
|
|
2461
2773
|
}
|
|
2462
2774
|
}
|
|
2463
2775
|
if (ts7.isExportAssignment(statement) && !statement.isExportEquals && ts7.isIdentifier(statement.expression)) {
|
|
2464
|
-
defaultExportName = statement.expression.text;
|
|
2465
|
-
}
|
|
2466
|
-
if (ts7.isExportDeclaration(statement) && !statement.exportClause && statement.moduleSpecifier && ts7.isStringLiteral(statement.moduleSpecifier)) {
|
|
2467
|
-
wildcardReExport = relativizeModuleSpecifier(statement.moduleSpecifier.text, fullPath);
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
return { kind: "file", exports, defaultExportName, wildcardReExport, sourcePath: fullPath };
|
|
2471
|
-
} catch (error) {
|
|
2472
|
-
console.error(`[TypeMapGenerator] Error parsing functions file ${fullPath}:`, error);
|
|
2473
|
-
return null;
|
|
2474
|
-
}
|
|
2475
|
-
};
|
|
2476
|
-
var insertFileNode = (root, keyPath, file) => {
|
|
2477
|
-
let target = root;
|
|
2478
|
-
for (const segment of keyPath.slice(0, -1)) {
|
|
2479
|
-
const existing = target.children.get(segment);
|
|
2480
|
-
if (existing?.kind === "dir") {
|
|
2481
|
-
target = existing;
|
|
2482
|
-
continue;
|
|
2483
|
-
}
|
|
2484
|
-
const created = { kind: "dir", children: /* @__PURE__ */ new Map(), sourcePath: segment };
|
|
2485
|
-
target.children.set(segment, created);
|
|
2486
|
-
target = created;
|
|
2487
|
-
}
|
|
2488
|
-
target.children.set(functionModuleFileName(keyPath), file);
|
|
2489
|
-
};
|
|
2490
|
-
var serializeIRDir = (dir, indent) => {
|
|
2491
|
-
let output = "";
|
|
2492
|
-
for (const [name, child] of dir.children) {
|
|
2493
|
-
if (child.kind === "dir") {
|
|
2494
|
-
const subOutput = serializeIRDir(child, `${indent} `);
|
|
2495
|
-
if (subOutput.trim()) {
|
|
2496
|
-
output += `${indent}${name}: {
|
|
2497
|
-
${subOutput}${indent}};
|
|
2498
|
-
`;
|
|
2499
|
-
}
|
|
2500
|
-
continue;
|
|
2501
|
-
}
|
|
2502
|
-
const exportsCopy = new Map(child.exports);
|
|
2503
|
-
const defaultExportName = child.defaultExportName;
|
|
2504
|
-
const defaultSig = defaultExportName ? exportsCopy.get(defaultExportName) : void 0;
|
|
2505
|
-
if (defaultSig && defaultExportName) exportsCopy.delete(defaultExportName);
|
|
2506
|
-
if (child.wildcardReExport && exportsCopy.size === 0 && !defaultSig) {
|
|
2507
|
-
output += `${indent}${name}: typeof import('${child.wildcardReExport}');
|
|
2508
|
-
`;
|
|
2509
|
-
continue;
|
|
2510
|
-
}
|
|
2511
|
-
if (!defaultSig && exportsCopy.size === 1 && exportsCopy.has("default")) {
|
|
2512
|
-
const reExportSig = exportsCopy.get("default");
|
|
2513
|
-
if (reExportSig) {
|
|
2514
|
-
exportsCopy.delete("default");
|
|
2515
|
-
exportsCopy.set(name, reExportSig);
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
let fileOutput = "";
|
|
2519
|
-
for (const [exportName, sig] of exportsCopy) {
|
|
2520
|
-
fileOutput += `${indent} ${exportName}: ${sig};
|
|
2521
|
-
`;
|
|
2522
|
-
}
|
|
2523
|
-
if (defaultSig && !fileOutput.trim()) {
|
|
2524
|
-
fileOutput += `${indent} ${name}: ${defaultSig};
|
|
2525
|
-
`;
|
|
2526
|
-
}
|
|
2527
|
-
if (fileOutput) {
|
|
2528
|
-
output += `${indent}${name}: {
|
|
2529
|
-
${fileOutput}${indent}};
|
|
2530
|
-
`;
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
|
-
return output;
|
|
2534
|
-
};
|
|
2535
|
-
var generateServerFunctions = (collectors) => {
|
|
2536
|
-
const modules = collectFunctionModules();
|
|
2537
|
-
if (modules.length === 0) return "";
|
|
2538
|
-
const merged = { kind: "dir", children: /* @__PURE__ */ new Map(), sourcePath: "<merged>" };
|
|
2539
|
-
for (const module of modules) {
|
|
2540
|
-
const absolutePath = path8.resolve(ROOT_DIR4, module.sourcePath);
|
|
2541
|
-
const parsed = parseFunctionFile(absolutePath, collectors);
|
|
2542
|
-
if (!parsed) continue;
|
|
2543
|
-
if (parsed.exports.size === 0 && parsed.defaultExportName === null && parsed.wildcardReExport === null) {
|
|
2544
|
-
continue;
|
|
2545
|
-
}
|
|
2546
|
-
insertFileNode(merged, module.keyPath, parsed);
|
|
2547
|
-
}
|
|
2548
|
-
return serializeIRDir(merged, " ");
|
|
2549
|
-
};
|
|
2550
|
-
|
|
2551
|
-
// src/typeMapGenerator.ts
|
|
2552
|
-
import { getSrcDir as getSrcDir2 } from "@luckystack/core";
|
|
2553
|
-
|
|
2554
|
-
// src/routeNamingValidation.ts
|
|
2555
|
-
import fs6 from "fs";
|
|
2556
|
-
import path9 from "path";
|
|
2557
|
-
import { ROOT_DIR as ROOT_DIR5 } from "@luckystack/core";
|
|
2558
|
-
var normalizePath2 = (value) => {
|
|
2559
|
-
return value.replaceAll("\\", "/");
|
|
2560
|
-
};
|
|
2561
|
-
var toRel = (absolute) => path9.relative(ROOT_DIR5, absolute).replaceAll("\\", "/");
|
|
2562
|
-
var isInsidePrivateRouteSubfolder = (fullPath) => {
|
|
2563
|
-
const { privateFolderPrefix, apiMarker, syncMarker } = getRoutingRules();
|
|
2564
|
-
const segments = normalizePath2(fullPath).split("/");
|
|
2565
|
-
const markerIndex = segments.findIndex((segment) => segment === apiMarker || segment === syncMarker);
|
|
2566
|
-
if (markerIndex === -1) return false;
|
|
2567
|
-
return segments.slice(markerIndex + 1).some((segment) => segment.startsWith(privateFolderPrefix));
|
|
2568
|
-
};
|
|
2569
|
-
var walkRouteFiles = (dir, results = []) => {
|
|
2570
|
-
const entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2571
|
-
const apiSeg = apiMarkerSegment();
|
|
2572
|
-
const syncSeg = syncMarkerSegment();
|
|
2573
|
-
const { ignore } = getRoutingRules();
|
|
2574
|
-
for (const entry of entries) {
|
|
2575
|
-
const fullPath = path9.join(dir, entry.name);
|
|
2576
|
-
const normalizedFullPath = normalizePath2(fullPath);
|
|
2577
|
-
if (ignore(toRel(fullPath))) continue;
|
|
2578
|
-
if (isInsidePrivateRouteSubfolder(fullPath)) continue;
|
|
2579
|
-
if (entry.isDirectory()) {
|
|
2580
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
2581
|
-
continue;
|
|
2582
|
-
}
|
|
2583
|
-
walkRouteFiles(fullPath, results);
|
|
2584
|
-
continue;
|
|
2585
|
-
}
|
|
2586
|
-
if (!entry.isFile() || !entry.name.endsWith(".ts")) {
|
|
2587
|
-
continue;
|
|
2588
|
-
}
|
|
2589
|
-
if (isRouteTestFile(entry.name)) {
|
|
2590
|
-
continue;
|
|
2591
|
-
}
|
|
2592
|
-
if (normalizedFullPath.includes(apiSeg) || normalizedFullPath.includes(syncSeg)) {
|
|
2593
|
-
results.push(fullPath);
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
return results;
|
|
2597
|
-
};
|
|
2598
|
-
var getFileRouteToken = ({
|
|
2599
|
-
normalizedFilePath,
|
|
2600
|
-
marker
|
|
2601
|
-
}) => {
|
|
2602
|
-
const markerIndex = normalizedFilePath.indexOf(marker);
|
|
2603
|
-
if (markerIndex === -1) {
|
|
2604
|
-
return "";
|
|
2605
|
-
}
|
|
2606
|
-
const tokenStart = markerIndex + marker.length;
|
|
2607
|
-
return normalizedFilePath.slice(tokenStart, normalizedFilePath.length - ".ts".length);
|
|
2608
|
-
};
|
|
2609
|
-
var validateRouteFilePath = (filePath) => {
|
|
2610
|
-
const issues = [];
|
|
2611
|
-
const normalizedFilePath = normalizePath2(path9.resolve(filePath));
|
|
2612
|
-
const fileName = path9.basename(filePath);
|
|
2613
|
-
const apiSeg = apiMarkerSegment();
|
|
2614
|
-
const syncSeg = syncMarkerSegment();
|
|
2615
|
-
if (normalizedFilePath.includes(apiSeg)) {
|
|
2616
|
-
const apiRouteToken = getFileRouteToken({ normalizedFilePath, marker: apiSeg });
|
|
2617
|
-
if (apiRouteToken.includes("/")) {
|
|
2618
|
-
issues.push({
|
|
2619
|
-
kind: "api",
|
|
2620
|
-
filePath: normalizedFilePath,
|
|
2621
|
-
reason: 'API route token cannot contain nested path segments ("/").',
|
|
2622
|
-
expected: ROUTE_NAMING_RULES.api
|
|
2623
|
-
});
|
|
2624
|
-
}
|
|
2625
|
-
if (!isVersionedApiFileName(fileName)) {
|
|
2626
|
-
issues.push({
|
|
2627
|
-
kind: "api",
|
|
2628
|
-
filePath: normalizedFilePath,
|
|
2629
|
-
reason: "API filename does not match versioned naming.",
|
|
2630
|
-
expected: ROUTE_NAMING_RULES.api
|
|
2631
|
-
});
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
if (normalizedFilePath.includes(syncSeg)) {
|
|
2635
|
-
const syncRouteToken = getFileRouteToken({ normalizedFilePath, marker: syncSeg });
|
|
2636
|
-
if (syncRouteToken.includes("/")) {
|
|
2637
|
-
issues.push({
|
|
2638
|
-
kind: "sync",
|
|
2639
|
-
filePath: normalizedFilePath,
|
|
2640
|
-
reason: 'Sync route token cannot contain nested path segments ("/").',
|
|
2641
|
-
expected: `${ROUTE_NAMING_RULES.syncServer} or ${ROUTE_NAMING_RULES.syncClient}`
|
|
2642
|
-
});
|
|
2643
|
-
}
|
|
2644
|
-
if (!isVersionedSyncFileName(fileName)) {
|
|
2645
|
-
issues.push({
|
|
2646
|
-
kind: "sync",
|
|
2647
|
-
filePath: normalizedFilePath,
|
|
2648
|
-
reason: "Sync filename does not match versioned naming.",
|
|
2649
|
-
expected: `${ROUTE_NAMING_RULES.syncServer} or ${ROUTE_NAMING_RULES.syncClient}`
|
|
2650
|
-
});
|
|
2776
|
+
defaultExportName = statement.expression.text;
|
|
2777
|
+
}
|
|
2778
|
+
if (ts7.isExportDeclaration(statement) && !statement.exportClause && statement.moduleSpecifier && ts7.isStringLiteral(statement.moduleSpecifier)) {
|
|
2779
|
+
wildcardReExport = relativizeModuleSpecifier(statement.moduleSpecifier.text, fullPath);
|
|
2780
|
+
}
|
|
2651
2781
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
};
|
|
2655
|
-
var resolveApiRouteKey = ({
|
|
2656
|
-
srcDir,
|
|
2657
|
-
filePath
|
|
2658
|
-
}) => {
|
|
2659
|
-
const rules2 = getRoutingRules();
|
|
2660
|
-
const relativePath = normalizePath2(path9.relative(srcDir, filePath));
|
|
2661
|
-
const segments = relativePath.split("/");
|
|
2662
|
-
const apiIndex = segments.indexOf(rules2.apiMarker);
|
|
2663
|
-
if (apiIndex === -1 || apiIndex === segments.length - 1) {
|
|
2664
|
-
return null;
|
|
2665
|
-
}
|
|
2666
|
-
const pageLocation = segments.slice(0, apiIndex).join("/");
|
|
2667
|
-
const apiFilePath = segments.slice(apiIndex + 1).join("/");
|
|
2668
|
-
const rawApiName = apiFilePath.replace(/\.ts$/, "");
|
|
2669
|
-
const versionMatch = rawApiName.match(rules2.apiVersionRegex);
|
|
2670
|
-
if (!versionMatch) {
|
|
2671
|
-
return null;
|
|
2672
|
-
}
|
|
2673
|
-
const version = `v${versionMatch[1]}`;
|
|
2674
|
-
const apiName = rawApiName.replace(rules2.apiVersionRegex, "");
|
|
2675
|
-
const mappedPageLocation = pageLocation || "system";
|
|
2676
|
-
return `api/${mappedPageLocation}/${apiName}/${version}`;
|
|
2677
|
-
};
|
|
2678
|
-
var resolveSyncRouteKey = ({
|
|
2679
|
-
srcDir,
|
|
2680
|
-
filePath
|
|
2681
|
-
}) => {
|
|
2682
|
-
const rules2 = getRoutingRules();
|
|
2683
|
-
const relativePath = normalizePath2(path9.relative(srcDir, filePath));
|
|
2684
|
-
const segments = relativePath.split("/");
|
|
2685
|
-
const syncIndex = segments.indexOf(rules2.syncMarker);
|
|
2686
|
-
if (syncIndex === -1 || syncIndex === segments.length - 1) {
|
|
2687
|
-
return null;
|
|
2688
|
-
}
|
|
2689
|
-
const pageLocation = segments.slice(0, syncIndex).join("/");
|
|
2690
|
-
const syncFilePath = segments.slice(syncIndex + 1).join("/");
|
|
2691
|
-
const rawSyncName = syncFilePath.replace(/\.ts$/, "");
|
|
2692
|
-
const syncMatch = rawSyncName.match(rules2.syncVersionRegex);
|
|
2693
|
-
if (!syncMatch) {
|
|
2782
|
+
return { kind: "file", exports, defaultExportName, wildcardReExport, sourcePath: fullPath };
|
|
2783
|
+
} catch (error) {
|
|
2784
|
+
console.error(`[TypeMapGenerator] Error parsing functions file ${fullPath}:`, error);
|
|
2694
2785
|
return null;
|
|
2695
2786
|
}
|
|
2696
|
-
const kind = syncMatch[1];
|
|
2697
|
-
const version = `v${syncMatch[2]}`;
|
|
2698
|
-
const syncName = rawSyncName.replace(rules2.syncVersionRegex, "");
|
|
2699
|
-
const routeBaseKey = pageLocation ? `sync/${pageLocation}/${syncName}/${version}` : `sync/${syncName}/${version}`;
|
|
2700
|
-
return `${routeBaseKey}_${kind}`;
|
|
2701
2787
|
};
|
|
2702
|
-
var
|
|
2703
|
-
|
|
2704
|
-
const
|
|
2705
|
-
|
|
2706
|
-
|
|
2788
|
+
var insertFileNode = (root, keyPath, file) => {
|
|
2789
|
+
let target = root;
|
|
2790
|
+
for (const segment of keyPath.slice(0, -1)) {
|
|
2791
|
+
const existing = target.children.get(segment);
|
|
2792
|
+
if (existing?.kind === "dir") {
|
|
2793
|
+
target = existing;
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
const created = { kind: "dir", children: /* @__PURE__ */ new Map(), sourcePath: segment };
|
|
2797
|
+
target.children.set(segment, created);
|
|
2798
|
+
target = created;
|
|
2707
2799
|
}
|
|
2708
|
-
|
|
2800
|
+
target.children.set(functionModuleFileName(keyPath), file);
|
|
2709
2801
|
};
|
|
2710
|
-
var
|
|
2711
|
-
|
|
2712
|
-
const
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
const routeKey = resolveApiRouteKey({ srcDir, filePath });
|
|
2720
|
-
if (!routeKey) {
|
|
2721
|
-
continue;
|
|
2802
|
+
var serializeIRDir = (dir, indent) => {
|
|
2803
|
+
let output = "";
|
|
2804
|
+
for (const [name, child] of dir.children) {
|
|
2805
|
+
if (child.kind === "dir") {
|
|
2806
|
+
const subOutput = serializeIRDir(child, `${indent} `);
|
|
2807
|
+
if (subOutput.trim()) {
|
|
2808
|
+
output += `${indent}${name}: {
|
|
2809
|
+
${subOutput}${indent}};
|
|
2810
|
+
`;
|
|
2722
2811
|
}
|
|
2723
|
-
getOrInit(routeKeyToFilePaths, routeKey, () => []).push(normalizedFilePath);
|
|
2724
|
-
routeKeyKinds.set(routeKey, "api");
|
|
2725
2812
|
continue;
|
|
2726
2813
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2814
|
+
const exportsCopy = new Map(child.exports);
|
|
2815
|
+
const defaultExportName = child.defaultExportName;
|
|
2816
|
+
const defaultSig = defaultExportName ? exportsCopy.get(defaultExportName) : void 0;
|
|
2817
|
+
if (defaultSig && defaultExportName) exportsCopy.delete(defaultExportName);
|
|
2818
|
+
if (child.wildcardReExport && exportsCopy.size === 0 && !defaultSig) {
|
|
2819
|
+
output += `${indent}${name}: typeof import('${child.wildcardReExport}');
|
|
2820
|
+
`;
|
|
2821
|
+
continue;
|
|
2822
|
+
}
|
|
2823
|
+
if (!defaultSig && exportsCopy.size === 1 && exportsCopy.has("default")) {
|
|
2824
|
+
const reExportSig = exportsCopy.get("default");
|
|
2825
|
+
if (reExportSig) {
|
|
2826
|
+
exportsCopy.delete("default");
|
|
2827
|
+
exportsCopy.set(name, reExportSig);
|
|
2731
2828
|
}
|
|
2732
|
-
getOrInit(routeKeyToFilePaths, routeKey, () => []).push(normalizedFilePath);
|
|
2733
|
-
routeKeyKinds.set(routeKey, "sync");
|
|
2734
2829
|
}
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
continue;
|
|
2830
|
+
let fileOutput = "";
|
|
2831
|
+
for (const [exportName, sig] of exportsCopy) {
|
|
2832
|
+
fileOutput += `${indent} ${exportName}: ${sig};
|
|
2833
|
+
`;
|
|
2740
2834
|
}
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
filePaths: filePaths.toSorted((a, b) => a.localeCompare(b))
|
|
2745
|
-
});
|
|
2746
|
-
}
|
|
2747
|
-
return issues.toSorted((a, b) => a.routeKey.localeCompare(b.routeKey));
|
|
2748
|
-
};
|
|
2749
|
-
var formatRouteNamingIssues = ({
|
|
2750
|
-
issues,
|
|
2751
|
-
context
|
|
2752
|
-
}) => {
|
|
2753
|
-
const plural = issues.length === 1 ? "" : "s";
|
|
2754
|
-
const header = `[RouteNaming] Found ${issues.length} invalid API/sync route file${plural} while ${context}.`;
|
|
2755
|
-
const details = issues.map((issue, index) => {
|
|
2756
|
-
return `${index + 1}. [${issue.kind.toUpperCase()}] ${issue.filePath}
|
|
2757
|
-
reason: ${issue.reason}
|
|
2758
|
-
expected: ${issue.expected}`;
|
|
2759
|
-
}).join("\n");
|
|
2760
|
-
return `${header}
|
|
2761
|
-
${details}`;
|
|
2762
|
-
};
|
|
2763
|
-
var formatDuplicateRouteKeyIssues = ({
|
|
2764
|
-
issues,
|
|
2765
|
-
context
|
|
2766
|
-
}) => {
|
|
2767
|
-
const plural = issues.length === 1 ? "" : "s";
|
|
2768
|
-
const header = `[RouteNaming] Found ${issues.length} duplicate normalized route key${plural} while ${context}.`;
|
|
2769
|
-
const details = issues.map((issue, index) => {
|
|
2770
|
-
const fileList = issue.filePaths.map((filePath) => ` - ${filePath}`).join("\n");
|
|
2771
|
-
return `${index + 1}. [${issue.kind.toUpperCase()}] ${issue.routeKey}
|
|
2772
|
-
files:
|
|
2773
|
-
${fileList}`;
|
|
2774
|
-
}).join("\n");
|
|
2775
|
-
return `${header}
|
|
2776
|
-
${details}`;
|
|
2777
|
-
};
|
|
2778
|
-
var assertValidRouteNaming = ({
|
|
2779
|
-
srcDir,
|
|
2780
|
-
context
|
|
2781
|
-
}) => {
|
|
2782
|
-
const issues = collectInvalidRouteNamingIssues(srcDir);
|
|
2783
|
-
if (issues.length === 0) {
|
|
2784
|
-
return;
|
|
2785
|
-
}
|
|
2786
|
-
throw new Error(formatRouteNamingIssues({ issues, context }));
|
|
2787
|
-
};
|
|
2788
|
-
var assertNoDuplicateNormalizedRouteKeys = ({
|
|
2789
|
-
srcDir,
|
|
2790
|
-
context
|
|
2791
|
-
}) => {
|
|
2792
|
-
const issues = collectDuplicateNormalizedRouteKeyIssues(srcDir);
|
|
2793
|
-
if (issues.length === 0) {
|
|
2794
|
-
return;
|
|
2795
|
-
}
|
|
2796
|
-
throw new Error(formatDuplicateRouteKeyIssues({ issues, context }));
|
|
2797
|
-
};
|
|
2798
|
-
var walkPageFiles = (dir, results = []) => {
|
|
2799
|
-
let entries;
|
|
2800
|
-
try {
|
|
2801
|
-
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
2802
|
-
} catch {
|
|
2803
|
-
return results;
|
|
2804
|
-
}
|
|
2805
|
-
for (const entry of entries) {
|
|
2806
|
-
const fullPath = path9.join(dir, entry.name);
|
|
2807
|
-
if (entry.isDirectory()) {
|
|
2808
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
2809
|
-
walkPageFiles(fullPath, results);
|
|
2810
|
-
continue;
|
|
2835
|
+
if (defaultSig && !fileOutput.trim()) {
|
|
2836
|
+
fileOutput += `${indent} ${name}: ${defaultSig};
|
|
2837
|
+
`;
|
|
2811
2838
|
}
|
|
2812
|
-
if (
|
|
2813
|
-
|
|
2814
|
-
|
|
2839
|
+
if (fileOutput) {
|
|
2840
|
+
output += `${indent}${name}: {
|
|
2841
|
+
${fileOutput}${indent}};
|
|
2842
|
+
`;
|
|
2815
2843
|
}
|
|
2816
2844
|
}
|
|
2817
|
-
return
|
|
2845
|
+
return output;
|
|
2818
2846
|
};
|
|
2819
|
-
var
|
|
2820
|
-
const
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
const
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
}
|
|
2830
|
-
const issues = [];
|
|
2831
|
-
for (const [route, filePaths] of routeToFiles) {
|
|
2832
|
-
if (filePaths.length > 1) {
|
|
2833
|
-
issues.push({ route, filePaths });
|
|
2847
|
+
var generateServerFunctions = (collectors) => {
|
|
2848
|
+
const modules = collectFunctionModules();
|
|
2849
|
+
if (modules.length === 0) return "";
|
|
2850
|
+
const merged = { kind: "dir", children: /* @__PURE__ */ new Map(), sourcePath: "<merged>" };
|
|
2851
|
+
for (const module of modules) {
|
|
2852
|
+
const absolutePath = path9.resolve(ROOT_DIR5, module.sourcePath);
|
|
2853
|
+
const parsed = parseFunctionFile(absolutePath, collectors);
|
|
2854
|
+
if (!parsed) continue;
|
|
2855
|
+
if (parsed.exports.size === 0 && parsed.defaultExportName === null && parsed.wildcardReExport === null) {
|
|
2856
|
+
continue;
|
|
2834
2857
|
}
|
|
2858
|
+
insertFileNode(merged, module.keyPath, parsed);
|
|
2835
2859
|
}
|
|
2836
|
-
return
|
|
2837
|
-
};
|
|
2838
|
-
var formatDuplicatePageRouteIssues = ({
|
|
2839
|
-
issues,
|
|
2840
|
-
context
|
|
2841
|
-
}) => {
|
|
2842
|
-
const plural = issues.length === 1 ? "" : "s";
|
|
2843
|
-
const header = `[RouteNaming] Found ${issues.length} duplicate page route${plural} while ${context}.`;
|
|
2844
|
-
const details = issues.map((issue, index) => {
|
|
2845
|
-
const fileList = issue.filePaths.map((filePath) => ` - ${filePath}`).join("\n");
|
|
2846
|
-
return `${index + 1}. ${issue.route}
|
|
2847
|
-
files:
|
|
2848
|
-
${fileList}
|
|
2849
|
-
fix: rename or move one of the files so only one page.tsx resolves to "${issue.route}". Remember that "_<folder>" segments are stripped from the URL (invisible-parent rule).`;
|
|
2850
|
-
}).join("\n");
|
|
2851
|
-
return `${header}
|
|
2852
|
-
${details}`;
|
|
2853
|
-
};
|
|
2854
|
-
var assertNoDuplicatePageRoutes = ({
|
|
2855
|
-
srcDir,
|
|
2856
|
-
context
|
|
2857
|
-
}) => {
|
|
2858
|
-
const issues = collectDuplicatePageRoutes(srcDir);
|
|
2859
|
-
if (issues.length === 0) return;
|
|
2860
|
-
throw new Error(formatDuplicatePageRouteIssues({ issues, context }));
|
|
2860
|
+
return serializeIRDir(merged, " ");
|
|
2861
2861
|
};
|
|
2862
2862
|
|
|
2863
2863
|
// src/typeMapGenerator.ts
|
|
2864
|
+
import { getSrcDir as getSrcDir2 } from "@luckystack/core";
|
|
2864
2865
|
var collectApiTypes = (apiFiles, collectors) => {
|
|
2865
2866
|
const { namedImports, unresolvedTypeAliases, quiet } = collectors;
|
|
2866
2867
|
const typesByPage = /* @__PURE__ */ new Map();
|
|
@@ -3085,7 +3086,7 @@ registerDefaultTemplateRules();
|
|
|
3085
3086
|
import fs7 from "fs";
|
|
3086
3087
|
import path10 from "path";
|
|
3087
3088
|
import { pathToFileURL } from "url";
|
|
3088
|
-
import { tryCatch, getSrcDir as getSrcDir3, ROOT_DIR as ROOT_DIR6 } from "@luckystack/core";
|
|
3089
|
+
import { tryCatch, getSrcDir as getSrcDir3, ROOT_DIR as ROOT_DIR6, isTestFile as isTestFile3 } from "@luckystack/core";
|
|
3089
3090
|
|
|
3090
3091
|
// src/runtimeTypeResolver.ts
|
|
3091
3092
|
import * as ts8 from "typescript";
|
|
@@ -3481,6 +3482,9 @@ var resolveApiRouteMetaFromPath = (filePath) => {
|
|
|
3481
3482
|
if (!normalizedAbsolutePath.startsWith(normalizedSrcDir) || !normalizedAbsolutePath.endsWith(".ts")) {
|
|
3482
3483
|
return null;
|
|
3483
3484
|
}
|
|
3485
|
+
if (!isRouteSurfaceFile(normalizedAbsolutePath)) {
|
|
3486
|
+
return null;
|
|
3487
|
+
}
|
|
3484
3488
|
const rules2 = getRoutingRules();
|
|
3485
3489
|
const relativePath = normalizePath3(path10.relative(getSrcDir3(), absolutePath));
|
|
3486
3490
|
const segments = relativePath.split("/");
|
|
@@ -3508,6 +3512,9 @@ var resolveSyncRouteMetaFromPath = (filePath) => {
|
|
|
3508
3512
|
if (!normalizedAbsolutePath.startsWith(normalizedSrcDir) || !normalizedAbsolutePath.endsWith(".ts")) {
|
|
3509
3513
|
return null;
|
|
3510
3514
|
}
|
|
3515
|
+
if (!isRouteSurfaceFile(normalizedAbsolutePath)) {
|
|
3516
|
+
return null;
|
|
3517
|
+
}
|
|
3511
3518
|
const rules2 = getRoutingRules();
|
|
3512
3519
|
const relativePath = normalizePath3(path10.relative(getSrcDir3(), absolutePath));
|
|
3513
3520
|
const segments = relativePath.split("/");
|
|
@@ -3560,8 +3567,9 @@ var collectTsFiles = (dir, relativeTo = "") => {
|
|
|
3560
3567
|
if (isIgnoredPath(entryPath)) continue;
|
|
3561
3568
|
const relPath = relativeTo ? `${relativeTo}/${entry}` : entry;
|
|
3562
3569
|
if (fs7.statSync(entryPath).isDirectory()) {
|
|
3570
|
+
if (isNonRouteDirectory(entry)) continue;
|
|
3563
3571
|
results.push(...collectTsFiles(entryPath, relPath));
|
|
3564
|
-
} else if (entry.endsWith(".ts") && !
|
|
3572
|
+
} else if (entry.endsWith(".ts") && !isTestFile3(entry)) {
|
|
3565
3573
|
results.push(relPath);
|
|
3566
3574
|
}
|
|
3567
3575
|
}
|
|
@@ -3600,7 +3608,7 @@ var upsertApiFromFile = async (filePath) => {
|
|
|
3600
3608
|
const routeMeta = resolveApiRouteMetaFromPath(filePath);
|
|
3601
3609
|
if (!routeMeta) {
|
|
3602
3610
|
const normalized = normalizePath3(path10.resolve(filePath));
|
|
3603
|
-
if (normalized.
|
|
3611
|
+
if (normalized.endsWith(".ts") && normalized.includes(apiMarkerSegment()) && isRouteSurfaceFile(normalized)) {
|
|
3604
3612
|
console.log(
|
|
3605
3613
|
`[loader][api] invalid filename: ${normalized}. Expected <name>_v<number>.ts. File will not be loaded.`,
|
|
3606
3614
|
"red"
|
|
@@ -3650,7 +3658,7 @@ var scanApiFolder = async (file, basePath = "") => {
|
|
|
3650
3658
|
const fullPath = path10.join(getSrcDir3(), basePath, file);
|
|
3651
3659
|
if (isIgnoredPath(fullPath)) return;
|
|
3652
3660
|
if (!fs7.statSync(fullPath).isDirectory()) return;
|
|
3653
|
-
if (
|
|
3661
|
+
if (file !== getRoutingRules().apiMarker) {
|
|
3654
3662
|
const subFolders = fs7.readdirSync(fullPath);
|
|
3655
3663
|
for (const sub of subFolders) {
|
|
3656
3664
|
await scanApiFolder(sub, path10.join(basePath, file));
|
|
@@ -3713,7 +3721,7 @@ var upsertSyncFromFile = async (filePath) => {
|
|
|
3713
3721
|
const routeMeta = resolveSyncRouteMetaFromPath(filePath);
|
|
3714
3722
|
if (!routeMeta) {
|
|
3715
3723
|
const normalized = normalizePath3(path10.resolve(filePath));
|
|
3716
|
-
if (normalized.
|
|
3724
|
+
if (normalized.endsWith(".ts") && normalized.includes(syncMarkerSegment()) && isRouteSurfaceFile(normalized)) {
|
|
3717
3725
|
console.log(
|
|
3718
3726
|
`[loader][sync] invalid filename: ${normalized}. Expected <name>_server_v<number>.ts or <name>_client_v<number>.ts. File will not be loaded.`,
|
|
3719
3727
|
"red"
|
|
@@ -3768,7 +3776,7 @@ var scanSyncFolder = async (file, basePath = "") => {
|
|
|
3768
3776
|
const fullPath = path10.join(getSrcDir3(), basePath, file);
|
|
3769
3777
|
if (isIgnoredPath(fullPath)) return;
|
|
3770
3778
|
if (!fs7.statSync(fullPath).isDirectory()) return;
|
|
3771
|
-
if (
|
|
3779
|
+
if (file !== getRoutingRules().syncMarker) {
|
|
3772
3780
|
const subFolders = fs7.readdirSync(fullPath);
|
|
3773
3781
|
for (const sub of subFolders) {
|
|
3774
3782
|
await scanSyncFolder(sub, path10.join(basePath, file));
|