@antelopejs/dms-frontend 0.10.0 → 0.10.2
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 +34 -11
- package/dist/commands/build-action.d.ts +3 -0
- package/dist/commands/build-action.js +74 -6
- package/dist/commands/build.js +7 -0
- package/dist/commands/clean-action.js +1 -1
- package/dist/commands/clean.js +1 -1
- package/dist/commands/dev-action.js +2 -2
- package/dist/commands/dev.js +1 -1
- package/dist/config.js +1 -0
- package/dist/derived-outputs.d.ts +1 -0
- package/dist/derived-outputs.js +3 -1
- package/dist/help.js +1 -1
- package/dist/internal/prepared-workspace.d.ts +12 -0
- package/dist/internal/prepared-workspace.js +74 -0
- package/dist/layer-watch.js +1 -1
- package/dist/locale-files.d.ts +4 -2
- package/dist/locale-files.js +11 -9
- package/dist/manifest.js +2 -1
- package/dist/ports.d.ts +3 -1
- package/dist/ports.js +1 -1
- package/dist/project-modules.d.ts +6 -1
- package/dist/project-modules.js +26 -16
- package/dist/verify-source-runner.js +2 -44
- package/dist/verify-source-workspace.d.ts +2 -0
- package/dist/verify-source-workspace.js +111 -0
- package/dist/workspace-setup.d.ts +1 -0
- package/dist/workspace-setup.js +3 -2
- package/dist/workspace.js +1 -1
- package/package.json +20 -7
- package/templates/vue/backend-boundary.mjs +132 -0
- package/templates/vue/client-chunking.mjs +2 -0
- package/templates/vue/entry-weight.mjs +9 -5
- package/templates/vue/frontend-build.ts +4 -3
- package/templates/vue/head-order.mjs +3 -2
- package/templates/vue/icon-api.mjs +2 -0
- package/templates/vue/server/auth/backend.mjs +4 -4
- package/templates/vue/server/auth/establish-endpoints.mjs +2 -2
- package/templates/vue/server/auth/session.mjs +5 -2
- package/templates/vue/server/auth-declarations.mjs +46 -0
- package/templates/vue/server/auth-hooks.mjs +41 -3
- package/templates/vue/server/backend-proxies.mjs +16 -12
- package/templates/vue/server/backend-response.mjs +3 -3
- package/templates/vue/server/cli-report.mjs +22 -1
- package/templates/vue/server/client-error.mjs +5 -0
- package/templates/vue/server/content.mjs +24 -11
- package/templates/vue/server/email-bundle-watch.mjs +9 -3
- package/templates/vue/server/email.mjs +5 -3
- package/templates/vue/server/generated-files.mjs +16 -6
- package/templates/vue/server/icons.mjs +15 -11
- package/templates/vue/server/in-place-render.mjs +3 -3
- package/templates/vue/server/inertia.mjs +17 -3
- package/templates/vue/server/module-event.mjs +28 -16
- package/templates/vue/server/module-route-types.mjs +2 -2
- package/templates/vue/server/module-routes.mjs +24 -76
- package/templates/vue/server/module-server.mjs +27 -17
- package/templates/vue/server/module-testing.mjs +23 -8
- package/templates/vue/server/page-props.mjs +6 -5
- package/templates/vue/server/page-redirects.mjs +1 -1
- package/templates/vue/server/path-patterns.mjs +22 -11
- package/templates/vue/server/proxy-headers.mjs +1 -1
- package/templates/vue/server/proxy-redirects.mjs +1 -0
- package/templates/vue/server/proxy-request.mjs +2 -1
- package/templates/vue/server/proxy-transfers.mjs +17 -11
- package/templates/vue/server/public-endpoints.mjs +12 -16
- package/templates/vue/server/request-failure.mjs +1 -1
- package/templates/vue/server/request-routing.mjs +6 -1
- package/templates/vue/server/request-target.mjs +22 -9
- package/templates/vue/server/route-rules.mjs +12 -6
- package/templates/vue/server/static-files.mjs +12 -7
- package/templates/vue/server/vite-errors.mjs +13 -2
- package/templates/vue/server/web-body.mjs +2 -1
- package/templates/vue/server-module.ts +2 -1
- package/templates/vue/server.mjs +41 -20
- package/templates/vue/ui-inertia-page.mjs +1 -1
- package/templates/vue/vite.config.ts +9 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { writeBadRequest } from "./request-failure.mjs";
|
|
2
1
|
import { isString } from "./type-guards.mjs";
|
|
3
2
|
|
|
4
3
|
const FRONTEND_ORIGIN = "http://frontend.local";
|
|
@@ -30,7 +29,7 @@ export function isOriginForm(target) {
|
|
|
30
29
|
*/
|
|
31
30
|
export function backendUrl(
|
|
32
31
|
target,
|
|
33
|
-
backendBaseUrl = process.env.DMS_API_BASE_URL,
|
|
32
|
+
backendBaseUrl = process.env.DMS_API_BASE_URL ?? "",
|
|
34
33
|
) {
|
|
35
34
|
const { pathname, search } = new URL(target, FRONTEND_ORIGIN);
|
|
36
35
|
const url = new URL(new URL(backendBaseUrl).origin);
|
|
@@ -40,8 +39,24 @@ export function backendUrl(
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* A request a server received, whose `url` and `method` Node always sets
|
|
43
|
+
* (`isServerRequest`).
|
|
44
|
+
*
|
|
45
|
+
* @typedef {import("node:http").IncomingMessage & { url: string, method: string }} ServerRequest
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @template {import("node:http").IncomingMessage} Request
|
|
50
|
+
* @param {Request} request
|
|
51
|
+
* @returns {request is Request & ServerRequest}
|
|
52
|
+
*/
|
|
53
|
+
export function isServerRequest(request) {
|
|
54
|
+
return isString(request.url) && isString(request.method);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether the request target is origin-form, the only kind the server routes:
|
|
59
|
+
* a request that fails it is answered 400 before any route reads it.
|
|
45
60
|
*
|
|
46
61
|
* The request target as the server reads it. Node hands `request.url` over
|
|
47
62
|
* verbatim, so it is not always a path: `//host/x`, `/\host/x` and the absolute
|
|
@@ -50,10 +65,8 @@ export function backendUrl(
|
|
|
50
65
|
* backend read the same normalized path from here.
|
|
51
66
|
*
|
|
52
67
|
* @param {import("node:http").IncomingMessage} request
|
|
53
|
-
* @
|
|
68
|
+
* @returns {request is ServerRequest}
|
|
54
69
|
*/
|
|
55
|
-
export function
|
|
56
|
-
|
|
57
|
-
writeBadRequest(response);
|
|
58
|
-
return true;
|
|
70
|
+
export function hasOriginFormTarget(request) {
|
|
71
|
+
return isOriginForm(request.url) && isString(request.method);
|
|
59
72
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { compilePathPattern } from "./path-patterns.mjs";
|
|
2
|
-
import {
|
|
1
|
+
import { compilePathPattern, PATH_PATTERN_FORMS } from "./path-patterns.mjs";
|
|
2
|
+
import {
|
|
3
|
+
isNonNullObject,
|
|
4
|
+
isNumber,
|
|
5
|
+
isObjectLike,
|
|
6
|
+
isRecord,
|
|
7
|
+
isString,
|
|
8
|
+
} from "./type-guards.mjs";
|
|
3
9
|
|
|
4
10
|
/** @typedef {import("../server-module.ts").DmsRouteRule} RouteRule */
|
|
5
11
|
/** @typedef {RouteRule & { headers: Record<string, string> }} MergedRouteRule */
|
|
@@ -37,7 +43,7 @@ const RESERVED_HEADERS = new Set([
|
|
|
37
43
|
|
|
38
44
|
/** @param {unknown} value */
|
|
39
45
|
function isSeconds(value) {
|
|
40
|
-
return Number.isSafeInteger(value) && value >= 0;
|
|
46
|
+
return isNumber(value) && Number.isSafeInteger(value) && value >= 0;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/** @param {unknown} headers */
|
|
@@ -63,7 +69,7 @@ function headersRejection(headers) {
|
|
|
63
69
|
function cacheRejection(cache) {
|
|
64
70
|
if (cache === undefined || cache === false) return undefined;
|
|
65
71
|
if (
|
|
66
|
-
!
|
|
72
|
+
!isRecord(cache) ||
|
|
67
73
|
cache.public !== true ||
|
|
68
74
|
!isSeconds(cache.maxAge) ||
|
|
69
75
|
(cache.staleWhileRevalidate !== undefined &&
|
|
@@ -85,8 +91,8 @@ function cacheRejection(cache) {
|
|
|
85
91
|
*/
|
|
86
92
|
export function ruleRejection(pattern, rule) {
|
|
87
93
|
if (!compilePathPattern(pattern))
|
|
88
|
-
return
|
|
89
|
-
if (!
|
|
94
|
+
return `the pattern is not ${PATH_PATTERN_FORMS}`;
|
|
95
|
+
if (!isRecord(rule)) return "it is not an object";
|
|
90
96
|
if (rule.ssr !== undefined && rule.ssr !== false)
|
|
91
97
|
return "ssr is either left out or false";
|
|
92
98
|
return headersRejection(rule.headers) ?? cacheRejection(rule.cache);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
2
2
|
import { extname } from "node:path";
|
|
3
|
+
import { acceptedCodings } from "./content.mjs";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* What a build or a module's `public/` directory may hold. A type missing
|
|
@@ -48,22 +49,26 @@ const IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
|
|
|
48
49
|
*/
|
|
49
50
|
const REVALIDATED_CACHE = "no-cache";
|
|
50
51
|
|
|
52
|
+
/** The suffix the build gives the copy of a file in each coding. */
|
|
53
|
+
const VARIANT_SUFFIXES = { br: ".br", gzip: ".gz" };
|
|
54
|
+
|
|
51
55
|
/** @param {string} path */
|
|
52
56
|
const isFile = (path) => existsSync(path) && statSync(path).isFile();
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
|
-
* The precompressed variant of `file` the client
|
|
59
|
+
* The precompressed variant of `file` the client wants most among those the
|
|
60
|
+
* build wrote, or `file` itself.
|
|
56
61
|
*
|
|
57
62
|
* @param {string} file
|
|
58
63
|
* @param {import("node:http").IncomingMessage} request
|
|
59
64
|
*/
|
|
60
65
|
function acceptedVariant(file, request) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
const encoding = acceptedCodings(request).find((coding) =>
|
|
67
|
+
isFile(`${file}${VARIANT_SUFFIXES[coding]}`),
|
|
68
|
+
);
|
|
69
|
+
return encoding
|
|
70
|
+
? { file: `${file}${VARIANT_SUFFIXES[encoding]}`, encoding }
|
|
71
|
+
: { file, encoding: undefined };
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
/**
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isObjectLike, isString } from "./type-guards.mjs";
|
|
2
|
+
|
|
1
3
|
const INTERNAL_ERROR_STATUS = 500;
|
|
2
4
|
const ERROR_CONTENT_TYPE = "text/plain; charset=utf-8";
|
|
3
5
|
const VITE_ERROR = Symbol("dms.viteError");
|
|
@@ -69,9 +71,18 @@ export function runViteMiddlewares(devServer, request, response) {
|
|
|
69
71
|
});
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Answers 500 with Vite's error message instead of rendering a page.
|
|
76
|
+
*
|
|
77
|
+
* @param {import("node:http").ServerResponse} response
|
|
78
|
+
* @param {unknown} error
|
|
79
|
+
*/
|
|
73
80
|
export function writeViteError(response, error) {
|
|
74
81
|
response.statusCode = INTERNAL_ERROR_STATUS;
|
|
75
82
|
response.setHeader("content-type", ERROR_CONTENT_TYPE);
|
|
76
|
-
response.end(
|
|
83
|
+
response.end(
|
|
84
|
+
isObjectLike(error) && isString(error.message)
|
|
85
|
+
? error.message
|
|
86
|
+
: String(error),
|
|
87
|
+
);
|
|
77
88
|
}
|
|
@@ -17,10 +17,11 @@ import { pipeline } from "node:stream/promises";
|
|
|
17
17
|
*/
|
|
18
18
|
export async function pipeWebBody(body, ...streams) {
|
|
19
19
|
const response = streams.at(-1);
|
|
20
|
+
if (!response) throw new TypeError("pipeWebBody needs a response to write");
|
|
20
21
|
if (response.closed || response.destroyed) {
|
|
21
22
|
for (const stream of streams.slice(0, -1)) stream.destroy();
|
|
22
23
|
await body.cancel().catch(() => {});
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
|
-
await pipeline(Readable.fromWeb(body), ...streams);
|
|
26
|
+
await pipeline([Readable.fromWeb(body), ...streams]);
|
|
26
27
|
}
|
|
@@ -186,7 +186,8 @@ export interface DmsServerRouteOptions {
|
|
|
186
186
|
* The session logic the frontend server runs outside any route, which the
|
|
187
187
|
* module owning sign-in provides. Without `publicSession`, pages carry no
|
|
188
188
|
* `user` and `session`; without `refreshSession`, a session the backend
|
|
189
|
-
* refuses is not renewed; `signInPath` is `/auth` by default.
|
|
189
|
+
* refuses is not renewed; `signInPath` is `/auth` by default. The server
|
|
190
|
+
* calls each hook with the registered object as `this`.
|
|
190
191
|
*/
|
|
191
192
|
export interface DmsServerAuth {
|
|
192
193
|
/** Where a signed-out visit to a private page goes, with `?redirect=`. */
|
package/templates/vue/server.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { handleClientError } from "./server/client-error.mjs";
|
|
|
17
17
|
import {
|
|
18
18
|
reportListenError,
|
|
19
19
|
reportReady,
|
|
20
|
+
reportRouting,
|
|
20
21
|
reportStartError,
|
|
21
22
|
} from "./server/cli-report.mjs";
|
|
22
23
|
import { publicBuildFile } from "./server/built-files.mjs";
|
|
@@ -64,7 +65,10 @@ import * as icons from "./server/icons.mjs";
|
|
|
64
65
|
import { ssrRenderer } from "./server/ssr-module.mjs";
|
|
65
66
|
import * as requestFailure from "./server/request-failure.mjs";
|
|
66
67
|
import { proxy } from "./server/proxy-request.mjs";
|
|
67
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
hasOriginFormTarget,
|
|
70
|
+
isServerRequest,
|
|
71
|
+
} from "./server/request-target.mjs";
|
|
68
72
|
import { SERVER_OPTIONS } from "./server/proxy-transfers.mjs";
|
|
69
73
|
import {
|
|
70
74
|
createInertiaPage,
|
|
@@ -74,10 +78,12 @@ import {
|
|
|
74
78
|
isFrontendVisit,
|
|
75
79
|
redirectFrontendVisit,
|
|
76
80
|
handleAssetVersionMismatch,
|
|
81
|
+
isSsrPage,
|
|
77
82
|
} from "./server/inertia.mjs";
|
|
78
83
|
export * from "./server/inertia.mjs";
|
|
79
84
|
|
|
80
85
|
/** @typedef {import("node:http").IncomingMessage} IncomingMessage */
|
|
86
|
+
/** @typedef {import("./server/request-target.mjs").ServerRequest} ServerRequest */
|
|
81
87
|
/** @typedef {import("node:http").ServerResponse} ServerResponse */
|
|
82
88
|
/** @typedef {import("vite").ViteDevServer} ViteDevServer */
|
|
83
89
|
/** @typedef {import("./server/inertia.mjs").InertiaPage} InertiaPage */
|
|
@@ -86,6 +92,12 @@ export * from "./server/inertia.mjs";
|
|
|
86
92
|
const INERTIA_HEADER = "x-inertia";
|
|
87
93
|
const JSON_TYPE = "application/json";
|
|
88
94
|
const PROJECT_ROOT = fileURLToPath(new URL(".", import.meta.url));
|
|
95
|
+
/**
|
|
96
|
+
* @type {[
|
|
97
|
+
* (pathname: string) => boolean,
|
|
98
|
+
* (request: ServerRequest, response: ServerResponse) => Promise<void>
|
|
99
|
+
* ][]}
|
|
100
|
+
*/
|
|
89
101
|
const LOCAL_ROUTES = [[icons.isIconRequest, icons.handleIcons]];
|
|
90
102
|
const CLIENT_ROOT = join(PROJECT_ROOT, "dist/client");
|
|
91
103
|
const SOURCE_TEMPLATE_PATH = join(PROJECT_ROOT, "index.html");
|
|
@@ -97,7 +109,9 @@ const SOURCE_TEMPLATE_PATH = join(PROJECT_ROOT, "index.html");
|
|
|
97
109
|
* NODE_ENV=production throws a ReferenceError on the first render.
|
|
98
110
|
*/
|
|
99
111
|
function defineVueCompileFlags() {
|
|
100
|
-
globalThis
|
|
112
|
+
/** @type {typeof globalThis & { __VUE_PROD_DEVTOOLS__?: boolean }} */
|
|
113
|
+
const vueGlobals = globalThis;
|
|
114
|
+
vueGlobals.__VUE_PROD_DEVTOOLS__ ??= false;
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
defineVueCompileFlags();
|
|
@@ -145,7 +159,7 @@ async function currentAuth() {
|
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
/**
|
|
148
|
-
* @param {
|
|
162
|
+
* @param {ServerRequest} request
|
|
149
163
|
* @returns {import("./server/page-props.mjs").PageBackend}
|
|
150
164
|
*/
|
|
151
165
|
function pageBackend(request) {
|
|
@@ -159,7 +173,7 @@ function pageBackend(request) {
|
|
|
159
173
|
}
|
|
160
174
|
|
|
161
175
|
/**
|
|
162
|
-
* @param {
|
|
176
|
+
* @param {ServerRequest} request
|
|
163
177
|
* @param {import("./server/page-props.mjs").PageAuth} auth
|
|
164
178
|
*/
|
|
165
179
|
function pageProps(request, auth) {
|
|
@@ -207,6 +221,8 @@ function clientRenderedShell(page) {
|
|
|
207
221
|
async function serverRendered(page, serverFetch, requestCookies, devServer) {
|
|
208
222
|
try {
|
|
209
223
|
const renderer = await ssrRenderer(devServer);
|
|
224
|
+
if (!isSsrPage(page))
|
|
225
|
+
throw new TypeError("The page has no path and no page props to render");
|
|
210
226
|
return await renderer.renderDmsPage(page, serverFetch, requestCookies);
|
|
211
227
|
} catch (error) {
|
|
212
228
|
console.error(
|
|
@@ -222,7 +238,7 @@ async function serverRendered(page, serverFetch, requestCookies, devServer) {
|
|
|
222
238
|
* renders the page from the shell instead.
|
|
223
239
|
*
|
|
224
240
|
* @param {InertiaPage} page
|
|
225
|
-
* @param {string
|
|
241
|
+
* @param {string} requestUrl
|
|
226
242
|
* @param {ServerReads} serverFetch
|
|
227
243
|
* @param {string | undefined} requestCookies
|
|
228
244
|
* @param {{ ssr?: false }} [options]
|
|
@@ -271,7 +287,7 @@ export async function renderHtml(
|
|
|
271
287
|
|
|
272
288
|
/**
|
|
273
289
|
* @param {InertiaPage} page
|
|
274
|
-
* @param {
|
|
290
|
+
* @param {ServerRequest} request
|
|
275
291
|
* @param {Pick<import("./server/route-rules.mjs").RouteRule, "ssr">} [rule]
|
|
276
292
|
* @param {ServerReads} [reads]
|
|
277
293
|
*/
|
|
@@ -287,7 +303,7 @@ function renderRequestHtml(page, request, rule = {}, reads) {
|
|
|
287
303
|
* see `renderInPlace`.
|
|
288
304
|
*
|
|
289
305
|
* @param {InertiaPage} page
|
|
290
|
-
* @param {
|
|
306
|
+
* @param {ServerRequest} request
|
|
291
307
|
* @param {import("./server/route-rules.mjs").MergedRouteRule} rule
|
|
292
308
|
* @param {import("./server/page-props.mjs").PageAuth} auth
|
|
293
309
|
*/
|
|
@@ -304,7 +320,7 @@ function renderDocument(page, request, rule, auth) {
|
|
|
304
320
|
|
|
305
321
|
/**
|
|
306
322
|
* @param {unknown} error
|
|
307
|
-
* @param {
|
|
323
|
+
* @param {ServerRequest} request
|
|
308
324
|
* @param {ServerResponse} response
|
|
309
325
|
*/
|
|
310
326
|
async function writeBackendError(error, request, response) {
|
|
@@ -422,7 +438,7 @@ function writeNotFound(response) {
|
|
|
422
438
|
}
|
|
423
439
|
|
|
424
440
|
/**
|
|
425
|
-
* @param {
|
|
441
|
+
* @param {ServerRequest} request
|
|
426
442
|
* @param {ServerResponse} response
|
|
427
443
|
*/
|
|
428
444
|
export async function handleRequest(request, response) {
|
|
@@ -488,6 +504,8 @@ export async function handleRequest(request, response) {
|
|
|
488
504
|
*/
|
|
489
505
|
async function writeRequestFailure(error, request, response) {
|
|
490
506
|
if (requestFailure.isResponseGone(error, response)) return;
|
|
507
|
+
if (!isServerRequest(request))
|
|
508
|
+
return requestFailure.abandonResponse(error, response);
|
|
491
509
|
if (
|
|
492
510
|
error instanceof BackendResponseError &&
|
|
493
511
|
error.status === 401 &&
|
|
@@ -513,7 +531,10 @@ async function writeRequestFailure(error, request, response) {
|
|
|
513
531
|
*/
|
|
514
532
|
export async function handleRequestSafely(request, response) {
|
|
515
533
|
try {
|
|
516
|
-
if (
|
|
534
|
+
if (!hasOriginFormTarget(request)) {
|
|
535
|
+
requestFailure.writeBadRequest(response);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
517
538
|
await handleRequest(request, response);
|
|
518
539
|
} catch (error) {
|
|
519
540
|
await writeRequestFailure(error, request, response).catch(
|
|
@@ -528,26 +549,26 @@ if (
|
|
|
528
549
|
import.meta.url === new URL(`file://${process.argv[1]}`).href
|
|
529
550
|
) {
|
|
530
551
|
frontendHttpServer = createServer(SERVER_OPTIONS, handleRequestSafely);
|
|
531
|
-
|
|
552
|
+
const server = frontendHttpServer;
|
|
553
|
+
server.on("clientError", (error, socket) =>
|
|
532
554
|
handleClientError(error, socket, maxHeaderSize),
|
|
533
555
|
);
|
|
534
556
|
const port = Number(process.env.PORT ?? 3001);
|
|
535
557
|
const host = listenHost();
|
|
536
558
|
/** @param {NodeJS.ErrnoException} error */
|
|
537
559
|
const onListenError = (error) => void reportListenError(error, host, port);
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
560
|
+
server.once("error", onListenError);
|
|
561
|
+
server.listen(port, host, () => {
|
|
562
|
+
server.off("error", onListenError);
|
|
541
563
|
void announceReady();
|
|
542
564
|
});
|
|
543
565
|
}
|
|
544
566
|
|
|
545
567
|
/** Prints who owns which paths, once the server is up. */
|
|
546
|
-
async function
|
|
568
|
+
async function reportServerModules() {
|
|
547
569
|
try {
|
|
548
570
|
const modules = await loadServerModules(await developmentServer());
|
|
549
|
-
|
|
550
|
-
console.log(line);
|
|
571
|
+
reportRouting(routingReport(modules.proxies, modules.routes.length));
|
|
551
572
|
} catch (error) {
|
|
552
573
|
console.error("DMS server modules failed to load", error);
|
|
553
574
|
}
|
|
@@ -568,11 +589,11 @@ async function announceReady() {
|
|
|
568
589
|
await reportStartError(error);
|
|
569
590
|
return;
|
|
570
591
|
}
|
|
571
|
-
const listening = frontendHttpServer
|
|
572
|
-
if (listening
|
|
592
|
+
const listening = frontendHttpServer?.address();
|
|
593
|
+
if (!listening || isString(listening))
|
|
573
594
|
throw new TypeError("The DMS server is not listening on a port");
|
|
574
595
|
const { address, port } = listening;
|
|
575
596
|
await reportReady(address, port);
|
|
576
|
-
await
|
|
597
|
+
await reportServerModules();
|
|
577
598
|
if (process.env.DMS_DEV === "true") watchEmailBundle();
|
|
578
599
|
}
|
|
@@ -13,7 +13,7 @@ const ANY_INERTIA_PAGE_IMPORT =
|
|
|
13
13
|
* name: string,
|
|
14
14
|
* enforce: "pre",
|
|
15
15
|
* transform: (
|
|
16
|
-
* this: import("vite").Rollup.TransformPluginContext,
|
|
16
|
+
* this: Pick<import("vite").Rollup.TransformPluginContext, "error">,
|
|
17
17
|
* code: string,
|
|
18
18
|
* id: string,
|
|
19
19
|
* ) => { code: string, map: null } | null,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "vite";
|
|
12
12
|
import { clientCodeSplitting, onBuildLog } from "./client-chunking.mjs";
|
|
13
13
|
import { recordEntryWeight } from "./entry-weight.mjs";
|
|
14
|
+
import { rejectBackendSources } from "./backend-boundary.mjs";
|
|
14
15
|
import { rejectServerSourcesInClient } from "./client-server-guard.mjs";
|
|
15
16
|
import { uiInertiaPage } from "./ui-inertia-page.mjs";
|
|
16
17
|
import { uiKbd } from "./ui-kbd.mjs";
|
|
@@ -29,6 +30,7 @@ interface FrontendModuleRegistryEntry {
|
|
|
29
30
|
id: string;
|
|
30
31
|
name?: string;
|
|
31
32
|
packageName?: string;
|
|
33
|
+
backendPackage?: string;
|
|
32
34
|
root: string;
|
|
33
35
|
priority: number;
|
|
34
36
|
}
|
|
@@ -45,6 +47,9 @@ const registry = JSON.parse(
|
|
|
45
47
|
readFileSync(registryPath, "utf8"),
|
|
46
48
|
) as FrontendModuleRegistry;
|
|
47
49
|
const moduleRoots = registry.modules.map((module) => module.root);
|
|
50
|
+
const backendPackages = registry.modules.flatMap((module) =>
|
|
51
|
+
module.backendPackage === undefined ? [] : [module.backendPackage],
|
|
52
|
+
);
|
|
48
53
|
const frontendSourceRoots = moduleRoots.flatMap((root) => {
|
|
49
54
|
const layersRoot = resolve(root, "layers");
|
|
50
55
|
if (!existsSync(layersRoot)) return [root];
|
|
@@ -175,6 +180,9 @@ export default defineConfig({
|
|
|
175
180
|
// Fails a client build that imports a dms.server.ts or a file under a
|
|
176
181
|
// module's server/ directory, however it is imported.
|
|
177
182
|
rejectServerSourcesInClient(moduleRoots),
|
|
183
|
+
// Fails a build, a worker build or a dev request that resolves a file
|
|
184
|
+
// under the src of the package that ships a module.
|
|
185
|
+
rejectBackendSources(backendPackages),
|
|
178
186
|
// What each module adds to the client entry, which `ajs dms build` reports.
|
|
179
187
|
recordEntryWeight(registry.modules),
|
|
180
188
|
{
|
|
@@ -267,6 +275,7 @@ export default defineConfig({
|
|
|
267
275
|
paintBeforeHydrate(),
|
|
268
276
|
...vitePlugins(registry.modules, frontendBuilds),
|
|
269
277
|
],
|
|
278
|
+
worker: { plugins: () => vitePlugins(registry.modules, frontendBuilds) },
|
|
270
279
|
resolve: {
|
|
271
280
|
// `@iconify/vue` keeps its API providers in module state: `main.ts` and
|
|
272
281
|
// Nuxt UI's `Icon` must share one copy for the same-origin provider to apply.
|