Skip to content

Commit df4731b

Browse files
committed
refactor(cli): extract parsing and validation into separate class
this makes the division of responsibilities more clear: the Validator parses cli args and validates their inputs against our types. The CLI is then free to accept the parsed args and simply trigger the provided commands
1 parent 90d8729 commit df4731b

File tree

2 files changed

+180
-166
lines changed

2 files changed

+180
-166
lines changed

packages/scripts/src/cli.ts

Lines changed: 12 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,39 @@ import { Command } from "commander";
88
import { runBuild } from "./commands/build";
99
import { ALL_PACKAGES, CATEGORY_VM } from "./common/cli.constants";
1010
import { startDeleteFlow } from "./commands/delete";
11-
import {
12-
Options_Cli,
13-
Options_Cli_Build,
14-
Options_Cli_Delete,
15-
Schema_Options_Cli_Build,
16-
Schema_Options_Cli_Delete,
17-
Schema_Options_Cli_Root,
18-
} from "./common/cli.types";
19-
import { getPckgsTo, log } from "./common/cli.utils";
11+
import { CliValidator } from "./cli.validator";
2012

2113
class CompassCli {
2214
private program: Command;
23-
private options: Options_Cli;
15+
private validator: CliValidator;
2416

2517
constructor(args: string[]) {
2618
this.program = this._createProgram();
19+
this.validator = new CliValidator(this.program);
2720
this.program.parse(args);
28-
this.options = this._getCliOptions();
2921
}
3022

3123
public async run() {
32-
const { force, user } = this.options;
24+
const options = this.validator.getCliOptions();
25+
const { force, user } = options;
3326
const cmd = this.program.args[0];
3427

3528
switch (true) {
3629
case cmd === "build": {
37-
await this._validateBuild();
38-
await runBuild(this.options);
30+
await this.validator.validateBuild(options);
31+
await runBuild(options);
3932
break;
4033
}
4134
case cmd === "delete": {
42-
this._validateDelete();
35+
this.validator.validateDelete(options);
4336
await startDeleteFlow(user as string, force);
4437
break;
4538
}
4639
default:
47-
this._exitHelpfully("root", `${cmd as string} is not a supported cmd`);
40+
this.validator.exitHelpfully(
41+
"root",
42+
`${cmd as string} is not a supported cmd`
43+
);
4844
}
4945
}
5046

@@ -78,156 +74,6 @@ class CompassCli {
7874
);
7975
return program;
8076
}
81-
82-
private _exitHelpfully(cmd: "root" | "build" | "delete", msg?: string) {
83-
msg && log.error(msg);
84-
85-
if (cmd === "root") {
86-
console.log(this.program.helpInformation());
87-
} else {
88-
const command = this.program.commands.find(
89-
(c) => c.name() === cmd
90-
) as Command;
91-
console.log(command.helpInformation());
92-
}
93-
94-
process.exit(1);
95-
}
96-
97-
private _getBuildOptions() {
98-
const buildOpts: Options_Cli_Build = {};
99-
100-
const buildCmd = this.program.commands.find(
101-
(cmd) => cmd.name() === "build"
102-
);
103-
if (buildCmd) {
104-
const packages = this.program.args[1]?.split(",");
105-
if (packages) {
106-
buildOpts.packages = packages;
107-
}
108-
109-
const environment = buildCmd?.opts()[
110-
"environment"
111-
] as Options_Cli_Build["environment"];
112-
if (environment) {
113-
buildOpts.environment = environment;
114-
}
115-
116-
const clientId = buildCmd?.opts()[
117-
"clientId"
118-
] as Options_Cli_Build["clientId"];
119-
if (clientId) {
120-
buildOpts.clientId = clientId;
121-
}
122-
}
123-
return buildOpts;
124-
}
125-
126-
private _getCliOptions(): Options_Cli {
127-
const options = this._mergeOptions();
128-
const validOptions = this._validateOptions(options);
129-
130-
console.log("options", options);
131-
console.log("validOptions:", validOptions);
132-
return validOptions;
133-
}
134-
135-
private _getDeleteOptions() {
136-
const deleteOpts: Options_Cli_Delete = {};
137-
138-
const deleteCmd = this.program.commands.find(
139-
(cmd) => cmd.name() === "delete"
140-
);
141-
if (deleteCmd) {
142-
const user = deleteCmd?.opts()["user"] as Options_Cli["user"];
143-
if (user) {
144-
deleteOpts.user = user;
145-
}
146-
}
147-
148-
return deleteOpts;
149-
}
150-
151-
private _mergeOptions = (): Options_Cli => {
152-
const _options = this.program.opts();
153-
let options: Options_Cli = {
154-
..._options,
155-
force: _options["force"] === true,
156-
};
157-
158-
const buildOptions = this._getBuildOptions();
159-
if (Object.keys(buildOptions).length > 0) {
160-
options = {
161-
...options,
162-
...buildOptions,
163-
};
164-
}
165-
166-
const deleteOptions = this._getDeleteOptions();
167-
if (Object.keys(deleteOptions).length > 0) {
168-
options = {
169-
...options,
170-
...deleteOptions,
171-
};
172-
}
173-
174-
return options;
175-
};
176-
177-
private async _validateBuild() {
178-
if (!this.options.packages) {
179-
this.options.packages = await getPckgsTo("build");
180-
}
181-
182-
const unsupportedPackages = this.options.packages.filter(
183-
(pkg) => !ALL_PACKAGES.includes(pkg)
184-
);
185-
if (unsupportedPackages.length > 0) {
186-
this._exitHelpfully(
187-
"build",
188-
`One or more of these packages isn't supported: ${unsupportedPackages.toString()}`
189-
);
190-
}
191-
}
192-
193-
private _validateDelete() {
194-
const { user } = this.options;
195-
if (!user || typeof user !== "string") {
196-
this._exitHelpfully("delete", "You must supply a user");
197-
}
198-
}
199-
200-
private _validateOptions(options: Options_Cli) {
201-
const { data: rootData, error: rootError } =
202-
Schema_Options_Cli_Root.safeParse(options);
203-
if (rootError) {
204-
this._exitHelpfully(
205-
"root",
206-
`Invalid CLI options: ${rootError.toString()}`
207-
);
208-
}
209-
210-
const { data: buildData, error: buildError } =
211-
Schema_Options_Cli_Build.safeParse(options);
212-
if (buildError) {
213-
this._exitHelpfully(
214-
"build",
215-
`Invalid build options: ${buildError.toString()}`
216-
);
217-
}
218-
219-
const { data: deleteData, error: deleteError } =
220-
Schema_Options_Cli_Delete.safeParse(options);
221-
if (deleteError) {
222-
this._exitHelpfully(
223-
"delete",
224-
`Invalid delete options: ${deleteError.toString()}`
225-
);
226-
}
227-
228-
const data: Options_Cli = { ...rootData, ...buildData, ...deleteData };
229-
return data;
230-
}
23177
}
23278

23379
const cli = new CompassCli(process.argv);
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { Command } from "commander";
2+
3+
import { ALL_PACKAGES } from "./common/cli.constants";
4+
import {
5+
Options_Cli,
6+
Options_Cli_Build,
7+
Options_Cli_Delete,
8+
Schema_Options_Cli_Build,
9+
Schema_Options_Cli_Delete,
10+
Schema_Options_Cli_Root,
11+
} from "./common/cli.types";
12+
import { getPckgsTo, log } from "./common/cli.utils";
13+
14+
export class CliValidator {
15+
private program: Command;
16+
17+
constructor(program: Command) {
18+
this.program = program;
19+
}
20+
21+
public exitHelpfully(cmd: "root" | "build" | "delete", msg?: string) {
22+
msg && log.error(msg);
23+
24+
if (cmd === "root") {
25+
console.log(this.program.helpInformation());
26+
} else {
27+
const command = this.program.commands.find(
28+
(c) => c.name() === cmd
29+
) as Command;
30+
console.log(command.helpInformation());
31+
}
32+
33+
process.exit(1);
34+
}
35+
36+
public getCliOptions(): Options_Cli {
37+
const options = this._mergeOptions();
38+
const validOptions = this._validateOptions(options);
39+
40+
return validOptions;
41+
}
42+
43+
public async validateBuild(options: Options_Cli) {
44+
if (!options.packages) {
45+
options.packages = await getPckgsTo("build");
46+
}
47+
48+
const unsupportedPackages = options.packages.filter(
49+
(pkg) => !ALL_PACKAGES.includes(pkg)
50+
);
51+
if (unsupportedPackages.length > 0) {
52+
this.exitHelpfully(
53+
"build",
54+
`One or more of these packages isn't supported: ${unsupportedPackages.toString()}`
55+
);
56+
}
57+
}
58+
59+
public validateDelete(options: Options_Cli) {
60+
const { user } = options;
61+
if (!user || typeof user !== "string") {
62+
this.exitHelpfully("delete", "You must supply a user");
63+
}
64+
}
65+
66+
private _getBuildOptions() {
67+
const buildOpts: Options_Cli_Build = {};
68+
69+
const buildCmd = this.program.commands.find(
70+
(cmd) => cmd.name() === "build"
71+
);
72+
if (buildCmd) {
73+
const packages = this.program.args[1]?.split(",");
74+
if (packages) {
75+
buildOpts.packages = packages;
76+
}
77+
78+
const environment = buildCmd?.opts()[
79+
"environment"
80+
] as Options_Cli_Build["environment"];
81+
if (environment) {
82+
buildOpts.environment = environment;
83+
}
84+
85+
const clientId = buildCmd?.opts()[
86+
"clientId"
87+
] as Options_Cli_Build["clientId"];
88+
if (clientId) {
89+
buildOpts.clientId = clientId;
90+
}
91+
}
92+
return buildOpts;
93+
}
94+
95+
private _getDeleteOptions() {
96+
const deleteOpts: Options_Cli_Delete = {};
97+
98+
const deleteCmd = this.program.commands.find(
99+
(cmd) => cmd.name() === "delete"
100+
);
101+
if (deleteCmd) {
102+
const user = deleteCmd?.opts()["user"] as Options_Cli["user"];
103+
if (user) {
104+
deleteOpts.user = user;
105+
}
106+
}
107+
108+
return deleteOpts;
109+
}
110+
111+
private _mergeOptions = (): Options_Cli => {
112+
const _options = this.program.opts();
113+
let options: Options_Cli = {
114+
..._options,
115+
force: _options["force"] === true,
116+
};
117+
118+
const buildOptions = this._getBuildOptions();
119+
if (Object.keys(buildOptions).length > 0) {
120+
options = {
121+
...options,
122+
...buildOptions,
123+
};
124+
}
125+
126+
const deleteOptions = this._getDeleteOptions();
127+
if (Object.keys(deleteOptions).length > 0) {
128+
options = {
129+
...options,
130+
...deleteOptions,
131+
};
132+
}
133+
134+
return options;
135+
};
136+
137+
private _validateOptions(options: Options_Cli) {
138+
const { data: rootData, error: rootError } =
139+
Schema_Options_Cli_Root.safeParse(options);
140+
if (rootError) {
141+
this.exitHelpfully(
142+
"root",
143+
`Invalid CLI options: ${rootError.toString()}`
144+
);
145+
}
146+
147+
const { data: buildData, error: buildError } =
148+
Schema_Options_Cli_Build.safeParse(options);
149+
if (buildError) {
150+
this.exitHelpfully(
151+
"build",
152+
`Invalid build options: ${buildError.toString()}`
153+
);
154+
}
155+
156+
const { data: deleteData, error: deleteError } =
157+
Schema_Options_Cli_Delete.safeParse(options);
158+
if (deleteError) {
159+
this.exitHelpfully(
160+
"delete",
161+
`Invalid delete options: ${deleteError.toString()}`
162+
);
163+
}
164+
165+
const data: Options_Cli = { ...rootData, ...buildData, ...deleteData };
166+
return data;
167+
}
168+
}

0 commit comments

Comments
 (0)