@antelopejs/dms-frontend 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fs-sync.js +3 -5
- package/dist/manifest.d.ts +3 -0
- package/dist/manifest.js +12 -3
- package/dist/ports.js +1 -1
- package/dist/renderer-range.js +6 -1
- package/dist/verify-source-runner.js +7 -1
- package/package.json +1 -1
- package/templates/vue/frontend-module.ts +0 -11
- package/templates/vue/server/client-error.mjs +41 -0
- package/templates/vue/server/vite-errors.mjs +50 -0
- package/templates/vue/server.mjs +22 -7
package/dist/fs-sync.js
CHANGED
|
@@ -151,8 +151,7 @@ function collectFiles(dir, baseDir = dir) {
|
|
|
151
151
|
const entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
152
152
|
for (const entry of entries) {
|
|
153
153
|
const fullPath = (0, node_path_1.join)(dir, entry.name);
|
|
154
|
-
const
|
|
155
|
-
const relPath = relative(baseDir, fullPath);
|
|
154
|
+
const relPath = (0, node_path_1.relative)(baseDir, fullPath);
|
|
156
155
|
if (entry.isDirectory()) {
|
|
157
156
|
files.push(...collectFiles(fullPath, baseDir));
|
|
158
157
|
}
|
|
@@ -179,12 +178,11 @@ function syncDirectories(src, dest) {
|
|
|
179
178
|
cleanEmptyDirs(dest);
|
|
180
179
|
}
|
|
181
180
|
function cleanEmptyDirs(dir) {
|
|
182
|
-
|
|
183
|
-
if (!(0, node_fs_1.existsSync)(dir) || !statSync(dir).isDirectory())
|
|
181
|
+
if (!(0, node_fs_1.existsSync)(dir) || !(0, node_fs_1.statSync)(dir).isDirectory())
|
|
184
182
|
return;
|
|
185
183
|
for (const entry of (0, node_fs_1.readdirSync)(dir)) {
|
|
186
184
|
const full = (0, node_path_1.join)(dir, entry);
|
|
187
|
-
if (statSync(full).isDirectory())
|
|
185
|
+
if ((0, node_fs_1.statSync)(full).isDirectory())
|
|
188
186
|
cleanEmptyDirs(full);
|
|
189
187
|
}
|
|
190
188
|
if ((0, node_fs_1.readdirSync)(dir).length === 0) {
|
package/dist/manifest.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ export declare class ManifestUnauthorizedError extends Error {
|
|
|
4
4
|
readonly status: number;
|
|
5
5
|
constructor(url: string, status: number);
|
|
6
6
|
}
|
|
7
|
+
export declare class ManifestRefusedError extends Error {
|
|
8
|
+
constructor(reason: string);
|
|
9
|
+
}
|
|
7
10
|
export declare function fetchManifest(backendUrl: string, clientUrl?: string, bootstrapSecret?: string): Promise<Manifest>;
|
|
8
11
|
export interface ManifestCacheEntry {
|
|
9
12
|
manifest: Manifest;
|
package/dist/manifest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ManifestUnauthorizedError = void 0;
|
|
3
|
+
exports.ManifestRefusedError = exports.ManifestUnauthorizedError = void 0;
|
|
4
4
|
exports.fetchManifest = fetchManifest;
|
|
5
5
|
exports.readCachedManifest = readCachedManifest;
|
|
6
6
|
exports.resolveManifest = resolveManifest;
|
|
@@ -22,6 +22,13 @@ class ManifestUnauthorizedError extends Error {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
exports.ManifestUnauthorizedError = ManifestUnauthorizedError;
|
|
25
|
+
class ManifestRefusedError extends Error {
|
|
26
|
+
constructor(reason) {
|
|
27
|
+
super(reason);
|
|
28
|
+
this.name = "ManifestRefusedError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.ManifestRefusedError = ManifestRefusedError;
|
|
25
32
|
async function fetchManifest(backendUrl, clientUrl, bootstrapSecret) {
|
|
26
33
|
const frontendUrl = new URL(`${backendUrl}/dms/frontend`);
|
|
27
34
|
frontendUrl.searchParams.set("renderer", "vue");
|
|
@@ -40,11 +47,11 @@ async function fetchManifest(backendUrl, clientUrl, bootstrapSecret) {
|
|
|
40
47
|
}
|
|
41
48
|
const manifest = (await response.json());
|
|
42
49
|
if (manifest.version !== workspace_1.FRONTEND_MANIFEST_VERSION) {
|
|
43
|
-
throw new
|
|
50
|
+
throw new ManifestRefusedError(`Unsupported frontend manifest version: ${String(manifest.version)}`);
|
|
44
51
|
}
|
|
45
52
|
const incompatible = manifest.modules.filter((module) => module.renderer?.name !== "vue" || module.renderer?.version !== "3");
|
|
46
53
|
if (incompatible.length) {
|
|
47
|
-
throw new
|
|
54
|
+
throw new ManifestRefusedError(`Frontend manifest contains incompatible renderer modules: ${incompatible.map((module) => module.name).join(", ")}`);
|
|
48
55
|
}
|
|
49
56
|
return { pack: manifest.archive, modules: manifest.modules };
|
|
50
57
|
}
|
|
@@ -80,6 +87,8 @@ async function resolveManifest(workspaceDir, backendUrl, offline, clientUrl, boo
|
|
|
80
87
|
catch (err) {
|
|
81
88
|
if (err instanceof ManifestUnauthorizedError)
|
|
82
89
|
throw err;
|
|
90
|
+
if (err instanceof ManifestRefusedError)
|
|
91
|
+
throw err;
|
|
83
92
|
const cached = readCachedManifest(workspaceDir);
|
|
84
93
|
if (!cached)
|
|
85
94
|
throw err;
|
package/dist/ports.js
CHANGED
|
@@ -19,7 +19,7 @@ async function reserveFreePort(preferred, range = exports.PORT_FALLBACK_RANGE) {
|
|
|
19
19
|
const server = await tryListen(port);
|
|
20
20
|
if (server) {
|
|
21
21
|
return {
|
|
22
|
-
port,
|
|
22
|
+
port: server.address().port,
|
|
23
23
|
release: () => new Promise((resolve) => {
|
|
24
24
|
server.close(() => resolve());
|
|
25
25
|
}),
|
package/dist/renderer-range.js
CHANGED
|
@@ -8,9 +8,14 @@ const semver_1 = __importDefault(require("semver"));
|
|
|
8
8
|
const layers_1 = require("./layers");
|
|
9
9
|
const OWN_RELEASE = require("../package.json");
|
|
10
10
|
const noticedModules = new Set();
|
|
11
|
+
function moduleLabel(layer) {
|
|
12
|
+
if (!layer.packageName)
|
|
13
|
+
return layer.path;
|
|
14
|
+
return `${layer.packageName} (${layer.path})`;
|
|
15
|
+
}
|
|
11
16
|
function declaredRange(layer, rendererName) {
|
|
12
17
|
return {
|
|
13
|
-
module: layer
|
|
18
|
+
module: moduleLabel(layer),
|
|
14
19
|
range: (0, layers_1.readLayerPackage)(layer.path)?.engines?.[rendererName],
|
|
15
20
|
};
|
|
16
21
|
}
|
|
@@ -74,7 +74,13 @@ const layers = roots.map((root) => ({
|
|
|
74
74
|
packageName: JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(root, "package.json"), "utf8"))
|
|
75
75
|
.name,
|
|
76
76
|
}));
|
|
77
|
-
|
|
77
|
+
try {
|
|
78
|
+
(0, common_1.assertLayersSupportRenderer)(layers);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.error(`Error: ${error.message}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
78
84
|
(0, common_1.materializeLayers)(workspace, layers);
|
|
79
85
|
const registry = (0, common_1.createFrontendModuleRegistry)(workspace, layers);
|
|
80
86
|
registry.modules.forEach((module) => {
|
package/package.json
CHANGED
|
@@ -906,9 +906,6 @@ export function useDmsRuntimeConfig(): DmsRuntimeConfig {
|
|
|
906
906
|
export function useDmsAppConfig(): DmsAppConfig {
|
|
907
907
|
return appConfig.value as DmsAppConfig;
|
|
908
908
|
}
|
|
909
|
-
export function configureDmsRuntime(config: DmsRuntimeConfig): void {
|
|
910
|
-
runtimeConfig.value = config;
|
|
911
|
-
}
|
|
912
909
|
export function defineAppConfig<T extends Record<string, unknown>>(
|
|
913
910
|
config: T,
|
|
914
911
|
): T {
|
|
@@ -934,14 +931,6 @@ export function defineDmsPageMeta(meta: Record<string, unknown>): void {
|
|
|
934
931
|
export function abortNavigation(): false {
|
|
935
932
|
return false;
|
|
936
933
|
}
|
|
937
|
-
export function addDmsMiddleware(
|
|
938
|
-
handler: DmsMiddleware,
|
|
939
|
-
name?: string,
|
|
940
|
-
isGlobal = false,
|
|
941
|
-
): void {
|
|
942
|
-
if (isGlobal) registry.middleware.push(handler);
|
|
943
|
-
else if (name) registry.namedMiddleware.set(name, handler);
|
|
944
|
-
}
|
|
945
934
|
export const useHead = useUnhead;
|
|
946
935
|
export const useSeoMeta = useUnheadSeoMeta;
|
|
947
936
|
export function useI18n(): DmsI18n {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Answers for requests Node rejects before the request handler runs. Without
|
|
2
|
+
// a `clientError` listener Node answers on its own and logs nothing, so a page
|
|
3
|
+
// refused for its headers left no trace in the server output.
|
|
4
|
+
|
|
5
|
+
const HEADER_OVERFLOW = "HPE_HEADER_OVERFLOW";
|
|
6
|
+
const HEADER_OVERFLOW_RESPONSE =
|
|
7
|
+
"HTTP/1.1 431 Request Header Fields Too Large\r\nConnection: close\r\n\r\n";
|
|
8
|
+
const BAD_REQUEST_RESPONSE =
|
|
9
|
+
"HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n";
|
|
10
|
+
// A browser retries a refused page with the same cookies: one hint per minute
|
|
11
|
+
// is enough to explain them all.
|
|
12
|
+
const HEADER_OVERFLOW_HINT_INTERVAL_MS = 60_000;
|
|
13
|
+
|
|
14
|
+
let lastHeaderOverflowHint = -Infinity;
|
|
15
|
+
|
|
16
|
+
function warnHeaderOverflow(maxHeaderSize) {
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
if (now - lastHeaderOverflowHint < HEADER_OVERFLOW_HINT_INTERVAL_MS) return;
|
|
19
|
+
lastHeaderOverflowHint = now;
|
|
20
|
+
console.warn(
|
|
21
|
+
`DMS refused a request with 431: its headers exceed the ${maxHeaderSize}-byte limit. ` +
|
|
22
|
+
"This is most often cookies accumulated on this host (browsers share " +
|
|
23
|
+
"localhost cookies across ports); clear this site's cookies and reload.",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The server's `clientError` listener: explains a 431 in the logs, and
|
|
29
|
+
* otherwise answers as Node does without a listener.
|
|
30
|
+
*/
|
|
31
|
+
export function handleClientError(error, socket, maxHeaderSize) {
|
|
32
|
+
let response = BAD_REQUEST_RESPONSE;
|
|
33
|
+
if (error.code === HEADER_OVERFLOW) {
|
|
34
|
+
response = HEADER_OVERFLOW_RESPONSE;
|
|
35
|
+
warnHeaderOverflow(maxHeaderSize);
|
|
36
|
+
}
|
|
37
|
+
// Once a response went out on this connection, a status line would land in
|
|
38
|
+
// the middle of it.
|
|
39
|
+
if (socket.writable && socket.bytesWritten === 0) socket.write(response);
|
|
40
|
+
socket.destroy();
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const INTERNAL_ERROR_STATUS = 500;
|
|
2
|
+
const ERROR_CONTENT_TYPE = "text/plain; charset=utf-8";
|
|
3
|
+
const VITE_ERROR = Symbol("dms.viteError");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* In middleware mode, Vite's own error middleware logs an error, sends it to
|
|
7
|
+
* the client overlay, then passes the request on with no error: a module that
|
|
8
|
+
* fails to transform would fall through to page handling and answer 404. This
|
|
9
|
+
* plugin runs just before it, keeps the error on the request and hands it on,
|
|
10
|
+
* so Vite still logs it and shows the overlay.
|
|
11
|
+
*/
|
|
12
|
+
export function captureViteErrors() {
|
|
13
|
+
return {
|
|
14
|
+
name: "dms-capture-vite-errors",
|
|
15
|
+
apply: "serve",
|
|
16
|
+
configureServer(server) {
|
|
17
|
+
return () =>
|
|
18
|
+
server.middlewares.use((error, request, _response, next) => {
|
|
19
|
+
request[VITE_ERROR] = error;
|
|
20
|
+
next(error);
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Hands a request to Vite's middlewares. Resolves `handled` once Vite answered
|
|
28
|
+
* it, `unhandled` when Vite passed it on, and `failed` with the error when a
|
|
29
|
+
* Vite middleware failed on it.
|
|
30
|
+
*/
|
|
31
|
+
export function runViteMiddlewares(devServer, request, response) {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
devServer.middlewares(request, response, (error) => {
|
|
34
|
+
const failure = error ?? request[VITE_ERROR];
|
|
35
|
+
resolve(
|
|
36
|
+
failure
|
|
37
|
+
? { status: "failed", error: failure }
|
|
38
|
+
: { status: "unhandled" },
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
response.once("finish", () => resolve({ status: "handled" }));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Answers 500 with Vite's error message instead of rendering a page. */
|
|
46
|
+
export function writeViteError(response, error) {
|
|
47
|
+
response.statusCode = INTERNAL_ERROR_STATUS;
|
|
48
|
+
response.setHeader("content-type", ERROR_CONTENT_TYPE);
|
|
49
|
+
response.end(error?.message ?? String(error));
|
|
50
|
+
}
|
package/templates/vue/server.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { createServer } from "node:http";
|
|
2
|
+
import { createServer, maxHeaderSize } from "node:http";
|
|
3
3
|
import { extname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { Readable } from "node:stream";
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
@@ -19,8 +19,14 @@ import {
|
|
|
19
19
|
redirectAccessRefusal,
|
|
20
20
|
UNEXPECTED_ERROR_BODY,
|
|
21
21
|
} from "./server/backend-response.mjs";
|
|
22
|
+
import { handleClientError } from "./server/client-error.mjs";
|
|
22
23
|
import { productionHtmlTemplate } from "./server/client-manifest.mjs";
|
|
23
24
|
import { writeContent } from "./server/content.mjs";
|
|
25
|
+
import {
|
|
26
|
+
captureViteErrors,
|
|
27
|
+
runViteMiddlewares,
|
|
28
|
+
writeViteError,
|
|
29
|
+
} from "./server/vite-errors.mjs";
|
|
24
30
|
import { documentStyleTags } from "./server/dev-styles.mjs";
|
|
25
31
|
import { handleEmailRender, watchEmailBundle } from "./server/email.mjs";
|
|
26
32
|
import { HOMEPAGE } from "./server/homepage.mjs";
|
|
@@ -88,7 +94,11 @@ async function developmentServer() {
|
|
|
88
94
|
const serverOptions = { middlewareMode: true };
|
|
89
95
|
if (frontendHttpServer)
|
|
90
96
|
serverOptions.hmr = { server: frontendHttpServer };
|
|
91
|
-
return createViteServer({
|
|
97
|
+
return createViteServer({
|
|
98
|
+
server: serverOptions,
|
|
99
|
+
appType: "custom",
|
|
100
|
+
plugins: [captureViteErrors()],
|
|
101
|
+
});
|
|
92
102
|
})
|
|
93
103
|
.then((server) => {
|
|
94
104
|
vite = server;
|
|
@@ -385,6 +395,12 @@ async function writeBackendError(error, request, response) {
|
|
|
385
395
|
response.end(JSON.stringify(payload));
|
|
386
396
|
}
|
|
387
397
|
|
|
398
|
+
async function answeredByVite(devServer, request, response) {
|
|
399
|
+
const result = await runViteMiddlewares(devServer, request, response);
|
|
400
|
+
if (result.status === "failed") writeViteError(response, result.error);
|
|
401
|
+
return result.status !== "unhandled";
|
|
402
|
+
}
|
|
403
|
+
|
|
388
404
|
export async function handleRequest(request, response) {
|
|
389
405
|
const url = new URL(request.url, "http://frontend.local");
|
|
390
406
|
const pathname = url.pathname;
|
|
@@ -409,11 +425,7 @@ export async function handleRequest(request, response) {
|
|
|
409
425
|
return;
|
|
410
426
|
const devServer = await developmentServer();
|
|
411
427
|
if (devServer && pathname !== "/") {
|
|
412
|
-
|
|
413
|
-
devServer.middlewares(request, response, () => resolve(false));
|
|
414
|
-
response.once("finish", () => resolve(true));
|
|
415
|
-
});
|
|
416
|
-
if (handled) return;
|
|
428
|
+
if (await answeredByVite(devServer, request, response)) return;
|
|
417
429
|
}
|
|
418
430
|
if (!devServer && serveAsset(pathname, request, response)) return;
|
|
419
431
|
if (handleAssetVersionMismatch(request, response)) return;
|
|
@@ -478,6 +490,9 @@ if (
|
|
|
478
490
|
import.meta.url === new URL(`file://${process.argv[1]}`).href
|
|
479
491
|
) {
|
|
480
492
|
frontendHttpServer = createServer(handleRequestSafely);
|
|
493
|
+
frontendHttpServer.on("clientError", (error, socket) =>
|
|
494
|
+
handleClientError(error, socket, maxHeaderSize),
|
|
495
|
+
);
|
|
481
496
|
frontendHttpServer.listen(
|
|
482
497
|
Number(process.env.PORT ?? 3001),
|
|
483
498
|
process.env.HOST ?? "0.0.0.0",
|