Object Arguments with Defaults and Validation
This example demonstrates how to work with object-valued options using dot-notation or JSON strings. Object options support:
- Nested properties: Define complex configuration structures with type-safe nested objects
- Default values: Provide defaults at any level (entire object or individual nested properties)
- Required properties: Mark nested properties as required (only validated when parent object is present)
- Validation: Validate the entire object or nested properties
- Coercion: Transform the final object after parsing
- Additional properties: Allow arbitrary properties with a specific type
Dot Notation Syntax
Object properties are accessed using dot notation on the command line:
--config.server.host localhostsetsconfig.server.hostto"localhost"--config.server.port 8080setsconfig.server.portto8080- Multiple properties can be set independently
JSON String Input
Alternatively, you can pass the entire object as a JSON string:
--config '{"server": {"host": "example.com", "port": 8080}}'- JSON input can be mixed with dot notation
- When mixed, the order matters (later values override earlier ones)
Default Value Behavior
- If no
configproperties are provided, the entire default object is used - If some properties are provided, defaults are applied for missing nested properties
- Default values work at any nesting level
Required Properties
Required properties within objects are conditionally required:
- If the parent object is not present at all, nested required properties are not checked
- If the parent object is present (any property is set), nested required properties are validated
For example, if database.host is marked as required:
--config.server.port 8080is valid (database config not present)--config.database.port 5432is invalid (database config present buthostis missing)
1 FILESTry in Playground
All Example Files
FILE EXPLORER
object-notation-cli.ts
1import cliForge from 'cli-forge';
2
3const cli = cliForge('object-arguments', {
4 builder: (args) =>
5 args.option('config', {
6 type: 'object',
7 description: 'Configuration object with nested properties',
8 properties: {
9 server: {
10 type: 'object',
11 description: 'Server configuration',
12 properties: {
13 host: {
14 type: 'string',
15 description: 'Server hostname',
16 default: 'localhost',
17 },
18 port: {
19 type: 'number',
20 description: 'Server port',
21 default: 3000,
22 },
23 ssl: {
24 type: 'boolean',
25 description: 'Enable SSL',
26 default: false,
27 },
28 },
29 },
30 database: {
31 type: 'object',
32 description: 'Database configuration',
33 properties: {
34 host: {
35 type: 'string',
36 description: 'Database hostname',
37 required: true, // This will only be required if database config is provided
38 },
39 port: {
40 type: 'number',
41 description: 'Database port',
42 default: 5432,
43 },
44 name: {
45 type: 'string',
46 description: 'Database name',
47 required: true, // This will only be required if database config is provided
48 },
49 },
50 },
51 features: {
52 type: 'array',
53 items: 'string',
54 description: 'Enabled features',
55 default: ['basic'],
56 },
57 },
58 // You can provide a default for the entire config object
59 default: {
60 server: {
61 host: 'localhost',
62 port: 3000,
63 ssl: false,
64 },
65 features: ['basic'],
66 },
67 // Validate the entire config object
68 validate: (config) => {
69 if (
70 config.server?.port &&
71 (config.server.port < 1 || config.server.port > 65535)
72 ) {
73 return 'Server port must be between 1 and 65535';
74 }
75 return true;
76 },
77 // Coerce can transform the final config object
78 coerce: (config) => {
79 // Add a computed property - use type assertion since we're extending the type
80 if (config.server) {
81 (config.server as { url?: string }).url = `${
82 config.server.ssl ? 'https' : 'http'
83 }://${config.server.host}:${config.server.port}`;
84 }
85 return config as typeof config & { server?: { url: string } };
86 },
87 }),
88 handler: (args) => {
89 console.log('Configuration:');
90 console.log(JSON.stringify(args.config, null, 2));
91
92 // Type-safe access to nested properties
93 if (args.config?.server) {
94 console.log(`\nServer will run at: ${args.config.server.url}`);
95 }
96
97 if (args.config?.database) {
98 console.log(
99 `Database: ${args.config.database.name} at ${args.config.database.host}:${args.config.database.port}`
100 );
101 }
102
103 if (args.config?.features) {
104 console.log(`Features: ${args.config.features.join(', ')}`);
105 }
106 },
107});
108
109// We export the CLI for a few reasons:
110// - Testing
111// - Composition (a CLI can be a subcommand of another CLI)
112// - Docs generation
113export default cli;
114
115// Calling `.forge()` executes the CLI. It's single parameter is the CLI args
116// and they default to `process.argv.slice(2)`.
117if (require.main === module) {
118 (async () => {
119 await cli.forge();
120 })();
121}
122