@antelopejs/dms-frontend 0.5.5 → 0.5.6

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.
package/README.md CHANGED
@@ -615,6 +615,6 @@ ajs dms verify-source \
615
615
 
616
616
  The checks look for what the core layer ships: its lazily loaded chart and rich-text libraries, the pages every DMS serves and its e-mail templates. A `--layer` that holds no core layer, such as a project's own `frontend-vue`, is refused before anything is installed, with exit code 2 and the command that verifies the same folder with `--module` instead.
617
617
 
618
- A supplied package whose declared range excludes this adapter version is refused before anything is installed (see [Declaring the loader releases a module supports](#declaring-the-loader-releases-a-module-supports)). The verifier generates a temporary workspace, installs it, builds client, SSR, and email bundles, checks what the client bundle loads, runs `vue-tsc`, renders eight DMS email templates, and checks that the email bundle excludes browser-only modules. Each check is one line that ends in ✔ or ✖, and the run ends with `Verified <n> modules` and its duration, or with the first failure: a type or Vite error points at the file in the package you passed (`./frontend-vue/app/components/Callout.vue:17:7`), not at its copy in the workspace, and the command exits 1. `--verbose` streams the pnpm, Vite and `vue-tsc` output. The workspace is removed when the run ends. It does not start a backend or publish packages. Repository development can invoke the same runner through `pnpm test:real-source`, with `DMS_LAYER_SOURCE` set to a core layer root and `DMS_MODULE_SOURCES` to a JSON array of module roots.
618
+ A supplied package whose declared range excludes this adapter version is refused before anything is installed (see [Declaring the loader releases a module supports](#declaring-the-loader-releases-a-module-supports)). The verifier generates a temporary workspace, installs it, builds the client, SSR and email bundles while `vue-tsc` runs alongside them (it does not compress the client assets, which only a served build needs), checks what the client bundle loads, renders eight DMS email templates, and checks that the email bundle excludes browser-only modules. Each check is one line that ends in ✔ or ✖, and the run ends with `Verified <n> modules` and its duration, or with the first failure: a type or Vite error points at the file in the package you passed (`./frontend-vue/app/components/Callout.vue:17:7`), not at its copy in the workspace, and the command exits 1. `--verbose` streams the pnpm, Vite and `vue-tsc` output. The workspace is removed when the run ends. It does not start a backend or publish packages. Repository development can invoke the same runner through `pnpm test:real-source`, with `DMS_LAYER_SOURCE` set to a core layer root and `DMS_MODULE_SOURCES` to a JSON array of module roots.
619
619
 
620
620
  Refresh rotation is single-flight within one frontend process. Horizontally scaled deployments must use sticky routing so a browser reaches the same process, or replace this process-local behavior with an external session adapter. It does not provide a distributed rotation guarantee.
package/dist/config.js CHANGED
@@ -59,6 +59,7 @@ exports.TEMPLATE_FILES = [
59
59
  ["email-markup.ts", "email-markup.ts"],
60
60
  ["tsconfig.json", "tsconfig.json"],
61
61
  ["typecheck-loader.mjs", "typecheck-loader.mjs"],
62
+ ["prepare-types.mjs", "prepare-types.mjs"],
62
63
  ["app-config-stub.mjs", "app-config-stub.mjs"],
63
64
  ["DmsDynamicPage.vue", "DmsDynamicPage.vue"],
64
65
  ["server.mjs", "server.mjs"],
@@ -0,0 +1,11 @@
1
+ import { type PathMapper } from "./child-output";
2
+ import { ViteWarnings } from "./vite-output";
3
+ export interface VerificationContext {
4
+ workspace: string;
5
+ env: NodeJS.ProcessEnv;
6
+ mapLine: PathMapper;
7
+ warnings: ViteWarnings;
8
+ isVerbose: boolean;
9
+ }
10
+ export declare function prepareTypes(context: VerificationContext): Promise<void>;
11
+ export declare function buildAndTypeCheck(context: VerificationContext): Promise<void>;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareTypes = prepareTypes;
4
+ exports.buildAndTypeCheck = buildAndTypeCheck;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const cli_1 = require("@antelopejs/core/cli");
8
+ const child_output_1 = require("./child-output");
9
+ const build_action_1 = require("./commands/build-action");
10
+ const common_1 = require("./common");
11
+ const typecheck_output_1 = require("./typecheck-output");
12
+ const verify_source_result_1 = require("./verify-source-result");
13
+ const vite_output_1 = require("./vite-output");
14
+ const VERIFY_BUILD_STEPS = build_action_1.BUILD_STEPS.filter((step) => step.script !== "build:compress");
15
+ async function prepareTypes(context) {
16
+ await (0, cli_1.getProcessTasks)().run("Generating the type declarations", async (task) => {
17
+ const result = await (0, common_1.runFramedCommand)("pnpm", ["--silent", "run", "build:types"], {
18
+ name: "types",
19
+ cwd: context.workspace,
20
+ env: context.env,
21
+ mapLine: context.mapLine,
22
+ });
23
+ if (result.code === 0)
24
+ return;
25
+ task.fail("Type declarations failed");
26
+ throw new cli_1.CliError((0, child_output_1.describeChildFailure)({
27
+ title: "Generating the type declarations failed",
28
+ command: "pnpm run build:types",
29
+ code: result.code,
30
+ lines: result.lines,
31
+ isVerbose: context.isVerbose,
32
+ }));
33
+ }, { done: "Generated the type declarations" });
34
+ snapshotTypeDeclarations(context.workspace);
35
+ }
36
+ const AUTO_IMPORTS_DECLARATIONS = "auto-imports.d.ts";
37
+ const AUTO_IMPORTS_SNAPSHOT = "auto-imports.verify.d.ts";
38
+ const TYPECHECK_CONFIG = "tsconfig.verify.json";
39
+ function snapshotTypeDeclarations(workspace) {
40
+ (0, node_fs_1.cpSync)((0, node_path_1.join)(workspace, AUTO_IMPORTS_DECLARATIONS), (0, node_path_1.join)(workspace, AUTO_IMPORTS_SNAPSHOT));
41
+ const config = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(workspace, "tsconfig.json"), "utf8"));
42
+ config.files = (config.files ?? []).map((file) => file === AUTO_IMPORTS_DECLARATIONS ? AUTO_IMPORTS_SNAPSHOT : file);
43
+ if (!config.files.includes(AUTO_IMPORTS_SNAPSHOT))
44
+ throw new cli_1.CliError({
45
+ title: `The workspace tsconfig.json does not list ${AUTO_IMPORTS_DECLARATIONS}`,
46
+ });
47
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(workspace, TYPECHECK_CONFIG), `${JSON.stringify(config, null, 2)}\n`);
48
+ }
49
+ async function buildBundle(context, step, warnings) {
50
+ await (0, cli_1.getProcessTasks)().run(step.running, async (task) => {
51
+ const result = await (0, common_1.runFramedCommand)("pnpm", ["--silent", "run", step.script], {
52
+ name: step.name,
53
+ cwd: context.workspace,
54
+ env: context.env,
55
+ mapLine: context.mapLine,
56
+ onLine: (line) => warnings.read(line),
57
+ });
58
+ if (result.code === 0)
59
+ return;
60
+ task.fail(step.failed);
61
+ throw (0, build_action_1.buildStepFailure)(step, result, context.isVerbose, verify_source_result_1.VERIFY_SOURCE_COMMAND);
62
+ }, { done: step.done });
63
+ }
64
+ async function buildAndTypeCheck(context) {
65
+ const stepWarnings = VERIFY_BUILD_STEPS.map(() => new vite_output_1.ViteWarnings());
66
+ const outcomes = await Promise.allSettled([
67
+ ...VERIFY_BUILD_STEPS.map((step, index) => buildBundle(context, step, stepWarnings[index])),
68
+ typeCheck(context),
69
+ ]);
70
+ for (const warnings of stepWarnings)
71
+ for (const line of warnings.lines())
72
+ context.warnings.read(line);
73
+ const failure = outcomes.find((outcome) => outcome.status === "rejected");
74
+ if (failure)
75
+ throw failure.reason;
76
+ (0, vite_output_1.reportViteWarnings)(context.warnings, context.isVerbose, (0, cli_1.getProcessUi)());
77
+ }
78
+ async function typeCheck(context) {
79
+ const typecheckArguments = [
80
+ "--import",
81
+ "./typecheck-loader.mjs",
82
+ "./node_modules/vue-tsc/bin/vue-tsc.js",
83
+ "--project",
84
+ TYPECHECK_CONFIG,
85
+ "--noEmit",
86
+ "--pretty",
87
+ ];
88
+ const isWindows = process.platform === "win32";
89
+ const command = isWindows ? process.execPath : "env";
90
+ const commandArguments = isWindows
91
+ ? typecheckArguments
92
+ : ["-u", "NODE_OPTIONS", process.execPath, ...typecheckArguments];
93
+ await (0, cli_1.getProcessTasks)().run("Type checking", async (task) => {
94
+ const result = await (0, common_1.runFramedCommand)(command, commandArguments, {
95
+ name: "vue-tsc",
96
+ cwd: context.workspace,
97
+ mapLine: context.mapLine,
98
+ });
99
+ if (result.code === 0)
100
+ return;
101
+ const report = (0, typecheck_output_1.parseTypeCheckOutput)(result.lines);
102
+ const count = report.count > 0 ? [(0, cli_1.pluralize)(report.count, "error")] : [];
103
+ task.fail(["Type check failed", ...count].join((0, cli_1.getProcessUi)().symbols.separator));
104
+ throw new cli_1.CliError((0, typecheck_output_1.describeTypeCheckErrors)(report, verify_source_result_1.VERIFY_SOURCE_COMMAND) ??
105
+ (0, child_output_1.describeChildFailure)({
106
+ title: "Type check failed",
107
+ command: "vue-tsc --noEmit",
108
+ code: result.code,
109
+ lines: result.lines,
110
+ isVerbose: context.isVerbose,
111
+ }));
112
+ }, { done: "Type check passed" });
113
+ }
@@ -37,11 +37,10 @@ const node_fs_1 = require("node:fs");
37
37
  const node_path_1 = require("node:path");
38
38
  const cli_1 = require("@antelopejs/core/cli");
39
39
  const child_output_1 = require("./child-output");
40
- const build_action_1 = require("./commands/build-action");
41
40
  const common_1 = require("./common");
42
41
  const output_1 = require("./output");
43
42
  const temporary_workspace_1 = require("./temporary-workspace");
44
- const typecheck_output_1 = require("./typecheck-output");
43
+ const verify_source_build_1 = require("./verify-source-build");
45
44
  const verify_source_result_1 = require("./verify-source-result");
46
45
  const verify_source_sources_1 = require("./verify-source-sources");
47
46
  const vite_output_1 = require("./vite-output");
@@ -95,26 +94,6 @@ function materializeWorkspace(workspace, layers, localPackages) {
95
94
  };
96
95
  (0, node_fs_1.writeFileSync)(packagePath, `${JSON.stringify(workspacePackage, null, 2)}\n`);
97
96
  }
98
- async function buildBundles(context) {
99
- const [firstStep] = build_action_1.BUILD_STEPS;
100
- await (0, cli_1.getProcessTasks)().run(firstStep.running, async (task) => {
101
- for (const step of build_action_1.BUILD_STEPS) {
102
- task.update(step.running);
103
- const result = await (0, common_1.runFramedCommand)("pnpm", ["--silent", "run", step.script], {
104
- name: step.name,
105
- cwd: context.workspace,
106
- env: context.env,
107
- mapLine: context.mapLine,
108
- onLine: (line) => context.warnings.read(line),
109
- });
110
- if (result.code !== 0) {
111
- task.fail(step.failed);
112
- throw (0, build_action_1.buildStepFailure)(step, result, context.isVerbose, verify_source_result_1.VERIFY_SOURCE_COMMAND);
113
- }
114
- }
115
- }, { done: "Built client, SSR and e-mail bundles" });
116
- (0, vite_output_1.reportViteWarnings)(context.warnings, context.isVerbose, (0, cli_1.getProcessUi)());
117
- }
118
97
  function checkClientBundle(workspace) {
119
98
  const clientRoot = (0, node_path_1.join)(workspace, "dist", "client");
120
99
  const manifest = readJson((0, node_path_1.join)(clientRoot, ".vite", "manifest.json"));
@@ -133,40 +112,6 @@ function checkClientBundle(workspace) {
133
112
  for (const marker of RUNTIME_MARKERS)
134
113
  ensure(javascript.includes(marker), `${marker} is not reachable at runtime`);
135
114
  }
136
- async function typeCheck(context) {
137
- const typecheckArguments = [
138
- "--import",
139
- "./typecheck-loader.mjs",
140
- "./node_modules/vue-tsc/bin/vue-tsc.js",
141
- "--noEmit",
142
- "--pretty",
143
- ];
144
- const isWindows = process.platform === "win32";
145
- const command = isWindows ? process.execPath : "env";
146
- const commandArguments = isWindows
147
- ? typecheckArguments
148
- : ["-u", "NODE_OPTIONS", process.execPath, ...typecheckArguments];
149
- await (0, cli_1.getProcessTasks)().run("Type checking", async (task) => {
150
- const result = await (0, common_1.runFramedCommand)(command, commandArguments, {
151
- name: "vue-tsc",
152
- cwd: context.workspace,
153
- mapLine: context.mapLine,
154
- });
155
- if (result.code === 0)
156
- return;
157
- const report = (0, typecheck_output_1.parseTypeCheckOutput)(result.lines);
158
- const count = report.count > 0 ? [(0, cli_1.pluralize)(report.count, "error")] : [];
159
- task.fail(["Type check failed", ...count].join((0, cli_1.getProcessUi)().symbols.separator));
160
- throw new cli_1.CliError((0, typecheck_output_1.describeTypeCheckErrors)(report, verify_source_result_1.VERIFY_SOURCE_COMMAND) ??
161
- (0, child_output_1.describeChildFailure)({
162
- title: "Type check failed",
163
- command: "vue-tsc --noEmit",
164
- code: result.code,
165
- lines: result.lines,
166
- isVerbose: context.isVerbose,
167
- }));
168
- }, { done: "Type check passed" });
169
- }
170
115
  const EMAIL_CASES = [
171
116
  {
172
117
  name: "EmailAdminInvite",
@@ -293,9 +238,9 @@ async function verifySources() {
293
238
  const tasks = (0, cli_1.getProcessTasks)();
294
239
  await tasks.run(`Materializing ${(0, cli_1.pluralize)(layers.length, "module")}`, async () => materializeWorkspace(workspace, layers, options.localPackages), { done: `Materialized ${(0, cli_1.pluralize)(layers.length, "module")}` });
295
240
  await (0, common_1.installDeps)(workspace, context.mapLine, ["--ignore-scripts"]);
296
- await buildBundles(context);
241
+ await (0, verify_source_build_1.prepareTypes)(context);
242
+ await (0, verify_source_build_1.buildAndTypeCheck)(context);
297
243
  await tasks.run("Checking the client bundle", async () => checkClientBundle(workspace), { done: "Client bundle checked", failed: "Client bundle check failed" });
298
- await typeCheck(context);
299
244
  await tasks.run("Checking the e-mail contracts", async () => checkEmailContracts(workspace), { done: (line) => line, failed: "E-mail contracts failed" });
300
245
  return { ok: true, modules: layers.length };
301
246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms-frontend",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Frontend-agnostic loader for AntelopeJS DMS, shipping the Vue 3 renderer (Vite, Inertia, SSR)",
5
5
  "keywords": [
6
6
  "antelope",
@@ -9,6 +9,7 @@
9
9
  "build:compress": "node compress-assets.mjs",
10
10
  "build:ssr": "vite build --ssr ssr-renderer.ts --outDir dist/ssr",
11
11
  "build:email": "vite build --config vite.email.config.ts",
12
+ "build:types": "node prepare-types.mjs",
12
13
  "typecheck": "node --import ./typecheck-loader.mjs ./node_modules/vue-tsc/bin/vue-tsc.js --noEmit",
13
14
  "start": "node server.mjs"
14
15
  },
@@ -0,0 +1,34 @@
1
+ // Writes the files Vite generates and the type check reads (auto-imports.d.ts
2
+ // and the Nuxt UI templates) without building anything: it resolves
3
+ // vite.config.ts and runs its build hooks over an empty entry, in seconds
4
+ // where a client build takes a minute. `ajs dms verify-source` runs it first
5
+ // (`pnpm run build:types`), so the type check no longer waits for the client
6
+ // bundle.
7
+
8
+ import { build } from "vite";
9
+
10
+ const ENTRY = "dms-prepare-types-entry";
11
+ const RESOLVED_ENTRY = `\0${ENTRY}`;
12
+
13
+ await build({
14
+ configFile: "vite.config.ts",
15
+ plugins: [
16
+ {
17
+ name: "dms-prepare-types-entry",
18
+ enforce: "pre",
19
+ resolveId: (id) => (id === ENTRY ? RESOLVED_ENTRY : null),
20
+ load: (id) => (id === RESOLVED_ENTRY ? "export {};" : null),
21
+ },
22
+ ],
23
+ build: {
24
+ write: false,
25
+ emptyOutDir: false,
26
+ rollupOptions: {
27
+ input: ENTRY,
28
+ // The entry is empty on purpose.
29
+ onwarn: (warning, warn) => {
30
+ if (warning.code !== "EMPTY_BUNDLE") warn(warning);
31
+ },
32
+ },
33
+ },
34
+ });
@@ -0,0 +1,25 @@
1
+ // Hop-by-hop headers describe the backend connection, not the client's one.
2
+ // Forwarded, an explicit `connection: keep-alive` overrode the client's own
3
+ // `Connection: close`, so Node kept the socket open until its idle timeout.
4
+ const HOP_BY_HOP_HEADERS = [
5
+ "connection",
6
+ "keep-alive",
7
+ "proxy-connection",
8
+ "te",
9
+ "trailer",
10
+ "transfer-encoding",
11
+ "upgrade",
12
+ ];
13
+
14
+ /** Writes the backend's status and end-to-end headers on `response`. */
15
+ export function writeProxyHeaders(response, upstream) {
16
+ const headers = Object.fromEntries(upstream.headers);
17
+ const named = (headers.connection ?? "")
18
+ .split(",")
19
+ .map((name) => name.trim().toLowerCase());
20
+ for (const name of [...HOP_BY_HOP_HEADERS, ...named]) delete headers[name];
21
+ delete headers["set-cookie"];
22
+ const cookies = upstream.headers.getSetCookie?.() ?? [];
23
+ if (cookies.length) headers["set-cookie"] = cookies;
24
+ response.writeHead(upstream.status, headers);
25
+ }
@@ -42,6 +42,7 @@ import { HOMEPAGE } from "./server/homepage.mjs";
42
42
  import { htmlTag } from "./server/html-tag.mjs";
43
43
  import * as icons from "./server/icons.mjs";
44
44
  import * as requestFailure from "./server/request-failure.mjs";
45
+ import { writeProxyHeaders } from "./server/proxy-headers.mjs";
45
46
  import { backendUrl, refuseForeignTarget } from "./server/request-target.mjs";
46
47
  import { handleTester } from "./server/tester.mjs";
47
48
  import {
@@ -194,14 +195,6 @@ async function pageProps(request) {
194
195
  }
195
196
  }
196
197
 
197
- function writeProxyHeaders(response, upstream) {
198
- const headers = Object.fromEntries(upstream.headers);
199
- delete headers["set-cookie"];
200
- const cookies = upstream.headers.getSetCookie?.() ?? [];
201
- if (cookies.length) headers["set-cookie"] = cookies;
202
- response.writeHead(upstream.status, headers);
203
- }
204
-
205
198
  function redirectToHomepage(request, response, pathname) {
206
199
  if (pathname !== "/" || HOMEPAGE === "/") return false;
207
200
  redirectFrontendVisit(request, response, HOMEPAGE);