@antelopejs/dms-frontend 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +4 -4
  2. package/dist/commands/build-action.d.ts +8 -5
  3. package/dist/commands/build-action.js +16 -41
  4. package/dist/commands/build.js +9 -15
  5. package/dist/commands/dev-action.d.ts +2 -0
  6. package/dist/commands/dev-action.js +14 -3
  7. package/dist/commands/dev.js +3 -1
  8. package/dist/commands/start-action.d.ts +2 -0
  9. package/dist/commands/start-action.js +65 -47
  10. package/dist/commands/start.js +8 -6
  11. package/dist/common.d.ts +1 -0
  12. package/dist/common.js +1 -0
  13. package/dist/config.d.ts +2 -0
  14. package/dist/config.js +9 -0
  15. package/dist/derived-outputs.d.ts +1 -5
  16. package/dist/derived-outputs.js +48 -35
  17. package/dist/dev-modules.d.ts +5 -0
  18. package/dist/dev-modules.js +88 -0
  19. package/dist/entry-weight.d.ts +24 -0
  20. package/dist/entry-weight.js +86 -0
  21. package/dist/fs-sync.d.ts +1 -0
  22. package/dist/fs-sync.js +6 -0
  23. package/dist/help.js +12 -4
  24. package/dist/index.js +4 -6
  25. package/dist/layer-watch.js +7 -2
  26. package/dist/layers.d.ts +2 -3
  27. package/dist/layers.js +1 -95
  28. package/dist/locale-files.d.ts +6 -0
  29. package/dist/locale-files.js +75 -0
  30. package/dist/manifest.d.ts +8 -8
  31. package/dist/manifest.js +17 -39
  32. package/dist/materialize.js +6 -0
  33. package/dist/module-set.d.ts +14 -12
  34. package/dist/module-set.js +39 -31
  35. package/dist/private-values.d.ts +3 -2
  36. package/dist/private-values.js +16 -5
  37. package/dist/project-modules-child.d.ts +13 -0
  38. package/dist/project-modules-child.js +26 -0
  39. package/dist/project-modules.d.ts +47 -0
  40. package/dist/project-modules.js +268 -0
  41. package/dist/workspace-setup.d.ts +3 -7
  42. package/dist/workspace-setup.js +79 -62
  43. package/dist/workspace.d.ts +8 -4
  44. package/dist/workspace.js +1 -1
  45. package/package.json +13 -8
  46. package/skills/dms-frontend/SKILL.md +2 -2
  47. package/templates/vue/app-runtime.ts +8 -1
  48. package/templates/vue/client-chunking.mjs +61 -0
  49. package/templates/vue/email-renderer.ts +6 -1
  50. package/templates/vue/entry-weight.mjs +132 -0
  51. package/templates/vue/frontend-build-loader.ts +1 -1
  52. package/templates/vue/frontend-module.ts +168 -1
  53. package/templates/vue/gitignore +1 -0
  54. package/templates/vue/main.ts +32 -2
  55. package/templates/vue/network-error.ts +3 -1
  56. package/templates/vue/package.json +3 -3
  57. package/templates/vue/patches/@vueuse__core@14.4.0.patch +24 -0
  58. package/templates/vue/pnpm-workspace.yaml +5 -0
  59. package/templates/vue/prepare-types.mjs +2 -7
  60. package/templates/vue/public-options.ts +19 -0
  61. package/templates/vue/server/auth/backend.mjs +20 -3
  62. package/templates/vue/server/auth/client-ip.mjs +64 -22
  63. package/templates/vue/server/auth/session.mjs +4 -11
  64. package/templates/vue/server/backend-proxies.mjs +13 -2
  65. package/templates/vue/server/backend-response.mjs +2 -13
  66. package/templates/vue/server/cli-report.mjs +42 -18
  67. package/templates/vue/server/client-manifest.mjs +34 -58
  68. package/templates/vue/server/dev-private-files.mjs +1 -1
  69. package/templates/vue/server/dev-styles.mjs +16 -5
  70. package/templates/vue/server/homepage.mjs +4 -1
  71. package/templates/vue/server/in-place-render.mjs +110 -0
  72. package/templates/vue/server/module-event.mjs +23 -8
  73. package/templates/vue/server/module-server.mjs +4 -4
  74. package/templates/vue/server/module-testing.mjs +5 -3
  75. package/templates/vue/server/page-props.mjs +10 -0
  76. package/templates/vue/server/proxy-compression.mjs +15 -8
  77. package/templates/vue/server/proxy-headers.mjs +12 -1
  78. package/templates/vue/server/proxy-redirects.mjs +66 -0
  79. package/templates/vue/server/proxy-transfers.mjs +110 -0
  80. package/templates/vue/server/public-endpoints.mjs +2 -2
  81. package/templates/vue/server/public-options.mjs +49 -0
  82. package/templates/vue/server/request-routing.mjs +20 -0
  83. package/templates/vue/server/web-body.mjs +24 -0
  84. package/templates/vue/server-module.ts +19 -7
  85. package/templates/vue/server.mjs +98 -78
  86. package/templates/vue/sfc-global-types.mjs +119 -0
  87. package/templates/vue/ssr-renderer.ts +27 -1
  88. package/templates/vue/ui-kbd.mjs +45 -0
  89. package/templates/vue/vite.config.ts +104 -47
  90. package/templates/vue/vite.email.config.ts +6 -3
  91. package/templates/vue/vite.server.config.ts +5 -2
  92. package/dist/commands/snapshot-action.d.ts +0 -6
  93. package/dist/commands/snapshot-action.js +0 -53
  94. package/dist/commands/snapshot.d.ts +0 -2
  95. package/dist/commands/snapshot.js +0 -55
  96. package/dist/snapshot.d.ts +0 -25
  97. package/dist/snapshot.js +0 -90
  98. package/templates/vue/server/backend-wake.mjs +0 -129
package/README.md CHANGED
@@ -44,11 +44,11 @@ In a DMS project started with `ajs project run -w`, serve the dashboard with hot
44
44
  pnpm exec ajs dms dev
45
45
  ```
46
46
 
47
- For production, build against the backend, then start the frontend server, with `DMS_SESSION_SECRET` and `DMS_BOOTSTRAP_SECRET` set:
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:
48
48
 
49
49
  ```bash
50
- pnpm exec ajs dms build -b https://dms.example.com
51
- pnpm exec ajs dms start -b https://dms.example.com -p 3001
50
+ pnpm exec ajs dms build
51
+ pnpm exec ajs dms start -b https://api.example.com -p 3001
52
52
  ```
53
53
 
54
54
  `ajs dms --help` lists every command, and `ajs dms help environment` every environment variable.
@@ -61,7 +61,7 @@ pnpm exec ajs dms start -b https://dms.example.com -p 3001
61
61
  | 0.9.0 | `>=0.6.0 <0.7.0` |
62
62
  | 0.6.0 – 0.8.3 | 0.5 (exact floor per release in the full table) |
63
63
 
64
- The 0.6 line needs Node.js 20.12 or later and `@antelopejs/core` `>=1.13.2 <2`. The [compatibility table](https://dms.antelopejs.com/docs/frontend/introduction#compatibility) lists every release.
64
+ 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.
65
65
 
66
66
  ## Documentation
67
67
 
@@ -1,13 +1,16 @@
1
1
  import { CliError } from "@antelopejs/core/cli";
2
2
  import { type FramedCommandResult } from "../common";
3
3
  export interface BuildOptions {
4
- backendUrl?: string;
5
- force?: boolean;
6
- offline?: boolean;
7
- bootstrapSecret?: string;
8
- from?: string;
4
+ project: string;
5
+ env: string;
9
6
  workspace?: string;
7
+ force?: boolean;
10
8
  }
9
+ export declare const DEFAULT_WORKSPACE_DIR = ".dms-frontend";
10
+ export declare function productionWorkspace(options: {
11
+ project: string;
12
+ workspace?: string;
13
+ }): string;
11
14
  interface BuildStep {
12
15
  script: string;
13
16
  name: string;
@@ -1,34 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BUILD_STEPS = void 0;
3
+ exports.BUILD_STEPS = exports.DEFAULT_WORKSPACE_DIR = void 0;
4
+ exports.productionWorkspace = productionWorkspace;
4
5
  exports.buildStepFailure = buildStepFailure;
5
6
  exports.runBuild = runBuild;
6
7
  const node_path_1 = require("node:path");
7
8
  const cli_1 = require("@antelopejs/core/cli");
8
9
  const child_output_1 = require("../child-output");
9
10
  const common_1 = require("../common");
10
- const snapshot_1 = require("../snapshot");
11
11
  const output_1 = require("../output");
12
12
  const vite_output_1 = require("../vite-output");
13
+ const entry_weight_1 = require("../entry-weight");
13
14
  const workspace_task_1 = require("./workspace-task");
14
- function buildSource(options) {
15
- if (!options.from) {
16
- return { backendUrl: (0, common_1.requireBackendUrl)(options.backendUrl) };
17
- }
18
- if (options.offline) {
19
- throw new cli_1.CliError({
20
- title: "--from and --offline cannot be combined",
21
- reason: "A snapshot build never asks the backend already.",
22
- fixes: ["Drop --offline"],
23
- });
24
- }
25
- const snapshot = (0, snapshot_1.readSnapshot)(options.from);
26
- return {
27
- backendUrl: options.backendUrl
28
- ? (0, common_1.parseBackendUrl)(options.backendUrl)
29
- : snapshot.backendUrl,
30
- snapshot: { dir: options.from, snapshot },
31
- };
15
+ exports.DEFAULT_WORKSPACE_DIR = ".dms-frontend";
16
+ function productionWorkspace(options) {
17
+ return options.workspace ?? (0, node_path_1.join)(options.project, exports.DEFAULT_WORKSPACE_DIR);
32
18
  }
33
19
  exports.BUILD_STEPS = [
34
20
  {
@@ -122,27 +108,16 @@ async function runBuild(options) {
122
108
  }
123
109
  }
124
110
  async function buildProduction(options, startedAt) {
125
- const { backendUrl, snapshot } = buildSource(options);
126
- const bootstrapSecret = snapshot
127
- ? undefined
128
- : (0, common_1.normalizeBootstrapSecret)(options.bootstrapSecret);
111
+ (0, entry_weight_1.entryWeightThreshold)(process.env);
129
112
  const ui = (0, cli_1.getProcessUi)();
130
- (0, output_1.writeHeader)("build", [backendUrl, "production"]);
131
- const { workspaceDir, layers, manifestFromCache, manifestFetchedAt, buildOrigin, } = await (0, workspace_task_1.setUpWorkspace)({
132
- backendUrl,
113
+ const workspace = productionWorkspace(options);
114
+ (0, output_1.writeHeader)("build", [options.project, options.env]);
115
+ const { workspaceDir, layers } = await (0, workspace_task_1.setUpWorkspace)({
133
116
  force: !!options.force,
134
117
  mode: "build",
135
- offline: snapshot ? false : (0, common_1.flagOrEnv)(options.offline, "DMS_OFFLINE"),
136
- bootstrapSecret,
137
- workspaceDir: options.workspace,
138
- snapshot,
118
+ workspaceDir: workspace,
119
+ project: { projectDir: options.project, environment: options.env },
139
120
  });
140
- if (snapshot) {
141
- ui.message("info", `Building from the snapshot of ${snapshot.snapshot.backendUrl}${(0, output_1.cachedAge)(snapshot.snapshot.takenAt)}`, { detail: `Modules digest ${snapshot.snapshot.digest}` });
142
- }
143
- else if (manifestFromCache) {
144
- ui.message("warn", `Building from the manifest and layers archive${(0, output_1.cachedAge)(manifestFetchedAt)}`, { detail: "The output may not match the current backend." });
145
- }
146
121
  ui.message("info", "Building for production", {
147
122
  detail: `Workspace ${(0, output_1.showWorkspace)(workspaceDir)}`,
148
123
  });
@@ -162,8 +137,8 @@ async function buildProduction(options, startedAt) {
162
137
  };
163
138
  for (const step of exports.BUILD_STEPS)
164
139
  await runBuildStep(step, context);
165
- (0, snapshot_1.writeBuildOrigin)(workspaceDir, buildOrigin);
166
140
  (0, vite_output_1.reportViteWarnings)(context.warnings, isVerbose, ui);
141
+ (0, entry_weight_1.reportEntryWeight)(ui, workspaceDir, process.env);
167
142
  (0, output_1.writeBlankLine)();
168
143
  ui.summary({
169
144
  headline: "Built the production frontend",
@@ -172,9 +147,9 @@ async function buildProduction(options, startedAt) {
172
147
  nextSteps: [
173
148
  {
174
149
  command: options.workspace
175
- ? `ajs dms start -b ${backendUrl} --workspace ${options.workspace}`
176
- : `ajs dms start -b ${backendUrl}`,
177
- description: "with DMS_SESSION_SECRET and the server's secrets in its environment",
150
+ ? `ajs dms start -b <backend> --workspace ${options.workspace}`
151
+ : "ajs dms start -b <backend>",
152
+ description: "from the project, with DMS_SESSION_SECRET, the server's secrets and the deployment's variables in its environment",
178
153
  },
179
154
  ],
180
155
  });
@@ -39,28 +39,22 @@ const config_1 = require("../config");
39
39
  const cli_1 = require("@antelopejs/core/cli");
40
40
  const BUILD_EXAMPLES = [
41
41
  {
42
- description: "With DMS_BOOTSTRAP_SECRET set in the environment or ./.env",
43
- command: "ajs dms build -b https://dms.example.com",
42
+ description: "From the project root, into .dms-frontend: no backend, no secret, no network",
43
+ command: "ajs dms build",
44
44
  },
45
45
  {
46
- description: "From a snapshot, without the backend or any secret, into the project (a container image)",
47
- command: "ajs dms build --from dms-snapshot --workspace .dms-frontend",
48
- },
49
- {
50
- description: "Rebuild from the cache while the backend is down",
51
- command: "ajs dms build -b https://dms.example.com --offline",
46
+ description: "With another environment's configuration, into a directory",
47
+ command: "ajs dms build -e staging --workspace /srv/dms-frontend",
52
48
  },
53
49
  ];
54
50
  function cmdBuild() {
55
51
  const command = new commander_1.Command("build")
56
52
  .summary("Build the production frontend")
57
- .description("Build the production frontend from the manifest and layers archive the backend serves, or from a snapshot of them, into the workspace's dist directory. The build holds no secret: the server reads its own from its environment when it starts.")
58
- .addOption(config_1.Options.backendUrl("Backend URL to build for; required, as -b or the variable, unless --from names a snapshot"))
59
- .addOption(new commander_1.Option("--from <dir>", "Build from the snapshot in this directory (ajs dms snapshot), without the backend"))
60
- .addOption(config_1.Options.workspace("Build into this directory instead of the workspace under ~/.antelopejs, to ship it with the project"))
61
- .addOption(config_1.Options.force("Extract the layers archive from scratch and reinstall the workspace dependencies"))
62
- .addOption(config_1.Options.offline("Build from the last cached manifest and layers archive, without the backend"))
63
- .addOption(config_1.Options.bootstrapSecret())
53
+ .description("Build the production frontend from the AntelopeJS project itself: its antelope.config.ts, evaluated for the environment, and the frontend of each installed module that declares one (dmsFrontend in its package.json). No backend is asked. The build holds no secret, and start computes the modules' options again from the deployment's variables.")
54
+ .addOption(config_1.Options.project)
55
+ .addOption(config_1.Options.env)
56
+ .addOption(config_1.Options.workspace("Build into this directory instead of <project>/.dms-frontend"))
57
+ .addOption(config_1.Options.force("Reinstall the workspace dependencies"))
64
58
  .action(async (options) => (await Promise.resolve().then(() => __importStar(require("./build-action")))).runBuild(options));
65
59
  return (0, cli_1.withExamples)(command, BUILD_EXAMPLES);
66
60
  }
@@ -4,5 +4,7 @@ export interface DevOptions {
4
4
  force?: boolean;
5
5
  offline?: boolean;
6
6
  bootstrapSecret?: string;
7
+ project?: string;
8
+ env?: string;
7
9
  }
8
10
  export declare function runDev(options: DevOptions): Promise<void>;
@@ -12,7 +12,9 @@ const server_process_1 = require("../server-process");
12
12
  const update_check_1 = require("../update-check");
13
13
  const manifest_1 = require("../manifest");
14
14
  const module_set_1 = require("../module-set");
15
+ const project_modules_1 = require("../project-modules");
15
16
  const workspace_task_1 = require("./workspace-task");
17
+ const BASE_ENVIRONMENT = "default";
16
18
  function clientHost() {
17
19
  const host = process.env.HOST?.trim();
18
20
  if (!host || host === "0.0.0.0" || host === "::" || host === "[::]") {
@@ -40,6 +42,10 @@ async function runDev(options) {
40
42
  const offline = (0, common_1.flagOrEnv)(options.offline, "DMS_OFFLINE");
41
43
  const sessionSecret = (0, common_1.resolveSessionSecret)("dev");
42
44
  const { backendUrl, workspaceKey, projectDir } = resolveBackend(options);
45
+ const project = {
46
+ projectDir: options.project ?? projectDir ?? process.cwd(),
47
+ environment: options.env ?? BASE_ENVIRONMENT,
48
+ };
43
49
  const bootstrapSecret = (0, common_1.resolveBootstrapSecret)(options.bootstrapSecret, backendUrl);
44
50
  const ui = (0, cli_1.getProcessUi)();
45
51
  (0, output_1.writeHeader)("dev", [backendUrl, "development"]);
@@ -57,6 +63,7 @@ async function runDev(options) {
57
63
  clientUrl,
58
64
  workspaceKey,
59
65
  bootstrapSecret,
66
+ project,
60
67
  });
61
68
  if (port !== requestedPort) {
62
69
  ui.message("warn", `Port ${requestedPort} is busy, using ${port}`);
@@ -102,14 +109,18 @@ async function runDev(options) {
102
109
  clientUrl,
103
110
  workspaceKey,
104
111
  bootstrapSecret: session.bootstrapSecret,
112
+ project,
105
113
  });
106
114
  }
107
115
  }
108
- function servedModules(setup) {
116
+ function backendModuleSet(setup) {
109
117
  return (0, manifest_1.readCachedManifest)(setup.workspaceDir)?.manifest.modules ?? [];
110
118
  }
111
119
  function reportServed(session, setup) {
112
- return (0, module_set_1.reportFrontendReady)(session.backendUrl, servedModules(setup).map((module) => module.name), session.bootstrapSecret);
120
+ return (0, module_set_1.reportFrontendReady)(session.backendUrl, setup.layers.map((layer) => ({
121
+ name: layer.name ?? layer.path,
122
+ version: (0, project_modules_1.readShippingVersion)(layer.path),
123
+ })), session.bootstrapSecret);
113
124
  }
114
125
  async function serveWorkspace(session, setup) {
115
126
  const restart = new AbortController();
@@ -122,7 +133,7 @@ async function serveWorkspace(session, setup) {
122
133
  session.bootstrapSecret = session.refreshBootstrapSecret();
123
134
  return session.bootstrapSecret;
124
135
  },
125
- fingerprint: (0, module_set_1.moduleSetFingerprint)(servedModules(setup)),
136
+ fingerprint: (0, module_set_1.moduleSetFingerprint)(backendModuleSet(setup)),
126
137
  onChange: () => restart.abort(),
127
138
  onUnchanged: () => void reportServed(session, setup),
128
139
  });
@@ -50,8 +50,10 @@ const DEV_EXAMPLES = [
50
50
  function cmdDev() {
51
51
  const command = new commander_1.Command("dev")
52
52
  .summary("Run the dev server against a local backend, with hot reload")
53
- .description("Run the dev server with hot reload. It reads the frontend modules in place, from the backend's own directories, so the backend runs on this machine.")
53
+ .description("Run the dev server with hot reload. It reads the frontend modules from the AntelopeJS project as build does, each module's frontendModule called with dev: true, and serves them in place from their sources. The backend, on this machine, gives the secrets it holds and says when the module store changes its modules.")
54
54
  .addOption(config_1.Options.backendUrl("Backend URL; when omitted, discovered from the enclosing antelope project's .antelope/dev.json"))
55
+ .addOption(new commander_1.Option("--project <dir>", "The AntelopeJS project the modules are read from (default: the discovered backend's, else the current directory)"))
56
+ .addOption(new commander_1.Option("-e, --env <environment>", "The AntelopeJS environment the backend runs, as ajs project dev -e (default: the base configuration)").env("ANTELOPEJS_LAUNCH_ENV"))
55
57
  .addOption(config_1.Options.port)
56
58
  .addOption(config_1.Options.force("Reinstall the workspace dependencies"))
57
59
  .addOption(config_1.Options.offline("Reuse the last cached manifest instead of fetching it"))
@@ -1,6 +1,8 @@
1
1
  export interface StartOptions {
2
2
  backendUrl?: string;
3
3
  port: string;
4
+ project: string;
5
+ env: string;
4
6
  workspace?: string;
5
7
  }
6
8
  export declare function runStart(options: StartOptions): Promise<void>;
@@ -10,11 +10,12 @@ const ports_1 = require("../ports");
10
10
  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
- const snapshot_1 = require("../snapshot");
13
+ const project_modules_1 = require("../project-modules");
14
14
  const config_1 = require("../config");
15
15
  const manifest_secrets_1 = require("../manifest-secrets");
16
16
  const private_values_1 = require("../private-values");
17
- const BUILD_TIME_MANIFEST = "the build-time manifest";
17
+ const build_action_1 = require("./build-action");
18
+ const PUBLIC_OPTIONS_VARIABLE = "DMS_PUBLIC_OPTIONS";
18
19
  function builtPrivateOptions(workspaceDir) {
19
20
  try {
20
21
  const { modules } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(workspaceDir, config_1.SERVER_OPTIONS_FILE), "utf8"));
@@ -51,72 +52,83 @@ function serverNodeEnv() {
51
52
  }
52
53
  return nodeEnv;
53
54
  }
54
- async function reportDrift(backendUrl, origin) {
55
- const ui = (0, cli_1.getProcessUi)();
56
- const bootstrapSecret = process.env.DMS_BOOTSTRAP_SECRET?.trim();
57
- if (!origin)
58
- return;
59
- if (origin.authenticated === false) {
60
- ui.message("info", `Built from modules ${origin.digest}; not compared with the backend`, {
61
- detail: "The build read the manifest without DMS_BOOTSTRAP_SECRET, which leaves out the backend proxies.",
62
- });
63
- return;
55
+ function publicOptionsById(modules) {
56
+ return Object.fromEntries(modules.map(({ layer }) => {
57
+ const options = layer.options ?? {};
58
+ return [
59
+ (0, common_1.getLayerSafeName)(layer),
60
+ layer.configKey ? { [layer.configKey]: options } : options,
61
+ ];
62
+ }));
63
+ }
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
+ }));
64
71
  }
65
- if (!bootstrapSecret) {
66
- ui.message("info", `Built from modules ${origin.digest}; not compared with the backend`, { detail: "Set DMS_BOOTSTRAP_SECRET to have start compare them." });
67
- return;
72
+ catch {
73
+ return [];
68
74
  }
69
- const drift = await (0, module_set_1.compareWithBackend)(backendUrl, origin.digest, snapshot_1.manifestDigest, bootstrapSecret);
70
- if (drift.status === "matches") {
71
- ui.message("info", `The backend serves the modules ${origin.digest} this build was made from`);
75
+ }
76
+ function reportModuleSetDrift(built, modules) {
77
+ const builtNames = new Set(built.map(({ name }) => name));
78
+ const currentNames = new Set(modules.map(({ layer }) => layer.name ?? layer.path));
79
+ const added = [...currentNames].filter((name) => !builtNames.has(name));
80
+ const removed = [...builtNames].filter((name) => !currentNames.has(name));
81
+ if (added.length === 0 && removed.length === 0)
72
82
  return;
73
- }
74
- if (drift.status === "unknown") {
75
- ui.message("info", `Built from modules ${origin.digest}; not compared with the backend: ${drift.reason}`);
83
+ const ui = (0, cli_1.getProcessUi)();
84
+ ui.message("warn", "The project's modules differ from the ones the build serves: rebuild to apply them", {
85
+ details: [
86
+ ...(added.length > 0
87
+ ? [`Not in the build: ${added.sort().join(", ")}`]
88
+ : []),
89
+ ...(removed.length > 0
90
+ ? [`Built, no longer in the project: ${removed.sort().join(", ")}`]
91
+ : []),
92
+ `${ui.symbols.levels.hint} Build again with the deployment's variables: ajs dms build`,
93
+ ],
94
+ });
95
+ }
96
+ async function reportServedModules(backendUrl, modules) {
97
+ const isTaken = await (0, module_set_1.reportFrontendReadyOnceAnswered)(backendUrl, modules, process.env.DMS_BOOTSTRAP_SECRET);
98
+ const ui = (0, cli_1.getProcessUi)();
99
+ if (isTaken) {
100
+ ui.message("info", `[DMS] Reported ${modules.length} modules to the backend (dms-frontend ${module_set_1.OWN_RELEASE.version})`);
76
101
  return;
77
102
  }
78
- const refresh = origin.source === "snapshot"
79
- ? `Refresh the snapshot and rebuild: ajs dms snapshot -b ${backendUrl} -o <dir>, then ajs dms build --from <dir>`
80
- : `Rebuild: ajs dms build -b ${backendUrl}`;
81
- ui.message("warn", `The backend serves modules ${drift.served}; this build was made from ${origin.digest} (${origin.source}${(0, output_1.cachedAge)(origin.takenAt)})`, {
103
+ ui.message("warn", "[DMS] The backend refused the ready report: it cannot check this build's module versions", {
82
104
  details: [
83
- "Pages of modules added or removed since, or whose routes changed, are missing or stale.",
84
- `${ui.symbols.levels.hint} ${refresh}`,
105
+ `${ui.symbols.levels.hint} Set DMS_BOOTSTRAP_SECRET to the backend's frontend.bootstrapSecret`,
85
106
  ],
86
107
  });
87
108
  }
88
109
  async function runStart(options) {
89
110
  const startedAt = Date.now();
90
- const explicitWorkspace = options.workspace
91
- ? (0, node_path_1.resolve)(options.workspace)
92
- : undefined;
93
- const origin = explicitWorkspace
94
- ? (0, snapshot_1.readBuildOrigin)(explicitWorkspace)
95
- : undefined;
96
- const backendUrl = (0, common_1.requireBackendUrl)(options.backendUrl ?? origin?.backendUrl);
111
+ const workspaceDir = (0, node_path_1.resolve)((0, build_action_1.productionWorkspace)(options));
112
+ const backendUrl = (0, common_1.requireBackendUrl)(options.backendUrl);
97
113
  const requestedPort = (0, common_1.parsePort)(options.port);
98
114
  const sessionSecret = (0, common_1.resolveSessionSecret)("start");
99
- const workspaceDir = explicitWorkspace ?? (0, common_1.getWorkspaceDir)(backendUrl);
100
115
  const serverPath = (0, node_path_1.join)(workspaceDir, "server.mjs");
101
116
  const clientPath = (0, node_path_1.join)(workspaceDir, "dist", "client", "index.html");
102
117
  if (!(0, node_fs_1.existsSync)(serverPath) || !(0, node_fs_1.existsSync)(clientPath)) {
103
118
  throw new cli_1.CliError({
104
- title: `No production build for ${backendUrl}`,
119
+ title: "No production build to serve",
105
120
  reason: `${(0, output_1.showWorkspace)(workspaceDir)} has no built server or client.`,
106
121
  fixes: [
107
- explicitWorkspace
108
- ? `Build it first: ajs dms build --from <snapshot> --workspace ${options.workspace}`
109
- : `Build it first: ajs dms build -b ${backendUrl}`,
122
+ options.workspace
123
+ ? `Build it first: ajs dms build --workspace ${options.workspace}`
124
+ : "Build it first: ajs dms build",
110
125
  ],
111
126
  });
112
127
  }
113
- if (!explicitWorkspace) {
114
- (0, common_1.writeWorkspaceMeta)(workspaceDir, backendUrl, (0, common_1.canonicalizeBackendUrl)(backendUrl));
115
- }
116
128
  const builtAt = (0, node_fs_1.statSync)(clientPath).mtime.toISOString();
117
129
  (0, output_1.writeHeader)("start", [
118
130
  backendUrl,
119
- "production",
131
+ options.env,
120
132
  `built ${(0, output_1.formatAge)(builtAt)}`,
121
133
  ]);
122
134
  const reserved = await (0, ports_1.reservePort)(requestedPort);
@@ -129,10 +141,16 @@ async function runStart(options) {
129
141
  });
130
142
  }
131
143
  const port = reserved.port;
132
- const secrets = (0, common_1.resolveManifestSecrets)((0, common_1.collectManifestSecrets)((0, common_1.readCachedManifest)(workspaceDir)?.manifest.modules ?? []));
144
+ const modules = await (0, project_modules_1.readProjectFrontendModules)({
145
+ projectDir: options.project,
146
+ environment: options.env,
147
+ });
148
+ const built = builtModules(workspaceDir);
149
+ reportModuleSetDrift(built, modules);
150
+ const secrets = (0, common_1.resolveManifestSecrets)({ published: {}, conflicts: [] });
133
151
  (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`);
134
152
  reportUnfilledPrivateValues(workspaceDir);
135
- const secretSources = (0, common_1.describeSecretSources)(secrets.sources, BUILD_TIME_MANIFEST);
153
+ const secretSources = (0, common_1.describeSecretSources)(secrets.sources);
136
154
  const nodeEnv = serverNodeEnv();
137
155
  await reserved.release();
138
156
  const code = await (0, server_process_1.runServer)({
@@ -145,6 +163,7 @@ async function runStart(options) {
145
163
  PORT: String(port),
146
164
  DMS_API_BASE_URL: backendUrl,
147
165
  DMS_SESSION_SECRET: sessionSecret,
166
+ [PUBLIC_OPTIONS_VARIABLE]: JSON.stringify(publicOptionsById(modules)),
148
167
  ...secrets.env,
149
168
  DMS_COOKIE_SECURE: process.env.DMS_COOKIE_SECURE ?? "true",
150
169
  },
@@ -160,9 +179,8 @@ async function runStart(options) {
160
179
  ]),
161
180
  footer: "Ctrl+C to stop",
162
181
  });
163
- void (0, module_set_1.reportFrontendReady)(backendUrl, ((0, common_1.readCachedManifest)(workspaceDir)?.manifest.modules ?? []).map((module) => module.name), process.env.DMS_BOOTSTRAP_SECRET);
182
+ void reportServedModules(backendUrl, built);
164
183
  void (0, update_check_1.reportAvailableUpdate)({ isFollowed: true });
165
- void reportDrift(backendUrl, origin ?? (0, snapshot_1.readBuildOrigin)(workspaceDir));
166
184
  },
167
185
  });
168
186
  process.exitCode = code;
@@ -39,21 +39,23 @@ const config_1 = require("../config");
39
39
  const cli_1 = require("@antelopejs/core/cli");
40
40
  const START_EXAMPLES = [
41
41
  {
42
- description: "After ajs dms build -b https://dms.example.com",
43
- command: "ajs dms start -b https://dms.example.com -p 3001",
42
+ description: "From the project root, after ajs dms build",
43
+ command: "ajs dms start -b https://api.example.com -p 3001",
44
44
  },
45
45
  {
46
- description: "From a build shipped with the project, in a read-only container",
46
+ description: "In a read-only container holding the project and its build",
47
47
  command: "ajs dms start --workspace .dms-frontend -p 3000",
48
48
  },
49
49
  ];
50
50
  function cmdStart() {
51
51
  const command = new commander_1.Command("start")
52
52
  .summary("Serve the production build")
53
- .description("Serve the production frontend ajs dms build made for the same backend URL, or the one in --workspace. Reads DMS_SESSION_SECRET and the server's secrets (DMS_HTML_RENDER_SECRET, DMS_OAUTH_RELAY_SECRET) from its environment, and says once ready whether the backend still serves the modules the build was made from.")
54
- .addOption(config_1.Options.backendUrl("Backend URL to serve; required, as -b or the variable, unless --workspace holds a build that records it"))
53
+ .description("Serve the production frontend ajs dms build made from the project. The modules' public options are computed again from the project's configuration and the current environment variables, so a deployment's variables need no rebuild. Reads DMS_SESSION_SECRET and the server's secrets (DMS_HTML_RENDER_SECRET, DMS_OAUTH_RELAY_SECRET) from its environment, and tells the backend once ready which module versions it serves.")
54
+ .addOption(config_1.Options.backendUrl("Backend URL to serve; required"))
55
55
  .addOption(config_1.Options.port)
56
- .addOption(config_1.Options.workspace("Serve the build in this directory (ajs dms build --workspace); start writes nothing into it"))
56
+ .addOption(config_1.Options.project)
57
+ .addOption(config_1.Options.env)
58
+ .addOption(config_1.Options.workspace("Serve the build in this directory instead of <project>/.dms-frontend; start writes nothing into it"))
57
59
  .action(async (options) => (await Promise.resolve().then(() => __importStar(require("./start-action")))).runStart(options));
58
60
  return (0, cli_1.withExamples)(command, START_EXAMPLES);
59
61
  }
package/dist/common.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from "./manifest-secrets";
8
8
  export * from "./layer-watch";
9
9
  export * from "./materialize";
10
10
  export * from "./derived-outputs";
11
+ export * from "./locale-files";
11
12
  export * from "./app-configs";
12
13
  export * from "./renderer-range";
13
14
  export * from "./workspace";
package/dist/common.js CHANGED
@@ -24,6 +24,7 @@ __exportStar(require("./manifest-secrets"), exports);
24
24
  __exportStar(require("./layer-watch"), exports);
25
25
  __exportStar(require("./materialize"), exports);
26
26
  __exportStar(require("./derived-outputs"), exports);
27
+ __exportStar(require("./locale-files"), exports);
27
28
  __exportStar(require("./app-configs"), exports);
28
29
  __exportStar(require("./renderer-range"), exports);
29
30
  __exportStar(require("./workspace"), exports);
package/dist/config.d.ts CHANGED
@@ -33,6 +33,8 @@ export declare const Options: {
33
33
  offline(description: string): Option;
34
34
  strict: Option;
35
35
  workspace(description: string): Option;
36
+ project: Option;
37
+ env: Option;
36
38
  bootstrapSecret(discovery?: string): Option;
37
39
  };
38
40
  export interface BootstrapDiscoveryOptions extends DiscoveryOptions {
package/dist/config.js CHANGED
@@ -62,15 +62,20 @@ exports.TEMPLATE_FILES = [
62
62
  ["globals.d.ts", "globals.d.ts"],
63
63
  ["compress-assets.mjs", "compress-assets.mjs"],
64
64
  ["head-order.mjs", "head-order.mjs"],
65
+ ["client-chunking.mjs", "client-chunking.mjs"],
66
+ ["entry-weight.mjs", "entry-weight.mjs"],
65
67
  ["client-server-guard.mjs", "client-server-guard.mjs"],
66
68
  ["ui-inertia-page.mjs", "ui-inertia-page.mjs"],
67
69
  ["ui-request-state.mjs", "ui-request-state.mjs"],
70
+ ["sfc-global-types.mjs", "sfc-global-types.mjs"],
68
71
  ["icon-api.mjs", "icon-api.mjs"],
69
72
  ["icon-scan.mjs", "icon-scan.mjs"],
73
+ ["ui-kbd.mjs", "ui-kbd.mjs"],
70
74
  ["email-renderer.ts", "email-renderer.ts"],
71
75
  ["email-runtime.ts", "email-runtime.ts"],
72
76
  ["email-locales.ts", "email-locales.ts"],
73
77
  ["email-markup.ts", "email-markup.ts"],
78
+ ["public-options.ts", "public-options.ts"],
74
79
  ["tsconfig.json", "tsconfig.json"],
75
80
  ["typecheck-loader.mjs", "typecheck-loader.mjs"],
76
81
  ["prepare-types.mjs", "prepare-types.mjs"],
@@ -185,6 +190,10 @@ exports.Options = {
185
190
  workspace(description) {
186
191
  return new commander_1.Option("--workspace <dir>", description).env("DMS_WORKSPACE_DIR");
187
192
  },
193
+ project: new commander_1.Option("--project <dir>", "The AntelopeJS project whose antelope.config.ts and installed modules make the frontend").default("."),
194
+ env: new commander_1.Option("-e, --env <environment>", "The AntelopeJS environment whose configuration applies, as ajs project start -e")
195
+ .default("production")
196
+ .env("ANTELOPEJS_LAUNCH_ENV"),
188
197
  bootstrapSecret(discovery) {
189
198
  return new commander_1.Option("--bootstrap-secret <secret>", discovery
190
199
  ? `${BOOTSTRAP_SECRET_DESCRIPTION}. ${discovery}`
@@ -3,6 +3,7 @@ export interface FrontendModuleRegistryEntry {
3
3
  id: string;
4
4
  name?: string;
5
5
  packageName?: string;
6
+ version?: string;
6
7
  root: string;
7
8
  priority: number;
8
9
  entry?: string;
@@ -14,11 +15,6 @@ export interface FrontendModuleRegistry {
14
15
  modules: FrontendModuleRegistryEntry[];
15
16
  }
16
17
  export declare function createFrontendModuleRegistry(workspaceDir: string, layers: ResolvedLayer[]): FrontendModuleRegistry;
17
- export declare class InvalidLocaleFileError extends Error {
18
- readonly path: string;
19
- readonly reason: string;
20
- constructor(path: string, reason: string);
21
- }
22
18
  export declare function affectsLocaleMessages(relativePath: string): boolean;
23
19
  export declare function writeLocaleMessages(workspaceDir: string, registry: FrontendModuleRegistry): boolean;
24
20
  export interface DerivedOutput {