@config-bound/cli 0.1.0 → 0.2.1

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 (62) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/package.json +29 -23
  4. package/scripts/generate-docs.ts +362 -0
  5. package/src/cli.module.ts +7 -1
  6. package/src/commands/generate-bind.command.ts +175 -0
  7. package/src/commands/generate.command.ts +13 -0
  8. package/src/services/bind-generator.service.ts +248 -0
  9. package/src/services/config-discovery.service.spec.ts +10 -10
  10. package/src/services/config-discovery.service.ts +26 -41
  11. package/src/services/config-loader.service.ts +8 -16
  12. package/src/services/schema-export.service.spec.ts +20 -22
  13. package/src/services/schema-export.service.ts +6 -11
  14. package/tsconfig.json +7 -2
  15. package/.turbo/turbo-build.log +0 -4
  16. package/.turbo/turbo-format$colon$ci.log +0 -6
  17. package/.turbo/turbo-lint$colon$ci.log +0 -4
  18. package/.turbo/turbo-test.log +0 -19
  19. package/dist/cli.module.d.ts +0 -3
  20. package/dist/cli.module.d.ts.map +0 -1
  21. package/dist/cli.module.js +0 -29
  22. package/dist/cli.module.js.map +0 -1
  23. package/dist/commands/export.command.d.ts +0 -30
  24. package/dist/commands/export.command.d.ts.map +0 -1
  25. package/dist/commands/export.command.js +0 -226
  26. package/dist/commands/export.command.js.map +0 -1
  27. package/dist/commands/list.command.d.ts +0 -13
  28. package/dist/commands/list.command.d.ts.map +0 -1
  29. package/dist/commands/list.command.js +0 -93
  30. package/dist/commands/list.command.js.map +0 -1
  31. package/dist/main.d.ts +0 -3
  32. package/dist/main.d.ts.map +0 -1
  33. package/dist/main.js +0 -17
  34. package/dist/main.js.map +0 -1
  35. package/dist/services/config-discovery.service.d.ts +0 -15
  36. package/dist/services/config-discovery.service.d.ts.map +0 -1
  37. package/dist/services/config-discovery.service.js +0 -191
  38. package/dist/services/config-discovery.service.js.map +0 -1
  39. package/dist/services/config-discovery.service.spec.d.ts +0 -2
  40. package/dist/services/config-discovery.service.spec.d.ts.map +0 -1
  41. package/dist/services/config-discovery.service.spec.js +0 -137
  42. package/dist/services/config-discovery.service.spec.js.map +0 -1
  43. package/dist/services/config-loader.service.d.ts +0 -13
  44. package/dist/services/config-loader.service.d.ts.map +0 -1
  45. package/dist/services/config-loader.service.js +0 -241
  46. package/dist/services/config-loader.service.js.map +0 -1
  47. package/dist/services/file-writer.service.d.ts +0 -6
  48. package/dist/services/file-writer.service.d.ts.map +0 -1
  49. package/dist/services/file-writer.service.js +0 -38
  50. package/dist/services/file-writer.service.js.map +0 -1
  51. package/dist/services/file-writer.service.spec.d.ts +0 -2
  52. package/dist/services/file-writer.service.spec.d.ts.map +0 -1
  53. package/dist/services/file-writer.service.spec.js +0 -98
  54. package/dist/services/file-writer.service.spec.js.map +0 -1
  55. package/dist/services/schema-export.service.d.ts +0 -14
  56. package/dist/services/schema-export.service.d.ts.map +0 -1
  57. package/dist/services/schema-export.service.js +0 -58
  58. package/dist/services/schema-export.service.js.map +0 -1
  59. package/dist/services/schema-export.service.spec.d.ts +0 -2
  60. package/dist/services/schema-export.service.spec.d.ts.map +0 -1
  61. package/dist/services/schema-export.service.spec.js +0 -69
  62. package/dist/services/schema-export.service.spec.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @config-bound/cli
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [87d2ab6]
8
+ - @config-bound/core@1.0.0
9
+ - @config-bound/schema-export@0.1.3
10
+
11
+ ## 0.2.0
12
+
13
+ ### Minor Changes
14
+
15
+ - f2299f6: Add `configbound generate bind` command
16
+
17
+ Scaffolds the boilerplate for a new custom bind — either as an embedded TypeScript class or a publishable npm package. Handles class structure, static `create()` factory, and cache setup so you can focus on the implementation.
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [02b3369]
22
+ - Updated dependencies [ac54dea]
23
+ - Updated dependencies [0f899ee]
24
+ - @config-bound/config-bound@0.2.0
25
+ - @config-bound/schema-export@0.1.1
26
+
3
27
  ## 0.1.0
4
28
 
5
29
  ### Minor Changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Robert Keyser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@config-bound/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "CLI tool for ConfigBound schema export and management",
5
5
  "keywords": [
6
6
  "cli",
@@ -17,46 +17,52 @@
17
17
  },
18
18
  "main": "./dist/main.js",
19
19
  "types": "./dist/main.d.ts",
20
- "scripts": {
21
- "build": "tsc",
22
- "clean": "rimraf dist coverage .turbo",
23
- "dev": "tsx src/main.ts",
24
- "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
25
- "test:dev": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
26
- "test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
27
- "lint": "eslint src/**/*.ts --fix",
28
- "lint:ci": "eslint src/**/*.ts",
29
- "format": "prettier --write --config ../../.config/.prettierrc --ignore-path ../../.config/.prettierignore src/**/*.ts",
30
- "format:ci": "prettier --check --config ../../.config/.prettierrc --ignore-path ../../.config/.prettierignore src/**/*.ts"
31
- },
32
20
  "dependencies": {
33
- "@config-bound/config-bound": "*",
34
- "@config-bound/schema-export": "*",
35
- "@nestjs/common": "^10.4.15",
36
- "@nestjs/core": "^10.4.15",
21
+ "@nestjs/common": "^11.1.18",
22
+ "@nestjs/core": "^11.1.18",
37
23
  "chalk": "^4.1.2",
38
24
  "esbuild-register": "^3.6.0",
39
25
  "nest-commander": "^3.15.0",
40
26
  "reflect-metadata": "^0.2.2",
41
27
  "ts-morph": "^24.0.0",
42
- "tsx": "^4.20.6"
28
+ "tsx": "^4.20.6",
29
+ "@config-bound/core": "1.0.0",
30
+ "@config-bound/schema-export": "0.1.3"
43
31
  },
44
32
  "devDependencies": {
45
- "@config-bound/eslint-config": "*",
33
+ "@nestjs/testing": "^11.1.18",
46
34
  "@types/jest": "^30.0.0",
47
- "@types/node": "^24.10.1",
35
+ "@types/node": "^24.10.0",
48
36
  "eslint": "^9.39.1",
49
37
  "jest": "^30.2.0",
50
- "rimraf": "^6.1.2",
38
+ "markdown-table": "^3.0.4",
39
+ "rimraf": "^6.1.0",
51
40
  "ts-jest": "^29.4.5",
52
- "typescript": "^5.9.3"
41
+ "typescript": "^6.0.0",
42
+ "@config-bound/eslint-config": "0.0.0",
43
+ "@config-bound/typescript-config": "0.0.0"
53
44
  },
54
45
  "publishConfig": {
55
46
  "access": "public",
56
47
  "registry": "https://registry.npmjs.org"
57
48
  },
49
+ "engines": {
50
+ "node": ">=22"
51
+ },
58
52
  "repository": {
59
53
  "type": "git",
60
54
  "url": "https://github.com/notr-ai/ConfigBound"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc",
58
+ "clean": "rimraf dist coverage .turbo",
59
+ "dev": "tsx src/main.ts",
60
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
61
+ "test:dev": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
62
+ "test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
63
+ "lint": "eslint src/**/*.ts --fix",
64
+ "lint:ci": "eslint src/**/*.ts",
65
+ "format": "prettier --write --config ../../.config/.prettierrc --ignore-path ../../.config/.prettierignore src/**/*.ts",
66
+ "format:ci": "prettier --check --config ../../.config/.prettierrc --ignore-path ../../.config/.prettierignore src/**/*.ts"
61
67
  }
62
- }
68
+ }
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Generates CLI reference documentation for the docs site.
3
+ *
4
+ * WHAT IT DOES:
5
+ * Boots the Nest CLI application, extracts command metadata from
6
+ * registered CommandRunner instances, and generates markdown reference pages
7
+ * combining authored prose with derived options tables.
8
+ *
9
+ * DEPENDENCIES:
10
+ * - @config-bound/cli package with Nest application and command runners
11
+ * - apps/cli/src/cli.module.ts and command classes
12
+ *
13
+ * OUTPUT:
14
+ * - apps/docs/reference/cli/*.md (index, list, export, generate-bind)
15
+ *
16
+ * RUN VIA:
17
+ * npm run docs:cli (from repo root)
18
+ */
19
+
20
+ import 'reflect-metadata';
21
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
22
+ import { resolve } from 'path';
23
+ import { markdownTable } from 'markdown-table';
24
+ import { CommandFactory } from 'nest-commander';
25
+ import { CliModule } from '../src/cli.module.js';
26
+ import { ExportCommand } from '../src/commands/export.command.js';
27
+ import { ListCommand } from '../src/commands/list.command.js';
28
+ import { GenerateBindCommand } from '../src/commands/generate-bind.command.js';
29
+
30
+ // ─── Types ─────────────────────────────────────────────────────────────────
31
+
32
+ interface CommanderOption {
33
+ flags: string;
34
+ description: string;
35
+ defaultValue: unknown;
36
+ hidden: boolean;
37
+ long: string;
38
+ }
39
+
40
+ interface CommandRunner {
41
+ command: { options: CommanderOption[] };
42
+ }
43
+
44
+ /**
45
+ * A page definition. `sections` is authored prose; `optionsHeading` marks where
46
+ * the derived options table is injected. Pages without options omit the heading.
47
+ */
48
+ interface PageSpec {
49
+ filename: string;
50
+ frontmatterDescription: string;
51
+ title: string;
52
+ intro: string;
53
+ usage: string;
54
+ positionalArgs?: { name: string; description: string; default?: string }[];
55
+ /** Heading text under which the derived options table is rendered. */
56
+ optionsHeading: string;
57
+ sections: { heading: string; body: string }[];
58
+ related: { text: string; link: string }[];
59
+ }
60
+
61
+ // ─── Rendering ─────────────────────────────────────────────────────────────
62
+
63
+ const SCRIPT_NAME = 'generate-docs';
64
+ const GENERATED_NOTICE = `<!-- This file is generated by scripts/generate-cli-docs.mjs. Do not edit it directly. -->`;
65
+
66
+ function renderOptionsTable(options: CommanderOption[]): string {
67
+ const visible = options.filter(
68
+ (opt) => !opt.hidden && opt.long !== '--help' && opt.long !== '--version',
69
+ );
70
+
71
+ const rows = visible.map((opt) => [
72
+ `\`${opt.flags}\``,
73
+ opt.description,
74
+ opt.defaultValue !== undefined && opt.defaultValue !== null
75
+ ? `\`${String(opt.defaultValue)}\``
76
+ : '—'
77
+ ]);
78
+
79
+ return markdownTable([
80
+ ['Flag', 'Description', 'Default'],
81
+ ...rows
82
+ ]);
83
+ }
84
+
85
+ function renderPage(spec: PageSpec, runner: CommandRunner | null): string {
86
+ const parts: string[] = [
87
+ `---\ndescription: ${spec.frontmatterDescription}\n---`,
88
+ GENERATED_NOTICE,
89
+ `# ${spec.title}`,
90
+ spec.intro,
91
+ `## Usage\n\n\`\`\`text\n${spec.usage}\n\`\`\``,
92
+ ];
93
+
94
+ if (spec.positionalArgs && spec.positionalArgs.length > 0) {
95
+ const rows = spec.positionalArgs.map((a) => [
96
+ `\`${a.name}\``,
97
+ a.description,
98
+ a.default ?? 'Required'
99
+ ]);
100
+ const argsTable = markdownTable([
101
+ ['Argument', 'Description', 'Default'],
102
+ ...rows
103
+ ]);
104
+ parts.push(`### Arguments\n\n${argsTable}`);
105
+ }
106
+
107
+ if (runner) {
108
+ parts.push(`### ${spec.optionsHeading}\n\n${renderOptionsTable(runner.command.options)}`);
109
+ }
110
+
111
+ for (const section of spec.sections) {
112
+ parts.push(`## ${section.heading}\n\n${section.body}`);
113
+ }
114
+
115
+ if (spec.related.length > 0) {
116
+ const links = spec.related.map((r) => `- [${r.text}](${r.link})`).join('\n');
117
+ parts.push(`## Related\n\n${links}`);
118
+ }
119
+
120
+ return parts.join('\n\n');
121
+ }
122
+
123
+ // ─── Page definitions ───────────────────────────────────────────────────────
124
+ // Authored prose lives here. Derived content (options tables) is injected at
125
+ // render time from the live CommandRunner instances.
126
+
127
+ const INDEX_PAGE = `---
128
+ description: The ConfigBound CLI provides tools to discover, inspect, and export configuration schemas without writing code.
129
+ ---
130
+
131
+ ${GENERATED_NOTICE}
132
+
133
+ # CLI
134
+
135
+ The \`@config-bound/cli\` package provides the \`configbound\` command — a set of tools for working with ConfigBound configurations from the terminal.
136
+
137
+ ## Installation
138
+
139
+ \`\`\`bash
140
+ npm install --save-dev @config-bound/cli
141
+ \`\`\`
142
+
143
+ After installation, \`npx configbound\` runs the CLI. For global use:
144
+
145
+ \`\`\`bash
146
+ npm install --global @config-bound/cli
147
+ \`\`\`
148
+
149
+ ## Commands
150
+
151
+ | Command | Description |
152
+ | ------- | ----------- |
153
+ | [\`configbound list\`](./list) | Discover and display all ConfigBound configurations in a directory tree |
154
+ | [\`configbound export\`](./export) | Export a configuration schema to JSON, YAML, or \`.env\` format |
155
+ | [\`configbound generate bind\`](./generate-bind) | Scaffold a new bind — embedded class or publishable package |
156
+
157
+ ## How discovery works
158
+
159
+ \`list\` and \`export\` both use config discovery — scanning source files for ConfigBound configurations. Discovery reads the actual exports from your TypeScript files. No separate manifest or registration step is required.
160
+
161
+ \`export\` auto-selects a config when only one is found. When multiple configs exist in the same project, it prompts for selection. Pass \`--config\` and \`--name\` explicitly to skip discovery entirely.
162
+
163
+ ## CLI vs. library
164
+
165
+ The CLI is a companion to the library, not a replacement for it. Schema definition, validation, and value resolution all happen in code. The CLI's job is to inspect what the library produces — listing what configs exist and exporting their schemas as artifacts.`;
166
+
167
+ const LIST_SPEC: PageSpec = {
168
+ filename: 'list.md',
169
+ frontmatterDescription:
170
+ 'Discover and display all ConfigBound configurations in a project using configbound list.',
171
+ title: 'configbound list',
172
+ intro:
173
+ '`list` scans a directory tree for ConfigBound configurations and prints what it finds, grouped by file.\n\nUse it to confirm that discovery works correctly, or to get the exact file paths and export names needed for `configbound export --config ... --name ...`.',
174
+ usage: 'configbound list [path] [options]',
175
+ positionalArgs: [{ name: 'path', description: 'Directory to search', default: 'Current working directory' }],
176
+ optionsHeading: 'Options',
177
+ sections: [
178
+ {
179
+ heading: 'Examples',
180
+ body: `\`\`\`bash
181
+ # List all configs in the current project
182
+ configbound list
183
+
184
+ # Search a specific directory
185
+ configbound list ./src
186
+
187
+ # Search non-recursively
188
+ configbound list --recursive false
189
+ \`\`\``,
190
+ },
191
+ {
192
+ heading: 'Output',
193
+ body: `Results are grouped by file. Each entry shows the export name, whether it is a default or named export, the config's display name if set, and the line number:
194
+
195
+ \`\`\`text
196
+ 📄 src/config/app.config.ts
197
+ default (default) - My App Config :42
198
+
199
+ 📄 src/config/db.config.ts
200
+ default (default) - Database Config :18
201
+ dbConfig (named) - Database Config (read-only) :67
202
+ \`\`\`
203
+
204
+ When a file has multiple exports, each is listed separately. Use the export name with \`--name\` when running \`export\`:
205
+
206
+ \`\`\`bash
207
+ configbound export --config src/config/db.config.ts --name dbConfig
208
+ \`\`\``,
209
+ },
210
+ ],
211
+ related: [{ text: '`configbound export`', link: './export' }],
212
+ };
213
+
214
+ const EXPORT_SPEC: PageSpec = {
215
+ filename: 'export.md',
216
+ frontmatterDescription:
217
+ 'Export a ConfigBound configuration schema to JSON, YAML, or .env format using configbound export.',
218
+ title: 'configbound export',
219
+ intro:
220
+ '`export` loads a ConfigBound configuration and serializes its schema to JSON, YAML, or `.env` format. The output goes to stdout by default, or to a file with `--output`.\n\nThis is the CLI surface for schema export. For the programmatic equivalent, see [Export your configuration schema](/how-to/schema-export). For why schema export matters, see [Schema as the Source of Truth](/explanation/schema-source-of-truth).',
221
+ usage: 'configbound export [options]',
222
+ optionsHeading: 'Options',
223
+ sections: [
224
+ {
225
+ heading: 'Discovery',
226
+ body: `When \`--config\` is omitted, \`export\` scans the current directory for ConfigBound configurations:
227
+
228
+ - One config found: selected automatically.
229
+ - Multiple configs found: a numbered menu is displayed and you choose.
230
+ - No configs found: the command exits and suggests using \`--config\`.
231
+
232
+ Pass \`--config\` to skip discovery entirely. When the file has more than one export, also pass \`--name\` to identify which one to export.`,
233
+ },
234
+ {
235
+ heading: 'Examples',
236
+ body: `\`\`\`bash
237
+ # Auto-discover and export to stdout as JSON
238
+ configbound export
239
+
240
+ # Export a specific file as YAML
241
+ configbound export --config ./src/config/app.config.ts --format yaml
242
+
243
+ # Write JSON output to a file
244
+ configbound export --output ./docs/schema.json
245
+
246
+ # Named export from a file with multiple configs
247
+ configbound export --config ./src/config/all.ts --name databaseConfig
248
+
249
+ # Generate a .env file including normally-omitted elements
250
+ configbound export --format env --include-omitted --output .env.example
251
+ \`\`\``,
252
+ },
253
+ {
254
+ heading: 'Omitted elements',
255
+ body: 'Elements defined with `omitFromSchema: true` are excluded from exported output by default. The intent is to keep sensitive or internal fields out of generated artifacts. Pass `--include-omitted` to override this — useful when generating a complete `.env.example` for onboarding or local development.',
256
+ },
257
+ ],
258
+ related: [
259
+ { text: '`configbound list`', link: './list' },
260
+ { text: 'Export your configuration schema', link: '/how-to/schema-export' },
261
+ { text: 'Schema as the Source of Truth', link: '/explanation/schema-source-of-truth' },
262
+ ],
263
+ };
264
+
265
+ const GENERATE_BIND_SPEC: PageSpec = {
266
+ filename: 'generate-bind.md',
267
+ frontmatterDescription: 'Scaffold a new custom bind using configbound generate bind.',
268
+ title: 'configbound generate bind',
269
+ intro:
270
+ '`generate bind` scaffolds the boilerplate for a new bind. It handles the repetitive parts — class structure, factory pattern, cache setup — so you can focus on the implementation.',
271
+ usage: 'configbound generate bind <name> [options]',
272
+ positionalArgs: [
273
+ { name: 'name', description: 'The bind name in kebab-case (e.g. `vault`, `aws-ssm`, `1password`)', default: 'Required' },
274
+ ],
275
+ optionsHeading: 'Options',
276
+ sections: [
277
+ {
278
+ heading: 'Generation modes',
279
+ body: `**\`embedded\`** generates a single TypeScript class file. The bind lives in your project and is not published. Use this when the bind is specific to one application.
280
+
281
+ **\`package\`** generates a full npm package scaffold: \`package.json\`, \`tsconfig.json\`, \`eslint.config.mjs\`, and a \`src/\` directory containing the bind class and an index. Use this when you intend to publish the bind for others to use.`,
282
+ },
283
+ {
284
+ heading: 'Examples',
285
+ body: `\`\`\`bash
286
+ # Interactive — prompts for embedded or package
287
+ configbound generate bind vault
288
+
289
+ # Non-interactive embedded bind
290
+ configbound generate bind vault --type embedded
291
+
292
+ # Non-interactive package scaffold
293
+ configbound generate bind aws-ssm --type package
294
+
295
+ # Preview what would be generated without writing anything
296
+ configbound generate bind 1password --type package --dry-run
297
+ \`\`\``,
298
+ },
299
+ {
300
+ heading: 'What gets generated',
301
+ body: `Both modes produce a class that extends \`Bind\` with a static async \`create()\` factory and a synchronous \`retrieve()\` method. The generated code includes inline comments explaining what to fill in.
302
+
303
+ The factory pattern pre-loads values into a cache at startup. \`retrieve()\` reads from that cache synchronously. This satisfies the \`Bind\` contract without requiring changes to the core library.`,
304
+ },
305
+ {
306
+ heading: 'Name derivation',
307
+ body: 'The kebab-case name you provide is used to derive the class name. `vault` becomes `VaultBind`. `aws-ssm` becomes `AwsSsmBind`. Leading digits are replaced with words: `1password` becomes `OnepasswordBind`.',
308
+ },
309
+ ],
310
+ related: [
311
+ { text: 'Create a custom bind', link: '/how-to/custom-bind' },
312
+ {
313
+ text: '`Bind` API reference',
314
+ link: '/reference/api/@config-bound.core.bind.bind.Class.Bind',
315
+ },
316
+ ],
317
+ };
318
+
319
+ // ─── Main ──────────────────────────────────────────────────────────────────
320
+
321
+ function writeDocFile(outDir: string, filename: string, content: string): void {
322
+ writeFileSync(resolve(outDir, filename), content + '\n', 'utf-8');
323
+ console.log(` generated: reference/cli/${filename}`);
324
+ }
325
+
326
+ async function main(): Promise<void> {
327
+ // Optional output directory override (defaults to apps/docs/reference/cli).
328
+ // Used for custom builds or testing. Normal builds use the default.
329
+ const outDir = process.argv[2] ?? resolve(import.meta.dirname, '../../apps/docs/reference/cli');
330
+
331
+ const app = await CommandFactory.createWithoutRunning(CliModule, { logger: false });
332
+
333
+ const listRunner = app.get(ListCommand) as CommandRunner;
334
+ const exportRunner = app.get(ExportCommand) as CommandRunner;
335
+ const generateBindRunner = app.get(GenerateBindCommand) as CommandRunner;
336
+
337
+ if (!listRunner?.command?.options) {
338
+ throw new Error(`[${SCRIPT_NAME}] Failed to retrieve ListCommand with valid options`);
339
+ }
340
+ if (!exportRunner?.command?.options) {
341
+ throw new Error(`[${SCRIPT_NAME}] Failed to retrieve ExportCommand with valid options`);
342
+ }
343
+ if (!generateBindRunner?.command?.options) {
344
+ throw new Error(`[${SCRIPT_NAME}] Failed to retrieve GenerateBindCommand with valid options`);
345
+ }
346
+
347
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
348
+
349
+ console.log(`[${SCRIPT_NAME}] Generating CLI reference docs...`);
350
+ writeDocFile(outDir, 'index.md', INDEX_PAGE);
351
+ writeDocFile(outDir, LIST_SPEC.filename, renderPage(LIST_SPEC, listRunner));
352
+ writeDocFile(outDir, EXPORT_SPEC.filename, renderPage(EXPORT_SPEC, exportRunner));
353
+ writeDocFile(outDir, GENERATE_BIND_SPEC.filename, renderPage(GENERATE_BIND_SPEC, generateBindRunner));
354
+ console.log(`[${SCRIPT_NAME}] Done.`);
355
+
356
+ await app.close();
357
+ }
358
+
359
+ main().catch((err: unknown) => {
360
+ console.error(`[${SCRIPT_NAME}] Unexpected error:`, err);
361
+ process.exit(1);
362
+ });
package/src/cli.module.ts CHANGED
@@ -1,19 +1,25 @@
1
1
  import { Module } from '@nestjs/common';
2
2
  import { ListCommand } from './commands/list.command.js';
3
3
  import { ExportCommand } from './commands/export.command.js';
4
+ import { GenerateCommand } from './commands/generate.command.js';
5
+ import { GenerateBindCommand } from './commands/generate-bind.command.js';
4
6
  import { ConfigDiscoveryService } from './services/config-discovery.service.js';
5
7
  import { ConfigLoaderService } from './services/config-loader.service.js';
6
8
  import { SchemaExportService } from './services/schema-export.service.js';
7
9
  import { FileWriterService } from './services/file-writer.service.js';
10
+ import { BindGeneratorService } from './services/bind-generator.service.js';
8
11
 
9
12
  @Module({
10
13
  providers: [
11
14
  ListCommand,
12
15
  ExportCommand,
16
+ GenerateCommand,
17
+ GenerateBindCommand,
13
18
  ConfigDiscoveryService,
14
19
  ConfigLoaderService,
15
20
  SchemaExportService,
16
- FileWriterService
21
+ FileWriterService,
22
+ BindGeneratorService
17
23
  ]
18
24
  })
19
25
  export class CliModule {}