@antelopejs/dms-frontend 0.9.3 → 0.10.1

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 (140) hide show
  1. package/README.md +46 -29
  2. package/dist/commands/build-action.d.ts +3 -0
  3. package/dist/commands/build-action.js +77 -8
  4. package/dist/commands/build.js +7 -0
  5. package/dist/commands/clean-action.js +1 -1
  6. package/dist/commands/clean.js +1 -1
  7. package/dist/commands/dev-action.js +4 -3
  8. package/dist/commands/dev.js +1 -1
  9. package/dist/commands/prepare-action.js +1 -1
  10. package/dist/commands/start-action.js +3 -24
  11. package/dist/commands/workspace-task.js +2 -1
  12. package/dist/config.js +1 -0
  13. package/dist/derived-outputs.d.ts +1 -0
  14. package/dist/derived-outputs.js +6 -4
  15. package/dist/discovery.js +17 -6
  16. package/dist/entry-weight.js +15 -0
  17. package/dist/fs-sync.js +7 -4
  18. package/dist/help.js +1 -1
  19. package/dist/index.js +2 -1
  20. package/dist/internal/built-workspace.d.ts +4 -0
  21. package/dist/internal/built-workspace.js +43 -0
  22. package/dist/internal/ensure.d.ts +1 -0
  23. package/dist/internal/ensure.js +8 -0
  24. package/dist/internal/locale-catalogs.d.ts +8 -3
  25. package/dist/internal/locale-catalogs.js +16 -6
  26. package/dist/internal/manifest-decoder.d.ts +7 -0
  27. package/dist/internal/manifest-decoder.js +81 -0
  28. package/dist/internal/own-release.d.ts +5 -0
  29. package/dist/internal/own-release.js +20 -0
  30. package/dist/internal/plain-object.d.ts +1 -0
  31. package/dist/internal/plain-object.js +6 -0
  32. package/dist/internal/prepared-workspace.d.ts +12 -0
  33. package/dist/internal/prepared-workspace.js +74 -0
  34. package/dist/internal/text-list.d.ts +1 -0
  35. package/dist/internal/text-list.js +10 -0
  36. package/dist/internal/vite-manifest.d.ts +6 -0
  37. package/dist/internal/vite-manifest.js +24 -0
  38. package/dist/layer-watch.js +25 -24
  39. package/dist/layers.d.ts +1 -1
  40. package/dist/layers.js +3 -1
  41. package/dist/locale-files.d.ts +6 -3
  42. package/dist/locale-files.js +15 -9
  43. package/dist/manifest.d.ts +1 -1
  44. package/dist/manifest.js +46 -32
  45. package/dist/materialize.js +6 -2
  46. package/dist/module-set.js +2 -1
  47. package/dist/node-options.d.ts +1 -0
  48. package/dist/node-options.js +8 -0
  49. package/dist/output.d.ts +15 -9
  50. package/dist/output.js +30 -18
  51. package/dist/ports.d.ts +3 -1
  52. package/dist/ports.js +1 -1
  53. package/dist/private-values.d.ts +1 -1
  54. package/dist/private-values.js +5 -3
  55. package/dist/project-modules.d.ts +5 -1
  56. package/dist/project-modules.js +23 -15
  57. package/dist/renderer-range.js +5 -3
  58. package/dist/server-process.js +1 -1
  59. package/dist/verify-source-build.js +10 -2
  60. package/dist/verify-source-client-bundle.d.ts +1 -0
  61. package/dist/verify-source-client-bundle.js +30 -0
  62. package/dist/verify-source-runner.js +36 -94
  63. package/dist/verify-source-sources.js +49 -11
  64. package/dist/verify-source-workspace.d.ts +2 -0
  65. package/dist/verify-source-workspace.js +111 -0
  66. package/dist/workspace-setup.d.ts +1 -0
  67. package/dist/workspace-setup.js +4 -3
  68. package/package.json +11 -7
  69. package/skills/dms-frontend/SKILL.md +1 -1
  70. package/templates/vue/app-runtime.ts +2 -9
  71. package/templates/vue/backend-boundary.mjs +132 -0
  72. package/templates/vue/client-chunking.mjs +2 -0
  73. package/templates/vue/client-server-guard.mjs +16 -0
  74. package/templates/vue/compress-assets.mjs +9 -1
  75. package/templates/vue/entry-weight.mjs +29 -3
  76. package/templates/vue/frontend-build.ts +4 -3
  77. package/templates/vue/frontend-module.ts +1 -0
  78. package/templates/vue/frontend-runtime/module-registration.ts +2 -2
  79. package/templates/vue/frontend-runtime/state.ts +2 -2
  80. package/templates/vue/frontend-runtime/types.ts +8 -2
  81. package/templates/vue/head-order.mjs +10 -2
  82. package/templates/vue/icon-api.mjs +2 -0
  83. package/templates/vue/icon-scan.mjs +4 -0
  84. package/templates/vue/server/auth/backend.mjs +42 -5
  85. package/templates/vue/server/auth/client-ip.mjs +52 -4
  86. package/templates/vue/server/auth/establish-endpoints.mjs +11 -7
  87. package/templates/vue/server/auth/session.mjs +86 -4
  88. package/templates/vue/server/auth-declarations.mjs +46 -0
  89. package/templates/vue/server/auth-hooks.mjs +55 -7
  90. package/templates/vue/server/backend-proxies.mjs +78 -18
  91. package/templates/vue/server/backend-request.mjs +61 -0
  92. package/templates/vue/server/backend-response.mjs +23 -6
  93. package/templates/vue/server/built-files.mjs +7 -0
  94. package/templates/vue/server/cli-report.mjs +75 -12
  95. package/templates/vue/server/client-error.mjs +5 -0
  96. package/templates/vue/server/client-manifest.mjs +23 -1
  97. package/templates/vue/server/content.mjs +40 -10
  98. package/templates/vue/server/dev-private-files.mjs +17 -1
  99. package/templates/vue/server/dev-styles.mjs +38 -0
  100. package/templates/vue/server/email-bundle-watch.mjs +9 -3
  101. package/templates/vue/server/email.mjs +14 -6
  102. package/templates/vue/server/favicon.mjs +3 -0
  103. package/templates/vue/server/generated-files.mjs +221 -0
  104. package/templates/vue/server/homepage.mjs +21 -9
  105. package/templates/vue/server/html-tag.mjs +4 -0
  106. package/templates/vue/server/icons.mjs +93 -16
  107. package/templates/vue/server/in-place-render.mjs +35 -0
  108. package/templates/vue/server/inertia.mjs +76 -3
  109. package/templates/vue/server/module-event.mjs +118 -25
  110. package/templates/vue/server/module-route-types.mjs +78 -0
  111. package/templates/vue/server/module-routes.mjs +151 -96
  112. package/templates/vue/server/module-server.mjs +121 -40
  113. package/templates/vue/server/module-testing.mjs +36 -12
  114. package/templates/vue/server/page-props.mjs +94 -3
  115. package/templates/vue/server/page-redirects.mjs +37 -0
  116. package/templates/vue/server/path-patterns.mjs +35 -12
  117. package/templates/vue/server/proxy-compression.mjs +22 -0
  118. package/templates/vue/server/proxy-headers.mjs +12 -2
  119. package/templates/vue/server/proxy-redirects.mjs +21 -5
  120. package/templates/vue/server/proxy-request.mjs +58 -0
  121. package/templates/vue/server/proxy-transfers.mjs +44 -10
  122. package/templates/vue/server/public-endpoints.mjs +139 -28
  123. package/templates/vue/server/public-options.mjs +20 -6
  124. package/templates/vue/server/request-failure.mjs +24 -1
  125. package/templates/vue/server/request-routing.mjs +32 -10
  126. package/templates/vue/server/request-target.mjs +30 -11
  127. package/templates/vue/server/route-rules.mjs +48 -18
  128. package/templates/vue/server/ssr-module.mjs +32 -0
  129. package/templates/vue/server/static-files.mjs +35 -7
  130. package/templates/vue/server/type-guards.mjs +71 -0
  131. package/templates/vue/server/vite-errors.mjs +45 -7
  132. package/templates/vue/server/web-body.mjs +5 -1
  133. package/templates/vue/server-module.ts +12 -2
  134. package/templates/vue/server.mjs +162 -157
  135. package/templates/vue/sfc-global-types.mjs +112 -13
  136. package/templates/vue/ssr-renderer.ts +2 -2
  137. package/templates/vue/ui-inertia-page.mjs +16 -1
  138. package/templates/vue/ui-kbd.mjs +2 -1
  139. package/templates/vue/ui-request-state.mjs +2 -1
  140. package/templates/vue/vite.config.ts +9 -0
package/README.md CHANGED
@@ -1,36 +1,30 @@
1
- <div align="center">
1
+ <a href="https://github.com/AntelopeJS/dms"><img data-illustration src="./.github/readme/strip.svg" width="100%" alt="DMS FRONTEND: part of AntelopeJS DMS, the open-source back office. A glowing button reads Star AntelopeJS DMS."></a>
2
2
 
3
- <picture>
4
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/AntelopeJS/dms/main/packages/dms/frontend-vue/layers/dms-layout/public/images/antelope-logo/dark.svg">
5
- <img alt="Antelope DMS" src="https://raw.githubusercontent.com/AntelopeJS/dms/main/packages/dms/frontend-vue/layers/dms-layout/public/images/antelope-logo/light.svg" height="72">
6
- </picture>
3
+ <div align="center">
7
4
 
8
- # @antelopejs/dms-frontend
5
+ # `@antelopejs/dms-frontend`
9
6
 
10
- <a href="https://www.npmjs.com/package/@antelopejs/dms-frontend"><img alt="npm" src="https://img.shields.io/npm/v/@antelopejs/dms-frontend?style=for-the-badge&labelColor=000000&color=000000"></a>
11
- <a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/@antelopejs/dms-frontend?style=for-the-badge&labelColor=000000&color=000000"></a>
12
- <a href="https://discord.gg/g4pFt6eXqu"><img alt="Discord" src="https://img.shields.io/badge/Discord-18181B?logo=discord&style=for-the-badge&color=000000"></a>
13
- <a href="https://dms.antelopejs.com/docs/frontend"><img alt="Documentation" src="https://img.shields.io/badge/Docs-18181B?style=for-the-badge&color=000000"></a>
7
+ <a href="https://www.npmjs.com/package/@antelopejs/dms-frontend"><img alt="npm" src="https://img.shields.io/npm/v/@antelopejs/dms-frontend?style=for-the-badge&labelColor=000000&color=000000" data-illustration></a> <a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/@antelopejs/dms-frontend?style=for-the-badge&labelColor=000000&color=000000" data-illustration></a> <a href="https://discord.gg/g4pFt6eXqu"><img alt="Discord" src="https://img.shields.io/badge/Discord-18181B?logo=discord&style=for-the-badge&color=000000" data-illustration></a> <a href="https://dms.antelopejs.com/docs/frontend"><img alt="Documentation" src="https://img.shields.io/badge/Docs-18181B?style=for-the-badge&color=000000" data-illustration></a> <a href="https://github.com/AntelopeJS/dms"><img data-illustration alt="Star AntelopeJS DMS" src="https://img.shields.io/github/stars/AntelopeJS/dms?label=Star%20AntelopeJS%20DMS&style=for-the-badge&labelColor=000000&color=000000&logo=github"></a>
14
8
 
15
9
  **The frontend of the AntelopeJS DMS: the `ajs dms` CLI, the Vue renderer and the frontend-module SDK.**
16
10
 
17
- [Documentation](https://dms.antelopejs.com/docs/frontend) · [DMS](https://github.com/AntelopeJS/dms) · [Modules](https://dms.antelopejs.com/docs/modules)
11
+ [Documentation](https://dms.antelopejs.com/docs/frontend) · [All modules](https://github.com/AntelopeJS/dms#modules)
18
12
 
19
13
  </div>
20
14
 
21
- ![The browser talks to the frontend server, which renders pages, answers module routes and proxies declared backend prefixes to the DMS backend; at build time ajs dms fetches the manifest and writes a workspace](./docs/images/architecture.svg)
15
+ ![The browser talks to the frontend server, which renders pages, answers module routes and proxies declared backend prefixes to the DMS backend, while the development and prepare commands fetch the manifest and write a workspace](./docs/images/architecture.svg "illustration")
22
16
 
23
17
  ## What it does
24
18
 
25
- - Asks a DMS backend which frontend modules it loads, materializes them into a generated workspace, builds it, and runs the frontend server.
26
- - Renders the dashboard with Vue 3, Vite, Inertia and server-side rendering.
27
- - Keeps the backend token on the server: the browser only holds a sealed, `HttpOnly` session cookie, and declared backend prefixes are proxied with the session's token.
19
+ - Reads the frontend modules of an AntelopeJS project, materializes them into a generated workspace, builds it, and runs the frontend server.
20
+ - Renders the dashboard with Vue, Vite, Inertia and server-side rendering.
21
+ - Keeps the backend token on the server. The browser only holds a sealed, `HttpOnly` session cookie, and the server adds the session's token to each request it proxies to a declared backend prefix.
28
22
  - Lets a module extend the dashboard with pages, components and plugins (`dms.frontend.ts`), server routes (`dms.server.ts`) and build-time declarations (`dms.frontend.build.ts`).
29
23
  - Serves development with hot reload, and production with pre-compressed assets.
30
24
 
31
25
  ## Install
32
26
 
33
- Add the package next to `@antelopejs/core`; the core `ajs` command runs it as `ajs dms`:
27
+ Add the package next to `@antelopejs/core`. The core `ajs` command runs it as `ajs dms`.
34
28
 
35
29
  ```bash
36
30
  pnpm add -D @antelopejs/core @antelopejs/dms-frontend
@@ -38,20 +32,20 @@ pnpm add -D @antelopejs/core @antelopejs/dms-frontend
38
32
 
39
33
  ## Quick start
40
34
 
41
- In a DMS project started with `ajs project run -w`, serve the dashboard with hot reload:
35
+ In a DMS project started with `ajs project dev -w`, serve the dashboard with hot reload:
42
36
 
43
37
  ```bash
44
38
  pnpm exec ajs dms dev
45
39
  ```
46
40
 
47
- For production, build from the project root, without the backend, then start the frontend server with `DMS_SESSION_SECRET` and the backend's secrets set:
41
+ For production, build from the project root, without the backend. Then start the frontend server with `DMS_SESSION_SECRET` and the backend's secrets set:
48
42
 
49
43
  ```bash
50
44
  pnpm exec ajs dms build
51
45
  pnpm exec ajs dms start -b https://api.example.com -p 3001
52
46
  ```
53
47
 
54
- `ajs dms --help` lists every command, and `ajs dms help environment` every environment variable.
48
+ `ajs dms --help` lists every command, and `ajs dms help environment` lists every environment variable.
55
49
 
56
50
  ## Compatibility
57
51
 
@@ -59,17 +53,11 @@ pnpm exec ajs dms start -b https://api.example.com -p 3001
59
53
 
60
54
  | Package | Supported | Tested with |
61
55
  | ------------------ | ------------- | ----------- |
62
- | `@antelopejs/core` | `>=1.16.0 <2` | `1.16.0` |
56
+ | `@antelopejs/core` | `>=1.16.0 <2` | `1.21.0` |
63
57
 
64
58
  <!-- antelopejs-docs-compat:end -->
65
59
 
66
- | `@antelopejs/dms` | `@antelopejs/dms-frontend` |
67
- | --- | --- |
68
- | 0.9.1 and later | `>=0.6.1 <0.7.0` |
69
- | 0.9.0 | `>=0.6.0 <0.7.0` |
70
- | 0.6.0 – 0.8.3 | 0.5 (exact floor per release in the full table) |
71
-
72
- The 0.6 line needs Node.js 20.19 or later and `@antelopejs/core` `>=1.13.2 <2`. The [compatibility table](https://dms.antelopejs.com/docs/frontend/introduction#compatibility) lists every release.
60
+ Each DMS release declares the renderer releases it runs on in `engines["@antelopejs/dms-frontend"]` of its frontend module's `package.json`. The `ajs dms` CLI refuses to build a module whose range excludes the installed renderer. To find the renderer releases that match a DMS release, read that `engines` range in the installed DMS frontend module, as the [Compatibility](https://dms.antelopejs.com/docs/frontend/introduction#compatibility) section of the introduction explains. The renderer needs the `@antelopejs/core` range in the table above, and the `engines` field of its `package.json` sets the Node.js versions it runs on.
73
61
 
74
62
  ## Documentation
75
63
 
@@ -90,13 +78,42 @@ Read [CONTRIBUTING.md](./CONTRIBUTING.md) before opening a pull request.
90
78
  ```bash
91
79
  pnpm install
92
80
  pnpm build
93
- pnpm lint # oxlint, oxfmt, interface ranges and the docs reference check
81
+ pnpm lint # oxlint, interface ranges, the docs reference check and the source checks
94
82
  pnpm typecheck
95
83
  pnpm test
96
84
  ```
97
85
 
98
- `pnpm docs:reference` regenerates the environment-variable table of `docs/4.configuration.md` from the list `ajs dms help environment` prints; `pnpm lint` fails when the table is stale.
86
+ `pnpm docs:reference` regenerates the environment-variable table of `docs/4.configuration.md` from the list `ajs dms help environment` prints. `pnpm lint` fails when the table is stale.
87
+
88
+ ## Part of AntelopeJS DMS
89
+
90
+ This package is the frontend of [AntelopeJS DMS](https://github.com/AntelopeJS/dms), the open-source back office. It renders the dashboard that every module adds its pages to, behind the same sign-in and roles.
91
+
92
+ <!-- antelopejs-dms-modules:start -->
93
+
94
+ | Module | What it adds |
95
+ | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
96
+ | [`dms-builder`](https://github.com/AntelopeJS/dms-builder) | Edit pages, tables and data sources by clicking, as TypeScript that compiles |
97
+ | [`dms-ai`](https://github.com/AntelopeJS/dms-ai) | An assistant chat in the dashboard, on your own Claude Code or Codex |
98
+ | [`dms-database`](https://github.com/AntelopeJS/dms-database) | Schemas, relation diagrams, a data browser and a query console |
99
+ | [`dms-api`](https://github.com/AntelopeJS/dms-api) | API health, request logs, per-route statistics and a request tester |
100
+ | [`dms-automation`](https://github.com/AntelopeJS/dms-automation) | Node-graph automations on schedules, webhooks and DMS events, with a trace for every run |
101
+ | [`dms-mailing`](https://github.com/AntelopeJS/dms-mailing) | E-mail templates in a block editor, versions, a send log and delivery metrics |
102
+ | [`dms-lang`](https://github.com/AntelopeJS/dms-lang) | Translation coverage, a translation matrix and a translation queue |
103
+ | [`dms-media`](https://github.com/AntelopeJS/dms-media) | A media library with folders, per-folder permissions, image sizes and light editing |
104
+ | [`dms-saas`](https://github.com/AntelopeJS/dms-saas) | Self-service sign-up, plans that gate features, and Stripe billing |
105
+ | [`dms-marketing`](https://github.com/AntelopeJS/dms-marketing) | Traffic, conversion funnels with A/B splits and click heatmaps, without cookies |
106
+ | [`dms-frontend`](https://github.com/AntelopeJS/dms-frontend) | The `ajs dms` CLI, the Vue renderer and the SDK every frontend module builds on |
107
+
108
+ <!-- antelopejs-dms-modules:end -->
99
109
 
100
110
  ## License
101
111
 
102
112
  [Apache-2.0](./LICENSE)
113
+
114
+ ## Requirements
115
+
116
+ <!-- antelopejs-docs-node:start -->
117
+
118
+ This package requires Node.js `>=20.19.0`.
119
+ <!-- antelopejs-docs-node:end -->
@@ -5,6 +5,9 @@ export interface BuildOptions {
5
5
  env: string;
6
6
  workspace?: string;
7
7
  force?: boolean;
8
+ setupOnly?: boolean;
9
+ install?: boolean;
10
+ prepared?: boolean;
8
11
  }
9
12
  export declare const DEFAULT_WORKSPACE_DIR = ".dms-frontend";
10
13
  export declare function productionWorkspace(options: {
@@ -6,12 +6,77 @@ exports.buildStepFailure = buildStepFailure;
6
6
  exports.runBuild = runBuild;
7
7
  const node_path_1 = require("node:path");
8
8
  const cli_1 = require("@antelopejs/core/cli");
9
+ const config_1 = require("../config");
10
+ const prepared_workspace_1 = require("../internal/prepared-workspace");
9
11
  const child_output_1 = require("../child-output");
10
12
  const common_1 = require("../common");
11
13
  const output_1 = require("../output");
12
14
  const vite_output_1 = require("../vite-output");
13
15
  const entry_weight_1 = require("../entry-weight");
14
16
  const workspace_task_1 = require("./workspace-task");
17
+ const node_options_1 = require("../node-options");
18
+ const NOT_PREPARED_EXIT_CODE = 3;
19
+ function buildPlan(options) {
20
+ const install = options.install !== false;
21
+ if (options.setupOnly)
22
+ return { kind: "setup", install };
23
+ if (!install)
24
+ throw new config_1.UsageError({
25
+ title: "--no-install needs --setup-only",
26
+ fixes: ["Run ajs dms build --setup-only --no-install"],
27
+ });
28
+ return options.prepared ? { kind: "prepared" } : { kind: "full" };
29
+ }
30
+ function workspaceFlag(options) {
31
+ return options.workspace ? ` --workspace ${options.workspace}` : "";
32
+ }
33
+ async function setUpProductionWorkspace(options, workspace, install) {
34
+ (0, prepared_workspace_1.forgetPreparedLayers)(workspace);
35
+ const { workspaceDir, layers } = await (0, workspace_task_1.setUpWorkspace)({
36
+ force: !!options.force,
37
+ mode: "build",
38
+ workspaceDir: workspace,
39
+ project: { projectDir: options.project, environment: options.env },
40
+ install,
41
+ });
42
+ (0, prepared_workspace_1.recordPreparedLayers)(workspaceDir, layers);
43
+ return { workspaceDir, layers };
44
+ }
45
+ function setupSummary(options, install, workspaceDir, startedAt) {
46
+ const flag = workspaceFlag(options);
47
+ return {
48
+ headline: install
49
+ ? "Set up the production workspace"
50
+ : "Wrote the production workspace without installing it",
51
+ artifact: (0, output_1.showWorkspace)(workspaceDir),
52
+ durationMs: Date.now() - startedAt,
53
+ nextSteps: [
54
+ install
55
+ ? {
56
+ command: `ajs dms build --prepared${flag}`,
57
+ description: "builds this workspace as it is",
58
+ }
59
+ : {
60
+ command: `ajs dms build --setup-only${flag}`,
61
+ description: "installs its dependencies",
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ function preparedProductionWorkspace(options, workspace) {
67
+ const workspaceDir = (0, node_path_1.resolve)(workspace);
68
+ const found = (0, prepared_workspace_1.readPreparedWorkspace)(workspaceDir);
69
+ if (found.prepared)
70
+ return { workspaceDir, layers: found.layers };
71
+ throw new cli_1.CliError({
72
+ title: `${(0, output_1.showWorkspace)(workspaceDir)} is not prepared`,
73
+ reason: found.reason,
74
+ fixes: [
75
+ `Run ajs dms build --setup-only${workspaceFlag(options)} first, or build without --prepared`,
76
+ ],
77
+ exitCode: NOT_PREPARED_EXIT_CODE,
78
+ });
79
+ }
15
80
  exports.DEFAULT_WORKSPACE_DIR = ".dms-frontend";
16
81
  function productionWorkspace(options) {
17
82
  return options.workspace ?? (0, node_path_1.join)(options.project, exports.DEFAULT_WORKSPACE_DIR);
@@ -87,7 +152,7 @@ async function runBuildStep(step, context) {
87
152
  });
88
153
  }
89
154
  catch (error) {
90
- (0, output_1.endInterruptedTask)(task, error, step.stopped);
155
+ (0, output_1.endInterruptedTask)(task, error instanceof common_1.CancelledError ? error : undefined, step.stopped);
91
156
  throw error;
92
157
  }
93
158
  if (result.code !== 0) {
@@ -108,16 +173,20 @@ async function runBuild(options) {
108
173
  }
109
174
  }
110
175
  async function buildProduction(options, startedAt) {
176
+ const plan = buildPlan(options);
111
177
  (0, entry_weight_1.entryWeightThreshold)(process.env);
112
178
  const ui = (0, cli_1.getProcessUi)();
113
179
  const workspace = productionWorkspace(options);
114
180
  (0, output_1.writeHeader)("build", [options.project, options.env]);
115
- const { workspaceDir, layers } = await (0, workspace_task_1.setUpWorkspace)({
116
- force: !!options.force,
117
- mode: "build",
118
- workspaceDir: workspace,
119
- project: { projectDir: options.project, environment: options.env },
120
- });
181
+ if (plan.kind === "setup") {
182
+ const { workspaceDir } = await setUpProductionWorkspace(options, workspace, plan.install);
183
+ (0, output_1.writeBlankLine)();
184
+ ui.summary(setupSummary(options, plan.install, workspaceDir, startedAt));
185
+ return;
186
+ }
187
+ const { workspaceDir, layers } = plan.kind === "prepared"
188
+ ? preparedProductionWorkspace(options, workspace)
189
+ : await setUpProductionWorkspace(options, workspace, true);
121
190
  ui.message("info", "Building for production", {
122
191
  detail: `Workspace ${(0, output_1.showWorkspace)(workspaceDir)}`,
123
192
  });
@@ -130,7 +199,7 @@ async function buildProduction(options, startedAt) {
130
199
  nameFile: (0, child_output_1.createFileNamer)(layers),
131
200
  env: {
132
201
  ...process.env,
133
- NODE_OPTIONS: "--max-old-space-size=4096",
202
+ NODE_OPTIONS: (0, node_options_1.serverNodeOptions)(process.env.NODE_OPTIONS),
134
203
  NODE_PATH: (0, node_path_1.join)(workspaceDir, "node_modules"),
135
204
  [vite_output_1.VITE_LOG_LEVEL_VARIABLE]: (0, vite_output_1.viteLogLevel)(isVerbose),
136
205
  },
@@ -46,6 +46,10 @@ const BUILD_EXAMPLES = [
46
46
  description: "With another environment's configuration, into a directory",
47
47
  command: "ajs dms build -e staging --workspace /srv/dms-frontend",
48
48
  },
49
+ {
50
+ description: "Set the workspace up and install it, then build it later as it is",
51
+ command: "ajs dms build --setup-only && ajs dms build --prepared",
52
+ },
49
53
  ];
50
54
  function cmdBuild() {
51
55
  const command = new commander_1.Command("build")
@@ -55,6 +59,9 @@ function cmdBuild() {
55
59
  .addOption(config_1.Options.env)
56
60
  .addOption(config_1.Options.workspace("Build into this directory instead of <project>/.dms-frontend"))
57
61
  .addOption(config_1.Options.force("Reinstall the workspace dependencies"))
62
+ .addOption(new commander_1.Option("--setup-only", "Set the workspace up and install its dependencies, then stop before the bundles"))
63
+ .addOption(new commander_1.Option("--no-install", "With --setup-only, write the workspace without installing its dependencies"))
64
+ .addOption(new commander_1.Option("--prepared", "Build the workspace --setup-only set up, as it is, without setting it up again; exit 3 when it is not prepared").conflicts(["setupOnly", "force"]))
58
65
  .action(async (options) => (await Promise.resolve().then(() => __importStar(require("./build-action")))).runBuild(options));
59
66
  return (0, cli_1.withExamples)(command, BUILD_EXAMPLES);
60
67
  }
@@ -79,7 +79,7 @@ async function runClean(options) {
79
79
  title: "Nothing to clean: pass -b <url> or --all",
80
80
  reason: "clean never uses DMS_API_BASE_URL from the environment or ./.env.",
81
81
  fixes: [
82
- "Remove the workspace build, start and dev -b share for a URL: ajs dms clean -b <url>",
82
+ "Remove the workspace dev -b and prepare use for a URL: ajs dms clean -b <url>",
83
83
  "Remove every workspace, including those dev created without -b: ajs dms clean --all",
84
84
  "List the workspaces: ajs dms workspaces",
85
85
  ],
@@ -38,7 +38,7 @@ const commander_1 = require("commander");
38
38
  const cli_1 = require("@antelopejs/core/cli");
39
39
  const CLEAN_EXAMPLES = [
40
40
  {
41
- description: "The workspace build, start and dev -b share for a backend",
41
+ description: "The workspace dev -b and prepare use for a backend",
42
42
  command: "ajs dms clean -b https://dms.example.com",
43
43
  },
44
44
  {
@@ -14,6 +14,7 @@ const manifest_1 = require("../manifest");
14
14
  const module_set_1 = require("../module-set");
15
15
  const project_modules_1 = require("../project-modules");
16
16
  const workspace_task_1 = require("./workspace-task");
17
+ const node_options_1 = require("../node-options");
17
18
  const BASE_ENVIRONMENT = "default";
18
19
  function clientHost() {
19
20
  const host = process.env.HOST?.trim();
@@ -65,7 +66,7 @@ async function runDev(options) {
65
66
  bootstrapSecret,
66
67
  project,
67
68
  });
68
- if (port !== requestedPort) {
69
+ if (reserved.isFallback) {
69
70
  ui.message("warn", `Port ${requestedPort} is busy, using ${port}`);
70
71
  }
71
72
  if (manifestFromCache) {
@@ -119,7 +120,7 @@ function backendModuleSet(setup) {
119
120
  function reportServed(session, setup) {
120
121
  return (0, module_set_1.reportFrontendReady)(session.backendUrl, setup.layers.map((layer) => ({
121
122
  name: layer.name ?? layer.path,
122
- version: (0, project_modules_1.readShippingVersion)(layer.path),
123
+ version: (0, project_modules_1.readShippingPackage)(layer.path).version,
123
124
  })), session.bootstrapSecret);
124
125
  }
125
126
  async function serveWorkspace(session, setup) {
@@ -175,7 +176,7 @@ function runDevServer(session, setup, stopSignal) {
175
176
  DMS_BOOTSTRAP_SECRET: session.bootstrapSecret,
176
177
  DMS_SESSION_SECRET: session.sessionSecret,
177
178
  ...secrets.env,
178
- NODE_OPTIONS: "--max-old-space-size=4096",
179
+ NODE_OPTIONS: (0, node_options_1.serverNodeOptions)(process.env.NODE_OPTIONS),
179
180
  NODE_PATH: nodeModulesDir,
180
181
  },
181
182
  onReady: (address) => {
@@ -43,7 +43,7 @@ const DEV_EXAMPLES = [
43
43
  command: "ajs dms dev",
44
44
  },
45
45
  {
46
- description: "Share one workspace with build and start",
46
+ description: "Run in the workspace prepare uses for that backend",
47
47
  command: "ajs dms dev -b http://localhost:5010 -p 3001",
48
48
  },
49
49
  ];
@@ -44,7 +44,7 @@ async function runPrepare(options) {
44
44
  catch (err) {
45
45
  if (err instanceof common_1.CancelledError || strict)
46
46
  throw err;
47
- const [title, ...details] = (0, output_1.failureDetails)(err, ui);
47
+ const [title, ...details] = (0, output_1.failureDetails)((0, output_1.classifyFailure)(err), ui);
48
48
  const outcome = isSkip(err) ? "Skipped prepare" : "Prepare failed";
49
49
  ui.message("warn", `${outcome}: ${title}`, {
50
50
  details: [...details, NOT_GENERATED, STRICT_HINT],
@@ -11,22 +11,13 @@ const server_process_1 = require("../server-process");
11
11
  const update_check_1 = require("../update-check");
12
12
  const module_set_1 = require("../module-set");
13
13
  const project_modules_1 = require("../project-modules");
14
- const config_1 = require("../config");
14
+ const built_workspace_1 = require("../internal/built-workspace");
15
15
  const manifest_secrets_1 = require("../manifest-secrets");
16
16
  const private_values_1 = require("../private-values");
17
17
  const build_action_1 = require("./build-action");
18
18
  const PUBLIC_OPTIONS_VARIABLE = "DMS_PUBLIC_OPTIONS";
19
- function builtPrivateOptions(workspaceDir) {
20
- try {
21
- const { modules } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(workspaceDir, config_1.SERVER_OPTIONS_FILE), "utf8"));
22
- return modules ?? {};
23
- }
24
- catch {
25
- return {};
26
- }
27
- }
28
19
  function reportUnfilledPrivateValues(workspaceDir) {
29
- const unfilled = (0, private_values_1.unfilledPrivateValues)(builtPrivateOptions(workspaceDir), (0, private_values_1.parsePrivateOptions)(process.env[private_values_1.PRIVATE_OPTIONS_VARIABLE]), Object.values(manifest_secrets_1.MANIFEST_SECRET_ENV));
20
+ const unfilled = (0, private_values_1.unfilledPrivateValues)((0, built_workspace_1.readBuiltPrivateOptions)(workspaceDir), (0, private_values_1.parsePrivateOptions)(process.env[private_values_1.PRIVATE_OPTIONS_VARIABLE]), Object.values(manifest_secrets_1.MANIFEST_SECRET_ENV));
30
21
  const ui = (0, cli_1.getProcessUi)();
31
22
  unfilled.forEach(({ module, path }, index) => {
32
23
  const isLast = index === unfilled.length - 1;
@@ -61,18 +52,6 @@ function publicOptionsById(modules) {
61
52
  ];
62
53
  }));
63
54
  }
64
- function builtModules(workspaceDir) {
65
- try {
66
- const { modules } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(workspaceDir, "generated-frontend-modules.json"), "utf8"));
67
- return (modules ?? []).map((module) => ({
68
- name: module.name ?? module.id,
69
- version: module.version,
70
- }));
71
- }
72
- catch {
73
- return [];
74
- }
75
- }
76
55
  function reportModuleSetDrift(built, modules) {
77
56
  const builtNames = new Set(built.map(({ name }) => name));
78
57
  const currentNames = new Set(modules.map(({ layer }) => layer.name ?? layer.path));
@@ -145,7 +124,7 @@ async function runStart(options) {
145
124
  projectDir: options.project,
146
125
  environment: options.env,
147
126
  });
148
- const built = builtModules(workspaceDir);
127
+ const built = (0, built_workspace_1.readBuiltModules)(workspaceDir);
149
128
  reportModuleSetDrift(built, modules);
150
129
  const secrets = (0, common_1.resolveManifestSecrets)({ published: {}, conflicts: [] });
151
130
  (0, common_1.reportManifestSecrets)(secrets, (count) => `Set ${count === 1 ? "it" : "them"} in the server's environment, with the value${count === 1 ? "" : "s"} the backend uses`);
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setUpWorkspace = setUpWorkspace;
4
4
  const cli_1 = require("@antelopejs/core/cli");
5
+ const cancellation_1 = require("../cancellation");
5
6
  const common_1 = require("../common");
6
7
  const output_1 = require("../output");
7
8
  async function setUpWorkspace(options) {
@@ -20,7 +21,7 @@ async function setUpWorkspace(options) {
20
21
  return result;
21
22
  }
22
23
  catch (error) {
23
- (0, output_1.endInterruptedTask)(task, error, "Workspace setup stopped");
24
+ (0, output_1.endInterruptedTask)(task, error instanceof cancellation_1.CancelledError ? error : undefined, "Workspace setup stopped");
24
25
  throw error;
25
26
  }
26
27
  }
package/dist/config.js CHANGED
@@ -67,6 +67,7 @@ exports.TEMPLATE_FILES = [
67
67
  ["head-order.mjs", "head-order.mjs"],
68
68
  ["client-chunking.mjs", "client-chunking.mjs"],
69
69
  ["entry-weight.mjs", "entry-weight.mjs"],
70
+ ["backend-boundary.mjs", "backend-boundary.mjs"],
70
71
  ["client-server-guard.mjs", "client-server-guard.mjs"],
71
72
  ["ui-inertia-page.mjs", "ui-inertia-page.mjs"],
72
73
  ["ui-request-state.mjs", "ui-request-state.mjs"],
@@ -4,6 +4,7 @@ export interface FrontendModuleRegistryEntry {
4
4
  name?: string;
5
5
  packageName?: string;
6
6
  version?: string;
7
+ backendPackage?: string;
7
8
  root: string;
8
9
  priority: number;
9
10
  entry?: string;
@@ -41,11 +41,13 @@ function createFrontendModuleRegistry(workspaceDir, layers) {
41
41
  const buildEntry = (0, node_path_1.join)(layer.path, config_1.FRONTEND_BUILD_ENTRY);
42
42
  const serverEntry = (0, node_path_1.join)(layer.path, config_1.SERVER_MODULE_ENTRY);
43
43
  const options = layer.options ?? {};
44
+ const shipping = (0, project_modules_1.readShippingPackage)(layer.sourcePath ?? layer.path);
44
45
  return {
45
46
  id: (0, layers_1.getLayerSafeName)(layer),
46
47
  name: layer.name ?? layer.packageName,
47
48
  packageName: layer.packageName,
48
- version: (0, project_modules_1.readShippingVersion)(layer.sourcePath ?? layer.path),
49
+ version: shipping.version,
50
+ backendPackage: shipping.name,
49
51
  root: (0, layers_1.getLayerWorkspacePath)(workspaceDir, layer),
50
52
  priority: layer.priority ?? 0,
51
53
  entry: (0, node_fs_1.existsSync)(sourceEntry) ? config_1.FRONTEND_MODULE_ENTRY : undefined,
@@ -140,16 +142,16 @@ function discoverAssets(registry, directory, extensions) {
140
142
  const LOCALE_DIRECTORY = "i18n/locales";
141
143
  function readLocaleMessages(assets, registry) {
142
144
  const roots = new Map(registry.modules.map((module) => [module.id, module.root]));
143
- const messages = {};
145
+ const messages = new Map();
144
146
  for (const asset of assets) {
145
147
  const locale = asset.relativePath.match(/(?:^|[/-])([a-z]{2})-[A-Z]{2}\.json$/)?.[1];
146
148
  const root = roots.get(asset.moduleId);
147
149
  if (!locale || !root)
148
150
  continue;
149
151
  const value = (0, locale_files_1.readLocaleFile)((0, node_path_1.join)(root, asset.relativePath));
150
- messages[locale] = (0, locale_catalogs_1.mergeLocaleMessages)(value, messages[locale] ?? {});
152
+ messages.set(locale, (0, locale_catalogs_1.mergeLocaleMessages)(value, messages.get(locale) ?? {}));
151
153
  }
152
- return messages;
154
+ return Object.fromEntries(messages);
153
155
  }
154
156
  function affectsLocaleMessages(relativePath) {
155
157
  return (0, asset_roots_1.affectsAssetDirectory)(LOCALE_DIRECTORY, relativePath);
package/dist/discovery.js CHANGED
@@ -10,6 +10,7 @@ const node_fs_1 = require("node:fs");
10
10
  const node_path_1 = require("node:path");
11
11
  const output_1 = require("./output");
12
12
  const guards_1 = require("./internal/guards");
13
+ const plain_object_1 = require("./internal/plain-object");
13
14
  exports.DEV_REGISTRY_RELATIVE_PATH = (0, node_path_1.join)(".antelope", "dev.json");
14
15
  function isPidAlive(pid) {
15
16
  if (!Number.isInteger(pid) || pid <= 0)
@@ -22,13 +23,23 @@ function isPidAlive(pid) {
22
23
  return err?.code === "EPERM";
23
24
  }
24
25
  }
26
+ function isEndpoint(value) {
27
+ return ((0, plain_object_1.isPlainObject)(value) &&
28
+ (0, guards_1.isText)(value.protocol) &&
29
+ (0, guards_1.isText)(value.host) &&
30
+ (0, guards_1.isNumber)(value.port));
31
+ }
32
+ function isServer(value) {
33
+ return ((0, plain_object_1.isPlainObject)(value) &&
34
+ Array.isArray(value.endpoints) &&
35
+ value.endpoints.every(isEndpoint));
36
+ }
25
37
  function isDevRegistry(value) {
26
- return ((0, guards_1.isObjectLike)(value) &&
27
- "pid" in value &&
28
- typeof value.pid === "number" &&
29
- "servers" in value &&
30
- typeof value.servers === "object" &&
31
- value.servers !== null);
38
+ return ((0, plain_object_1.isPlainObject)(value) &&
39
+ (0, guards_1.isNumber)(value.pid) &&
40
+ (value.startedAt === undefined || (0, guards_1.isText)(value.startedAt)) &&
41
+ (0, plain_object_1.isPlainObject)(value.servers) &&
42
+ Object.values(value.servers).every(isServer));
32
43
  }
33
44
  function parseRegistry(file) {
34
45
  try {
@@ -10,6 +10,8 @@ const node_fs_1 = require("node:fs");
10
10
  const node_path_1 = require("node:path");
11
11
  const node_zlib_1 = require("node:zlib");
12
12
  const cli_1 = require("@antelopejs/core/cli");
13
+ const guards_1 = require("./internal/guards");
14
+ const plain_object_1 = require("./internal/plain-object");
13
15
  const output_1 = require("./output");
14
16
  exports.ENTRY_WEIGHT_VARIABLE = "DMS_ENTRY_WEIGHT_WARNING";
15
17
  exports.DEFAULT_ENTRY_WEIGHT_WARNING_KB = 50;
@@ -18,6 +20,14 @@ const SHARED_OWNER = "shared";
18
20
  const TRANSLATIONS_OWNER = "translations";
19
21
  const NON_MODULE_OWNERS = new Set([SHARED_OWNER, TRANSLATIONS_OWNER]);
20
22
  const BYTES_PER_KB = 1024;
23
+ function isRecordedEntryWeight(value) {
24
+ return ((0, plain_object_1.isPlainObject)(value) &&
25
+ Array.isArray(value.files) &&
26
+ value.files.every(guards_1.isText) &&
27
+ (0, guards_1.isNumber)(value.bytes) &&
28
+ (0, plain_object_1.isPlainObject)(value.owners) &&
29
+ Object.values(value.owners).every(guards_1.isNumber));
30
+ }
21
31
  function brotliSize(file) {
22
32
  const compressed = `${file}.br`;
23
33
  if ((0, node_fs_1.existsSync)(compressed))
@@ -31,6 +41,11 @@ function readEntryWeight(workspaceDir) {
31
41
  if (!(0, node_fs_1.existsSync)(path))
32
42
  return undefined;
33
43
  const recorded = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
44
+ if (!isRecordedEntryWeight(recorded))
45
+ throw new cli_1.CliError({
46
+ title: "The client build recorded an entry weight this CLI cannot read",
47
+ fixes: ["Build again"],
48
+ });
34
49
  const brotli = recorded.files.reduce((sum, file) => sum + brotliSize((0, node_path_1.join)(workspaceDir, "dist", "client", file)), 0);
35
50
  const ratio = recorded.bytes ? brotli / recorded.bytes : 0;
36
51
  const shares = Object.entries(recorded.owners)
package/dist/fs-sync.js CHANGED
@@ -18,6 +18,7 @@ const node_fs_1 = require("node:fs");
18
18
  const node_path_1 = require("node:path");
19
19
  const ignore_1 = __importDefault(require("ignore"));
20
20
  const config_1 = require("./config");
21
+ const plain_object_1 = require("./internal/plain-object");
21
22
  function stripExtendedLengthPrefix(p) {
22
23
  return p.replace(/^\\\\\?\\/, "");
23
24
  }
@@ -32,10 +33,12 @@ function isBlocklistedCopyPath(src, srcPath) {
32
33
  }
33
34
  function sanitizedPackageContent(raw) {
34
35
  const pkg = JSON.parse(raw);
35
- delete pkg.devDependencies;
36
- if (pkg.scripts) {
37
- for (const name of config_1.PNPM_LIFECYCLE_SCRIPTS) {
38
- delete pkg.scripts[name];
36
+ if ((0, plain_object_1.isPlainObject)(pkg)) {
37
+ delete pkg.devDependencies;
38
+ if ((0, plain_object_1.isPlainObject)(pkg.scripts)) {
39
+ for (const name of config_1.PNPM_LIFECYCLE_SCRIPTS) {
40
+ delete pkg.scripts[name];
41
+ }
39
42
  }
40
43
  }
41
44
  return `${JSON.stringify(pkg, null, 2)}\n`;
package/dist/help.js CHANGED
@@ -81,7 +81,7 @@ exports.ENVIRONMENT_VARIABLES = [
81
81
  ];
82
82
  const ENVIRONMENT_NOTES = [
83
83
  "A variable set in the shell wins over both files, and .env.local wins over .env. Only the current directory is read: the generated workspace never loads a .env of its own.",
84
- "Workspaces live in ~/.antelopejs/dms-frontend, one per backend URL. dev without -b keys its own on the project directory instead; pass -b to share one with build and start. ajs dms workspaces lists them.",
84
+ "dev and prepare keep their workspaces in ~/.antelopejs/dms-frontend, one per backend URL, and ajs dms workspaces lists them. dev without -b keys its own on the project directory instead. build and start use <project>/.dms-frontend, or --workspace.",
85
85
  ];
86
86
  function subcommandTerm(command) {
87
87
  const args = command.registeredArguments.map((argument) => argument.required ? `<${argument.name()}>` : `[${argument.name()}]`);
package/dist/index.js CHANGED
@@ -12,10 +12,11 @@ const start_1 = require("./commands/start");
12
12
  const verify_source_1 = require("./commands/verify-source");
13
13
  const workspaces_1 = require("./commands/workspaces");
14
14
  const env_file_1 = require("./env-file");
15
+ const own_release_1 = require("./internal/own-release");
15
16
  const help_1 = require("./help");
16
17
  const output_1 = require("./output");
17
18
  const update_check_1 = require("./update-check");
18
- const { version } = require("../package.json");
19
+ const { version } = own_release_1.ownRelease;
19
20
  const QUIET_FLAGS = ["-q", "--quiet"];
20
21
  const GLOBAL_FLAGS = ["--no-color", ...QUIET_FLAGS];
21
22
  const VERBOSE_FLAG = "--verbose";
@@ -0,0 +1,4 @@
1
+ import type { ServedModule } from "../module-set";
2
+ import type { FrontendModuleOptions } from "../workspace";
3
+ export declare function readBuiltPrivateOptions(workspaceDir: string): Record<string, FrontendModuleOptions>;
4
+ export declare function readBuiltModules(workspaceDir: string): ServedModule[];