yargs-file-commands 0.0.19 → 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 +6 -0
- package/README.md +46 -0
- package/dist/lib/importCommand.d.ts +7 -3
- package/dist/lib/importCommand.js +39 -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 +58 -10
package/CHANGELOG.md
CHANGED
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,26 +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
23
|
const url = 'file://' + filePath;
|
|
20
|
-
const handlerModule = (await import(url));
|
|
21
24
|
const { logLevel = 'info' } = options;
|
|
25
|
+
// Import the module
|
|
26
|
+
const imported = await import(url);
|
|
22
27
|
// Check if this is the default command
|
|
23
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;
|
|
24
56
|
const command = {
|
|
25
57
|
command: handlerModule.command ?? (isDefault ? '$0' : name),
|
|
26
58
|
describe: handlerModule.describe,
|
|
27
|
-
|
|
59
|
+
aliases: handlerModule.aliases,
|
|
28
60
|
builder: handlerModule.builder,
|
|
29
61
|
deprecated: handlerModule.deprecated,
|
|
30
62
|
handler: handlerModule.handler ??
|
|
@@ -32,6 +64,7 @@ export const importCommandFromFile = async (filePath, name, options) => {
|
|
|
32
64
|
// null implementation
|
|
33
65
|
})
|
|
34
66
|
};
|
|
67
|
+
// Validate exports
|
|
35
68
|
const supportedNames = [
|
|
36
69
|
'command',
|
|
37
70
|
'describe',
|
|
@@ -40,13 +73,13 @@ export const importCommandFromFile = async (filePath, name, options) => {
|
|
|
40
73
|
'deprecated',
|
|
41
74
|
'handler'
|
|
42
75
|
];
|
|
43
|
-
const module =
|
|
76
|
+
const module = imported;
|
|
44
77
|
const unsupportedExports = Object.keys(module).filter((key) => !supportedNames.includes(key));
|
|
45
78
|
if (unsupportedExports.length > 0) {
|
|
46
79
|
throw new Error(`Command module ${name} in ${filePath} has some unsupported exports, probably a misspelling: ${unsupportedExports.join(', ')}`);
|
|
47
80
|
}
|
|
48
81
|
if (logLevel === 'debug') {
|
|
49
|
-
console.debug('Importing
|
|
82
|
+
console.debug('Importing individual exports from', filePath, 'as', name, 'with description', command.describe);
|
|
50
83
|
}
|
|
51
84
|
return command;
|
|
52
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,36 +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
99
|
const url = 'file://' + filePath;
|
|
96
|
-
|
|
97
|
-
const handlerModule = (await import(url)) as CommandImportModule;
|
|
98
100
|
const { logLevel = 'info' } = options;
|
|
99
101
|
|
|
102
|
+
// Import the module
|
|
103
|
+
const imported = await import(url);
|
|
104
|
+
|
|
100
105
|
// Check if this is the default command
|
|
101
106
|
const isDefault = name === '$default';
|
|
102
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
|
+
|
|
103
148
|
const command = {
|
|
104
149
|
command: handlerModule.command ?? (isDefault ? '$0' : name),
|
|
105
150
|
describe: handlerModule.describe,
|
|
106
|
-
|
|
151
|
+
aliases: handlerModule.aliases,
|
|
107
152
|
builder: handlerModule.builder,
|
|
108
153
|
deprecated: handlerModule.deprecated,
|
|
109
154
|
handler:
|
|
@@ -113,6 +158,7 @@ export const importCommandFromFile = async (
|
|
|
113
158
|
})
|
|
114
159
|
} as CommandModule;
|
|
115
160
|
|
|
161
|
+
// Validate exports
|
|
116
162
|
const supportedNames = [
|
|
117
163
|
'command',
|
|
118
164
|
'describe',
|
|
@@ -121,10 +167,12 @@ export const importCommandFromFile = async (
|
|
|
121
167
|
'deprecated',
|
|
122
168
|
'handler'
|
|
123
169
|
];
|
|
124
|
-
|
|
170
|
+
|
|
171
|
+
const module = imported as Record<string, any>;
|
|
125
172
|
const unsupportedExports = Object.keys(module).filter(
|
|
126
173
|
(key) => !supportedNames.includes(key)
|
|
127
174
|
);
|
|
175
|
+
|
|
128
176
|
if (unsupportedExports.length > 0) {
|
|
129
177
|
throw new Error(
|
|
130
178
|
`Command module ${name} in ${filePath} has some unsupported exports, probably a misspelling: ${unsupportedExports.join(
|
|
@@ -135,7 +183,7 @@ export const importCommandFromFile = async (
|
|
|
135
183
|
|
|
136
184
|
if (logLevel === 'debug') {
|
|
137
185
|
console.debug(
|
|
138
|
-
'Importing
|
|
186
|
+
'Importing individual exports from',
|
|
139
187
|
filePath,
|
|
140
188
|
'as',
|
|
141
189
|
name,
|