goke 6.1.3 → 6.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.
package/README.md CHANGED
@@ -51,7 +51,7 @@ cli
51
51
  .option('--minify', 'Minify output')
52
52
  .example('build src/index.ts')
53
53
  .example('build src/index.ts --minify')
54
- .action((entry, options) => {
54
+ .action(async (entry, options) => { // options is type safe! no need to type it
55
55
  console.log(entry, options)
56
56
  })
57
57
 
@@ -89,6 +89,196 @@ cli.help()
89
89
  cli.parse()
90
90
  ```
91
91
 
92
+ ### Rich Multi-line Command Descriptions (`string-dedent`)
93
+
94
+ When a command needs a long description (with bullets, quotes, inline code, and
95
+ multiple examples), use [`string-dedent`](https://www.npmjs.com/package/string-dedent)
96
+ to keep the source readable while preserving clean help output.
97
+
98
+ Install:
99
+
100
+ ```bash
101
+ npm install string-dedent
102
+ ```
103
+
104
+ Example with detailed command descriptions:
105
+
106
+ ```ts
107
+ import { goke } from 'goke'
108
+ import dedent from 'string-dedent'
109
+
110
+ const cli = goke('acme')
111
+
112
+ cli
113
+ .command(
114
+ 'release <version>',
115
+ dedent`
116
+ Publish a versioned release to your distribution channels.
117
+
118
+ - **Validates** release metadata and changelog before publishing.
119
+ - **Builds** production artifacts with reproducible settings.
120
+ - **Tags** git history using semantic version format.
121
+ - **Publishes** to npm and creates release notes.
122
+
123
+ > Recommended flow: run with \`--dry-run\` first in CI to verify output.
124
+
125
+ Examples:
126
+ - \`acme release 2.4.0 --channel stable\`
127
+ - \`acme release 2.5.0-rc.1 --channel beta --dry-run\`
128
+ - \`acme release 3.0.0 --notes-file ./docs/releases/3.0.0.md\`
129
+ `,
130
+ )
131
+ .option('--channel <name>', 'Target channel: stable, beta, alpha')
132
+ .option('--notes-file <path>', 'Markdown file used as release notes')
133
+ .option('--dry-run', 'Preview every step without publishing')
134
+ .action((version, options) => {
135
+ console.log('release', version, options)
136
+ })
137
+
138
+ cli
139
+ .command(
140
+ 'db migrate',
141
+ dedent`
142
+ Apply pending database migrations in a controlled sequence.
143
+
144
+ - Runs migrations in timestamp order.
145
+ - Stops immediately on first failure.
146
+ - Prints SQL statements when \`--verbose\` is enabled.
147
+ - Supports smoke-testing with \`--dry-run\`.
148
+
149
+ > Safety: always run this command against staging before production.
150
+
151
+ Examples:
152
+ - \`acme db migrate\`
153
+ - \`acme db migrate --target 20260210120000_add_users\`
154
+ - \`acme db migrate --dry-run --verbose\`
155
+ `,
156
+ )
157
+ .option('--target <migration>', 'Apply up to a specific migration id')
158
+ .option('--dry-run', 'Print plan only, do not execute SQL')
159
+ .option('--verbose', 'Show each executed statement')
160
+ .action((options) => {
161
+ console.log('migrate', options)
162
+ })
163
+
164
+ cli.help()
165
+ cli.parse()
166
+ ```
167
+
168
+ Why this pattern works well:
169
+
170
+ - `dedent` keeps template literals readable in source files.
171
+ - Help text stays aligned without extra leading whitespace.
172
+ - You can include rich formatting patterns users already recognize:
173
+ lists, quotes, and inline command snippets.
174
+ - Long descriptions remain maintainable as your CLI grows.
175
+
176
+ ### Rich `.example(...)` Blocks with `dedent`
177
+
178
+ You can also use `dedent` in `.example(...)` so examples stay readable in code and
179
+ render nicely in help output. A useful pattern is to make the **first line a `#`
180
+ comment** that explains the scenario.
181
+
182
+ ```ts
183
+ import { goke } from 'goke'
184
+ import dedent from 'string-dedent'
185
+
186
+ const cli = goke('tuistory')
187
+
188
+ cli
189
+ .command('start', 'Start an interactive session')
190
+ .example(dedent`
191
+ # Launch and immediately check what the app shows
192
+ tuistory launch "claude" -s ai && tuistory -s ai snapshot --trim
193
+ `)
194
+ .example(dedent`
195
+ # Start a focused coding session with explicit context
196
+ tuistory start --agent code --context "Fix OAuth callback timeout"
197
+ `)
198
+ .example(dedent`
199
+ # Recover recent activity and inspect the latest run details
200
+ tuistory runs list --limit 5 && tuistory runs show --latest
201
+ `)
202
+ .action(() => {
203
+ // command implementation
204
+ })
205
+
206
+ cli
207
+ .command('deploy', 'Deploy current workspace')
208
+ .example(dedent`
209
+ # Dry-run deployment first to validate plan
210
+ tuistory deploy --env staging --dry-run
211
+ `)
212
+ .example(dedent`
213
+ # Deploy production with release notes attached
214
+ tuistory deploy --env production --notes ./docs/release.md
215
+ `)
216
+ .action(() => {
217
+ // command implementation
218
+ })
219
+
220
+ cli.help()
221
+ cli.parse()
222
+ ```
223
+
224
+ Notes:
225
+
226
+ - Keep each example focused on one workflow.
227
+ - Use the first `#` line as a human-readable intent label.
228
+ - Keep command lines copy-pastable (avoid placeholder-heavy examples).
229
+
230
+ Where examples are rendered today:
231
+
232
+ - For root help (`deploy --help`), examples from the root/default command appear in an **Examples** section at the end.
233
+ - For subcommand help (`deploy logs --help`), examples from that specific subcommand appear in its own **Examples** section at the end.
234
+
235
+ Inline snapshot-style output (many commands):
236
+
237
+ ```txt
238
+ deploy
239
+
240
+ Usage:
241
+ $ deploy [options]
242
+
243
+ Commands:
244
+ deploy Deploy the current project
245
+ init Initialize a new project
246
+ login Authenticate with the server
247
+ logout Clear saved credentials
248
+ status Show deployment status
249
+ logs <deploymentId> Stream logs for a deployment
250
+
251
+ Options:
252
+ --env <env> Target environment
253
+ --dry-run Preview without deploying
254
+ -h, --help Display this message
255
+
256
+ Examples:
257
+ # Deploy to staging first
258
+ deploy --env staging --dry-run
259
+ ```
260
+
261
+ ```txt
262
+ deploy
263
+
264
+ Usage:
265
+ $ deploy logs <deploymentId>
266
+
267
+ Options:
268
+ --follow Follow log output
269
+ --lines <n> Number of lines (default: 100)
270
+ -h, --help Display this message
271
+
272
+ Description:
273
+ Stream logs for a deployment
274
+
275
+ Examples:
276
+ # Stream last 200 lines for a deployment
277
+ deploy logs dep_123 --lines 200
278
+ # Keep following new log lines
279
+ deploy logs dep_123 --follow
280
+ ```
281
+
92
282
  ### Many Commands with a Root Command
93
283
 
94
284
  Use `''` as the command name to define a root command that runs when no subcommand is given. This is useful for CLIs that have a primary action alongside several subcommands:
@@ -229,11 +419,39 @@ cli
229
419
  cli.parse()
230
420
  ```
231
421
 
232
- The second argument accepts any object implementing [Standard JSON Schema V1](https://github.com/standard-schema/standard-schema), including:
422
+ **Important:** When using a schema with `.default()`, do **not** repeat the default in the description string. The framework automatically appends `(default: <value>)` to help output from the schema default. Writing `.default(100).describe('Number of lines (default: 100)')` would display the default twice.
423
+
424
+ The second argument accepts any object implementing [Standard Schema](https://github.com/standard-schema/standard-schema), including:
233
425
 
234
426
  - **Zod** v4.2+ (e.g. `z.number()`, `z.string()`, `z.array(z.number())`)
235
427
  - **Valibot**, **ArkType**, and other Standard Schema-compatible libraries
236
- - **Plain JSON Schema** via `wrapJsonSchema({ type: "number", description: "Port" })`
428
+
429
+ ### Hiding Deprecated Options
430
+
431
+ Mark options as deprecated using Zod's `.meta({ deprecated: true })`. Deprecated options are hidden from help output but still work for parsing — useful for backward compatibility.
432
+
433
+ ```ts
434
+ import { goke } from 'goke'
435
+ import { z } from 'zod'
436
+
437
+ const cli = goke()
438
+
439
+ cli
440
+ .command('serve', 'Start server')
441
+ // Deprecated option: hidden from --help, still parses
442
+ .option('--old-port <port>', z.number().meta({ deprecated: true, description: 'Use --port instead' }))
443
+ // Current option: visible in help
444
+ .option('--port <port>', z.number().describe('Port number'))
445
+ .action((options) => {
446
+ const port = options.port ?? options.oldPort
447
+ console.log('Starting on port', port)
448
+ })
449
+
450
+ cli.help()
451
+ cli.parse()
452
+ ```
453
+
454
+ When users run `--help`, deprecated options won't appear, but `--old-port 3000` still works.
237
455
 
238
456
  ### Brackets
239
457
 
@@ -267,6 +485,48 @@ cli
267
485
  })
268
486
  ```
269
487
 
488
+ ### Double-dash `--` (end of options)
489
+
490
+ The `--` token signals the end of options. Everything after `--` is available via `options['--']` as a separate array, not mixed into positional args. This lets you distinguish between your command's own arguments and passthrough args — the same pattern used by `doppler`, `npm`, `pnpm`, and `docker`.
491
+
492
+ ```ts
493
+ import { goke } from 'goke'
494
+ import { z } from 'zod'
495
+ import { execSync } from 'child_process'
496
+
497
+ const cli = goke('runner')
498
+
499
+ cli
500
+ .command('run <script>', 'Run a script with injected environment variables')
501
+ .option('--env <env>', z.enum(['dev', 'staging', 'production']).describe('Target environment'))
502
+ .example('# Pass extra flags to the child script via --')
503
+ .example('runner run --env staging server.js -- --port 3000 --verbose')
504
+ .action((script, options) => {
505
+ // runner run --env staging server.js -- --port 3000 --verbose
506
+ // script = 'server.js' (positional arg)
507
+ // options.env = 'staging' (runner's own option)
508
+ // options['--'] = ['--port', '3000', '--verbose'] (passthrough)
509
+
510
+ const secrets = loadSecrets(options.env)
511
+ const extraArgs = (options['--'] || []).join(' ')
512
+ execSync(`node ${script} ${extraArgs}`, {
513
+ env: { ...process.env, ...secrets },
514
+ stdio: 'inherit',
515
+ })
516
+ })
517
+
518
+ cli.help()
519
+ cli.parse()
520
+ ```
521
+
522
+ ```bash
523
+ runner run --env staging server.js -- --port 3000 --verbose
524
+ # ^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
525
+ # runner option positional passthrough (options['--'])
526
+ ```
527
+
528
+ Without `--`, flags like `--port` would be parsed as runner options and fail with "Unknown option `--port`". The `--` tells goke to stop parsing and collect the rest separately.
529
+
270
530
  ### Dot-nested Options
271
531
 
272
532
  Dot-nested options will be merged into a single option.
@@ -386,6 +646,24 @@ Add a global option. The second argument is either:
386
646
 
387
647
  - Type: `() => CLI`
388
648
 
649
+ Print the help message to stdout.
650
+
651
+ #### cli.helpText()
652
+
653
+ - Type: `() => string`
654
+
655
+ Return the formatted help string without printing it. Useful for embedding help text in documentation, tests, or other programmatic uses.
656
+
657
+ ```ts
658
+ const cli = goke('mycli')
659
+ cli.command('build', 'Build project')
660
+ cli.option('--watch', 'Watch mode')
661
+ cli.help()
662
+
663
+ const help = cli.helpText()
664
+ // => "mycli\n\nUsage:\n $ mycli ..."
665
+ ```
666
+
389
667
  #### cli.usage(text)
390
668
 
391
669
  - Type: `(text: string) => CLI`