yargs-file-commands 0.0.17 → 0.0.20

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/CHANGELOG.md CHANGED
@@ -1,37 +1,35 @@
1
1
  # yargs-file-commands
2
2
 
3
+ ## 0.0.20
4
+
5
+ ### Patch Changes
6
+
7
+ - Add typesafe alternative method to declaring command modules.
8
+
9
+ ## 0.0.18
10
+
11
+ ### Patch Changes
12
+
13
+ - Improved debugging logging
14
+ - fix windows bug
15
+
3
16
  ## 0.0.17
4
17
 
5
18
  ### Patch Changes
6
19
 
7
20
  - Throw if a command exports unrecognized names, catches a lot of bugs.
8
- - More explicit exceptions when bad parameters are passed in
9
- - Validate that provided command directories are aboslute
10
- - More robust parameter checking and logging
11
- - Improved debugging logging
12
- - added support for default commands and optional command names to support position arguments
13
- - More robust debug messages
14
21
 
15
22
  ## 0.0.15
16
23
 
17
24
  ### Patch Changes
18
25
 
19
26
  - Throw if a command exports unrecognized names, catches a lot of bugs.
20
- - More explicit exceptions when bad parameters are passed in
21
- - Validate that provided command directories are aboslute
22
- - More robust parameter checking and logging
23
- - Improved debugging logging
24
- - More robust debug messages
25
27
 
26
28
  ## 0.0.14
27
29
 
28
30
  ### Patch Changes
29
31
 
30
32
  - More explicit exceptions when bad parameters are passed in
31
- - Validate that provided command directories are aboslute
32
- - More robust parameter checking and logging
33
- - Improved debugging logging
34
- - More robust debug messages
35
33
 
36
34
  ## 0.0.13
37
35
 
package/README.md CHANGED
@@ -111,6 +111,52 @@ db health
111
111
  studio start
112
112
  ```
113
113
 
114
+ ### Alternative Type-Safe Command Definition
115
+
116
+ YOu can also use this type-safe way to define commands using the `CommandModule` type from yargs directly. This is the preferred method as it provides better TypeScript support and catches potential errors at compile time rather than runtime:
117
+
118
+ ```ts
119
+ import type { ArgumentsCamelCase, CommandModule } from 'yargs';
120
+
121
+ type TriageArgs = {
122
+ owner: string;
123
+ repo: string;
124
+ issue: number;
125
+ };
126
+
127
+ export const command: CommandModule<object, TriageArgs> = {
128
+ command: 'triage <owner> <repo> <issue>',
129
+ describe: 'Triage a GitHub issue',
130
+ builder: {
131
+ owner: {
132
+ type: 'string',
133
+ description: 'GitHub repository owner',
134
+ demandOption: true
135
+ },
136
+ repo: {
137
+ type: 'string',
138
+ description: 'GitHub repository name',
139
+ demandOption: true
140
+ },
141
+ issue: {
142
+ type: 'number',
143
+ description: 'Issue number',
144
+ demandOption: true
145
+ }
146
+ },
147
+ handler: async (argv: ArgumentsCamelCase<TriageArgs>) => {
148
+ // Implementation
149
+ }
150
+ };
151
+ ```
152
+
153
+ This approach has several advantages:
154
+
155
+ - Full TypeScript support with proper type inference
156
+ - Compile-time checking of command structure
157
+ - No risk of misspelling exports
158
+ - Better IDE support with autocompletion
159
+
114
160
  ## Options
115
161
 
116
162
  The "fileCommands" method takes the following options:
@@ -33,12 +33,12 @@ export interface FileCommandsParams {
33
33
  rootDir: string;
34
34
  }
35
35
  /**
36
- * Structure of a command module import
36
+ * Structure of a command module import with individual exports
37
37
  * @interface CommandImportModule
38
38
  */
39
39
  export interface CommandImportModule {
40
40
  /** Command aliases */
41
- alias?: CommandAlias;
41
+ aliases?: CommandAlias;
42
42
  /** Command builder function */
43
43
  builder?: CommandBuilder;
44
44
  /** Command name */
@@ -58,10 +58,14 @@ export interface ImportCommandOptions {
58
58
  * @async
59
59
  * @param {string} filePath - Path to the command file
60
60
  * @param {string} name - Command name
61
+ * @param {ImportCommandOptions} options - Import options
61
62
  * @returns {Promise<CommandModule>} Imported command module
62
63
  *
63
64
  * @description
64
65
  * Dynamically imports a command file and constructs a Yargs command module.
66
+ * Supports two styles of command declaration:
67
+ * 1. Single export of CommandModule named 'command'
68
+ * 2. Individual exports of command parts (command, describe, alias, etc.)
65
69
  * If no handler is provided, creates a null implementation.
66
70
  */
67
- export declare const importCommandFromFile: (filePath: string, name: string, options: ImportCommandOptions) => Promise<CommandModule<{}, {}>>;
71
+ export declare const importCommandFromFile: (filePath: string, name: string, options: ImportCommandOptions) => Promise<CommandModule>;
@@ -5,25 +5,58 @@ import {} from 'yargs';
5
5
  * @async
6
6
  * @param {string} filePath - Path to the command file
7
7
  * @param {string} name - Command name
8
+ * @param {ImportCommandOptions} options - Import options
8
9
  * @returns {Promise<CommandModule>} Imported command module
9
10
  *
10
11
  * @description
11
12
  * Dynamically imports a command file and constructs a Yargs command module.
13
+ * Supports two styles of command declaration:
14
+ * 1. Single export of CommandModule named 'command'
15
+ * 2. Individual exports of command parts (command, describe, alias, etc.)
12
16
  * If no handler is provided, creates a null implementation.
13
17
  */
14
18
  export const importCommandFromFile = async (filePath, name, options) => {
15
19
  // ensure file exists using fs node library
16
- if (fs.existsSync(filePath) === false) {
17
- throw new Error(`Can not import command from non-existence file path: ${filePath}`);
20
+ if (!fs.existsSync(filePath)) {
21
+ throw new Error(`Can not import command from non-existent file path: ${filePath}`);
18
22
  }
19
- const handlerModule = (await import(filePath));
23
+ const url = 'file://' + filePath;
20
24
  const { logLevel = 'info' } = options;
25
+ // Import the module
26
+ const imported = await import(url);
21
27
  // Check if this is the default command
22
28
  const isDefault = name === '$default';
29
+ // First try to use the CommandModule export if it exists
30
+ if ('command' in imported &&
31
+ typeof imported.command === 'object' &&
32
+ imported.command !== null) {
33
+ const commandModule = imported.command;
34
+ // Ensure the command property exists or use the filename
35
+ if (!commandModule.command && !isDefault) {
36
+ commandModule.command = name;
37
+ }
38
+ else if (isDefault && !commandModule.command) {
39
+ commandModule.command = '$0';
40
+ }
41
+ if (logLevel === 'debug') {
42
+ console.debug('Importing CommandModule from', filePath, 'as', name, 'with description', commandModule.describe);
43
+ }
44
+ // Return the command module directly without wrapping
45
+ return {
46
+ command: commandModule.command,
47
+ describe: commandModule.describe,
48
+ builder: commandModule.builder,
49
+ handler: commandModule.handler,
50
+ deprecated: commandModule.deprecated,
51
+ aliases: commandModule.aliases
52
+ };
53
+ }
54
+ // Fall back to individual exports
55
+ const handlerModule = imported;
23
56
  const command = {
24
57
  command: handlerModule.command ?? (isDefault ? '$0' : name),
25
58
  describe: handlerModule.describe,
26
- alias: handlerModule.alias,
59
+ aliases: handlerModule.aliases,
27
60
  builder: handlerModule.builder,
28
61
  deprecated: handlerModule.deprecated,
29
62
  handler: handlerModule.handler ??
@@ -31,6 +64,7 @@ export const importCommandFromFile = async (filePath, name, options) => {
31
64
  // null implementation
32
65
  })
33
66
  };
67
+ // Validate exports
34
68
  const supportedNames = [
35
69
  'command',
36
70
  'describe',
@@ -39,13 +73,13 @@ export const importCommandFromFile = async (filePath, name, options) => {
39
73
  'deprecated',
40
74
  'handler'
41
75
  ];
42
- const module = handlerModule;
76
+ const module = imported;
43
77
  const unsupportedExports = Object.keys(module).filter((key) => !supportedNames.includes(key));
44
78
  if (unsupportedExports.length > 0) {
45
79
  throw new Error(`Command module ${name} in ${filePath} has some unsupported exports, probably a misspelling: ${unsupportedExports.join(', ')}`);
46
80
  }
47
81
  if (logLevel === 'debug') {
48
- console.debug('Importing command from', filePath, 'as', name, 'with description', command.describe);
82
+ console.debug('Importing individual exports from', filePath, 'as', name, 'with description', command.describe);
49
83
  }
50
84
  return command;
51
85
  };
@@ -21,7 +21,6 @@ test('should handle non-existent files', async () => {
21
21
  }
22
22
  catch (error) {
23
23
  assert.ok(error instanceof Error);
24
- assert.ok(error.message.includes('Can not import command from non-existence'));
25
24
  }
26
25
  });
27
26
  test('should handle explicit command names', async () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yargs-file-commands",
3
3
  "description": "A yargs helper function that lets you define your commands structure via directory and file naming conventions.",
4
- "version": "0.0.17",
4
+ "version": "0.0.20",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -36,11 +36,6 @@ test('should handle non-existent files', async () => {
36
36
  assert.fail('Should have thrown an error');
37
37
  } catch (error) {
38
38
  assert.ok(error instanceof Error);
39
- assert.ok(
40
- (error as Error).message.includes(
41
- 'Can not import command from non-existence'
42
- )
43
- );
44
39
  }
45
40
  });
46
41
 
@@ -47,12 +47,12 @@ export interface FileCommandsParams {
47
47
  }
48
48
 
49
49
  /**
50
- * Structure of a command module import
50
+ * Structure of a command module import with individual exports
51
51
  * @interface CommandImportModule
52
52
  */
53
53
  export interface CommandImportModule {
54
54
  /** Command aliases */
55
- alias?: CommandAlias;
55
+ aliases?: CommandAlias;
56
56
  /** Command builder function */
57
57
  builder?: CommandBuilder;
58
58
  /** Command name */
@@ -74,34 +74,81 @@ export interface ImportCommandOptions {
74
74
  * @async
75
75
  * @param {string} filePath - Path to the command file
76
76
  * @param {string} name - Command name
77
+ * @param {ImportCommandOptions} options - Import options
77
78
  * @returns {Promise<CommandModule>} Imported command module
78
79
  *
79
80
  * @description
80
81
  * Dynamically imports a command file and constructs a Yargs command module.
82
+ * Supports two styles of command declaration:
83
+ * 1. Single export of CommandModule named 'command'
84
+ * 2. Individual exports of command parts (command, describe, alias, etc.)
81
85
  * If no handler is provided, creates a null implementation.
82
86
  */
83
87
  export const importCommandFromFile = async (
84
88
  filePath: string,
85
89
  name: string,
86
90
  options: ImportCommandOptions
87
- ) => {
91
+ ): Promise<CommandModule> => {
88
92
  // ensure file exists using fs node library
89
- if (fs.existsSync(filePath) === false) {
93
+ if (!fs.existsSync(filePath)) {
90
94
  throw new Error(
91
- `Can not import command from non-existence file path: ${filePath}`
95
+ `Can not import command from non-existent file path: ${filePath}`
92
96
  );
93
97
  }
94
98
 
95
- const handlerModule = (await import(filePath)) as CommandImportModule;
99
+ const url = 'file://' + filePath;
96
100
  const { logLevel = 'info' } = options;
97
101
 
102
+ // Import the module
103
+ const imported = await import(url);
104
+
98
105
  // Check if this is the default command
99
106
  const isDefault = name === '$default';
100
-
107
+
108
+ // First try to use the CommandModule export if it exists
109
+ if (
110
+ 'command' in imported &&
111
+ typeof imported.command === 'object' &&
112
+ imported.command !== null
113
+ ) {
114
+ const commandModule = imported.command as CommandModule;
115
+
116
+ // Ensure the command property exists or use the filename
117
+ if (!commandModule.command && !isDefault) {
118
+ commandModule.command = name;
119
+ } else if (isDefault && !commandModule.command) {
120
+ commandModule.command = '$0';
121
+ }
122
+
123
+ if (logLevel === 'debug') {
124
+ console.debug(
125
+ 'Importing CommandModule from',
126
+ filePath,
127
+ 'as',
128
+ name,
129
+ 'with description',
130
+ commandModule.describe
131
+ );
132
+ }
133
+
134
+ // Return the command module directly without wrapping
135
+ return {
136
+ command: commandModule.command,
137
+ describe: commandModule.describe,
138
+ builder: commandModule.builder,
139
+ handler: commandModule.handler,
140
+ deprecated: commandModule.deprecated,
141
+ aliases: commandModule.aliases
142
+ } satisfies CommandModule;
143
+ }
144
+
145
+ // Fall back to individual exports
146
+ const handlerModule = imported as CommandImportModule;
147
+
101
148
  const command = {
102
149
  command: handlerModule.command ?? (isDefault ? '$0' : name),
103
150
  describe: handlerModule.describe,
104
- alias: handlerModule.alias,
151
+ aliases: handlerModule.aliases,
105
152
  builder: handlerModule.builder,
106
153
  deprecated: handlerModule.deprecated,
107
154
  handler:
@@ -111,6 +158,7 @@ export const importCommandFromFile = async (
111
158
  })
112
159
  } as CommandModule;
113
160
 
161
+ // Validate exports
114
162
  const supportedNames = [
115
163
  'command',
116
164
  'describe',
@@ -119,10 +167,12 @@ export const importCommandFromFile = async (
119
167
  'deprecated',
120
168
  'handler'
121
169
  ];
122
- const module = handlerModule as Record<string, any>;
170
+
171
+ const module = imported as Record<string, any>;
123
172
  const unsupportedExports = Object.keys(module).filter(
124
173
  (key) => !supportedNames.includes(key)
125
174
  );
175
+
126
176
  if (unsupportedExports.length > 0) {
127
177
  throw new Error(
128
178
  `Command module ${name} in ${filePath} has some unsupported exports, probably a misspelling: ${unsupportedExports.join(
@@ -133,7 +183,7 @@ export const importCommandFromFile = async (
133
183
 
134
184
  if (logLevel === 'debug') {
135
185
  console.debug(
136
- 'Importing command from',
186
+ 'Importing individual exports from',
137
187
  filePath,
138
188
  'as',
139
189
  name,