From a6640efb309960660377281f9bbb01f8b94d7556 Mon Sep 17 00:00:00 2001 From: Ryo Ota Date: Tue, 29 Aug 2023 21:31:51 +0900 Subject: [PATCH] feat: add --env-path option to load env file --- package-lock.json | 12 ++++ package.json | 1 + src/command/migrate-config-command.ts | 6 +- src/custom-yaml-load.ts | 96 ++++++++++++++------------- src/index.ts | 28 ++++++-- test/test-utils.ts | 4 +- 6 files changed, 91 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index e58c5c6d..12b9e2f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "basic-auth": "^2.0.1", "cookie": "^0.5.0", + "dotenv": "^16.3.1", "js-yaml": "^4.1.0", "log4js": "^6.9.1", "openid-client": "^5.4.3", @@ -1417,6 +1418,17 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", diff --git a/package.json b/package.json index 2077f780..723e9120 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "dependencies": { "basic-auth": "^2.0.1", "cookie": "^0.5.0", + "dotenv": "^16.3.1", "js-yaml": "^4.1.0", "log4js": "^6.9.1", "openid-client": "^5.4.3", diff --git a/src/command/migrate-config-command.ts b/src/command/migrate-config-command.ts index eb8cb67e..575011e6 100644 --- a/src/command/migrate-config-command.ts +++ b/src/command/migrate-config-command.ts @@ -3,9 +3,11 @@ import * as fs from "fs"; import {configV1Schema, migrateToConfigV1} from "../config/v1"; import {configWihtoutVersionSchema} from "../config/without-version"; import * as yaml from "js-yaml"; +import * as dotenv from "dotenv"; -export function migrateConfigCommand(configPath: string) { - const configYaml = customYamlLoad(fs.readFileSync(configPath, 'utf8')); +export function migrateConfigCommand(envFilePath: string | undefined, configPath: string) { + const extraEnv = envFilePath == undefined ? {} : dotenv.parse(fs.readFileSync(envFilePath)); + const configYaml = customYamlLoad({ extraEnv, yamlString: fs.readFileSync(configPath, 'utf8') }); if (configV1Schema.safeParse(configYaml).success) { console.log("The config is already a valid config v1"); return; diff --git a/src/custom-yaml-load.ts b/src/custom-yaml-load.ts index c7ff5f59..3274e9bc 100644 --- a/src/custom-yaml-load.ts +++ b/src/custom-yaml-load.ts @@ -1,52 +1,58 @@ import * as yaml from "js-yaml"; -export function customYamlLoad(str: string) { - return yaml.load(str, { schema: customYamlSchema }); -} +export function customYamlLoad({extraEnv, yamlString}: {extraEnv: { [name: string]: string }, yamlString: string}) { + const concatYamlType = new yaml.Type('!concat', { + kind: 'sequence', + resolve(data) { + return Array.isArray(data); + }, + construct(data) { + return data.join(""); + }, + }); -const concatYamlType = new yaml.Type('!concat', { - kind: 'sequence', - resolve(data) { - return Array.isArray(data); - }, - construct(data) { - return data.join(""); - }, -}); + const envYamlType = new yaml.Type('!env', { + kind: 'scalar', + resolve(data) { + return typeof data === "string"; + }, + construct(data) { + // if env[data] is undefined, the config validation will occur an error. + const env = { + ...process.env, + // override + ...extraEnv, + } + return env[data]; + }, + }); -const envYamlType = new yaml.Type('!env', { - kind: 'scalar', - resolve(data) { - return typeof data === "string"; - }, - construct(data) { - return process.env[data]; - }, -}); + const jsonDecodeYamlType = new yaml.Type('!json_decode', { + kind: 'scalar', + resolve(data) { + return typeof data === "string"; + }, + construct(data) { + return JSON.parse(data); + }, + }); -const jsonDecodeYamlType = new yaml.Type('!json_decode', { - kind: 'scalar', - resolve(data) { - return typeof data === "string"; - }, - construct(data) { - return JSON.parse(data); - }, -}); + const unrecommendedJsYamlType = new yaml.Type('!unrecommended_js', { + kind: 'scalar', + resolve(data) { + return typeof data === "string"; + }, + construct(data) { + return new Function("require", data)(require); + }, + }); -const unrecommendedJsYamlType = new yaml.Type('!unrecommended_js', { - kind: 'scalar', - resolve(data) { - return typeof data === "string"; - }, - construct(data) { - return new Function("require", data)(require); - }, -}); + const customYamlSchema = yaml.DEFAULT_SCHEMA.extend([ + concatYamlType, + envYamlType, + jsonDecodeYamlType, + unrecommendedJsYamlType, + ]); -const customYamlSchema = yaml.DEFAULT_SCHEMA.extend([ - concatYamlType, - envYamlType, - jsonDecodeYamlType, - unrecommendedJsYamlType, -]); + return yaml.load(yamlString, { schema: customYamlSchema }); +} diff --git a/src/index.ts b/src/index.ts index 0568240b..88970720 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import * as log4js from "log4js"; import * as yargs from "yargs"; import { z } from "zod"; import * as piping from "piping-server"; +import * as dotenv from "dotenv"; import {generateHandler} from "./rich-piping-server"; import {configWihtoutVersionSchema} from "./config/without-version"; @@ -46,6 +47,10 @@ const parser = yargs describe: "Certification path", type: "string" }) + .option('env-path', { + describe: ".env file path", + type: "string", + }) .option('config-path', { describe: "Config YAML path", type: "string", @@ -63,7 +68,7 @@ https://github.com/nwtgck/rich-piping-server#readme }) .command("migrate-config", "Print migrated config", (yargs) => { }, (argv) => { - migrateConfigCommand(argv.configPath); + migrateConfigCommand(argv.envPath, argv.configPath); }); @@ -82,6 +87,7 @@ if (args._.length === 0) { httpsPort: args["https-port"], serverKeyPath: args["key-path"], serverCrtPath: args["crt-path"], + envFilePath: args["env-path"], configYamlPath: args["config-yaml-path"], printConfigJson: args["debug-config"], }); @@ -104,11 +110,12 @@ function logZodError(zodError: z.ZodError) { } } -function loadAndUpdateConfig(logger: log4js.Logger, configYamlPath: string, printConfigJson: boolean): void { +function loadAndUpdateConfig(logger: log4js.Logger, envFilePath: string | undefined, configYamlPath: string, printConfigJson: boolean): void { // Load config - logger.info(`Loading ${JSON.stringify(configYamlPath)}...`); + logger.info(`Loading ${ envFilePath === undefined ? "" : `${JSON.stringify(envFilePath)} and ` }${JSON.stringify(configYamlPath)}...`); try { - const configYaml = customYamlLoad(fs.readFileSync(configYamlPath, 'utf8')); + const extraEnv = envFilePath == undefined ? {} : dotenv.parse(fs.readFileSync(envFilePath)); + const configYaml = customYamlLoad({ extraEnv, yamlString: fs.readFileSync(configYamlPath, 'utf8') }); // NOTE: using configBasicSchema makes error message better const configBasicParsed = configBasicSchema.safeParse(configYaml); if (!configBasicParsed.success) { @@ -142,22 +149,29 @@ function loadAndUpdateConfig(logger: log4js.Logger, configYamlPath: string, prin } } -async function serve({ host, httpPort, enableHttps, httpsPort, serverKeyPath, serverCrtPath, configYamlPath, printConfigJson }: { +async function serve({ host, httpPort, enableHttps, httpsPort, serverKeyPath, serverCrtPath, envFilePath, configYamlPath, printConfigJson }: { host: string | undefined, httpPort: number, enableHttps: boolean, httpsPort: number | undefined, serverKeyPath: string | undefined, + envFilePath: string | undefined, serverCrtPath: string | undefined, configYamlPath: string, printConfigJson: boolean, }) { // Load config - loadAndUpdateConfig(logger, configYamlPath, printConfigJson); + loadAndUpdateConfig(logger, envFilePath, configYamlPath, printConfigJson); + // Watch env file + if (envFilePath !== undefined) { + fs.watch(envFilePath, () => { + loadAndUpdateConfig(logger, envFilePath, configYamlPath, printConfigJson); + }); + } // Watch config yaml fs.watch(configYamlPath, () => { - loadAndUpdateConfig(logger, configYamlPath, printConfigJson); + loadAndUpdateConfig(logger, envFilePath, configYamlPath, printConfigJson); }); // Create a piping server diff --git a/test/test-utils.ts b/test/test-utils.ts index 8bad7b65..31deedeb 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -116,12 +116,12 @@ export function createTransferAssertions({getPipingUrl}: { getPipingUrl: () => s } export function readConfigWithoutVersionAndMigrateToV1AndNormalize(yamlString: string): NormalizedConfig { - const configYaml = customYamlLoad(yamlString); + const configYaml = customYamlLoad({extraEnv: {}, yamlString}); const configWithoutVersion = configWihtoutVersionSchema.parse(configYaml); return normalizeConfigV1(undefined, migrateToConfigV1(configWithoutVersion)); } export function readConfigV1AndNormalize(yamlString: string): NormalizedConfig { - const configYaml = customYamlLoad(yamlString); + const configYaml = customYamlLoad({extraEnv: {}, yamlString}); return normalizeConfigV1(undefined, configV1Schema.parse(configYaml)); }