Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 fix: extract cli args before running build cmd #196

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/backend/src/common/constants/env.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ if (!Object.values(NodeEnv).includes(_nodeEnv)) {
throw new Error(`Invalid NODE_ENV value: '${_nodeEnv}'`);
}

const IS_DEV = isDev(_nodeEnv);
export const IS_DEV = isDev(_nodeEnv);

const EnvSchema = z
.object({
Expand Down
121 changes: 73 additions & 48 deletions packages/scripts/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,91 @@ 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 { Options_Cli } from "./common/cli.types";
import {
log,
mergeOptions,
validateOptions,
validatePackages,
} from "./common/cli.utils";

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

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"
);
constructor(args: string[]) {
this.program = this.createProgram();
this.program.parse(args);
this.options = this.getCliOptions();
}

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");
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("delete")
.description("deletes users data from compass database");
program
.command("build")
.description("build compass package")
.argument(
`[${ALL_PACKAGES.join(" | ")}]`,
"package to build (only provde 1 at a time)"
)
.option(
"-c, --clientId <clientId>",
"google client id to inject into build"
);

program.parse(process.argv);
program
.command("delete")
.description("delete user data from compass database");
return program;
}

const options = program.opts();
const cmd = program.args[0];
private getCliOptions(): Options_Cli {
const options = mergeOptions(this.program);
const validOptions = validateOptions(options);

switch (true) {
case cmd === "build": {
await runBuild(options);
break;
}
case cmd === "delete": {
const force = options["force"] as boolean;
const user = options["user"] as string;
return validOptions;
}

if (!user || typeof user !== "string") {
exitHelpfully("You must supply a user");
}
public async run() {
const { user, force } = this.options;
const cmd = this.program.args[0];

await startDeleteFlow(user, force);
break;
switch (true) {
case cmd === "build": {
await runBuild(this.options);
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("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);
});
54 changes: 28 additions & 26 deletions packages/scripts/src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,40 @@
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,
NODE_BUILD,
PCKG,
} from "@scripts/common/cli.constants";
import {
getVmInfo,
getPckgsTo,
_confirm,
log,
fileExists,
getClientId,
getApiBaseUrl,
getEnvironmentAnswer,
validatePackages,
} from "@scripts/common/cli.utils";

export const runBuild = async (options: Options_Cli) => {
const env = options["environment"];
const vmInfo = await getVmInfo(env);

const pckgs =
options["packages"] === undefined
? await getPckgsTo("build")
: options["packages"];
const pckgs = options.packages ? options.packages : await getPckgsTo("build");
validatePackages(pckgs);

if (pckgs.includes(PCKG.NODE)) {
await buildNodePckgs(vmInfo, options["skipEnv"]);
await buildNodePckgs(options);
}

if (pckgs.includes(PCKG.WEB)) {
await buildWeb(vmInfo);
await buildWeb(options);
}
};

// eslint-disable-next-line @typescript-eslint/require-await
const buildNodePckgs = async (vmInfo: Info_VM, skipEnv?: boolean) => {
const buildNodePckgs = async (options: Options_Cli) => {
removeOldBuildFor(PCKG.NODE);
createNodeDirs();
await copyNodeConfigsToBuild(vmInfo, skipEnv);
await copyNodeConfigsToBuild(options);

log.info("Compiling node packages ...");
shell.exec(
Expand All @@ -58,11 +53,17 @@ const buildNodePckgs = async (vmInfo: Info_VM, skipEnv?: boolean) => {
);
};

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 = options.clientId
? options.clientId
: await getClientId(environment);

const envPath = path.join(__dirname, "..", "..", "..", "backend", envFile);
dotenv.config({ path: envPath });
Expand All @@ -78,15 +79,16 @@ 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
- copy it to your ${destination} server
- unzip it
- run it`);
- zip the build dir
- 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) => {
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}`;

Expand All @@ -99,7 +101,7 @@ 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?");
options.force === true ? true : await _confirm("Continue anyway?");

if (!keepGoing) {
log.error("Exiting due to missing env file");
Expand Down
25 changes: 11 additions & 14 deletions packages/scripts/src/common/cli.types.ts
Original file line number Diff line number Diff line change
@@ -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<typeof Schema_Options_Cli>;
Loading
Loading