From 3ad35f3358b33515c2dbed893b0b7b4c87b2b357 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Thu, 28 Oct 2021 22:08:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8A=A0=E8=BD=BD=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/config-loader.ts | 4 +- package-lock.json | 140 ++------------------- package.json | 19 +-- src/config-loader.ts | 33 +++-- src/config/base-configuration.interface.ts | 1 + src/config/etcd-config-reader.ts | 43 +++++++ src/config/file-config-reader.ts | 27 ++++ src/config/index.ts | 15 +++ src/etcd-connection.ts | 2 +- src/index.ts | 9 +- src/interfaces/options.interface.ts | 6 + src/service-register.ts | 4 +- src/utils/app-pkg.ts | 18 +++ src/utils/find-up.ts | 33 +++++ tsconfig.json | 1 - 15 files changed, 186 insertions(+), 169 deletions(-) create mode 100644 src/config/base-configuration.interface.ts create mode 100644 src/config/etcd-config-reader.ts create mode 100644 src/config/file-config-reader.ts create mode 100644 src/config/index.ts create mode 100644 src/interfaces/options.interface.ts create mode 100644 src/utils/app-pkg.ts create mode 100644 src/utils/find-up.ts diff --git a/examples/config-loader.ts b/examples/config-loader.ts index 68e2db9..2195785 100644 --- a/examples/config-loader.ts +++ b/examples/config-loader.ts @@ -1,9 +1,9 @@ -import { ConfigLoader } from "../src/config-loader.js"; +import { ConfigLoader } from "../src/config-loader"; const loader = new ConfigLoader(); loader - .loadConfig() + .load() .then((config) => { console.log("config", config); }) diff --git a/package-lock.json b/package-lock.json index 245a483..cd73b82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "debug": "^4.3.2", "etcd3": "^1.1.0", - "find-up": "^6.1.0", "js-yaml": "^4.1.0" }, "devDependencies": { @@ -21,7 +20,7 @@ "@types/node": "^14.17.17", "rimraf": "^3.0.2", "ts-node": "^10.2.1", - "typescript": "^4.5.0-beta" + "typescript": "^4.4.4" } }, "node_modules/@cspotcode/source-map-consumer": { @@ -325,22 +324,6 @@ "cockatiel": "^1.1.1" } }, - "node_modules/find-up": { - "version": "6.1.0", - "resolved": "https://npm.ivanli.cc/find-up/-/find-up-6.1.0.tgz", - "integrity": "sha512-aBlseiBgQ1RSiF/brMW+toDud3NHJ2Hn3pgNJLmBf2+gBwwNbfhE/Lbg2wwwoHfD3qXReOvDH4hlywQCXp4/Lw==", - "license": "MIT", - "dependencies": { - "locate-path": "^7.0.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://npm.ivanli.cc/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -399,21 +382,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/locate-path": { - "version": "7.0.0", - "resolved": "https://npm.ivanli.cc/locate-path/-/locate-path-7.0.0.tgz", - "integrity": "sha512-+cg2yXqDUKfo4hsFxwa3G1cBJeA+gs1vD8FyV9/odWoUlQe/4syxHQ5DPtKjtfm6gnKbZzjCqzX03kXosvZB1w==", - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://npm.ivanli.cc/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -462,45 +430,6 @@ "wrappy": "1" } }, - "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://npm.ivanli.cc/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://npm.ivanli.cc/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://npm.ivanli.cc/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://npm.ivanli.cc/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -599,9 +528,9 @@ } }, "node_modules/typescript": { - "version": "4.5.0-beta", - "resolved": "https://npm.ivanli.cc/typescript/-/typescript-4.5.0-beta.tgz", - "integrity": "sha512-7PvWhki2lwukaR9osVhFnNzxaE4LM+gC94dlwcvS+Tqz8+U65va7FbKo02bT+/MFlnMPM8bsPUXvHiMD/Mg3Jg==", + "version": "4.4.4", + "resolved": "https://npm.ivanli.cc/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -628,18 +557,6 @@ "engines": { "node": ">=6" } - }, - "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://npm.ivanli.cc/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } }, "dependencies": { @@ -870,15 +787,6 @@ "cockatiel": "^1.1.1" } }, - "find-up": { - "version": "6.1.0", - "resolved": "https://npm.ivanli.cc/find-up/-/find-up-6.1.0.tgz", - "integrity": "sha512-aBlseiBgQ1RSiF/brMW+toDud3NHJ2Hn3pgNJLmBf2+gBwwNbfhE/Lbg2wwwoHfD3qXReOvDH4hlywQCXp4/Lw==", - "requires": { - "locate-path": "^7.0.0", - "path-exists": "^5.0.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://npm.ivanli.cc/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -923,14 +831,6 @@ "argparse": "^2.0.1" } }, - "locate-path": { - "version": "7.0.0", - "resolved": "https://npm.ivanli.cc/locate-path/-/locate-path-7.0.0.tgz", - "integrity": "sha512-+cg2yXqDUKfo4hsFxwa3G1cBJeA+gs1vD8FyV9/odWoUlQe/4syxHQ5DPtKjtfm6gnKbZzjCqzX03kXosvZB1w==", - "requires": { - "p-locate": "^6.0.0" - } - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://npm.ivanli.cc/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -970,27 +870,6 @@ "wrappy": "1" } }, - "p-limit": { - "version": "4.0.0", - "resolved": "https://npm.ivanli.cc/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "requires": { - "yocto-queue": "^1.0.0" - } - }, - "p-locate": { - "version": "6.0.0", - "resolved": "https://npm.ivanli.cc/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "requires": { - "p-limit": "^4.0.0" - } - }, - "path-exists": { - "version": "5.0.0", - "resolved": "https://npm.ivanli.cc/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==" - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://npm.ivanli.cc/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1047,9 +926,9 @@ } }, "typescript": { - "version": "4.5.0-beta", - "resolved": "https://npm.ivanli.cc/typescript/-/typescript-4.5.0-beta.tgz", - "integrity": "sha512-7PvWhki2lwukaR9osVhFnNzxaE4LM+gC94dlwcvS+Tqz8+U65va7FbKo02bT+/MFlnMPM8bsPUXvHiMD/Mg3Jg==", + "version": "4.4.4", + "resolved": "https://npm.ivanli.cc/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true }, "wrappy": { @@ -1063,11 +942,6 @@ "resolved": "https://npm.ivanli.cc/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true - }, - "yocto-queue": { - "version": "1.0.0", - "resolved": "https://npm.ivanli.cc/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==" } } } diff --git a/package.json b/package.json index 8de07cd..09b6935 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,19 @@ { - "name": "configuration", + "name": "@fennec/configuration", "version": "0.0.1", "description": "Fennec configuration loader and share info.", - "main": "./lib/index.js", - "types": "./lib/types/index.d.ts", - "type": "module", + "main": "lib", + "types": "lib/types/index.d.ts", + "files": [ + "lib/**/*", + "*.md" + ], "scripts": { "prebuild": "rimraf lib", "build": "tsc -p \"tsconfig.build.json\"", + "prepublishOnly": "npm run build", "start:example-register": "DEBUG=* ts-node ./examples/register.ts", - "start:example-config-loader": "DEBUG=* node --loader ts-node/esm ./examples/config-loader.ts", - "test": "echo \"Error: no test specified\" && exit 1" + "start:example-config-loader": "DEBUG=* ts-node ./examples/config-loader.ts" }, "repository": { "type": "git", @@ -25,9 +28,7 @@ "license": "MIT", "dependencies": { "debug": "^4.3.2", - "esm": "^3.2.25", "etcd3": "^1.1.0", - "find-up": "^6.1.0", "js-yaml": "^4.1.0" }, "devDependencies": { @@ -37,6 +38,6 @@ "@types/node": "^14.17.17", "rimraf": "^3.0.2", "ts-node": "^10.2.1", - "typescript": "^4.5.0-beta" + "typescript": "^4.4.4" } } diff --git a/src/config-loader.ts b/src/config-loader.ts index 30a807f..e0ed3f2 100644 --- a/src/config-loader.ts +++ b/src/config-loader.ts @@ -1,10 +1,11 @@ -import { AppConfig } from "./app-config.model.js"; +import { AppConfig } from "./app-config.model"; import { Etcd3 } from "etcd3"; import { load } from "js-yaml"; import { hostname } from "os"; -import { getEtcdInstance } from "./etcd-connection.js"; +import { getEtcdInstance } from "./etcd-connection"; import { readFile } from "fs/promises"; import debug from "debug"; +import { findUp } from "./utils/find-up"; export class ConfigLoader { private etcd: Etcd3; @@ -17,16 +18,17 @@ export class ConfigLoader { this.etcd = getEtcdInstance(config); } - async loadConfig(configKey?: string): Promise { - const config = await (await import("find-up")) - .findUp(["config,yml", "config.yaml"]) - .then((path) => { - if (path) { - return readFile(path, "utf8").then((str) => load(str)); - } else { - return undefined; - } - }); + async load(configKey?: string): Promise { + const config = await findUp( + ["config.yml", "config.yaml"], + process.cwd() + ).then((path) => { + if (path) { + return readFile(path, "utf8").then((str) => load(str)); + } else { + return undefined; + } + }); if (configKey === undefined) { if (process.env.CONFIG_KEY) { @@ -34,16 +36,13 @@ export class ConfigLoader { } else if (process.env.config_key) { configKey = process.env.config_key; } else { - configKey = await ( - await import("find-up") - ) - .findUp("package.json") + configKey = await findUp("package.json", process.cwd()) .then((path) => { if (path) { return readFile(path, "utf8"); } else { throw new Error( - "Fallback to using package name as configuration name" + "Can't find package.json. Fallback to using package name as configuration name when CONFIG_KEY not defined." ); } }) diff --git a/src/config/base-configuration.interface.ts b/src/config/base-configuration.interface.ts new file mode 100644 index 0000000..2bf7965 --- /dev/null +++ b/src/config/base-configuration.interface.ts @@ -0,0 +1 @@ +export interface BaseConfiguration extends Record {} diff --git a/src/config/etcd-config-reader.ts b/src/config/etcd-config-reader.ts new file mode 100644 index 0000000..15d52e5 --- /dev/null +++ b/src/config/etcd-config-reader.ts @@ -0,0 +1,43 @@ +import debug from "debug"; +import { Etcd3, IOptions } from "etcd3"; +import { load } from "js-yaml"; +import { hostname } from "os"; +import { Options } from "../interfaces/options.interface"; +import { getAppPkg } from "../utils/app-pkg"; +import { BaseConfiguration } from "./base-configuration.interface"; + +const configKeyPrefix = "share/config"; +const logger = debug("fennec:config:loader:etcd"); + +export async function readEtcdConfig({ + etcd, + appName, + environment, +}: Options): Promise { + if (!(etcd instanceof Etcd3)) { + etcd = new Etcd3(etcd); + } + + if (appName == undefined) { + appName = await getAppPkg().then((pkg) => pkg.name); + debug.log("found appName from package.json. [%s]", appName); + } + + const key = `${configKeyPrefix}/${environment ?? hostname()}/${appName}`; + debug.log('etcd key: "%s"', key); + + const plainText = await etcd.get(key); + + if (plainText === null) { + debug.log('No configuration found at "%s"', key); + throw new Error(`No configuration found at ${key}`); + } + try { + return (await load(plainText)) as T; + } catch (error) { + logger.log("Can not parse value as yaml"); + logger.log("plainText: %s", plainText); + logger.log("%O", error); + throw new Error(`Failed to parse configuration at ${key}`); + } +} diff --git a/src/config/file-config-reader.ts b/src/config/file-config-reader.ts new file mode 100644 index 0000000..c400099 --- /dev/null +++ b/src/config/file-config-reader.ts @@ -0,0 +1,27 @@ +import debug from "debug"; +import { readFile } from "fs/promises"; +import { load } from "js-yaml"; +import { Options } from "../interfaces/options.interface"; +import { findUp } from "../utils/find-up"; +import { BaseConfiguration } from "./base-configuration.interface"; + +const configKeyPrefix = "share/config"; +const logger = debug("fennec:config:loader:etcd"); + +export async function readFileConfig< + T = BaseConfiguration +>({}: Options = {}): Promise { + const path = await findUp(["config.yml", "config.yaml"], process.cwd()); + try { + if (path) { + return readFile(path, "utf8").then((str) => load(str) as T); + } else { + throw new Error("No config file found"); + } + } catch (error) { + logger.log("Can not parse file as yaml"); + logger.log("File path: %s", path); + logger.log("%O", error); + throw new Error(`Failed to parse configuration at ${path}`); + } +} diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..9e2a5cc --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,15 @@ +import { Options } from "../interfaces/options.interface"; +import { readEtcdConfig } from "./etcd-config-reader"; +import { readFileConfig } from "./file-config-reader"; + +export * from "./base-configuration.interface"; +export * from "./etcd-config-reader"; +export * from "./file-config-reader"; + +export function readConfiguration(option: Options) { + try { + return readEtcdConfig(option); + } catch (e) { + return readFileConfig(option); + } +} diff --git a/src/etcd-connection.ts b/src/etcd-connection.ts index 90f0c72..b1b4b7f 100644 --- a/src/etcd-connection.ts +++ b/src/etcd-connection.ts @@ -1,5 +1,5 @@ import { Etcd3 } from "etcd3"; -import { AppConfig } from "./app-config.model.js"; +import { AppConfig } from "./app-config.model"; function connectEtcd(config?: AppConfig) { return new Etcd3({ ...(config?.etcd ?? { hosts: ["http://rpi:2379"] }), diff --git a/src/index.ts b/src/index.ts index 1f0eef7..c10db8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ -export * from "./app-config.model.js"; -export * from "./config-loader.js"; -export * from "./etcd-connection.js"; -export * from "./service-register.js"; +export * from "./app-config.model"; +export * from "./config-loader"; +export * from "./etcd-connection"; +export * from "./service-register"; +export * from "./config"; diff --git a/src/interfaces/options.interface.ts b/src/interfaces/options.interface.ts new file mode 100644 index 0000000..e02a4ee --- /dev/null +++ b/src/interfaces/options.interface.ts @@ -0,0 +1,6 @@ +import { Etcd3, IOptions } from "etcd3"; +export interface Options { + etcd: Etcd3 | IOptions; + appName?: string; + environment?: string; +} diff --git a/src/service-register.ts b/src/service-register.ts index 0cda366..6d346b2 100644 --- a/src/service-register.ts +++ b/src/service-register.ts @@ -1,7 +1,7 @@ -import { AppConfig } from "./app-config.model.js"; +import { AppConfig } from "./app-config.model"; import { Etcd3 } from "etcd3"; import { hostname } from "os"; -import { getEtcdInstance } from "./etcd-connection.js"; +import { getEtcdInstance } from "./etcd-connection"; import debug from "debug"; export class ServiceRegister { diff --git a/src/utils/app-pkg.ts b/src/utils/app-pkg.ts new file mode 100644 index 0000000..2e8d3df --- /dev/null +++ b/src/utils/app-pkg.ts @@ -0,0 +1,18 @@ +import { readFile } from "fs/promises"; +import { findUp } from "./find-up"; + +interface Pkg { + name: string; +} + +export async function getAppPkg(): Promise { + return await findUp("package.json", process.cwd()) + .then((path) => { + if (path) { + return readFile(path, "utf8"); + } else { + throw new Error("Can't find package.json."); + } + }) + .then((str) => JSON.parse(str)); +} diff --git a/src/utils/find-up.ts b/src/utils/find-up.ts new file mode 100644 index 0000000..9432252 --- /dev/null +++ b/src/utils/find-up.ts @@ -0,0 +1,33 @@ +import path from "path"; +import fs from "fs/promises"; + +export const findUp = async ( + filenameList: string[] | string, + from: string +): Promise => { + if (!Array.isArray(filenameList)) { + filenameList = [filenameList]; + } + try { + const isDir = await fs.stat(from).then((stat) => stat.isDirectory()); + if (!isDir) { + return null; + } + for (const filename of filenameList) { + const filepath = path.resolve(from, filename); + if ( + await fs + .stat(filepath) + .then((stat) => stat.isFile() || stat.isDirectory()) + ) { + return filepath; + } + } + } catch (err: any) { + if (err.code === "ENOENT") { + return null; + } + throw err; + } + return findUp(filenameList, path.resolve(from, "..")); +}; diff --git a/tsconfig.json b/tsconfig.json index d31f7cc..3749577 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,5 @@ "sourceMap": true, "declaration": true, "declarationDir": "./lib/types", - "module": "node12", }, }