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 +13 -15
- package/README.md +46 -0
- package/dist/lib/importCommand.d.ts +7 -3
- package/dist/lib/importCommand.js +40 -6
- package/dist/lib/importCommand.test.js +0 -1
- package/package.json +1 -1
- package/src/lib/importCommand.test.ts +0 -5
- package/src/lib/importCommand.ts +60 -10
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
|
-
|
|
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)
|
|
17
|
-
throw new Error(`Can not import command from non-
|
|
20
|
+
if (!fs.existsSync(filePath)) {
|
|
21
|
+
throw new Error(`Can not import command from non-existent file path: ${filePath}`);
|
|
18
22
|
}
|
|
19
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
|
package/src/lib/importCommand.ts
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
93
|
+
if (!fs.existsSync(filePath)) {
|
|
90
94
|
throw new Error(
|
|
91
|
-
`Can not import command from non-
|
|
95
|
+
`Can not import command from non-existent file path: ${filePath}`
|
|
92
96
|
);
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
186
|
+
'Importing individual exports from',
|
|
137
187
|
filePath,
|
|
138
188
|
'as',
|
|
139
189
|
name,
|