@antelopejs/dms-frontend 0.7.1 → 0.9.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.
Files changed (98) hide show
  1. package/README.md +4 -4
  2. package/dist/commands/build-action.d.ts +8 -5
  3. package/dist/commands/build-action.js +16 -41
  4. package/dist/commands/build.js +9 -15
  5. package/dist/commands/dev-action.d.ts +2 -0
  6. package/dist/commands/dev-action.js +14 -3
  7. package/dist/commands/dev.js +3 -1
  8. package/dist/commands/start-action.d.ts +2 -0
  9. package/dist/commands/start-action.js +65 -47
  10. package/dist/commands/start.js +8 -6
  11. package/dist/common.d.ts +1 -0
  12. package/dist/common.js +1 -0
  13. package/dist/config.d.ts +2 -0
  14. package/dist/config.js +9 -0
  15. package/dist/derived-outputs.d.ts +1 -5
  16. package/dist/derived-outputs.js +48 -35
  17. package/dist/dev-modules.d.ts +5 -0
  18. package/dist/dev-modules.js +88 -0
  19. package/dist/entry-weight.d.ts +24 -0
  20. package/dist/entry-weight.js +86 -0
  21. package/dist/fs-sync.d.ts +1 -0
  22. package/dist/fs-sync.js +6 -0
  23. package/dist/help.js +12 -4
  24. package/dist/index.js +4 -6
  25. package/dist/layer-watch.js +7 -2
  26. package/dist/layers.d.ts +2 -3
  27. package/dist/layers.js +1 -95
  28. package/dist/locale-files.d.ts +6 -0
  29. package/dist/locale-files.js +75 -0
  30. package/dist/manifest.d.ts +8 -8
  31. package/dist/manifest.js +17 -39
  32. package/dist/materialize.js +6 -0
  33. package/dist/module-set.d.ts +14 -12
  34. package/dist/module-set.js +39 -31
  35. package/dist/private-values.d.ts +3 -2
  36. package/dist/private-values.js +16 -5
  37. package/dist/project-modules-child.d.ts +13 -0
  38. package/dist/project-modules-child.js +26 -0
  39. package/dist/project-modules.d.ts +47 -0
  40. package/dist/project-modules.js +268 -0
  41. package/dist/workspace-setup.d.ts +3 -7
  42. package/dist/workspace-setup.js +79 -62
  43. package/dist/workspace.d.ts +8 -4
  44. package/dist/workspace.js +1 -1
  45. package/package.json +13 -8
  46. package/skills/dms-frontend/SKILL.md +2 -2
  47. package/templates/vue/app-runtime.ts +8 -1
  48. package/templates/vue/client-chunking.mjs +61 -0
  49. package/templates/vue/email-renderer.ts +6 -1
  50. package/templates/vue/entry-weight.mjs +132 -0
  51. package/templates/vue/frontend-build-loader.ts +1 -1
  52. package/templates/vue/frontend-module.ts +168 -1
  53. package/templates/vue/gitignore +1 -0
  54. package/templates/vue/main.ts +32 -2
  55. package/templates/vue/network-error.ts +3 -1
  56. package/templates/vue/package.json +3 -3
  57. package/templates/vue/patches/@vueuse__core@14.4.0.patch +24 -0
  58. package/templates/vue/pnpm-workspace.yaml +5 -0
  59. package/templates/vue/prepare-types.mjs +2 -7
  60. package/templates/vue/public-options.ts +19 -0
  61. package/templates/vue/server/auth/backend.mjs +20 -3
  62. package/templates/vue/server/auth/client-ip.mjs +64 -22
  63. package/templates/vue/server/auth/session.mjs +4 -11
  64. package/templates/vue/server/backend-proxies.mjs +13 -2
  65. package/templates/vue/server/backend-response.mjs +2 -13
  66. package/templates/vue/server/cli-report.mjs +42 -18
  67. package/templates/vue/server/client-manifest.mjs +34 -58
  68. package/templates/vue/server/dev-private-files.mjs +1 -1
  69. package/templates/vue/server/dev-styles.mjs +16 -5
  70. package/templates/vue/server/homepage.mjs +4 -1
  71. package/templates/vue/server/in-place-render.mjs +110 -0
  72. package/templates/vue/server/module-event.mjs +23 -8
  73. package/templates/vue/server/module-server.mjs +4 -4
  74. package/templates/vue/server/module-testing.mjs +5 -3
  75. package/templates/vue/server/page-props.mjs +10 -0
  76. package/templates/vue/server/proxy-compression.mjs +15 -8
  77. package/templates/vue/server/proxy-headers.mjs +12 -1
  78. package/templates/vue/server/proxy-redirects.mjs +66 -0
  79. package/templates/vue/server/proxy-transfers.mjs +110 -0
  80. package/templates/vue/server/public-endpoints.mjs +2 -2
  81. package/templates/vue/server/public-options.mjs +49 -0
  82. package/templates/vue/server/request-routing.mjs +20 -0
  83. package/templates/vue/server/web-body.mjs +24 -0
  84. package/templates/vue/server-module.ts +19 -7
  85. package/templates/vue/server.mjs +98 -78
  86. package/templates/vue/sfc-global-types.mjs +119 -0
  87. package/templates/vue/ssr-renderer.ts +27 -1
  88. package/templates/vue/ui-kbd.mjs +45 -0
  89. package/templates/vue/vite.config.ts +104 -47
  90. package/templates/vue/vite.email.config.ts +6 -3
  91. package/templates/vue/vite.server.config.ts +5 -2
  92. package/dist/commands/snapshot-action.d.ts +0 -6
  93. package/dist/commands/snapshot-action.js +0 -53
  94. package/dist/commands/snapshot.d.ts +0 -2
  95. package/dist/commands/snapshot.js +0 -55
  96. package/dist/snapshot.d.ts +0 -25
  97. package/dist/snapshot.js +0 -90
  98. package/templates/vue/server/backend-wake.mjs +0 -129
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DERIVED_OUTPUTS = exports.InvalidLocaleFileError = void 0;
3
+ exports.DERIVED_OUTPUTS = void 0;
4
4
  exports.createFrontendModuleRegistry = createFrontendModuleRegistry;
5
5
  exports.affectsLocaleMessages = affectsLocaleMessages;
6
6
  exports.writeLocaleMessages = writeLocaleMessages;
@@ -8,9 +8,11 @@ const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = require("node:path");
9
9
  const config_1 = require("./config");
10
10
  const app_configs_1 = require("./app-configs");
11
+ const locale_files_1 = require("./locale-files");
11
12
  const asset_roots_1 = require("./asset-roots");
12
13
  const layer_aliases_1 = require("./layer-aliases");
13
14
  const layers_1 = require("./layers");
15
+ const project_modules_1 = require("./project-modules");
14
16
  const fs_sync_1 = require("./fs-sync");
15
17
  const GENERATED_MODULE_ID = /^[A-Za-z0-9_-]+$/;
16
18
  function validateFrontendModuleRegistry(registry, workspaceDir) {
@@ -42,6 +44,7 @@ function createFrontendModuleRegistry(workspaceDir, layers) {
42
44
  id: (0, layers_1.getLayerSafeName)(layer),
43
45
  name: layer.name ?? layer.packageName,
44
46
  packageName: layer.packageName,
47
+ version: (0, project_modules_1.readShippingVersion)(layer.sourcePath ?? layer.path),
45
48
  root: (0, layers_1.getLayerWorkspacePath)(workspaceDir, layer),
46
49
  priority: layer.priority ?? 0,
47
50
  entry: (0, node_fs_1.existsSync)(sourceEntry) ? config_1.FRONTEND_MODULE_ENTRY : undefined,
@@ -72,7 +75,7 @@ function writeFrontendModuleLoader(workspaceDir, registry) {
72
75
  .map((module, index) => `import module${index} from "@frontend/${module.id}/${module.entry}";`)
73
76
  .join("\n");
74
77
  const registrations = modules
75
- .map((module, index) => `{ name: ${JSON.stringify(module.name)}, module: module${index}, options: { public: ${JSON.stringify(module.options)} } }`)
78
+ .map((module, index) => `{ id: ${JSON.stringify(module.id)}, name: ${JSON.stringify(module.name)}, module: module${index}, options: { public: ${JSON.stringify(module.options)} } }`)
76
79
  .join(", ");
77
80
  const content = `${imports}\n\nimport type { DmsFrontendModuleRegistration } from "./frontend-module";\n\nexport const frontendModules: DmsFrontendModuleRegistration[] = [${registrations}];\n`;
78
81
  return (0, fs_sync_1.applyContent)(content, (0, node_path_1.join)(workspaceDir, "frontend-modules.generated.ts"));
@@ -85,7 +88,7 @@ function writeFrontendBuildLoader(workspaceDir, registry) {
85
88
  const declarations = modules
86
89
  .map((module, index) => `{ id: ${JSON.stringify(module.id)}, setup: build${index} }`)
87
90
  .join(", ");
88
- const content = `${imports}\n\nimport type { FrontendBuildDeclaration } from "./frontend-build-loader";\n\nexport const frontendBuilds: FrontendBuildDeclaration[] = [${declarations}];\n`;
91
+ const content = `${imports}\n\nimport type { FrontendBuildDeclaration } from "./frontend-build-loader.ts";\n\nexport const frontendBuilds: FrontendBuildDeclaration[] = [${declarations}];\n`;
89
92
  return (0, fs_sync_1.applyContent)(content, (0, node_path_1.join)(workspaceDir, "frontend-builds.generated.ts"));
90
93
  }
91
94
  function writeServerModuleLoader(workspaceDir, registry) {
@@ -148,24 +151,6 @@ function mergeLocaleMessages(source, existing) {
148
151
  return merged;
149
152
  }
150
153
  const LOCALE_DIRECTORY = "i18n/locales";
151
- class InvalidLocaleFileError extends Error {
152
- constructor(path, reason) {
153
- super(`Invalid locale file ${path}: ${reason}`);
154
- this.path = path;
155
- this.reason = reason;
156
- this.name = "InvalidLocaleFileError";
157
- }
158
- }
159
- exports.InvalidLocaleFileError = InvalidLocaleFileError;
160
- function readLocaleFile(path) {
161
- try {
162
- return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
163
- }
164
- catch (error) {
165
- const reason = error instanceof Error ? error.message : String(error);
166
- throw new InvalidLocaleFileError(path, reason);
167
- }
168
- }
169
154
  function readLocaleMessages(assets, registry) {
170
155
  const roots = new Map(registry.modules.map((module) => [module.id, module.root]));
171
156
  const messages = {};
@@ -174,7 +159,7 @@ function readLocaleMessages(assets, registry) {
174
159
  const root = roots.get(asset.moduleId);
175
160
  if (!locale || !root)
176
161
  continue;
177
- const value = readLocaleFile((0, node_path_1.join)(root, asset.relativePath));
162
+ const value = (0, locale_files_1.readLocaleFile)((0, node_path_1.join)(root, asset.relativePath));
178
163
  messages[locale] = mergeLocaleMessages(value, messages[locale] ?? {});
179
164
  }
180
165
  return messages;
@@ -182,13 +167,28 @@ function readLocaleMessages(assets, registry) {
182
167
  function affectsLocaleMessages(relativePath) {
183
168
  return (0, asset_roots_1.affectsAssetDirectory)(LOCALE_DIRECTORY, relativePath);
184
169
  }
170
+ const FALLBACK_LOCALE = "en";
171
+ const RENDERER_TRANSLATED_KEYS = ["error.network"];
172
+ function withoutMessage(messages, key) {
173
+ const [head, ...rest] = key.split(".");
174
+ if (!(head in messages))
175
+ return messages;
176
+ const { [head]: value, ...others } = messages;
177
+ if (!rest.length)
178
+ return others;
179
+ if (!isRecord(value))
180
+ return messages;
181
+ const remaining = withoutMessage(value, rest.join("."));
182
+ return Object.keys(remaining).length
183
+ ? { ...others, [head]: remaining }
184
+ : others;
185
+ }
185
186
  function localeModuleSource(supportedLocales) {
186
- const loaders = supportedLocales
187
- .filter((locale) => locale !== "en")
188
- .map((locale) => ` ${JSON.stringify(locale)}: () => import(${JSON.stringify(`./locales.generated/${locale}`)}),`);
189
- return `import defaultMessages from "./locales.generated/en";
190
-
191
- type LocaleMessages = Record<string, unknown>;
187
+ const loaders = supportedLocales.map((locale) => ` ${JSON.stringify(locale)}: () => import(${JSON.stringify(`./locales.generated/${locale}`)}),`);
188
+ const fallback = supportedLocales.includes(FALLBACK_LOCALE)
189
+ ? FALLBACK_LOCALE
190
+ : supportedLocales[0];
191
+ return `type LocaleMessages = Record<string, unknown>;
192
192
  type LocaleLoader = () => Promise<{ default: LocaleMessages }>;
193
193
  interface LocaleMessagesTarget {
194
194
  setLocaleMessage(locale: string, messages: LocaleMessages): void;
@@ -200,13 +200,13 @@ interface LocaleState {
200
200
  }
201
201
 
202
202
  export const supportedLocales = ${JSON.stringify(supportedLocales)};
203
+ const fallbackLocale: string | undefined = ${JSON.stringify(fallback)};
203
204
  const localeLoaders: Record<string, LocaleLoader> = {
204
- "en": async () => ({ default: defaultMessages }),
205
205
  ${loaders.join("\n")}
206
206
  };
207
207
  const shared: LocaleState | undefined = import.meta.hot?.data.state;
208
208
  const state: LocaleState = shared ?? {
209
- messages: { en: defaultMessages },
209
+ messages: {},
210
210
  targets: new Set(),
211
211
  version: 0,
212
212
  };
@@ -216,11 +216,20 @@ async function readLocaleMessages(locale: string): Promise<LocaleMessages> {
216
216
  return (await localeLoaders[locale]()).default;
217
217
  }
218
218
 
219
- export async function loadLocaleMessages(locale: string): Promise<LocaleMessages> {
219
+ /**
220
+ * The catalog that serves \`locale\`: its own when the application has one,
221
+ * else the fallback locale's.
222
+ */
223
+ export function catalogLocale(locale: string): string | undefined {
220
224
  const normalized = locale.slice(0, 2);
221
- if (!localeLoaders[normalized]) return localeMessages.en;
222
- localeMessages[normalized] ??= await readLocaleMessages(normalized);
223
- return localeMessages[normalized];
225
+ return normalized in localeLoaders ? normalized : fallbackLocale;
226
+ }
227
+
228
+ export async function loadLocaleMessages(locale: string): Promise<LocaleMessages> {
229
+ const catalog = catalogLocale(locale);
230
+ if (!catalog) return {};
231
+ localeMessages[catalog] ??= await readLocaleMessages(catalog);
232
+ return localeMessages[catalog];
224
233
  }
225
234
 
226
235
  /** Development only: applies every regenerated catalog to \`target\`. */
@@ -262,10 +271,14 @@ function writeLocaleMessages(workspaceDir, registry) {
262
271
  changed = (0, fs_sync_1.applyContent)(content, path) || changed;
263
272
  };
264
273
  (0, node_fs_1.mkdirSync)(outputDir, { recursive: true });
274
+ const fallback = RENDERER_TRANSLATED_KEYS.reduce(withoutMessage, messages[FALLBACK_LOCALE] ?? {});
265
275
  for (const [locale, value] of Object.entries(messages)) {
266
276
  const catalog = JSON.stringify(value);
277
+ const complete = locale === FALLBACK_LOCALE
278
+ ? catalog
279
+ : JSON.stringify(mergeLocaleMessages(fallback, value));
267
280
  apply(catalog, (0, node_path_1.join)(outputDir, `${locale}.json`));
268
- apply(`const messages = ${catalog};\nexport default messages;\n`, (0, node_path_1.join)(outputDir, `${locale}.ts`));
281
+ apply(`const messages = ${complete};\nexport default messages;\n`, (0, node_path_1.join)(outputDir, `${locale}.ts`));
269
282
  outputs.add(`${locale}.json`).add(`${locale}.ts`);
270
283
  }
271
284
  const supportedLocales = Object.keys(messages);
@@ -0,0 +1,5 @@
1
+ import type { ProjectFrontendModule, ProjectModulesOptions } from "./project-modules";
2
+ import type { Manifest, ResolvedLayer } from "./workspace";
3
+ export declare function readProjectModulesFresh(options: ProjectModulesOptions): Promise<ProjectFrontendModule[]>;
4
+ export declare function warnModuleDrift(layers: readonly ResolvedLayer[], manifest: Manifest): void;
5
+ export declare function withBackendValues(modules: readonly ProjectFrontendModule[], manifest: Manifest): ResolvedLayer[];
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readProjectModulesFresh = readProjectModulesFresh;
4
+ exports.warnModuleDrift = warnModuleDrift;
5
+ exports.withBackendValues = withBackendValues;
6
+ const node_child_process_1 = require("node:child_process");
7
+ const cli_1 = require("@antelopejs/core/cli");
8
+ const private_values_1 = require("./private-values");
9
+ function readProjectModulesFresh(options) {
10
+ return new Promise((resolve, reject) => {
11
+ const child = (0, node_child_process_1.fork)(require.resolve("./project-modules-child"), [], {
12
+ stdio: ["ignore", "inherit", "inherit", "ipc"],
13
+ });
14
+ let reply;
15
+ child.once("message", (message) => {
16
+ reply = message;
17
+ });
18
+ child.once("error", reject);
19
+ child.once("exit", (code, signal) => {
20
+ if (!reply) {
21
+ reject(new Error(`Reading the project's modules stopped (${signal ?? `exit code ${code}`})`));
22
+ return;
23
+ }
24
+ const ui = (0, cli_1.getProcessUi)();
25
+ for (const message of reply.messages)
26
+ ui.message(...message);
27
+ if ("modules" in reply)
28
+ resolve(reply.modules);
29
+ else if ("problem" in reply)
30
+ reject(new cli_1.CliError(reply.problem));
31
+ else
32
+ reject(new Error(reply.failure));
33
+ });
34
+ child.send(options);
35
+ });
36
+ }
37
+ const URL_BOUNDARY = "(?=[/?#]|$)";
38
+ function escapeRegExp(value) {
39
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
40
+ }
41
+ function rewriteOrigins(options, rewrites) {
42
+ if (rewrites.length === 0)
43
+ return options;
44
+ const replacements = new Map(rewrites.map(({ from, to }) => [from, to]));
45
+ const alternation = [...replacements.keys()]
46
+ .sort((left, right) => right.length - left.length)
47
+ .map(escapeRegExp)
48
+ .join("|");
49
+ const pattern = new RegExp(`(?:${alternation})${URL_BOUNDARY}`, "g");
50
+ const rewrite = (value) => {
51
+ if (typeof value === "string")
52
+ return value.replace(pattern, (match) => replacements.get(match) ?? match);
53
+ if (Array.isArray(value))
54
+ return value.map(rewrite);
55
+ if (value !== null && typeof value === "object")
56
+ return rewriteObject(value);
57
+ return value;
58
+ };
59
+ const rewriteObject = (value) => Object.fromEntries(Object.entries(value).map(([key, item]) => [key, rewrite(item)]));
60
+ return rewriteObject(options);
61
+ }
62
+ function warnModuleDrift(layers, manifest) {
63
+ const served = new Set(layers.map((layer) => layer.name ?? ""));
64
+ const registered = new Set(manifest.modules.map((module) => module.name));
65
+ const ui = (0, cli_1.getProcessUi)();
66
+ for (const name of served) {
67
+ if (!registered.has(name))
68
+ ui.message("warn", `${name} is served from the project, but the backend does not register it`, {
69
+ detail: "Its frontendModule returns a registration the running backend did not add: check the module's configuration and startup",
70
+ });
71
+ }
72
+ for (const name of registered) {
73
+ if (!served.has(name))
74
+ ui.message("warn", `${name} is registered by the backend, but not in the project: its pages are left out`, {
75
+ detail: `A build leaves it out too. Declare its frontendModule (dmsFrontend in its package.json), or add the module to antelope.config.ts`,
76
+ });
77
+ }
78
+ }
79
+ function withBackendValues(modules, manifest) {
80
+ const registered = new Map(manifest.modules.map((module) => [module.name, module]));
81
+ return modules.map(({ layer }) => ({
82
+ ...layer,
83
+ options: layer.options &&
84
+ rewriteOrigins(layer.options, manifest.originRewrites ?? []),
85
+ privateOptions: layer.privateOptions &&
86
+ (0, private_values_1.filledOptions)(layer.privateOptions, registered.get(layer.name ?? "")?.privateOptions),
87
+ }));
88
+ }
@@ -0,0 +1,24 @@
1
+ export declare const ENTRY_WEIGHT_VARIABLE = "DMS_ENTRY_WEIGHT_WARNING";
2
+ export declare const DEFAULT_ENTRY_WEIGHT_WARNING_KB = 50;
3
+ export interface EntryShare {
4
+ owner: string;
5
+ bytes: number;
6
+ brotli: number;
7
+ }
8
+ export interface EntryWeight {
9
+ bytes: number;
10
+ brotli: number;
11
+ shares: EntryShare[];
12
+ }
13
+ export declare function readEntryWeight(workspaceDir: string): EntryWeight | undefined;
14
+ export declare function entryWeightThreshold(env: NodeJS.ProcessEnv): number;
15
+ export declare function heavyEntryModules(weight: EntryWeight, threshold: number): EntryShare[];
16
+ export declare function describeEntryShares(weight: EntryWeight): string[];
17
+ interface ReportUi {
18
+ message(level: "info" | "warn", text: string, options?: {
19
+ detail?: string;
20
+ details?: string[];
21
+ }): void;
22
+ }
23
+ export declare function reportEntryWeight(ui: ReportUi, workspaceDir: string, env: NodeJS.ProcessEnv): void;
24
+ export {};
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB = exports.ENTRY_WEIGHT_VARIABLE = void 0;
4
+ exports.readEntryWeight = readEntryWeight;
5
+ exports.entryWeightThreshold = entryWeightThreshold;
6
+ exports.heavyEntryModules = heavyEntryModules;
7
+ exports.describeEntryShares = describeEntryShares;
8
+ exports.reportEntryWeight = reportEntryWeight;
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const node_zlib_1 = require("node:zlib");
12
+ const cli_1 = require("@antelopejs/core/cli");
13
+ const output_1 = require("./output");
14
+ exports.ENTRY_WEIGHT_VARIABLE = "DMS_ENTRY_WEIGHT_WARNING";
15
+ exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB = 50;
16
+ const ENTRY_WEIGHT_FILE = (0, node_path_1.join)("dist", "client", ".vite", "entry-weight.json");
17
+ const SHARED_OWNER = "shared";
18
+ const TRANSLATIONS_OWNER = "translations";
19
+ const NON_MODULE_OWNERS = new Set([SHARED_OWNER, TRANSLATIONS_OWNER]);
20
+ const BYTES_PER_KB = 1024;
21
+ function brotliSize(file) {
22
+ const compressed = `${file}.br`;
23
+ if ((0, node_fs_1.existsSync)(compressed))
24
+ return (0, node_fs_1.statSync)(compressed).size;
25
+ return (0, node_zlib_1.brotliCompressSync)((0, node_fs_1.readFileSync)(file), {
26
+ params: { [node_zlib_1.constants.BROTLI_PARAM_QUALITY]: node_zlib_1.constants.BROTLI_MAX_QUALITY },
27
+ }).length;
28
+ }
29
+ function readEntryWeight(workspaceDir) {
30
+ const path = (0, node_path_1.join)(workspaceDir, ENTRY_WEIGHT_FILE);
31
+ if (!(0, node_fs_1.existsSync)(path))
32
+ return undefined;
33
+ const recorded = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
34
+ const brotli = recorded.files.reduce((sum, file) => sum + brotliSize((0, node_path_1.join)(workspaceDir, "dist", "client", file)), 0);
35
+ const ratio = recorded.bytes ? brotli / recorded.bytes : 0;
36
+ const shares = Object.entries(recorded.owners)
37
+ .map(([owner, bytes]) => ({
38
+ owner,
39
+ bytes,
40
+ brotli: Math.round(bytes * ratio),
41
+ }))
42
+ .sort((left, right) => right.bytes - left.bytes);
43
+ return { bytes: recorded.bytes, brotli, shares };
44
+ }
45
+ function entryWeightThreshold(env) {
46
+ const value = env[exports.ENTRY_WEIGHT_VARIABLE]?.trim();
47
+ if (!value)
48
+ return exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB * BYTES_PER_KB;
49
+ const kilobytes = Number(value);
50
+ if (!Number.isFinite(kilobytes) || kilobytes < 0)
51
+ throw new cli_1.CliError({
52
+ title: `${exports.ENTRY_WEIGHT_VARIABLE} is not a size`,
53
+ reason: `It is ${JSON.stringify(value)}; it takes a number of KB of Brotli.`,
54
+ fixes: [
55
+ `Set ${exports.ENTRY_WEIGHT_VARIABLE}=${exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB}, or 0 to turn the warning off`,
56
+ ],
57
+ });
58
+ return kilobytes * BYTES_PER_KB;
59
+ }
60
+ function heavyEntryModules(weight, threshold) {
61
+ if (threshold <= 0)
62
+ return [];
63
+ return weight.shares.filter((share) => !NON_MODULE_OWNERS.has(share.owner) && share.brotli > threshold);
64
+ }
65
+ function describeEntryShares(weight) {
66
+ return weight.shares.map((share) => `${share.owner} ${(0, output_1.formatSize)(share.brotli)} (${(0, output_1.formatSize)(share.bytes)})`);
67
+ }
68
+ function reportEntryWeight(ui, workspaceDir, env) {
69
+ const threshold = entryWeightThreshold(env);
70
+ const weight = readEntryWeight(workspaceDir);
71
+ if (!weight)
72
+ return;
73
+ ui.message("info", `Client entry ${(0, output_1.formatSize)(weight.brotli)} Brotli (${(0, output_1.formatSize)(weight.bytes)}), loaded first by every page`, { details: describeEntryShares(weight) });
74
+ const heavy = heavyEntryModules(weight, threshold);
75
+ heavy.forEach((share, index) => {
76
+ const isLast = index === heavy.length - 1;
77
+ ui.message("warn", `${share.owner} adds ${(0, output_1.formatSize)(share.brotli)} Brotli to the client entry`, {
78
+ details: isLast
79
+ ? [
80
+ "What a module's dms.frontend.ts and its plugins import is loaded by every page: render a component they only show on some pages by its registered name or with defineAsyncComponent",
81
+ `The threshold is ${(0, output_1.formatSize)(threshold)} (${exports.ENTRY_WEIGHT_VARIABLE}, in KB; 0 turns the warning off)`,
82
+ ]
83
+ : [],
84
+ });
85
+ });
86
+ }
package/dist/fs-sync.d.ts CHANGED
@@ -2,6 +2,7 @@ export declare function stripExtendedLengthPrefix(p: string): string;
2
2
  export declare function toPosixPath(path: string, separator?: string): string;
3
3
  export declare function isBlocklistedCopyPath(src: string, srcPath: string): boolean;
4
4
  export declare function sanitizedPackageContent(raw: string): string;
5
+ export declare function sanitizedBuildDeclaration(raw: string): string;
5
6
  export declare function filesIdentical(srcPath: string, destPath: string): boolean;
6
7
  export declare function applyContent(content: string, destPath: string): boolean;
7
8
  export declare function applyFile(srcPath: string, destPath: string): boolean;
package/dist/fs-sync.js CHANGED
@@ -7,6 +7,7 @@ exports.stripExtendedLengthPrefix = stripExtendedLengthPrefix;
7
7
  exports.toPosixPath = toPosixPath;
8
8
  exports.isBlocklistedCopyPath = isBlocklistedCopyPath;
9
9
  exports.sanitizedPackageContent = sanitizedPackageContent;
10
+ exports.sanitizedBuildDeclaration = sanitizedBuildDeclaration;
10
11
  exports.filesIdentical = filesIdentical;
11
12
  exports.applyContent = applyContent;
12
13
  exports.applyFile = applyFile;
@@ -39,6 +40,11 @@ function sanitizedPackageContent(raw) {
39
40
  }
40
41
  return `${JSON.stringify(pkg, null, 2)}\n`;
41
42
  }
43
+ const BUILD_HELPER_IMPORT = /(["'])#dms\/frontend-build\1/g;
44
+ const BUILD_HELPER_FROM_MODULE_ROOT = "../../frontend-build.ts";
45
+ function sanitizedBuildDeclaration(raw) {
46
+ return raw.replace(BUILD_HELPER_IMPORT, (_, quote) => `${quote}${BUILD_HELPER_FROM_MODULE_ROOT}${quote}`);
47
+ }
42
48
  function filesIdentical(srcPath, destPath) {
43
49
  if (!(0, node_fs_1.existsSync)(destPath))
44
50
  return false;
package/dist/help.js CHANGED
@@ -15,7 +15,15 @@ exports.ENVIRONMENT_VARIABLES = [
15
15
  ],
16
16
  ["DMS_OFFLINE", "Same as --offline, unless 0, false, no or off"],
17
17
  ["DMS_WORKSPACE_DIR", "Same as build and start --workspace"],
18
+ [
19
+ "ANTELOPEJS_LAUNCH_ENV",
20
+ "AntelopeJS environment dev, build and start evaluate the project in, same as -e",
21
+ ],
18
22
  ["DMS_PREPARE_STRICT", "Same as prepare --strict, read like DMS_OFFLINE"],
23
+ [
24
+ "DMS_ENTRY_WEIGHT_WARNING",
25
+ "KB of Brotli a module may add to the client entry before build warns (default: 50; 0: never)",
26
+ ],
19
27
  ["PORT", "Same as -p"],
20
28
  [
21
29
  "HOST",
@@ -29,6 +37,10 @@ exports.ENVIRONMENT_VARIABLES = [
29
37
  "NODE_ENV",
30
38
  "Build of Vue and its libraries the server loads (default: development in dev, production in start)",
31
39
  ],
40
+ [
41
+ "DMS_CLIENT_IP_HEADER",
42
+ "Header the hosting platform overwrites with the client's address, e.g. X-Client-IP (default: none)",
43
+ ],
32
44
  [
33
45
  "DMS_TRUSTED_PROXY_HOPS",
34
46
  "Trusted reverse-proxy hops, counted from the right (default: 0)",
@@ -61,10 +73,6 @@ exports.ENVIRONMENT_VARIABLES = [
61
73
  "DMS_BACKEND_PROXIES",
62
74
  "More backend prefixes the server proxies, as a JSON array of { path, auth? }",
63
75
  ],
64
- [
65
- "DMS_BACKEND_WAKE_BUDGET_MS",
66
- "How long a GET or HEAD waits out a waking or starting backend's 503, in ms (default: 30000; 0: never)",
67
- ],
68
76
  ["DMS_CLIENT_BASE_URL", "Public frontend URL used in links and e-mails"],
69
77
  ["NO_COLOR", "Same as --no-color"],
70
78
  ["ANTELOPEJS_VERBOSE", "Same as --verbose"],
package/dist/index.js CHANGED
@@ -8,7 +8,6 @@ const build_1 = require("./commands/build");
8
8
  const clean_1 = require("./commands/clean");
9
9
  const dev_1 = require("./commands/dev");
10
10
  const prepare_1 = require("./commands/prepare");
11
- const snapshot_1 = require("./commands/snapshot");
12
11
  const start_1 = require("./commands/start");
13
12
  const verify_source_1 = require("./commands/verify-source");
14
13
  const workspaces_1 = require("./commands/workspaces");
@@ -29,12 +28,12 @@ const ROOT_EXAMPLES = [
29
28
  command: "ajs dms dev",
30
29
  },
31
30
  {
32
- description: "Build for production, then serve the build",
33
- command: "ajs dms build -b https://dms.example.com",
31
+ description: "Build for production from the project root, without a backend",
32
+ command: "ajs dms build",
34
33
  },
35
34
  {
36
- description: "Serve it on another port",
37
- command: "ajs dms start -b https://dms.example.com -p 3001",
35
+ description: "Serve the build against its backend, on another port",
36
+ command: "ajs dms start -b https://api.example.com -p 3001",
38
37
  },
39
38
  ];
40
39
  function describeVersion(version, width) {
@@ -117,7 +116,6 @@ const runCLI = async () => {
117
116
  program.addCommand((0, dev_1.cmdDev)());
118
117
  program.addCommand((0, build_1.cmdBuild)());
119
118
  program.addCommand((0, start_1.cmdStart)());
120
- program.addCommand((0, snapshot_1.cmdSnapshot)());
121
119
  program.addCommand((0, prepare_1.cmdPrepare)());
122
120
  program.addCommand((0, workspaces_1.cmdWorkspaces)());
123
121
  program.addCommand((0, clean_1.cmdClean)());
@@ -9,8 +9,10 @@ const node_path_1 = require("node:path");
9
9
  const cli_1 = require("@antelopejs/core/cli");
10
10
  const chokidar_1 = __importDefault(require("chokidar"));
11
11
  const child_output_1 = require("./child-output");
12
+ const config_1 = require("./config");
12
13
  const layers_1 = require("./layers");
13
14
  const derived_outputs_1 = require("./derived-outputs");
15
+ const locale_files_1 = require("./locale-files");
14
16
  const output_1 = require("./output");
15
17
  const fs_sync_1 = require("./fs-sync");
16
18
  const WATCH_DEBOUNCE_MS = 80;
@@ -62,6 +64,9 @@ function createLayerSync(src, dest, reporter, onStaged) {
62
64
  if (rel === "package.json") {
63
65
  (0, fs_sync_1.applyContent)((0, fs_sync_1.sanitizedPackageContent)((0, node_fs_1.readFileSync)(srcPath, "utf-8")), destPath);
64
66
  }
67
+ else if (rel === config_1.FRONTEND_BUILD_ENTRY) {
68
+ (0, fs_sync_1.applyContent)((0, fs_sync_1.sanitizedBuildDeclaration)((0, node_fs_1.readFileSync)(srcPath, "utf-8")), destPath);
69
+ }
65
70
  else {
66
71
  (0, fs_sync_1.applyFile)(srcPath, destPath);
67
72
  }
@@ -124,11 +129,11 @@ function createLayerSync(src, dest, reporter, onStaged) {
124
129
  }
125
130
  function describeOutputFailure(output, err, reporter) {
126
131
  const kept = `The dev server keeps the previous ${output.name} until the file is fixed.`;
127
- if (err instanceof derived_outputs_1.InvalidLocaleFileError) {
132
+ if (err instanceof locale_files_1.InvalidLocaleFileError) {
128
133
  const file = reporter.show(err.path);
129
134
  return {
130
135
  text: `${file} is not a valid locale file`,
131
- options: { details: [err.reason, kept] },
136
+ options: { details: [...err.reason.split("\n"), kept] },
132
137
  fixed: [`${file} fixed`, `${output.name} regenerated`].join((0, cli_1.getProcessUi)().symbols.separator),
133
138
  };
134
139
  }
package/dist/layers.d.ts CHANGED
@@ -7,15 +7,14 @@ interface PathlessManifestSource {
7
7
  };
8
8
  }
9
9
  export declare function assertLayerPathsServed(modules: ManifestModule[], backendUrl: string, { bootstrapSecret, cache }?: PathlessManifestSource): asserts modules is ServedWithPath[];
10
- export declare function assertCachedArchivesExist(modules: ManifestModule[], cacheDir: string, fetchedAt?: string): void;
11
10
  export interface LayerPackage {
12
11
  name?: unknown;
13
12
  engines?: Record<string, unknown>;
13
+ dmsFrontend?: unknown;
14
+ antelopeJs?: unknown;
14
15
  }
15
16
  export declare function readLayerPackage(layerPath: string): LayerPackage | undefined;
16
17
  export declare function buildLayersFromPaths(modules: ServedWithPath[]): ResolvedLayer[];
17
- export declare function downloadAndExtractLayers(backendUrl: string, packUrl: string, cacheDir: string, force: boolean, bootstrapSecret?: string): Promise<void>;
18
- export declare function buildLayersFromCache(modules: ManifestModule[], cacheDir: string): Promise<ResolvedLayer[]>;
19
18
  export declare function getLayerSafeName(layer: ResolvedLayer): string;
20
19
  export declare function getLayerWorkspacePath(workspaceDir: string, layer: ResolvedLayer): string;
21
20
  export {};
package/dist/layers.js CHANGED
@@ -1,57 +1,17 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.assertLayerPathsServed = assertLayerPathsServed;
37
- exports.assertCachedArchivesExist = assertCachedArchivesExist;
38
4
  exports.readLayerPackage = readLayerPackage;
39
5
  exports.buildLayersFromPaths = buildLayersFromPaths;
40
- exports.downloadAndExtractLayers = downloadAndExtractLayers;
41
- exports.buildLayersFromCache = buildLayersFromCache;
42
6
  exports.getLayerSafeName = getLayerSafeName;
43
7
  exports.getLayerWorkspacePath = getLayerWorkspacePath;
44
8
  const node_fs_1 = require("node:fs");
45
- const promises_1 = require("node:fs/promises");
46
9
  const node_path_1 = require("node:path");
47
- const node_stream_1 = require("node:stream");
48
- const consumers_1 = require("node:stream/consumers");
49
10
  const cli_1 = require("@antelopejs/core/cli");
50
11
  const manifest_1 = require("./manifest");
51
- const fs_sync_1 = require("./fs-sync");
52
12
  const config_1 = require("./config");
53
13
  const output_1 = require("./output");
54
- const BUILD_FIX = "A remote or production backend never sends them: use `ajs dms build` against it";
14
+ const BUILD_FIX = "A remote or production backend never sends them: build the frontend from the project instead, with `ajs dms build`";
55
15
  function assertLayerPathsServed(modules, backendUrl, { bootstrapSecret, cache } = {}) {
56
16
  const pathless = modules.filter((mod) => !mod.path);
57
17
  if (pathless.length === 0)
@@ -87,17 +47,6 @@ function assertLayerPathsServed(modules, backendUrl, { bootstrapSecret, cache }
87
47
  fixes: [fix, BUILD_FIX],
88
48
  });
89
49
  }
90
- function assertCachedArchivesExist(modules, cacheDir, fetchedAt) {
91
- const present = new Set((0, node_fs_1.readdirSync)(cacheDir));
92
- const missing = modules.filter((mod) => !present.has(mod.archiveName));
93
- if (missing.length === 0)
94
- return;
95
- throw new cli_1.CliError({
96
- title: `The cached layers archive is missing entries listed in the manifest${fetchedAt ? ` (fetched ${(0, output_1.formatAge)(fetchedAt)})` : ""}`,
97
- fixes: ["Run once with the backend reachable to download the layers again"],
98
- details: missing.map((mod) => `${mod.name}: ${mod.archiveName}`),
99
- });
100
- }
101
50
  function sortModulesByPriority(modules) {
102
51
  return [...modules].sort((a, b) => b.priority - a.priority);
103
52
  }
@@ -134,49 +83,6 @@ function resolveLayer(layerPath, mod) {
134
83
  function buildLayersFromPaths(modules) {
135
84
  return sortModulesByPriority(modules).map((mod) => resolveLayer(mod.path, mod));
136
85
  }
137
- async function downloadAndExtractLayers(backendUrl, packUrl, cacheDir, force, bootstrapSecret) {
138
- const url = (0, manifest_1.backendEndpoint)(backendUrl, packUrl);
139
- const response = await (0, manifest_1.fetchFromBackend)(backendUrl, url, bootstrapSecret);
140
- if (!response.ok) {
141
- throw new manifest_1.BackendResponseError("the layers archive", url, response);
142
- }
143
- if (!response.body) {
144
- throw new Error(`The layers archive from ${(0, manifest_1.displayUrl)(url)} came without a body`);
145
- }
146
- const modulesBlob = await (0, consumers_1.buffer)(node_stream_1.Readable.fromWeb(response.body));
147
- const { Open } = await Promise.resolve().then(() => __importStar(require("unzipper")));
148
- const cacheExists = (0, node_fs_1.existsSync)(cacheDir);
149
- if (force || !cacheExists) {
150
- if (cacheExists) {
151
- (0, node_fs_1.rmSync)(cacheDir, { recursive: true, force: true });
152
- }
153
- await (await Open.buffer(modulesBlob)).extract({ path: cacheDir });
154
- return;
155
- }
156
- const tempDir = `${cacheDir}_temp`;
157
- if ((0, node_fs_1.existsSync)(tempDir)) {
158
- (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
159
- }
160
- await (await Open.buffer(modulesBlob)).extract({ path: tempDir });
161
- const tempEntries = (0, node_fs_1.readdirSync)(tempDir);
162
- for (const entry of tempEntries) {
163
- (0, fs_sync_1.syncDirectories)((0, node_path_1.join)(tempDir, entry), (0, node_path_1.join)(cacheDir, entry));
164
- }
165
- (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
166
- }
167
- async function buildLayersFromCache(modules, cacheDir) {
168
- if (!(0, node_fs_1.existsSync)(cacheDir))
169
- return [];
170
- const layerFiles = await (0, promises_1.readdir)(cacheDir);
171
- const layers = [];
172
- for (const mod of sortModulesByPriority(modules)) {
173
- if (!layerFiles.includes(mod.archiveName))
174
- continue;
175
- const layerPath = (0, node_path_1.join)(cacheDir, mod.archiveName);
176
- layers.push(resolveLayer(layerPath, mod));
177
- }
178
- return layers;
179
- }
180
86
  function getLayerSafeName(layer) {
181
87
  if (layer.packageName) {
182
88
  return layer.packageName.replace(/^@/, "").replace(/\//g, "__");
@@ -0,0 +1,6 @@
1
+ export declare class InvalidLocaleFileError extends Error {
2
+ readonly path: string;
3
+ readonly reason: string;
4
+ constructor(path: string, reason: string);
5
+ }
6
+ export declare function readLocaleFile(path: string): Record<string, unknown>;