@antelopejs/dms-frontend 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -24
- package/dist/commands/build-action.js +3 -2
- package/dist/commands/dev-action.js +2 -1
- package/dist/commands/prepare-action.js +1 -1
- package/dist/commands/start-action.js +3 -24
- package/dist/commands/workspace-task.js +2 -1
- package/dist/config.js +3 -0
- package/dist/derived-outputs.js +3 -3
- package/dist/discovery.js +17 -6
- package/dist/entry-weight.js +15 -0
- package/dist/fs-sync.js +7 -4
- package/dist/index.js +2 -1
- package/dist/internal/built-workspace.d.ts +4 -0
- package/dist/internal/built-workspace.js +43 -0
- package/dist/internal/ensure.d.ts +1 -0
- package/dist/internal/ensure.js +8 -0
- package/dist/internal/locale-catalogs.d.ts +8 -3
- package/dist/internal/locale-catalogs.js +16 -6
- package/dist/internal/manifest-decoder.d.ts +7 -0
- package/dist/internal/manifest-decoder.js +81 -0
- package/dist/internal/own-release.d.ts +5 -0
- package/dist/internal/own-release.js +20 -0
- package/dist/internal/plain-object.d.ts +1 -0
- package/dist/internal/plain-object.js +6 -0
- package/dist/internal/text-list.d.ts +1 -0
- package/dist/internal/text-list.js +10 -0
- package/dist/internal/vite-manifest.d.ts +6 -0
- package/dist/internal/vite-manifest.js +24 -0
- package/dist/layer-watch.js +25 -24
- package/dist/layers.d.ts +1 -1
- package/dist/layers.js +3 -1
- package/dist/locale-files.d.ts +2 -1
- package/dist/locale-files.js +4 -0
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +44 -31
- package/dist/materialize.js +6 -2
- package/dist/module-set.js +2 -1
- package/dist/node-options.d.ts +1 -0
- package/dist/node-options.js +8 -0
- package/dist/output.d.ts +15 -9
- package/dist/output.js +30 -18
- package/dist/private-values.d.ts +1 -1
- package/dist/private-values.js +5 -3
- package/dist/project-modules.js +13 -8
- package/dist/renderer-range.js +5 -3
- package/dist/server-process.js +1 -1
- package/dist/verify-source-build.js +10 -2
- package/dist/verify-source-client-bundle.d.ts +1 -0
- package/dist/verify-source-client-bundle.js +30 -0
- package/dist/verify-source-runner.js +44 -60
- package/dist/verify-source-sources.js +49 -11
- package/dist/workspace-setup.js +1 -1
- package/package.json +6 -4
- package/skills/dms-frontend/SKILL.md +1 -1
- package/templates/vue/app-runtime.ts +90 -16
- package/templates/vue/client-server-guard.mjs +16 -0
- package/templates/vue/compress-assets.mjs +9 -1
- package/templates/vue/entry-weight.mjs +22 -0
- package/templates/vue/frontend-module.ts +1 -0
- package/templates/vue/frontend-runtime/module-registration.ts +2 -2
- package/templates/vue/frontend-runtime/state.ts +2 -2
- package/templates/vue/frontend-runtime/types.ts +8 -2
- package/templates/vue/globals.d.ts +5 -0
- package/templates/vue/head-order.mjs +7 -0
- package/templates/vue/icon-bundle.ts +31 -0
- package/templates/vue/icon-scan.mjs +4 -0
- package/templates/vue/message-subset.ts +65 -0
- package/templates/vue/network-error.ts +4 -0
- package/templates/vue/server/auth/backend.mjs +41 -4
- package/templates/vue/server/auth/client-ip.mjs +52 -4
- package/templates/vue/server/auth/establish-endpoints.mjs +10 -6
- package/templates/vue/server/auth/session.mjs +81 -2
- package/templates/vue/server/auth-hooks.mjs +15 -5
- package/templates/vue/server/backend-proxies.mjs +65 -9
- package/templates/vue/server/backend-request.mjs +61 -0
- package/templates/vue/server/backend-response.mjs +23 -6
- package/templates/vue/server/built-files.mjs +7 -0
- package/templates/vue/server/cli-report.mjs +53 -11
- package/templates/vue/server/client-manifest.mjs +23 -1
- package/templates/vue/server/content.mjs +18 -1
- package/templates/vue/server/dev-private-files.mjs +17 -1
- package/templates/vue/server/dev-styles.mjs +38 -0
- package/templates/vue/server/email.mjs +9 -3
- package/templates/vue/server/favicon.mjs +3 -0
- package/templates/vue/server/generated-files.mjs +211 -0
- package/templates/vue/server/homepage.mjs +21 -9
- package/templates/vue/server/html-tag.mjs +4 -0
- package/templates/vue/server/icons.mjs +80 -7
- package/templates/vue/server/in-place-render.mjs +35 -0
- package/templates/vue/server/inertia.mjs +61 -2
- package/templates/vue/server/module-event.mjs +98 -17
- package/templates/vue/server/module-route-types.mjs +78 -0
- package/templates/vue/server/module-routes.mjs +138 -31
- package/templates/vue/server/module-server.mjs +98 -27
- package/templates/vue/server/module-testing.mjs +14 -5
- package/templates/vue/server/page-props.mjs +92 -2
- package/templates/vue/server/page-redirects.mjs +37 -0
- package/templates/vue/server/path-patterns.mjs +13 -1
- package/templates/vue/server/proxy-compression.mjs +22 -0
- package/templates/vue/server/proxy-headers.mjs +11 -1
- package/templates/vue/server/proxy-redirects.mjs +20 -5
- package/templates/vue/server/proxy-request.mjs +57 -0
- package/templates/vue/server/proxy-transfers.mjs +33 -5
- package/templates/vue/server/public-endpoints.mjs +138 -23
- package/templates/vue/server/public-options.mjs +20 -6
- package/templates/vue/server/request-failure.mjs +23 -0
- package/templates/vue/server/request-routing.mjs +26 -9
- package/templates/vue/server/request-target.mjs +9 -3
- package/templates/vue/server/route-rules.mjs +39 -15
- package/templates/vue/server/ssr-module.mjs +32 -0
- package/templates/vue/server/static-files.mjs +24 -1
- package/templates/vue/server/type-guards.mjs +71 -0
- package/templates/vue/server/vite-errors.mjs +32 -5
- package/templates/vue/server/web-body.mjs +3 -0
- package/templates/vue/server-module.ts +10 -1
- package/templates/vue/server.mjs +132 -148
- package/templates/vue/sfc-global-types.mjs +112 -13
- package/templates/vue/ssr-renderer.ts +12 -3
- package/templates/vue/ui-inertia-page.mjs +16 -1
- package/templates/vue/ui-kbd.mjs +2 -1
- package/templates/vue/ui-lazy-icons.mjs +44 -0
- package/templates/vue/ui-request-state.mjs +2 -1
- package/templates/vue/vite.config.ts +2 -0
package/templates/vue/server.mjs
CHANGED
|
@@ -2,18 +2,14 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|
|
2
2
|
import { createServer, maxHeaderSize } from "node:http";
|
|
3
3
|
import { extname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { RequestBodyError, UpstreamError } from "./server/auth/backend.mjs";
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
UpstreamError,
|
|
10
|
-
} from "./server/auth/backend.mjs";
|
|
11
|
-
import { forwardedHeaders } from "./server/auth/client-ip.mjs";
|
|
12
|
-
import { proxyAccess, refuseCrossOrigin } from "./server/public-endpoints.mjs";
|
|
7
|
+
backendJson,
|
|
8
|
+
serverComponentFetch,
|
|
9
|
+
} from "./server/backend-request.mjs";
|
|
13
10
|
import { readSession } from "./server/auth/session.mjs";
|
|
14
11
|
import {
|
|
15
12
|
BackendResponseError,
|
|
16
|
-
backendResponseError,
|
|
17
13
|
redirectAccessRefusal,
|
|
18
14
|
UNEXPECTED_ERROR_BODY,
|
|
19
15
|
} from "./server/backend-response.mjs";
|
|
@@ -50,20 +46,26 @@ import {
|
|
|
50
46
|
handleModuleRoute,
|
|
51
47
|
loadServerModules,
|
|
52
48
|
} from "./server/module-server.mjs";
|
|
53
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
redirectSignedOutPrivatePage,
|
|
51
|
+
redirectToHomepage,
|
|
52
|
+
} from "./server/page-redirects.mjs";
|
|
54
53
|
import { publicOptionsScript } from "./server/public-options.mjs";
|
|
55
|
-
import { renderInPlace
|
|
56
|
-
import {
|
|
54
|
+
import { renderInPlace } from "./server/in-place-render.mjs";
|
|
55
|
+
import {
|
|
56
|
+
loadPageProps,
|
|
57
|
+
pageRoute,
|
|
58
|
+
propsForQuery,
|
|
59
|
+
} from "./server/page-props.mjs";
|
|
57
60
|
import { htmlTag } from "./server/html-tag.mjs";
|
|
61
|
+
import { isString } from "./server/type-guards.mjs";
|
|
58
62
|
import { listenHost } from "./server/listen-host.mjs";
|
|
59
63
|
import * as icons from "./server/icons.mjs";
|
|
64
|
+
import { ssrRenderer } from "./server/ssr-module.mjs";
|
|
60
65
|
import * as requestFailure from "./server/request-failure.mjs";
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
} from "./server/proxy-compression.mjs";
|
|
65
|
-
import { backendUrl, refuseForeignTarget } from "./server/request-target.mjs";
|
|
66
|
-
import { sendWithinLimit, SERVER_OPTIONS } from "./server/proxy-transfers.mjs";
|
|
66
|
+
import { proxy } from "./server/proxy-request.mjs";
|
|
67
|
+
import { refuseForeignTarget } from "./server/request-target.mjs";
|
|
68
|
+
import { SERVER_OPTIONS } from "./server/proxy-transfers.mjs";
|
|
67
69
|
import {
|
|
68
70
|
createInertiaPage,
|
|
69
71
|
htmlHeaders,
|
|
@@ -75,21 +77,18 @@ import {
|
|
|
75
77
|
} from "./server/inertia.mjs";
|
|
76
78
|
export * from "./server/inertia.mjs";
|
|
77
79
|
|
|
80
|
+
/** @typedef {import("node:http").IncomingMessage} IncomingMessage */
|
|
81
|
+
/** @typedef {import("node:http").ServerResponse} ServerResponse */
|
|
82
|
+
/** @typedef {import("vite").ViteDevServer} ViteDevServer */
|
|
83
|
+
/** @typedef {import("./server/inertia.mjs").InertiaPage} InertiaPage */
|
|
84
|
+
/** @typedef {import("./server/in-place-render.mjs").ServerReads} ServerReads */
|
|
85
|
+
|
|
78
86
|
const INERTIA_HEADER = "x-inertia";
|
|
79
87
|
const JSON_TYPE = "application/json";
|
|
80
88
|
const PROJECT_ROOT = fileURLToPath(new URL(".", import.meta.url));
|
|
81
|
-
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
82
89
|
const LOCAL_ROUTES = [[icons.isIconRequest, icons.handleIcons]];
|
|
83
90
|
const CLIENT_ROOT = join(PROJECT_ROOT, "dist/client");
|
|
84
91
|
const SOURCE_TEMPLATE_PATH = join(PROJECT_ROOT, "index.html");
|
|
85
|
-
/**
|
|
86
|
-
* A URL, not a path: on Windows, `import()` reads `C:\...` as a URL whose
|
|
87
|
-
* scheme is `c:` and refuses it.
|
|
88
|
-
*/
|
|
89
|
-
const BUILT_SSR_RENDERER_URL = new URL(
|
|
90
|
-
"dist/ssr/ssr-renderer.js",
|
|
91
|
-
import.meta.url,
|
|
92
|
-
);
|
|
93
92
|
|
|
94
93
|
/**
|
|
95
94
|
* The SSR bundle keeps vue-i18n external, so Node loads its esm-bundler build,
|
|
@@ -102,16 +101,20 @@ function defineVueCompileFlags() {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
defineVueCompileFlags();
|
|
105
|
-
|
|
104
|
+
/** @type {ViteDevServer | undefined} */
|
|
106
105
|
let vite;
|
|
106
|
+
/** @type {Promise<ViteDevServer> | undefined} */
|
|
107
107
|
let vitePromise;
|
|
108
|
+
/** @type {import("node:http").Server | undefined} */
|
|
108
109
|
let frontendHttpServer;
|
|
109
110
|
|
|
111
|
+
/** @returns {Promise<ViteDevServer | undefined>} */
|
|
110
112
|
async function developmentServer() {
|
|
111
113
|
if (process.env.DMS_DEV !== "true") return undefined;
|
|
112
114
|
if (vite) return vite;
|
|
113
115
|
vitePromise ??= import("vite")
|
|
114
116
|
.then(({ createServer: createViteServer }) => {
|
|
117
|
+
/** @type {import("vite").ServerOptions} */
|
|
115
118
|
const serverOptions = {
|
|
116
119
|
middlewareMode: true,
|
|
117
120
|
fs: { deny: DEV_FS_DENY },
|
|
@@ -127,57 +130,24 @@ async function developmentServer() {
|
|
|
127
130
|
vite = server;
|
|
128
131
|
return server;
|
|
129
132
|
})
|
|
130
|
-
.catch(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
.catch(
|
|
134
|
+
/** @param {unknown} error */ (error) => {
|
|
135
|
+
vitePromise = undefined;
|
|
136
|
+
throw error;
|
|
137
|
+
},
|
|
138
|
+
);
|
|
134
139
|
return vitePromise;
|
|
135
140
|
}
|
|
136
141
|
|
|
137
|
-
/**
|
|
138
|
-
* The headers of a backend request: the relayed client headers, the client's
|
|
139
|
-
* Origin and Referer as received, the `X-Forwarded-*` headers (set last, so no
|
|
140
|
-
* caller writes them), and the session's access token unless the request goes
|
|
141
|
-
* anonymously.
|
|
142
|
-
*/
|
|
143
|
-
function backendHeaders(request, access = { anonymous: false, headers: [] }) {
|
|
144
|
-
const headers = {
|
|
145
|
-
...relayedHeaders(request, [
|
|
146
|
-
"accept",
|
|
147
|
-
"content-type",
|
|
148
|
-
"if-match",
|
|
149
|
-
"origin",
|
|
150
|
-
"referer",
|
|
151
|
-
...CLIENT_HEADERS,
|
|
152
|
-
...access.headers,
|
|
153
|
-
]),
|
|
154
|
-
...forwardedHeaders(request),
|
|
155
|
-
};
|
|
156
|
-
const session = access.anonymous ? undefined : readSession(request);
|
|
157
|
-
if (session?.accessToken)
|
|
158
|
-
headers.authorization = `Bearer ${session.accessToken}`;
|
|
159
|
-
return headers;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function serverComponentFetch(request) {
|
|
163
|
-
return serverReads(() => backendHeaders(request));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function backendJson(path, request) {
|
|
167
|
-
const response = await fetch(new URL(path, process.env.DMS_API_BASE_URL), {
|
|
168
|
-
headers: backendHeaders(request),
|
|
169
|
-
});
|
|
170
|
-
if (!response.ok) {
|
|
171
|
-
throw await backendResponseError(response);
|
|
172
|
-
}
|
|
173
|
-
return response.json();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
142
|
/** The session logic of the module that owns sign-in. */
|
|
177
143
|
async function currentAuth() {
|
|
178
144
|
return (await loadServerModules(await developmentServer())).serverAuth;
|
|
179
145
|
}
|
|
180
146
|
|
|
147
|
+
/**
|
|
148
|
+
* @param {IncomingMessage} request
|
|
149
|
+
* @returns {import("./server/page-props.mjs").PageBackend}
|
|
150
|
+
*/
|
|
181
151
|
function pageBackend(request) {
|
|
182
152
|
return {
|
|
183
153
|
fetchJson: (path) => backendJson(path, request),
|
|
@@ -188,76 +158,23 @@ function pageBackend(request) {
|
|
|
188
158
|
};
|
|
189
159
|
}
|
|
190
160
|
|
|
191
|
-
function pageProps(request, auth) {
|
|
192
|
-
return loadPageProps(request, pageBackend(request), auth);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function redirectToHomepage(request, response, pathname) {
|
|
196
|
-
if (pathname !== "/" || HOMEPAGE === "/") return false;
|
|
197
|
-
redirectFrontendVisit(request, response, HOMEPAGE);
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function redirectSignedOutPrivatePage(request, response, props, auth) {
|
|
202
|
-
const route = props.page?.route;
|
|
203
|
-
if (
|
|
204
|
-
route?.publicAccess === true ||
|
|
205
|
-
route?.hasAccess !== false ||
|
|
206
|
-
readSession(request)
|
|
207
|
-
)
|
|
208
|
-
return false;
|
|
209
|
-
redirectFrontendVisit(request, response, auth.signInLocation(request.url));
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
161
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* the frontend's origin.
|
|
217
|
-
*
|
|
218
|
-
* A client that left while the request was routed closed the response already,
|
|
219
|
-
* and would never abort the request below.
|
|
220
|
-
*
|
|
221
|
-
* Cancelling the body alone misses a client that leaves before the backend
|
|
222
|
-
* answers: its stream, an SSE feed above all, then stayed open for good.
|
|
223
|
-
*
|
|
224
|
-
* A backend redirect is the browser's to follow: Media sends a file's link to
|
|
225
|
-
* the storage's signed URL, on the public origin the server itself may not
|
|
226
|
-
* reach, which turned every download into a 502.
|
|
162
|
+
* @param {IncomingMessage} request
|
|
163
|
+
* @param {import("./server/page-props.mjs").PageAuth} auth
|
|
227
164
|
*/
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (access.isRefused) {
|
|
231
|
-
refuseCrossOrigin(response);
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
234
|
-
if (response.closed) return;
|
|
235
|
-
const abandoned = new AbortController();
|
|
236
|
-
response.once("close", () => abandoned.abort());
|
|
237
|
-
const init = {
|
|
238
|
-
signal: abandoned.signal,
|
|
239
|
-
method: request.method,
|
|
240
|
-
headers: { ...backendHeaders(request, access), ...PLAIN_ANSWER_HEADERS },
|
|
241
|
-
duplex: "half",
|
|
242
|
-
redirect: "manual",
|
|
243
|
-
};
|
|
244
|
-
const upstream = await sendWithinLimit(
|
|
245
|
-
request,
|
|
246
|
-
response,
|
|
247
|
-
backendProxy.maxBodyBytes,
|
|
248
|
-
(body) => fetch(backendUrl(request.url), { ...init, body }),
|
|
249
|
-
);
|
|
250
|
-
if (!upstream) return true;
|
|
251
|
-
await relayAnswer(request, response, upstream, {
|
|
252
|
-
fetched: true,
|
|
253
|
-
isProxiedPath,
|
|
254
|
-
});
|
|
165
|
+
function pageProps(request, auth) {
|
|
166
|
+
return loadPageProps(request, pageBackend(request), auth);
|
|
255
167
|
}
|
|
256
168
|
|
|
257
169
|
/**
|
|
258
170
|
* Send the built file `pathname` names, or `false` when the build has none
|
|
259
171
|
* there. `publicBuildFile` decides which file that is; `serveStaticFile` how
|
|
260
172
|
* it goes out.
|
|
173
|
+
*
|
|
174
|
+
* @param {string} pathname
|
|
175
|
+
* @param {IncomingMessage} request
|
|
176
|
+
* @param {ServerResponse} response
|
|
177
|
+
* @returns {boolean}
|
|
261
178
|
*/
|
|
262
179
|
function serveAsset(pathname, request, response) {
|
|
263
180
|
const file = publicBuildFile(CLIENT_ROOT, pathname);
|
|
@@ -268,14 +185,10 @@ function serveAsset(pathname, request, response) {
|
|
|
268
185
|
return true;
|
|
269
186
|
}
|
|
270
187
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
await icons.provideIconCollections(renderer);
|
|
276
|
-
return renderer;
|
|
277
|
-
}
|
|
278
|
-
|
|
188
|
+
/**
|
|
189
|
+
* @param {InertiaPage} page
|
|
190
|
+
* @returns {import("./ssr-renderer.ts").DmsSsrResult}
|
|
191
|
+
*/
|
|
279
192
|
function clientRenderedShell(page) {
|
|
280
193
|
return {
|
|
281
194
|
body: inertiaAppHtml(page),
|
|
@@ -285,6 +198,12 @@ function clientRenderedShell(page) {
|
|
|
285
198
|
};
|
|
286
199
|
}
|
|
287
200
|
|
|
201
|
+
/**
|
|
202
|
+
* @param {InertiaPage} page
|
|
203
|
+
* @param {ServerReads} serverFetch
|
|
204
|
+
* @param {string | undefined} requestCookies
|
|
205
|
+
* @param {ViteDevServer | undefined} devServer
|
|
206
|
+
*/
|
|
288
207
|
async function serverRendered(page, serverFetch, requestCookies, devServer) {
|
|
289
208
|
try {
|
|
290
209
|
const renderer = await ssrRenderer(devServer);
|
|
@@ -301,6 +220,13 @@ async function serverRendered(page, serverFetch, requestCookies, devServer) {
|
|
|
301
220
|
/**
|
|
302
221
|
* Renders a page document. With `{ ssr: false }` (a route rule), the client
|
|
303
222
|
* renders the page from the shell instead.
|
|
223
|
+
*
|
|
224
|
+
* @param {InertiaPage} page
|
|
225
|
+
* @param {string | undefined} requestUrl
|
|
226
|
+
* @param {ServerReads} serverFetch
|
|
227
|
+
* @param {string | undefined} requestCookies
|
|
228
|
+
* @param {{ ssr?: false }} [options]
|
|
229
|
+
* @returns {Promise<{ html: string, status: number, redirect?: string }>}
|
|
304
230
|
*/
|
|
305
231
|
export async function renderHtml(
|
|
306
232
|
page,
|
|
@@ -330,7 +256,7 @@ export async function renderHtml(
|
|
|
330
256
|
.replace("<title>Antelope DMS</title>", () =>
|
|
331
257
|
withFaviconPlaceholder(rendered.head.headTags),
|
|
332
258
|
)
|
|
333
|
-
.replace(/<html([^>]*)>/, (_, attributes) =>
|
|
259
|
+
.replace(/<html([^>]*)>/, (_, /** @type {string} */ attributes) =>
|
|
334
260
|
htmlTag(attributes, rendered.head.htmlAttrs),
|
|
335
261
|
)
|
|
336
262
|
.replace("<body", `<body ${rendered.head.bodyAttrs}`)
|
|
@@ -343,6 +269,12 @@ export async function renderHtml(
|
|
|
343
269
|
return { html, status: rendered.error?.statusCode ?? 200 };
|
|
344
270
|
}
|
|
345
271
|
|
|
272
|
+
/**
|
|
273
|
+
* @param {InertiaPage} page
|
|
274
|
+
* @param {IncomingMessage} request
|
|
275
|
+
* @param {Pick<import("./server/route-rules.mjs").RouteRule, "ssr">} [rule]
|
|
276
|
+
* @param {ServerReads} [reads]
|
|
277
|
+
*/
|
|
346
278
|
function renderRequestHtml(page, request, rule = {}, reads) {
|
|
347
279
|
const fetch = reads ?? serverComponentFetch(request);
|
|
348
280
|
return renderHtml(page, request.url, fetch, request.headers.cookie, {
|
|
@@ -353,6 +285,11 @@ function renderRequestHtml(page, request, rule = {}, reads) {
|
|
|
353
285
|
/**
|
|
354
286
|
* Renders a page document, at the query the page completes as it renders:
|
|
355
287
|
* see `renderInPlace`.
|
|
288
|
+
*
|
|
289
|
+
* @param {InertiaPage} page
|
|
290
|
+
* @param {IncomingMessage} request
|
|
291
|
+
* @param {import("./server/route-rules.mjs").MergedRouteRule} rule
|
|
292
|
+
* @param {import("./server/page-props.mjs").PageAuth} auth
|
|
356
293
|
*/
|
|
357
294
|
function renderDocument(page, request, rule, auth) {
|
|
358
295
|
const fetch = serverComponentFetch(request);
|
|
@@ -365,9 +302,14 @@ function renderDocument(page, request, rule, auth) {
|
|
|
365
302
|
);
|
|
366
303
|
}
|
|
367
304
|
|
|
305
|
+
/**
|
|
306
|
+
* @param {unknown} error
|
|
307
|
+
* @param {IncomingMessage} request
|
|
308
|
+
* @param {ServerResponse} response
|
|
309
|
+
*/
|
|
368
310
|
async function writeBackendError(error, request, response) {
|
|
369
311
|
const auth = await currentAuth();
|
|
370
|
-
const status =
|
|
312
|
+
const status = requestFailure.failureStatus(error);
|
|
371
313
|
const pathname = new URL(request.url, "http://frontend.local").pathname;
|
|
372
314
|
if (
|
|
373
315
|
error instanceof BackendResponseError &&
|
|
@@ -410,6 +352,12 @@ async function writeBackendError(error, request, response) {
|
|
|
410
352
|
response.end(JSON.stringify(payload));
|
|
411
353
|
}
|
|
412
354
|
|
|
355
|
+
/**
|
|
356
|
+
* @param {ViteDevServer} devServer
|
|
357
|
+
* @param {IncomingMessage} request
|
|
358
|
+
* @param {ServerResponse} response
|
|
359
|
+
* @returns {Promise<boolean>}
|
|
360
|
+
*/
|
|
413
361
|
async function answeredByVite(devServer, request, response) {
|
|
414
362
|
const result = await runViteMiddlewares(devServer, request, response);
|
|
415
363
|
if (result.status === "failed") writeViteError(response, result.error);
|
|
@@ -422,6 +370,10 @@ async function answeredByVite(devServer, request, response) {
|
|
|
422
370
|
* production server serves. Only a path with an extension names one.
|
|
423
371
|
*
|
|
424
372
|
* The path is decoded as Vite and the asset server read it: `%2E` is a dot.
|
|
373
|
+
*
|
|
374
|
+
* @param {ViteDevServer | undefined} devServer
|
|
375
|
+
* @param {string} requested
|
|
376
|
+
* @returns {boolean}
|
|
425
377
|
*/
|
|
426
378
|
function hasRendererFile(devServer, requested) {
|
|
427
379
|
let pathname;
|
|
@@ -445,7 +397,15 @@ function hasRendererFile(devServer, requested) {
|
|
|
445
397
|
});
|
|
446
398
|
}
|
|
447
399
|
|
|
448
|
-
/**
|
|
400
|
+
/**
|
|
401
|
+
* Vite's files in development, the build's in production; else a 404.
|
|
402
|
+
*
|
|
403
|
+
* @param {ViteDevServer | undefined} devServer
|
|
404
|
+
* @param {IncomingMessage} request
|
|
405
|
+
* @param {ServerResponse} response
|
|
406
|
+
* @param {string} pathname
|
|
407
|
+
* @returns {Promise<void>}
|
|
408
|
+
*/
|
|
449
409
|
async function serveRendererFile(devServer, request, response, pathname) {
|
|
450
410
|
const answered = devServer
|
|
451
411
|
? await answeredByVite(devServer, request, response)
|
|
@@ -455,11 +415,16 @@ async function serveRendererFile(devServer, request, response, pathname) {
|
|
|
455
415
|
response.end("Not found");
|
|
456
416
|
}
|
|
457
417
|
|
|
418
|
+
/** @param {ServerResponse} response */
|
|
458
419
|
function writeNotFound(response) {
|
|
459
420
|
response.writeHead(404, { "content-type": JSON_TYPE });
|
|
460
421
|
response.end(JSON.stringify({ message: "Not found" }));
|
|
461
422
|
}
|
|
462
423
|
|
|
424
|
+
/**
|
|
425
|
+
* @param {IncomingMessage} request
|
|
426
|
+
* @param {ServerResponse} response
|
|
427
|
+
*/
|
|
463
428
|
export async function handleRequest(request, response) {
|
|
464
429
|
const url = new URL(request.url, "http://frontend.local");
|
|
465
430
|
const pathname = url.pathname;
|
|
@@ -467,6 +432,7 @@ export async function handleRequest(request, response) {
|
|
|
467
432
|
if (localRoute) return localRoute[1](request, response);
|
|
468
433
|
const devServer = await developmentServer();
|
|
469
434
|
const modules = await loadServerModules(devServer);
|
|
435
|
+
/** @param {string} path */
|
|
470
436
|
const hasFile = (path) => hasRendererFile(devServer, path);
|
|
471
437
|
const owner = requestOwner(request.method, pathname, modules, hasFile);
|
|
472
438
|
if (devServer) response.setHeader("x-dms-route-owner", ownerLabel(owner));
|
|
@@ -474,6 +440,7 @@ export async function handleRequest(request, response) {
|
|
|
474
440
|
return serveRendererFile(devServer, request, response, pathname);
|
|
475
441
|
if (owner.kind === "module")
|
|
476
442
|
return handleModuleRoute(request, response, pathname, modules);
|
|
443
|
+
/** @param {string} path */
|
|
477
444
|
const isProxiedPath = (path) => reachesBackend(path, modules, hasFile);
|
|
478
445
|
if (owner.kind === "backend")
|
|
479
446
|
return proxy(request, response, owner.proxy, isProxiedPath);
|
|
@@ -487,8 +454,9 @@ export async function handleRequest(request, response) {
|
|
|
487
454
|
if (redirectSignedOutPrivatePage(request, response, props, auth)) return;
|
|
488
455
|
const page = createInertiaPage(request.url, props, undefined, onceProps);
|
|
489
456
|
const rule = routeRuleFor(rules, pathname);
|
|
457
|
+
/** @param {number} status */
|
|
490
458
|
const headersFor = (status) =>
|
|
491
|
-
ruleHeaders(rule, request, props
|
|
459
|
+
ruleHeaders(rule, request, pageRoute(props), status);
|
|
492
460
|
if (request.headers[INERTIA_HEADER]) {
|
|
493
461
|
return writeContent(
|
|
494
462
|
request,
|
|
@@ -513,6 +481,11 @@ export async function handleRequest(request, response) {
|
|
|
513
481
|
);
|
|
514
482
|
}
|
|
515
483
|
|
|
484
|
+
/**
|
|
485
|
+
* @param {unknown} error
|
|
486
|
+
* @param {IncomingMessage & { dmsSessionRetry?: boolean }} request
|
|
487
|
+
* @param {ServerResponse} response
|
|
488
|
+
*/
|
|
516
489
|
async function writeRequestFailure(error, request, response) {
|
|
517
490
|
if (requestFailure.isResponseGone(error, response)) return;
|
|
518
491
|
if (
|
|
@@ -531,14 +504,21 @@ async function writeRequestFailure(error, request, response) {
|
|
|
531
504
|
await writeBackendError(error, request, response);
|
|
532
505
|
}
|
|
533
506
|
|
|
534
|
-
/**
|
|
507
|
+
/**
|
|
508
|
+
* An error page failing too must not reject: that would end the process.
|
|
509
|
+
*
|
|
510
|
+
* @param {IncomingMessage} request
|
|
511
|
+
* @param {ServerResponse} response
|
|
512
|
+
* @returns {Promise<void>}
|
|
513
|
+
*/
|
|
535
514
|
export async function handleRequestSafely(request, response) {
|
|
536
515
|
try {
|
|
537
516
|
if (refuseForeignTarget(request, response)) return;
|
|
538
517
|
await handleRequest(request, response);
|
|
539
518
|
} catch (error) {
|
|
540
|
-
await writeRequestFailure(error, request, response).catch(
|
|
541
|
-
|
|
519
|
+
await writeRequestFailure(error, request, response).catch(
|
|
520
|
+
/** @param {unknown} failure */ (failure) =>
|
|
521
|
+
requestFailure.abandonResponse(failure, response),
|
|
542
522
|
);
|
|
543
523
|
}
|
|
544
524
|
}
|
|
@@ -553,6 +533,7 @@ if (
|
|
|
553
533
|
);
|
|
554
534
|
const port = Number(process.env.PORT ?? 3001);
|
|
555
535
|
const host = listenHost();
|
|
536
|
+
/** @param {NodeJS.ErrnoException} error */
|
|
556
537
|
const onListenError = (error) => void reportListenError(error, host, port);
|
|
557
538
|
frontendHttpServer.once("error", onListenError);
|
|
558
539
|
frontendHttpServer.listen(port, host, () => {
|
|
@@ -587,7 +568,10 @@ async function announceReady() {
|
|
|
587
568
|
await reportStartError(error);
|
|
588
569
|
return;
|
|
589
570
|
}
|
|
590
|
-
const
|
|
571
|
+
const listening = frontendHttpServer.address();
|
|
572
|
+
if (listening === null || isString(listening))
|
|
573
|
+
throw new TypeError("The DMS server is not listening on a port");
|
|
574
|
+
const { address, port } = listening;
|
|
591
575
|
await reportReady(address, port);
|
|
592
576
|
await reportRouting();
|
|
593
577
|
if (process.env.DMS_DEV === "true") watchEmailBundle();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { isFunction, isObjectLike, isString } from "./server/type-guards.mjs";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The very unimport the auto-import plugin scans with, so both read the
|
|
@@ -12,14 +13,83 @@ const unimportUrl = pathToFileURL(
|
|
|
12
13
|
createRequire(import.meta.url).resolve("unplugin-auto-import/package.json"),
|
|
13
14
|
).resolve("unimport"),
|
|
14
15
|
);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/** @type {unknown} */
|
|
17
|
+
const unimport = await import(unimportUrl.href);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* An export of unimport, which this package does not depend on and so has no
|
|
21
|
+
* types for.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} name
|
|
24
|
+
* @returns {unknown}
|
|
25
|
+
*/
|
|
26
|
+
function unimportExport(name) {
|
|
27
|
+
return isObjectLike(unimport) ? unimport[name] : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The function unimport exports as `name`.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} name
|
|
34
|
+
* @returns {Function}
|
|
35
|
+
*/
|
|
36
|
+
function unimportFunction(name) {
|
|
37
|
+
const exported = unimportExport(name);
|
|
38
|
+
if (!isFunction(exported))
|
|
39
|
+
throw new Error(`unimport exports no function ${name}`);
|
|
40
|
+
return exported;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A name unimport scanned or resolved: where it comes from, what it is called
|
|
45
|
+
* and whether it is only a type.
|
|
46
|
+
*
|
|
47
|
+
* @typedef {object} ImportedName
|
|
48
|
+
* @property {string} name
|
|
49
|
+
* @property {string} from
|
|
50
|
+
* @property {string} [as]
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {unknown} entry
|
|
55
|
+
* @returns {entry is ImportedName}
|
|
56
|
+
*/
|
|
57
|
+
function isImportedName(entry) {
|
|
58
|
+
return (
|
|
59
|
+
isObjectLike(entry) &&
|
|
60
|
+
isString(entry.name) &&
|
|
61
|
+
isString(entry.from) &&
|
|
62
|
+
(entry.as === undefined || isString(entry.as))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The names of type declarations among `entries`, as unimport lists them.
|
|
68
|
+
*
|
|
69
|
+
* @param {unknown} entries
|
|
70
|
+
* @returns {ImportedName[]}
|
|
71
|
+
*/
|
|
72
|
+
function typeNames(entries) {
|
|
73
|
+
if (!Array.isArray(entries)) throw new Error("unimport listed no names");
|
|
74
|
+
/** @type {unknown[]} */
|
|
75
|
+
const listed = entries;
|
|
76
|
+
/** @type {ImportedName[]} */
|
|
77
|
+
const names = [];
|
|
78
|
+
for (const entry of listed) {
|
|
79
|
+
if (!isObjectLike(entry)) throw new Error("unimport listed a non-name");
|
|
80
|
+
if (!entry.type) continue;
|
|
81
|
+
if (!isImportedName(entry))
|
|
82
|
+
throw new Error("unimport listed a type without a name and a source");
|
|
83
|
+
names.push(entry);
|
|
84
|
+
}
|
|
85
|
+
return names;
|
|
86
|
+
}
|
|
18
87
|
|
|
19
88
|
const HEADER =
|
|
20
89
|
"// Generated by @antelopejs/dms-frontend from the auto-imported types, for\n" +
|
|
21
90
|
"// Vue's SFC compiler (script.globalTypeFiles). Not read by TypeScript.\n";
|
|
22
91
|
|
|
92
|
+
/** @param {string} path */
|
|
23
93
|
const withoutExtension = (path) => path.replace(/\.[cm]?[jt]sx?$/, "");
|
|
24
94
|
|
|
25
95
|
/**
|
|
@@ -45,13 +115,22 @@ const withoutExtension = (path) => path.replace(/\.[cm]?[jt]sx?$/, "");
|
|
|
45
115
|
* @param {(string | { glob: string, types?: boolean })[]} dirs the auto-import
|
|
46
116
|
* directories, as handed to the auto-import plugin
|
|
47
117
|
* @param {string} cwd the workspace
|
|
118
|
+
* @returns {Promise<string>}
|
|
48
119
|
*/
|
|
49
120
|
export async function sfcGlobalTypes(dirs, cwd) {
|
|
50
|
-
const
|
|
51
|
-
|
|
121
|
+
const presets = unimportExport("builtinPresets");
|
|
122
|
+
/** @type {unknown} */
|
|
123
|
+
const vue = await unimportFunction("resolvePreset")(
|
|
124
|
+
isObjectLike(presets) ? presets.vue : undefined,
|
|
125
|
+
);
|
|
126
|
+
/** @type {unknown} */
|
|
127
|
+
const scanned = await unimportFunction("scanDirExports")(dirs, {
|
|
128
|
+
cwd,
|
|
129
|
+
types: true,
|
|
130
|
+
});
|
|
131
|
+
/** @type {Map<string, string>} */
|
|
52
132
|
const aliases = new Map();
|
|
53
|
-
for (const entry of [...vue, ...scanned]) {
|
|
54
|
-
if (!entry.type) continue;
|
|
133
|
+
for (const entry of [...typeNames(vue), ...typeNames(scanned)]) {
|
|
55
134
|
const name = entry.as ?? entry.name;
|
|
56
135
|
const source = JSON.stringify(withoutExtension(entry.from));
|
|
57
136
|
aliases.set(
|
|
@@ -66,8 +145,13 @@ export async function sfcGlobalTypes(dirs, cwd) {
|
|
|
66
145
|
* Written through a temporary file: a build starting meanwhile reads the
|
|
67
146
|
* previous declarations or the new ones, never a half-written file the
|
|
68
147
|
* compiler would then keep in its cache.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} file
|
|
150
|
+
* @param {string} content
|
|
151
|
+
* @returns {boolean}
|
|
69
152
|
*/
|
|
70
153
|
function writeIfChanged(file, content) {
|
|
154
|
+
/** @type {string | undefined} */
|
|
71
155
|
let current;
|
|
72
156
|
try {
|
|
73
157
|
current = readFileSync(file, "utf8");
|
|
@@ -81,7 +165,14 @@ function writeIfChanged(file, content) {
|
|
|
81
165
|
return true;
|
|
82
166
|
}
|
|
83
167
|
|
|
84
|
-
/**
|
|
168
|
+
/**
|
|
169
|
+
* Writes the declarations once, before Vite's config is returned.
|
|
170
|
+
*
|
|
171
|
+
* @param {(string | { glob: string, types?: boolean })[]} dirs
|
|
172
|
+
* @param {string} cwd
|
|
173
|
+
* @param {string} file
|
|
174
|
+
* @returns {Promise<void>}
|
|
175
|
+
*/
|
|
85
176
|
export async function writeSfcGlobalTypes(dirs, cwd, file) {
|
|
86
177
|
writeIfChanged(file, await sfcGlobalTypes(dirs, cwd));
|
|
87
178
|
}
|
|
@@ -94,24 +185,32 @@ export async function writeSfcGlobalTypes(dirs, cwd, file) {
|
|
|
94
185
|
* The file is under the workspace, which Vite watches: rewriting it is the
|
|
95
186
|
* change the Vue plugin reloads the dependent components on. A failed scan or
|
|
96
187
|
* write keeps the previous declarations, since it must not end the dev server.
|
|
188
|
+
*
|
|
189
|
+
* @param {(string | { glob: string, types?: boolean })[]} dirs
|
|
190
|
+
* @param {string} cwd
|
|
191
|
+
* @param {string} file
|
|
192
|
+
* @returns {import("vite").Plugin}
|
|
97
193
|
*/
|
|
98
194
|
export function sfcGlobalTypesWatcher(dirs, cwd, file) {
|
|
99
195
|
return {
|
|
100
196
|
name: "dms-sfc-global-types",
|
|
101
197
|
apply: "serve",
|
|
102
198
|
configureServer(server) {
|
|
199
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
103
200
|
let pending;
|
|
201
|
+
/** @param {string} path */
|
|
104
202
|
const refresh = (path) => {
|
|
105
203
|
if (!/\.[cm]?[jt]sx?$/.test(path) || path === file) return;
|
|
106
204
|
clearTimeout(pending);
|
|
107
205
|
pending = setTimeout(() => {
|
|
108
206
|
sfcGlobalTypes(dirs, cwd)
|
|
109
207
|
.then((content) => writeIfChanged(file, content))
|
|
110
|
-
.catch(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
`
|
|
114
|
-
|
|
208
|
+
.catch(
|
|
209
|
+
/** @param {unknown} error */ (error) =>
|
|
210
|
+
server.config.logger.warn(
|
|
211
|
+
`Could not update ${file}, the types Vue's SFC compiler ` +
|
|
212
|
+
`reads: ${error instanceof Error ? error.message : String(error)}`,
|
|
213
|
+
),
|
|
115
214
|
);
|
|
116
215
|
}, 100);
|
|
117
216
|
};
|