refactor: 重构配置加载功能。
This commit is contained in:
parent
a02bc282bb
commit
3ad35f3358
@ -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);
|
||||
})
|
||||
|
140
package-lock.json
generated
140
package-lock.json
generated
@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
package.json
19
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"
|
||||
}
|
||||
}
|
||||
|
@ -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<T extends Object>(configKey?: string): Promise<any> {
|
||||
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<T extends Object>(configKey?: string): Promise<any> {
|
||||
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."
|
||||
);
|
||||
}
|
||||
})
|
||||
|
1
src/config/base-configuration.interface.ts
Normal file
1
src/config/base-configuration.interface.ts
Normal file
@ -0,0 +1 @@
|
||||
export interface BaseConfiguration extends Record<string, any> {}
|
43
src/config/etcd-config-reader.ts
Normal file
43
src/config/etcd-config-reader.ts
Normal file
@ -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<T = BaseConfiguration>({
|
||||
etcd,
|
||||
appName,
|
||||
environment,
|
||||
}: Options): Promise<T> {
|
||||
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}`);
|
||||
}
|
||||
}
|
27
src/config/file-config-reader.ts
Normal file
27
src/config/file-config-reader.ts
Normal file
@ -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<T> {
|
||||
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}`);
|
||||
}
|
||||
}
|
15
src/config/index.ts
Normal file
15
src/config/index.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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"] }),
|
||||
|
@ -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";
|
||||
|
6
src/interfaces/options.interface.ts
Normal file
6
src/interfaces/options.interface.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Etcd3, IOptions } from "etcd3";
|
||||
export interface Options {
|
||||
etcd: Etcd3 | IOptions;
|
||||
appName?: string;
|
||||
environment?: string;
|
||||
}
|
@ -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 {
|
||||
|
18
src/utils/app-pkg.ts
Normal file
18
src/utils/app-pkg.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { readFile } from "fs/promises";
|
||||
import { findUp } from "./find-up";
|
||||
|
||||
interface Pkg {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function getAppPkg<T = Pkg>(): Promise<T> {
|
||||
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));
|
||||
}
|
33
src/utils/find-up.ts
Normal file
33
src/utils/find-up.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
|
||||
export const findUp = async (
|
||||
filenameList: string[] | string,
|
||||
from: string
|
||||
): Promise<string | null> => {
|
||||
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, ".."));
|
||||
};
|
@ -5,6 +5,5 @@
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "./lib/types",
|
||||
"module": "node12",
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user