@antelopejs/dms-frontend 0.9.2 → 0.10.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 (123) hide show
  1. package/README.md +18 -24
  2. package/dist/commands/build-action.js +3 -2
  3. package/dist/commands/dev-action.js +2 -1
  4. package/dist/commands/prepare-action.js +1 -1
  5. package/dist/commands/start-action.js +3 -24
  6. package/dist/commands/workspace-task.js +2 -1
  7. package/dist/config.js +3 -0
  8. package/dist/derived-outputs.js +3 -3
  9. package/dist/discovery.js +17 -6
  10. package/dist/entry-weight.js +15 -0
  11. package/dist/fs-sync.js +7 -4
  12. package/dist/index.js +2 -1
  13. package/dist/internal/built-workspace.d.ts +4 -0
  14. package/dist/internal/built-workspace.js +43 -0
  15. package/dist/internal/ensure.d.ts +1 -0
  16. package/dist/internal/ensure.js +8 -0
  17. package/dist/internal/locale-catalogs.d.ts +8 -3
  18. package/dist/internal/locale-catalogs.js +16 -6
  19. package/dist/internal/manifest-decoder.d.ts +7 -0
  20. package/dist/internal/manifest-decoder.js +81 -0
  21. package/dist/internal/own-release.d.ts +5 -0
  22. package/dist/internal/own-release.js +20 -0
  23. package/dist/internal/plain-object.d.ts +1 -0
  24. package/dist/internal/plain-object.js +6 -0
  25. package/dist/internal/text-list.d.ts +1 -0
  26. package/dist/internal/text-list.js +10 -0
  27. package/dist/internal/vite-manifest.d.ts +6 -0
  28. package/dist/internal/vite-manifest.js +24 -0
  29. package/dist/layer-watch.js +25 -24
  30. package/dist/layers.d.ts +1 -1
  31. package/dist/layers.js +3 -1
  32. package/dist/locale-files.d.ts +2 -1
  33. package/dist/locale-files.js +4 -0
  34. package/dist/manifest.d.ts +1 -1
  35. package/dist/manifest.js +44 -31
  36. package/dist/materialize.js +6 -2
  37. package/dist/module-set.js +2 -1
  38. package/dist/node-options.d.ts +1 -0
  39. package/dist/node-options.js +8 -0
  40. package/dist/output.d.ts +15 -9
  41. package/dist/output.js +30 -18
  42. package/dist/private-values.d.ts +1 -1
  43. package/dist/private-values.js +5 -3
  44. package/dist/project-modules.js +13 -8
  45. package/dist/renderer-range.js +5 -3
  46. package/dist/server-process.js +1 -1
  47. package/dist/verify-source-build.js +10 -2
  48. package/dist/verify-source-client-bundle.d.ts +1 -0
  49. package/dist/verify-source-client-bundle.js +30 -0
  50. package/dist/verify-source-runner.js +44 -60
  51. package/dist/verify-source-sources.js +49 -11
  52. package/dist/workspace-setup.js +1 -1
  53. package/package.json +6 -4
  54. package/skills/dms-frontend/SKILL.md +1 -1
  55. package/templates/vue/app-runtime.ts +90 -16
  56. package/templates/vue/client-server-guard.mjs +16 -0
  57. package/templates/vue/compress-assets.mjs +9 -1
  58. package/templates/vue/entry-weight.mjs +22 -0
  59. package/templates/vue/frontend-module.ts +1 -0
  60. package/templates/vue/frontend-runtime/module-registration.ts +2 -2
  61. package/templates/vue/frontend-runtime/state.ts +2 -2
  62. package/templates/vue/frontend-runtime/types.ts +8 -2
  63. package/templates/vue/globals.d.ts +5 -0
  64. package/templates/vue/head-order.mjs +7 -0
  65. package/templates/vue/icon-bundle.ts +31 -0
  66. package/templates/vue/icon-scan.mjs +4 -0
  67. package/templates/vue/message-subset.ts +65 -0
  68. package/templates/vue/network-error.ts +4 -0
  69. package/templates/vue/server/auth/backend.mjs +41 -4
  70. package/templates/vue/server/auth/client-ip.mjs +52 -4
  71. package/templates/vue/server/auth/establish-endpoints.mjs +10 -6
  72. package/templates/vue/server/auth/session.mjs +81 -2
  73. package/templates/vue/server/auth-hooks.mjs +15 -5
  74. package/templates/vue/server/backend-proxies.mjs +65 -9
  75. package/templates/vue/server/backend-request.mjs +61 -0
  76. package/templates/vue/server/backend-response.mjs +23 -6
  77. package/templates/vue/server/built-files.mjs +7 -0
  78. package/templates/vue/server/cli-report.mjs +53 -11
  79. package/templates/vue/server/client-manifest.mjs +23 -1
  80. package/templates/vue/server/content.mjs +18 -1
  81. package/templates/vue/server/dev-private-files.mjs +17 -1
  82. package/templates/vue/server/dev-styles.mjs +38 -0
  83. package/templates/vue/server/email.mjs +9 -3
  84. package/templates/vue/server/favicon.mjs +3 -0
  85. package/templates/vue/server/generated-files.mjs +211 -0
  86. package/templates/vue/server/homepage.mjs +21 -9
  87. package/templates/vue/server/html-tag.mjs +4 -0
  88. package/templates/vue/server/icons.mjs +80 -7
  89. package/templates/vue/server/in-place-render.mjs +35 -0
  90. package/templates/vue/server/inertia.mjs +61 -2
  91. package/templates/vue/server/module-event.mjs +98 -17
  92. package/templates/vue/server/module-route-types.mjs +78 -0
  93. package/templates/vue/server/module-routes.mjs +138 -31
  94. package/templates/vue/server/module-server.mjs +98 -27
  95. package/templates/vue/server/module-testing.mjs +14 -5
  96. package/templates/vue/server/page-props.mjs +92 -2
  97. package/templates/vue/server/page-redirects.mjs +37 -0
  98. package/templates/vue/server/path-patterns.mjs +13 -1
  99. package/templates/vue/server/proxy-compression.mjs +22 -0
  100. package/templates/vue/server/proxy-headers.mjs +11 -1
  101. package/templates/vue/server/proxy-redirects.mjs +20 -5
  102. package/templates/vue/server/proxy-request.mjs +57 -0
  103. package/templates/vue/server/proxy-transfers.mjs +33 -5
  104. package/templates/vue/server/public-endpoints.mjs +138 -23
  105. package/templates/vue/server/public-options.mjs +20 -6
  106. package/templates/vue/server/request-failure.mjs +23 -0
  107. package/templates/vue/server/request-routing.mjs +26 -9
  108. package/templates/vue/server/request-target.mjs +9 -3
  109. package/templates/vue/server/route-rules.mjs +39 -15
  110. package/templates/vue/server/ssr-module.mjs +32 -0
  111. package/templates/vue/server/static-files.mjs +24 -1
  112. package/templates/vue/server/type-guards.mjs +71 -0
  113. package/templates/vue/server/vite-errors.mjs +32 -5
  114. package/templates/vue/server/web-body.mjs +3 -0
  115. package/templates/vue/server-module.ts +10 -1
  116. package/templates/vue/server.mjs +132 -148
  117. package/templates/vue/sfc-global-types.mjs +112 -13
  118. package/templates/vue/ssr-renderer.ts +12 -3
  119. package/templates/vue/ui-inertia-page.mjs +16 -1
  120. package/templates/vue/ui-kbd.mjs +2 -1
  121. package/templates/vue/ui-lazy-icons.mjs +44 -0
  122. package/templates/vue/ui-request-state.mjs +2 -1
  123. package/templates/vue/vite.config.ts +2 -0
@@ -11,7 +11,8 @@ const semver_1 = __importDefault(require("semver"));
11
11
  const layers_1 = require("./layers");
12
12
  const output_1 = require("./output");
13
13
  const guards_1 = require("./internal/guards");
14
- const OWN_RELEASE = require("../package.json");
14
+ const plain_object_1 = require("./internal/plain-object");
15
+ const own_release_1 = require("./internal/own-release");
15
16
  const noticedModules = new Set();
16
17
  const NOTICED_MODULES_FILE = ".renderer-range-noticed";
17
18
  function readNoticedModules(workspaceDir) {
@@ -35,10 +36,11 @@ function shownModule(layer) {
35
36
  return layer.packageName ? `${layer.packageName} (${where})` : where;
36
37
  }
37
38
  function declaredRange(layer, rendererName) {
39
+ const engines = (0, layers_1.readLayerPackage)(layer.path)?.engines;
38
40
  return {
39
41
  module: moduleLabel(layer),
40
42
  shown: shownModule(layer),
41
- range: (0, layers_1.readLayerPackage)(layer.path)?.engines?.[rendererName],
43
+ range: (0, plain_object_1.isPlainObject)(engines) ? engines[rendererName] : undefined,
42
44
  };
43
45
  }
44
46
  function releaseLine(version) {
@@ -73,7 +75,7 @@ function noticeUndeclared(declarations, renderer, warn, workspaceDir) {
73
75
  });
74
76
  }
75
77
  function assertLayersSupportRenderer(layers, options = {}) {
76
- const renderer = options.renderer ?? OWN_RELEASE;
78
+ const renderer = options.renderer ?? own_release_1.ownRelease;
77
79
  const declarations = layers.map((layer) => declaredRange(layer, renderer.name));
78
80
  noticeUndeclared(declarations, renderer, options.warn ??
79
81
  ((text, messageOptions) => (0, cli_1.getProcessUi)().message("warn", text, messageOptions)), options.workspaceDir);
@@ -73,7 +73,7 @@ function readListenError(message) {
73
73
  function readStartError(message) {
74
74
  if (!isServerMessage(message) || message.type !== START_ERROR_MESSAGE)
75
75
  return undefined;
76
- const { location } = message;
76
+ const location = message.location;
77
77
  const isLocated = hasFile(location);
78
78
  return {
79
79
  message: String(message.message),
@@ -8,6 +8,8 @@ const cli_1 = require("@antelopejs/core/cli");
8
8
  const child_output_1 = require("./child-output");
9
9
  const build_action_1 = require("./commands/build-action");
10
10
  const common_1 = require("./common");
11
+ const text_list_1 = require("./internal/text-list");
12
+ const plain_object_1 = require("./internal/plain-object");
11
13
  const typecheck_output_1 = require("./typecheck-output");
12
14
  const verify_source_result_1 = require("./verify-source-result");
13
15
  const vite_output_1 = require("./vite-output");
@@ -39,8 +41,14 @@ const TYPECHECK_CONFIG = "tsconfig.verify.json";
39
41
  function snapshotTypeDeclarations(workspace) {
40
42
  (0, node_fs_1.cpSync)((0, node_path_1.join)(workspace, AUTO_IMPORTS_DECLARATIONS), (0, node_path_1.join)(workspace, AUTO_IMPORTS_SNAPSHOT));
41
43
  const config = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(workspace, "tsconfig.json"), "utf8"));
42
- config.files = (config.files ?? []).map((file) => file === AUTO_IMPORTS_DECLARATIONS ? AUTO_IMPORTS_SNAPSHOT : file);
43
- if (!config.files.includes(AUTO_IMPORTS_SNAPSHOT))
44
+ const listed = (0, plain_object_1.isPlainObject)(config) ? (config.files ?? []) : undefined;
45
+ if (!(0, plain_object_1.isPlainObject)(config) || !(0, text_list_1.isTextList)(listed))
46
+ throw new cli_1.CliError({
47
+ title: "The workspace tsconfig.json does not list its files as paths",
48
+ });
49
+ const files = listed.map((file) => file === AUTO_IMPORTS_DECLARATIONS ? AUTO_IMPORTS_SNAPSHOT : file);
50
+ config.files = files;
51
+ if (!files.includes(AUTO_IMPORTS_SNAPSHOT))
44
52
  throw new cli_1.CliError({
45
53
  title: `The workspace tsconfig.json does not list ${AUTO_IMPORTS_DECLARATIONS}`,
46
54
  });
@@ -0,0 +1 @@
1
+ export declare function checkClientBundle(workspace: string): void;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkClientBundle = checkClientBundle;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const ensure_1 = require("./internal/ensure");
7
+ const vite_manifest_1 = require("./internal/vite-manifest");
8
+ const LAZY_LIBRARIES = [
9
+ { name: "ApexCharts", pattern: /ApexCharts/ },
10
+ { name: "Tiptap", pattern: /tiptap/i },
11
+ ];
12
+ const RUNTIME_MARKERS = ["auth/login", "DefaultLayout", "error.500.title"];
13
+ function checkClientBundle(workspace) {
14
+ const clientRoot = (0, node_path_1.join)(workspace, "dist", "client");
15
+ const manifest = (0, vite_manifest_1.parseViteManifest)((0, node_fs_1.readFileSync)((0, node_path_1.join)(clientRoot, ".vite", "manifest.json"), "utf8"), "client build manifest");
16
+ const mainEntry = manifest.find((entry) => entry.isEntry);
17
+ (0, ensure_1.ensure)(mainEntry !== undefined, "The Vite manifest names no entry chunk");
18
+ const mainJavascript = (0, node_fs_1.readFileSync)((0, node_path_1.join)(clientRoot, mainEntry.file), "utf8");
19
+ const javascript = (0, node_fs_1.readdirSync)((0, node_path_1.join)(clientRoot, "assets"))
20
+ .filter((file) => file.endsWith(".js"))
21
+ .map((file) => (0, node_fs_1.readFileSync)((0, node_path_1.join)(clientRoot, "assets", file), "utf8"))
22
+ .join("\n");
23
+ for (const { name, pattern } of LAZY_LIBRARIES) {
24
+ (0, ensure_1.ensure)(!pattern.test(mainJavascript), `The main chunk bundles ${name}`, "It must stay in a lazily loaded chunk.");
25
+ (0, ensure_1.ensure)(pattern.test(javascript), `No client chunk bundles ${name}`);
26
+ }
27
+ (0, ensure_1.ensure)(manifest.some((chunk) => chunk.src?.endsWith("/kpi/KpiCard.vue")), "DmsKpiCard is not reachable at runtime");
28
+ for (const marker of RUNTIME_MARKERS)
29
+ (0, ensure_1.ensure)(javascript.includes(marker), `${marker} is not reachable at runtime`);
30
+ }
@@ -37,32 +37,27 @@ const node_fs_1 = require("node:fs");
37
37
  const node_path_1 = require("node:path");
38
38
  const cli_1 = require("@antelopejs/core/cli");
39
39
  const child_output_1 = require("./child-output");
40
+ const ensure_1 = require("./internal/ensure");
41
+ const guards_1 = require("./internal/guards");
42
+ const plain_object_1 = require("./internal/plain-object");
43
+ const vite_manifest_1 = require("./internal/vite-manifest");
40
44
  const common_1 = require("./common");
41
45
  const output_1 = require("./output");
42
46
  const temporary_workspace_1 = require("./temporary-workspace");
47
+ const verify_source_client_bundle_1 = require("./verify-source-client-bundle");
43
48
  const verify_source_build_1 = require("./verify-source-build");
44
49
  const verify_source_result_1 = require("./verify-source-result");
45
50
  const verify_source_sources_1 = require("./verify-source-sources");
46
51
  const vite_output_1 = require("./vite-output");
47
52
  const EMAIL_BUILD_SIZE_CEILING_BYTES = 256 * 1024;
48
53
  const NON_EMAIL_RUNTIME_PATTERN = /DisplayRichText|ApexCharts|Tiptap|FlowCanvas|app\/.*table|components\/table/i;
49
- const LAZY_LIBRARIES = [
50
- { name: "ApexCharts", pattern: /ApexCharts/ },
51
- { name: "Tiptap", pattern: /tiptap/i },
52
- ];
53
- const RUNTIME_MARKERS = ["auth/login", "DefaultLayout", "error.500.title"];
54
- function readJson(path) {
55
- return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
56
- }
57
- function ensure(condition, title, reason) {
58
- if (!condition)
59
- throw new cli_1.CliError({ title, reason });
60
- }
61
54
  function removeDevelopmentDependencies(root) {
62
55
  const packagePath = (0, node_path_1.join)(root, "package.json");
63
56
  if (!(0, node_fs_1.existsSync)(packagePath))
64
57
  return;
65
- const packageData = readJson(packagePath);
58
+ const packageData = JSON.parse((0, node_fs_1.readFileSync)(packagePath, "utf8"));
59
+ if (!(0, plain_object_1.isPlainObject)(packageData))
60
+ return;
66
61
  delete packageData.devDependencies;
67
62
  (0, node_fs_1.writeFileSync)(packagePath, `${JSON.stringify(packageData, null, 2)}\n`);
68
63
  }
@@ -82,10 +77,14 @@ function materializeWorkspace(workspace, layers, localPackages) {
82
77
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspace, "dms-main.css"), '@import "tailwindcss";\n@import "@nuxt/ui";\n');
83
78
  (0, common_1.writeWorkspacePackageJson)(workspace, layers);
84
79
  const packagePath = (0, node_path_1.join)(workspace, "package.json");
85
- const workspacePackage = readJson(packagePath);
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");
86
82
  workspacePackage.pnpm = {
87
83
  overrides: {
88
- ...Object.fromEntries(layers.map((layer) => [layer.packageName, "workspace:*"])),
84
+ ...Object.fromEntries(layers.map((layer) => [
85
+ String(layer.packageName),
86
+ "workspace:*",
87
+ ])),
89
88
  ...Object.fromEntries(Object.entries(localPackages).map(([name, path]) => [
90
89
  name,
91
90
  `link:${(0, node_path_1.resolve)(path)}`,
@@ -94,33 +93,15 @@ function materializeWorkspace(workspace, layers, localPackages) {
94
93
  };
95
94
  (0, node_fs_1.writeFileSync)(packagePath, `${JSON.stringify(workspacePackage, null, 2)}\n`);
96
95
  }
97
- function checkClientBundle(workspace) {
98
- const clientRoot = (0, node_path_1.join)(workspace, "dist", "client");
99
- const manifest = readJson((0, node_path_1.join)(clientRoot, ".vite", "manifest.json"));
100
- const mainEntry = Object.values(manifest).find((entry) => entry.isEntry);
101
- ensure(mainEntry !== undefined, "The Vite manifest names no entry chunk");
102
- const mainJavascript = (0, node_fs_1.readFileSync)((0, node_path_1.join)(clientRoot, mainEntry.file), "utf8");
103
- const javascript = (0, node_fs_1.readdirSync)((0, node_path_1.join)(clientRoot, "assets"))
104
- .filter((file) => file.endsWith(".js"))
105
- .map((file) => (0, node_fs_1.readFileSync)((0, node_path_1.join)(clientRoot, "assets", file), "utf8"))
106
- .join("\n");
107
- for (const { name, pattern } of LAZY_LIBRARIES) {
108
- ensure(!pattern.test(mainJavascript), `The main chunk bundles ${name}`, "It must stay in a lazily loaded chunk.");
109
- ensure(pattern.test(javascript), `No client chunk bundles ${name}`);
110
- }
111
- ensure(Object.keys(manifest).some((source) => source.endsWith("/kpi/KpiCard.vue")), "DmsKpiCard is not reachable at runtime");
112
- for (const marker of RUNTIME_MARKERS)
113
- ensure(javascript.includes(marker), `${marker} is not reachable at runtime`);
114
- }
115
96
  async function checkServerModulesBundle(workspace) {
116
97
  const root = (0, node_path_1.join)(workspace, "dist", "server-modules");
117
98
  const javascript = (0, node_fs_1.readdirSync)(root, { recursive: true, encoding: "utf8" })
118
99
  .filter((file) => file.endsWith(".js"))
119
100
  .map((file) => (0, node_fs_1.readFileSync)((0, node_path_1.join)(root, file), "utf8"))
120
101
  .join("\n");
121
- ensure(!/\.vue["'?]|createApp\(|DefaultLayout/.test(javascript), "The server modules bundle loads application code", "A dms.server.ts imports server code only, never .vue files or dms.frontend.ts.");
122
- const { serverModules } = await Promise.resolve(`${(0, node_path_1.join)(root, "server-modules.generated.js")}`).then(s => __importStar(require(s)));
123
- ensure(Array.isArray(serverModules), "The server modules bundle exports none");
102
+ (0, ensure_1.ensure)(!/\.vue["'?]|createApp\(|DefaultLayout/.test(javascript), "The server modules bundle loads application code", "A dms.server.ts imports server code only, never .vue files or dms.frontend.ts.");
103
+ const serverModules = (await Promise.resolve(`${(0, node_path_1.join)(root, "server-modules.generated.js")}`).then(s => __importStar(require(s)))).serverModules;
104
+ (0, ensure_1.ensure)(Array.isArray(serverModules), "The server modules bundle exports none");
124
105
  return `${(0, cli_1.pluralize)(serverModules.length, "server module")}, ${(0, output_1.formatSize)(Buffer.byteLength(javascript))} JS`;
125
106
  }
126
107
  const EMAIL_CASES = [
@@ -177,19 +158,19 @@ const EMAIL_CASES = [
177
158
  ];
178
159
  const BROWSER_ONLY_REASON = "It must not load rich text, charts, tables or flows.";
179
160
  function checkLayoutEmail(html) {
180
- ensure(/<meta charset="utf-8"/.test(html), "EmailLayout declares no UTF-8 charset");
181
- ensure(/images\/antelope-logo\/light\.svg/.test(html), "EmailLayout renders no logo");
182
- ensure(!/<h1[^>]*\bas=/.test(html), "EmailLayout leaks an `as` attribute onto its heading");
161
+ (0, ensure_1.ensure)(/<meta charset="utf-8"/.test(html), "EmailLayout declares no UTF-8 charset");
162
+ (0, ensure_1.ensure)(/images\/antelope-logo\/light\.svg/.test(html), "EmailLayout renders no logo");
163
+ (0, ensure_1.ensure)(!/<h1[^>]*\bas=/.test(html), "EmailLayout leaks an `as` attribute onto its heading");
183
164
  }
184
165
  async function checkEmailContracts(workspace) {
185
166
  const emailRoot = (0, node_path_1.join)(workspace, "dist", "server");
186
167
  const emailManifestContent = (0, node_fs_1.readFileSync)((0, node_path_1.join)(emailRoot, ".vite", "manifest.json"), "utf8");
187
- ensure(!NON_EMAIL_RUNTIME_PATTERN.test(emailManifestContent), "The e-mail manifest references browser-only modules", BROWSER_ONLY_REASON);
188
- const emailManifest = JSON.parse(emailManifestContent);
168
+ (0, ensure_1.ensure)(!NON_EMAIL_RUNTIME_PATTERN.test(emailManifestContent), "The e-mail manifest references browser-only modules", BROWSER_ONLY_REASON);
169
+ const emailManifest = (0, vite_manifest_1.parseViteManifest)(emailManifestContent, "e-mail build manifest");
189
170
  const emailJavascriptFiles = [
190
171
  ...new Set([
191
172
  "email-renderer.js",
192
- ...Object.values(emailManifest)
173
+ ...emailManifest
193
174
  .map((entry) => entry.file)
194
175
  .filter((file) => file.endsWith(".js")),
195
176
  ]),
@@ -197,18 +178,18 @@ async function checkEmailContracts(workspace) {
197
178
  const emailJavascript = emailJavascriptFiles
198
179
  .map((file) => (0, node_fs_1.readFileSync)((0, node_path_1.join)(emailRoot, file), "utf8"))
199
180
  .join("\n");
200
- ensure(!NON_EMAIL_RUNTIME_PATTERN.test(emailJavascript), "The e-mail bundle loads browser-only modules", BROWSER_ONLY_REASON);
181
+ (0, ensure_1.ensure)(!NON_EMAIL_RUNTIME_PATTERN.test(emailJavascript), "The e-mail bundle loads browser-only modules", BROWSER_ONLY_REASON);
201
182
  const emailBuildSize = Buffer.byteLength(emailJavascript);
202
183
  const emailLocaleBytes = (0, node_fs_1.readdirSync)((0, node_path_1.join)(emailRoot, "locales")).reduce((total, file) => total + (0, node_fs_1.readFileSync)((0, node_path_1.join)(emailRoot, "locales", file)).byteLength, 0);
203
184
  const limit = (0, output_1.formatSize)(EMAIL_BUILD_SIZE_CEILING_BYTES);
204
- ensure(emailBuildSize <= EMAIL_BUILD_SIZE_CEILING_BYTES, `The e-mail bundle is ${(0, output_1.formatSize)(emailBuildSize)} of JavaScript`, `The limit is ${limit}.`);
185
+ (0, ensure_1.ensure)(emailBuildSize <= EMAIL_BUILD_SIZE_CEILING_BYTES, `The e-mail bundle is ${(0, output_1.formatSize)(emailBuildSize)} of JavaScript`, `The limit is ${limit}.`);
205
186
  const { renderEmail } = await Promise.resolve(`${(0, node_path_1.join)(emailRoot, "email-renderer.js")}`).then(s => __importStar(require(s)));
206
187
  for (const emailCase of EMAIL_CASES) {
207
188
  const html = await renderEmail(emailCase.name, emailCase.props);
208
- ensure(html.startsWith("<!doctype html>"), `${emailCase.name} does not render a full HTML document`);
189
+ (0, ensure_1.ensure)((0, guards_1.isText)(html) && html.startsWith("<!doctype html>"), `${emailCase.name} does not render a full HTML document`);
209
190
  if (emailCase.name === "EmailLayout")
210
191
  checkLayoutEmail(html);
211
- ensure(html.includes(emailCase.marker), `${emailCase.name} does not render its props`, `Its HTML lacks ${emailCase.marker}.`);
192
+ (0, ensure_1.ensure)(html.includes(emailCase.marker), `${emailCase.name} does not render its props`, `Its HTML lacks ${emailCase.marker}.`);
212
193
  }
213
194
  return [
214
195
  "E-mail contracts hold",
@@ -219,18 +200,6 @@ async function checkEmailContracts(workspace) {
219
200
  }
220
201
  const LINE_BREAK = /\r?\n/;
221
202
  const STACK_TRACE_HINT = "Run with --verbose for the full trace.";
222
- function describeError(error, isVerbose) {
223
- if (error instanceof cli_1.CliError)
224
- return error.problem;
225
- if (!(error instanceof Error))
226
- return { title: String(error) };
227
- const [title] = error.message.split(LINE_BREAK);
228
- const stack = (error.stack ?? "").split(LINE_BREAK);
229
- return {
230
- title: title || error.name,
231
- details: isVerbose ? stack : [STACK_TRACE_HINT],
232
- };
233
- }
234
203
  async function verifySources() {
235
204
  const options = (0, verify_source_sources_1.readSourceOptions)();
236
205
  const { layers, coreLayer } = (0, verify_source_sources_1.readSources)(options);
@@ -251,7 +220,7 @@ async function verifySources() {
251
220
  await (0, common_1.installDeps)(workspace, context.mapLine, ["--ignore-scripts"]);
252
221
  await (0, verify_source_build_1.prepareTypes)(context);
253
222
  await (0, verify_source_build_1.buildAndTypeCheck)(context);
254
- await tasks.run("Checking the client bundle", async () => checkClientBundle(workspace), { done: "Client bundle checked", failed: "Client bundle check failed" });
223
+ await tasks.run("Checking the client bundle", async () => (0, verify_source_client_bundle_1.checkClientBundle)(workspace), { done: "Client bundle checked", failed: "Client bundle check failed" });
255
224
  await tasks.run("Checking the e-mail contracts", async () => checkEmailContracts(workspace), { done: (line) => line, failed: "E-mail contracts failed" });
256
225
  await tasks.run("Checking the server modules", async () => checkServerModulesBundle(workspace), { done: (line) => line, failed: "Server modules check failed" });
257
226
  return { ok: true, modules: layers.length };
@@ -276,7 +245,22 @@ async function main() {
276
245
  process.exitCode = error.exitCode;
277
246
  return;
278
247
  }
279
- result = { ok: false, problem: describeError(error, (0, cli_1.isVerboseRun)()) };
248
+ let problem;
249
+ if (error instanceof cli_1.CliError) {
250
+ problem = error.problem;
251
+ }
252
+ else if (error instanceof Error) {
253
+ const [title] = error.message.split(LINE_BREAK);
254
+ const stack = (error.stack ?? "").split(LINE_BREAK);
255
+ problem = {
256
+ title: title || error.name,
257
+ details: (0, cli_1.isVerboseRun)() ? stack : [STACK_TRACE_HINT],
258
+ };
259
+ }
260
+ else {
261
+ problem = { title: String(error) };
262
+ }
263
+ result = { ok: false, problem };
280
264
  }
281
265
  if (process.send) {
282
266
  process.exitCode = result.ok ? 0 : 1;
@@ -5,18 +5,20 @@ exports.readSources = readSources;
5
5
  const node_fs_1 = require("node:fs");
6
6
  const node_path_1 = require("node:path");
7
7
  const common_1 = require("./common");
8
+ const guards_1 = require("./internal/guards");
9
+ const plain_object_1 = require("./internal/plain-object");
8
10
  const output_1 = require("./output");
9
11
  const verify_source_result_1 = require("./verify-source-result");
10
12
  const DMS_PACKAGE = "@antelopejs/dms";
11
13
  const DMS_CORE_LAYER_PACKAGE = "@antelopejs/dms-frontend-vue";
12
14
  const CORE_LAYER_DIRECTORY = "frontend-vue";
13
15
  const FRONTEND_ENTRY = "dms.frontend.ts";
14
- const ADD_DEV_DEPENDENCY = {
15
- pnpm: "pnpm add -D",
16
- npm: "npm install -D",
17
- yarn: "yarn add -D",
18
- bun: "bun add -d",
19
- };
16
+ const ADD_DEV_DEPENDENCY = new Map([
17
+ ["pnpm", "pnpm add -D"],
18
+ ["npm", "npm install -D"],
19
+ ["yarn", "yarn add -D"],
20
+ ["bun", "bun add -d"],
21
+ ]);
20
22
  const LOCKFILES = [
21
23
  ["pnpm-lock.yaml", "pnpm"],
22
24
  ["package-lock.json", "npm"],
@@ -24,16 +26,46 @@ const LOCKFILES = [
24
26
  ["bun.lock", "bun"],
25
27
  ["bun.lockb", "bun"],
26
28
  ];
29
+ function malformedVariable(variable, expected) {
30
+ return new common_1.UsageError({
31
+ title: `${variable} is not ${expected}`,
32
+ reason: `\`ajs dms verify-source\` starts the runner with ${variable} set to ${expected}.`,
33
+ });
34
+ }
35
+ function readTextList(variable) {
36
+ const value = JSON.parse(process.env[variable] ?? "[]");
37
+ if (Array.isArray(value) && value.every(guards_1.isText))
38
+ return value;
39
+ throw malformedVariable(variable, "a JSON list of paths");
40
+ }
41
+ function readTextRecord(variable) {
42
+ const value = JSON.parse(process.env[variable] ?? "{}");
43
+ if ((0, plain_object_1.isPlainObject)(value)) {
44
+ const entries = Object.entries(value);
45
+ if (entries.every((entry) => (0, guards_1.isText)(entry[1]))) {
46
+ return Object.fromEntries(entries);
47
+ }
48
+ }
49
+ throw malformedVariable(variable, "a JSON record of paths by package name");
50
+ }
27
51
  function readSourceOptions() {
28
52
  return {
29
53
  projectDir: process.cwd(),
30
54
  layerSource: process.env.DMS_LAYER_SOURCE || undefined,
31
- moduleSources: JSON.parse(process.env.DMS_MODULE_SOURCES ?? "[]"),
32
- localPackages: JSON.parse(process.env.DMS_LOCAL_PACKAGES ?? "{}"),
55
+ moduleSources: readTextList("DMS_MODULE_SOURCES"),
56
+ localPackages: readTextRecord("DMS_LOCAL_PACKAGES"),
33
57
  };
34
58
  }
35
59
  function readManifest(packagePath) {
36
- return JSON.parse((0, node_fs_1.readFileSync)(packagePath, "utf8"));
60
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(packagePath, "utf8"));
61
+ if (!(0, plain_object_1.isPlainObject)(parsed))
62
+ return {};
63
+ const { name, version, packageManager } = parsed;
64
+ return {
65
+ name: (0, guards_1.isText)(name) ? name : undefined,
66
+ version: (0, guards_1.isText)(version) ? version : undefined,
67
+ packageManager: (0, guards_1.isText)(packageManager) ? packageManager : undefined,
68
+ };
37
69
  }
38
70
  function readLayer(root) {
39
71
  const packagePath = (0, node_path_1.join)(root, "package.json");
@@ -68,7 +100,7 @@ function packageManager(projectDir) {
68
100
  const packagePath = (0, node_path_1.join)(directory, "package.json");
69
101
  if ((0, node_fs_1.existsSync)(packagePath)) {
70
102
  const [name] = (readManifest(packagePath).packageManager ?? "").split("@");
71
- if (name && ADD_DEV_DEPENDENCY[name])
103
+ if (name && ADD_DEV_DEPENDENCY.has(name))
72
104
  return name;
73
105
  const lockfile = LOCKFILES.find(([file]) => (0, node_fs_1.existsSync)((0, node_path_1.join)(directory, file)));
74
106
  if (lockfile)
@@ -79,7 +111,7 @@ function packageManager(projectDir) {
79
111
  }
80
112
  }
81
113
  function addDmsCommand(projectDir, version = "") {
82
- return `${ADD_DEV_DEPENDENCY[packageManager(projectDir)]} ${DMS_PACKAGE}${version}`;
114
+ return `${ADD_DEV_DEPENDENCY.get(packageManager(projectDir))} ${DMS_PACKAGE}${version}`;
83
115
  }
84
116
  function describeDirectory(directory) {
85
117
  return (0, node_path_1.resolve)(directory) === process.cwd()
@@ -107,6 +139,12 @@ function installedCoreLayer(projectDir) {
107
139
  ],
108
140
  });
109
141
  const { version } = readManifest(packagePath);
142
+ if (version === undefined)
143
+ throw new common_1.UsageError({
144
+ title: `${DMS_PACKAGE} has no text "version" in its package.json`,
145
+ reason: `${(0, output_1.showPath)(packagePath)} has no "version" that is text, and the verification names the installed ${DMS_PACKAGE} by it.`,
146
+ fixes: [`Reinstall it: ${addDmsCommand(projectDir, "@latest")}`],
147
+ });
110
148
  const root = (0, node_path_1.join)((0, node_path_1.dirname)(packagePath), CORE_LAYER_DIRECTORY);
111
149
  const layer = (0, node_fs_1.existsSync)((0, node_path_1.join)(root, "package.json"))
112
150
  ? readLayer(root)
@@ -95,7 +95,7 @@ async function installDeps(workspaceDir, mapLine, extraArgs = []) {
95
95
  task.succeed(progress.doneLabel);
96
96
  }
97
97
  catch (error) {
98
- (0, output_1.endInterruptedTask)(task, error, "Dependency install stopped");
98
+ (0, output_1.endInterruptedTask)(task, error instanceof cancellation_1.CancelledError ? error : undefined, "Dependency install stopped");
99
99
  throw error;
100
100
  }
101
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms-frontend",
3
- "version": "0.9.2",
3
+ "version": "0.10.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,9 +76,7 @@
76
76
  "prepare": "if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && command -v lefthook >/dev/null 2>&1; then lefthook install; fi",
77
77
  "checks": "antelopejs-checks",
78
78
  "size": "antelopejs-check-size",
79
- "ratchet": "antelopejs-ratchet",
80
79
  "sync": "antelopejs-sync",
81
- "ratchet:update": "antelopejs-ratchet --update",
82
80
  "sync:check": "antelopejs-sync --check",
83
81
  "format:check": "oxfmt --check .",
84
82
  "suppressions": "antelopejs-suppressions",
@@ -92,7 +90,8 @@
92
90
  "semver": "^7.7.4"
93
91
  },
94
92
  "devDependencies": {
95
- "@antelopejs/core": "^1.16.0",
93
+ "@antelopejs/core": "^1.20.0",
94
+ "@antelopejs/interface-dms": "^0.10.0",
96
95
  "@antelopejs/tooling-configs": "^0.2.17",
97
96
  "@iconify/utils": "^3.1.0",
98
97
  "@iconify/vue": "^5.0.1",
@@ -141,6 +140,9 @@
141
140
  }
142
141
  },
143
142
  "antelopeJs": {
143
+ "docs": {
144
+ "standard": true
145
+ },
144
146
  "skills": [
145
147
  "./skills"
146
148
  ]
@@ -40,7 +40,7 @@ tags: [frontend, cli, renderer, vue, inertia, ssr, deployment, dms-module]
40
40
 
41
41
  | Task | Page |
42
42
  | --- | --- |
43
- | Compatibility with DMS releases | `/docs/frontend/introduction#compatibility` |
43
+ | Where to read the renderer releases a DMS release runs on | `/docs/frontend/introduction#compatibility` |
44
44
  | One command in depth | `/docs/frontend/cli/<command>` |
45
45
  | Variables and secrets | `/docs/frontend/configuration` |
46
46
  | Module files and SDK | `/docs/frontend/frontend-modules/anatomy`, then `server-routes`, `sessions-and-sign-in`, `route-rules`, `emails` |
@@ -4,11 +4,11 @@ import { useToast } from "@nuxt/ui/composables/useToast";
4
4
  import { useAppConfig } from "@nuxt/ui/runtime/vue/composables/useAppConfig";
5
5
  import { en as uiEn, fr as uiFr } from "@nuxt/ui/locale";
6
6
  import ui from "@nuxt/ui/vue-plugin";
7
+ import { router } from "@inertiajs/vue3";
7
8
  import { useHead } from "@unhead/vue";
8
9
  import type { VueHeadClient } from "@unhead/vue/types";
9
10
  import type { IconifyIcon } from "@iconify/vue";
10
11
  import { parse } from "devalue";
11
- import type { FetchOptions } from "ofetch";
12
12
  import {
13
13
  type App,
14
14
  type Component,
@@ -27,6 +27,7 @@ import {
27
27
  type DmsLocaleLoader,
28
28
  type DmsPageProps,
29
29
  type DmsPayload,
30
+ type DmsServerFetch,
30
31
  type DmsUser,
31
32
  createDmsFrontendRuntime,
32
33
  dmsPageSuspenseProps,
@@ -64,7 +65,13 @@ import {
64
65
  supportedLocales,
65
66
  syncLocaleMessages,
66
67
  } from "./locales.generated";
67
- import { showNetworkErrors } from "./network-error";
68
+ import {
69
+ type DmsMessageSubset,
70
+ SSR_MESSAGES_ID,
71
+ recordMessageReads,
72
+ } from "./message-subset";
73
+ import { NETWORK_ERROR_KEYS, showNetworkErrors } from "./network-error";
74
+ import { loadIconBundle } from "./icon-bundle";
68
75
  import { createUiAppConfigMerger } from "./ui-app-config";
69
76
 
70
77
  export interface DmsInertiaSetupProps {
@@ -86,17 +93,20 @@ export interface DmsAppOptions {
86
93
  }
87
94
 
88
95
  export interface DmsConfiguredApp {
89
- /** The locale of the catalog the app shows its messages from now. */
96
+ /**
97
+ * The locale of the catalog the app shows its messages from now, none when
98
+ * the page is served the messages it read alone.
99
+ */
90
100
  catalogLocale: () => string | undefined;
101
+ /**
102
+ * On the server, for a page of a visitor who is not signed in: the messages
103
+ * the render read, which the page carries instead of the whole catalog.
104
+ */
105
+ messageSubset: () => DmsMessageSubset | undefined;
91
106
  mounted: () => Promise<void>;
92
107
  runtime: ReturnType<typeof createDmsFrontendRuntime>;
93
108
  }
94
109
 
95
- export type DmsServerFetch = (
96
- request: string,
97
- options?: FetchOptions,
98
- ) => Promise<unknown>;
99
-
100
110
  export const DMS_PAYLOAD_ID = "dms-payload";
101
111
  export const SSR_ASYNC_COMPONENTS_ID = "dms-ssr-async-components";
102
112
  const DEFAULT_LOCALE = "en";
@@ -141,9 +151,7 @@ drawLoadedIconsAtOnce();
141
151
  function createBrowserRuntime(
142
152
  serverFetch?: DmsServerFetch,
143
153
  ): DmsConfiguredApp["runtime"] {
144
- const runtime = createDmsFrontendRuntime(
145
- serverFetch as typeof import("ofetch").ofetch,
146
- );
154
+ const runtime = createDmsFrontendRuntime(serverFetch);
147
155
  // Nothing when the server did not render the page: no hydration then.
148
156
  const payload = readServerRendered<DmsPayload | undefined>(
149
157
  DMS_PAYLOAD_ID,
@@ -238,8 +246,18 @@ const DmsPersistentInertiaPage = Object.assign(DmsInertiaPage, {
238
246
  layout: DmsPersistentLayout,
239
247
  });
240
248
 
241
- export function resolveDmsInertiaPage(): Component {
242
- return DmsPersistentInertiaPage;
249
+ /**
250
+ * What a visit waits for before it shows its page, set when the first one
251
+ * starts from a page that began without what the pages after it read: a
252
+ * subset of the catalog, the icon bundle. Inertia also resolves the first page
253
+ * once the app is set up, which must not wait for it.
254
+ */
255
+ let completeInitialAssets: (() => Promise<void>) | undefined;
256
+
257
+ export function resolveDmsInertiaPage(): Component | Promise<Component> {
258
+ return completeInitialAssets
259
+ ? completeInitialAssets().then(() => DmsPersistentInertiaPage)
260
+ : DmsPersistentInertiaPage;
243
261
  }
244
262
 
245
263
  function renderDmsPage(
@@ -310,13 +328,66 @@ export async function configureDmsApp(
310
328
  userLocale(options.initialPageProps.user) ??
311
329
  readDmsLocaleCookie(runtime, supportedLocales) ??
312
330
  DEFAULT_LOCALE;
313
- const messages = await loadLocaleMessages(locale);
331
+ const anonymous = !options.initialPageProps.user;
332
+ const subset = anonymous
333
+ ? readServerRendered<DmsMessageSubset | undefined>(
334
+ SSR_MESSAGES_ID,
335
+ undefined,
336
+ )
337
+ : undefined;
338
+ const started = subset?.locale === locale ? subset : undefined;
339
+ const messages = started?.messages ?? (await loadLocaleMessages(locale));
340
+ const recorder =
341
+ anonymous && typeof window === "undefined"
342
+ ? recordMessageReads(messages)
343
+ : undefined;
344
+ let loading: Promise<void> | undefined;
314
345
  const i18n = createI18n({
315
346
  legacy: false,
316
347
  locale,
317
348
  fallbackLocale: DEFAULT_LOCALE,
318
- messages: { ...localeMessages, [locale]: messages },
349
+ messages: started
350
+ ? { [locale]: messages }
351
+ : { ...localeMessages, [locale]: recorder?.messages ?? messages },
352
+ ...(started && {
353
+ missing: (_locale: string, key: string) => {
354
+ if (i18n.global.locale.value !== locale) return key;
355
+ void loadCompleteCatalog();
356
+ return "";
357
+ },
358
+ }),
319
359
  });
360
+ function loadCompleteCatalog(): Promise<void> {
361
+ if (i18n.global.locale.value !== locale) return Promise.resolve();
362
+ loading ??= loadLocaleMessages(locale).then(
363
+ (complete) => {
364
+ i18n.global.setLocaleMessage(locale, complete);
365
+ i18n.global.setMissingHandler(null);
366
+ },
367
+ () => {
368
+ loading = undefined;
369
+ },
370
+ );
371
+ return loading;
372
+ }
373
+ if (anonymous && typeof window !== "undefined") {
374
+ const complete = async (): Promise<void> => {
375
+ await Promise.all([
376
+ started ? loadCompleteCatalog() : undefined,
377
+ loadIconBundle(),
378
+ ]);
379
+ };
380
+ router.on("before", () => {
381
+ completeInitialAssets = complete;
382
+ void complete();
383
+ });
384
+ for (const type of ["pointerdown", "keydown", "focusin", "touchstart"])
385
+ addEventListener(type, () => void loadIconBundle(), {
386
+ once: true,
387
+ capture: true,
388
+ });
389
+ }
390
+ if (recorder) for (const key of NETWORK_ERROR_KEYS) i18n.global.te(key);
320
391
  // A translation changed in a layer during development reaches the page
321
392
  // without a reload. A server render imports the new catalogs instead.
322
393
  if (typeof window !== "undefined") syncLocaleMessages(i18n.global);
@@ -339,6 +410,7 @@ export async function configureDmsApp(
339
410
  // rendered: the page, its layout, and the components the render reached.
340
411
  // Hydration then adopts the server-rendered markup instead of discarding it.
341
412
  await Promise.all([
413
+ anonymous ? undefined : loadIconBundle(),
342
414
  preloadDmsPage(options.initialPageProps),
343
415
  resolveDmsAsyncComponents(
344
416
  readServerRendered<string[]>(SSR_ASYNC_COMPONENTS_ID, []),
@@ -352,7 +424,9 @@ export async function configureDmsApp(
352
424
  supportedLocales,
353
425
  );
354
426
  return {
355
- catalogLocale: () => catalogLocale(i18n.global.locale.value),
427
+ catalogLocale: () =>
428
+ recorder ? undefined : catalogLocale(i18n.global.locale.value),
429
+ messageSubset: () => recorder && { locale, messages: recorder.subset() },
356
430
  mounted,
357
431
  runtime,
358
432
  };