Skip to content

Commit

Permalink
feat: support directory loader
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikaple committed Jul 21, 2021
1 parent 4e5836b commit 1ae405b
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 3 deletions.
77 changes: 75 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ TypedConfigModule.forRoot({
The `fileLoader` function optionally expects a `FileLoaderOptions` object as a first parameter:

```ts
import { Options } from 'cosmiconfig';
import { OptionsSync } from 'cosmiconfig';

export interface FileLoaderOptions extends Partial<Options> {
export interface FileLoaderOptions extends Partial<OptionsSync> {
/**
* basename of config file, defaults to `.env`.
*
Expand Down Expand Up @@ -287,6 +287,79 @@ TypedConfigModule.forRoot({
})
```

### Using directory loader

The `directoryLoader` function allows you to load configuration within a given directory.

The basename of files will be interpreted as config namespace, for example:

```
.
└─config
├── app.toml
└── db.toml
// app.toml
foo = 1
// db.toml
bar = 1
```

The folder above will generate configuration as follows:

```json
{
"app": {
"foo": 1
},
"db": {
"bar": 1
}
}
```

#### Example

```ts
TypedConfigModule.forRoot({
schema: RootConfig,
load: directoryLoader({
directory: '/absolute/path/to/config/directory',
/* other cosmiconfig options */
}),
})
```

#### Passing options

The `directoryLoader` function optionally expects a `DirectoryLoaderOptions` object as a first parameter:

```ts
import { OptionsSync } from 'cosmiconfig';

export interface DirectoryLoaderOptions extends OptionsSync {
/**
* The directory containing all configuration files.
*/
directory: string;
}
```

If you want to add support for other extensions, you can use [`loaders`](https://github.com/davidtheclark/cosmiconfig#loaders) property provided by `cosmiconfig`:

```ts
TypedConfigModule.forRoot({
schema: RootConfig,
load: directoryLoader({
directory: '/path/to/configuration',
// .env.ini has the highest priority now
loaders: {
'.ini': iniLoader
}
}),
})
```
### Using remote loader

The `remoteLoader` function allows you to load configuration from a remote endpoint, such as configuration center. Internally [axios](https://github.com/axios/axios) is used to perform http requests.
Expand Down
49 changes: 49 additions & 0 deletions lib/loader/directory-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { OptionsSync } from 'cosmiconfig';
import { readdirSync } from 'fs';
import { fileLoader } from './file-loader';
import fromPairs from 'lodash.frompairs';

export interface DirectoryLoaderOptions extends OptionsSync {
/**
* The directory containing all configuration files.
*/
directory: string;
}

/**
* Directory loader loads configuration in a specific folder.
* The basename of file will be used as configuration key, for the directory below:
*
* ```
* .
* └─config
* ├── app.toml
* └── db.toml
* ```
*
* The parsed config will be `{ app: "config in app.toml", db: "config in db.toml" }`
* @param options directory loader options.
*/
export const directoryLoader = ({
directory,
...options
}: DirectoryLoaderOptions) => {
return (): Record<string, any> => {
const files = readdirSync(directory);
const fileNames = [
...new Set(files.map(file => file.replace(/\.\w+$/, ''))),
];
const configs = fromPairs(
fileNames.map(name => [
name,
fileLoader({
basename: name,
searchFrom: directory,
...options,
})(),
]),
);

return configs;
};
};
1 change: 1 addition & 0 deletions lib/loader/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './file-loader';
export * from './remote-loader';
export * from './dotenv-loader';
export * from './directory-loader';
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"debug": "^4.3.1",
"dotenv": "^9.0.0",
"dotenv-expand": "^5.1.0",
"lodash.frompairs": "^4.0.1",
"lodash.merge": "^4.6.2",
"lodash.set": "^4.3.2",
"parse-json": "^5.2.0",
Expand All @@ -53,6 +54,7 @@
"@types/debug": "^4.1.5",
"@types/express": "^4.17.11",
"@types/jest": "26.0.23",
"@types/lodash.frompairs": "^4.0.6",
"@types/lodash.merge": "^4.6.6",
"@types/lodash.set": "^4.3.6",
"@types/node": "^7.10.14",
Expand Down
29 changes: 29 additions & 0 deletions tests/e2e/directory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
import { DirectoryConfig, DatabaseConfig } from '../src/config.model';

describe('Directory loader', () => {
let app: INestApplication;

beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [AppModule.withDirectory()],
}).compile();

app = module.createNestApplication();
await app.init();
});

it(`should be able to config from specific folder`, () => {
const config = app.get(DirectoryConfig);
expect(config.table.name).toEqual('table2');

const databaseConfig = app.get(DatabaseConfig);
expect(databaseConfig.port).toBe(3000);
});

afterEach(async () => {
await app?.close();
});
});
17 changes: 16 additions & 1 deletion tests/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DynamicModule, Module } from '@nestjs/common';
import { join } from 'path';
import { parse as parseYaml } from 'yaml';
import {
directoryLoader,
remoteLoader,
RemoteLoaderOptions,
TypedConfigModule,
Expand All @@ -11,7 +12,7 @@ import {
dotenvLoader,
DotenvLoaderOptions,
} from '../../lib/loader/dotenv-loader';
import { Config, TableConfig } from './config.model';
import { Config, DirectoryConfig, TableConfig } from './config.model';

const loadYaml = function loadYaml(filepath: string, content: string) {
try {
Expand Down Expand Up @@ -74,6 +75,20 @@ export class AppModule {
};
}

static withDirectory(): DynamicModule {
return {
module: AppModule,
imports: [
TypedConfigModule.forRoot({
schema: DirectoryConfig,
load: directoryLoader({
directory: join(__dirname, 'dir'),
}),
}),
],
};
}

static withRawModule(): DynamicModule {
return TypedConfigModule.forRoot({
schema: Config,
Expand Down
12 changes: 12 additions & 0 deletions tests/src/config.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,15 @@ export class Config {
@IsBoolean()
public readonly isAuthEnabled!: boolean;
}

export class DirectoryConfig {
@Type(() => DatabaseConfig)
@ValidateNested()
@IsDefined()
public readonly database!: DatabaseConfig;

@Type(() => TableConfig)
@ValidateNested()
@IsDefined()
public readonly table!: TableConfig;
}
5 changes: 5 additions & 0 deletions tests/src/dir/database.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
host = '127.0.0.1'
port = 3000

[table]
name = 'test'
1 change: 1 addition & 0 deletions tests/src/dir/table.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name = 'table2'

0 comments on commit 1ae405b

Please sign in to comment.