zudojs-cli 2.1.2 → 2.1.3

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.
@@ -9,7 +9,7 @@ import { join } from "node:path";
9
9
  import { generateModule } from "../generators/module/module.generator.js";
10
10
  import { generateCommand } from "../generators/command/command.generator.js";
11
11
  import { generateQuery } from "../generators/query/query.generator.js";
12
- import { generateMiddleware } from "../generators/middleware/middleware.generator.js";
12
+ import { generateMiddleware, } from "../generators/middleware/middleware.generator.js";
13
13
  import { generateEvent } from "../generators/event/event.generator.js";
14
14
  import { generateJob } from "../generators/job/job.generator.js";
15
15
  import { generateModel } from "../generators/model/model.generator.js";
@@ -209,6 +209,15 @@ export async function runGenerateCommand(context) {
209
209
  .map((step) => ` ${step}`)
210
210
  .join("\n")}`);
211
211
  },
212
+ onMiddlewareRegistered: (registration) => {
213
+ if (registration.registered) {
214
+ context.logger.info(`Registered in the middleware pipeline of ${registration.serverFile}.`);
215
+ return;
216
+ }
217
+ context.logger.warn(`Could not register the middleware automatically (the zudojs:server-imports or zudojs:server-middleware markers are missing). Add:\n${registration.manualSteps
218
+ .map((step) => ` ${step}`)
219
+ .join("\n")}`);
220
+ },
212
221
  }, cwd);
213
222
  if (dryRun) {
214
223
  context.logger.info(`Dry run: ${fileCount(result.length)} would be generated (nothing written):`);
@@ -249,7 +258,7 @@ async function runSchematic(schematic, name, options, cwd) {
249
258
  case "query":
250
259
  return await generateQuery({ name, ...(cqrsService ? { service: cqrsService } : {}), basePath, dryRun }, cwd);
251
260
  case "middleware":
252
- return await generateMiddleware({ name, basePath, dryRun }, cwd);
261
+ return await generateMiddleware({ name, basePath, dryRun, onRegistered: options.onMiddlewareRegistered }, cwd);
253
262
  case "event":
254
263
  return await generateEvent({ name, basePath, dryRun }, cwd);
255
264
  case "job":
@@ -6,43 +6,43 @@
6
6
  * Do not edit by hand; run `node scripts/generateZudojsVersions.mjs`.
7
7
  */
8
8
  export const ZUDOJS_PACKAGE_VERSIONS = Object.freeze({
9
- "@zudojs/adapters": "1.2.2",
10
- "@zudojs/api": "1.2.2",
11
- "@zudojs/auth": "1.3.2",
12
- "@zudojs/auth-oauth": "1.2.4",
13
- "@zudojs/cache": "1.2.2",
14
- "@zudojs/config": "1.3.2",
15
- "@zudojs/constants": "1.1.3",
16
- "@zudojs/container": "1.2.2",
17
- "@zudojs/core": "1.2.3",
18
- "@zudojs/cqrs": "1.2.2",
19
- "@zudojs/crypto": "1.3.2",
20
- "@zudojs/database": "1.3.2",
21
- "@zudojs/docs": "1.0.5",
22
- "@zudojs/errors": "1.3.1",
23
- "@zudojs/events": "1.3.2",
24
- "@zudojs/feature-flags": "1.4.1",
25
- "@zudojs/http": "1.4.3",
26
- "@zudojs/lifecycle": "1.2.2",
27
- "@zudojs/logger": "1.4.2",
28
- "@zudojs/messaging": "1.2.2",
29
- "@zudojs/middleware": "1.1.1",
30
- "@zudojs/observability": "1.2.2",
31
- "@zudojs/openapi": "1.5.1",
32
- "@zudojs/permissions": "1.4.2",
33
- "@zudojs/plugins": "1.3.2",
34
- "@zudojs/queue": "1.5.0",
35
- "@zudojs/rpc": "1.4.2",
36
- "@zudojs/runtime": "1.3.2",
37
- "@zudojs/scheduler": "1.2.1",
38
- "@zudojs/schema": "1.2.2",
39
- "@zudojs/security": "1.3.2",
40
- "@zudojs/serialization": "1.2.2",
41
- "@zudojs/storage": "1.2.2",
42
- "@zudojs/tenancy": "1.3.2",
43
- "@zudojs/testing": "1.2.3",
44
- "@zudojs/transactions": "1.2.1",
9
+ "@zudojs/adapters": "1.2.3",
10
+ "@zudojs/api": "1.2.3",
11
+ "@zudojs/auth": "1.3.3",
12
+ "@zudojs/auth-oauth": "1.2.5",
13
+ "@zudojs/cache": "1.2.3",
14
+ "@zudojs/config": "1.3.3",
15
+ "@zudojs/constants": "1.1.4",
16
+ "@zudojs/container": "1.2.3",
17
+ "@zudojs/core": "1.2.4",
18
+ "@zudojs/cqrs": "1.2.3",
19
+ "@zudojs/crypto": "1.3.3",
20
+ "@zudojs/database": "1.4.0",
21
+ "@zudojs/docs": "1.0.6",
22
+ "@zudojs/errors": "1.3.2",
23
+ "@zudojs/events": "1.3.3",
24
+ "@zudojs/feature-flags": "1.4.2",
25
+ "@zudojs/http": "1.4.4",
26
+ "@zudojs/lifecycle": "1.2.3",
27
+ "@zudojs/logger": "1.4.3",
28
+ "@zudojs/messaging": "1.2.3",
29
+ "@zudojs/middleware": "1.1.2",
30
+ "@zudojs/observability": "1.2.3",
31
+ "@zudojs/openapi": "1.5.2",
32
+ "@zudojs/permissions": "1.4.3",
33
+ "@zudojs/plugins": "1.3.3",
34
+ "@zudojs/queue": "1.5.1",
35
+ "@zudojs/rpc": "1.4.3",
36
+ "@zudojs/runtime": "1.3.3",
37
+ "@zudojs/scheduler": "1.2.2",
38
+ "@zudojs/schema": "1.2.3",
39
+ "@zudojs/security": "1.3.3",
40
+ "@zudojs/serialization": "1.2.3",
41
+ "@zudojs/storage": "1.2.3",
42
+ "@zudojs/tenancy": "1.3.3",
43
+ "@zudojs/testing": "1.2.4",
44
+ "@zudojs/transactions": "1.2.2",
45
45
  "@zudojs/types": "1.2.0",
46
- "@zudojs/validation": "1.1.1",
46
+ "@zudojs/validation": "1.1.2",
47
47
  });
48
48
  //# sourceMappingURL=zudojsVersions.generated.js.map
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * zudojs-cli — Middleware Generator
3
3
  */
4
- export { generateMiddleware, type GenerateMiddlewareOptions, } from "./middleware.generator.js";
4
+ export { generateMiddleware, middlewareAppSrc, middlewareServerLines, type GenerateMiddlewareOptions, type MiddlewareRegistration, } from "./middleware.generator.js";
5
+ export { renderMiddlewareFile, type MiddlewareFileOptions } from "./middleware.template.js";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * zudojs-cli — Middleware Generator
3
3
  */
4
- export { generateMiddleware, } from "./middleware.generator.js";
4
+ export { generateMiddleware, middlewareAppSrc, middlewareServerLines, } from "./middleware.generator.js";
5
+ export { renderMiddlewareFile } from "./middleware.template.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -1,12 +1,39 @@
1
1
  /**
2
2
  * zudojs-cli — Middleware Generator
3
3
  *
4
- * Generates middleware files.
4
+ * Writes `<basePath>/middlewares/<name>.middleware.ts` (an `HttpMiddleware`
5
+ * from @zudojs/http), exports it from the folder barrel and registers it in
6
+ * the app's `src/server.ts` between the `zudojs:server-imports` and
7
+ * `zudojs:server-middleware` markers. A server.ts missing either marker pair
8
+ * is left unchanged (no import without its registration) and the lines to
9
+ * add by hand are reported instead.
5
10
  */
6
11
  export interface GenerateMiddlewareOptions {
7
12
  readonly name: string;
8
13
  readonly basePath: string;
9
14
  readonly dryRun?: boolean;
15
+ /** Receives the registration outcome (e.g. lines to add by hand). */
16
+ readonly onRegistered?: (registration: MiddlewareRegistration) => void;
10
17
  }
18
+ /** Outcome of registering a generated middleware in `server.ts`. */
19
+ export interface MiddlewareRegistration {
20
+ /** Whether server.ts now runs the middleware. */
21
+ readonly registered: boolean;
22
+ /** The server file it was (or should be) registered in. */
23
+ readonly serverFile: string;
24
+ /** Lines to add by hand when `registered` is false. */
25
+ readonly manualSteps: readonly string[];
26
+ }
27
+ /**
28
+ * The app source directory owning `basePath`: `src` for `src` and
29
+ * `src/modules/billing`, `apps/services/x/src` for a service.
30
+ */
31
+ export declare function middlewareAppSrc(basePath: string): string;
32
+ /** The import and pipeline lines that register `factoryName` in server.ts. */
33
+ export declare function middlewareServerLines(basePath: string, factoryName: string): {
34
+ readonly serverFile: string;
35
+ readonly importLine: string;
36
+ readonly entryLine: string;
37
+ };
11
38
  export declare function generateMiddleware(options: GenerateMiddlewareOptions, cwd: string): Promise<string[]>;
12
39
  //# sourceMappingURL=middleware.generator.d.ts.map
@@ -1,29 +1,74 @@
1
1
  /**
2
2
  * zudojs-cli — Middleware Generator
3
3
  *
4
- * Generates middleware files.
4
+ * Writes `<basePath>/middlewares/<name>.middleware.ts` (an `HttpMiddleware`
5
+ * from @zudojs/http), exports it from the folder barrel and registers it in
6
+ * the app's `src/server.ts` between the `zudojs:server-imports` and
7
+ * `zudojs:server-middleware` markers. A server.ts missing either marker pair
8
+ * is left unchanged (no import without its registration) and the lines to
9
+ * add by hand are reported instead.
5
10
  */
6
- import { writeFileTree } from "../../utils/utils.fileSystem.js";
11
+ import { posix } from "node:path";
12
+ import { CLIValidationError } from "../../errors/index.js";
13
+ import { mergeBarrelExport, writeFileTree } from "../../utils/utils.fileSystem.js";
7
14
  import { normalizeName, toCamelCase } from "../../utils/utils.name.js";
15
+ import { MARKERS, applyMarkerEdits, conflictingImport, planMarkerEdits, } from "../../wiring/index.js";
16
+ import { renderMiddlewareFile } from "./middleware.template.js";
17
+ /**
18
+ * The app source directory owning `basePath`: `src` for `src` and
19
+ * `src/modules/billing`, `apps/services/x/src` for a service.
20
+ */
21
+ export function middlewareAppSrc(basePath) {
22
+ return basePath.replace(/\/modules\/[^/]+$/, "");
23
+ }
24
+ /** The import and pipeline lines that register `factoryName` in server.ts. */
25
+ export function middlewareServerLines(basePath, factoryName) {
26
+ const appSrc = middlewareAppSrc(basePath);
27
+ const barrel = posix.relative(appSrc, `${basePath}/middlewares/index.js`);
28
+ return {
29
+ serverFile: `${appSrc}/server.ts`,
30
+ importLine: `import { ${factoryName} } from "./${barrel}";`,
31
+ entryLine: `${factoryName}(),`,
32
+ };
33
+ }
8
34
  export async function generateMiddleware(options, cwd) {
9
35
  const name = normalizeName(options.name);
10
- const nameCamel = toCamelCase(options.name);
36
+ const factoryName = `${toCamelCase(options.name)}Middleware`;
37
+ const lines = middlewareServerLines(options.basePath, factoryName);
38
+ const barrel = `${options.basePath}/middlewares/index.ts`;
39
+ const clash = conflictingImport(cwd, lines.serverFile, lines.importLine);
40
+ if (clash !== undefined) {
41
+ throw new CLIValidationError(`${lines.serverFile} already imports ${factoryName} (${clash.trim()}), so middleware "${name}" cannot be registered. Choose another name.`);
42
+ }
11
43
  const files = {
12
- [`${options.basePath}/middlewares/${name}.middleware.ts`]: `/**
13
- * ${name} middleware.
14
- */
15
-
16
- export function ${nameCamel}Middleware() {
17
- return async (ctx: unknown, next: () => Promise<void>) => {
18
- await next();
19
- };
20
- }
21
- `,
44
+ [`${options.basePath}/middlewares/${name}.middleware.ts`]: renderMiddlewareFile({
45
+ name,
46
+ factoryName,
47
+ serverFile: lines.serverFile,
48
+ }),
49
+ [barrel]: mergeBarrelExport(cwd, barrel, `export { ${factoryName} } from "./${name}.middleware.js";`),
22
50
  };
51
+ const edits = [
52
+ { file: lines.serverFile, marker: MARKERS.serverImports, line: lines.importLine },
53
+ { file: lines.serverFile, marker: MARKERS.serverMiddleware, line: lines.entryLine },
54
+ ];
55
+ const planned = planMarkerEdits(cwd, edits).outcome;
56
+ const registered = planned.manualSteps.length === 0;
23
57
  if (options.dryRun) {
24
- return Object.keys(files);
58
+ return [...Object.keys(files), ...(registered ? planned.edited : [])];
25
59
  }
26
60
  await writeFileTree(cwd, files);
27
- return Object.keys(files);
61
+ const edited = registered ? (await applyMarkerEdits(cwd, edits)).edited : [];
62
+ options.onRegistered?.({
63
+ registered,
64
+ serverFile: lines.serverFile,
65
+ manualSteps: registered
66
+ ? []
67
+ : [
68
+ `In ${lines.serverFile}: ${lines.importLine}`,
69
+ `In ${lines.serverFile}: add ${lines.entryLine} to the middlewares list of the HttpMiddlewarePipeline, before dispatch`,
70
+ ],
71
+ });
72
+ return [...Object.keys(files), ...edited];
28
73
  }
29
74
  //# sourceMappingURL=middleware.generator.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * zudojs-cli — Generated `<name>.middleware.ts`.
3
+ *
4
+ * `generate middleware` used to write
5
+ * `(ctx: unknown, next: () => Promise<void>) => Promise<void>`: registering
6
+ * it in the server pipeline failed tsc with TS2322, and because it returned
7
+ * nothing the response produced by `next()` was dropped. The file is now a
8
+ * real `HttpMiddleware` from @zudojs/http that returns `next()`'s response.
9
+ */
10
+ /** Options for {@link renderMiddlewareFile}. */
11
+ export interface MiddlewareFileOptions {
12
+ /** Normalized (kebab-case) name, e.g. `request-timer`. */
13
+ readonly name: string;
14
+ /** Factory function name, e.g. `requestTimerMiddleware`. */
15
+ readonly factoryName: string;
16
+ /** Where the factory is registered, e.g. `src/server.ts`. */
17
+ readonly serverFile: string;
18
+ }
19
+ /** Renders the middleware source file. */
20
+ export declare function renderMiddlewareFile(options: MiddlewareFileOptions): string;
21
+ //# sourceMappingURL=middleware.template.d.ts.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * zudojs-cli — Generated `<name>.middleware.ts`.
3
+ *
4
+ * `generate middleware` used to write
5
+ * `(ctx: unknown, next: () => Promise<void>) => Promise<void>`: registering
6
+ * it in the server pipeline failed tsc with TS2322, and because it returned
7
+ * nothing the response produced by `next()` was dropped. The file is now a
8
+ * real `HttpMiddleware` from @zudojs/http that returns `next()`'s response.
9
+ */
10
+ /** Renders the middleware source file. */
11
+ export function renderMiddlewareFile(options) {
12
+ return `import type { HttpMiddleware } from "@zudojs/http";
13
+
14
+ /**
15
+ * ${options.name} middleware.
16
+ *
17
+ * It runs when \`${options.factoryName}()\` is in the \`middlewares\` list of
18
+ * ${options.serverFile}, between the \`// zudojs:server-middleware\` markers:
19
+ * after the security headers, CORS and rate limit, before routing.
20
+ * \`zudojs generate middleware\` adds it there when the markers exist.
21
+ *
22
+ * To answer without calling \`next()\`, return a response such as
23
+ * \`createResponseContext({ status: 403 }).json({ error: "Forbidden" })\`.
24
+ * Otherwise return what \`next()\` resolves to, and \`clone()\` it before
25
+ * changing its headers.
26
+ */
27
+ export function ${options.factoryName}(): HttpMiddleware {
28
+ return async (_context, next) => {
29
+ const response = await next();
30
+ return response;
31
+ };
32
+ }
33
+ `;
34
+ }
35
+ //# sourceMappingURL=middleware.template.js.map
@@ -14,6 +14,13 @@
14
14
  * 500. On SIGINT/SIGTERM integrations drain, HTTP stops, then the runtime
15
15
  * stops. `tests/generatedProject.typecheck.test.ts` type-checks the output
16
16
  * against the current `@zudojs/*` sources.
17
+ *
18
+ * The signal listeners stay registered (`process.on`, not `once`) for the
19
+ * whole shutdown. Under `tsx watch`, Ctrl+C delivers SIGINT twice (from the
20
+ * terminal and forwarded by the watcher); with `once` the second signal
21
+ * found no listener and Node's default action killed the process before
22
+ * the runtime had stopped or logged anything. "Listening on" is logged once
23
+ * the listeners are in place, so a signal sent after it is always handled.
17
24
  */
18
25
  /** Options for {@link renderServerFile}. */
19
26
  export interface ServerFileOptions {
@@ -14,6 +14,13 @@
14
14
  * 500. On SIGINT/SIGTERM integrations drain, HTTP stops, then the runtime
15
15
  * stops. `tests/generatedProject.typecheck.test.ts` type-checks the output
16
16
  * against the current `@zudojs/*` sources.
17
+ *
18
+ * The signal listeners stay registered (`process.on`, not `once`) for the
19
+ * whole shutdown. Under `tsx watch`, Ctrl+C delivers SIGINT twice (from the
20
+ * terminal and forwarded by the watcher); with `once` the second signal
21
+ * found no listener and Node's default action killed the process before
22
+ * the runtime had stopped or logged anything. "Listening on" is logged once
23
+ * the listeners are in place, so a signal sent after it is always handled.
17
24
  */
18
25
  import { MARKERS, renderMarkerBlock } from "../../wiring/index.js";
19
26
  /** Emits a string as a TypeScript string literal that cannot break out. */
@@ -98,13 +105,16 @@ const server = createHttpServer({
98
105
  });
99
106
 
100
107
  await server.start();
101
- console.log(\`Listening on http://\${config.host}:\${server.address?.port ?? config.port}\`);
102
108
 
103
109
  let stopping = false;
104
110
  for (const signal of ["SIGINT", "SIGTERM"] as const) {
105
- process.once(signal, () => {
106
- if (stopping) return;
111
+ process.on(signal, () => {
112
+ if (stopping) {
113
+ console.log(\`Received \${signal} again: already shutting down.\`);
114
+ return;
115
+ }
107
116
  stopping = true;
117
+ console.log(\`Received \${signal}: shutting down.\`);
108
118
  void drainIntegrations(integrations)
109
119
  .then(() => server.stop())
110
120
  .then(() => runtime.stop())
@@ -117,6 +127,8 @@ for (const signal of ["SIGINT", "SIGTERM"] as const) {
117
127
  });
118
128
  });
119
129
  }
130
+
131
+ console.log(\`Listening on http://\${config.host}:\${server.address?.port ?? config.port}\`);
120
132
  `;
121
133
  }
122
134
  //# sourceMappingURL=server.template.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudojs-cli",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "Command-line interface for scaffolding, generating, and managing Zudojs framework projects.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -32,14 +32,14 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@clack/prompts": "^1.8.1",
35
- "@zudojs/config": "1.3.2",
36
- "@zudojs/core": "1.2.3",
37
- "@zudojs/errors": "1.3.1",
38
- "@zudojs/logger": "1.4.2"
35
+ "@zudojs/config": "1.3.3",
36
+ "@zudojs/core": "1.2.4",
37
+ "@zudojs/errors": "1.3.2",
38
+ "@zudojs/logger": "1.4.3"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^26.6.2",
42
- "@zudojs/constants": "1.1.3",
42
+ "@zudojs/constants": "1.1.4",
43
43
  "tsx": "^4.23.15",
44
44
  "typescript": "7.0.2",
45
45
  "vitest": "^5.0.1"