From c27921a73586e1550d406e18af8a4f5f9977f15b Mon Sep 17 00:00:00 2001 From: Miguel Romero Karam Date: Wed, 9 Aug 2023 12:55:17 +0200 Subject: [PATCH] wip(cli): add `sync_env` subcommand --- lib/cli/deps.ts | 2 +- lib/cli/src/subcommands/deploy.ts | 48 ++++++------ lib/cli/src/subcommands/logs.ts | 17 ++-- lib/cli/src/subcommands/sync_env.ts | 116 ++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 33 deletions(-) create mode 100644 lib/cli/src/subcommands/sync_env.ts diff --git a/lib/cli/deps.ts b/lib/cli/deps.ts index 7389414d7..4fbf07d05 100644 --- a/lib/cli/deps.ts +++ b/lib/cli/deps.ts @@ -16,7 +16,7 @@ export { export { parse as parseArgs } from "https://deno.land/std@0.170.0/flags/mod.ts"; export { TextLineStream } from "https://deno.land/std@0.170.0/streams/text_line_stream.ts"; export { default as question } from "https://deno.land/x/question@0.0.2/mod.ts"; -export { load } from "https://deno.land/std@0.192.0/dotenv/mod.ts"; +export { config, load } from "https://deno.land/std@0.192.0/dotenv/mod.ts"; // x/semver export { diff --git a/lib/cli/src/subcommands/deploy.ts b/lib/cli/src/subcommands/deploy.ts index 0a73f3409..fde9d3d51 100644 --- a/lib/cli/src/subcommands/deploy.ts +++ b/lib/cli/src/subcommands/deploy.ts @@ -45,7 +45,6 @@ USAGE: netzo deploy [OPTIONS] OPTIONS: - --api-key= The API key to use (defaults to NETZO_API_KEY environment variable) --exclude= Exclude files that match this pattern --include= Only upload files that match this pattern --import-map= Use import map file @@ -54,6 +53,7 @@ OPTIONS: --prod Create a production deployment (default is preview deployment) -p, --project= The UID of the project to deploy to --dry-run Dry run the deployment process + --api-key= The API key to use (defaults to NETZO_API_KEY environment variable) ARGS: The file path to the entrypoint file (e.g. main.tsx) @@ -65,11 +65,11 @@ export interface Args { prod: boolean; exclude?: string[]; include?: string[]; - apiKey: string | null; - apiUrl?: string; project: string | null; importMap: string | null; dryRun: boolean; + apiKey: string | null; + apiUrl?: string; } // deno-lint-ignore no-explicit-any @@ -78,13 +78,13 @@ export default async function (rawArgs: Record): Promise { help: !!rawArgs.help, static: !rawArgs["no-static"], // negate the flag prod: !!rawArgs.prod, - apiKey: rawArgs["api-key"] ? String(rawArgs["api-key"]) : null, - apiUrl: rawArgs["api-url"] ?? "https://api.netzo.io", project: rawArgs.project ? String(rawArgs.project) : null, importMap: rawArgs["import-map"] ? String(rawArgs["import-map"]) : null, exclude: rawArgs.exclude?.split(","), include: rawArgs.include?.split(","), dryRun: !!rawArgs["dry-run"], + apiKey: rawArgs["api-key"] ? String(rawArgs["api-key"]) : null, + apiUrl: rawArgs["api-url"] ?? "https://api.netzo.io", }; const entrypoint = typeof rawArgs._[0] === "string" ? rawArgs._[0] : null; if (args.help) { @@ -111,23 +111,23 @@ export default async function (rawArgs: Record): Promise { error("Missing project UID."); } - const opts: DeployOpts = { - entrypoint: await parseEntrypoint(entrypoint).catch((e) => error(e)), - importMapUrl: args.importMap === null - ? null - : await parseEntrypoint(args.importMap, undefined, "import map") - .catch((e) => error(e)), - static: args.static, - prod: args.prod, - apiKey, - apiUrl: args.apiUrl, - project: args.project, - include: args.include?.map((pattern) => normalize(pattern)), - exclude: args.exclude?.map((pattern) => normalize(pattern)), - dryRun: args.dryRun, - }; - - await deploy(opts); + await deploy( + { + entrypoint: await parseEntrypoint(entrypoint).catch((e) => error(e)), + importMapUrl: args.importMap === null + ? null + : await parseEntrypoint(args.importMap, undefined, "import map") + .catch((e) => error(e)), + static: args.static, + prod: args.prod, + project: args.project, + include: args.include?.map((pattern) => normalize(pattern)), + exclude: args.exclude?.map((pattern) => normalize(pattern)), + dryRun: args.dryRun, + apiKey, + apiUrl: args.apiUrl, + } satisfies DeployOpts, + ); } interface DeployOpts { @@ -137,10 +137,10 @@ interface DeployOpts { prod: boolean; exclude?: string[]; include?: string[]; - apiKey: string; - apiUrl?: string; project: string; dryRun: boolean; + apiKey: string; + apiUrl?: string; } async function deploy(opts: DeployOpts): Promise { diff --git a/lib/cli/src/subcommands/logs.ts b/lib/cli/src/subcommands/logs.ts index 11778192b..c1de0a774 100644 --- a/lib/cli/src/subcommands/logs.ts +++ b/lib/cli/src/subcommands/logs.ts @@ -21,19 +21,19 @@ USAGE: netzo logs [OPTIONS] OPTIONS: - --api-key The API key to use (defaults to NETZO_API_KEY environment variable) --deployment The ID of the deployment you want to stream logs for (defaults to latest deployment) --prod Select the production deployment -p, --project The UID of the project you want to stream logs for + --api-key The API key to use (defaults to NETZO_API_KEY environment variable) `; export interface Args { help: boolean; prod: boolean; - apiKey: string | null; - apiUrl?: string; deployment: string | null; project: string | null; + apiKey: string | null; + apiUrl?: string; } // deno-lint-ignore no-explicit-any @@ -41,10 +41,10 @@ export default async function (rawArgs: Record): Promise { const args: Args = { help: !!rawArgs.help, prod: !!rawArgs.prod, - apiKey: rawArgs["api-key"] ? String(rawArgs["api-key"]) : null, - apiUrl: rawArgs["api-url"] ?? "https://api.netzo.io", deployment: rawArgs.deployment ? String(rawArgs.deployment) : null, project: rawArgs.project ? String(rawArgs.project) : null, + apiKey: rawArgs["api-key"] ? String(rawArgs["api-key"]) : null, + apiUrl: rawArgs["api-url"] ?? "https://api.netzo.io", }; if (args.help) { @@ -67,7 +67,7 @@ export default async function (rawArgs: Record): Promise { error("Too many positional arguments given."); } - const opts: DeployOpts = { + const opts: LogsOpts = { project: args.project, deploymentId: args.deployment, prod: args.prod, @@ -78,7 +78,7 @@ export default async function (rawArgs: Record): Promise { await logs(opts); } -interface DeployOpts { +interface LogsOpts { project: string; deploymentId: string | null; prod: boolean; @@ -86,7 +86,7 @@ interface DeployOpts { apiUrl?: string; } -async function logs(opts: DeployOpts): Promise { +async function logs(opts: LogsOpts): Promise { if (opts.prod && opts.deploymentId) { error( "You can't select a deployment and choose production flag at the same time", @@ -98,6 +98,7 @@ async function logs(opts: DeployOpts): Promise { uid: opts.project, $limit: 1, }); + // FIXME: get deployments from project using `api` client const projectDeployments = await DenoAPI.getDeployments(project._id); if (project === null) { projectSpinner.fail("Project not found."); diff --git a/lib/cli/src/subcommands/sync_env.ts b/lib/cli/src/subcommands/sync_env.ts new file mode 100644 index 000000000..07f5647bc --- /dev/null +++ b/lib/cli/src/subcommands/sync_env.ts @@ -0,0 +1,116 @@ +// from https://github.com/drollinger/deployctl +// see https://github.com/denoland/deployctl/issues/138 + +import { config, netzo, Paginated, Project, wait } from "../../deps.ts"; +import { error } from "../console.ts"; + +const help = `netzo env +Sync environment variable with Netzo. + +USAGE: + netzo env [OPTIONS] + +OPTIONS: + -h, --help Prints help information + -p, --project=NAME The project to deploy to + --api-key= The API key to use (defaults to NETZO_API_KEY environment variable) +`; + +export interface Args { + help: boolean; + project: string | null; + apiKey: string | null; + apiUrl?: string; +} + +// deno-lint-ignore no-explicit-any +export default async function (rawArgs: Record): Promise { + const args: Args = { + help: !!rawArgs.help, + project: rawArgs.project ? String(rawArgs.project) : null, + apiKey: rawArgs["api-key"] ? String(rawArgs["api-key"]) : null, + apiUrl: rawArgs["api-url"] ?? "https://api.netzo.io", + }; + const envFile: string | null = typeof rawArgs._[0] === "string" + ? rawArgs._[0] + : null; + if (args.help) { + console.log(help); + Deno.exit(0); + } + const apiKey = args.apiKey ?? Deno.env.get("NETZO_API_KEY") ?? null; + if (apiKey === null) { + console.error(help); + error( + "Missing API key. Set via --api-key flag or NETZO_API_KEY environment variable to avoid passing it each time.", + ); + } + if (envFile === null) { + console.error(help); + error("No environment file specifier given."); + } + if (rawArgs._.length > 1) { + console.error(help); + error("Too many positional arguments given."); + } + if (args.project === null) { + console.error(help); + error("Missing project name."); + } + + await syncEnv( + { + envFile, + project: args.project, + apiKey, + apiUrl: args.apiUrl, + } satisfies SyncEnvOpts, + ); +} + +interface SyncEnvOpts { + envFile: string; + project: string; + apiKey: string; + apiUrl?: string; +} + +async function syncEnv(opts: SyncEnvOpts): Promise { + const projectSpinner = wait("Fetching project information...").start(); + const { api } = netzo({ apiKey: opts.apiKey, baseURL: opts.apiUrl }); + const { data: [project] } = await api.projects.get>({ + uid: opts.project, + $limit: 1, + }); + if (project === null) { + projectSpinner.fail("Project not found."); + Deno.exit(1); + } + projectSpinner.succeed(`Project: ${project.name}`); + + const fileSpinner = wait("Reading env file...").start(); + let envVars: Record = {}; + try { + envVars = await config({ path: opts.envFile }); + if (Object.keys(envVars).length === 0) { + fileSpinner.info("File did not contain any variables."); + Deno.exit(1); + } + } catch { + fileSpinner.fail(`Could not load file: ${opts.envFile}`); + Deno.exit(1); + } + fileSpinner.succeed(`File Loaded: ${opts.envFile}`); + + const syncSpinner = wait("Syncing environment variables...").start(); + try { + await api.projects[project._id].patch({ + ...project.configuration, + envVars, + }); + } catch { + syncSpinner.fail("Failed to sync variables."); + Deno.exit(1); + } + syncSpinner.succeed("Environment variables synced."); +}