@torpor/build 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{flattenHeaders-C_YYLdOq.mjs → Server-BnjLJleZ.mjs} +2 -12
- package/dist/Server-BnjLJleZ.mjs.map +1 -0
- package/dist/{Server-C_IKav_e.d.mts → Server-Cler2w5O.d.mts} +3 -48
- package/dist/Server-Cler2w5O.d.mts.map +1 -0
- package/dist/ServerEvent-CTx3Q-hk.d.mts +48 -0
- package/dist/ServerEvent-CTx3Q-hk.d.mts.map +1 -0
- package/dist/{Site-CMa153FA.d.mts → Site-CE0_o5Ci.d.mts} +3 -2
- package/dist/Site-CE0_o5Ci.d.mts.map +1 -0
- package/dist/bin/index.js +1 -1
- package/dist/clientEntry.d.mts +1 -0
- package/dist/clientEntry.mjs +59 -0
- package/dist/clientEntry.mjs.map +1 -0
- package/dist/clientEntryDev.d.mts +1 -0
- package/dist/clientEntryDev.mjs +7 -0
- package/dist/clientEntryDev.mjs.map +1 -0
- package/dist/dev.d.mts +3 -0
- package/dist/dev.mjs +3 -0
- package/dist/endpoint-EULRGCKA.mjs +108 -0
- package/dist/endpoint-EULRGCKA.mjs.map +1 -0
- package/dist/flattenHeaders-DM5qsSqO.mjs +13 -0
- package/dist/flattenHeaders-DM5qsSqO.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/load-BntzvmwL.mjs +228 -0
- package/dist/load-BntzvmwL.mjs.map +1 -0
- package/dist/nav.mjs +1 -225
- package/dist/nav.mjs.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/run.d.mts +1 -1
- package/dist/run.d.mts.map +1 -1
- package/dist/run.mjs +1 -1
- package/dist/{runOpenApi-BUi1RH1l.mjs → runOpenApi-DlOgF2Il.mjs} +54 -16
- package/dist/runOpenApi-DlOgF2Il.mjs.map +1 -0
- package/dist/server/Server.d.mts +2 -0
- package/dist/server/Server.mjs +2 -0
- package/dist/server.d.mts +2 -1
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +2 -1
- package/dist/server.mjs.map +1 -1
- package/dist/serverEntry-1qY3wZig.mjs +351 -0
- package/dist/serverEntry-1qY3wZig.mjs.map +1 -0
- package/dist/serverEntry-BAO8m03V.d.mts +6 -0
- package/dist/serverEntry-BAO8m03V.d.mts.map +1 -0
- package/dist/serverEntry.d.mts +2 -0
- package/dist/serverEntry.mjs +2 -0
- package/dist/test.d.mts +2 -2
- package/dist/test.mjs +2 -104
- package/dist/test.mjs.map +1 -1
- package/package.json +9 -8
- package/dist/Server-C_IKav_e.d.mts.map +0 -1
- package/dist/Site-CMa153FA.d.mts.map +0 -1
- package/dist/flattenHeaders-C_YYLdOq.mjs.map +0 -1
- package/dist/runOpenApi-BUi1RH1l.mjs.map +0 -1
- package/src/bin/index.ts +0 -22
- package/src/dev.ts +0 -10
- package/src/form/formDataToRecord.ts +0 -23
- package/src/form/readForm.ts +0 -71
- package/src/form.ts +0 -4
- package/src/index.ts +0 -82
- package/src/nav/api.test-d.ts +0 -79
- package/src/nav/api.ts +0 -104
- package/src/nav/formSubmit.ts +0 -70
- package/src/nav/load.ts +0 -9
- package/src/nav/loadData.ts +0 -134
- package/src/nav/navigate.ts +0 -189
- package/src/nav/reload.ts +0 -9
- package/src/nav/route.ts +0 -38
- package/src/nav.ts +0 -6
- package/src/openapi/docsHtml.ts +0 -25
- package/src/openapi/document.ts +0 -154
- package/src/openapi/plugin.ts +0 -78
- package/src/openapi/types.ts +0 -82
- package/src/openapi.ts +0 -14
- package/src/response/TypedResponse.ts +0 -17
- package/src/response/badRequest.ts +0 -31
- package/src/response/created.ts +0 -31
- package/src/response/forbidden.ts +0 -21
- package/src/response/found.ts +0 -30
- package/src/response/methodNotAllowed.ts +0 -18
- package/src/response/movedPermanently.ts +0 -25
- package/src/response/notFound.ts +0 -24
- package/src/response/notModified.ts +0 -34
- package/src/response/ok.ts +0 -38
- package/src/response/permanentRedirect.ts +0 -30
- package/src/response/response.ts +0 -22
- package/src/response/seeOther.ts +0 -19
- package/src/response/serverError.ts +0 -24
- package/src/response/temporaryRedirect.ts +0 -31
- package/src/response/transfer.ts +0 -14
- package/src/response/unauthorized.ts +0 -21
- package/src/response/unprocessable.ts +0 -30
- package/src/response.ts +0 -37
- package/src/run/depCache.ts +0 -175
- package/src/run/devPlugin.ts +0 -128
- package/src/run/prepareTemplate.ts +0 -42
- package/src/run/run.ts +0 -86
- package/src/run/runBuild.ts +0 -128
- package/src/run/runDev.ts +0 -177
- package/src/run/runOpenApi.ts +0 -52
- package/src/run/runPreview.ts +0 -84
- package/src/run/staleTorpCopies.ts +0 -100
- package/src/run.ts +0 -7
- package/src/schema.ts +0 -7
- package/src/server/CookieHelper.ts +0 -46
- package/src/server/HeaderHelper.ts +0 -25
- package/src/server/Server.ts +0 -168
- package/src/server/ServerEvent.ts +0 -62
- package/src/server/connect/connectMiddleware.ts +0 -85
- package/src/server/connect/flattenHeaders.ts +0 -22
- package/src/server/connect/nodeMessageToNodeResponse.ts +0 -89
- package/src/server/connect/requestToNodeMessage.ts +0 -29
- package/src/server/contentType.ts +0 -85
- package/src/server/types/HttpMethod.ts +0 -12
- package/src/server/types/MiddlewareFunction.ts +0 -22
- package/src/server/types/ServerFunction.ts +0 -7
- package/src/server.ts +0 -7
- package/src/site/Router.ts +0 -175
- package/src/site/Site.ts +0 -388
- package/src/site/checkLayoutSlots.ts +0 -114
- package/src/site/checkRoutes.ts +0 -435
- package/src/site/clientEntry.ts +0 -121
- package/src/site/clientEntryDev.ts +0 -3
- package/src/site/defaultAdapter.ts +0 -11
- package/src/site/layoutSlots.ts +0 -53
- package/src/site/manifest.ts +0 -173
- package/src/site/serverEntry.ts +0 -648
- package/src/state/$page.ts +0 -25
- package/src/state/$serverPage.ts +0 -22
- package/src/state/client.ts +0 -15
- package/src/state.ts +0 -3
- package/src/test/runTest.ts +0 -538
- package/src/test.ts +0 -3
- package/src/types/Adapter.ts +0 -20
- package/src/types/ClientState.ts +0 -8
- package/src/types/InternalState.ts +0 -7
- package/src/types/Jsonify.ts +0 -20
- package/src/types/LayoutHandler.ts +0 -5
- package/src/types/LayoutPath.ts +0 -10
- package/src/types/ManifestRoute.ts +0 -6
- package/src/types/PageData.test-d.ts +0 -136
- package/src/types/PageData.ts +0 -35
- package/src/types/PageEndPoint.ts +0 -55
- package/src/types/PageForm.test-d.ts +0 -78
- package/src/types/PageForm.ts +0 -29
- package/src/types/PageLoadEvent.ts +0 -26
- package/src/types/PageLoadReturn.ts +0 -13
- package/src/types/PageProps.ts +0 -14
- package/src/types/PageServerAction.ts +0 -13
- package/src/types/PageServerEndPoint.test-d.ts +0 -105
- package/src/types/PageServerEndPoint.ts +0 -110
- package/src/types/PageServerLoad.ts +0 -15
- package/src/types/PageState.ts +0 -8
- package/src/types/ParseRouteParams.test-d.ts +0 -84
- package/src/types/ParseRouteParams.ts +0 -64
- package/src/types/Route.ts +0 -30
- package/src/types/RouteHandler.ts +0 -23
- package/src/types/RouteLayoutHandler.ts +0 -5
- package/src/types/RouteMatchResult.ts +0 -7
- package/src/types/RouteType.ts +0 -19
- package/src/types/ServerEndPoint.test-d.ts +0 -77
- package/src/types/ServerEndPoint.ts +0 -169
- package/src/types/ServerHook.ts +0 -18
- package/src/types/ServerLoadEvent.ts +0 -82
- package/src/types/ServerRequest.ts +0 -15
- package/src/types/SitePlugin.ts +0 -24
- package/src/types/StandardSchema.ts +0 -109
- package/src/utils/pathToRegex.ts +0 -17
- package/src/utils/pathTrie.ts +0 -182
- package/src/utils/searchParamsToRecord.ts +0 -18
- package/src/utils/torporPackages.ts +0 -150
- package/src/utils/tsconfigAliases.ts +0 -90
- package/src/validation/ValidationError.ts +0 -18
- package/src/validation/endpoint.ts +0 -83
- package/src/validation/validate.ts +0 -26
- package/src/vite-env.d.ts +0 -2
package/src/site/checkRoutes.ts
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import type Route from "../types/Route";
|
|
4
|
-
import {
|
|
5
|
-
ERROR_ROUTE,
|
|
6
|
-
HOOK_ROUTE,
|
|
7
|
-
HOOK_SERVER_ROUTE,
|
|
8
|
-
LAYOUT_ROUTE,
|
|
9
|
-
LAYOUT_SERVER_ROUTE,
|
|
10
|
-
PAGE_SERVER_ROUTE,
|
|
11
|
-
SERVER_ROUTE,
|
|
12
|
-
} from "../types/RouteType";
|
|
13
|
-
import tsconfigAliases, { type AliasEntry } from "../utils/tsconfigAliases";
|
|
14
|
-
import type Site from "./Site";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* A problem found in a route file's type annotations by `checkRoutes`.
|
|
18
|
-
*/
|
|
19
|
-
export interface RouteCheckIssue {
|
|
20
|
-
/** The route file, relative to the site root */
|
|
21
|
-
file: string;
|
|
22
|
-
/** 1-based line number of the annotation */
|
|
23
|
-
line: number;
|
|
24
|
-
severity: "error" | "warning";
|
|
25
|
-
message: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const ENDPOINT_TYPES = ["PageServerEndPoint", "PageEndPoint", "ServerEndPoint", "ServerHook"];
|
|
29
|
-
|
|
30
|
-
const USAGE_RE = new RegExp(`\\b(satisfies|as|:)\\s*\\b(${ENDPOINT_TYPES.join("|")})\\b`, "g");
|
|
31
|
-
|
|
32
|
-
const TYPE_NAME_RE = new RegExp(`^(?:${ENDPOINT_TYPES.join("|")})\\b`);
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Checks the route files of a site against their type annotations:
|
|
36
|
-
*
|
|
37
|
-
* - Route path annotations (e.g. `PageServerEndPoint<"/posts/[id]">`) must
|
|
38
|
-
* match the route derived from the file's location, so types can't silently
|
|
39
|
-
* lie when files move
|
|
40
|
-
* - Exports that widen the endpoint type (`satisfies T as T`, `as T`, or a
|
|
41
|
-
* variable annotation) are flagged, because they destroy `PageData` /
|
|
42
|
-
* `PageForm` / param inference
|
|
43
|
-
* - Routes with dynamic params but no route annotation are flagged, because
|
|
44
|
-
* their params are loosely typed
|
|
45
|
-
*
|
|
46
|
-
* Only `.ts` route files are checked; annotations are matched by the standard
|
|
47
|
-
* type names (renamed imports are not detected). Best-effort, source-level —
|
|
48
|
-
* comments mentioning these patterns may produce false positives.
|
|
49
|
-
*/
|
|
50
|
-
export function checkRoutes(site: Site): RouteCheckIssue[] {
|
|
51
|
-
const issues: RouteCheckIssue[] = [];
|
|
52
|
-
for (const route of site.routes) {
|
|
53
|
-
issues.push(...checkRoute(site, route));
|
|
54
|
-
}
|
|
55
|
-
return issues;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Checks a single route's file. Returns no issues for routes without a `.ts`
|
|
60
|
-
* file (`.torp` components, inline endpoints, `.js` files).
|
|
61
|
-
*/
|
|
62
|
-
export function checkRoute(site: Site, route: Route): RouteCheckIssue[] {
|
|
63
|
-
if (!route.file || !route.file.endsWith(".ts")) return [];
|
|
64
|
-
let source: string;
|
|
65
|
-
try {
|
|
66
|
-
source = readFileSync(path.join(site.root, route.file), "utf8");
|
|
67
|
-
} catch {
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
return checkRouteSource(source, route.file, expectedAnnotationPath(route), typeNameFor(route));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Checks a single route file's source. Exported for reuse (e.g. re-checking a
|
|
75
|
-
* changed file during dev).
|
|
76
|
-
* @param source The route file's source code
|
|
77
|
-
* @param file The file path, relative to the site root (used in messages)
|
|
78
|
-
* @param expected The route path derived from the file's location
|
|
79
|
-
* @param typeName The endpoint type name conventionally used by this route
|
|
80
|
-
* type (used in suggestions)
|
|
81
|
-
*/
|
|
82
|
-
export function checkRouteSource(
|
|
83
|
-
source: string,
|
|
84
|
-
file: string,
|
|
85
|
-
expected: string,
|
|
86
|
-
typeName: string,
|
|
87
|
-
): RouteCheckIssue[] {
|
|
88
|
-
const issues: RouteCheckIssue[] = [];
|
|
89
|
-
let sawAnnotation = false;
|
|
90
|
-
USAGE_RE.lastIndex = 0;
|
|
91
|
-
for (let m = USAGE_RE.exec(source); m; m = USAGE_RE.exec(source)) {
|
|
92
|
-
sawAnnotation = true;
|
|
93
|
-
const kind = m[1];
|
|
94
|
-
const usedTypeName = m[2];
|
|
95
|
-
const { routePath, end } = parseTypeArgs(source, m.index + m[0].length);
|
|
96
|
-
const line = lineOf(source, m.index);
|
|
97
|
-
if (routePath !== undefined && routePath !== expected) {
|
|
98
|
-
issues.push({
|
|
99
|
-
file,
|
|
100
|
-
line,
|
|
101
|
-
severity: "error",
|
|
102
|
-
message: `Route annotation "${routePath}" doesn't match this file's route "${expected}" (derived from its location). Did the file move?`,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
if (kind === "as" || kind === ":") {
|
|
106
|
-
issues.push({
|
|
107
|
-
file,
|
|
108
|
-
line,
|
|
109
|
-
severity: "warning",
|
|
110
|
-
message:
|
|
111
|
-
kind === "as"
|
|
112
|
-
? `The \`as ${usedTypeName}\` cast widens the exported type, so PageData/PageForm/param inference falls back to loose types. Use \`satisfies ${usedTypeName}${routePathArg(expected, routePath)}\` without a cast instead.`
|
|
113
|
-
: `The variable annotation widens the exported type, so PageData/PageForm/param inference falls back to loose types. Use \`satisfies ${usedTypeName}${routePathArg(expected, routePath)}\` on the default export instead.`,
|
|
114
|
-
});
|
|
115
|
-
} else {
|
|
116
|
-
const cast = parseAsCast(source, end);
|
|
117
|
-
if (cast) {
|
|
118
|
-
issues.push({
|
|
119
|
-
file,
|
|
120
|
-
line,
|
|
121
|
-
severity: "warning",
|
|
122
|
-
message: `The \`as\` cast after \`satisfies\` widens the exported type, so PageData/PageForm/param inference falls back to loose types. Use \`satisfies ${usedTypeName}${routePathArg(expected, routePath ?? cast.routePath)}\` on its own.`,
|
|
123
|
-
});
|
|
124
|
-
if (cast.routePath !== undefined && cast.routePath !== expected) {
|
|
125
|
-
issues.push({
|
|
126
|
-
file,
|
|
127
|
-
line,
|
|
128
|
-
severity: "error",
|
|
129
|
-
message: `Route annotation "${cast.routePath}" doesn't match this file's route "${expected}" (derived from its location). Did the file move?`,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
// Skip the `as T` so it isn't reported again as a separate cast
|
|
133
|
-
USAGE_RE.lastIndex = cast.end;
|
|
134
|
-
} else if (routePath === undefined && expected.includes("[")) {
|
|
135
|
-
issues.push({
|
|
136
|
-
file,
|
|
137
|
-
line,
|
|
138
|
-
severity: "warning",
|
|
139
|
-
message: `No route path on the annotation, so params are loosely typed. Use \`satisfies ${usedTypeName}<"${expected}">\`.`,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (!sawAnnotation && file.endsWith(".ts") && expected.includes("[")) {
|
|
145
|
-
issues.push({
|
|
146
|
-
file,
|
|
147
|
-
line: 1,
|
|
148
|
-
severity: "warning",
|
|
149
|
-
message: `No route annotation, so params are loosely typed. Add \`satisfies ${typeName}<"${expected}">\` to the default export.`,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
return issues;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Prints issues to the console and returns the number of errors.
|
|
157
|
-
*/
|
|
158
|
-
export function reportRouteIssues(issues: RouteCheckIssue[]): number {
|
|
159
|
-
for (const issue of issues) {
|
|
160
|
-
console.error(`[torpor] ${issue.severity}: ${issue.file}:${issue.line} ${issue.message}`);
|
|
161
|
-
}
|
|
162
|
-
return issues.filter((i) => i.severity === "error").length;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const MAKE_API_RE = /\bmakeApi\s*<\s*(["'])([^"']+)\1\s*,\s*typeof\s+([A-Za-z_$][\w$]*)/g;
|
|
166
|
-
|
|
167
|
-
const DEFAULT_IMPORT_RE = /\bimport\s+(?:type\s+)?([A-Za-z_$][\w$]*)\s+from\s*(["'])([^"']+)\2/g;
|
|
168
|
-
|
|
169
|
-
const SKIP_DIRS = new Set(["node_modules", "dist", ".git", ".torpor", ".vite", "coverage"]);
|
|
170
|
-
|
|
171
|
-
const SOURCE_FILE_RE = /\.(ts|js|torp)$/;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Precomputed state for checking `makeApi` calls: the site root, its route
|
|
175
|
-
* files keyed by path, and tsconfig path aliases for resolving imports.
|
|
176
|
-
*/
|
|
177
|
-
export interface ApiCallCheck {
|
|
178
|
-
root: string;
|
|
179
|
-
routesByFile: Map<string, Route>;
|
|
180
|
-
aliases: AliasEntry[];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Creates the shared state used by `checkApiCallSource`, so per-file checks
|
|
185
|
-
* (e.g. re-checking a changed file during dev) don't rebuild it.
|
|
186
|
-
*/
|
|
187
|
-
export function createApiCallCheck(site: Site): ApiCallCheck {
|
|
188
|
-
const routesByFile = new Map<string, Route>();
|
|
189
|
-
for (const route of site.routes) {
|
|
190
|
-
if (route.file) routesByFile.set(route.file.replaceAll("\\", "/"), route);
|
|
191
|
-
}
|
|
192
|
-
return { root: site.root, routesByFile, aliases: tsconfigAliases(site.root) };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Checks `makeApi<Route, typeof endpoint>` calls across the site's source
|
|
197
|
-
* files: the `Route` argument must match the route derived from the imported
|
|
198
|
-
* endpoint file's location. Calls whose endpoint isn't a type-only import of
|
|
199
|
-
* a known route file are skipped.
|
|
200
|
-
*/
|
|
201
|
-
export function checkApiCalls(site: Site): RouteCheckIssue[] {
|
|
202
|
-
const check = createApiCallCheck(site);
|
|
203
|
-
const issues: RouteCheckIssue[] = [];
|
|
204
|
-
for (const file of walkSourceFiles(site.root)) {
|
|
205
|
-
let source: string;
|
|
206
|
-
try {
|
|
207
|
-
source = readFileSync(file, "utf8");
|
|
208
|
-
} catch {
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
issues.push(...checkApiCallSource(source, file, check));
|
|
212
|
-
}
|
|
213
|
-
return issues;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Checks the `makeApi` calls in a single file's source.
|
|
218
|
-
* @param source The file's source code
|
|
219
|
-
* @param file The file's absolute path (used to resolve relative imports and
|
|
220
|
-
* to derive the path shown in messages)
|
|
221
|
-
* @param check Shared state from `createApiCallCheck`
|
|
222
|
-
*/
|
|
223
|
-
export function checkApiCallSource(
|
|
224
|
-
source: string,
|
|
225
|
-
file: string,
|
|
226
|
-
check: ApiCallCheck,
|
|
227
|
-
): RouteCheckIssue[] {
|
|
228
|
-
const imports = new Map<string, string>();
|
|
229
|
-
for (let m = DEFAULT_IMPORT_RE.exec(source); m; m = DEFAULT_IMPORT_RE.exec(source)) {
|
|
230
|
-
imports.set(m[1], m[3]);
|
|
231
|
-
}
|
|
232
|
-
const issues: RouteCheckIssue[] = [];
|
|
233
|
-
for (let m = MAKE_API_RE.exec(source); m; m = MAKE_API_RE.exec(source)) {
|
|
234
|
-
const specifier = imports.get(m[3]);
|
|
235
|
-
if (!specifier) continue;
|
|
236
|
-
const resolved = resolveSpecifier(specifier, file, check);
|
|
237
|
-
if (!resolved) continue;
|
|
238
|
-
const relFile = path.relative(check.root, resolved).replaceAll("\\", "/");
|
|
239
|
-
const route = check.routesByFile.get(relFile);
|
|
240
|
-
if (!route) continue;
|
|
241
|
-
const expected = expectedAnnotationPath(route);
|
|
242
|
-
if (m[2] !== expected) {
|
|
243
|
-
issues.push({
|
|
244
|
-
file: path.relative(check.root, file).replaceAll("\\", "/"),
|
|
245
|
-
line: lineOf(source, m.index),
|
|
246
|
-
severity: "error",
|
|
247
|
-
message: `makeApi route "${m[2]}" doesn't match the endpoint's route "${expected}" (derived from ${relFile}). Did the endpoint move?`,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return issues;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Resolves an import specifier to an existing file: relative specifiers are
|
|
256
|
-
* resolved against the importing file, others against tsconfig path aliases.
|
|
257
|
-
* Bare module specifiers that match no alias return undefined.
|
|
258
|
-
*/
|
|
259
|
-
export function resolveSpecifier(
|
|
260
|
-
specifier: string,
|
|
261
|
-
fromFile: string,
|
|
262
|
-
check: ApiCallCheck,
|
|
263
|
-
): string | undefined {
|
|
264
|
-
let candidate: string | undefined;
|
|
265
|
-
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
266
|
-
candidate = path.resolve(path.dirname(fromFile), specifier);
|
|
267
|
-
} else {
|
|
268
|
-
for (const alias of check.aliases) {
|
|
269
|
-
if (typeof alias.find === "string") {
|
|
270
|
-
if (specifier.startsWith(alias.find)) {
|
|
271
|
-
candidate = path.resolve(alias.replacement, specifier.slice(alias.find.length));
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
} else if (alias.find.test(specifier)) {
|
|
275
|
-
candidate = specifier.replace(alias.find, alias.replacement);
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (!candidate) return undefined;
|
|
281
|
-
const probes = [
|
|
282
|
-
candidate,
|
|
283
|
-
`${candidate}.ts`,
|
|
284
|
-
`${candidate}.js`,
|
|
285
|
-
path.join(candidate, "index.ts"),
|
|
286
|
-
path.join(candidate, "index.js"),
|
|
287
|
-
];
|
|
288
|
-
for (const probe of probes) {
|
|
289
|
-
if (existsSync(probe) && statSync(probe).isFile()) return probe;
|
|
290
|
-
}
|
|
291
|
-
return undefined;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function walkSourceFiles(root: string): string[] {
|
|
295
|
-
const files: string[] = [];
|
|
296
|
-
const visit = (dir: string): void => {
|
|
297
|
-
let entries;
|
|
298
|
-
try {
|
|
299
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
300
|
-
} catch {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
for (const entry of entries) {
|
|
304
|
-
if (entry.isDirectory()) {
|
|
305
|
-
if (!SKIP_DIRS.has(entry.name)) visit(path.join(dir, entry.name));
|
|
306
|
-
} else if (
|
|
307
|
-
entry.isFile() &&
|
|
308
|
-
SOURCE_FILE_RE.test(entry.name) &&
|
|
309
|
-
!entry.name.endsWith(".d.ts")
|
|
310
|
-
) {
|
|
311
|
-
files.push(path.join(dir, entry.name));
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
visit(root);
|
|
316
|
-
return files;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* The route path a file's annotation should use, derived from its manifest
|
|
321
|
-
* entry: internal suffixes (`/~server`, `/_layout` etc) are stripped so e.g.
|
|
322
|
-
* `posts/[id]/+page.server.ts` expects `"/posts/[id]"`.
|
|
323
|
-
*/
|
|
324
|
-
export function expectedAnnotationPath(route: Route): string {
|
|
325
|
-
let p = route.path;
|
|
326
|
-
switch (route.type) {
|
|
327
|
-
case PAGE_SERVER_ROUTE:
|
|
328
|
-
p = p.replace(/\/~server$/, "");
|
|
329
|
-
break;
|
|
330
|
-
case LAYOUT_ROUTE:
|
|
331
|
-
p = p.replace(/\/_layout$/, "");
|
|
332
|
-
break;
|
|
333
|
-
case LAYOUT_SERVER_ROUTE:
|
|
334
|
-
p = p.replace(/\/_layout\/~server$/, "");
|
|
335
|
-
break;
|
|
336
|
-
case HOOK_ROUTE:
|
|
337
|
-
p = p.replace(/\/_hook$/, "");
|
|
338
|
-
break;
|
|
339
|
-
case HOOK_SERVER_ROUTE:
|
|
340
|
-
p = p.replace(/\/_hook\/~server$/, "");
|
|
341
|
-
break;
|
|
342
|
-
case ERROR_ROUTE:
|
|
343
|
-
p = p.replace(/\/_error$/, "");
|
|
344
|
-
break;
|
|
345
|
-
}
|
|
346
|
-
return p === "" ? "/" : p;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function typeNameFor(route: Route): string {
|
|
350
|
-
switch (route.type) {
|
|
351
|
-
case PAGE_SERVER_ROUTE:
|
|
352
|
-
case LAYOUT_SERVER_ROUTE:
|
|
353
|
-
return "PageServerEndPoint";
|
|
354
|
-
case SERVER_ROUTE:
|
|
355
|
-
return "ServerEndPoint";
|
|
356
|
-
case HOOK_ROUTE:
|
|
357
|
-
case HOOK_SERVER_ROUTE:
|
|
358
|
-
return "ServerHook";
|
|
359
|
-
default:
|
|
360
|
-
return "PageEndPoint";
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Parses the type arguments that follow an endpoint type name, returning the
|
|
366
|
-
* first argument when it is a string literal (the route path), plus the index
|
|
367
|
-
* after the closing `>` (or after the type name when there are no arguments).
|
|
368
|
-
*/
|
|
369
|
-
function parseTypeArgs(
|
|
370
|
-
source: string,
|
|
371
|
-
start: number,
|
|
372
|
-
): { routePath: string | undefined; end: number } {
|
|
373
|
-
let i = start;
|
|
374
|
-
while (i < source.length && /\s/.test(source[i])) i++;
|
|
375
|
-
if (source[i] !== "<") return { routePath: undefined, end: i };
|
|
376
|
-
i++;
|
|
377
|
-
let depth = 1;
|
|
378
|
-
let routePath: string | undefined;
|
|
379
|
-
let seenComma = false;
|
|
380
|
-
while (i < source.length) {
|
|
381
|
-
const c = source[i];
|
|
382
|
-
if (c === '"' || c === "'") {
|
|
383
|
-
const quote = c;
|
|
384
|
-
let j = i + 1;
|
|
385
|
-
while (j < source.length && source[j] !== quote) {
|
|
386
|
-
if (source[j] === "\\") j++;
|
|
387
|
-
j++;
|
|
388
|
-
}
|
|
389
|
-
if (routePath === undefined && !seenComma) routePath = source.slice(i + 1, j);
|
|
390
|
-
i = j + 1;
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
if (c === "<") depth++;
|
|
394
|
-
else if (c === ">") {
|
|
395
|
-
depth--;
|
|
396
|
-
if (depth === 0) return { routePath, end: i + 1 };
|
|
397
|
-
} else if (c === "," && routePath === undefined) {
|
|
398
|
-
seenComma = true;
|
|
399
|
-
}
|
|
400
|
-
i++;
|
|
401
|
-
}
|
|
402
|
-
return { routePath, end: i };
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Parses an `as T`-style cast that follows a `satisfies T` usage, returning
|
|
407
|
-
* its optional route path argument and the index after the cast.
|
|
408
|
-
*/
|
|
409
|
-
function parseAsCast(
|
|
410
|
-
source: string,
|
|
411
|
-
start: number,
|
|
412
|
-
): { routePath: string | undefined; end: number } | undefined {
|
|
413
|
-
let i = start;
|
|
414
|
-
while (i < source.length && /\s/.test(source[i])) i++;
|
|
415
|
-
if (!/^as\b/.test(source.slice(i, i + 3))) return undefined;
|
|
416
|
-
i += 2;
|
|
417
|
-
while (i < source.length && /\s/.test(source[i])) i++;
|
|
418
|
-
if (!TYPE_NAME_RE.test(source.slice(i))) return undefined;
|
|
419
|
-
i = i + source.slice(i).match(TYPE_NAME_RE)![0].length;
|
|
420
|
-
return parseTypeArgs(source, i);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function routePathArg(expected: string, actual: string | undefined): string {
|
|
424
|
-
// Keep the user's own (correct) path when widening a properly-annotated
|
|
425
|
-
// export; otherwise suggest the path derived from the file's location
|
|
426
|
-
return `<"${actual ?? expected}">`;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function lineOf(source: string, index: number): number {
|
|
430
|
-
let line = 1;
|
|
431
|
-
for (let i = 0; i < index; i++) {
|
|
432
|
-
if (source[i] === "\n") line++;
|
|
433
|
-
}
|
|
434
|
-
return line;
|
|
435
|
-
}
|
package/src/site/clientEntry.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import manifest from "@torpor/build/manifest";
|
|
2
|
-
import loadData from "../nav/loadData.ts";
|
|
3
|
-
import navigate from "../nav/navigate.ts";
|
|
4
|
-
import $page from "../state/$page.ts";
|
|
5
|
-
import client from "../state/client";
|
|
6
|
-
import type LayoutPath from "../types/LayoutPath";
|
|
7
|
-
import type PageEndPoint from "../types/PageEndPoint";
|
|
8
|
-
import type PageServerEndPoint from "../types/PageServerEndPoint";
|
|
9
|
-
import Router from "./Router.ts";
|
|
10
|
-
|
|
11
|
-
// Build the router from the Site object created by the user
|
|
12
|
-
const router = new Router();
|
|
13
|
-
router.addPages(manifest.routes);
|
|
14
|
-
|
|
15
|
-
// Put it in client state so that it can be accessed from navigate. We can't
|
|
16
|
-
// just import it to navigate because that gets built by tsdown, without Vite
|
|
17
|
-
// and the manifest
|
|
18
|
-
client.router = router;
|
|
19
|
-
|
|
20
|
-
// Intercept clicks on links
|
|
21
|
-
window.addEventListener("click", async (e) => {
|
|
22
|
-
const link = getLink(e.target);
|
|
23
|
-
if (link) {
|
|
24
|
-
if (ignoreAnchorMouseEvent(link, e)) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
e.preventDefault();
|
|
29
|
-
|
|
30
|
-
const href = link.href;
|
|
31
|
-
const url = new URL(href);
|
|
32
|
-
|
|
33
|
-
if (await navigate(url)) {
|
|
34
|
-
window.history.pushState(null, "", url);
|
|
35
|
-
} else {
|
|
36
|
-
window.location.href = href;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Intercept mouseover/focus/touchstart on links for prefetching
|
|
42
|
-
// TODO: settings e.g. <a href="" data-tb-prefetch="eager"/"visible"/"hover"/"touch">
|
|
43
|
-
window.addEventListener("mouseover", maybePrefetch);
|
|
44
|
-
window.addEventListener("touchstart", maybePrefetch);
|
|
45
|
-
|
|
46
|
-
async function maybePrefetch(e: MouseEvent | TouchEvent) {
|
|
47
|
-
const link = getLink(e.target);
|
|
48
|
-
if (link) {
|
|
49
|
-
if (ignoreAnchorMouseEvent(link, e)) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const href = link.href;
|
|
54
|
-
const url = new URL(href);
|
|
55
|
-
|
|
56
|
-
const path = url.pathname;
|
|
57
|
-
const query = url.searchParams;
|
|
58
|
-
const route = router.match(path, query);
|
|
59
|
-
if (route) {
|
|
60
|
-
const handler = route.handler;
|
|
61
|
-
const params = route.params || {};
|
|
62
|
-
|
|
63
|
-
// There should be a client endpoint
|
|
64
|
-
const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;
|
|
65
|
-
|
|
66
|
-
// There may be a server endpoint
|
|
67
|
-
const serverEndPoint: PageServerEndPoint | undefined =
|
|
68
|
-
handler.serverEndPoint && (await handler.serverEndPoint())?.default;
|
|
69
|
-
|
|
70
|
-
// Just dummy this up
|
|
71
|
-
let newStack: LayoutPath[] = [];
|
|
72
|
-
|
|
73
|
-
await loadData(handler, params, path, query, newStack, clientEndPoint, serverEndPoint, true);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function getLink(target: EventTarget | null): HTMLAnchorElement | undefined {
|
|
79
|
-
// The target may be a child element of the link (e.g. an icon inside it),
|
|
80
|
-
// so walk up to the nearest actual link. Fragment-only links (href="#...")
|
|
81
|
-
// are left to the browser's native jump-to-anchor behavior
|
|
82
|
-
if (!(target instanceof Element)) return;
|
|
83
|
-
const link = target.closest("a[href]");
|
|
84
|
-
if (link?.getAttribute("href")?.startsWith("#")) return;
|
|
85
|
-
return (link as HTMLAnchorElement) ?? undefined;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function ignoreAnchorMouseEvent(link: HTMLAnchorElement, e: MouseEvent | TouchEvent) {
|
|
89
|
-
// Ignore the event if it was already handled (e.g. a user handler calling
|
|
90
|
-
// preventDefault), a modifier key is pressed, the target is not "_self"
|
|
91
|
-
// (e.g. "_blank"), rel="external", it's a download link, or it's in a
|
|
92
|
-
// contenteditable region
|
|
93
|
-
return (
|
|
94
|
-
e.defaultPrevented ||
|
|
95
|
-
e.altKey ||
|
|
96
|
-
e.ctrlKey ||
|
|
97
|
-
e.metaKey ||
|
|
98
|
-
e.shiftKey ||
|
|
99
|
-
(link.target && link.target !== "_self") ||
|
|
100
|
-
link.rel === "external" ||
|
|
101
|
-
link.hasAttribute("download") ||
|
|
102
|
-
link.isContentEditable
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Listen for changes to the URL that occur when the user navigates using the
|
|
107
|
-
// back or forward buttons
|
|
108
|
-
window.addEventListener("popstate", async () => {
|
|
109
|
-
await navigateToLocation(document.location);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Do the initial navigation and hydration
|
|
113
|
-
await navigateToLocation(document.location, true);
|
|
114
|
-
|
|
115
|
-
async function navigateToLocation(location: Location, firstTime = false) {
|
|
116
|
-
// Reset $page on submit and link navigation
|
|
117
|
-
// TODO: Should we put data in $page.data??
|
|
118
|
-
$page.form = undefined;
|
|
119
|
-
|
|
120
|
-
return await navigate(new URL(location.toString()), firstTime);
|
|
121
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type Adapter from "../types/Adapter";
|
|
2
|
-
|
|
3
|
-
// TODO: This should be an adapter-auto package that conditionally loads other packages...
|
|
4
|
-
|
|
5
|
-
const defaultAdapter: Adapter = {
|
|
6
|
-
serve: () => {
|
|
7
|
-
console.log("An adapter needs to be set in site.config");
|
|
8
|
-
},
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export default defaultAdapter;
|
package/src/site/layoutSlots.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layouts are composed by the layout engine passing each layout (and finally
|
|
3
|
-
* the page) as the default slot of its parent — so every `_layout` component
|
|
4
|
-
* must render `<slot />`, or pass the default slot down to a child component
|
|
5
|
-
* it renders. A layout that doesn't drops its entire content: the server
|
|
6
|
-
* renders the layout's own markup with an empty content area (no error), and
|
|
7
|
-
* the first client-side navigation that reuses the layout crashes on a null
|
|
8
|
-
* slot region.
|
|
9
|
-
*
|
|
10
|
-
* The static `checkLayoutSlots` check catches this at build/dev time; these
|
|
11
|
-
* helpers report it at request time for the cases static analysis can't see
|
|
12
|
-
* (e.g. a `<slot />` rendered conditionally inside `@if`). They are
|
|
13
|
-
* server-side only — nothing here is imported into the client bundle.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Returns the path of the outermost layout that never rendered its
|
|
18
|
-
* `<slot />`, or undefined when every level rendered.
|
|
19
|
-
*
|
|
20
|
-
* Slot level j (1-based) is the slot render that layout j - 1 receives: the
|
|
21
|
-
* root layout (layouts[0]) renders level 1, layout i renders level i + 1,
|
|
22
|
-
* and level layouts.length renders the page. A broken level makes every
|
|
23
|
-
* deeper level unreachable, so the first level that never ran identifies
|
|
24
|
-
* the offending layout.
|
|
25
|
-
*/
|
|
26
|
-
export function findMissingSlotLayout(
|
|
27
|
-
layouts: readonly { path: string }[],
|
|
28
|
-
invokedLevels: ReadonlySet<number>,
|
|
29
|
-
): string | undefined {
|
|
30
|
-
for (let level = 1; level <= layouts.length; level++) {
|
|
31
|
-
if (!invokedLevels.has(level)) {
|
|
32
|
-
return layouts[level - 1].path;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const warnedLayouts = new Set<string>();
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Reports a layout that dropped its slot content. The condition is a genuine
|
|
42
|
-
* rendering bug (the page content is missing from the output), so it is not
|
|
43
|
-
* dev-gated — but it warns only once per layout path to avoid per-request
|
|
44
|
-
* noise.
|
|
45
|
-
*/ export function warnMissingSlotContent(layoutPath: string): void {
|
|
46
|
-
if (warnedLayouts.has(layoutPath)) return;
|
|
47
|
-
warnedLayouts.add(layoutPath);
|
|
48
|
-
console.warn(
|
|
49
|
-
`[torpor] The layout at "${layoutPath}" never rendered <slot />, so the page content ` +
|
|
50
|
-
`was not included in the render. Every _layout component must render <slot /> (or ` +
|
|
51
|
-
`pass the default slot down to a child component it renders).`,
|
|
52
|
-
);
|
|
53
|
-
}
|