diff --git a/deployctl.ts b/deployctl.ts index 1e3ac027..49b2d6da 100755 --- a/deployctl.ts +++ b/deployctl.ts @@ -7,6 +7,7 @@ import { parseArgs } from "./src/args.ts"; import { error } from "./src/error.ts"; import deploySubcommand from "./src/subcommands/deploy.ts"; import upgradeSubcommand from "./src/subcommands/upgrade.ts"; +import syncEnvSubcommand from "./src/subcommands/sync_env.ts"; import logsSubcommand from "./src/subcommands/logs.ts"; import { MINIMUM_DENO_VERSION, VERSION } from "./src/version.ts"; import { fetchReleases, getConfigPaths } from "./src/utils/info.ts"; @@ -20,10 +21,14 @@ To deploy a local script: To deploy a remote script: deployctl deploy --project=helloworld https://deno.land/x/deploy/examples/hello.js +To send local env vars a project: + deployctl env --project=helloworld ./.env + SUBCOMMANDS: deploy Deploy a script with static files to Deno Deploy upgrade Upgrade deployctl to the given version (defaults to latest) logs View logs for the given project + env Send environment variables defined in a file to a project `; if (!semverGreaterThanOrEquals(Deno.version.deno, MINIMUM_DENO_VERSION)) { @@ -84,6 +89,8 @@ switch (subcommand) { case "upgrade": await upgradeSubcommand(args); break; + case "env": + await syncEnvSubcommand(args); case "logs": await logsSubcommand(args); break; diff --git a/deps.ts b/deps.ts index baa81c6a..dc665bce 100644 --- a/deps.ts +++ b/deps.ts @@ -14,6 +14,8 @@ export { red, yellow, } from "https://deno.land/std@0.170.0/fmt/colors.ts"; +export { LineStream } from "https://deno.land/std@0.148.0/streams/delimiter.ts"; +export { config } from "https://deno.land/std@0.148.0/dotenv/mod.ts"; export { parse } from "https://deno.land/std@0.195.0/flags/mod.ts"; export { TextLineStream } from "https://deno.land/std@0.170.0/streams/text_line_stream.ts"; diff --git a/src/subcommands/sync_env.ts b/src/subcommands/sync_env.ts new file mode 100644 index 00000000..eb466277 --- /dev/null +++ b/src/subcommands/sync_env.ts @@ -0,0 +1,88 @@ +// Copyright 2021 Deno Land Inc. All rights reserved. MIT license. + +import { config, wait } from "../../deps.ts"; +import { error } from "../error.ts"; +import { API } from "../utils/api.ts"; + +const help = `deployctl env +Sync environment variable with Deno Deploy. + +USAGE: + deployctl env [OPTIONS] + +OPTIONS: + -h, --help Prints help information + -p, --project=NAME The project to deploy to + --token=TOKEN The API token to use (defaults to DENO_DEPLOY_TOKEN env var) +`; + +export interface Args { + help: boolean; + token: string | null; + project: string | null; +} + +// deno-lint-ignore no-explicit-any +export default async function (rawArgs: Record): Promise { + const args: Args = { + help: !!rawArgs.help, + token: rawArgs.token ? String(rawArgs.token) : null, + project: rawArgs.project ? String(rawArgs.project) : null, + }; + const envFile: string | null = typeof rawArgs._[0] === "string" + ? rawArgs._[0] + : null; + if (args.help) { + console.log(help); + Deno.exit(0); + } + const token = args.token ?? Deno.env.get("DENO_DEPLOY_TOKEN") ?? null; + if (token === null) { + console.error(help); + error("Missing access token. Set via --token or DENO_DEPLOY_TOKEN."); + } + if (envFile === null) { + console.error(help); + error("No enivronment 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."); + } + + const projectSpinner = wait("Fetching project information...").start(); + const api = API.fromToken(token); + const project = await api.getProject(args.project); + if (project === null) { + projectSpinner.fail("Project not found."); + Deno.exit(1); + } + projectSpinner.succeed(`Project: ${project!.name}`); + + const fileSpinner = wait("Reading env file...").start(); + let envObj: Record = {}; + try { + envObj = await config({ path: envFile }); + if (Object.keys(envObj).length === 0) { + fileSpinner.info("File did not contain any variables."); + Deno.exit(1); + } + } catch { + fileSpinner.fail(`Could not load file: ${envFile}`); + Deno.exit(1); + } + fileSpinner.succeed(`File Loaded: ${envFile}`); + + const sendSpinner = wait("Sending env variables...").start(); + try { + await api.sendEnv(project!.id, envObj); + } catch { + sendSpinner.fail("Failed to send variables."); + Deno.exit(1); + } + sendSpinner.succeed("Env variables sent."); +} diff --git a/src/utils/api.ts b/src/utils/api.ts index 843f7fa7..d3dd545d 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -203,4 +203,14 @@ export class API { getMetadata(): Promise { return this.#requestJson("/meta"); } + + async sendEnv( + projectId: string, + body: Record, + ): Promise { + return await this.#requestJson( + `/projects/${projectId}/env`, + { method: "POST", body }, + ); + } }