Skip to content
Merged
3 changes: 2 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"saslprep": "^1.0.3",
"socket.io": "^4.7.5",
"supertokens-node": "^20.0.5",
"tslib": "^2.4.0"
"tslib": "^2.4.0",
"zod": "^3.24.1"
},
"devDependencies": {
"@shelf/jest-mongodb": "^4.1.4",
Expand Down
110 changes: 60 additions & 50 deletions packages/scripts/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,76 @@ import { Command } from "commander";
import { runBuild } from "./commands/build";
import { ALL_PACKAGES, CATEGORY_VM } from "./common/cli.constants";
import { startDeleteFlow } from "./commands/delete";
import { log } from "./common/cli.utils";
import { CliValidator } from "./cli.validator";

const runScript = async () => {
const exitHelpfully = (msg?: string) => {
msg && log.error(msg);
console.log(program.helpInformation());
process.exit(1);
};
class CompassCli {
private program: Command;
private validator: CliValidator;

const program = new Command();
program.option(
`-e, --environment [${CATEGORY_VM.STAG}|${CATEGORY_VM.PROD}]`,
"specify environment"
);
program.option("-f, --force", "forces operation, no cautionary prompts");
program.option(
"-u, --user [id|email]",
"specifies which user to run script for"
);

program
.command("build")
.description("build compass package(s)")
.argument(
`[${ALL_PACKAGES.join("|")}]`,
"package(s) to build, separated by comma"
)
.option("--skip-env", "skips copying env files to build");
constructor(args: string[]) {
this.program = this._createProgram();
this.validator = new CliValidator(this.program);
this.program.parse(args);
}

program
.command("delete")
.description("deletes users data from compass database");
public async run() {
const options = this.validator.getCliOptions();
const { force, user } = options;
const cmd = this.program.args[0];

program.parse(process.argv);
switch (true) {
case cmd === "build": {
await this.validator.validateBuild(options);
await runBuild(options);
break;
}
case cmd === "delete": {
this.validator.validateDelete(options);
await startDeleteFlow(user as string, force);
break;
}
default:
this.validator.exitHelpfully(
"root",
`${cmd as string} is not a supported cmd`
);
}
}

const options = program.opts();
const cmd = program.args[0];
private _createProgram(): Command {
const program = new Command();

switch (true) {
case cmd === "build": {
await runBuild(options);
break;
}
case cmd === "delete": {
const force = options["force"] as boolean;
const user = options["user"] as string;
program.option("-f, --force", "force operation, no cautionary prompts");

if (!user || typeof user !== "string") {
exitHelpfully("You must supply a user");
}
program
.command("build")
.description("build compass package")
.argument(
`[${ALL_PACKAGES.join(" | ")}]`,
"package to build (only provide 1)"
)
.option(
"-c, --clientId <clientId>",
"google client id to inject into build"
)
.option(
`-e, --environment [${CATEGORY_VM.STAG} | ${CATEGORY_VM.PROD}]`,
"specify environment"
);

await startDeleteFlow(user, force);
break;
}
default:
exitHelpfully("Unsupported cmd");
program
.command("delete")
.description("delete user data from compass database")
.option(
"-u, --user [id | email]",
"specify which user to run script for"
);
return program;
}
};
}

runScript().catch((err) => {
const cli = new CompassCli(process.argv);
cli.run().catch((err) => {
console.log(err);
process.exit(1);
});
168 changes: 168 additions & 0 deletions packages/scripts/src/cli.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { Command } from "commander";

import { ALL_PACKAGES } from "./common/cli.constants";
import {
Options_Cli,
Options_Cli_Build,
Options_Cli_Delete,
Schema_Options_Cli_Build,
Schema_Options_Cli_Delete,
Schema_Options_Cli_Root,
} from "./common/cli.types";
import { getPckgsTo, log } from "./common/cli.utils";

export class CliValidator {
private program: Command;

constructor(program: Command) {
this.program = program;
}

public exitHelpfully(cmd: "root" | "build" | "delete", msg?: string) {
msg && log.error(msg);

if (cmd === "root") {
console.log(this.program.helpInformation());
} else {
const command = this.program.commands.find(
(c) => c.name() === cmd
) as Command;
console.log(command.helpInformation());
}

process.exit(1);
}

public getCliOptions(): Options_Cli {
const options = this._mergeOptions();
const validOptions = this._validateOptions(options);

return validOptions;
}

public async validateBuild(options: Options_Cli) {
if (!options.packages) {
options.packages = await getPckgsTo("build");
}

const unsupportedPackages = options.packages.filter(
(pkg) => !ALL_PACKAGES.includes(pkg)
);
if (unsupportedPackages.length > 0) {
this.exitHelpfully(
"build",
`One or more of these packages isn't supported: ${unsupportedPackages.toString()}`
);
}
}

public validateDelete(options: Options_Cli) {
const { user } = options;
if (!user || typeof user !== "string") {
this.exitHelpfully("delete", "You must supply a user");
}
}

private _getBuildOptions() {
const buildOpts: Options_Cli_Build = {};

const buildCmd = this.program.commands.find(
(cmd) => cmd.name() === "build"
);
if (buildCmd) {
const packages = this.program.args[1]?.split(",");
if (packages) {
buildOpts.packages = packages;
}

const environment = buildCmd?.opts()[
"environment"
] as Options_Cli_Build["environment"];
if (environment) {
buildOpts.environment = environment;
}

const clientId = buildCmd?.opts()[
"clientId"
] as Options_Cli_Build["clientId"];
if (clientId) {
buildOpts.clientId = clientId;
}
}
return buildOpts;
}

private _getDeleteOptions() {
const deleteOpts: Options_Cli_Delete = {};

const deleteCmd = this.program.commands.find(
(cmd) => cmd.name() === "delete"
);
if (deleteCmd) {
const user = deleteCmd?.opts()["user"] as Options_Cli["user"];
if (user) {
deleteOpts.user = user;
}
}

return deleteOpts;
}

private _mergeOptions = (): Options_Cli => {
const _options = this.program.opts();
let options: Options_Cli = {
..._options,
force: _options["force"] === true,
};

const buildOptions = this._getBuildOptions();
if (Object.keys(buildOptions).length > 0) {
options = {
...options,
...buildOptions,
};
}

const deleteOptions = this._getDeleteOptions();
if (Object.keys(deleteOptions).length > 0) {
options = {
...options,
...deleteOptions,
};
}

return options;
};

private _validateOptions(options: Options_Cli) {
const { data: rootData, error: rootError } =
Schema_Options_Cli_Root.safeParse(options);
if (rootError) {
this.exitHelpfully(
"root",
`Invalid CLI options: ${rootError.toString()}`
);
}

const { data: buildData, error: buildError } =
Schema_Options_Cli_Build.safeParse(options);
if (buildError) {
this.exitHelpfully(
"build",
`Invalid build options: ${buildError.toString()}`
);
}

const { data: deleteData, error: deleteError } =
Schema_Options_Cli_Delete.safeParse(options);
if (deleteError) {
this.exitHelpfully(
"delete",
`Invalid delete options: ${deleteError.toString()}`
);
}

const data: Options_Cli = { ...rootData, ...buildData, ...deleteData };
return data;
}
}
Loading
Loading