Skip to content

Commit 90d8729

Browse files
committed
refactor(cli): move validation from util to Cli class
this improves readability, because the class has access to the 'options', which means we don't have to keep passing that arg to the util functions
1 parent c4d387f commit 90d8729

File tree

4 files changed

+200
-98
lines changed

4 files changed

+200
-98
lines changed

packages/scripts/src/cli.ts

Lines changed: 181 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,86 +8,225 @@ 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 { Options_Cli } from "./common/cli.types";
1211
import {
13-
log,
14-
mergeOptions,
15-
validateOptions,
16-
validatePackages,
17-
} from "./common/cli.utils";
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";
1820

1921
class CompassCli {
2022
private program: Command;
2123
private options: Options_Cli;
2224

2325
constructor(args: string[]) {
24-
this.program = this.createProgram();
26+
this.program = this._createProgram();
2527
this.program.parse(args);
26-
this.options = this.getCliOptions();
28+
this.options = this._getCliOptions();
2729
}
2830

29-
private createProgram(): Command {
31+
public async run() {
32+
const { force, user } = this.options;
33+
const cmd = this.program.args[0];
34+
35+
switch (true) {
36+
case cmd === "build": {
37+
await this._validateBuild();
38+
await runBuild(this.options);
39+
break;
40+
}
41+
case cmd === "delete": {
42+
this._validateDelete();
43+
await startDeleteFlow(user as string, force);
44+
break;
45+
}
46+
default:
47+
this._exitHelpfully("root", `${cmd as string} is not a supported cmd`);
48+
}
49+
}
50+
51+
private _createProgram(): Command {
3052
const program = new Command();
31-
program.option(
32-
`-e, --environment [${CATEGORY_VM.STAG} | ${CATEGORY_VM.PROD}]`,
33-
"specify environment"
34-
);
53+
3554
program.option("-f, --force", "force operation, no cautionary prompts");
36-
program.option(
37-
"--user [id | email]",
38-
"specify which user to run script for"
39-
);
4055

4156
program
4257
.command("build")
4358
.description("build compass package")
4459
.argument(
4560
`[${ALL_PACKAGES.join(" | ")}]`,
46-
"package to build (only provde 1 at a time)"
61+
"package to build (only provide 1)"
4762
)
4863
.option(
4964
"-c, --clientId <clientId>",
5065
"google client id to inject into build"
66+
)
67+
.option(
68+
`-e, --environment [${CATEGORY_VM.STAG} | ${CATEGORY_VM.PROD}]`,
69+
"specify environment"
5170
);
5271

5372
program
5473
.command("delete")
55-
.description("delete user data from compass database");
74+
.description("delete user data from compass database")
75+
.option(
76+
"-u, --user [id | email]",
77+
"specify which user to run script for"
78+
);
5679
return program;
5780
}
5881

59-
private getCliOptions(): Options_Cli {
60-
const options = mergeOptions(this.program);
61-
const validOptions = validateOptions(options);
82+
private _exitHelpfully(cmd: "root" | "build" | "delete", msg?: string) {
83+
msg && log.error(msg);
6284

63-
return validOptions;
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);
6495
}
6596

66-
public async run() {
67-
const { user, force } = this.options;
68-
const cmd = this.program.args[0];
97+
private _getBuildOptions() {
98+
const buildOpts: Options_Cli_Build = {};
6999

70-
switch (true) {
71-
case cmd === "build": {
72-
await runBuild(this.options);
73-
break;
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;
74107
}
75-
case cmd === "delete": {
76-
if (!user || typeof user !== "string") {
77-
this.exitHelpfully("You must supply a user");
78-
}
79-
await startDeleteFlow(user as string, force);
80-
break;
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;
81121
}
82-
default:
83-
this.exitHelpfully("Unsupported cmd");
84122
}
123+
return buildOpts;
85124
}
86125

87-
private exitHelpfully(msg?: string) {
88-
msg && log.error(msg);
89-
console.log(this.program.helpInformation());
90-
process.exit(1);
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;
91230
}
92231
}
93232

packages/scripts/src/commands/build.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,21 @@ import {
99
PCKG,
1010
} from "@scripts/common/cli.constants";
1111
import {
12-
getPckgsTo,
1312
_confirm,
1413
log,
1514
fileExists,
1615
getClientId,
1716
getApiBaseUrl,
1817
getEnvironmentAnswer,
19-
validatePackages,
2018
} from "@scripts/common/cli.utils";
2119

2220
export const runBuild = async (options: Options_Cli) => {
23-
const pckgs = options.packages ? options.packages : await getPckgsTo("build");
24-
validatePackages(pckgs);
21+
const packages = options.packages as string[];
2522

26-
if (pckgs.includes(PCKG.NODE)) {
23+
if (packages.includes(PCKG.NODE)) {
2724
await buildNodePckgs(options);
2825
}
29-
if (pckgs.includes(PCKG.WEB)) {
26+
if (packages.includes(PCKG.WEB)) {
3027
await buildWeb(options);
3128
}
3229
};
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import { z } from "zod";
22

3-
export type Environment_Cli = "staging" | "production";
3+
export const Schema_Options_Cli_Root = z.object({
4+
force: z.boolean().optional(),
5+
});
46

5-
export const Schema_Options_Cli = z.object({
7+
export const Schema_Options_Cli_Build = z.object({
68
clientId: z.string().optional(),
79
environment: z.enum(["staging", "production"]).optional(),
8-
force: z.boolean().optional(),
910
packages: z.array(z.string()).optional(),
11+
});
12+
13+
export const Schema_Options_Cli_Delete = z.object({
1014
user: z.string().optional(),
1115
});
1216

13-
export type Options_Cli = z.infer<typeof Schema_Options_Cli>;
17+
export type Options_Cli_Delete = z.infer<typeof Schema_Options_Cli_Delete>;
18+
export type Options_Cli_Build = z.infer<typeof Schema_Options_Cli_Build>;
19+
export type Options_Cli_Root = z.infer<typeof Schema_Options_Cli_Root>;
20+
export type Options_Cli = Options_Cli_Root &
21+
Options_Cli_Build &
22+
Options_Cli_Delete;
23+
24+
export type Environment_Cli = "staging" | "production";

packages/scripts/src/common/cli.utils.ts

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import pkg from "inquirer";
22
import chalk from "chalk";
33
const { prompt } = pkg;
44
import shell from "shelljs";
5-
import { Command } from "commander";
65

76
import { ALL_PACKAGES, CLI_ENV } from "./cli.constants";
8-
import { Environment_Cli, Options_Cli, Schema_Options_Cli } from "./cli.types";
7+
import { Environment_Cli } from "./cli.types";
98

109
export const fileExists = (file: string) => {
1110
return shell.test("-e", file);
@@ -129,24 +128,6 @@ export const log = {
129128
tip: (msg: string) => console.log(chalk.hex("#f5c150")(msg)),
130129
};
131130

132-
export const mergeOptions = (program: Command): Options_Cli => {
133-
const _options = program.opts();
134-
const packages = program.args[1]?.split(",");
135-
const options: Options_Cli = {
136-
..._options,
137-
force: _options["force"] === true,
138-
packages,
139-
};
140-
141-
const build = program.commands.find((cmd) => cmd.name() === "build");
142-
const clientId = build?.opts()["clientId"] as string;
143-
if (build && clientId) {
144-
options.clientId = clientId;
145-
}
146-
147-
return options;
148-
};
149-
150131
export const _confirm = async (question: string, _default = true) => {
151132
const q = [
152133
{
@@ -163,29 +144,3 @@ export const _confirm = async (question: string, _default = true) => {
163144
process.exit(1);
164145
});
165146
};
166-
167-
export const validateOptions = (options: Options_Cli): Options_Cli => {
168-
const { data, error } = Schema_Options_Cli.safeParse(options);
169-
if (error) {
170-
log.error(`Invalid CLI options: ${JSON.stringify(error.format())}`);
171-
process.exit(1);
172-
}
173-
174-
return data;
175-
};
176-
177-
export const validatePackages = (packages: string[] | undefined) => {
178-
if (!packages) {
179-
log.error("Package must be defined");
180-
process.exit(1);
181-
}
182-
const unsupportedPackages = packages.filter(
183-
(pkg) => !ALL_PACKAGES.includes(pkg)
184-
);
185-
if (unsupportedPackages.length > 0) {
186-
log.error(
187-
`One or more of these packages isn't supported: ${unsupportedPackages.toString()}`
188-
);
189-
process.exit(1);
190-
}
191-
};

0 commit comments

Comments
 (0)