@antelopejs/dms-frontend 0.7.0 → 0.8.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 +4 -4
- package/dist/commands/build-action.d.ts +8 -5
- package/dist/commands/build-action.js +13 -41
- package/dist/commands/build.js +9 -15
- package/dist/commands/dev-action.js +5 -1
- package/dist/commands/start-action.d.ts +2 -0
- package/dist/commands/start-action.js +36 -49
- package/dist/commands/start.js +8 -6
- package/dist/config.d.ts +2 -0
- package/dist/config.js +6 -0
- package/dist/derived-outputs.js +2 -2
- package/dist/fs-sync.d.ts +1 -0
- package/dist/fs-sync.js +6 -0
- package/dist/help.js +8 -4
- package/dist/index.js +4 -6
- package/dist/layer-watch.js +4 -0
- package/dist/layers.d.ts +1 -3
- package/dist/layers.js +1 -95
- package/dist/manifest.d.ts +8 -8
- package/dist/manifest.js +15 -39
- package/dist/materialize.js +6 -0
- package/dist/module-set.d.ts +14 -12
- package/dist/module-set.js +39 -31
- package/dist/private-values.d.ts +2 -2
- package/dist/private-values.js +1 -6
- package/dist/project-modules.d.ts +46 -0
- package/dist/project-modules.js +233 -0
- package/dist/workspace-setup.d.ts +3 -7
- package/dist/workspace-setup.js +41 -62
- package/dist/workspace.d.ts +2 -4
- package/dist/workspace.js +1 -1
- package/package.json +5 -7
- package/skills/dms-frontend/SKILL.md +2 -2
- package/templates/vue/client-chunking.mjs +61 -0
- package/templates/vue/email-renderer.ts +6 -1
- package/templates/vue/frontend-build-loader.ts +1 -1
- package/templates/vue/frontend-module.ts +24 -0
- package/templates/vue/main.ts +11 -2
- package/templates/vue/package.json +2 -2
- package/templates/vue/prepare-types.mjs +2 -7
- package/templates/vue/public-options.ts +19 -0
- package/templates/vue/server/auth/backend.mjs +20 -3
- package/templates/vue/server/auth/client-ip.mjs +64 -22
- package/templates/vue/server/auth/session.mjs +4 -11
- package/templates/vue/server/backend-proxies.mjs +13 -2
- package/templates/vue/server/backend-response.mjs +2 -13
- package/templates/vue/server/cli-report.mjs +42 -18
- package/templates/vue/server/client-manifest.mjs +22 -58
- package/templates/vue/server/dev-private-files.mjs +1 -1
- package/templates/vue/server/dev-styles.mjs +16 -5
- package/templates/vue/server/homepage.mjs +4 -1
- package/templates/vue/server/module-event.mjs +23 -8
- package/templates/vue/server/module-server.mjs +4 -4
- package/templates/vue/server/module-testing.mjs +5 -3
- package/templates/vue/server/proxy-compression.mjs +15 -8
- package/templates/vue/server/proxy-headers.mjs +12 -1
- package/templates/vue/server/proxy-redirects.mjs +66 -0
- package/templates/vue/server/proxy-transfers.mjs +84 -0
- package/templates/vue/server/public-endpoints.mjs +2 -2
- package/templates/vue/server/public-options.mjs +49 -0
- package/templates/vue/server/request-routing.mjs +20 -0
- package/templates/vue/server/web-body.mjs +24 -0
- package/templates/vue/server-module.ts +19 -7
- package/templates/vue/server.mjs +59 -49
- package/templates/vue/ssr-renderer.ts +19 -1
- package/templates/vue/vite.config.ts +72 -46
- package/templates/vue/vite.email.config.ts +6 -3
- package/templates/vue/vite.server.config.ts +5 -2
- package/dist/commands/snapshot-action.d.ts +0 -6
- package/dist/commands/snapshot-action.js +0 -53
- package/dist/commands/snapshot.d.ts +0 -2
- package/dist/commands/snapshot.js +0 -55
- package/dist/snapshot.d.ts +0 -25
- package/dist/snapshot.js +0 -90
- package/templates/vue/server/backend-wake.mjs +0 -129
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FRONTEND_DECLARATION_FIELD = void 0;
|
|
37
|
+
exports.loadProjectConfig = loadProjectConfig;
|
|
38
|
+
exports.readShippingVersion = readShippingVersion;
|
|
39
|
+
exports.readProjectFrontendModules = readProjectFrontendModules;
|
|
40
|
+
const node_fs_1 = require("node:fs");
|
|
41
|
+
const fsPromises = __importStar(require("node:fs/promises"));
|
|
42
|
+
const node_path_1 = require("node:path");
|
|
43
|
+
const cli_1 = require("@antelopejs/core/cli");
|
|
44
|
+
const layers_1 = require("./layers");
|
|
45
|
+
const private_values_1 = require("./private-values");
|
|
46
|
+
exports.FRONTEND_DECLARATION_FIELD = "dmsFrontend";
|
|
47
|
+
const FRONTEND_SOURCE_DIR = "frontend-vue";
|
|
48
|
+
const RENDERER = { name: "vue", version: "3" };
|
|
49
|
+
const CONFIG_VAR_PATTERN = /\$\{@([^.}]+)\.([^}]+)\}/g;
|
|
50
|
+
const PURE_CONFIG_VAR = /^\$\{@([^.}]+)\.([^}]+)\}$/;
|
|
51
|
+
const TRAILING_SLASHES = /\/+$/;
|
|
52
|
+
const DEFAULT_API_PORT = 80;
|
|
53
|
+
const LOOPBACK_HOST = "127.0.0.1";
|
|
54
|
+
const WILDCARD_HOSTS = new Set(["0.0.0.0", "::", "[::]"]);
|
|
55
|
+
const NODE_FILE_SYSTEM = {
|
|
56
|
+
readFile: (path) => fsPromises.readFile(path),
|
|
57
|
+
readFileString: (path, encoding = "utf-8") => fsPromises.readFile(path, encoding),
|
|
58
|
+
writeFile: (path, data) => fsPromises.writeFile(path, data),
|
|
59
|
+
readdir: (path) => fsPromises.readdir(path),
|
|
60
|
+
stat: (path) => fsPromises.stat(path),
|
|
61
|
+
mkdir: async (path, options) => {
|
|
62
|
+
await fsPromises.mkdir(path, options);
|
|
63
|
+
},
|
|
64
|
+
rm: (path, options) => fsPromises.rm(path, options),
|
|
65
|
+
exists: async (path) => (0, node_fs_1.existsSync)(path),
|
|
66
|
+
access: (path) => fsPromises.access(path),
|
|
67
|
+
copyFile: (src, dest) => fsPromises.copyFile(src, dest),
|
|
68
|
+
rename: (oldPath, newPath) => fsPromises.rename(oldPath, newPath),
|
|
69
|
+
};
|
|
70
|
+
function apiLocalBaseUrl(server) {
|
|
71
|
+
const host = !server.host || WILDCARD_HOSTS.has(server.host)
|
|
72
|
+
? LOOPBACK_HOST
|
|
73
|
+
: server.host;
|
|
74
|
+
const protocol = server.protocol ?? "http";
|
|
75
|
+
return `${protocol}://${host}:${server.port ?? DEFAULT_API_PORT}`;
|
|
76
|
+
}
|
|
77
|
+
const CONFIG_VAR_PROVIDERS = {
|
|
78
|
+
api: (config) => {
|
|
79
|
+
const api = (config ?? {});
|
|
80
|
+
const server = api.servers?.[0];
|
|
81
|
+
if (!server)
|
|
82
|
+
return {};
|
|
83
|
+
const local = apiLocalBaseUrl(server);
|
|
84
|
+
return {
|
|
85
|
+
API_PORT: server.port ?? DEFAULT_API_PORT,
|
|
86
|
+
API_LOCAL_BASE_URL: local,
|
|
87
|
+
API_PUBLIC_BASE_URL: api.publicBaseUrl?.trim().replace(TRAILING_SLASHES, "") || local,
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
function projectConfigVars(config) {
|
|
92
|
+
return Object.fromEntries(Object.entries(CONFIG_VAR_PROVIDERS)
|
|
93
|
+
.filter(([id]) => config.modules[id])
|
|
94
|
+
.map(([id, provide]) => [id, provide(config.modules[id].config)]));
|
|
95
|
+
}
|
|
96
|
+
function substituteConfigVars(value, vars) {
|
|
97
|
+
if (typeof value === "string") {
|
|
98
|
+
const pure = value.match(PURE_CONFIG_VAR);
|
|
99
|
+
if (pure)
|
|
100
|
+
return vars[pure[1]]?.[pure[2]];
|
|
101
|
+
return value.replace(CONFIG_VAR_PATTERN, (token, module, name) => {
|
|
102
|
+
const resolved = vars[module]?.[name];
|
|
103
|
+
return resolved === undefined ? token : String(resolved);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(value))
|
|
107
|
+
return value.map((item) => substituteConfigVars(item, vars));
|
|
108
|
+
if (value && typeof value === "object") {
|
|
109
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
110
|
+
key,
|
|
111
|
+
substituteConfigVars(item, vars),
|
|
112
|
+
]));
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
async function loadProjectConfig(projectDir, environment) {
|
|
117
|
+
const { ConfigLoader } = await Promise.resolve().then(() => __importStar(require("@antelopejs/core")));
|
|
118
|
+
const loader = new ConfigLoader(NODE_FILE_SYSTEM);
|
|
119
|
+
let loaded;
|
|
120
|
+
try {
|
|
121
|
+
loaded = (await loader.load(projectDir, environment));
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw new cli_1.CliError({
|
|
125
|
+
title: `Could not evaluate the project's configuration in ${projectDir}`,
|
|
126
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
127
|
+
fixes: ["Run from the project root, or pass --project <dir>"],
|
|
128
|
+
}, { cause: error });
|
|
129
|
+
}
|
|
130
|
+
const vars = projectConfigVars(loaded);
|
|
131
|
+
return {
|
|
132
|
+
cacheFolder: loaded.cacheFolder,
|
|
133
|
+
modules: Object.fromEntries(Object.entries(loaded.modules).map(([id, module]) => [
|
|
134
|
+
id,
|
|
135
|
+
{ ...module, config: substituteConfigVars(module.config, vars) },
|
|
136
|
+
])),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function moduleFolder(projectDir, cacheFolder, source) {
|
|
140
|
+
const candidates = source.type === "local" && source.path
|
|
141
|
+
? [(0, node_path_1.resolve)(projectDir, source.path)]
|
|
142
|
+
: source.package
|
|
143
|
+
? [
|
|
144
|
+
(0, node_path_1.join)(projectDir, "node_modules", source.package),
|
|
145
|
+
(0, node_path_1.resolve)(projectDir, cacheFolder, source.package),
|
|
146
|
+
]
|
|
147
|
+
: [];
|
|
148
|
+
return candidates.find((folder) => (0, node_fs_1.existsSync)((0, node_path_1.join)(folder, "package.json")));
|
|
149
|
+
}
|
|
150
|
+
function readShippingVersion(sourcePath) {
|
|
151
|
+
try {
|
|
152
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)((0, node_path_1.dirname)(sourcePath), "package.json"), "utf-8"));
|
|
153
|
+
return typeof pkg.version === "string" ? pkg.version : undefined;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function toLayer(registration) {
|
|
160
|
+
const name = (0, layers_1.readLayerPackage)(registration.sourcePath)?.name;
|
|
161
|
+
return {
|
|
162
|
+
path: registration.sourcePath,
|
|
163
|
+
name: registration.name,
|
|
164
|
+
sourcePath: registration.sourcePath,
|
|
165
|
+
packageName: typeof name === "string" ? name : undefined,
|
|
166
|
+
priority: registration.priority ?? 0,
|
|
167
|
+
configKey: registration.configKey,
|
|
168
|
+
options: registration.options ?? {},
|
|
169
|
+
privateOptions: registration.privateOptions &&
|
|
170
|
+
(0, private_values_1.emptiedOptions)(registration.privateOptions),
|
|
171
|
+
authEstablishEndpoints: registration.authEstablishEndpoints ?? [],
|
|
172
|
+
publicEndpoints: registration.publicEndpoints ?? [],
|
|
173
|
+
backendProxies: registration.backendProxies ?? [],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function warnMissingModule(id) {
|
|
177
|
+
(0, cli_1.getProcessUi)().message("warn", `${id} is not installed in the project: its pages, if it has any, are left out`, { detail: "Install the project's modules first: ajs project build" });
|
|
178
|
+
}
|
|
179
|
+
function warnUndeclaredFrontend(id, folder) {
|
|
180
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(folder, FRONTEND_SOURCE_DIR)))
|
|
181
|
+
return;
|
|
182
|
+
(0, cli_1.getProcessUi)().message("warn", `${id} ships ${FRONTEND_SOURCE_DIR}/ but declares no ${exports.FRONTEND_DECLARATION_FIELD} in its package.json: its pages are left out`);
|
|
183
|
+
}
|
|
184
|
+
function registrationOf(id, folder, config, context) {
|
|
185
|
+
const declaration = (0, layers_1.readLayerPackage)(folder)?.[exports.FRONTEND_DECLARATION_FIELD];
|
|
186
|
+
if (typeof declaration !== "string") {
|
|
187
|
+
warnUndeclaredFrontend(id, folder);
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
const { frontendModule } = require((0, node_path_1.join)(folder, declaration));
|
|
191
|
+
if (typeof frontendModule !== "function") {
|
|
192
|
+
throw new cli_1.CliError({
|
|
193
|
+
title: `${id}: ${declaration} exports no frontendModule function`,
|
|
194
|
+
fixes: [`Export frontendModule(config, context) from ${declaration}`],
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return frontendModule(config, context);
|
|
198
|
+
}
|
|
199
|
+
function isServedRenderer(registration) {
|
|
200
|
+
return (registration.renderer.name === RENDERER.name &&
|
|
201
|
+
registration.renderer.version === RENDERER.version);
|
|
202
|
+
}
|
|
203
|
+
function keepHighestPriority(registrations) {
|
|
204
|
+
const byName = new Map();
|
|
205
|
+
for (const registration of registrations) {
|
|
206
|
+
const kept = byName.get(registration.name);
|
|
207
|
+
if (!kept || (kept.priority ?? 0) <= (registration.priority ?? 0))
|
|
208
|
+
byName.set(registration.name, registration);
|
|
209
|
+
}
|
|
210
|
+
return [...byName.values()];
|
|
211
|
+
}
|
|
212
|
+
async function readProjectFrontendModules(options) {
|
|
213
|
+
const projectDir = (0, node_path_1.resolve)(options.projectDir);
|
|
214
|
+
const config = await loadProjectConfig(projectDir, options.environment);
|
|
215
|
+
const context = { dev: !!options.dev };
|
|
216
|
+
const registrations = Object.entries(config.modules).flatMap(([id, module]) => {
|
|
217
|
+
const folder = moduleFolder(projectDir, config.cacheFolder, module.source);
|
|
218
|
+
if (!folder) {
|
|
219
|
+
warnMissingModule(id);
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
const registration = registrationOf(id, folder, module.config, context);
|
|
223
|
+
return registration && isServedRenderer(registration)
|
|
224
|
+
? [registration]
|
|
225
|
+
: [];
|
|
226
|
+
});
|
|
227
|
+
return keepHighestPriority(registrations)
|
|
228
|
+
.sort((left, right) => (right.priority ?? 0) - (left.priority ?? 0))
|
|
229
|
+
.map((registration) => ({
|
|
230
|
+
layer: toLayer(registration),
|
|
231
|
+
version: readShippingVersion(registration.sourcePath),
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ChildProcess, type SpawnOptions } from "node:child_process";
|
|
2
2
|
import { type PathMapper } from "./child-output";
|
|
3
|
+
import { type ProjectModulesOptions } from "./project-modules";
|
|
3
4
|
import { type ManifestSecrets } from "./manifest-secrets";
|
|
4
5
|
import { ResolvedLayer } from "./workspace";
|
|
5
|
-
import { type BuildOrigin, type FrontendSnapshot } from "./snapshot";
|
|
6
6
|
export declare function computeDepsHash(layerPaths: string[], workspacePackage?: string, installInputs?: readonly string[]): string;
|
|
7
7
|
export declare function needsInstall(workspaceDir: string, layerPaths: string[], force: boolean): boolean;
|
|
8
8
|
export declare function saveDepsHash(workspaceDir: string, layerPaths: string[]): void;
|
|
@@ -21,18 +21,15 @@ export interface FramedCommandResult {
|
|
|
21
21
|
}
|
|
22
22
|
export declare function runFramedCommand(command: string, args: string[], options: FramedCommandOptions): Promise<FramedCommandResult>;
|
|
23
23
|
export interface SetupWorkspaceOptions {
|
|
24
|
-
backendUrl: string;
|
|
25
24
|
force: boolean;
|
|
26
25
|
mode: "dev" | "build";
|
|
26
|
+
backendUrl?: string;
|
|
27
27
|
offline?: boolean;
|
|
28
28
|
clientUrl?: string;
|
|
29
29
|
workspaceKey?: string;
|
|
30
30
|
bootstrapSecret?: string;
|
|
31
31
|
workspaceDir?: string;
|
|
32
|
-
|
|
33
|
-
dir: string;
|
|
34
|
-
snapshot: FrontendSnapshot;
|
|
35
|
-
};
|
|
32
|
+
project?: ProjectModulesOptions;
|
|
36
33
|
beforeInstall?: () => Promise<void>;
|
|
37
34
|
}
|
|
38
35
|
export interface SetupWorkspaceResult {
|
|
@@ -41,6 +38,5 @@ export interface SetupWorkspaceResult {
|
|
|
41
38
|
manifestFromCache: boolean;
|
|
42
39
|
manifestFetchedAt?: string;
|
|
43
40
|
manifestSecrets: ManifestSecrets;
|
|
44
|
-
buildOrigin: BuildOrigin;
|
|
45
41
|
}
|
|
46
42
|
export declare function setupWorkspace(opts: SetupWorkspaceOptions): Promise<SetupWorkspaceResult>;
|
package/dist/workspace-setup.js
CHANGED
|
@@ -15,11 +15,11 @@ const cli_1 = require("@antelopejs/core/cli");
|
|
|
15
15
|
const child_output_1 = require("./child-output");
|
|
16
16
|
const layers_1 = require("./layers");
|
|
17
17
|
const manifest_1 = require("./manifest");
|
|
18
|
+
const project_modules_1 = require("./project-modules");
|
|
18
19
|
const manifest_secrets_1 = require("./manifest-secrets");
|
|
19
20
|
const renderer_range_1 = require("./renderer-range");
|
|
20
21
|
const materialize_1 = require("./materialize");
|
|
21
22
|
const workspace_1 = require("./workspace");
|
|
22
|
-
const snapshot_1 = require("./snapshot");
|
|
23
23
|
const cancellation_1 = require("./cancellation");
|
|
24
24
|
const output_1 = require("./output");
|
|
25
25
|
const config_1 = require("./config");
|
|
@@ -215,77 +215,57 @@ async function runFramedCommand(command, args, options) {
|
|
|
215
215
|
await Promise.race([ended, drainTimeout()]);
|
|
216
216
|
return { code, lines: tails.flatMap((tail) => tail.lines()) };
|
|
217
217
|
}
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
return "offline";
|
|
221
|
-
return mode === "build" ? "never" : "fallback";
|
|
222
|
-
}
|
|
223
|
-
async function snapshotLayers({ dir, snapshot }, workspaceDir) {
|
|
224
|
-
const { manifest, takenAt } = snapshot;
|
|
225
|
-
const cacheDir = (0, node_path_1.join)(dir, snapshot_1.SNAPSHOT_LAYERS_DIR);
|
|
226
|
-
(0, layers_1.assertCachedArchivesExist)(manifest.modules, cacheDir, takenAt);
|
|
227
|
-
(0, manifest_1.writeCachedManifest)(workspaceDir, manifest, "drop", takenAt);
|
|
218
|
+
async function projectLayers(project) {
|
|
219
|
+
const modules = await (0, project_modules_1.readProjectFrontendModules)(project);
|
|
228
220
|
return {
|
|
229
|
-
manifest,
|
|
230
221
|
fromCache: false,
|
|
231
|
-
layers:
|
|
222
|
+
layers: modules.map((module) => module.layer),
|
|
223
|
+
secrets: { published: {}, conflicts: [] },
|
|
232
224
|
};
|
|
233
225
|
}
|
|
234
|
-
async function servedLayers(opts, workspaceDir) {
|
|
235
|
-
const {
|
|
236
|
-
const
|
|
237
|
-
|
|
226
|
+
async function servedLayers(opts, backendUrl, workspaceDir) {
|
|
227
|
+
const { offline, clientUrl, bootstrapSecret } = opts;
|
|
228
|
+
const cacheUse = offline ? "offline" : "fallback";
|
|
229
|
+
const { manifest, fromCache, fetchedAt } = await (0, manifest_1.resolveManifest)(workspaceDir, backendUrl, cacheUse, { clientUrl, bootstrapSecret });
|
|
230
|
+
(0, layers_1.assertLayerPathsServed)(manifest.modules, backendUrl, {
|
|
238
231
|
bootstrapSecret,
|
|
239
|
-
|
|
232
|
+
cache: fromCache ? { fetchedAt } : undefined,
|
|
240
233
|
});
|
|
241
|
-
const { manifest, fromCache, fetchedAt } = resolved;
|
|
242
|
-
if (mode === "dev") {
|
|
243
|
-
(0, layers_1.assertLayerPathsServed)(manifest.modules, backendUrl, {
|
|
244
|
-
bootstrapSecret,
|
|
245
|
-
cache: fromCache ? { fetchedAt } : undefined,
|
|
246
|
-
});
|
|
247
|
-
if (fromCache) {
|
|
248
|
-
(0, manifest_1.assertCachedLayerPathsExist)(manifest.modules, fetchedAt);
|
|
249
|
-
}
|
|
250
|
-
return { ...resolved, layers: (0, layers_1.buildLayersFromPaths)(manifest.modules) };
|
|
251
|
-
}
|
|
252
|
-
const cacheDir = (0, node_path_1.join)(workspaceDir, ".layers-cache");
|
|
253
234
|
if (fromCache) {
|
|
254
|
-
|
|
255
|
-
throw new cli_1.CliError({
|
|
256
|
-
title: "No layers archive was downloaded for this workspace",
|
|
257
|
-
fixes: [
|
|
258
|
-
"Run once without --offline, with the backend reachable, first",
|
|
259
|
-
],
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
(0, layers_1.assertCachedArchivesExist)(manifest.modules, cacheDir, fetchedAt);
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
await (0, layers_1.downloadAndExtractLayers)(backendUrl, manifest.pack, cacheDir, force, bootstrapSecret);
|
|
235
|
+
(0, manifest_1.assertCachedLayerPathsExist)(manifest.modules, fetchedAt);
|
|
266
236
|
}
|
|
267
237
|
return {
|
|
268
|
-
|
|
269
|
-
|
|
238
|
+
fromCache,
|
|
239
|
+
fetchedAt,
|
|
240
|
+
layers: (0, layers_1.buildLayersFromPaths)(manifest.modules),
|
|
241
|
+
secrets: (0, manifest_secrets_1.collectManifestSecrets)(manifest.modules),
|
|
270
242
|
};
|
|
271
243
|
}
|
|
272
|
-
|
|
273
|
-
|
|
244
|
+
function workspaceDirFor(opts) {
|
|
245
|
+
if (opts.workspaceDir)
|
|
246
|
+
return (0, workspace_1.ensureExplicitWorkspace)(opts.workspaceDir);
|
|
247
|
+
const backendUrl = requireDevBackend(opts);
|
|
274
248
|
const workspaceKey = opts.workspaceKey ?? (0, config_1.canonicalizeBackendUrl)(backendUrl);
|
|
275
|
-
const workspaceDir =
|
|
276
|
-
? (0, workspace_1.ensureExplicitWorkspace)(opts.workspaceDir)
|
|
277
|
-
: (0, workspace_1.ensureWorkspace)(workspaceKey);
|
|
249
|
+
const workspaceDir = (0, workspace_1.ensureWorkspace)(workspaceKey);
|
|
278
250
|
(0, workspace_1.writeWorkspaceMeta)(workspaceDir, backendUrl, workspaceKey);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
251
|
+
return workspaceDir;
|
|
252
|
+
}
|
|
253
|
+
function requireDevBackend(opts) {
|
|
254
|
+
if (!opts.backendUrl)
|
|
255
|
+
throw new Error("A dev workspace is set up from a backend URL");
|
|
256
|
+
return opts.backendUrl;
|
|
257
|
+
}
|
|
258
|
+
function workspaceSource(opts, workspaceDir) {
|
|
259
|
+
if (opts.mode === "build") {
|
|
260
|
+
if (!opts.project)
|
|
261
|
+
throw new Error("A build reads a project");
|
|
262
|
+
return projectLayers(opts.project);
|
|
263
|
+
}
|
|
264
|
+
return servedLayers(opts, requireDevBackend(opts), workspaceDir);
|
|
265
|
+
}
|
|
266
|
+
async function setupWorkspace(opts) {
|
|
267
|
+
const workspaceDir = workspaceDirFor(opts);
|
|
268
|
+
const { fromCache, fetchedAt, layers, secrets } = await workspaceSource(opts, workspaceDir);
|
|
289
269
|
(0, renderer_range_1.assertLayersSupportRenderer)(layers, { workspaceDir });
|
|
290
270
|
(0, materialize_1.copyStaticTemplates)(workspaceDir);
|
|
291
271
|
(0, materialize_1.writeWorkspacePackageJson)(workspaceDir, layers);
|
|
@@ -293,7 +273,7 @@ async function setupWorkspace(opts) {
|
|
|
293
273
|
(0, materialize_1.writeFrontendModuleRegistry)(workspaceDir, layers);
|
|
294
274
|
(0, materialize_1.writeDmsMainCss)(workspaceDir, layers);
|
|
295
275
|
const layerPaths = layers.map((layer) => layer.path);
|
|
296
|
-
const shouldInstall = needsInstall(workspaceDir, layerPaths, force);
|
|
276
|
+
const shouldInstall = needsInstall(workspaceDir, layerPaths, opts.force);
|
|
297
277
|
if (shouldInstall) {
|
|
298
278
|
await opts.beforeInstall?.();
|
|
299
279
|
await installDeps(workspaceDir, (0, child_output_1.createPathMapper)(workspaceDir, layers));
|
|
@@ -304,7 +284,6 @@ async function setupWorkspace(opts) {
|
|
|
304
284
|
layers,
|
|
305
285
|
manifestFromCache: fromCache,
|
|
306
286
|
manifestFetchedAt: fetchedAt,
|
|
307
|
-
manifestSecrets:
|
|
308
|
-
buildOrigin,
|
|
287
|
+
manifestSecrets: secrets,
|
|
309
288
|
};
|
|
310
289
|
}
|
package/dist/workspace.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export interface FrontendModuleOptions {
|
|
|
4
4
|
}
|
|
5
5
|
export interface ManifestModule {
|
|
6
6
|
name: string;
|
|
7
|
-
archiveName: string;
|
|
8
7
|
renderer?: {
|
|
9
8
|
name: string;
|
|
10
9
|
version: string;
|
|
@@ -21,6 +20,7 @@ export interface ManifestModule {
|
|
|
21
20
|
export interface BackendProxyDeclaration {
|
|
22
21
|
path: string;
|
|
23
22
|
auth?: "session" | "none";
|
|
23
|
+
maxBodyBytes?: number;
|
|
24
24
|
}
|
|
25
25
|
export interface PublicEndpointDeclaration {
|
|
26
26
|
path: string;
|
|
@@ -28,13 +28,11 @@ export interface PublicEndpointDeclaration {
|
|
|
28
28
|
headers?: string[];
|
|
29
29
|
}
|
|
30
30
|
export interface Manifest {
|
|
31
|
-
pack: string;
|
|
32
31
|
modules: ManifestModule[];
|
|
33
32
|
settling?: boolean;
|
|
34
33
|
}
|
|
35
34
|
export interface FrontendManifest {
|
|
36
35
|
version: number;
|
|
37
|
-
archive: string;
|
|
38
36
|
modules: ManifestModule[];
|
|
39
37
|
settling?: boolean;
|
|
40
38
|
}
|
|
@@ -70,6 +68,6 @@ export interface WorkspaceListing {
|
|
|
70
68
|
export declare function listWorkspaces(): WorkspaceListing;
|
|
71
69
|
export declare function directorySize(dir: string): number;
|
|
72
70
|
export declare function writeWorkspaceMeta(workspaceDir: string, backendUrl: string, workspaceKey: string): void;
|
|
73
|
-
export declare const FRONTEND_MANIFEST_VERSION =
|
|
71
|
+
export declare const FRONTEND_MANIFEST_VERSION = 2;
|
|
74
72
|
export declare function bootstrapHeaders(secret?: string): Record<string, string>;
|
|
75
73
|
export declare function isUnauthorized(status: number): boolean;
|
package/dist/workspace.js
CHANGED
|
@@ -131,7 +131,7 @@ function writeWorkspaceMeta(workspaceDir, backendUrl, workspaceKey) {
|
|
|
131
131
|
}
|
|
132
132
|
const BOOTSTRAP_HEADER = "x-dms-bootstrap";
|
|
133
133
|
const UNAUTHORIZED_STATUSES = [401, 403];
|
|
134
|
-
exports.FRONTEND_MANIFEST_VERSION =
|
|
134
|
+
exports.FRONTEND_MANIFEST_VERSION = 2;
|
|
135
135
|
function bootstrapHeaders(secret) {
|
|
136
136
|
return secret ? { [BOOTSTRAP_HEADER]: secret } : {};
|
|
137
137
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms-frontend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Frontend-agnostic loader for AntelopeJS DMS, shipping the Vue 3 renderer (Vite, Inertia, SSR)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelope",
|
|
@@ -76,8 +76,7 @@
|
|
|
76
76
|
"chokidar": "^4.0.3",
|
|
77
77
|
"commander": "^13.1.0",
|
|
78
78
|
"ignore": "^7.0.5",
|
|
79
|
-
"semver": "^7.7.4"
|
|
80
|
-
"unzipper": "^0.12.3"
|
|
79
|
+
"semver": "^7.7.4"
|
|
81
80
|
},
|
|
82
81
|
"devDependencies": {
|
|
83
82
|
"@antelopejs/core": "^1.16.0",
|
|
@@ -87,9 +86,8 @@
|
|
|
87
86
|
"@inertiajs/vue3": "^3.7.1",
|
|
88
87
|
"@types/node": "^24.8.1",
|
|
89
88
|
"@types/semver": "^7.7.1",
|
|
90
|
-
"@types/unzipper": "^0.10.11",
|
|
91
89
|
"@unhead/vue": "^2.1.4",
|
|
92
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
90
|
+
"@vitejs/plugin-vue": "^6.0.6",
|
|
93
91
|
"@vue/server-renderer": "^3.5.25",
|
|
94
92
|
"defu": "^6.1.4",
|
|
95
93
|
"devalue": "^5.9.4",
|
|
@@ -104,7 +102,7 @@
|
|
|
104
102
|
"tsx": "^4.20.6",
|
|
105
103
|
"typescript": "^6.0.3",
|
|
106
104
|
"unplugin-auto-import": "^20.2.0",
|
|
107
|
-
"vite": "^
|
|
105
|
+
"vite": "^8.3.4",
|
|
108
106
|
"vue": "^3.5.25",
|
|
109
107
|
"vue-i18n": "^11.2.7"
|
|
110
108
|
},
|
|
@@ -119,7 +117,7 @@
|
|
|
119
117
|
"@oxc-parser/binding-win32-x64-msvc": "^0.95.0"
|
|
120
118
|
},
|
|
121
119
|
"engines": {
|
|
122
|
-
"node": ">=20.
|
|
120
|
+
"node": ">=20.19.0"
|
|
123
121
|
},
|
|
124
122
|
"packageManager": "pnpm@10.6.5",
|
|
125
123
|
"antelopeJs": {
|
|
@@ -18,8 +18,8 @@ tags: [frontend, cli, renderer, vue, inertia, ssr, deployment, dms-module]
|
|
|
18
18
|
|
|
19
19
|
## Rules that bite
|
|
20
20
|
|
|
21
|
-
- **One workspace per backend URL**, under `~/.antelopejs/dms-frontend/<sha256>/`. `dev` without `-b` keys its own on the project directory instead
|
|
22
|
-
- **Secrets.** `
|
|
21
|
+
- **One dev workspace per backend URL**, under `~/.antelopejs/dms-frontend/<sha256>/`. `dev` without `-b` keys its own on the project directory instead. A production build is different: `build` reads the project itself (`antelope.config.ts` with `-e production`, each installed module's `dmsFrontend` function), needs no backend, and writes `<project>/.dms-frontend`, which `start` serves from the project root.
|
|
22
|
+
- **Secrets.** `start` needs `DMS_SESSION_SECRET` (32+ characters); `dev` generates one only when it is unset. `build` needs no secret. `start` reads `DMS_HTML_RENDER_SECRET` and `DMS_OAUTH_RELAY_SECRET` from its environment and warns when they are unset, and computes the modules' public options from the project's configuration with its own variables, which must match the backend's.
|
|
23
23
|
- **`.env`** is read from the current directory only (shell, then `.env.local`, then `.env`). A `DMS_API_BASE_URL` in `.env` turns off backend discovery for `dev`. `clean` never reads it.
|
|
24
24
|
- **`prepare` never fails** without `--strict`: it warns and exits 0. Use `--strict` where the types are required.
|
|
25
25
|
- **Exit codes:** 0 success, 1 failure, 2 usage error, 130 Ctrl+C.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// How the client bundle is cut into files, and what the build says about it.
|
|
2
|
+
//
|
|
3
|
+
// Kept beside vite.config.ts rather than inside it so the options can be
|
|
4
|
+
// exercised on their own, the way head-order.mjs and client-server-guard.mjs are.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rolldown's `output.codeSplitting` for the client build.
|
|
8
|
+
*
|
|
9
|
+
* By default Rolldown gives every set of dynamic imports that shares a module
|
|
10
|
+
* its own common chunk. The DMS's pages are hundreds of dynamic imports (a
|
|
11
|
+
* module's components and pages are `import.meta.glob`ed), so the code the
|
|
12
|
+
* entry also needs lands in about sixty small common chunks the entry imports
|
|
13
|
+
* before anything runs: one request each, 55 to 60 more files on every page
|
|
14
|
+
* than the single entry file Rollup made, for no byte saved.
|
|
15
|
+
*
|
|
16
|
+
* The `$initial` tag is every module the entry imports statically, directly or
|
|
17
|
+
* not. One group over it puts them in the entry file again, and leaves every
|
|
18
|
+
* lazy chunk to Rolldown's own splitting, which a page only loads when it needs
|
|
19
|
+
* it: what an anonymous page loads is still what that page's own routes pull.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately not here: groups over what the lazy chunks share (the UI
|
|
22
|
+
* libraries, a chunk per module). They cut a signed-in page's files by a
|
|
23
|
+
* quarter to four fifths, but every group is loaded whole by each page that
|
|
24
|
+
* uses any of it, and the anonymous pages pay 70 to 115 KB of Brotli for code
|
|
25
|
+
* only the signed-in shell uses.
|
|
26
|
+
*/
|
|
27
|
+
export const clientCodeSplitting = {
|
|
28
|
+
groups: [{ name: "entry", tags: ["$initial"] }],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const MODULE_ENTRY = /[\\/]dms\.frontend\.[cm]?[jt]sx?$/;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Rolldown's `onLog` for every build: drops the
|
|
35
|
+
* INEFFECTIVE_DYNAMIC_IMPORT notices whose dynamic importer is a module's
|
|
36
|
+
* `dms.frontend.ts`.
|
|
37
|
+
*
|
|
38
|
+
* That file `import.meta.glob`s every component of the module so a page can
|
|
39
|
+
* resolve one by name. A few of them (StatusPill, MonoCell, IdentityCell: the
|
|
40
|
+
* table cell renderers) are also imported statically by the entry, which is
|
|
41
|
+
* what a cell renderer needs at hydration. The glob entry then resolves to the
|
|
42
|
+
* chunk already loaded: nothing is duplicated and nothing was meant to be
|
|
43
|
+
* lazy, and nobody can act on the notice. A `import()` a module author
|
|
44
|
+
* writes by hand for a file that is also imported statically is the same
|
|
45
|
+
* mistake with a different cause (the lazy load never happens), and still
|
|
46
|
+
* warns.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} level
|
|
49
|
+
* @param {{ code?: string, ids?: string[] }} log
|
|
50
|
+
* @param {(level: string, log: object) => void} defaultHandler
|
|
51
|
+
*/
|
|
52
|
+
export function onBuildLog(level, log, defaultHandler) {
|
|
53
|
+
if (
|
|
54
|
+
log.code === "INEFFECTIVE_DYNAMIC_IMPORT" &&
|
|
55
|
+
log.ids?.length &&
|
|
56
|
+
MODULE_ENTRY.test(log.ids[0])
|
|
57
|
+
) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
defaultHandler(level, log);
|
|
61
|
+
}
|
|
@@ -6,6 +6,7 @@ import { appConfigs } from "./app-configs.generated";
|
|
|
6
6
|
import moduleRegistry from "./generated-frontend-modules.json";
|
|
7
7
|
import { loadEmailLocaleMessages } from "./email-locales";
|
|
8
8
|
import { stripSsrMarkers } from "./email-markup";
|
|
9
|
+
import { parsePublicOptions } from "./public-options";
|
|
9
10
|
|
|
10
11
|
interface EmailModule {
|
|
11
12
|
default: Component;
|
|
@@ -20,6 +21,8 @@ interface EmailRenderOptions {
|
|
|
20
21
|
locale?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
const runtimeOptions = parsePublicOptions(process.env.DMS_PUBLIC_OPTIONS);
|
|
25
|
+
|
|
23
26
|
const registries = import.meta.glob<EmailRegistry>(
|
|
24
27
|
"/frontend-modules/*/dms.email.ts",
|
|
25
28
|
{ eager: true },
|
|
@@ -157,7 +160,9 @@ export async function renderEmail(
|
|
|
157
160
|
app.provide("dmsEmailAppConfig", defu({}, ...emailAppConfigs));
|
|
158
161
|
const publicConfig = defu(
|
|
159
162
|
{},
|
|
160
|
-
...moduleRegistry.modules.map(
|
|
163
|
+
...moduleRegistry.modules.map(
|
|
164
|
+
(module) => runtimeOptions?.[module.id] ?? module.options,
|
|
165
|
+
),
|
|
161
166
|
);
|
|
162
167
|
app.provide("dmsEmailRuntimeConfig", {
|
|
163
168
|
public: defu(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isAbsolute, posix, resolve } from "node:path";
|
|
2
2
|
import { normalizePath } from "vite";
|
|
3
|
-
import type { DmsFrontendBuildSetup } from "./frontend-build";
|
|
3
|
+
import type { DmsFrontendBuildSetup } from "./frontend-build.ts";
|
|
4
4
|
|
|
5
5
|
/** A module's `dms.frontend.build.ts`, as the generated loader imports it. */
|
|
6
6
|
export interface FrontendBuildDeclaration {
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
mergeLayerAppConfigs,
|
|
56
56
|
withAppConfigChanges,
|
|
57
57
|
} from "./layer-app-config";
|
|
58
|
+
import type { DmsPublicOptionsById } from "./public-options";
|
|
58
59
|
|
|
59
60
|
export interface DmsPageRoute {
|
|
60
61
|
displayName?: string;
|
|
@@ -457,11 +458,34 @@ export interface DmsFrontendModule {
|
|
|
457
458
|
setup(sdk: DmsFrontendSdk): void | Promise<void>;
|
|
458
459
|
}
|
|
459
460
|
export interface DmsFrontendModuleRegistration {
|
|
461
|
+
/** The module's id in the generated module registry. */
|
|
462
|
+
id?: string;
|
|
460
463
|
/** The module's manifest name, which collision warnings name it by. */
|
|
461
464
|
name?: string;
|
|
462
465
|
module: DmsFrontendModule;
|
|
463
466
|
options: DmsModuleOptions;
|
|
464
467
|
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* The registrations with the public options the server runs with in place of
|
|
471
|
+
* the build's: `ajs dms start` computes them from the project's configuration
|
|
472
|
+
* and the deployment's variables. Unset, the build's options stand.
|
|
473
|
+
*/
|
|
474
|
+
export function withPublicOptions(
|
|
475
|
+
registrations: DmsFrontendModuleRegistration[],
|
|
476
|
+
byId: DmsPublicOptionsById | undefined,
|
|
477
|
+
): DmsFrontendModuleRegistration[] {
|
|
478
|
+
if (!byId) return registrations;
|
|
479
|
+
return registrations.map((registration) => {
|
|
480
|
+
const options = registration.id ? byId[registration.id] : undefined;
|
|
481
|
+
return options
|
|
482
|
+
? {
|
|
483
|
+
...registration,
|
|
484
|
+
options: { ...registration.options, public: options },
|
|
485
|
+
}
|
|
486
|
+
: registration;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
465
489
|
/** A layer's `app/app.config.ts`, as `app-configs.generated.ts` lists them. */
|
|
466
490
|
export interface DmsLayerAppConfig {
|
|
467
491
|
/** The id of the frontend module the layer belongs to. */
|