@antelopejs/dms-frontend 0.7.0 → 0.8.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 (75) 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 +13 -41
  4. package/dist/commands/build.js +9 -15
  5. package/dist/commands/dev-action.js +5 -1
  6. package/dist/commands/start-action.d.ts +2 -0
  7. package/dist/commands/start-action.js +36 -49
  8. package/dist/commands/start.js +8 -6
  9. package/dist/config.d.ts +2 -0
  10. package/dist/config.js +6 -0
  11. package/dist/derived-outputs.js +2 -2
  12. package/dist/fs-sync.d.ts +1 -0
  13. package/dist/fs-sync.js +6 -0
  14. package/dist/help.js +8 -4
  15. package/dist/index.js +4 -6
  16. package/dist/layer-watch.js +4 -0
  17. package/dist/layers.d.ts +1 -3
  18. package/dist/layers.js +1 -95
  19. package/dist/manifest.d.ts +8 -8
  20. package/dist/manifest.js +15 -39
  21. package/dist/materialize.js +6 -0
  22. package/dist/module-set.d.ts +14 -12
  23. package/dist/module-set.js +39 -31
  24. package/dist/private-values.d.ts +2 -2
  25. package/dist/private-values.js +1 -6
  26. package/dist/project-modules.d.ts +46 -0
  27. package/dist/project-modules.js +233 -0
  28. package/dist/workspace-setup.d.ts +3 -7
  29. package/dist/workspace-setup.js +41 -62
  30. package/dist/workspace.d.ts +2 -4
  31. package/dist/workspace.js +1 -1
  32. package/package.json +5 -7
  33. package/skills/dms-frontend/SKILL.md +2 -2
  34. package/templates/vue/client-chunking.mjs +61 -0
  35. package/templates/vue/email-renderer.ts +6 -1
  36. package/templates/vue/frontend-build-loader.ts +1 -1
  37. package/templates/vue/frontend-module.ts +24 -0
  38. package/templates/vue/main.ts +11 -2
  39. package/templates/vue/package.json +2 -2
  40. package/templates/vue/prepare-types.mjs +2 -7
  41. package/templates/vue/public-options.ts +19 -0
  42. package/templates/vue/server/auth/backend.mjs +20 -3
  43. package/templates/vue/server/auth/client-ip.mjs +64 -22
  44. package/templates/vue/server/auth/session.mjs +4 -11
  45. package/templates/vue/server/backend-proxies.mjs +13 -2
  46. package/templates/vue/server/backend-response.mjs +2 -13
  47. package/templates/vue/server/cli-report.mjs +42 -18
  48. package/templates/vue/server/client-manifest.mjs +22 -58
  49. package/templates/vue/server/dev-private-files.mjs +1 -1
  50. package/templates/vue/server/dev-styles.mjs +16 -5
  51. package/templates/vue/server/homepage.mjs +4 -1
  52. package/templates/vue/server/module-event.mjs +23 -8
  53. package/templates/vue/server/module-server.mjs +4 -4
  54. package/templates/vue/server/module-testing.mjs +5 -3
  55. package/templates/vue/server/proxy-compression.mjs +15 -8
  56. package/templates/vue/server/proxy-headers.mjs +12 -1
  57. package/templates/vue/server/proxy-redirects.mjs +66 -0
  58. package/templates/vue/server/proxy-transfers.mjs +84 -0
  59. package/templates/vue/server/public-endpoints.mjs +2 -2
  60. package/templates/vue/server/public-options.mjs +49 -0
  61. package/templates/vue/server/request-routing.mjs +20 -0
  62. package/templates/vue/server/web-body.mjs +24 -0
  63. package/templates/vue/server-module.ts +19 -7
  64. package/templates/vue/server.mjs +59 -49
  65. package/templates/vue/ssr-renderer.ts +19 -1
  66. package/templates/vue/vite.config.ts +72 -46
  67. package/templates/vue/vite.email.config.ts +6 -3
  68. package/templates/vue/vite.server.config.ts +5 -2
  69. package/dist/commands/snapshot-action.d.ts +0 -6
  70. package/dist/commands/snapshot-action.js +0 -53
  71. package/dist/commands/snapshot.d.ts +0 -2
  72. package/dist/commands/snapshot.js +0 -55
  73. package/dist/snapshot.d.ts +0 -25
  74. package/dist/snapshot.js +0 -90
  75. 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,19 @@
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
13
  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
- };
14
+ exports.DEFAULT_WORKSPACE_DIR = ".dms-frontend";
15
+ function productionWorkspace(options) {
16
+ return options.workspace ?? (0, node_path_1.join)(options.project, exports.DEFAULT_WORKSPACE_DIR);
32
17
  }
33
18
  exports.BUILD_STEPS = [
34
19
  {
@@ -122,27 +107,15 @@ async function runBuild(options) {
122
107
  }
123
108
  }
124
109
  async function buildProduction(options, startedAt) {
125
- const { backendUrl, snapshot } = buildSource(options);
126
- const bootstrapSecret = snapshot
127
- ? undefined
128
- : (0, common_1.normalizeBootstrapSecret)(options.bootstrapSecret);
129
110
  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,
111
+ const workspace = productionWorkspace(options);
112
+ (0, output_1.writeHeader)("build", [options.project, options.env]);
113
+ const { workspaceDir, layers } = await (0, workspace_task_1.setUpWorkspace)({
133
114
  force: !!options.force,
134
115
  mode: "build",
135
- offline: snapshot ? false : (0, common_1.flagOrEnv)(options.offline, "DMS_OFFLINE"),
136
- bootstrapSecret,
137
- workspaceDir: options.workspace,
138
- snapshot,
116
+ workspaceDir: workspace,
117
+ project: { projectDir: options.project, environment: options.env },
139
118
  });
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
119
  ui.message("info", "Building for production", {
147
120
  detail: `Workspace ${(0, output_1.showWorkspace)(workspaceDir)}`,
148
121
  });
@@ -162,7 +135,6 @@ async function buildProduction(options, startedAt) {
162
135
  };
163
136
  for (const step of exports.BUILD_STEPS)
164
137
  await runBuildStep(step, context);
165
- (0, snapshot_1.writeBuildOrigin)(workspaceDir, buildOrigin);
166
138
  (0, vite_output_1.reportViteWarnings)(context.warnings, isVerbose, ui);
167
139
  (0, output_1.writeBlankLine)();
168
140
  ui.summary({
@@ -172,9 +144,9 @@ async function buildProduction(options, startedAt) {
172
144
  nextSteps: [
173
145
  {
174
146
  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",
147
+ ? `ajs dms start -b <backend> --workspace ${options.workspace}`
148
+ : "ajs dms start -b <backend>",
149
+ description: "from the project, with DMS_SESSION_SECRET, the server's secrets and the deployment's variables in its environment",
178
150
  },
179
151
  ],
180
152
  });
@@ -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
  }
@@ -12,6 +12,7 @@ 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");
16
17
  function clientHost() {
17
18
  const host = process.env.HOST?.trim();
@@ -109,7 +110,10 @@ function servedModules(setup) {
109
110
  return (0, manifest_1.readCachedManifest)(setup.workspaceDir)?.manifest.modules ?? [];
110
111
  }
111
112
  function reportServed(session, setup) {
112
- return (0, module_set_1.reportFrontendReady)(session.backendUrl, servedModules(setup).map((module) => module.name), session.bootstrapSecret);
113
+ return (0, module_set_1.reportFrontendReady)(session.backendUrl, servedModules(setup).map((module) => ({
114
+ name: module.name,
115
+ version: module.path ? (0, project_modules_1.readShippingVersion)(module.path) : undefined,
116
+ })), session.bootstrapSecret);
113
117
  }
114
118
  async function serveWorkspace(session, setup) {
115
119
  const restart = new AbortController();
@@ -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,54 @@ function serverNodeEnv() {
51
52
  }
52
53
  return nodeEnv;
53
54
  }
54
- async function reportDrift(backendUrl, origin) {
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
+ async function reportServedModules(backendUrl, modules) {
65
+ const isTaken = await (0, module_set_1.reportFrontendReadyOnceAnswered)(backendUrl, modules.map(({ layer, version }) => ({
66
+ name: layer.name ?? layer.path,
67
+ version,
68
+ })), process.env.DMS_BOOTSTRAP_SECRET);
55
69
  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
- });
70
+ if (isTaken) {
71
+ ui.message("info", `[DMS] Reported ${modules.length} modules to the backend (dms-frontend ${module_set_1.OWN_RELEASE.version})`);
63
72
  return;
64
73
  }
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;
68
- }
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`);
72
- return;
73
- }
74
- if (drift.status === "unknown") {
75
- ui.message("info", `Built from modules ${origin.digest}; not compared with the backend: ${drift.reason}`);
76
- return;
77
- }
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)})`, {
74
+ ui.message("warn", "[DMS] The backend refused the ready report: it cannot check this build's module versions", {
82
75
  details: [
83
- "Pages of modules added or removed since, or whose routes changed, are missing or stale.",
84
- `${ui.symbols.levels.hint} ${refresh}`,
76
+ `${ui.symbols.levels.hint} Set DMS_BOOTSTRAP_SECRET to the backend's frontend.bootstrapSecret`,
85
77
  ],
86
78
  });
87
79
  }
88
80
  async function runStart(options) {
89
81
  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);
82
+ const workspaceDir = (0, node_path_1.resolve)((0, build_action_1.productionWorkspace)(options));
83
+ const backendUrl = (0, common_1.requireBackendUrl)(options.backendUrl);
97
84
  const requestedPort = (0, common_1.parsePort)(options.port);
98
85
  const sessionSecret = (0, common_1.resolveSessionSecret)("start");
99
- const workspaceDir = explicitWorkspace ?? (0, common_1.getWorkspaceDir)(backendUrl);
100
86
  const serverPath = (0, node_path_1.join)(workspaceDir, "server.mjs");
101
87
  const clientPath = (0, node_path_1.join)(workspaceDir, "dist", "client", "index.html");
102
88
  if (!(0, node_fs_1.existsSync)(serverPath) || !(0, node_fs_1.existsSync)(clientPath)) {
103
89
  throw new cli_1.CliError({
104
- title: `No production build for ${backendUrl}`,
90
+ title: "No production build to serve",
105
91
  reason: `${(0, output_1.showWorkspace)(workspaceDir)} has no built server or client.`,
106
92
  fixes: [
107
- explicitWorkspace
108
- ? `Build it first: ajs dms build --from <snapshot> --workspace ${options.workspace}`
109
- : `Build it first: ajs dms build -b ${backendUrl}`,
93
+ options.workspace
94
+ ? `Build it first: ajs dms build --workspace ${options.workspace}`
95
+ : "Build it first: ajs dms build",
110
96
  ],
111
97
  });
112
98
  }
113
- if (!explicitWorkspace) {
114
- (0, common_1.writeWorkspaceMeta)(workspaceDir, backendUrl, (0, common_1.canonicalizeBackendUrl)(backendUrl));
115
- }
116
99
  const builtAt = (0, node_fs_1.statSync)(clientPath).mtime.toISOString();
117
100
  (0, output_1.writeHeader)("start", [
118
101
  backendUrl,
119
- "production",
102
+ options.env,
120
103
  `built ${(0, output_1.formatAge)(builtAt)}`,
121
104
  ]);
122
105
  const reserved = await (0, ports_1.reservePort)(requestedPort);
@@ -129,10 +112,14 @@ async function runStart(options) {
129
112
  });
130
113
  }
131
114
  const port = reserved.port;
132
- const secrets = (0, common_1.resolveManifestSecrets)((0, common_1.collectManifestSecrets)((0, common_1.readCachedManifest)(workspaceDir)?.manifest.modules ?? []));
115
+ const modules = await (0, project_modules_1.readProjectFrontendModules)({
116
+ projectDir: options.project,
117
+ environment: options.env,
118
+ });
119
+ const secrets = (0, common_1.resolveManifestSecrets)({ published: {}, conflicts: [] });
133
120
  (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
121
  reportUnfilledPrivateValues(workspaceDir);
135
- const secretSources = (0, common_1.describeSecretSources)(secrets.sources, BUILD_TIME_MANIFEST);
122
+ const secretSources = (0, common_1.describeSecretSources)(secrets.sources);
136
123
  const nodeEnv = serverNodeEnv();
137
124
  await reserved.release();
138
125
  const code = await (0, server_process_1.runServer)({
@@ -145,6 +132,7 @@ async function runStart(options) {
145
132
  PORT: String(port),
146
133
  DMS_API_BASE_URL: backendUrl,
147
134
  DMS_SESSION_SECRET: sessionSecret,
135
+ [PUBLIC_OPTIONS_VARIABLE]: JSON.stringify(publicOptionsById(modules)),
148
136
  ...secrets.env,
149
137
  DMS_COOKIE_SECURE: process.env.DMS_COOKIE_SECURE ?? "true",
150
138
  },
@@ -160,9 +148,8 @@ async function runStart(options) {
160
148
  ]),
161
149
  footer: "Ctrl+C to stop",
162
150
  });
163
- void (0, module_set_1.reportFrontendReady)(backendUrl, ((0, common_1.readCachedManifest)(workspaceDir)?.manifest.modules ?? []).map((module) => module.name), process.env.DMS_BOOTSTRAP_SECRET);
151
+ void reportServedModules(backendUrl, modules);
164
152
  void (0, update_check_1.reportAvailableUpdate)({ isFollowed: true });
165
- void reportDrift(backendUrl, origin ?? (0, snapshot_1.readBuildOrigin)(workspaceDir));
166
153
  },
167
154
  });
168
155
  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/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,6 +62,7 @@ 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"],
65
66
  ["client-server-guard.mjs", "client-server-guard.mjs"],
66
67
  ["ui-inertia-page.mjs", "ui-inertia-page.mjs"],
67
68
  ["ui-request-state.mjs", "ui-request-state.mjs"],
@@ -71,6 +72,7 @@ exports.TEMPLATE_FILES = [
71
72
  ["email-runtime.ts", "email-runtime.ts"],
72
73
  ["email-locales.ts", "email-locales.ts"],
73
74
  ["email-markup.ts", "email-markup.ts"],
75
+ ["public-options.ts", "public-options.ts"],
74
76
  ["tsconfig.json", "tsconfig.json"],
75
77
  ["typecheck-loader.mjs", "typecheck-loader.mjs"],
76
78
  ["prepare-types.mjs", "prepare-types.mjs"],
@@ -185,6 +187,10 @@ exports.Options = {
185
187
  workspace(description) {
186
188
  return new commander_1.Option("--workspace <dir>", description).env("DMS_WORKSPACE_DIR");
187
189
  },
190
+ project: new commander_1.Option("--project <dir>", "The AntelopeJS project whose antelope.config.ts and installed modules make the frontend").default("."),
191
+ env: new commander_1.Option("-e, --env <environment>", "The AntelopeJS environment whose configuration applies, as ajs project start -e")
192
+ .default("production")
193
+ .env("ANTELOPEJS_LAUNCH_ENV"),
188
194
  bootstrapSecret(discovery) {
189
195
  return new commander_1.Option("--bootstrap-secret <secret>", discovery
190
196
  ? `${BOOTSTRAP_SECRET_DESCRIPTION}. ${discovery}`
@@ -72,7 +72,7 @@ function writeFrontendModuleLoader(workspaceDir, registry) {
72
72
  .map((module, index) => `import module${index} from "@frontend/${module.id}/${module.entry}";`)
73
73
  .join("\n");
74
74
  const registrations = modules
75
- .map((module, index) => `{ name: ${JSON.stringify(module.name)}, module: module${index}, options: { public: ${JSON.stringify(module.options)} } }`)
75
+ .map((module, index) => `{ id: ${JSON.stringify(module.id)}, name: ${JSON.stringify(module.name)}, module: module${index}, options: { public: ${JSON.stringify(module.options)} } }`)
76
76
  .join(", ");
77
77
  const content = `${imports}\n\nimport type { DmsFrontendModuleRegistration } from "./frontend-module";\n\nexport const frontendModules: DmsFrontendModuleRegistration[] = [${registrations}];\n`;
78
78
  return (0, fs_sync_1.applyContent)(content, (0, node_path_1.join)(workspaceDir, "frontend-modules.generated.ts"));
@@ -85,7 +85,7 @@ function writeFrontendBuildLoader(workspaceDir, registry) {
85
85
  const declarations = modules
86
86
  .map((module, index) => `{ id: ${JSON.stringify(module.id)}, setup: build${index} }`)
87
87
  .join(", ");
88
- const content = `${imports}\n\nimport type { FrontendBuildDeclaration } from "./frontend-build-loader";\n\nexport const frontendBuilds: FrontendBuildDeclaration[] = [${declarations}];\n`;
88
+ const content = `${imports}\n\nimport type { FrontendBuildDeclaration } from "./frontend-build-loader.ts";\n\nexport const frontendBuilds: FrontendBuildDeclaration[] = [${declarations}];\n`;
89
89
  return (0, fs_sync_1.applyContent)(content, (0, node_path_1.join)(workspaceDir, "frontend-builds.generated.ts"));
90
90
  }
91
91
  function writeServerModuleLoader(workspaceDir, registry) {
package/dist/fs-sync.d.ts CHANGED
@@ -2,6 +2,7 @@ export declare function stripExtendedLengthPrefix(p: string): string;
2
2
  export declare function toPosixPath(path: string, separator?: string): string;
3
3
  export declare function isBlocklistedCopyPath(src: string, srcPath: string): boolean;
4
4
  export declare function sanitizedPackageContent(raw: string): string;
5
+ export declare function sanitizedBuildDeclaration(raw: string): string;
5
6
  export declare function filesIdentical(srcPath: string, destPath: string): boolean;
6
7
  export declare function applyContent(content: string, destPath: string): boolean;
7
8
  export declare function applyFile(srcPath: string, destPath: string): boolean;
package/dist/fs-sync.js CHANGED
@@ -7,6 +7,7 @@ exports.stripExtendedLengthPrefix = stripExtendedLengthPrefix;
7
7
  exports.toPosixPath = toPosixPath;
8
8
  exports.isBlocklistedCopyPath = isBlocklistedCopyPath;
9
9
  exports.sanitizedPackageContent = sanitizedPackageContent;
10
+ exports.sanitizedBuildDeclaration = sanitizedBuildDeclaration;
10
11
  exports.filesIdentical = filesIdentical;
11
12
  exports.applyContent = applyContent;
12
13
  exports.applyFile = applyFile;
@@ -39,6 +40,11 @@ function sanitizedPackageContent(raw) {
39
40
  }
40
41
  return `${JSON.stringify(pkg, null, 2)}\n`;
41
42
  }
43
+ const BUILD_HELPER_IMPORT = /(["'])#dms\/frontend-build\1/g;
44
+ const BUILD_HELPER_FROM_MODULE_ROOT = "../../frontend-build.ts";
45
+ function sanitizedBuildDeclaration(raw) {
46
+ return raw.replace(BUILD_HELPER_IMPORT, (_, quote) => `${quote}${BUILD_HELPER_FROM_MODULE_ROOT}${quote}`);
47
+ }
42
48
  function filesIdentical(srcPath, destPath) {
43
49
  if (!(0, node_fs_1.existsSync)(destPath))
44
50
  return false;
package/dist/help.js CHANGED
@@ -15,6 +15,10 @@ exports.ENVIRONMENT_VARIABLES = [
15
15
  ],
16
16
  ["DMS_OFFLINE", "Same as --offline, unless 0, false, no or off"],
17
17
  ["DMS_WORKSPACE_DIR", "Same as build and start --workspace"],
18
+ [
19
+ "ANTELOPEJS_LAUNCH_ENV",
20
+ "AntelopeJS environment build and start evaluate the project in, same as -e",
21
+ ],
18
22
  ["DMS_PREPARE_STRICT", "Same as prepare --strict, read like DMS_OFFLINE"],
19
23
  ["PORT", "Same as -p"],
20
24
  [
@@ -29,6 +33,10 @@ exports.ENVIRONMENT_VARIABLES = [
29
33
  "NODE_ENV",
30
34
  "Build of Vue and its libraries the server loads (default: development in dev, production in start)",
31
35
  ],
36
+ [
37
+ "DMS_CLIENT_IP_HEADER",
38
+ "Header the hosting platform overwrites with the client's address, e.g. X-Client-IP (default: none)",
39
+ ],
32
40
  [
33
41
  "DMS_TRUSTED_PROXY_HOPS",
34
42
  "Trusted reverse-proxy hops, counted from the right (default: 0)",
@@ -61,10 +69,6 @@ exports.ENVIRONMENT_VARIABLES = [
61
69
  "DMS_BACKEND_PROXIES",
62
70
  "More backend prefixes the server proxies, as a JSON array of { path, auth? }",
63
71
  ],
64
- [
65
- "DMS_BACKEND_WAKE_BUDGET_MS",
66
- "How long a GET or HEAD waits out a backend's cold-start 503, in ms (default: 30000; 0: never)",
67
- ],
68
72
  ["DMS_CLIENT_BASE_URL", "Public frontend URL used in links and e-mails"],
69
73
  ["NO_COLOR", "Same as --no-color"],
70
74
  ["ANTELOPEJS_VERBOSE", "Same as --verbose"],
package/dist/index.js CHANGED
@@ -8,7 +8,6 @@ const build_1 = require("./commands/build");
8
8
  const clean_1 = require("./commands/clean");
9
9
  const dev_1 = require("./commands/dev");
10
10
  const prepare_1 = require("./commands/prepare");
11
- const snapshot_1 = require("./commands/snapshot");
12
11
  const start_1 = require("./commands/start");
13
12
  const verify_source_1 = require("./commands/verify-source");
14
13
  const workspaces_1 = require("./commands/workspaces");
@@ -29,12 +28,12 @@ const ROOT_EXAMPLES = [
29
28
  command: "ajs dms dev",
30
29
  },
31
30
  {
32
- description: "Build for production, then serve the build",
33
- command: "ajs dms build -b https://dms.example.com",
31
+ description: "Build for production from the project root, without a backend",
32
+ command: "ajs dms build",
34
33
  },
35
34
  {
36
- description: "Serve it on another port",
37
- command: "ajs dms start -b https://dms.example.com -p 3001",
35
+ description: "Serve the build against its backend, on another port",
36
+ command: "ajs dms start -b https://api.example.com -p 3001",
38
37
  },
39
38
  ];
40
39
  function describeVersion(version, width) {
@@ -117,7 +116,6 @@ const runCLI = async () => {
117
116
  program.addCommand((0, dev_1.cmdDev)());
118
117
  program.addCommand((0, build_1.cmdBuild)());
119
118
  program.addCommand((0, start_1.cmdStart)());
120
- program.addCommand((0, snapshot_1.cmdSnapshot)());
121
119
  program.addCommand((0, prepare_1.cmdPrepare)());
122
120
  program.addCommand((0, workspaces_1.cmdWorkspaces)());
123
121
  program.addCommand((0, clean_1.cmdClean)());
@@ -9,6 +9,7 @@ const node_path_1 = require("node:path");
9
9
  const cli_1 = require("@antelopejs/core/cli");
10
10
  const chokidar_1 = __importDefault(require("chokidar"));
11
11
  const child_output_1 = require("./child-output");
12
+ const config_1 = require("./config");
12
13
  const layers_1 = require("./layers");
13
14
  const derived_outputs_1 = require("./derived-outputs");
14
15
  const output_1 = require("./output");
@@ -62,6 +63,9 @@ function createLayerSync(src, dest, reporter, onStaged) {
62
63
  if (rel === "package.json") {
63
64
  (0, fs_sync_1.applyContent)((0, fs_sync_1.sanitizedPackageContent)((0, node_fs_1.readFileSync)(srcPath, "utf-8")), destPath);
64
65
  }
66
+ else if (rel === config_1.FRONTEND_BUILD_ENTRY) {
67
+ (0, fs_sync_1.applyContent)((0, fs_sync_1.sanitizedBuildDeclaration)((0, node_fs_1.readFileSync)(srcPath, "utf-8")), destPath);
68
+ }
65
69
  else {
66
70
  (0, fs_sync_1.applyFile)(srcPath, destPath);
67
71
  }
package/dist/layers.d.ts CHANGED
@@ -7,15 +7,13 @@ interface PathlessManifestSource {
7
7
  };
8
8
  }
9
9
  export declare function assertLayerPathsServed(modules: ManifestModule[], backendUrl: string, { bootstrapSecret, cache }?: PathlessManifestSource): asserts modules is ServedWithPath[];
10
- export declare function assertCachedArchivesExist(modules: ManifestModule[], cacheDir: string, fetchedAt?: string): void;
11
10
  export interface LayerPackage {
12
11
  name?: unknown;
13
12
  engines?: Record<string, unknown>;
13
+ dmsFrontend?: unknown;
14
14
  }
15
15
  export declare function readLayerPackage(layerPath: string): LayerPackage | undefined;
16
16
  export declare function buildLayersFromPaths(modules: ServedWithPath[]): ResolvedLayer[];
17
- export declare function downloadAndExtractLayers(backendUrl: string, packUrl: string, cacheDir: string, force: boolean, bootstrapSecret?: string): Promise<void>;
18
- export declare function buildLayersFromCache(modules: ManifestModule[], cacheDir: string): Promise<ResolvedLayer[]>;
19
17
  export declare function getLayerSafeName(layer: ResolvedLayer): string;
20
18
  export declare function getLayerWorkspacePath(workspaceDir: string, layer: ResolvedLayer): string;
21
19
  export {};