@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
|
@@ -39,11 +39,11 @@ const cli_1 = require("@antelopejs/core/cli");
|
|
|
39
39
|
const child_output_1 = require("./child-output");
|
|
40
40
|
const ensure_1 = require("./internal/ensure");
|
|
41
41
|
const guards_1 = require("./internal/guards");
|
|
42
|
-
const plain_object_1 = require("./internal/plain-object");
|
|
43
42
|
const vite_manifest_1 = require("./internal/vite-manifest");
|
|
44
43
|
const common_1 = require("./common");
|
|
45
44
|
const output_1 = require("./output");
|
|
46
45
|
const temporary_workspace_1 = require("./temporary-workspace");
|
|
46
|
+
const verify_source_workspace_1 = require("./verify-source-workspace");
|
|
47
47
|
const verify_source_client_bundle_1 = require("./verify-source-client-bundle");
|
|
48
48
|
const verify_source_build_1 = require("./verify-source-build");
|
|
49
49
|
const verify_source_result_1 = require("./verify-source-result");
|
|
@@ -51,48 +51,6 @@ const verify_source_sources_1 = require("./verify-source-sources");
|
|
|
51
51
|
const vite_output_1 = require("./vite-output");
|
|
52
52
|
const EMAIL_BUILD_SIZE_CEILING_BYTES = 256 * 1024;
|
|
53
53
|
const NON_EMAIL_RUNTIME_PATTERN = /DisplayRichText|ApexCharts|Tiptap|FlowCanvas|app\/.*table|components\/table/i;
|
|
54
|
-
function removeDevelopmentDependencies(root) {
|
|
55
|
-
const packagePath = (0, node_path_1.join)(root, "package.json");
|
|
56
|
-
if (!(0, node_fs_1.existsSync)(packagePath))
|
|
57
|
-
return;
|
|
58
|
-
const packageData = JSON.parse((0, node_fs_1.readFileSync)(packagePath, "utf8"));
|
|
59
|
-
if (!(0, plain_object_1.isPlainObject)(packageData))
|
|
60
|
-
return;
|
|
61
|
-
delete packageData.devDependencies;
|
|
62
|
-
(0, node_fs_1.writeFileSync)(packagePath, `${JSON.stringify(packageData, null, 2)}\n`);
|
|
63
|
-
}
|
|
64
|
-
function materializeWorkspace(workspace, layers, localPackages) {
|
|
65
|
-
const templateRoot = (0, node_path_1.join)((0, common_1.getPackageRoot)(), "templates", "vue");
|
|
66
|
-
const templateFiles = (0, node_fs_1.readdirSync)(templateRoot).filter((file) => !file.startsWith("npmrc"));
|
|
67
|
-
for (const file of templateFiles)
|
|
68
|
-
(0, node_fs_1.cpSync)((0, node_path_1.join)(templateRoot, file), (0, node_path_1.join)(workspace, file), {
|
|
69
|
-
recursive: true,
|
|
70
|
-
});
|
|
71
|
-
(0, common_1.materializeLayers)(workspace, layers);
|
|
72
|
-
const registry = (0, common_1.createFrontendModuleRegistry)(workspace, layers);
|
|
73
|
-
registry.modules.forEach((module) => {
|
|
74
|
-
removeDevelopmentDependencies(module.root);
|
|
75
|
-
});
|
|
76
|
-
(0, common_1.writeFrontendModuleRegistry)(workspace, layers);
|
|
77
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspace, "dms-main.css"), '@import "tailwindcss";\n@import "@nuxt/ui";\n');
|
|
78
|
-
(0, common_1.writeWorkspacePackageJson)(workspace, layers);
|
|
79
|
-
const packagePath = (0, node_path_1.join)(workspace, "package.json");
|
|
80
|
-
const workspacePackage = JSON.parse((0, node_fs_1.readFileSync)(packagePath, "utf8"));
|
|
81
|
-
(0, ensure_1.ensure)((0, plain_object_1.isPlainObject)(workspacePackage), "The workspace package.json is not a JSON object");
|
|
82
|
-
workspacePackage.pnpm = {
|
|
83
|
-
overrides: {
|
|
84
|
-
...Object.fromEntries(layers.map((layer) => [
|
|
85
|
-
String(layer.packageName),
|
|
86
|
-
"workspace:*",
|
|
87
|
-
])),
|
|
88
|
-
...Object.fromEntries(Object.entries(localPackages).map(([name, path]) => [
|
|
89
|
-
name,
|
|
90
|
-
`link:${(0, node_path_1.resolve)(path)}`,
|
|
91
|
-
])),
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
(0, node_fs_1.writeFileSync)(packagePath, `${JSON.stringify(workspacePackage, null, 2)}\n`);
|
|
95
|
-
}
|
|
96
54
|
async function checkServerModulesBundle(workspace) {
|
|
97
55
|
const root = (0, node_path_1.join)(workspace, "dist", "server-modules");
|
|
98
56
|
const javascript = (0, node_fs_1.readdirSync)(root, { recursive: true, encoding: "utf8" })
|
|
@@ -216,7 +174,7 @@ async function verifySources() {
|
|
|
216
174
|
env: { ...process.env, [vite_output_1.VITE_LOG_LEVEL_VARIABLE]: (0, vite_output_1.viteLogLevel)(isVerbose) },
|
|
217
175
|
};
|
|
218
176
|
const tasks = (0, cli_1.getProcessTasks)();
|
|
219
|
-
await tasks.run(`Materializing ${(0, cli_1.pluralize)(layers.length, "module")}`, async () => materializeWorkspace(workspace, layers, options.localPackages), { done: `Materialized ${(0, cli_1.pluralize)(layers.length, "module")}` });
|
|
177
|
+
await tasks.run(`Materializing ${(0, cli_1.pluralize)(layers.length, "module")}`, async () => (0, verify_source_workspace_1.materializeWorkspace)(workspace, layers, options.localPackages), { done: `Materialized ${(0, cli_1.pluralize)(layers.length, "module")}` });
|
|
220
178
|
await (0, common_1.installDeps)(workspace, context.mapLine, ["--ignore-scripts"]);
|
|
221
179
|
await (0, verify_source_build_1.prepareTypes)(context);
|
|
222
180
|
await (0, verify_source_build_1.buildAndTypeCheck)(context);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.materializeWorkspace = materializeWorkspace;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const yaml_1 = require("yaml");
|
|
7
|
+
const common_1 = require("./common");
|
|
8
|
+
const ensure_1 = require("./internal/ensure");
|
|
9
|
+
const guards_1 = require("./internal/guards");
|
|
10
|
+
const plain_object_1 = require("./internal/plain-object");
|
|
11
|
+
const output_1 = require("./output");
|
|
12
|
+
function removeDevelopmentDependencies(root) {
|
|
13
|
+
const packagePath = (0, node_path_1.join)(root, "package.json");
|
|
14
|
+
if (!(0, node_fs_1.existsSync)(packagePath))
|
|
15
|
+
return;
|
|
16
|
+
const packageData = JSON.parse((0, node_fs_1.readFileSync)(packagePath, "utf8"));
|
|
17
|
+
if (!(0, plain_object_1.isPlainObject)(packageData))
|
|
18
|
+
return;
|
|
19
|
+
delete packageData.devDependencies;
|
|
20
|
+
(0, node_fs_1.writeFileSync)(packagePath, `${JSON.stringify(packageData, null, 2)}\n`);
|
|
21
|
+
}
|
|
22
|
+
const PATH_PROTOCOLS = ["file:", "link:"];
|
|
23
|
+
const WORKSPACE_PROTOCOL = "workspace:";
|
|
24
|
+
const LOCKFILE = "pnpm-lock.yaml";
|
|
25
|
+
const WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
26
|
+
const OWN_LOCKFILES = /^\s*shared-workspace-lockfile\s*=\s*false\s*$/m;
|
|
27
|
+
function findUp(directory, file) {
|
|
28
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(directory, file)))
|
|
29
|
+
return directory;
|
|
30
|
+
const parent = (0, node_path_1.dirname)(directory);
|
|
31
|
+
return parent === directory ? undefined : findUp(parent, file);
|
|
32
|
+
}
|
|
33
|
+
function sharesLockfile(root) {
|
|
34
|
+
const npmrc = (0, node_path_1.join)(root, ".npmrc");
|
|
35
|
+
if ((0, node_fs_1.existsSync)(npmrc) && OWN_LOCKFILES.test((0, node_fs_1.readFileSync)(npmrc, "utf8")))
|
|
36
|
+
return false;
|
|
37
|
+
const settings = (0, yaml_1.parse)((0, node_fs_1.readFileSync)((0, node_path_1.join)(root, WORKSPACE_FILE), "utf8"));
|
|
38
|
+
return !((0, plain_object_1.isPlainObject)(settings) && settings.sharedWorkspaceLockfile === false);
|
|
39
|
+
}
|
|
40
|
+
function findLockfile(directory) {
|
|
41
|
+
const workspaceRoot = findUp(directory, WORKSPACE_FILE);
|
|
42
|
+
const lockfileRoot = workspaceRoot && sharesLockfile(workspaceRoot)
|
|
43
|
+
? workspaceRoot
|
|
44
|
+
: findUp(directory, LOCKFILE);
|
|
45
|
+
if (!lockfileRoot)
|
|
46
|
+
return undefined;
|
|
47
|
+
const lockfile = (0, node_path_1.join)(lockfileRoot, LOCKFILE);
|
|
48
|
+
const outsideWorkspace = workspaceRoot !== undefined &&
|
|
49
|
+
(0, node_path_1.relative)(workspaceRoot, lockfileRoot).startsWith("..");
|
|
50
|
+
return (0, node_fs_1.existsSync)(lockfile) && !outsideWorkspace ? lockfile : undefined;
|
|
51
|
+
}
|
|
52
|
+
function readLayerOverrides(layer) {
|
|
53
|
+
const lockfile = findLockfile(layer.path);
|
|
54
|
+
if (!lockfile)
|
|
55
|
+
return [];
|
|
56
|
+
const root = (0, node_path_1.dirname)(lockfile);
|
|
57
|
+
const overrides = [];
|
|
58
|
+
for (const document of (0, yaml_1.parseAllDocuments)((0, node_fs_1.readFileSync)(lockfile, "utf8"))) {
|
|
59
|
+
const [error] = document.errors;
|
|
60
|
+
(0, ensure_1.ensure)(error === undefined, `${(0, output_1.showPath)(lockfile)} is not a pnpm lockfile`, error?.message);
|
|
61
|
+
const lock = document.toJS();
|
|
62
|
+
if (!(0, plain_object_1.isPlainObject)(lock) || !(0, plain_object_1.isPlainObject)(lock.overrides))
|
|
63
|
+
continue;
|
|
64
|
+
for (const [name, spec] of Object.entries(lock.overrides)) {
|
|
65
|
+
if (!(0, guards_1.isText)(spec) || spec.startsWith(WORKSPACE_PROTOCOL))
|
|
66
|
+
continue;
|
|
67
|
+
overrides.push([name, resolveSpec(root, spec)]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return overrides;
|
|
71
|
+
}
|
|
72
|
+
function resolveSpec(root, spec) {
|
|
73
|
+
const protocol = PATH_PROTOCOLS.find((prefix) => spec.startsWith(prefix));
|
|
74
|
+
if (!protocol)
|
|
75
|
+
return spec;
|
|
76
|
+
const path = spec.slice(protocol.length);
|
|
77
|
+
return (0, node_path_1.isAbsolute)(path) ? spec : `${protocol}${(0, node_path_1.resolve)(root, path)}`;
|
|
78
|
+
}
|
|
79
|
+
function materializeWorkspace(workspace, layers, localPackages) {
|
|
80
|
+
const templateRoot = (0, node_path_1.join)((0, common_1.getPackageRoot)(), "templates", "vue");
|
|
81
|
+
const templateFiles = (0, node_fs_1.readdirSync)(templateRoot).filter((file) => !file.startsWith("npmrc"));
|
|
82
|
+
for (const file of templateFiles)
|
|
83
|
+
(0, node_fs_1.cpSync)((0, node_path_1.join)(templateRoot, file), (0, node_path_1.join)(workspace, file), {
|
|
84
|
+
recursive: true,
|
|
85
|
+
});
|
|
86
|
+
(0, common_1.materializeLayers)(workspace, layers);
|
|
87
|
+
const registry = (0, common_1.createFrontendModuleRegistry)(workspace, layers);
|
|
88
|
+
registry.modules.forEach((module) => {
|
|
89
|
+
removeDevelopmentDependencies(module.root);
|
|
90
|
+
});
|
|
91
|
+
(0, common_1.writeFrontendModuleRegistry)(workspace, layers);
|
|
92
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspace, "dms-main.css"), '@import "tailwindcss";\n@import "@nuxt/ui";\n');
|
|
93
|
+
(0, common_1.writeWorkspacePackageJson)(workspace, layers);
|
|
94
|
+
const packagePath = (0, node_path_1.join)(workspace, "package.json");
|
|
95
|
+
const workspacePackage = JSON.parse((0, node_fs_1.readFileSync)(packagePath, "utf8"));
|
|
96
|
+
(0, ensure_1.ensure)((0, plain_object_1.isPlainObject)(workspacePackage), "The workspace package.json is not a JSON object");
|
|
97
|
+
workspacePackage.pnpm = {
|
|
98
|
+
overrides: {
|
|
99
|
+
...Object.fromEntries(layers.flatMap(readLayerOverrides)),
|
|
100
|
+
...Object.fromEntries(layers.map((layer) => [
|
|
101
|
+
String(layer.packageName),
|
|
102
|
+
"workspace:*",
|
|
103
|
+
])),
|
|
104
|
+
...Object.fromEntries(Object.entries(localPackages).map(([name, path]) => [
|
|
105
|
+
name,
|
|
106
|
+
`link:${(0, node_path_1.resolve)(path)}`,
|
|
107
|
+
])),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
(0, node_fs_1.writeFileSync)(packagePath, `${JSON.stringify(workspacePackage, null, 2)}\n`);
|
|
111
|
+
}
|
package/dist/workspace-setup.js
CHANGED
|
@@ -299,7 +299,8 @@ async function setupWorkspace(opts) {
|
|
|
299
299
|
writeDerivedOutputs(workspaceDir, layers);
|
|
300
300
|
(0, materialize_1.writeDmsMainCss)(workspaceDir, layers);
|
|
301
301
|
const layerPaths = layers.map((layer) => layer.path);
|
|
302
|
-
const shouldInstall =
|
|
302
|
+
const shouldInstall = opts.install !== false &&
|
|
303
|
+
needsInstall(workspaceDir, layerPaths, opts.force);
|
|
303
304
|
if (shouldInstall) {
|
|
304
305
|
await opts.beforeInstall?.();
|
|
305
306
|
await installDeps(workspaceDir, (0, child_output_1.createPathMapper)(workspaceDir, layers));
|
|
@@ -321,6 +322,6 @@ function writeDerivedOutputs(workspaceDir, layers) {
|
|
|
321
322
|
if (!(error instanceof locale_files_1.InvalidLocaleFileError))
|
|
322
323
|
throw error;
|
|
323
324
|
const showSource = (0, child_output_1.createPathMapper)(workspaceDir, layers);
|
|
324
|
-
throw new locale_files_1.InvalidLocaleFileError(showSource(error.path), error.reason);
|
|
325
|
+
throw new locale_files_1.InvalidLocaleFileError(showSource(error.path), error.reason, error.details);
|
|
325
326
|
}
|
|
326
327
|
}
|
package/dist/workspace.js
CHANGED
|
@@ -105,7 +105,7 @@ function directorySize(dir) {
|
|
|
105
105
|
continue;
|
|
106
106
|
}
|
|
107
107
|
for (const entry of entries) {
|
|
108
|
-
const path =
|
|
108
|
+
const path = `${current}${node_path_1.sep}${entry.name}`;
|
|
109
109
|
if (entry.isDirectory()) {
|
|
110
110
|
pending.push(path);
|
|
111
111
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms-frontend",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Frontend-agnostic loader for AntelopeJS DMS, shipping the Vue 3 renderer (Vite, Inertia, SSR)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelope",
|
|
@@ -60,20 +60,20 @@
|
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "rimraf dist && tsc",
|
|
62
62
|
"prepack": "pnpm run build",
|
|
63
|
-
"release": "pnpm run lint && pnpm run format:check && pnpm run test && pnpm run test:cli && release-it",
|
|
63
|
+
"release": "pnpm run typecheck && pnpm run lint && pnpm run format:check && pnpm run test && pnpm run test:cli && release-it",
|
|
64
64
|
"test": "node --import tsx --test 'tests/!(cli|cli-*).test.{ts,mts}'",
|
|
65
65
|
"test:cli": "pnpm run build && DMS_PREBUILT=1 node --import tsx --test tests/cli.test.ts 'tests/cli-*.test.{ts,mts}'",
|
|
66
66
|
"test:real-source": "pnpm run build && node dist/verify-source-runner.js",
|
|
67
67
|
"format": "oxfmt .",
|
|
68
68
|
"lint": "oxlint && antelopejs-check-interface-ranges && pnpm run docs:reference:check && pnpm run source-checks",
|
|
69
|
-
"typecheck": "tsc --noEmit",
|
|
69
|
+
"typecheck": "tsc --noEmit && tsx scripts/typecheck-projects.ts tsconfig.templates.json tsconfig.tests.json",
|
|
70
70
|
"lint:fix": "oxlint --fix --max-warnings=1000000 && oxfmt .",
|
|
71
71
|
"knip": "knip --dependencies",
|
|
72
72
|
"knip:all": "knip",
|
|
73
73
|
"docs:reference": "tsx scripts/docs-reference.ts",
|
|
74
74
|
"docs:reference:check": "tsx scripts/docs-reference.ts --check",
|
|
75
75
|
"source-checks": "tsx scripts/source-checks.ts",
|
|
76
|
-
"prepare": "if
|
|
76
|
+
"prepare": "if [ -e .git ] && command -v git >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1 && GIT_DIR=.git GIT_WORK_TREE=. git rev-parse --git-dir >/dev/null 2>&1; then GIT_DIR=.git GIT_WORK_TREE=. lefthook install; fi",
|
|
77
77
|
"checks": "antelopejs-checks",
|
|
78
78
|
"size": "antelopejs-check-size",
|
|
79
79
|
"sync": "antelopejs-sync",
|
|
@@ -87,12 +87,13 @@
|
|
|
87
87
|
"chokidar": "^4.0.3",
|
|
88
88
|
"commander": "^13.1.0",
|
|
89
89
|
"ignore": "^7.0.5",
|
|
90
|
-
"semver": "^7.7.4"
|
|
90
|
+
"semver": "^7.7.4",
|
|
91
|
+
"yaml": "^2.9.1"
|
|
91
92
|
},
|
|
92
93
|
"devDependencies": {
|
|
93
|
-
"@antelopejs/core": "^1.
|
|
94
|
+
"@antelopejs/core": "^1.21.2",
|
|
94
95
|
"@antelopejs/interface-dms": "^0.10.0",
|
|
95
|
-
"@antelopejs/tooling-configs": "^0.
|
|
96
|
+
"@antelopejs/tooling-configs": "^0.5.1",
|
|
96
97
|
"@iconify/utils": "^3.1.0",
|
|
97
98
|
"@iconify/vue": "^5.0.1",
|
|
98
99
|
"@inertiajs/vue3": "^3.7.1",
|
|
@@ -104,15 +105,20 @@
|
|
|
104
105
|
"@vueuse/core": "14.4.0",
|
|
105
106
|
"defu": "^6.1.4",
|
|
106
107
|
"devalue": "^5.9.4",
|
|
108
|
+
"fast-check": "^4.10.2",
|
|
107
109
|
"knip": "^6.34.0",
|
|
108
110
|
"lefthook": "^2.2.2",
|
|
111
|
+
"mongodb-memory-server": "^11.3.0",
|
|
109
112
|
"ofetch": "^1.5.1",
|
|
110
113
|
"oxfmt": "^0.66.0",
|
|
111
114
|
"oxlint": "1.81.0",
|
|
112
115
|
"oxlint-tsgolint": "^7.0.2001",
|
|
116
|
+
"pixelmatch": "^8.0.0",
|
|
117
|
+
"playwright-core": "^1.56.1",
|
|
113
118
|
"release-it": "^19.2.4",
|
|
114
119
|
"release-it-changelogen": "^0.1.0",
|
|
115
120
|
"rimraf": "^6.1.2",
|
|
121
|
+
"sharp": "^0.35.5",
|
|
116
122
|
"tsx": "^4.23.15",
|
|
117
123
|
"typescript": "^6.0.3",
|
|
118
124
|
"unplugin-auto-import": "^20.2.0",
|
|
@@ -137,6 +143,13 @@
|
|
|
137
143
|
"pnpm": {
|
|
138
144
|
"patchedDependencies": {
|
|
139
145
|
"@vueuse/core@14.4.0": "templates/vue/patches/@vueuse__core@14.4.0.patch"
|
|
146
|
+
},
|
|
147
|
+
"packageExtensions": {
|
|
148
|
+
"@antelopejs/core@1.21": {
|
|
149
|
+
"dependencies": {
|
|
150
|
+
"@types/mocha": "^10.0.10"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
140
153
|
}
|
|
141
154
|
},
|
|
142
155
|
"antelopeJs": {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
+
import { isRecord, isString } from "./server/type-guards.mjs";
|
|
4
|
+
|
|
5
|
+
const BACKEND_SOURCE = "src";
|
|
6
|
+
const QUERY_OR_HASH = /[?#].*$/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} manifest
|
|
10
|
+
* @returns {string | undefined}
|
|
11
|
+
*/
|
|
12
|
+
function manifestName(manifest) {
|
|
13
|
+
try {
|
|
14
|
+
/** @type {unknown} */
|
|
15
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
16
|
+
return isRecord(parsed) && isString(parsed.name) ? parsed.name : undefined;
|
|
17
|
+
} catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {{ root: string, name: string }} BackendPackage
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The backend package that holds `directory`: the nearest `package.json` above
|
|
28
|
+
* it, when it names one of `backendPackages`.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} directory
|
|
31
|
+
* @param {ReadonlySet<string>} backendPackages
|
|
32
|
+
* @param {Map<string, BackendPackage | null>} known
|
|
33
|
+
* @returns {BackendPackage | null}
|
|
34
|
+
*/
|
|
35
|
+
function owningBackend(directory, backendPackages, known) {
|
|
36
|
+
const cached = known.get(directory);
|
|
37
|
+
if (cached !== undefined) return cached;
|
|
38
|
+
const manifest = join(directory, "package.json");
|
|
39
|
+
const parent = dirname(directory);
|
|
40
|
+
/** @type {BackendPackage | null} */
|
|
41
|
+
let owner = null;
|
|
42
|
+
if (existsSync(manifest)) {
|
|
43
|
+
const name = manifestName(manifest);
|
|
44
|
+
owner =
|
|
45
|
+
name !== undefined && backendPackages.has(name)
|
|
46
|
+
? { root: directory, name }
|
|
47
|
+
: null;
|
|
48
|
+
} else if (parent !== directory) {
|
|
49
|
+
owner = owningBackend(parent, backendPackages, known);
|
|
50
|
+
}
|
|
51
|
+
known.set(directory, owner);
|
|
52
|
+
return owner;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The real path of a resolved module id under the `src` of a backend package,
|
|
57
|
+
* and that package's name, or `undefined`. The package that owns the file
|
|
58
|
+
* decides, not where it lies, so a backend linked or installed into the
|
|
59
|
+
* workspace is caught as well as its source checkout. Virtual ids and ids that
|
|
60
|
+
* are not files are ignored.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} id
|
|
63
|
+
* @param {ReadonlySet<string>} backendPackages
|
|
64
|
+
* @param {Map<string, BackendPackage | null>} known
|
|
65
|
+
* @returns {{ file: string, name: string } | undefined}
|
|
66
|
+
*/
|
|
67
|
+
function backendSourceFile(id, backendPackages, known) {
|
|
68
|
+
const path = id.replace(QUERY_OR_HASH, "");
|
|
69
|
+
if (!isAbsolute(path)) return undefined;
|
|
70
|
+
let file;
|
|
71
|
+
try {
|
|
72
|
+
file = realpathSync(path);
|
|
73
|
+
} catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const owner = owningBackend(dirname(file), backendPackages, known);
|
|
77
|
+
if (owner === null) return undefined;
|
|
78
|
+
return relative(owner.root, file).split(sep)[0] === BACKEND_SOURCE
|
|
79
|
+
? { file, name: owner.name }
|
|
80
|
+
: undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {ReadonlySet<string>} backendPackages
|
|
85
|
+
* @returns {import("vite").Plugin}
|
|
86
|
+
*/
|
|
87
|
+
function boundaryCheck(backendPackages) {
|
|
88
|
+
/** @type {Map<string, BackendPackage | null>} */
|
|
89
|
+
const known = new Map();
|
|
90
|
+
return {
|
|
91
|
+
name: "dms-backend-boundary",
|
|
92
|
+
enforce: "pre",
|
|
93
|
+
async resolveId(source, importer, options) {
|
|
94
|
+
const resolved = await this.resolve(source, importer, {
|
|
95
|
+
...options,
|
|
96
|
+
skipSelf: true,
|
|
97
|
+
});
|
|
98
|
+
if (resolved === null) return null;
|
|
99
|
+
const backend = backendSourceFile(resolved.id, backendPackages, known);
|
|
100
|
+
if (backend === undefined) return resolved;
|
|
101
|
+
const from =
|
|
102
|
+
importer === undefined
|
|
103
|
+
? "an entry point"
|
|
104
|
+
: importer.replace(QUERY_OR_HASH, "");
|
|
105
|
+
return this.error(
|
|
106
|
+
`Backend boundary: ${from} imports ${backend.file}, which is under the src of ${backend.name}. ` +
|
|
107
|
+
"The frontend reaches the backend only through its HTTP interface.",
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Fail the build, a worker build and the dev server when a module of the
|
|
115
|
+
* frontend resolves to a file under the `src` of a backend package: the package
|
|
116
|
+
* that holds a module's layer, as the registry names it. The browser and the
|
|
117
|
+
* server renderer reach the backend only through its HTTP interface.
|
|
118
|
+
*
|
|
119
|
+
* The check reads what the whole resolution chain returns, so an alias, a
|
|
120
|
+
* symlink and an id marked external are all seen through. Vite builds workers
|
|
121
|
+
* with their own plugin list, so the plugin adds the check there too.
|
|
122
|
+
*
|
|
123
|
+
* @param {readonly string[]} backendPackages
|
|
124
|
+
* @returns {import("vite").Plugin}
|
|
125
|
+
*/
|
|
126
|
+
export function rejectBackendSources(backendPackages) {
|
|
127
|
+
const names = new Set(backendPackages);
|
|
128
|
+
return {
|
|
129
|
+
...boundaryCheck(names),
|
|
130
|
+
config: () => ({ worker: { plugins: () => [boundaryCheck(names)] } }),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* quarter to four fifths, but every group is loaded whole by each page that
|
|
19
19
|
* uses any of it, and the anonymous pages pay 70 to 115 KB of Brotli for code
|
|
20
20
|
* only the signed-in shell uses.
|
|
21
|
+
*
|
|
22
|
+
* @type {import("vite").Rolldown.CodeSplittingOptions}
|
|
21
23
|
*/
|
|
22
24
|
export const clientCodeSplitting = {
|
|
23
25
|
groups: [{ name: "entry", tags: ["$initial"] }],
|
|
@@ -55,23 +55,27 @@ export function entryOwner(id, modules) {
|
|
|
55
55
|
* their static imports, transitively, by file name.
|
|
56
56
|
*
|
|
57
57
|
* @param {import("vite").Rollup.OutputBundle} bundle
|
|
58
|
-
* @returns {
|
|
58
|
+
* @returns {import("vite").Rollup.OutputChunk[]}
|
|
59
59
|
*/
|
|
60
60
|
function entryClosure(bundle) {
|
|
61
61
|
const chunks = Object.values(bundle).filter(
|
|
62
|
+
/**
|
|
63
|
+
* @param {import("vite").Rollup.OutputChunk | import("vite").Rollup.OutputAsset} output
|
|
64
|
+
* @returns {output is import("vite").Rollup.OutputChunk}
|
|
65
|
+
*/
|
|
62
66
|
(output) => output.type === "chunk",
|
|
63
67
|
);
|
|
64
68
|
const byName = new Map(chunks.map((chunk) => [chunk.fileName, chunk]));
|
|
65
|
-
/** @type {
|
|
66
|
-
const seen = new
|
|
69
|
+
/** @type {Map<string, import("vite").Rollup.OutputChunk>} */
|
|
70
|
+
const seen = new Map();
|
|
67
71
|
/** @param {import("vite").Rollup.OutputChunk | undefined} chunk */
|
|
68
72
|
const visit = (chunk) => {
|
|
69
73
|
if (!chunk || seen.has(chunk.fileName)) return;
|
|
70
|
-
seen.
|
|
74
|
+
seen.set(chunk.fileName, chunk);
|
|
71
75
|
for (const file of chunk.imports ?? []) visit(byName.get(file));
|
|
72
76
|
};
|
|
73
77
|
for (const chunk of chunks) if (chunk.isEntry) visit(chunk);
|
|
74
|
-
return [...seen
|
|
78
|
+
return [...seen.values()];
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
/**
|
|
@@ -31,10 +31,11 @@ export interface DmsFrontendBuild {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Adds Vite plugins to the application's build, for `ajs dms dev`,
|
|
34
|
-
* `ajs dms build` and the preview alike
|
|
34
|
+
* `ajs dms build` and the preview alike, and to the bundle of each web worker
|
|
35
|
+
* it starts with `new Worker(new URL(...))`.
|
|
35
36
|
*
|
|
36
|
-
* `create` runs in the build process each time Vite loads its configuration
|
|
37
|
-
* and returns real Vite plugin objects (an array, each with a `name`; false,
|
|
37
|
+
* `create` runs in the build process each time Vite loads its configuration
|
|
38
|
+
* and once more for each worker bundle, and returns real Vite plugin objects (an array, each with a `name`; false,
|
|
38
39
|
* null and undefined entries are skipped and nested arrays flattened, as in a
|
|
39
40
|
* Vite configuration) or nothing. It is synchronous: a Promise is refused,
|
|
40
41
|
* because the configuration is built synchronously. The plugins come after
|
|
@@ -35,8 +35,9 @@ function deprioritize(html) {
|
|
|
35
35
|
export function orderHeadForFirstPaint(html) {
|
|
36
36
|
const prioritized = deprioritize(html);
|
|
37
37
|
const stylesheet = prioritized.match(STYLESHEET);
|
|
38
|
-
const
|
|
39
|
-
|
|
38
|
+
const stylesheetAt = prioritized.search(STYLESHEET);
|
|
39
|
+
const scriptAt = prioritized.search(SCRIPT);
|
|
40
|
+
if (!stylesheet || scriptAt < 0 || stylesheetAt < scriptAt)
|
|
40
41
|
return prioritized;
|
|
41
42
|
return prioritized
|
|
42
43
|
.replace(STYLESHEET, "")
|
|
@@ -9,6 +9,8 @@ export const ICON_API_PATH = "/api/_dms/icons/";
|
|
|
9
9
|
/**
|
|
10
10
|
* The `@iconify/vue` API provider serving every icon the client bundle lacks
|
|
11
11
|
* from `origin`, instead of `api.iconify.design` and its fallbacks.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} origin
|
|
12
14
|
*/
|
|
13
15
|
export function sameOriginIconProvider(origin) {
|
|
14
16
|
return { resources: [origin], path: ICON_API_PATH };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { REDIRECT_STATUSES } from "../proxy-redirects.mjs";
|
|
2
|
-
import { isString } from "../type-guards.mjs";
|
|
2
|
+
import { isObjectLike, isString } from "../type-guards.mjs";
|
|
3
3
|
import { clientCountryHeader, clientIp, clientIpHeader } from "./client-ip.mjs";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -39,7 +39,7 @@ export const GENERIC_ERROR_MESSAGE = "error.500.description";
|
|
|
39
39
|
*/
|
|
40
40
|
export function publicBackendMessage(data) {
|
|
41
41
|
/** @type {unknown} */
|
|
42
|
-
const message =
|
|
42
|
+
const message = isObjectLike(data) ? data.message : data;
|
|
43
43
|
return isString(message) && PUBLIC_AUTH_MESSAGE.test(message)
|
|
44
44
|
? message
|
|
45
45
|
: undefined;
|
|
@@ -59,7 +59,7 @@ export function relayedHeaders(request, names) {
|
|
|
59
59
|
...clientCountryHeader(request),
|
|
60
60
|
...Object.fromEntries(
|
|
61
61
|
names.flatMap((name) =>
|
|
62
|
-
request.headers[name] ? [[name, request.headers[name]]] : [],
|
|
62
|
+
request.headers[name] ? [[name, String(request.headers[name])]] : [],
|
|
63
63
|
),
|
|
64
64
|
),
|
|
65
65
|
};
|
|
@@ -86,7 +86,7 @@ export function relayedHeaders(request, names) {
|
|
|
86
86
|
* is not JSON
|
|
87
87
|
*/
|
|
88
88
|
export async function backend(path, request, options = {}) {
|
|
89
|
-
/** @type {Record<string, string
|
|
89
|
+
/** @type {Record<string, string>} */
|
|
90
90
|
const headers = Object.fromEntries(
|
|
91
91
|
Object.entries(options.headers ?? {}).map(([name, value]) => [
|
|
92
92
|
name.toLowerCase(),
|
|
@@ -71,8 +71,8 @@ export function allowedEstablishEndpoints(
|
|
|
71
71
|
.map((entry) => entry.trim());
|
|
72
72
|
return [
|
|
73
73
|
...new Set(
|
|
74
|
-
[...declared, ...fromEnvironment].
|
|
75
|
-
|
|
74
|
+
[...declared, ...fromEnvironment].flatMap((entry) =>
|
|
75
|
+
isString(entry) && BACKEND_API_PATH.test(entry) ? [entry] : [],
|
|
76
76
|
),
|
|
77
77
|
),
|
|
78
78
|
];
|
|
@@ -96,7 +96,10 @@ function decode(value) {
|
|
|
96
96
|
);
|
|
97
97
|
decipher.setAuthTag(payload.subarray(12, 28));
|
|
98
98
|
return JSON.parse(
|
|
99
|
-
Buffer.concat([
|
|
99
|
+
Buffer.concat([
|
|
100
|
+
decipher.update(payload.subarray(28)),
|
|
101
|
+
decipher.final(),
|
|
102
|
+
]).toString(),
|
|
100
103
|
);
|
|
101
104
|
} catch {
|
|
102
105
|
return undefined;
|
|
@@ -150,7 +153,7 @@ function appendCookie(response, name, value, cookieAttributes) {
|
|
|
150
153
|
response.setHeader(
|
|
151
154
|
"set-cookie",
|
|
152
155
|
current
|
|
153
|
-
? [cookie, ...(Array.isArray(current) ? current : [current])]
|
|
156
|
+
? [cookie, ...(Array.isArray(current) ? current : [String(current)])]
|
|
154
157
|
: cookie,
|
|
155
158
|
);
|
|
156
159
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { authRejection } from "./auth-hooks.mjs";
|
|
2
|
+
import { compilePathPattern } from "./path-patterns.mjs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @import { AuthProvider, DeclaredModule } from "./module-route-types.mjs"
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The auth hooks: those of the highest-priority module that registers them.
|
|
10
|
+
* A module registering them twice, or after another one, is reported.
|
|
11
|
+
*
|
|
12
|
+
* @param {DeclaredModule[]} modules
|
|
13
|
+
* @param {(message: string) => void} warn
|
|
14
|
+
*/
|
|
15
|
+
export function resolveAuth(modules, warn) {
|
|
16
|
+
/** @type {AuthProvider | undefined} */
|
|
17
|
+
let provider;
|
|
18
|
+
for (const module of modules) {
|
|
19
|
+
for (const auth of module.declared.auth) {
|
|
20
|
+
const reason = authRejection(auth);
|
|
21
|
+
if (reason) {
|
|
22
|
+
warn(
|
|
23
|
+
`Frontend module ${module.id} registers auth hooks, which are ignored: ${reason}.`,
|
|
24
|
+
);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (provider) {
|
|
28
|
+
warn(
|
|
29
|
+
`Frontend module ${module.id} registers auth hooks, which are ignored: ${provider.id} (priority ${provider.priority ?? 0}) provides them.`,
|
|
30
|
+
);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
provider = {
|
|
34
|
+
id: module.id,
|
|
35
|
+
priority: module.priority ?? 0,
|
|
36
|
+
signInPath: auth.signInPath,
|
|
37
|
+
signInExempt: auth.signInExempt?.flatMap(
|
|
38
|
+
(pattern) => compilePathPattern(pattern) ?? [],
|
|
39
|
+
),
|
|
40
|
+
refreshSession: auth.refreshSession?.bind(auth),
|
|
41
|
+
publicSession: auth.publicSession?.bind(auth),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return provider;
|
|
46
|
+
}
|