From 00b71bdfe811cd8b031052ee1509f5c1c1b6d40f Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Thu, 19 Dec 2024 19:46:05 -0600 Subject: [PATCH 01/10] refactor: extract cli args before running cmd --- packages/scripts/src/cli.ts | 53 ++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/packages/scripts/src/cli.ts b/packages/scripts/src/cli.ts index c6a77cf36..e7cffd0de 100644 --- a/packages/scripts/src/cli.ts +++ b/packages/scripts/src/cli.ts @@ -9,22 +9,17 @@ 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 { Options_Cli } from "./common/cli.types"; -const runScript = async () => { - const exitHelpfully = (msg?: string) => { - msg && log.error(msg); - console.log(program.helpInformation()); - process.exit(1); - }; - +const createProgram = () => { const program = new Command(); program.option( - `-e, --environment [${CATEGORY_VM.STAG}|${CATEGORY_VM.PROD}]`, + `-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]", + "-u, --user [id | email]", "specifies which user to run script for" ); @@ -32,7 +27,7 @@ const runScript = async () => { .command("build") .description("build compass package(s)") .argument( - `[${ALL_PACKAGES.join("|")}]`, + `[${ALL_PACKAGES.join(" | ")}]`, "package(s) to build, separated by comma" ) .option("--skip-env", "skips copying env files to build"); @@ -40,30 +35,52 @@ const runScript = async () => { program .command("delete") .description("deletes users data from compass database"); + return program; +}; + +const exitHelpfully = (program: Command, msg?: string) => { + msg && log.error(msg); + console.log(program.helpInformation()); + process.exit(1); +}; + +const getCliOptions = (program: Command): Options_Cli => { + const _options = program.opts(); + const packages = program.args[1]?.split(","); + const options = { + ..._options, + packages, + force: _options["force"] === true, + user: _options["user"] as string, + }; + + return options; +}; + +const runScript = async () => { + const program = createProgram(); program.parse(process.argv); - const options = program.opts(); - const cmd = program.args[0]; + const options = getCliOptions(program); + const { user, force } = options; + const cmd = program.args[0]; switch (true) { case cmd === "build": { await runBuild(options); break; } case cmd === "delete": { - const force = options["force"] as boolean; - const user = options["user"] as string; - if (!user || typeof user !== "string") { - exitHelpfully("You must supply a user"); + exitHelpfully(program, "You must supply a user"); } - await startDeleteFlow(user, force); + await startDeleteFlow(user as string, force); break; } default: - exitHelpfully("Unsupported cmd"); + exitHelpfully(program, "Unsupported cmd"); } }; From 7d8d1b7fee8f0a5554b2c08c42a6fabed504d6ce Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Fri, 20 Dec 2024 06:27:07 -0600 Subject: [PATCH 02/10] feat: validate pckgs before building --- packages/scripts/src/cli.ts | 18 +++++++++++++++++- packages/scripts/src/commands/build.ts | 17 +++++++---------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/scripts/src/cli.ts b/packages/scripts/src/cli.ts index e7cffd0de..56a681394 100644 --- a/packages/scripts/src/cli.ts +++ b/packages/scripts/src/cli.ts @@ -6,7 +6,7 @@ dotenv.config({ import { Command } from "commander"; import { runBuild } from "./commands/build"; -import { ALL_PACKAGES, CATEGORY_VM } from "./common/cli.constants"; +import { ALL_PACKAGES, CATEGORY_VM, PCKG } from "./common/cli.constants"; import { startDeleteFlow } from "./commands/delete"; import { log } from "./common/cli.utils"; import { Options_Cli } from "./common/cli.types"; @@ -58,6 +58,21 @@ const getCliOptions = (program: Command): Options_Cli => { return options; }; +const validatePackages = (packages: string[] | undefined) => { + if (!packages) { + log.error("Packages must be defined"); + } + if (!packages?.includes(PCKG.NODE) && !packages?.includes(PCKG.WEB)) { + log.error( + `One or more of these pckgs isn't supported: ${( + packages as string[] + )?.toString()}` + ); + + process.exit(1); + } +}; + const runScript = async () => { const program = createProgram(); program.parse(process.argv); @@ -68,6 +83,7 @@ const runScript = async () => { const cmd = program.args[0]; switch (true) { case cmd === "build": { + validatePackages(options.packages); await runBuild(options); break; } diff --git a/packages/scripts/src/commands/build.ts b/packages/scripts/src/commands/build.ts index adb014a84..e3850ca17 100644 --- a/packages/scripts/src/commands/build.ts +++ b/packages/scripts/src/commands/build.ts @@ -18,24 +18,21 @@ import { } from "@scripts/common/cli.utils"; export const runBuild = async (options: Options_Cli) => { - const env = options["environment"]; - const vmInfo = await getVmInfo(env); + const vmInfo = await getVmInfo(options.environment); const pckgs = - options["packages"] === undefined + options?.packages?.length === 0 ? await getPckgsTo("build") - : options["packages"]; + : (options.packages as string[]); if (pckgs.includes(PCKG.NODE)) { - await buildNodePckgs(vmInfo, options["skipEnv"]); + await buildNodePckgs(vmInfo, options.skipEnv); } - if (pckgs.includes(PCKG.WEB)) { await buildWeb(vmInfo); } }; -// eslint-disable-next-line @typescript-eslint/require-await const buildNodePckgs = async (vmInfo: Info_VM, skipEnv?: boolean) => { removeOldBuildFor(PCKG.NODE); createNodeDirs(); @@ -78,10 +75,10 @@ const buildWeb = async (vmInfo: Info_VM) => { log.success(`Done building web files.`); log.tip(` Now you'll probably want to: - - zip the build/web dir + - zip the build dir - copy it to your ${destination} server - - unzip it - - run it`); + - unzip it and serve as the static assets + `); process.exit(0); }; From 6c320c1d042f8b8681fcdc2b70ee307c29b77a1f Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Sat, 21 Dec 2024 07:23:40 -0600 Subject: [PATCH 03/10] chore: update delete cmd description --- packages/scripts/src/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/src/cli.ts b/packages/scripts/src/cli.ts index 56a681394..33275df20 100644 --- a/packages/scripts/src/cli.ts +++ b/packages/scripts/src/cli.ts @@ -34,7 +34,7 @@ const createProgram = () => { program .command("delete") - .description("deletes users data from compass database"); + .description("delete user data from compass database"); return program; }; From 7e4e01b8f567bf4fa87b23f1cf8b0a13fdf54aff Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Sat, 21 Dec 2024 07:24:07 -0600 Subject: [PATCH 04/10] feat: support force option when building node pckgs --- packages/scripts/src/commands/build.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/scripts/src/commands/build.ts b/packages/scripts/src/commands/build.ts index e3850ca17..889f9223e 100644 --- a/packages/scripts/src/commands/build.ts +++ b/packages/scripts/src/commands/build.ts @@ -26,17 +26,17 @@ export const runBuild = async (options: Options_Cli) => { : (options.packages as string[]); if (pckgs.includes(PCKG.NODE)) { - await buildNodePckgs(vmInfo, options.skipEnv); + await buildNodePckgs(vmInfo, options); } if (pckgs.includes(PCKG.WEB)) { await buildWeb(vmInfo); } }; -const buildNodePckgs = async (vmInfo: Info_VM, skipEnv?: boolean) => { +const buildNodePckgs = async (vmInfo: Info_VM, options: Options_Cli) => { removeOldBuildFor(PCKG.NODE); createNodeDirs(); - await copyNodeConfigsToBuild(vmInfo, skipEnv); + await copyNodeConfigsToBuild(vmInfo, options.skipEnv, options.force); log.info("Compiling node packages ..."); shell.exec( @@ -82,7 +82,11 @@ const buildWeb = async (vmInfo: Info_VM) => { process.exit(0); }; -const copyNodeConfigsToBuild = async (vmInfo: Info_VM, skipEnv?: boolean) => { +const copyNodeConfigsToBuild = async ( + vmInfo: Info_VM, + skipEnv?: boolean, + force?: boolean +) => { const envName = vmInfo.destination === "production" ? ".prod.env" : ".env"; const envPath = `${COMPASS_ROOT_DEV}/packages/backend/${envName}`; @@ -96,7 +100,9 @@ const copyNodeConfigsToBuild = async (vmInfo: Info_VM, skipEnv?: boolean) => { log.warning(`Env file does not exist: ${envPath}`); const keepGoing = - skipEnv === true ? true : await _confirm("Continue anyway?"); + skipEnv === true || force === true + ? true + : await _confirm("Continue anyway?"); if (!keepGoing) { log.error("Exiting due to missing env file"); From 52689a1d3cb6acfb6ab6f2b94dc7756bc4dc3007 Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Sun, 29 Dec 2024 07:51:05 -0600 Subject: [PATCH 05/10] refactor(cli): convert CLI to class and use zod for validation --- packages/scripts/src/cli.ts | 164 ++++++++++++----------- packages/scripts/src/commands/build.ts | 44 +++--- packages/scripts/src/common/cli.types.ts | 25 ++-- packages/scripts/src/common/cli.utils.ts | 38 +++--- 4 files changed, 141 insertions(+), 130 deletions(-) diff --git a/packages/scripts/src/cli.ts b/packages/scripts/src/cli.ts index 33275df20..1ced114cd 100644 --- a/packages/scripts/src/cli.ts +++ b/packages/scripts/src/cli.ts @@ -6,101 +6,113 @@ dotenv.config({ import { Command } from "commander"; import { runBuild } from "./commands/build"; -import { ALL_PACKAGES, CATEGORY_VM, PCKG } from "./common/cli.constants"; +import { ALL_PACKAGES, CATEGORY_VM } from "./common/cli.constants"; import { startDeleteFlow } from "./commands/delete"; +import { Options_Cli, Schema_Options_Cli } from "./common/cli.types"; import { log } from "./common/cli.utils"; -import { Options_Cli } from "./common/cli.types"; -const createProgram = () => { - 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" - ); +class CompassCli { + private program: Command; + private options: Options_Cli; - 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.program.parse(args); + this.options = this.getCliOptions(); + } - program - .command("delete") - .description("delete user data from compass database"); - return program; -}; + private createProgram(): Command { + const program = new Command(); + program.option( + `-e, --environment [${CATEGORY_VM.STAG} | ${CATEGORY_VM.PROD}]`, + "specify environment" + ); + program.option("-f, --force", "force operation, no cautionary prompts"); + program.option( + "-u, --user [id | email]", + "specify which user to run script for" + ); -const exitHelpfully = (program: Command, msg?: string) => { - msg && log.error(msg); - console.log(program.helpInformation()); - process.exit(1); -}; + program + .command("build") + .description("build compass package(s)") + .argument( + `[${ALL_PACKAGES.join(" | ")}]`, + "package(s) to build, separated by comma" + ) + .option("--skip-env", "skip copying env files to build"); -const getCliOptions = (program: Command): Options_Cli => { - const _options = program.opts(); - const packages = program.args[1]?.split(","); + program + .command("delete") + .description("delete user data from compass database"); + return program; + } - const options = { - ..._options, - packages, - force: _options["force"] === true, - user: _options["user"] as string, - }; + private getCliOptions(): Options_Cli { + const _options = this.program.opts(); + const packages = this.program.args[1]?.split(","); + const options: Options_Cli = { + ..._options, + force: _options["force"] === true, + packages, + }; - return options; -}; + const { data, error } = Schema_Options_Cli.safeParse(options); + if (error) { + log.error(`Invalid CLI options: ${JSON.stringify(error.format())}`); + process.exit(1); + } -const validatePackages = (packages: string[] | undefined) => { - if (!packages) { - log.error("Packages must be defined"); + return data; } - if (!packages?.includes(PCKG.NODE) && !packages?.includes(PCKG.WEB)) { - log.error( - `One or more of these pckgs isn't supported: ${( - packages as string[] - )?.toString()}` - ); - process.exit(1); + private validatePackages(packages: string[] | undefined) { + if (!packages) { + log.error("Packages must be defined"); + process.exit(1); + } + const unsupportedPackages = packages.filter( + (pkg) => !ALL_PACKAGES.includes(pkg) + ); + if (unsupportedPackages.length > 0) { + log.error( + `One or more of these packages isn't supported: ${unsupportedPackages.toString()}` + ); + process.exit(1); + } } -}; - -const runScript = async () => { - const program = createProgram(); - program.parse(process.argv); - const options = getCliOptions(program); - const { user, force } = options; + public async run() { + const { user, force, packages } = this.options; + const cmd = this.program.args[0]; - const cmd = program.args[0]; - switch (true) { - case cmd === "build": { - validatePackages(options.packages); - await runBuild(options); - break; - } - case cmd === "delete": { - if (!user || typeof user !== "string") { - exitHelpfully(program, "You must supply a user"); + switch (true) { + case cmd === "build": { + this.validatePackages(packages); + await runBuild(this.options); + break; } - - await startDeleteFlow(user as string, force); - break; + case cmd === "delete": { + if (!user || typeof user !== "string") { + this.exitHelpfully("You must supply a user"); + } + await startDeleteFlow(user as string, force); + break; + } + default: + this.exitHelpfully("Unsupported cmd"); } - default: - exitHelpfully(program, "Unsupported cmd"); } -}; -runScript().catch((err) => { + private exitHelpfully(msg?: string) { + msg && log.error(msg); + console.log(this.program.helpInformation()); + process.exit(1); + } +} + +const cli = new CompassCli(process.argv); +cli.run().catch((err) => { console.log(err); process.exit(1); }); diff --git a/packages/scripts/src/commands/build.ts b/packages/scripts/src/commands/build.ts index 889f9223e..3c4412941 100644 --- a/packages/scripts/src/commands/build.ts +++ b/packages/scripts/src/commands/build.ts @@ -1,7 +1,7 @@ import dotenv from "dotenv"; import path from "path"; import shell from "shelljs"; -import { Options_Cli, Info_VM } from "@scripts/common/cli.types"; +import { Options_Cli } from "@scripts/common/cli.types"; import { COMPASS_BUILD_DEV, COMPASS_ROOT_DEV, @@ -9,34 +9,33 @@ import { PCKG, } from "@scripts/common/cli.constants"; import { - getVmInfo, getPckgsTo, _confirm, log, fileExists, getClientId, + getApiBaseUrl, + getEnvironmentAnswer, } from "@scripts/common/cli.utils"; export const runBuild = async (options: Options_Cli) => { - const vmInfo = await getVmInfo(options.environment); - const pckgs = options?.packages?.length === 0 ? await getPckgsTo("build") : (options.packages as string[]); if (pckgs.includes(PCKG.NODE)) { - await buildNodePckgs(vmInfo, options); + await buildNodePckgs(options); } if (pckgs.includes(PCKG.WEB)) { - await buildWeb(vmInfo); + await buildWeb(options); } }; -const buildNodePckgs = async (vmInfo: Info_VM, options: Options_Cli) => { +const buildNodePckgs = async (options: Options_Cli) => { removeOldBuildFor(PCKG.NODE); createNodeDirs(); - await copyNodeConfigsToBuild(vmInfo, options.skipEnv, options.force); + await copyNodeConfigsToBuild(options); log.info("Compiling node packages ..."); shell.exec( @@ -55,11 +54,15 @@ const buildNodePckgs = async (vmInfo: Info_VM, options: Options_Cli) => { ); }; -const buildWeb = async (vmInfo: Info_VM) => { - const { baseUrl, destination } = vmInfo; - const envFile = destination === "staging" ? ".env" : ".env.prod"; +const buildWeb = async (options: Options_Cli) => { + const environment = + options.environment !== undefined + ? options.environment + : await getEnvironmentAnswer(); - const gClientId = await getClientId(destination); + const envFile = environment === "staging" ? ".env" : ".env.prod"; + const baseUrl = await getApiBaseUrl(environment); + const gClientId = await getClientId(environment); const envPath = path.join(__dirname, "..", "..", "..", "backend", envFile); dotenv.config({ path: envPath }); @@ -76,18 +79,15 @@ const buildWeb = async (vmInfo: Info_VM) => { log.tip(` Now you'll probably want to: - zip the build dir - - copy it to your ${destination} server - - unzip it and serve as the static assets + - copy it to your ${environment} environment + - unzip it to expose the static assets + - serve assets `); process.exit(0); }; -const copyNodeConfigsToBuild = async ( - vmInfo: Info_VM, - skipEnv?: boolean, - force?: boolean -) => { - const envName = vmInfo.destination === "production" ? ".prod.env" : ".env"; +const copyNodeConfigsToBuild = async (options: Options_Cli) => { + const envName = options.environment === "production" ? ".prod.env" : ".env"; const envPath = `${COMPASS_ROOT_DEV}/packages/backend/${envName}`; @@ -100,9 +100,7 @@ const copyNodeConfigsToBuild = async ( log.warning(`Env file does not exist: ${envPath}`); const keepGoing = - skipEnv === true || force === true - ? true - : await _confirm("Continue anyway?"); + options.force === true ? true : await _confirm("Continue anyway?"); if (!keepGoing) { log.error("Exiting due to missing env file"); diff --git a/packages/scripts/src/common/cli.types.ts b/packages/scripts/src/common/cli.types.ts index c08c9b89c..cc9465fad 100644 --- a/packages/scripts/src/common/cli.types.ts +++ b/packages/scripts/src/common/cli.types.ts @@ -1,16 +1,13 @@ -export type Category_VM = "staging" | "production"; +import { z } from "zod"; -export interface Info_VM { - baseUrl: string; - destination: Category_VM; -} +export type Environment_Cli = "staging" | "production"; -export interface Options_Cli { - build?: boolean; - delete?: boolean; - environment?: Category_VM; - force?: boolean; - packages?: string[]; - skipEnv?: boolean; - user?: string; -} +export const Schema_Options_Cli = z.object({ + clientId: z.string().optional(), + environment: z.enum(["staging", "production"]).optional(), + force: z.boolean().optional(), + packages: z.array(z.string()).optional(), + user: z.string().optional(), +}); + +export type Options_Cli = z.infer; diff --git a/packages/scripts/src/common/cli.utils.ts b/packages/scripts/src/common/cli.utils.ts index 98ac9443a..18ed41d4f 100644 --- a/packages/scripts/src/common/cli.utils.ts +++ b/packages/scripts/src/common/cli.utils.ts @@ -4,18 +4,27 @@ const { prompt } = pkg; import shell from "shelljs"; import { ALL_PACKAGES, CLI_ENV } from "./cli.constants"; -import { Category_VM } from "./cli.types"; +import { Environment_Cli } from "./cli.types"; export const fileExists = (file: string) => { return shell.test("-e", file); }; -export const getClientId = async (destination: Category_VM) => { - if (destination === "staging") { +export const getApiBaseUrl = async (environment: Environment_Cli) => { + const category = environment ? environment : await getEnvironmentAnswer(); + const isStaging = category === "staging"; + const domain = await getDomainAnswer(isStaging); + const baseUrl = `https://${domain}/api`; + + return baseUrl; +}; + +export const getClientId = async (environment: Environment_Cli) => { + if (environment === "staging") { return process.env["CLIENT_ID"] as string; } - if (destination === "production") { + if (environment === "production") { const q = `Enter the googleClientId for the production environment:`; return prompt([{ type: "input", name: "answer", message: q }]) @@ -58,22 +67,17 @@ const getDomainAnswer = async (isStaging: boolean) => { process.exit(1); }); }; -export const getVmInfo = async (environment?: Category_VM) => { - const destination = environment - ? environment - : ((await getListAnswer("Select environment to use:", [ - "staging", - "production", - ])) as Category_VM); - - const isStaging = destination === "staging"; - const domain = await getDomainAnswer(isStaging); - const baseUrl = `https://${domain}/api`; - return { baseUrl, destination }; +export const getEnvironmentAnswer = async (): Promise => { + const environment = (await getListAnswer("Select environment to use:", [ + "staging", + "production", + ])) as Environment_Cli; + + return environment; }; -const getListAnswer = async (question: string, choices: string[]) => { +export const getListAnswer = async (question: string, choices: string[]) => { const q = [ { type: "list", From 0eaa11245dc3aea850633a07dfcbcdd2a14b6f9c Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Mon, 30 Dec 2024 07:17:07 -0600 Subject: [PATCH 06/10] feat(cli): add clientId as build option --- packages/scripts/src/cli.ts | 55 ++++++++---------------- packages/scripts/src/commands/build.ts | 4 +- packages/scripts/src/common/cli.utils.ts | 47 +++++++++++++++++++- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/packages/scripts/src/cli.ts b/packages/scripts/src/cli.ts index 1ced114cd..484902938 100644 --- a/packages/scripts/src/cli.ts +++ b/packages/scripts/src/cli.ts @@ -8,8 +8,13 @@ import { Command } from "commander"; import { runBuild } from "./commands/build"; import { ALL_PACKAGES, CATEGORY_VM } from "./common/cli.constants"; import { startDeleteFlow } from "./commands/delete"; -import { Options_Cli, Schema_Options_Cli } from "./common/cli.types"; -import { log } from "./common/cli.utils"; +import { Options_Cli } from "./common/cli.types"; +import { + log, + mergeOptions, + validateOptions, + validatePackages, +} from "./common/cli.utils"; class CompassCli { private program: Command; @@ -29,18 +34,21 @@ class CompassCli { ); program.option("-f, --force", "force operation, no cautionary prompts"); program.option( - "-u, --user [id | email]", + "--user [id | email]", "specify which user to run script for" ); program .command("build") - .description("build compass package(s)") + .description("build compass package") .argument( `[${ALL_PACKAGES.join(" | ")}]`, - "package(s) to build, separated by comma" + "package to build (only provde 1 at a time)" ) - .option("--skip-env", "skip copying env files to build"); + .option( + "-c, --clientId ", + "google client id to inject into build" + ); program .command("delete") @@ -49,37 +57,10 @@ class CompassCli { } private getCliOptions(): Options_Cli { - const _options = this.program.opts(); - const packages = this.program.args[1]?.split(","); - const options: Options_Cli = { - ..._options, - force: _options["force"] === true, - packages, - }; - - const { data, error } = Schema_Options_Cli.safeParse(options); - if (error) { - log.error(`Invalid CLI options: ${JSON.stringify(error.format())}`); - process.exit(1); - } - - return data; - } + const options = mergeOptions(this.program); + const validOptions = validateOptions(options); - private validatePackages(packages: string[] | undefined) { - if (!packages) { - log.error("Packages must be defined"); - process.exit(1); - } - const unsupportedPackages = packages.filter( - (pkg) => !ALL_PACKAGES.includes(pkg) - ); - if (unsupportedPackages.length > 0) { - log.error( - `One or more of these packages isn't supported: ${unsupportedPackages.toString()}` - ); - process.exit(1); - } + return validOptions; } public async run() { @@ -88,7 +69,7 @@ class CompassCli { switch (true) { case cmd === "build": { - this.validatePackages(packages); + validatePackages(packages); await runBuild(this.options); break; } diff --git a/packages/scripts/src/commands/build.ts b/packages/scripts/src/commands/build.ts index 3c4412941..fb2fdf000 100644 --- a/packages/scripts/src/commands/build.ts +++ b/packages/scripts/src/commands/build.ts @@ -62,7 +62,9 @@ const buildWeb = async (options: Options_Cli) => { const envFile = environment === "staging" ? ".env" : ".env.prod"; const baseUrl = await getApiBaseUrl(environment); - const gClientId = await getClientId(environment); + const gClientId = options.clientId + ? options.clientId + : await getClientId(environment); const envPath = path.join(__dirname, "..", "..", "..", "backend", envFile); dotenv.config({ path: envPath }); diff --git a/packages/scripts/src/common/cli.utils.ts b/packages/scripts/src/common/cli.utils.ts index 18ed41d4f..bf9e8acab 100644 --- a/packages/scripts/src/common/cli.utils.ts +++ b/packages/scripts/src/common/cli.utils.ts @@ -2,9 +2,10 @@ import pkg from "inquirer"; import chalk from "chalk"; const { prompt } = pkg; import shell from "shelljs"; +import { Command } from "commander"; import { ALL_PACKAGES, CLI_ENV } from "./cli.constants"; -import { Environment_Cli } from "./cli.types"; +import { Environment_Cli, Options_Cli, Schema_Options_Cli } from "./cli.types"; export const fileExists = (file: string) => { return shell.test("-e", file); @@ -128,6 +129,24 @@ export const log = { tip: (msg: string) => console.log(chalk.hex("#f5c150")(msg)), }; +export const mergeOptions = (program: Command): Options_Cli => { + const _options = program.opts(); + const packages = program.args[1]?.split(","); + const options: Options_Cli = { + ..._options, + force: _options["force"] === true, + packages, + }; + + const build = program.commands.find((cmd) => cmd.name() === "build"); + const clientId = build?.opts()["clientId"] as string; + if (build && clientId) { + options.clientId = clientId; + } + + return options; +}; + export const _confirm = async (question: string, _default = true) => { const q = [ { @@ -144,3 +163,29 @@ export const _confirm = async (question: string, _default = true) => { process.exit(1); }); }; + +export const validateOptions = (options: Options_Cli): Options_Cli => { + const { data, error } = Schema_Options_Cli.safeParse(options); + if (error) { + log.error(`Invalid CLI options: ${JSON.stringify(error.format())}`); + process.exit(1); + } + + return data; +}; + +export const validatePackages = (packages: string[] | undefined) => { + if (!packages) { + log.error("Packages must be defined"); + process.exit(1); + } + const unsupportedPackages = packages.filter( + (pkg) => !ALL_PACKAGES.includes(pkg) + ); + if (unsupportedPackages.length > 0) { + log.error( + `One or more of these packages isn't supported: ${unsupportedPackages.toString()}` + ); + process.exit(1); + } +}; From c4d387f3b8a4d7d16b2034987453bd566df843da Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Mon, 30 Dec 2024 07:50:34 -0600 Subject: [PATCH 07/10] fix(cli): validate packages after they're provided --- packages/scripts/src/cli.ts | 3 +-- packages/scripts/src/commands/build.ts | 7 +++---- packages/scripts/src/common/cli.utils.ts | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/scripts/src/cli.ts b/packages/scripts/src/cli.ts index 484902938..c342ba881 100644 --- a/packages/scripts/src/cli.ts +++ b/packages/scripts/src/cli.ts @@ -64,12 +64,11 @@ class CompassCli { } public async run() { - const { user, force, packages } = this.options; + const { user, force } = this.options; const cmd = this.program.args[0]; switch (true) { case cmd === "build": { - validatePackages(packages); await runBuild(this.options); break; } diff --git a/packages/scripts/src/commands/build.ts b/packages/scripts/src/commands/build.ts index fb2fdf000..d3a3aaca4 100644 --- a/packages/scripts/src/commands/build.ts +++ b/packages/scripts/src/commands/build.ts @@ -16,13 +16,12 @@ import { getClientId, getApiBaseUrl, getEnvironmentAnswer, + validatePackages, } from "@scripts/common/cli.utils"; export const runBuild = async (options: Options_Cli) => { - const pckgs = - options?.packages?.length === 0 - ? await getPckgsTo("build") - : (options.packages as string[]); + const pckgs = options.packages ? options.packages : await getPckgsTo("build"); + validatePackages(pckgs); if (pckgs.includes(PCKG.NODE)) { await buildNodePckgs(options); diff --git a/packages/scripts/src/common/cli.utils.ts b/packages/scripts/src/common/cli.utils.ts index bf9e8acab..297690db8 100644 --- a/packages/scripts/src/common/cli.utils.ts +++ b/packages/scripts/src/common/cli.utils.ts @@ -176,7 +176,7 @@ export const validateOptions = (options: Options_Cli): Options_Cli => { export const validatePackages = (packages: string[] | undefined) => { if (!packages) { - log.error("Packages must be defined"); + log.error("Package must be defined"); process.exit(1); } const unsupportedPackages = packages.filter( From 90d872902c1332fab98d3285d33d891d576e3478 Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Fri, 3 Jan 2025 08:06:16 -0600 Subject: [PATCH 08/10] 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 --- packages/scripts/src/cli.ts | 223 ++++++++++++++++++----- packages/scripts/src/commands/build.ts | 9 +- packages/scripts/src/common/cli.types.ts | 19 +- packages/scripts/src/common/cli.utils.ts | 47 +---- 4 files changed, 200 insertions(+), 98 deletions(-) diff --git a/packages/scripts/src/cli.ts b/packages/scripts/src/cli.ts index c342ba881..930495bb3 100644 --- a/packages/scripts/src/cli.ts +++ b/packages/scripts/src/cli.ts @@ -8,86 +8,225 @@ import { Command } from "commander"; import { runBuild } from "./commands/build"; import { ALL_PACKAGES, CATEGORY_VM } from "./common/cli.constants"; import { startDeleteFlow } from "./commands/delete"; -import { Options_Cli } from "./common/cli.types"; import { - log, - mergeOptions, - validateOptions, - validatePackages, -} from "./common/cli.utils"; + 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"; class CompassCli { private program: Command; private options: Options_Cli; constructor(args: string[]) { - this.program = this.createProgram(); + this.program = this._createProgram(); this.program.parse(args); - this.options = this.getCliOptions(); + this.options = this._getCliOptions(); } - private createProgram(): Command { + public async run() { + const { force, user } = this.options; + const cmd = this.program.args[0]; + + switch (true) { + case cmd === "build": { + await this._validateBuild(); + await runBuild(this.options); + break; + } + case cmd === "delete": { + this._validateDelete(); + await startDeleteFlow(user as string, force); + break; + } + default: + this._exitHelpfully("root", `${cmd as string} is not a supported cmd`); + } + } + + private _createProgram(): Command { const program = new Command(); - program.option( - `-e, --environment [${CATEGORY_VM.STAG} | ${CATEGORY_VM.PROD}]`, - "specify environment" - ); + program.option("-f, --force", "force operation, no cautionary prompts"); - program.option( - "--user [id | email]", - "specify which user to run script for" - ); program .command("build") .description("build compass package") .argument( `[${ALL_PACKAGES.join(" | ")}]`, - "package to build (only provde 1 at a time)" + "package to build (only provide 1)" ) .option( "-c, --clientId ", "google client id to inject into build" + ) + .option( + `-e, --environment [${CATEGORY_VM.STAG} | ${CATEGORY_VM.PROD}]`, + "specify environment" ); program .command("delete") - .description("delete user data from compass database"); + .description("delete user data from compass database") + .option( + "-u, --user [id | email]", + "specify which user to run script for" + ); return program; } - private getCliOptions(): Options_Cli { - const options = mergeOptions(this.program); - const validOptions = validateOptions(options); + private _exitHelpfully(cmd: "root" | "build" | "delete", msg?: string) { + msg && log.error(msg); - return validOptions; + 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 async run() { - const { user, force } = this.options; - const cmd = this.program.args[0]; + private _getBuildOptions() { + const buildOpts: Options_Cli_Build = {}; - switch (true) { - case cmd === "build": { - await runBuild(this.options); - break; + const buildCmd = this.program.commands.find( + (cmd) => cmd.name() === "build" + ); + if (buildCmd) { + const packages = this.program.args[1]?.split(","); + if (packages) { + buildOpts.packages = packages; } - case cmd === "delete": { - if (!user || typeof user !== "string") { - this.exitHelpfully("You must supply a user"); - } - await startDeleteFlow(user as string, force); - break; + + 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; } - default: - this.exitHelpfully("Unsupported cmd"); } + return buildOpts; } - private exitHelpfully(msg?: string) { - msg && log.error(msg); - console.log(this.program.helpInformation()); - process.exit(1); + private _getCliOptions(): Options_Cli { + const options = this._mergeOptions(); + const validOptions = this._validateOptions(options); + + console.log("options", options); + console.log("validOptions:", validOptions); + return validOptions; + } + + 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 async _validateBuild() { + if (!this.options.packages) { + this.options.packages = await getPckgsTo("build"); + } + + const unsupportedPackages = this.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()}` + ); + } + } + + private _validateDelete() { + const { user } = this.options; + if (!user || typeof user !== "string") { + this._exitHelpfully("delete", "You must supply a user"); + } + } + + 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; } } diff --git a/packages/scripts/src/commands/build.ts b/packages/scripts/src/commands/build.ts index d3a3aaca4..0693a4203 100644 --- a/packages/scripts/src/commands/build.ts +++ b/packages/scripts/src/commands/build.ts @@ -9,24 +9,21 @@ import { PCKG, } from "@scripts/common/cli.constants"; import { - getPckgsTo, _confirm, log, fileExists, getClientId, getApiBaseUrl, getEnvironmentAnswer, - validatePackages, } from "@scripts/common/cli.utils"; export const runBuild = async (options: Options_Cli) => { - const pckgs = options.packages ? options.packages : await getPckgsTo("build"); - validatePackages(pckgs); + const packages = options.packages as string[]; - if (pckgs.includes(PCKG.NODE)) { + if (packages.includes(PCKG.NODE)) { await buildNodePckgs(options); } - if (pckgs.includes(PCKG.WEB)) { + if (packages.includes(PCKG.WEB)) { await buildWeb(options); } }; diff --git a/packages/scripts/src/common/cli.types.ts b/packages/scripts/src/common/cli.types.ts index cc9465fad..dee887a4c 100644 --- a/packages/scripts/src/common/cli.types.ts +++ b/packages/scripts/src/common/cli.types.ts @@ -1,13 +1,24 @@ import { z } from "zod"; -export type Environment_Cli = "staging" | "production"; +export const Schema_Options_Cli_Root = z.object({ + force: z.boolean().optional(), +}); -export const Schema_Options_Cli = z.object({ +export const Schema_Options_Cli_Build = z.object({ clientId: z.string().optional(), environment: z.enum(["staging", "production"]).optional(), - force: z.boolean().optional(), packages: z.array(z.string()).optional(), +}); + +export const Schema_Options_Cli_Delete = z.object({ user: z.string().optional(), }); -export type Options_Cli = z.infer; +export type Options_Cli_Delete = z.infer; +export type Options_Cli_Build = z.infer; +export type Options_Cli_Root = z.infer; +export type Options_Cli = Options_Cli_Root & + Options_Cli_Build & + Options_Cli_Delete; + +export type Environment_Cli = "staging" | "production"; diff --git a/packages/scripts/src/common/cli.utils.ts b/packages/scripts/src/common/cli.utils.ts index 297690db8..18ed41d4f 100644 --- a/packages/scripts/src/common/cli.utils.ts +++ b/packages/scripts/src/common/cli.utils.ts @@ -2,10 +2,9 @@ import pkg from "inquirer"; import chalk from "chalk"; const { prompt } = pkg; import shell from "shelljs"; -import { Command } from "commander"; import { ALL_PACKAGES, CLI_ENV } from "./cli.constants"; -import { Environment_Cli, Options_Cli, Schema_Options_Cli } from "./cli.types"; +import { Environment_Cli } from "./cli.types"; export const fileExists = (file: string) => { return shell.test("-e", file); @@ -129,24 +128,6 @@ export const log = { tip: (msg: string) => console.log(chalk.hex("#f5c150")(msg)), }; -export const mergeOptions = (program: Command): Options_Cli => { - const _options = program.opts(); - const packages = program.args[1]?.split(","); - const options: Options_Cli = { - ..._options, - force: _options["force"] === true, - packages, - }; - - const build = program.commands.find((cmd) => cmd.name() === "build"); - const clientId = build?.opts()["clientId"] as string; - if (build && clientId) { - options.clientId = clientId; - } - - return options; -}; - export const _confirm = async (question: string, _default = true) => { const q = [ { @@ -163,29 +144,3 @@ export const _confirm = async (question: string, _default = true) => { process.exit(1); }); }; - -export const validateOptions = (options: Options_Cli): Options_Cli => { - const { data, error } = Schema_Options_Cli.safeParse(options); - if (error) { - log.error(`Invalid CLI options: ${JSON.stringify(error.format())}`); - process.exit(1); - } - - return data; -}; - -export const validatePackages = (packages: string[] | undefined) => { - if (!packages) { - log.error("Package must be defined"); - process.exit(1); - } - const unsupportedPackages = packages.filter( - (pkg) => !ALL_PACKAGES.includes(pkg) - ); - if (unsupportedPackages.length > 0) { - log.error( - `One or more of these packages isn't supported: ${unsupportedPackages.toString()}` - ); - process.exit(1); - } -}; From df4731b8b7c37699d6798b399cc14562084b1a94 Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Sun, 5 Jan 2025 08:24:36 -0600 Subject: [PATCH 09/10] 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 --- packages/scripts/src/cli.ts | 178 ++------------------------ packages/scripts/src/cli.validator.ts | 168 ++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 166 deletions(-) create mode 100644 packages/scripts/src/cli.validator.ts diff --git a/packages/scripts/src/cli.ts b/packages/scripts/src/cli.ts index 930495bb3..969a9b4c2 100644 --- a/packages/scripts/src/cli.ts +++ b/packages/scripts/src/cli.ts @@ -8,43 +8,39 @@ import { Command } from "commander"; import { runBuild } from "./commands/build"; import { ALL_PACKAGES, CATEGORY_VM } from "./common/cli.constants"; import { startDeleteFlow } from "./commands/delete"; -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"; +import { CliValidator } from "./cli.validator"; class CompassCli { private program: Command; - private options: Options_Cli; + private validator: CliValidator; constructor(args: string[]) { this.program = this._createProgram(); + this.validator = new CliValidator(this.program); this.program.parse(args); - this.options = this._getCliOptions(); } public async run() { - const { force, user } = this.options; + const options = this.validator.getCliOptions(); + const { force, user } = options; const cmd = this.program.args[0]; switch (true) { case cmd === "build": { - await this._validateBuild(); - await runBuild(this.options); + await this.validator.validateBuild(options); + await runBuild(options); break; } case cmd === "delete": { - this._validateDelete(); + this.validator.validateDelete(options); await startDeleteFlow(user as string, force); break; } default: - this._exitHelpfully("root", `${cmd as string} is not a supported cmd`); + this.validator.exitHelpfully( + "root", + `${cmd as string} is not a supported cmd` + ); } } @@ -78,156 +74,6 @@ class CompassCli { ); return program; } - - private _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); - } - - 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 _getCliOptions(): Options_Cli { - const options = this._mergeOptions(); - const validOptions = this._validateOptions(options); - - console.log("options", options); - console.log("validOptions:", validOptions); - return validOptions; - } - - 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 async _validateBuild() { - if (!this.options.packages) { - this.options.packages = await getPckgsTo("build"); - } - - const unsupportedPackages = this.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()}` - ); - } - } - - private _validateDelete() { - const { user } = this.options; - if (!user || typeof user !== "string") { - this._exitHelpfully("delete", "You must supply a user"); - } - } - - 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; - } } const cli = new CompassCli(process.argv); diff --git a/packages/scripts/src/cli.validator.ts b/packages/scripts/src/cli.validator.ts new file mode 100644 index 000000000..5a357a0c8 --- /dev/null +++ b/packages/scripts/src/cli.validator.ts @@ -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; + } +} From e34eb1148331e1138cea4a6c1449aeb97a2289df Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Mon, 6 Jan 2025 06:44:21 -0600 Subject: [PATCH 10/10] build(deps): add zod to backend --- packages/backend/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index eccf2daf9..5c53762e4 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -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",