padrone 1.1.0 → 1.3.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +97 -1
  2. package/LICENSE +1 -1
  3. package/README.md +60 -30
  4. package/dist/args-DFEI7_G_.mjs +197 -0
  5. package/dist/args-DFEI7_G_.mjs.map +1 -0
  6. package/dist/chunk-y_GBKt04.mjs +5 -0
  7. package/dist/codegen/index.d.mts +305 -0
  8. package/dist/codegen/index.d.mts.map +1 -0
  9. package/dist/codegen/index.mjs +1358 -0
  10. package/dist/codegen/index.mjs.map +1 -0
  11. package/dist/completion.d.mts +64 -0
  12. package/dist/completion.d.mts.map +1 -0
  13. package/dist/completion.mjs +417 -0
  14. package/dist/completion.mjs.map +1 -0
  15. package/dist/docs/index.d.mts +34 -0
  16. package/dist/docs/index.d.mts.map +1 -0
  17. package/dist/docs/index.mjs +405 -0
  18. package/dist/docs/index.mjs.map +1 -0
  19. package/dist/formatter-XroimS3Q.d.mts +83 -0
  20. package/dist/formatter-XroimS3Q.d.mts.map +1 -0
  21. package/dist/help-CgGP7hQU.mjs +1229 -0
  22. package/dist/help-CgGP7hQU.mjs.map +1 -0
  23. package/dist/index.d.mts +120 -546
  24. package/dist/index.d.mts.map +1 -1
  25. package/dist/index.mjs +1220 -1204
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/test.d.mts +112 -0
  28. package/dist/test.d.mts.map +1 -0
  29. package/dist/test.mjs +138 -0
  30. package/dist/test.mjs.map +1 -0
  31. package/dist/types-BS7RP5Ls.d.mts +1059 -0
  32. package/dist/types-BS7RP5Ls.d.mts.map +1 -0
  33. package/dist/update-check-EbNDkzyV.mjs +146 -0
  34. package/dist/update-check-EbNDkzyV.mjs.map +1 -0
  35. package/package.json +61 -21
  36. package/src/args.ts +457 -0
  37. package/src/cli/completions.ts +29 -0
  38. package/src/cli/docs.ts +86 -0
  39. package/src/cli/doctor.ts +330 -0
  40. package/src/cli/index.ts +159 -0
  41. package/src/cli/init.ts +135 -0
  42. package/src/cli/link.ts +320 -0
  43. package/src/cli/wrap.ts +152 -0
  44. package/src/codegen/README.md +118 -0
  45. package/src/codegen/code-builder.ts +226 -0
  46. package/src/codegen/discovery.ts +232 -0
  47. package/src/codegen/file-emitter.ts +73 -0
  48. package/src/codegen/generators/barrel-file.ts +16 -0
  49. package/src/codegen/generators/command-file.ts +197 -0
  50. package/src/codegen/generators/command-tree.ts +124 -0
  51. package/src/codegen/index.ts +33 -0
  52. package/src/codegen/parsers/fish.ts +163 -0
  53. package/src/codegen/parsers/help.ts +378 -0
  54. package/src/codegen/parsers/merge.ts +158 -0
  55. package/src/codegen/parsers/zsh.ts +221 -0
  56. package/src/codegen/schema-to-code.ts +199 -0
  57. package/src/codegen/template.ts +69 -0
  58. package/src/codegen/types.ts +143 -0
  59. package/src/colorizer.ts +2 -2
  60. package/src/command-utils.ts +504 -0
  61. package/src/completion.ts +110 -97
  62. package/src/create.ts +1048 -308
  63. package/src/docs/index.ts +607 -0
  64. package/src/errors.ts +131 -0
  65. package/src/formatter.ts +195 -73
  66. package/src/help.ts +159 -58
  67. package/src/index.ts +12 -15
  68. package/src/interactive.ts +169 -0
  69. package/src/parse.ts +52 -21
  70. package/src/repl-loop.ts +317 -0
  71. package/src/runtime.ts +304 -0
  72. package/src/shell-utils.ts +83 -0
  73. package/src/test.ts +285 -0
  74. package/src/type-helpers.ts +10 -10
  75. package/src/type-utils.ts +124 -14
  76. package/src/types.ts +752 -154
  77. package/src/update-check.ts +244 -0
  78. package/src/wrap.ts +44 -40
  79. package/src/zod.d.ts +2 -2
  80. package/src/options.ts +0 -180
package/src/completion.ts CHANGED
@@ -1,46 +1,8 @@
1
- import { extractSchemaMetadata } from './options.ts';
1
+ import { extractSchemaMetadata } from './args.ts';
2
+ import { detectShell, getRcFile, type ShellType, writeToRcFile } from './shell-utils.ts';
2
3
  import type { AnyPadroneCommand } from './types.ts';
3
4
 
4
- export type ShellType = 'bash' | 'zsh' | 'fish' | 'powershell';
5
-
6
- /**
7
- * Detects the current shell from environment variables and process info.
8
- * @returns The detected shell type, or undefined if unknown
9
- */
10
- export function detectShell(): ShellType | undefined {
11
- if (typeof process === 'undefined') return undefined;
12
-
13
- // Method 1: Check SHELL environment variable (most common)
14
- const shellEnv = process.env.SHELL || '';
15
- if (shellEnv.includes('zsh')) return 'zsh';
16
- if (shellEnv.includes('bash')) return 'bash';
17
- if (shellEnv.includes('fish')) return 'fish';
18
-
19
- // Method 2: Check Windows-specific shells
20
- if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) {
21
- return 'powershell';
22
- }
23
-
24
- // Method 3: Check parent process on Unix-like systems
25
- try {
26
- const { execSync } = require('node:child_process');
27
- const ppid = process.ppid;
28
- if (ppid) {
29
- const processName = execSync(`ps -p ${ppid} -o comm=`, {
30
- encoding: 'utf-8',
31
- stdio: ['pipe', 'pipe', 'ignore'],
32
- }).trim();
33
-
34
- if (processName.includes('zsh')) return 'zsh';
35
- if (processName.includes('bash')) return 'bash';
36
- if (processName.includes('fish')) return 'fish';
37
- }
38
- } catch {
39
- // Ignore errors (e.g., ps not available)
40
- }
41
-
42
- return undefined;
43
- }
5
+ export { detectShell, escapeRegExp, getRcFile, type ShellType, writeToRcFile } from './shell-utils.ts';
44
6
 
45
7
  /**
46
8
  * Collects all commands from a program recursively.
@@ -61,29 +23,29 @@ function collectAllCommands(cmd: AnyPadroneCommand): AnyPadroneCommand[] {
61
23
  }
62
24
 
63
25
  /**
64
- * Extracts all option names from a command's schema.
26
+ * Extracts all argument names from a command's schema.
65
27
  */
66
- function extractOptions(cmd: AnyPadroneCommand): { name: string; alias?: string; isBoolean: boolean }[] {
67
- const options: { name: string; alias?: string; isBoolean: boolean }[] = [];
28
+ function extractArguments(cmd: AnyPadroneCommand): { name: string; alias?: string; isBoolean: boolean }[] {
29
+ const argList: { name: string; alias?: string; isBoolean: boolean }[] = [];
68
30
 
69
- if (!cmd.options) return options;
31
+ if (!cmd.argsSchema) return argList;
70
32
 
71
33
  try {
72
- const optionsMeta = cmd.meta?.options;
73
- const { aliases } = extractSchemaMetadata(cmd.options, optionsMeta);
34
+ const argsMeta = cmd.meta?.fields;
35
+ const { aliases } = extractSchemaMetadata(cmd.argsSchema, argsMeta, cmd.meta?.autoAlias);
74
36
 
75
- // Reverse aliases map (alias -> option name)
76
- const aliasToOption: Record<string, string> = {};
77
- for (const [opt, alias] of Object.entries(aliases)) {
78
- aliasToOption[alias] = opt;
37
+ // Reverse aliases map (alias -> arg name)
38
+ const aliasToArgument: Record<string, string> = {};
39
+ for (const [arg, alias] of Object.entries(aliases)) {
40
+ aliasToArgument[alias] = arg;
79
41
  }
80
42
 
81
- const jsonSchema = cmd.options['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;
43
+ const jsonSchema = cmd.argsSchema['~standard'].jsonSchema.input({ target: 'draft-2020-12' }) as Record<string, any>;
82
44
 
83
45
  if (jsonSchema.type === 'object' && jsonSchema.properties) {
84
46
  for (const [key, prop] of Object.entries(jsonSchema.properties as Record<string, any>)) {
85
- const alias = Object.entries(aliases).find(([opt]) => opt === key)?.[1];
86
- options.push({
47
+ const alias = Object.entries(aliases).find(([arg]) => arg === key)?.[1];
48
+ argList.push({
87
49
  name: key,
88
50
  alias: alias,
89
51
  isBoolean: prop?.type === 'boolean',
@@ -94,7 +56,7 @@ function extractOptions(cmd: AnyPadroneCommand): { name: string; alias?: string;
94
56
  // Ignore schema parsing errors
95
57
  }
96
58
 
97
- return options;
59
+ return argList;
98
60
  }
99
61
 
100
62
  /**
@@ -105,19 +67,19 @@ export function generateBashCompletion(program: AnyPadroneCommand): string {
105
67
  const commands = collectAllCommands(program);
106
68
  const commandNames = commands.map((c) => c.name).join(' ');
107
69
 
108
- // Collect all options from all commands
109
- const allOptions = new Set<string>();
110
- allOptions.add('--help');
111
- allOptions.add('--version');
70
+ // Collect all args from all commands
71
+ const allArguments = new Set<string>();
72
+ allArguments.add('--help');
73
+ allArguments.add('--version');
112
74
 
113
75
  for (const cmd of [program, ...commands]) {
114
- for (const opt of extractOptions(cmd)) {
115
- allOptions.add(`--${opt.name}`);
116
- if (opt.alias) allOptions.add(`-${opt.alias}`);
76
+ for (const arg of extractArguments(cmd)) {
77
+ allArguments.add(`--${arg.name}`);
78
+ if (arg.alias) allArguments.add(`-${arg.alias}`);
117
79
  }
118
80
  }
119
81
 
120
- const optionsList = Array.from(allOptions).join(' ');
82
+ const argsList = Array.from(allArguments).join(' ');
121
83
 
122
84
  return `###-begin-${programName}-completion-###
123
85
  #
@@ -141,11 +103,11 @@ if type complete &>/dev/null; then
141
103
  prev="\${words[cword-1]}"
142
104
 
143
105
  local commands="${commandNames}"
144
- local options="${optionsList}"
106
+ local args="${argsList}"
145
107
 
146
- # Complete options when current word starts with -
108
+ # Complete args when current word starts with -
147
109
  if [[ "$cur" == -* ]]; then
148
- COMPREPLY=($(compgen -W "$options" -- "$cur"))
110
+ COMPREPLY=($(compgen -W "$args" -- "$cur"))
149
111
  return 0
150
112
  fi
151
113
 
@@ -157,10 +119,10 @@ elif type compdef &>/dev/null; then
157
119
  _${programName}_completion() {
158
120
  local si=$IFS
159
121
  local commands="${commandNames}"
160
- local options="${optionsList}"
122
+ local args="${argsList}"
161
123
 
162
124
  if [[ "\${words[CURRENT]}" == -* ]]; then
163
- compadd -- \${=options}
125
+ compadd -- \${=args}
164
126
  else
165
127
  compadd -- \${=commands}
166
128
  fi
@@ -170,10 +132,10 @@ elif type compdef &>/dev/null; then
170
132
  elif type compctl &>/dev/null; then
171
133
  _${programName}_completion() {
172
134
  local commands="${commandNames}"
173
- local options="${optionsList}"
135
+ local args="${argsList}"
174
136
 
175
137
  if [[ "\${words[CURRENT]}" == -* ]]; then
176
- reply=(\${=options})
138
+ reply=(\${=args})
177
139
  else
178
140
  reply=(\${=commands})
179
141
  fi
@@ -199,25 +161,25 @@ export function generateZshCompletion(program: AnyPadroneCommand): string {
199
161
  })
200
162
  .join('\n');
201
163
 
202
- // Collect all options with descriptions
203
- const optionCompletions: string[] = [];
204
- optionCompletions.push(" '--help[Show help information]'");
205
- optionCompletions.push(" '--version[Show version number]'");
164
+ // Collect all args with descriptions
165
+ const argumentCompletions: string[] = [];
166
+ argumentCompletions.push(" '--help[Show help information]'");
167
+ argumentCompletions.push(" '--version[Show version number]'");
206
168
 
207
- const seenOptions = new Set<string>(['help', 'version']);
169
+ const seenArgs = new Set<string>(['help', 'version']);
208
170
 
209
171
  for (const cmd of [program, ...commands]) {
210
- for (const opt of extractOptions(cmd)) {
211
- if (seenOptions.has(opt.name)) continue;
212
- seenOptions.add(opt.name);
172
+ for (const arg of extractArguments(cmd)) {
173
+ if (seenArgs.has(arg.name)) continue;
174
+ seenArgs.add(arg.name);
213
175
 
214
- const desc = cmd.meta?.options?.[opt.name]?.description || '';
176
+ const desc = cmd.meta?.fields?.[arg.name]?.description || '';
215
177
  const escapedDesc = desc.replace(/'/g, "'\\''").replace(/\[/g, '\\[').replace(/\]/g, '\\]');
216
178
 
217
- if (opt.alias) {
218
- optionCompletions.push(` {-${opt.alias},--${opt.name}}'[${escapedDesc}]'`);
179
+ if (arg.alias) {
180
+ argumentCompletions.push(` {-${arg.alias},--${arg.name}}'[${escapedDesc}]'`);
219
181
  } else {
220
- optionCompletions.push(` '--${opt.name}[${escapedDesc}]'`);
182
+ argumentCompletions.push(` '--${arg.name}[${escapedDesc}]'`);
221
183
  }
222
184
  }
223
185
  }
@@ -233,18 +195,18 @@ export function generateZshCompletion(program: AnyPadroneCommand): string {
233
195
 
234
196
  _${programName}() {
235
197
  local -a commands
236
- local -a options
198
+ local -a args
237
199
 
238
200
  commands=(
239
201
  ${commandCompletions}
240
202
  )
241
203
 
242
- options=(
243
- ${optionCompletions.join('\n')}
204
+ args=(
205
+ ${argumentCompletions.join('\n')}
244
206
  )
245
207
 
246
208
  _arguments -s \\
247
- $options \\
209
+ $args \\
248
210
  '1: :->command' \\
249
211
  '*::arg:->args'
250
212
 
@@ -287,24 +249,24 @@ export function generateFishCompletion(program: AnyPadroneCommand): string {
287
249
  }
288
250
 
289
251
  lines.push('');
290
- lines.push('# Global options');
252
+ lines.push('# Global arguments');
291
253
  lines.push(`complete -c ${programName} -l help -d 'Show help information'`);
292
254
  lines.push(`complete -c ${programName} -l version -d 'Show version number'`);
293
255
 
294
- const seenOptions = new Set<string>(['help', 'version']);
256
+ const seenArgs = new Set<string>(['help', 'version']);
295
257
 
296
258
  for (const cmd of [program, ...commands]) {
297
- for (const opt of extractOptions(cmd)) {
298
- if (seenOptions.has(opt.name)) continue;
299
- seenOptions.add(opt.name);
259
+ for (const arg of extractArguments(cmd)) {
260
+ if (seenArgs.has(arg.name)) continue;
261
+ seenArgs.add(arg.name);
300
262
 
301
- const desc = cmd.meta?.options?.[opt.name]?.description || '';
263
+ const desc = cmd.meta?.fields?.[arg.name]?.description || '';
302
264
  const escapedDesc = desc.replace(/'/g, "\\'");
303
265
 
304
- if (opt.alias) {
305
- lines.push(`complete -c ${programName} -s ${opt.alias} -l ${opt.name} -d '${escapedDesc}'`);
266
+ if (arg.alias) {
267
+ lines.push(`complete -c ${programName} -s ${arg.alias} -l ${arg.name} -d '${escapedDesc}'`);
306
268
  } else {
307
- lines.push(`complete -c ${programName} -l ${opt.name} -d '${escapedDesc}'`);
269
+ lines.push(`complete -c ${programName} -l ${arg.name} -d '${escapedDesc}'`);
308
270
  }
309
271
  }
310
272
  }
@@ -334,10 +296,10 @@ Register-ArgumentCompleter -Native -CommandName ${programName} -ScriptBlock {
334
296
  param($wordToComplete, $commandAst, $cursorPosition)
335
297
 
336
298
  $commands = @(${commandNames})
337
- $options = @('--help', '--version')
299
+ $args = @('--help', '--version')
338
300
 
339
301
  if ($wordToComplete -like '-*') {
340
- $options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
302
+ $args | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
341
303
  [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
342
304
  }
343
305
  } else {
@@ -445,3 +407,54 @@ ${script}`;
445
407
  # ${programName} completion fish > ~/.config/fish/completions/${programName}.fish
446
408
  # ${programName} completion powershell >> $PROFILE`;
447
409
  }
410
+
411
+ export interface SetupCompletionsResult {
412
+ /** The file that was written to. */
413
+ file: string;
414
+ /** Whether an existing completion block was replaced (true) or a new one was appended (false). */
415
+ updated: boolean;
416
+ }
417
+
418
+ /**
419
+ * Sets up shell completions by writing an eval snippet to the appropriate shell config file.
420
+ * Uses marker comments for idempotency — re-running replaces the existing block.
421
+ */
422
+ export function setupCompletions(programName: string, shell: ShellType): SetupCompletionsResult {
423
+ const { existsSync, mkdirSync, writeFileSync } = require('node:fs') as typeof import('node:fs');
424
+ const { join } = require('node:path') as typeof import('node:path');
425
+ const { homedir } = require('node:os') as typeof import('node:os');
426
+
427
+ const beginMarker = `###-begin-${programName}-completion-###`;
428
+ const endMarker = `###-end-${programName}-completion-###`;
429
+ const snippet = buildSetupSnippet(programName, shell, beginMarker, endMarker);
430
+
431
+ if (shell === 'fish') {
432
+ const completionsDir = join(homedir(), '.config', 'fish', 'completions');
433
+ const filePath = join(completionsDir, `${programName}.fish`);
434
+ mkdirSync(completionsDir, { recursive: true });
435
+ const existed = existsSync(filePath);
436
+ writeFileSync(filePath, `${snippet}\n`);
437
+ return { file: filePath, updated: existed };
438
+ }
439
+
440
+ const rcFile = getRcFile(shell);
441
+ if (!rcFile) {
442
+ throw new Error(`Could not determine config file for ${shell}.`);
443
+ }
444
+
445
+ return writeToRcFile(rcFile, snippet, beginMarker, endMarker);
446
+ }
447
+
448
+ function buildSetupSnippet(programName: string, shell: ShellType, beginMarker: string, endMarker: string): string {
449
+ const evalCmd = `${programName} completion ${shell}`;
450
+
451
+ switch (shell) {
452
+ case 'bash':
453
+ case 'zsh':
454
+ return `${beginMarker}\neval "$(${evalCmd})"\n${endMarker}`;
455
+ case 'fish':
456
+ return `${beginMarker}\n${evalCmd} | source\n${endMarker}`;
457
+ case 'powershell':
458
+ return `${beginMarker}\n${evalCmd} | Invoke-Expression\n${endMarker}`;
459
+ }
460
+ }