Skip to content

Commit

Permalink
Merge pull request #2 from SecJS/feat/len-load-sync
Browse files Browse the repository at this point in the history
Feat/len load sync
  • Loading branch information
jlenon7 authored Nov 18, 2021
2 parents 7d21ea4 + edaacd3 commit bd6e0eb
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 20 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# editorconfig.org
root = true

[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ import { Config } from '@secjs/config'
console.log(Config.get('app')) // { hello: 'world' }
console.log(Config.get('app.hello')) // world

// You can use Config to get environment variables too but we recommend using @secjs/env
// You can use Config to get environment variables too but we recommend using Env function from @secjs/env
console.log(Config.get('DB_HOST')) // 127.0.0.1

// You can use a defaultValue, if config does not exist, defaultValue will be returned
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@secjs/config",
"version": "1.0.2",
"version": "1.0.3",
"description": "",
"license": "MIT",
"author": "João Lenon",
Expand Down Expand Up @@ -151,12 +151,12 @@
}
},
"files": [
"src/*.d.ts",
"src/*.js",
"src/**/*.d.ts",
"src/*.d.ts",
"src/**/*.js",
"index.d.ts",
"index.js"
"src/**/*.d.ts",
"*.js",
"*.d.ts"
],
"config": {
"commitizen": {
Expand Down
52 changes: 45 additions & 7 deletions src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import { parse } from 'path'
import { Env } from '@secjs/env'
import { getFolders } from '@secjs/utils'
import { FileContract } from '@secjs/contracts'
import { getFoldersSync } from './utils/getFoldersSync'
import { InternalServerException } from '@secjs/exceptions'

export class Config {
private static configs: Map<string, any> = new Map()

constructor() {
const isInitialized = Config.configs.size >= 1

if (isInitialized) {
logger.debug(
'Reloading the Config class has no effect on the configuration files, only for environment variables, as the files have already been loaded as Singletons',
)
}

Config.configs.clear()
}

Expand All @@ -26,30 +35,57 @@ export class Config {
})
}

if (!config) config = Env(mainKey, defaultValue)
if (!config) config = this.configs.get(`env-${mainKey}`)
if (!config) config = defaultValue

return config
}

loadSync() {
Config.loadEnvs()

const path = `${process.cwd()}/config`

const { files } = getFoldersSync(path, true)

files.forEach(file => Config.loadOnDemand(`${path}/${file.name}`, files, 0))

return this
}

async load() {
// Important to load all envs in process.env
Env('NODE_ENV', '')
Config.loadEnvs()

const path = `${process.cwd()}/config`

const { files } = await getFolders(path, true)

files.forEach(file => this.loadOnDemand(`${path}/${file.name}`, files, 0))
files.forEach(file => Config.loadOnDemand(`${path}/${file.name}`, files, 0))

return this
}

private loadOnDemand(path: string, files: FileContract[], callNumber = 0) {
private static loadEnvs() {
// Important to load all env files in process.env
Env('NODE_ENV', '')

Object.keys(process.env).forEach(key => {
const envValue = process.env[key]

this.configs.set(`env-${key}`, envValue)
})
}

private static loadOnDemand(
path: string,
files: FileContract[],
callNumber = 0,
) {
const { dir, name, base } = parse(path)
const file = files.find(file => file.name === base)

if (!file) return
if (Config.configs.get(name)) return
if (this.configs.get(name)) return

if (callNumber > 500) {
const content = `Your config file ${base} is using Config.get() to an other config file that is using a Config.get('${name}*'), creating a infinite recursive call.`
Expand All @@ -61,6 +97,8 @@ export class Config {
const matches = file.value.match(/\(([^)]+)\)/g)

for (const match of matches) {
if (this.configs.get(`env-${match}`)) continue

const filePath = `${dir}/${
match.replace(/[(^)']/g, '').split('.')[0]
}.ts`
Expand All @@ -70,6 +108,6 @@ export class Config {
}

logger.debug(`Loading ${name} configuration file`)
Config.configs.set(name, require(`${dir}/${base}`).default)
this.configs.set(name, require(`${dir}/${base}`).default)
}
}
46 changes: 46 additions & 0 deletions src/utils/getFoldersSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { resolve, basename } from 'path'
import { readdirSync, readFileSync } from 'fs'
import { DirectoryContract } from '@secjs/contracts'

/**
* Return all folders of a directory and files inside
*
* @param dir The directory
* @param withFiles If need to get files inside folders by default false
* @param buffer Return the buffer of the file by default false
* @param fullPath Return the full path of folder or archive by default false
* @return The directory root with sub folders and files when withFiles true
*/
export function getFoldersSync(
dir: string,
withFiles = false,
buffer = false,
fullPath = false,
): DirectoryContract {
const dirents = readdirSync(dir, { withFileTypes: true })

const directory = {
path: fullPath ? resolve(dir) : basename(resolve(dir)),
files: [],
folders: [],
}

for (const dirent of dirents) {
const res = resolve(dir, dirent.name)

if (dirent.isDirectory()) {
directory.folders.push(getFoldersSync(res, withFiles, buffer, fullPath))

continue
}

if (dirent.isFile() && withFiles) {
directory.files.push({
name: fullPath ? res : basename(res),
value: buffer ? Buffer.from(res) : readFileSync(res, 'utf-8'),
})
}
}

return directory
}
8 changes: 6 additions & 2 deletions src/utils/global.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Config } from '../Config'
import { Config as ConfigInstance } from '../Config'

export {}

declare global {
class Config {
static get<T>(key: string, defaultValue?: any): T
load(): Promise<void>
loadSync(): void
}
}

new ConfigInstance().loadSync()

const _global = global as any
_global.Config = Config
_global.Config = ConfigInstance
7 changes: 7 additions & 0 deletions tests/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,11 @@ describe('\n Config', () => {
await rm(circularPath)
}
})

it('should be able to use loadSync method to load configs without need of promises', async () => {
new Config().loadSync()

expect(Config.get('DB_NAME')).toBe('testing')
expect(Config.get('database.dbName')).toBe('testing')
})
})
13 changes: 8 additions & 5 deletions tests/global.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import '../src/utils/global'
import { Config } from '../src/Config'

describe('\n Config', () => {
beforeAll(() => (process.env.DB_NAME = 'testing'))

it('should be able to use global Config instance', async () => {
await new Config().load()
expect(Config.get('app.hello')).toBe('world')
})

it('should be able to reload environment variables from Config class but never the config files', async () => {
process.env.DB_NAME = 'testing'

new Config().loadSync()

expect(Config.get('DB_NAME')).toBe('testing')
expect(Config.get('database.dbName')).toBe('testing')
expect(Config.get('database.dbName')).toBe(undefined)
})
})

0 comments on commit bd6e0eb

Please sign in to comment.