diff --git a/package.json b/package.json index c269053..7c8922f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "ts-module-default", + "name": "@itsericwoodward/utils", "version": "0.1.0", "description": "", "main": "./lib/cjs/index.js", @@ -24,12 +24,12 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://git.itsericwoodward.com/eric/ts-npm-module-default.git" + "url": "https://git.itsericwoodward.com/eric/utils.git" }, "bugs": { - "url": "https://git.itsericwoodward.com/eric/ts-npm-module-default/issues" + "url": "https://git.itsericwoodward.com/eric/utils/issues" }, - "homepage": "https://git.itsericwoodward.com/eric/ts-npm-module-default#readme", + "homepage": "https://git.itsericwoodward.com/eric/utils#readme", "keywords": [], "devDependencies": { "@tsconfig/node20": "^20.1.2", diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..092dbff --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export * from "./utils.js"; +export { default as loadConfig } from "./loadConfig.js"; diff --git a/src/loadConfig.ts b/src/loadConfig.ts new file mode 100644 index 0000000..da1755f --- /dev/null +++ b/src/loadConfig.ts @@ -0,0 +1,50 @@ +import path from "path"; + +import { + convertCamelToUpperSnakeCase, + getDirname, + readJsonIfExists, +} from "./utils"; + +export default ( + opts: Partial, + envKey: string, + localDefaultFilePath = "" +) => { + const { env } = process, + { __dirname } = getDirname(), + def = readJsonIfExists( + path.join(__dirname, localDefaultFilePath || "./defaults.json5") + ), + // gets value from ENV || options || defaults (in that order) + getVal = (envName: keyof T) => { + const snakeEnvName = `${envKey}_${convertCamelToUpperSnakeCase( + envName as string + )}`; + if (env[snakeEnvName]) return env[snakeEnvName]; + if (opts[envName]) return opts[envName]; + return def[envName]; + }, + // gets array from ENV || options || defaults (in that order) + getArray = (envName: keyof T, optName: keyof T | "" = "") => { + let newEnvName; + if (optName === "") { + optName = envName; + newEnvName = convertCamelToUpperSnakeCase(envName as string); + } + newEnvName = `${envKey}_${String(envName)}`; + if (env[newEnvName]) + return env?.[newEnvName as keyof typeof env]?.split(path.delimiter); + if (Array.isArray(opts[optName]) && (opts[optName] as []).length) + return opts[optName]; + return def[optName]; + }; + + return { + ...Object.keys(def).reduce((acc: Partial, curr) => { + if (Array.isArray(def[curr])) acc[curr] = getArray(curr as keyof T); + else acc[curr] = getVal(curr as keyof T); + return acc; + }, {}), + }; +}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..5c19874 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,67 @@ +import chalk from "chalk"; +import fs from "fs"; +import json5 from "json5"; + +export interface ErrorWithCode { + code: string; +} + +export const convertCamelToUpperSnakeCase = (str: string) => + str.replace(/[A-Z]/g, (letter) => `_${letter}`).toUpperCase(); + +/** + * From: https://www.webmanajemen.com/2022/05/use-import-meta-cjs.html + */ +export const getDirname = () => { + // get the stack + const { stack } = new Error(); + // get the third line (the original invoker) + const invokeFileLine = (stack || "").split(`\n`)[2]; + // match the file url from file://(.+.(ts|js)) and get the first capturing group + const __filename = (invokeFileLine.match(/file:\/\/(.+.(ts|js))/) || + [])[1].slice(1); + // match the file URL from file://(.+)/ and get the first capturing group + // the (.+) is a greedy quantifier and will make the RegExp expand to the largest match + const __dirname = (invokeFileLine.match(/file:\/\/(.+)\//) || [])[1].slice(1); + return { __dirname, __filename }; +}; + +export const getTime = () => { + const now = new Date(), + tzo = -now.getTimezoneOffset(), + dif = tzo >= 0 ? "+" : "-", + pad = (num: number) => { + const norm = Math.floor(Math.abs(num)); + return `${norm < 10 ? "0" : ""}${norm}`; + }; + return [ + now.getFullYear(), + "-", + pad(now.getMonth() + 1), + "-", + pad(now.getDate()), + "T", + pad(now.getHours()), + ":", + pad(now.getMinutes()), + ":", + pad(now.getSeconds()), + dif, + pad(tzo / 60), + ":", + pad(tzo % 60), + ].join(""); +}; + +export const log = (...msg: unknown[]) => + console.log(`${chalk.grey(`${getTime()}:`)} ${msg}`); + +export const readJsonIfExists = (filePath: string | URL) => { + try { + return json5.parse(fs.readFileSync(filePath, { encoding: "utf8" })); + } catch (err) { + // if no file, return empty object + if ((err as ErrorWithCode)?.code === "ENOENT") return {}; + throw err; + } +}; diff --git a/tsconfig-cjs.json b/tsconfig-cjs.json index 10a2cac..338edd0 100644 --- a/tsconfig-cjs.json +++ b/tsconfig-cjs.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "module": "CommonJS", - "moduleResolution": "Classic", + "moduleResolution": "Node10", "outDir": "./lib/cjs" }, } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index b7cfb80..8a2dda5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/node18/tsconfig.json", + "extends": "@tsconfig/node20/tsconfig.json", "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ @@ -9,7 +9,7 @@ // values from https://github.com/tsconfig/bases#centralized-recommendations-for-tsconfig-bases "module": "Node16", - "moduleResolution": "node16", + "moduleResolution": "Node16", "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ "declarationMap": true, /* Create sourcemaps for d.ts files. */