@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/README.md +31 -10
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +13 -5
- package/dist/index.mjs +1 -1
- package/dist/{main-CLoS12u4.mjs → main-D_BXiYBu.mjs} +63 -29
- package/dist/main-D_BXiYBu.mjs.map +1 -0
- package/package.json +8 -8
- package/src/commands/new.ts +2 -2
- package/src/scaffold/options.ts +9 -5
- package/src/scaffold/templates.ts +45 -28
- package/src/scaffold/versions.ts +1 -0
- package/dist/main-CLoS12u4.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@znight/cli",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
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.
|
|
53
|
-
"@znight/compiler": "0.
|
|
54
|
-
"@znight/openapi": "0.
|
|
55
|
-
"@znight/schedule": "0.
|
|
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.
|
|
58
|
+
"@znight/compiler": "^0.7.0",
|
|
59
59
|
"vite": "^8.0.0",
|
|
60
|
-
"@znight/openapi": "^0.
|
|
60
|
+
"@znight/openapi": "^0.7.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependenciesMeta": {
|
|
63
63
|
"@znight/compiler": {
|
package/src/commands/new.ts
CHANGED
|
@@ -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' },
|
package/src/scaffold/options.ts
CHANGED
|
@@ -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
|
-
/**
|
|
13
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
'
|
|
435
|
-
'
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
'
|
|
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
|
-
|
|
480
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
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
|
-
? '├─
|
|
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 \`
|
|
1040
|
-
\`.env\` / \`.env.local\` files. Missing or invalid values stop the application at boot with
|
|
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 ?
|
|
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
|
package/src/scaffold/versions.ts
CHANGED