@antelopejs/dms-frontend 0.9.3 → 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 (116) 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/derived-outputs.js +3 -3
  8. package/dist/discovery.js +17 -6
  9. package/dist/entry-weight.js +15 -0
  10. package/dist/fs-sync.js +7 -4
  11. package/dist/index.js +2 -1
  12. package/dist/internal/built-workspace.d.ts +4 -0
  13. package/dist/internal/built-workspace.js +43 -0
  14. package/dist/internal/ensure.d.ts +1 -0
  15. package/dist/internal/ensure.js +8 -0
  16. package/dist/internal/locale-catalogs.d.ts +8 -3
  17. package/dist/internal/locale-catalogs.js +16 -6
  18. package/dist/internal/manifest-decoder.d.ts +7 -0
  19. package/dist/internal/manifest-decoder.js +81 -0
  20. package/dist/internal/own-release.d.ts +5 -0
  21. package/dist/internal/own-release.js +20 -0
  22. package/dist/internal/plain-object.d.ts +1 -0
  23. package/dist/internal/plain-object.js +6 -0
  24. package/dist/internal/text-list.d.ts +1 -0
  25. package/dist/internal/text-list.js +10 -0
  26. package/dist/internal/vite-manifest.d.ts +6 -0
  27. package/dist/internal/vite-manifest.js +24 -0
  28. package/dist/layer-watch.js +25 -24
  29. package/dist/layers.d.ts +1 -1
  30. package/dist/layers.js +3 -1
  31. package/dist/locale-files.d.ts +2 -1
  32. package/dist/locale-files.js +4 -0
  33. package/dist/manifest.d.ts +1 -1
  34. package/dist/manifest.js +44 -31
  35. package/dist/materialize.js +6 -2
  36. package/dist/module-set.js +2 -1
  37. package/dist/node-options.d.ts +1 -0
  38. package/dist/node-options.js +8 -0
  39. package/dist/output.d.ts +15 -9
  40. package/dist/output.js +30 -18
  41. package/dist/private-values.d.ts +1 -1
  42. package/dist/private-values.js +5 -3
  43. package/dist/project-modules.js +13 -8
  44. package/dist/renderer-range.js +5 -3
  45. package/dist/server-process.js +1 -1
  46. package/dist/verify-source-build.js +10 -2
  47. package/dist/verify-source-client-bundle.d.ts +1 -0
  48. package/dist/verify-source-client-bundle.js +30 -0
  49. package/dist/verify-source-runner.js +44 -60
  50. package/dist/verify-source-sources.js +49 -11
  51. package/dist/workspace-setup.js +1 -1
  52. package/package.json +6 -4
  53. package/skills/dms-frontend/SKILL.md +1 -1
  54. package/templates/vue/app-runtime.ts +2 -9
  55. package/templates/vue/client-server-guard.mjs +16 -0
  56. package/templates/vue/compress-assets.mjs +9 -1
  57. package/templates/vue/entry-weight.mjs +22 -0
  58. package/templates/vue/frontend-module.ts +1 -0
  59. package/templates/vue/frontend-runtime/module-registration.ts +2 -2
  60. package/templates/vue/frontend-runtime/state.ts +2 -2
  61. package/templates/vue/frontend-runtime/types.ts +8 -2
  62. package/templates/vue/head-order.mjs +7 -0
  63. package/templates/vue/icon-scan.mjs +4 -0
  64. package/templates/vue/server/auth/backend.mjs +41 -4
  65. package/templates/vue/server/auth/client-ip.mjs +52 -4
  66. package/templates/vue/server/auth/establish-endpoints.mjs +10 -6
  67. package/templates/vue/server/auth/session.mjs +81 -2
  68. package/templates/vue/server/auth-hooks.mjs +15 -5
  69. package/templates/vue/server/backend-proxies.mjs +65 -9
  70. package/templates/vue/server/backend-request.mjs +61 -0
  71. package/templates/vue/server/backend-response.mjs +23 -6
  72. package/templates/vue/server/built-files.mjs +7 -0
  73. package/templates/vue/server/cli-report.mjs +53 -11
  74. package/templates/vue/server/client-manifest.mjs +23 -1
  75. package/templates/vue/server/content.mjs +18 -1
  76. package/templates/vue/server/dev-private-files.mjs +17 -1
  77. package/templates/vue/server/dev-styles.mjs +38 -0
  78. package/templates/vue/server/email.mjs +9 -3
  79. package/templates/vue/server/favicon.mjs +3 -0
  80. package/templates/vue/server/generated-files.mjs +211 -0
  81. package/templates/vue/server/homepage.mjs +21 -9
  82. package/templates/vue/server/html-tag.mjs +4 -0
  83. package/templates/vue/server/icons.mjs +80 -7
  84. package/templates/vue/server/in-place-render.mjs +35 -0
  85. package/templates/vue/server/inertia.mjs +61 -2
  86. package/templates/vue/server/module-event.mjs +98 -17
  87. package/templates/vue/server/module-route-types.mjs +78 -0
  88. package/templates/vue/server/module-routes.mjs +138 -31
  89. package/templates/vue/server/module-server.mjs +98 -27
  90. package/templates/vue/server/module-testing.mjs +14 -5
  91. package/templates/vue/server/page-props.mjs +92 -2
  92. package/templates/vue/server/page-redirects.mjs +37 -0
  93. package/templates/vue/server/path-patterns.mjs +13 -1
  94. package/templates/vue/server/proxy-compression.mjs +22 -0
  95. package/templates/vue/server/proxy-headers.mjs +11 -1
  96. package/templates/vue/server/proxy-redirects.mjs +20 -5
  97. package/templates/vue/server/proxy-request.mjs +57 -0
  98. package/templates/vue/server/proxy-transfers.mjs +33 -5
  99. package/templates/vue/server/public-endpoints.mjs +138 -23
  100. package/templates/vue/server/public-options.mjs +20 -6
  101. package/templates/vue/server/request-failure.mjs +23 -0
  102. package/templates/vue/server/request-routing.mjs +26 -9
  103. package/templates/vue/server/request-target.mjs +9 -3
  104. package/templates/vue/server/route-rules.mjs +39 -15
  105. package/templates/vue/server/ssr-module.mjs +32 -0
  106. package/templates/vue/server/static-files.mjs +24 -1
  107. package/templates/vue/server/type-guards.mjs +71 -0
  108. package/templates/vue/server/vite-errors.mjs +32 -5
  109. package/templates/vue/server/web-body.mjs +3 -0
  110. package/templates/vue/server-module.ts +10 -1
  111. package/templates/vue/server.mjs +132 -148
  112. package/templates/vue/sfc-global-types.mjs +112 -13
  113. package/templates/vue/ssr-renderer.ts +2 -2
  114. package/templates/vue/ui-inertia-page.mjs +16 -1
  115. package/templates/vue/ui-kbd.mjs +2 -1
  116. package/templates/vue/ui-request-state.mjs +2 -1
@@ -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.3",
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` |
@@ -9,7 +9,6 @@ import { useHead } from "@unhead/vue";
9
9
  import type { VueHeadClient } from "@unhead/vue/types";
10
10
  import type { IconifyIcon } from "@iconify/vue";
11
11
  import { parse } from "devalue";
12
- import type { FetchOptions } from "ofetch";
13
12
  import {
14
13
  type App,
15
14
  type Component,
@@ -28,6 +27,7 @@ import {
28
27
  type DmsLocaleLoader,
29
28
  type DmsPageProps,
30
29
  type DmsPayload,
30
+ type DmsServerFetch,
31
31
  type DmsUser,
32
32
  createDmsFrontendRuntime,
33
33
  dmsPageSuspenseProps,
@@ -107,11 +107,6 @@ export interface DmsConfiguredApp {
107
107
  runtime: ReturnType<typeof createDmsFrontendRuntime>;
108
108
  }
109
109
 
110
- export type DmsServerFetch = (
111
- request: string,
112
- options?: FetchOptions,
113
- ) => Promise<unknown>;
114
-
115
110
  export const DMS_PAYLOAD_ID = "dms-payload";
116
111
  export const SSR_ASYNC_COMPONENTS_ID = "dms-ssr-async-components";
117
112
  const DEFAULT_LOCALE = "en";
@@ -156,9 +151,7 @@ drawLoadedIconsAtOnce();
156
151
  function createBrowserRuntime(
157
152
  serverFetch?: DmsServerFetch,
158
153
  ): DmsConfiguredApp["runtime"] {
159
- const runtime = createDmsFrontendRuntime(
160
- serverFetch as typeof import("ofetch").ofetch,
161
- );
154
+ const runtime = createDmsFrontendRuntime(serverFetch);
162
155
  // Nothing when the server did not render the page: no hydration then.
163
156
  const payload = readServerRendered<DmsPayload | undefined>(
164
157
  DMS_PAYLOAD_ID,
@@ -2,11 +2,20 @@ import { relative, sep } from "node:path";
2
2
 
3
3
  const SERVER_ENTRY = /(?:^|[\\/])dms\.server\.[cm]?[jt]sx?$/;
4
4
 
5
+ /**
6
+ * @param {string} id
7
+ * @returns {string}
8
+ */
5
9
  function stripQuery(id) {
6
10
  const query = id.indexOf("?");
7
11
  return query === -1 ? id : id.slice(0, query);
8
12
  }
9
13
 
14
+ /**
15
+ * @param {string} file
16
+ * @param {readonly string[]} moduleRoots
17
+ * @returns {boolean}
18
+ */
10
19
  function insideModuleServerDirectory(file, moduleRoots) {
11
20
  return moduleRoots.some((root) => {
12
21
  const path = relative(root, file);
@@ -20,6 +29,10 @@ function insideModuleServerDirectory(file, moduleRoots) {
20
29
  * Whether `id`, a resolved module id, is server code: a module's server entry
21
30
  * (`dms.server.*`) or a file under a `server/` directory of a module. Installed
22
31
  * packages never are: `node_modules` has its own `server` directories.
32
+ *
33
+ * @param {string} id
34
+ * @param {readonly string[]} moduleRoots
35
+ * @returns {boolean}
23
36
  */
24
37
  export function isServerSource(id, moduleRoots) {
25
38
  if (id.startsWith("\0")) return false;
@@ -44,6 +57,9 @@ export function isServerSource(id, moduleRoots) {
44
57
  * import; the guard sits in the module graph instead, where every resolved file
45
58
  * passes. Kept beside vite.config.ts rather than inside it so the plugin can be
46
59
  * exercised on its own, the way head-order.mjs is.
60
+ *
61
+ * @param {readonly string[]} moduleRoots
62
+ * @returns {import("vite").Plugin}
47
63
  */
48
64
  export function rejectServerSourcesInClient(moduleRoots) {
49
65
  let ssrBuild = false;
@@ -16,10 +16,14 @@ const MINIMUM_SIZE_BYTES = 1_024;
16
16
  const compressBrotli = promisify(brotliCompress);
17
17
  const compressGzip = promisify(gzip);
18
18
 
19
+ /**
20
+ * @param {string} directory
21
+ * @returns {Promise<string[]>}
22
+ */
19
23
  async function assetFiles(directory) {
20
24
  const entries = await readdir(directory, { withFileTypes: true });
21
25
  const files = await Promise.all(
22
- entries.map((entry) => {
26
+ entries.map(async (entry) => {
23
27
  const path = join(directory, entry.name);
24
28
  return entry.isDirectory() ? assetFiles(path) : [path];
25
29
  }),
@@ -27,6 +31,10 @@ async function assetFiles(directory) {
27
31
  return files.flat();
28
32
  }
29
33
 
34
+ /**
35
+ * @param {string} path
36
+ * @returns {Promise<void>}
37
+ */
30
38
  async function compressAsset(path) {
31
39
  if (!COMPRESSIBLE_EXTENSIONS.has(extname(path))) return;
32
40
  const content = await readFile(path);
@@ -10,11 +10,20 @@ export const TRANSLATIONS_OWNER = "translations";
10
10
 
11
11
  const LOCALE_CATALOG = /[\\/]locales\.generated[\\/]/;
12
12
 
13
+ /**
14
+ * @param {string} id
15
+ * @returns {string}
16
+ */
13
17
  function stripQuery(id) {
14
18
  const query = id.indexOf("?");
15
19
  return query === -1 ? id : id.slice(0, query);
16
20
  }
17
21
 
22
+ /**
23
+ * @param {string} root
24
+ * @param {string} file
25
+ * @returns {boolean}
26
+ */
18
27
  function isInside(root, file) {
19
28
  const path = relative(root, file);
20
29
  return Boolean(path) && !path.startsWith("..") && !path.startsWith(sep);
@@ -26,7 +35,9 @@ function isInside(root, file) {
26
35
  * what every page shares (the renderer's runtime, the UI and icon libraries,
27
36
  * an installed package).
28
37
  *
38
+ * @param {string} id
29
39
  * @param {{ root: string, name?: string, packageName?: string, id: string }[]} modules
40
+ * @returns {string}
30
41
  */
31
42
  export function entryOwner(id, modules) {
32
43
  if (id.startsWith("\0")) return SHARED_OWNER;
@@ -42,13 +53,18 @@ export function entryOwner(id, modules) {
42
53
  /**
43
54
  * The files a page loads before its first line runs: the entry chunks and
44
55
  * their static imports, transitively, by file name.
56
+ *
57
+ * @param {import("vite").Rollup.OutputBundle} bundle
58
+ * @returns {(import("vite").Rollup.OutputChunk | undefined)[]}
45
59
  */
46
60
  function entryClosure(bundle) {
47
61
  const chunks = Object.values(bundle).filter(
48
62
  (output) => output.type === "chunk",
49
63
  );
50
64
  const byName = new Map(chunks.map((chunk) => [chunk.fileName, chunk]));
65
+ /** @type {Set<string>} */
51
66
  const seen = new Set();
67
+ /** @param {import("vite").Rollup.OutputChunk | undefined} chunk */
52
68
  const visit = (chunk) => {
53
69
  if (!chunk || seen.has(chunk.fileName)) return;
54
70
  seen.add(chunk.fileName);
@@ -64,10 +80,13 @@ function entryClosure(bundle) {
64
80
  * rendered for each (`renderedLength`, before minification): an estimate that
65
81
  * needs nothing the bundle does not already hold.
66
82
  *
83
+ * @param {import("vite").Rollup.OutputBundle} bundle
84
+ * @param {{ root: string, name?: string, packageName?: string, id: string }[]} modules
67
85
  * @returns {{ files: string[], bytes: number, owners: Record<string, number> }}
68
86
  */
69
87
  export function attributeEntry(bundle, modules) {
70
88
  const files = entryClosure(bundle);
89
+ /** @type {Record<string, number>} */
71
90
  const owners = {};
72
91
  let bytes = 0;
73
92
  for (const chunk of files) {
@@ -109,6 +128,9 @@ export function attributeEntry(bundle, modules) {
109
128
  *
110
129
  * The weight is recorded once the bundle is written: plugins that generate the
111
130
  * bundle after this one still rewrite the chunks' code.
131
+ *
132
+ * @param {{ root: string, name?: string, packageName?: string, id: string }[]} modules
133
+ * @returns {import("vite").Plugin}
112
134
  */
113
135
  export function recordEntryWeight(modules) {
114
136
  let ssrBuild = false;
@@ -34,6 +34,7 @@ export type {
34
34
  DmsFetchTransformContext,
35
35
  UseAsyncDataOptions,
36
36
  UseFetchOptions,
37
+ DmsServerFetch,
37
38
  DmsErrorData,
38
39
  DmsPayload,
39
40
  DmsStateOptions,
@@ -1,5 +1,4 @@
1
1
  import { usePage } from "@inertiajs/vue3";
2
- import { ofetch } from "ofetch";
3
2
  import {
4
3
  type Component,
5
4
  type EffectScope,
@@ -19,6 +18,7 @@ import {
19
18
  type DmsPluginRegistrationOptions,
20
19
  type DmsPluginSetup,
21
20
  type DmsRoute,
21
+ type DmsServerFetch,
22
22
  type DmsSharedPagePayload,
23
23
  type RouteLocationRaw,
24
24
  } from "./types";
@@ -212,7 +212,7 @@ export interface DmsFrontendRuntime {
212
212
  /** Whether to warn about what a development render does wrong. */
213
213
  devWarnings?: boolean;
214
214
  runtimeHooks: Map<string, DmsRuntimeHook[]>;
215
- serverFetch?: typeof ofetch;
215
+ serverFetch?: DmsServerFetch;
216
216
  currentError: Ref<Error | null>;
217
217
  route: DmsRoute;
218
218
  currentRoutePattern?: string;