@tkeron/commands 0.3.1 → 0.4.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/.github/workflows/npm_deploy.yml +13 -7
- package/bun.lockb +0 -0
- package/changelog.md +21 -0
- package/examples/example.ts +116 -0
- package/package.json +8 -2
- package/readme.md +119 -2
- package/src/applyDefaults.ts +20 -0
- package/src/getCommandsFuncs.ts +92 -21
- package/src/getStart.ts +23 -28
- package/src/index.ts +1 -1
- package/src/parseArgs.ts +233 -0
- package/src/textFuncs.ts +32 -7
- package/src/types.ts +59 -4
- package/src/validateOptions.ts +39 -0
- package/tests/applyDefaults.test.ts +133 -0
- package/tests/backward-compatibility.test.ts +187 -0
- package/tests/example.e2e.test.ts +42 -0
- package/tests/getCommandsFuncs.test.ts +492 -0
- package/tests/getStart.test.ts +265 -0
- package/{src → tests/helpers}/testConstants.ts +1 -1
- package/tests/parseArgs.test.ts +565 -0
- package/tests/textFuncs.test.ts +179 -0
- package/tests/validateOptions.test.ts +157 -0
- package/tests/version-command.test.ts +292 -0
- package/tsconfig.json +2 -3
- package/src/example.e2e.test.ts +0 -37
- package/src/example.ts +0 -69
- package/src/getCommandsFuncs.test.ts +0 -94
- package/src/getStart.test.ts +0 -79
- package/src/textFuncs.test.ts +0 -59
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
mock,
|
|
8
|
+
spyOn,
|
|
9
|
+
type Mock,
|
|
10
|
+
} from "bun:test";
|
|
11
|
+
|
|
12
|
+
import { getStart } from "../src/getStart.js";
|
|
13
|
+
import { commandsCollection } from "./helpers/testConstants.js";
|
|
14
|
+
|
|
15
|
+
describe("getStart", () => {
|
|
16
|
+
let callback: Mock<any>;
|
|
17
|
+
let start: (argv?: string[]) => void;
|
|
18
|
+
let logMock: Mock<any>;
|
|
19
|
+
let logs: any[] = [];
|
|
20
|
+
let savedArgv: string[];
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
savedArgv = process.argv;
|
|
24
|
+
logs = [];
|
|
25
|
+
callback = mock();
|
|
26
|
+
commandsCollection.command_1.callback = callback;
|
|
27
|
+
start = getStart(commandsCollection);
|
|
28
|
+
logMock = spyOn(console, "log").mockImplementation((...args: any) => {
|
|
29
|
+
logs.push(args);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
process.argv = savedArgv;
|
|
34
|
+
callback.mockClear();
|
|
35
|
+
logMock.mockRestore();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("happy path, run command", () => {
|
|
39
|
+
start(["", "", "command_1"]);
|
|
40
|
+
expect(callback).toBeCalledTimes(1);
|
|
41
|
+
});
|
|
42
|
+
it("run command with option", () => {
|
|
43
|
+
start(["", "", "command_1", "op1=value1"]);
|
|
44
|
+
expect(callback).toBeCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
it("run command with extra positioned arguments", () => {
|
|
47
|
+
logs = [];
|
|
48
|
+
start(["", "", "command_1", "pos1", "pos2", "pos3"]);
|
|
49
|
+
start(["", "", "command_1", "pos1", "pos2", "pos3", "pos4"]);
|
|
50
|
+
expect(logs).toHaveLength(2);
|
|
51
|
+
expect(logs[0]).toHaveLength(1);
|
|
52
|
+
expect(logs[0][0]).toBe("argument 'pos3' not defined");
|
|
53
|
+
expect(logs[1]).toHaveLength(1);
|
|
54
|
+
expect(logs[1][0]).toBe("arguments 'pos3, pos4' not defined");
|
|
55
|
+
});
|
|
56
|
+
it("run with process.argv", () => {
|
|
57
|
+
process.argv = ["", ""];
|
|
58
|
+
start();
|
|
59
|
+
logs = [];
|
|
60
|
+
commandsCollection.help.callback();
|
|
61
|
+
expect(logs).toHaveLength(1);
|
|
62
|
+
});
|
|
63
|
+
it("should throw when no args passed", () => {
|
|
64
|
+
process.argv = <string[]>(<unknown>undefined);
|
|
65
|
+
expect(start).toThrow(new Error("no arguments passed"));
|
|
66
|
+
});
|
|
67
|
+
it("should throw when less than 2 args passed", () => {
|
|
68
|
+
process.argv = [];
|
|
69
|
+
expect(start).toThrow(new Error("arguments out of range"));
|
|
70
|
+
});
|
|
71
|
+
it("should show message when passed a nonexistent command", () => {
|
|
72
|
+
process.argv = ["", "", "fakeCommand"];
|
|
73
|
+
logs = [];
|
|
74
|
+
start();
|
|
75
|
+
expect(logs).toHaveLength(1);
|
|
76
|
+
expect(logs[0]).toHaveLength(1);
|
|
77
|
+
expect(logs[0][0]).toBe("command 'fakeCommand' not found");
|
|
78
|
+
});
|
|
79
|
+
it("run with less positioned arguments", () => {
|
|
80
|
+
start(["", "", "al1", "arg001"]);
|
|
81
|
+
expect(callback).toBeCalledWith({ pos1: "arg001" });
|
|
82
|
+
});
|
|
83
|
+
it("should parse option values correctly", () => {
|
|
84
|
+
start(["node", "app", "command_1", "opt1=hello"]);
|
|
85
|
+
expect(callback).toBeCalledWith({ opt1: "hello" });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should parse multiple options", () => {
|
|
89
|
+
start(["node", "app", "command_1", "opt1=hello", "opt2=world"]);
|
|
90
|
+
expect(callback).toBeCalledWith({ opt1: "hello", opt2: "world" });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should handle mixed options and positioned arguments", () => {
|
|
94
|
+
start(["node", "app", "al1", "arg001", "opt1=value1"]);
|
|
95
|
+
expect(callback).toBeCalledWith({ pos1: "arg001", opt1: "value1" });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should handle empty option value", () => {
|
|
99
|
+
start(["node", "app", "command_1", "opt1="]);
|
|
100
|
+
expect(callback).toBeCalledWith({ opt1: "" });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should call callback with empty object when no options or args", () => {
|
|
104
|
+
start(["node", "app", "command_1"]);
|
|
105
|
+
expect(callback).toBeCalledWith({});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("getStart - Standard CLI Syntax", () => {
|
|
110
|
+
let callback: Mock<any>;
|
|
111
|
+
let start: (argv?: string[]) => void;
|
|
112
|
+
let logMock: Mock<any>;
|
|
113
|
+
let logs: any[] = [];
|
|
114
|
+
let savedArgv: string[];
|
|
115
|
+
let commandsCollectionStandard: any;
|
|
116
|
+
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
savedArgv = process.argv;
|
|
119
|
+
logs = [];
|
|
120
|
+
callback = mock();
|
|
121
|
+
commandsCollectionStandard = {
|
|
122
|
+
build: {
|
|
123
|
+
name: "build",
|
|
124
|
+
aliases: [],
|
|
125
|
+
description: "Build the project",
|
|
126
|
+
options: [],
|
|
127
|
+
optionsExamples: [],
|
|
128
|
+
positionedArguments: ["source"],
|
|
129
|
+
optionDefinitions: [
|
|
130
|
+
{
|
|
131
|
+
name: "output",
|
|
132
|
+
shortFlag: "o",
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "Output directory",
|
|
135
|
+
default: "./dist",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "verbose",
|
|
139
|
+
shortFlag: "v",
|
|
140
|
+
type: "boolean",
|
|
141
|
+
description: "Verbose output",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "minify",
|
|
145
|
+
type: "boolean",
|
|
146
|
+
description: "Minify output",
|
|
147
|
+
required: false,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
getHelpLine: () => "",
|
|
151
|
+
callback,
|
|
152
|
+
},
|
|
153
|
+
help: {
|
|
154
|
+
name: "help",
|
|
155
|
+
aliases: [],
|
|
156
|
+
description: "Show help",
|
|
157
|
+
options: [],
|
|
158
|
+
optionsExamples: [],
|
|
159
|
+
positionedArguments: [],
|
|
160
|
+
optionDefinitions: [],
|
|
161
|
+
getHelpLine: () => "",
|
|
162
|
+
callback: () => {},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
start = getStart(commandsCollectionStandard);
|
|
166
|
+
logMock = spyOn(console, "log").mockImplementation((...args: any) => {
|
|
167
|
+
logs.push(args);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
afterEach(() => {
|
|
172
|
+
process.argv = savedArgv;
|
|
173
|
+
callback.mockClear();
|
|
174
|
+
logMock.mockRestore();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should parse boolean flag --verbose", () => {
|
|
178
|
+
start(["node", "app", "build", "--verbose"]);
|
|
179
|
+
expect(callback).toBeCalledTimes(1);
|
|
180
|
+
const options = callback.mock.calls[0][0] as any;
|
|
181
|
+
expect(options.verbose).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should parse short flag -v", () => {
|
|
185
|
+
start(["node", "app", "build", "-v"]);
|
|
186
|
+
expect(callback).toBeCalledTimes(1);
|
|
187
|
+
const options = callback.mock.calls[0][0] as any;
|
|
188
|
+
expect(options.verbose).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should parse option with value --output dist", () => {
|
|
192
|
+
start(["node", "app", "build", "--output", "dist"]);
|
|
193
|
+
expect(callback).toBeCalledTimes(1);
|
|
194
|
+
const options = callback.mock.calls[0][0] as any;
|
|
195
|
+
expect(options.output).toBe("dist");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should parse short option -o dist", () => {
|
|
199
|
+
start(["node", "app", "build", "-o", "dist"]);
|
|
200
|
+
expect(callback).toBeCalledTimes(1);
|
|
201
|
+
const options = callback.mock.calls[0][0] as any;
|
|
202
|
+
expect(options.output).toBe("dist");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should parse multiple flags and options", () => {
|
|
206
|
+
start([
|
|
207
|
+
"node",
|
|
208
|
+
"app",
|
|
209
|
+
"build",
|
|
210
|
+
"--verbose",
|
|
211
|
+
"--output",
|
|
212
|
+
"build",
|
|
213
|
+
"--minify",
|
|
214
|
+
]);
|
|
215
|
+
expect(callback).toBeCalledTimes(1);
|
|
216
|
+
const options = callback.mock.calls[0][0] as any;
|
|
217
|
+
expect(options.verbose).toBe(true);
|
|
218
|
+
expect(options.output).toBe("build");
|
|
219
|
+
expect(options.minify).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should apply default values", () => {
|
|
223
|
+
start(["node", "app", "build"]);
|
|
224
|
+
expect(callback).toBeCalledTimes(1);
|
|
225
|
+
const options = callback.mock.calls[0][0] as any;
|
|
226
|
+
expect(options.output).toBe("./dist");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should handle positioned args with standard syntax", () => {
|
|
230
|
+
start(["node", "app", "build", "src", "--verbose"]);
|
|
231
|
+
expect(callback).toBeCalledTimes(1);
|
|
232
|
+
const options = callback.mock.calls[0][0] as any;
|
|
233
|
+
expect(options.source).toBe("src");
|
|
234
|
+
expect(options.verbose).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should validate required options and show error", () => {
|
|
238
|
+
commandsCollectionStandard.build.optionDefinitions[0].required = true;
|
|
239
|
+
commandsCollectionStandard.build.optionDefinitions[0].default = undefined;
|
|
240
|
+
start(["node", "app", "build"]);
|
|
241
|
+
expect(logs.length).toBeGreaterThan(0);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should support mixed legacy and standard syntax", () => {
|
|
245
|
+
start(["node", "app", "build", "--verbose", "key=value"]);
|
|
246
|
+
expect(callback).toBeCalledTimes(1);
|
|
247
|
+
const options = callback.mock.calls[0][0] as any;
|
|
248
|
+
expect(options.verbose).toBe(true);
|
|
249
|
+
expect(options.key).toBe("value");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should handle combined short flags -vx", () => {
|
|
253
|
+
commandsCollectionStandard.build.optionDefinitions.push({
|
|
254
|
+
name: "watch",
|
|
255
|
+
shortFlag: "w",
|
|
256
|
+
type: "boolean",
|
|
257
|
+
description: "Watch mode",
|
|
258
|
+
});
|
|
259
|
+
start(["node", "app", "build", "-vw"]);
|
|
260
|
+
expect(callback).toBeCalledTimes(1);
|
|
261
|
+
const options = callback.mock.calls[0][0] as any;
|
|
262
|
+
expect(options.verbose).toBe(true);
|
|
263
|
+
expect(options.watch).toBe(true);
|
|
264
|
+
});
|
|
265
|
+
});
|