@znight/cli 0.6.5 → 0.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@znight/cli",
3
- "version": "0.6.5",
3
+ "version": "0.7.0",
4
4
  "description": "Znight CLI: scaffold projects, generate code, run dev server and builds",
5
5
  "license": "MIT",
6
6
  "author": "Ugho STEPHAN",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@clack/prompts": "^1.8.1",
44
- "@znight/core": "0.6.5",
44
+ "@znight/core": "0.7.0",
45
45
  "citty": "^0.2.2",
46
46
  "picocolors": "^1.1.1"
47
47
  },
@@ -49,15 +49,15 @@
49
49
  "znight": "./bin/znight.mjs"
50
50
  },
51
51
  "devDependencies": {
52
- "vite": "8.3.5",
53
- "@znight/compiler": "0.6.5",
54
- "@znight/openapi": "0.6.5",
55
- "@znight/schedule": "0.6.5"
52
+ "vite": "8.3.6",
53
+ "@znight/compiler": "0.7.0",
54
+ "@znight/openapi": "0.7.0",
55
+ "@znight/schedule": "0.7.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@znight/compiler": "^0.6.5",
58
+ "@znight/compiler": "^0.7.0",
59
59
  "vite": "^8.0.0",
60
- "@znight/openapi": "^0.6.5"
60
+ "@znight/openapi": "^0.7.0"
61
61
  },
62
62
  "peerDependenciesMeta": {
63
63
  "@znight/compiler": {
@@ -69,8 +69,8 @@ export const newCommand: Command = defineCommand({
69
69
  },
70
70
  'openapi-ui': {
71
71
  type: 'enum',
72
- options: ['scalar', 'swagger'],
73
- description: 'API reference UI of the openapi feature, served at /docs (default: scalar)',
72
+ options: ['scalar', 'swagger', 'none'],
73
+ description: 'API reference UI of the openapi feature, served at /docs, or none (default: scalar)',
74
74
  },
75
75
  docker: { type: 'boolean', default: true, description: 'Do not generate a Dockerfile' },
76
76
  install: { type: 'boolean', default: true, description: 'Do not install the dependencies' },
@@ -9,8 +9,11 @@ export type HttpAdapterName = 'node' | 'fastify' | 'express' | 'hono'
9
9
  /** Optional features of a new project. */
10
10
  export type Feature = 'config' | 'observability' | 'messaging' | 'schedule' | 'openapi' | 'graphql' | 'websocket'
11
11
 
12
- /** API reference UIs of `@znight/openapi` (its `ui` option). */
13
- export type OpenApiUiName = 'scalar' | 'swagger'
12
+ /**
13
+ * API reference UI of the `openapi` feature: `'scalar'` or `'swagger'` (the `ui` option of
14
+ * `OpenApiPlugin`), or `'none'` (no `ui` option: `/openapi.json` only).
15
+ */
16
+ export type OpenApiUiName = 'scalar' | 'swagger' | 'none'
14
17
 
15
18
  /** Every package manager, in display order. */
16
19
  export const PACKAGE_MANAGERS: readonly PackageManager[] = ['bun', 'npm', 'pnpm', 'yarn']
@@ -33,7 +36,7 @@ export const FEATURES: readonly Feature[] = [
33
36
  ]
34
37
 
35
38
  /** Every OpenAPI reference UI, in display order (the default first). */
36
- export const OPENAPI_UIS: readonly OpenApiUiName[] = ['scalar', 'swagger']
39
+ export const OPENAPI_UIS: readonly OpenApiUiName[] = ['scalar', 'swagger', 'none']
37
40
 
38
41
  /** OpenAPI reference UI used when none is given. */
39
42
  export const DEFAULT_OPENAPI_UI: OpenApiUiName = 'scalar'
@@ -55,7 +58,7 @@ export const FEATURE_LABELS: Readonly<Record<Feature, string>> = {
55
58
  observability: 'Observability: structured logs, metrics, tracing (@znight/observability)',
56
59
  messaging: 'Messaging: message consumers and publishing (@znight/messaging)',
57
60
  schedule: 'Schedule: cron, interval and timeout jobs (@znight/schedule)',
58
- openapi: 'OpenAPI: openapi.json and an API reference at /docs (@znight/openapi)',
61
+ openapi: 'OpenAPI: openapi.json and an optional API reference at /docs (@znight/openapi)',
59
62
  graphql: 'GraphQL: code-first schema derived from the TypeScript types (@znight/graphql)',
60
63
  websocket: 'WebSocket: gateways served on the HTTP port (@znight/websocket)',
61
64
  }
@@ -64,6 +67,7 @@ export const FEATURE_LABELS: Readonly<Record<Feature, string>> = {
64
67
  export const OPENAPI_UI_LABELS: Readonly<Record<OpenApiUiName, string>> = {
65
68
  scalar: 'Scalar',
66
69
  swagger: 'Swagger UI',
70
+ none: 'No UI (openapi.json only)',
67
71
  }
68
72
 
69
73
  /** Everything that determines the files of a new project. */
@@ -74,7 +78,7 @@ export interface ProjectOptions {
74
78
  readonly adapter: HttpAdapterName
75
79
  /** Selected features, in canonical order. */
76
80
  readonly features: readonly Feature[]
77
- /** API reference UI served at `/docs` with the `openapi` feature. Default: `'scalar'`. */
81
+ /** API reference UI served at `/docs` with the `openapi` feature (`'none'`: no UI). Default: `'scalar'`. */
78
82
  readonly openapiUi?: OpenApiUiName
79
83
  /** Generate a multi-stage `Dockerfile` and a `.dockerignore`. Default: `true`. */
80
84
  readonly docker?: boolean
@@ -75,7 +75,7 @@ export function renderProject(options: ProjectOptions): ProjectFile[] {
75
75
  files.push({ path: 'src/app/heartbeat.jobs.ts', content: heartbeatJobs() })
76
76
  files.push({ path: 'test/heartbeat.jobs.test.ts', content: heartbeatJobsTest(flags) })
77
77
  }
78
- if (flags.openapi) files.push({ path: 'src/api.ts', content: apiDocument(options) })
78
+ if (flags.openapi) files.push({ path: 'src/openapi.ts', content: openApiDocument(flags) })
79
79
  if (flags.graphql) {
80
80
  files.push({ path: 'src/app/greeting.resolver.ts', content: greetingResolver() })
81
81
  files.push({ path: 'test/greeting.resolver.test.ts', content: greetingResolverTest(flags) })
@@ -137,6 +137,8 @@ function packageJson(options: ProjectOptions, flags: Flags): string {
137
137
  }
138
138
  if (flags.bun) devDependencies['@types/bun'] = versionOf('@types/bun')
139
139
  else devDependencies['vitest'] = versionOf('vitest')
140
+ // logging.pretty in src/main.ts, loaded only in development: the production image does not ship it
141
+ if (flags.observability) devDependencies['pino-pretty'] = versionOf('pino-pretty')
140
142
  if (flags.adapter === 'express') devDependencies['@types/express'] = versionOf('@types/express')
141
143
  // in-memory requests (TestApp.inject) with the node and express adapters
142
144
  if (flags.adapter === 'node' || flags.adapter === 'express') {
@@ -425,19 +427,22 @@ function mainTs(flags: Flags): string {
425
427
 
426
428
  const plugins: string[] = []
427
429
  if (flags.observability) {
428
- plugins.push(
429
- ' // first, so that its hooks wrap everything: request logs, metrics (with prom-client), traces',
430
- ' ObservabilityPlugin.configure({',
430
+ const logging = [
431
431
  ' logging: {',
432
432
  ` name: ${quote(flags.name)},`,
433
+ ' // human-readable logs in development (`znight dev`), JSON logs in production',
434
+ flags.config
435
+ ? " pretty: container.get(AppConfig).environment === 'development',"
436
+ : " pretty: process.env['NODE_ENV'] === 'development',",
433
437
  ' },',
434
- ' // metrics: {',
435
- ' // // options of the metrics registry',
436
- ' // },',
437
- ' // tracing: {',
438
- ' // // options of the tracer',
439
- ' // },',
440
- ' }),',
438
+ ' metrics: false,',
439
+ ' tracing: false,',
440
+ ]
441
+ plugins.push(
442
+ ' // first, so that its hooks wrap everything; metrics (with prom-client) and tracing are off',
443
+ ...(flags.config
444
+ ? [' ObservabilityPlugin.configure(container => ({', ...logging, ' })),']
445
+ : [' ObservabilityPlugin.configure({', ...logging, ' }),']),
441
446
  )
442
447
  }
443
448
  if (flags.config) {
@@ -476,8 +481,15 @@ function mainTs(flags: Flags): string {
476
481
  }
477
482
  if (flags.openapi) {
478
483
  plugins.push(
479
- ` // GET /openapi.json and the API reference at /docs, with ${OPENAPI_UI_LABELS[flags.openapiUi]} (document options: src/api.ts)`,
480
- ` OpenApiPlugin.configure({ ui: ${quote(flags.openapiUi)} }),`,
484
+ ...(flags.openapiUi === 'none'
485
+ ? [
486
+ ' // GET /openapi.json, no API reference UI (document options: src/openapi.ts)',
487
+ ' OpenApiPlugin.configure(),',
488
+ ]
489
+ : [
490
+ ` // GET /openapi.json and the API reference at /docs, with ${OPENAPI_UI_LABELS[flags.openapiUi]} (document options: src/openapi.ts)`,
491
+ ` OpenApiPlugin.configure({ ui: ${quote(flags.openapiUi)} }),`,
492
+ ]),
481
493
  )
482
494
  }
483
495
  if (flags.graphql) {
@@ -539,6 +551,10 @@ export class AppConfig {
539
551
 
540
552
  /** Greeting used by GreetingService (APP_GREETING). */
541
553
  greeting = 'Hello'
554
+
555
+ /** Runtime environment (NODE_ENV): \`znight dev\` sets "development"; unset, the application runs as in production. */
556
+ @Env('NODE_ENV')
557
+ environment = 'production'
542
558
  }
543
559
  `
544
560
  }
@@ -706,17 +722,18 @@ export class HeartbeatJobs {
706
722
  `
707
723
  }
708
724
 
709
- function apiDocument(options: ProjectOptions): string {
725
+ function openApiDocument(flags: Flags): string {
726
+ const served = flags.openapiUi === 'none' ? 'GET /openapi.json' : 'GET /openapi.json, the API reference at /docs'
710
727
  return `import { OpenApiDocument } from '@znight/openapi'
711
728
 
712
- /**
713
- * ${options.name}
714
- *
715
- * The HTTP API of ${options.name}. Title and description of the OpenAPI document come from this
716
- * comment, the version from package.json.
717
- */
729
+ // Options of the OpenAPI document (${served}), read by the
730
+ // compiler. The class is a marker: it is never instantiated.
718
731
  @OpenApiDocument({
719
- tags: [{ name: 'Greeting', description: 'Greetings' }],
732
+ info: {
733
+ title: ${quote(flags.name)},
734
+ description: ${quote(`The HTTP API of ${flags.name}.`)},
735
+ version: '1.0.0',
736
+ },
720
737
  })
721
738
  export class Api {}
722
739
  `
@@ -993,12 +1010,12 @@ function readme(options: ProjectOptions, flags: Flags): string {
993
1010
  `- **HTTP** with \`@znight/http\` (${flags.adapter === 'node' ? 'built-in node:http server' : `${flags.adapter} adapter`})`,
994
1011
  flags.config ? '- **Configuration** with `@znight/config` (`src/app/app.config.ts`, `.env` files)' : undefined,
995
1012
  flags.observability
996
- ? '- **Observability** with `@znight/observability` (structured logs, metrics, traces)'
1013
+ ? '- **Observability** with `@znight/observability` (structured logs: pretty-printed by `pino-pretty` in development, JSON in production; metrics and tracing are off in `src/main.ts`)'
997
1014
  : undefined,
998
1015
  flags.messaging ? '- **Messaging** with `@znight/messaging` (in-memory broker in `src/main.ts`)' : undefined,
999
1016
  flags.schedule ? '- **Scheduled jobs** with `@znight/schedule` (`src/app/heartbeat.jobs.ts`)' : undefined,
1000
1017
  flags.openapi
1001
- ? `- **OpenAPI** with \`@znight/openapi\` (\`/openapi.json\`, ${OPENAPI_UI_LABELS[flags.openapiUi]} API reference at \`/docs\`, options in \`src/api.ts\`)`
1018
+ ? `- **OpenAPI** with \`@znight/openapi\` (\`/openapi.json\`, ${flags.openapiUi === 'none' ? '' : `${OPENAPI_UI_LABELS[flags.openapiUi]} API reference at \`/docs\`, `}options in \`src/openapi.ts\`)`
1002
1019
  : undefined,
1003
1020
  flags.graphql
1004
1021
  ? '- **GraphQL** with `@znight/graphql` (code-first: the schema is derived from `src/app/greeting.resolver.ts`)'
@@ -1022,7 +1039,7 @@ function readme(options: ProjectOptions, flags: Flags): string {
1022
1039
  'src/',
1023
1040
  '├─ main.ts # Application.create() + plugins + start()',
1024
1041
  flags.openapi
1025
- ? '├─ api.ts # @OpenApiDocument: title, tags, security of openapi.json'
1042
+ ? '├─ openapi.ts # @OpenApiDocument: title, description, version of openapi.json'
1026
1043
  : undefined,
1027
1044
  '├─ .znight/ # generated DI container (git-ignored)',
1028
1045
  '└─ app/',
@@ -1036,9 +1053,9 @@ function readme(options: ProjectOptions, flags: Flags): string {
1036
1053
  ? `
1037
1054
  ## Configuration
1038
1055
 
1039
- \`AppConfig\` (\`src/app/app.config.ts\`) reads \`PORT\` and \`APP_GREETING\` from the environment or from
1040
- \`.env\` / \`.env.local\` files. Missing or invalid values stop the application at boot with every
1041
- problem listed.${
1056
+ \`AppConfig\` (\`src/app/app.config.ts\`) reads \`PORT\`, \`APP_GREETING\` and \`NODE_ENV\` from the environment
1057
+ or from \`.env\` / \`.env.local\` files. Missing or invalid values stop the application at boot with
1058
+ every problem listed.${
1042
1059
  flags.bun
1043
1060
  ? `
1044
1061
 
@@ -1097,7 +1114,7 @@ ${
1097
1114
  ${run('dev')}
1098
1115
  curl localhost:3000/health
1099
1116
  curl localhost:3000/greetings/Ada?excited=true
1100
- curl -X POST localhost:3000/greetings -H 'content-type: application/json' -d '{"name":"Grace"}'${flags.openapi ? '\ncurl localhost:3000/openapi.json # the API reference is at http://localhost:3000/docs' : ''}${flags.graphql ? `\ncurl localhost:3000/graphql -H 'content-type: application/json' -d '{"query":"{ greeting(name: \\"Ada\\") { message } }"}'` : ''}
1117
+ curl -X POST localhost:3000/greetings -H 'content-type: application/json' -d '{"name":"Grace"}'${flags.openapi ? `\ncurl localhost:3000/openapi.json${flags.openapiUi === 'none' ? '' : ' # the API reference is at http://localhost:3000/docs'}` : ''}${flags.graphql ? `\ncurl localhost:3000/graphql -H 'content-type: application/json' -d '{"query":"{ greeting(name: \\"Ada\\") { message } }"}'` : ''}
1101
1118
  \`\`\`
1102
1119
 
1103
1120
  ## Project structure
@@ -12,6 +12,7 @@ export const DEPENDENCY_VERSIONS: Readonly<Record<string, string>> = {
12
12
  graphql: '^17.0.2',
13
13
  hono: '^4.13.14',
14
14
  'light-my-request': '^6.8.0',
15
+ 'pino-pretty': '^13.2.0',
15
16
  typescript: '^7.0.0',
16
17
  vite: '^8.0.0',
17
18
  vitest: '^4.1.0',