diff --git a/packages/uniconfig-core/src/config.js b/packages/uniconfig-core/src/config.js index 98213308..d4298e4d 100644 --- a/packages/uniconfig-core/src/config.js +++ b/packages/uniconfig-core/src/config.js @@ -105,7 +105,7 @@ export default class Config { evaluate () { const data = this.data = {} each(this.input.data, (value, key)=> { - if (/^\$.+:.+/.test(value)) { + if (/^\$.+:.*/.test(value)) { const [sourceName, path] = value.slice(1).split(':') const source = this.context.source.get(sourceName) diff --git a/packages/uniconfig-core/src/interface.js b/packages/uniconfig-core/src/interface.js index d317c187..7cd64cc4 100644 --- a/packages/uniconfig-core/src/interface.js +++ b/packages/uniconfig-core/src/interface.js @@ -102,7 +102,7 @@ export interface ISource { on(event: string, listener: IEventListener): ISource, emit(event: string, data?: IAny): boolean, - get(path: string): IAny, + get(path?: string): IAny, has(path: string): boolean, } diff --git a/packages/uniconfig-core/src/source/source.js b/packages/uniconfig-core/src/source/source.js index ffca06d9..868abcdb 100644 --- a/packages/uniconfig-core/src/source/source.js +++ b/packages/uniconfig-core/src/source/source.js @@ -97,10 +97,12 @@ export default class Source implements ISource { return this } - get (path: string): IAny { + get (path?: string): IAny { this.constructor.assertReady(this) - return get(this.data, path) + return path + ? get(this.data, path) + : this.data } has (path: string): boolean { diff --git a/packages/uniconfig-plugin-api-file/.babelrc b/packages/uniconfig-plugin-api-file/.babelrc new file mode 100644 index 00000000..33a624a3 --- /dev/null +++ b/packages/uniconfig-plugin-api-file/.babelrc @@ -0,0 +1,35 @@ +{ + "comments": false, + "ignore": [ + "interface.js" + ], + "presets": [ + "@babel/preset-flow" + ], + "env": { + "production": { + "presets": [ + "@babel/preset-flow", + ["@babel/preset-env", { + "modules": false + }] + ], + "plugins": [ + "@babel/plugin-transform-modules-commonjs", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-object-rest-spread" + ] + }, + "test": { + "presets": [ + "@babel/preset-flow", + "@babel/preset-env" + ], + "plugins": [ + "@babel/plugin-transform-modules-commonjs", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-object-rest-spread" + ] + } + } +} diff --git a/packages/uniconfig-plugin-api-file/README.md b/packages/uniconfig-plugin-api-file/README.md new file mode 100644 index 00000000..81865667 --- /dev/null +++ b/packages/uniconfig-plugin-api-file/README.md @@ -0,0 +1,44 @@ +# @qiwi/uniconfig-plugin-api-file + +Uniconfig File API plugin + +## Install +```bash + npm i @qiwi/uniconfig-plugin-api-file + yarn add @qiwi/uniconfig-plugin-api-fil +``` + + +## Usage +```javascript +import uniconfig, {rollupPlugin} from '@qiwi/uniconfig-core' +import uniconfigFileApiPlugin from '@qiwi/uniconfig-plugin-api-file' +import uniconfigJsonParserPlugin from '@qiwi/uniconfig-plugin-parser-json' + +rollupPlugin(uniconfigFileApiPlugin) +rollupPlugin(uniconfigJsonParserPlugin) + +const target = './foo.json' +/** foo.json content: +{ + "foo": "bar" +} +*/ + +const config = uniconfig({ + data: { + someParam: '$fromFile:foo' + }, + source: { + fromFile: { + target, + api: 'file', + parser: 'json' + } + } +}, { + mode: 'sync' +}) + +config.get('someParam') // "bar" +``` diff --git a/packages/uniconfig-plugin-api-file/package.json b/packages/uniconfig-plugin-api-file/package.json new file mode 100644 index 00000000..29f97958 --- /dev/null +++ b/packages/uniconfig-plugin-api-file/package.json @@ -0,0 +1,38 @@ +{ + "name": "@qiwi/uniconfig-plugin-api-file", + "version": "1.16.0", + "description": "Uniconfig file api (fs api) plugin", + "main": "dist/es6/index.js", + "scripts": { + "jest": "BABEL_ENV=test NODE_ENV=test jest -w 1 --detectOpenHandles --config jest.config.json", + "build_es6": "flow-remove-types src/ --out-dir dist/es6/", + "build_es5": "BABEL_ENV=production babel src --out-dir dist/es5/", + "build_bundle": "parcel build dist/es6/index.js --out-dir dist/bundle --experimental-scope-hoisting", + "build": "rm -rf dist && npm run build_es6 && npm run build_es5 && npm run build_bundle" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/qiwi/uniconfig.git" + }, + "keywords": [ + "universal config", + "unified config" + ], + "author": "Qiwi ", + "license": "MIT", + "bugs": { + "url": "https://github.com/qiwi/uniconfig/issues" + }, + "homepage": "https://github.com/qiwi/uniconfig#readme", + "dependencies": { + "@qiwi/uniconfig-core": "^1.0.0" + }, + "devDependencies": { + "@babel/core": "^7.1.2" + }, + "files": [ + "README.md", + "CHANGELOG.md", + "dist/" + ] +} diff --git a/packages/uniconfig-plugin-api-file/src/index.js b/packages/uniconfig-plugin-api-file/src/index.js new file mode 100644 index 00000000..18e7f0bb --- /dev/null +++ b/packages/uniconfig-plugin-api-file/src/index.js @@ -0,0 +1,48 @@ +// @flow + +import type {IApi, IAny, IResolve, IReject, IContext, IPlugin} from '../../uniconfig-core/src/interface' +export type IFsOpts = { + encoding: string, + flag?: string +} + +export interface IFileApi extends IApi { + readSync (target: string, opts?: ?IFsOpts): IAny, + read (target: string, opts?: ?IFsOpts): Promise +} + +export const DEFAULT_OPTS: IFsOpts = { + encoding: 'utf8' +} + +export const type = 'file' + +export const api: IFileApi = { + readSync (target: string, opts?: ?IFsOpts): IAny { + return require('fs').readFileSync(target, processOpts(opts)) + }, + read (target: string, opts?: ?IFsOpts): Promise { + return new Promise((resolve: IResolve, reject: IReject): void => { + require('fs').readFile(target, processOpts(opts), (err: IAny | null, data: IAny) => { + if (err) { + reject(err) + } else { + resolve(data) + } + }) + }) + } +} + +export default ({ + rollup(context: IContext): void { + context.api.add(type, api) + }, + rollback(context: IContext): void { + context.api.remove(type) + }, +}: IPlugin) + +export function processOpts (opts?: ?IFsOpts): IFsOpts { + return { ...DEFAULT_OPTS, ...opts } +} diff --git a/packages/uniconfig-plugin-api-file/test/foobar.json b/packages/uniconfig-plugin-api-file/test/foobar.json new file mode 100644 index 00000000..9f5dd4e3 --- /dev/null +++ b/packages/uniconfig-plugin-api-file/test/foobar.json @@ -0,0 +1 @@ +{"foo":"bar"} \ No newline at end of file diff --git a/packages/uniconfig-plugin-api-file/test/index.js b/packages/uniconfig-plugin-api-file/test/index.js new file mode 100644 index 00000000..f52b6f86 --- /dev/null +++ b/packages/uniconfig-plugin-api-file/test/index.js @@ -0,0 +1,71 @@ +import plugin, {api} from '../src' +import path from 'path' +import {ASYNC, SYNC} from '@qiwi/uniconfig-core/src/source/source' +import {Config, rollupPlugin, rollbackPlugin} from '@qiwi/uniconfig-core/src' + +describe('uniconfig-plugin-api-file', () => { + const target = path.resolve(__dirname, './foobar.json') + + describe('#readSync', () => { + it('gets file data as string', () => { + expect(api.readSync(target)).toEqual(JSON.stringify({ foo: 'bar' })) + }) + + it('gets err as result', () => { + expect(() => api.readSync('bazqux')).toThrow('ENOENT: no such file or directory, open \'bazqux\'') + }) + }) + + describe('#read', () => { + it('resolves promise with string', () => { + return expect(api.read(target)).resolves.toEqual(JSON.stringify({ foo: 'bar' })) + }) + + it('rejects promise with err', () => { + return expect(api.read('bazqux')).rejects.toThrow('ENOENT: no such file or directory, open \'bazqux\'') + }) + }) + + describe('integration', () => { + beforeAll(() => { + rollupPlugin(plugin) + }) + + afterAll(() => { + rollbackPlugin(plugin) + }) + + const input = { + data: { + someParam: '$fromFile:' + }, + source: { + fromFile: { + target, + api: 'file' + } + } + } + + it('sync', () => { + const mode = SYNC + const opts = {mode} + const config = new Config(input, opts) + + expect(config.context.source.get('fromFile').get()).toBe('{"foo":"bar"}') + expect(config.get('someParam')).toBe('{"foo":"bar"}') + }) + + it('async', done => { + const mode = ASYNC + const opts = {mode} + const config = new Config(input, opts) + + config.on('CONFIG_READY', () => { + expect(config.context.source.get('fromFile').get()).toBe('{"foo":"bar"}') + expect(config.get('someParam')).toBe('{"foo":"bar"}') + done() + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/uniconfig-plugin-json/src/index.js b/packages/uniconfig-plugin-json/src/index.js index 6fbda432..61ebfc76 100644 --- a/packages/uniconfig-plugin-json/src/index.js +++ b/packages/uniconfig-plugin-json/src/index.js @@ -2,11 +2,11 @@ import type {IContext, IPlugin, IAny, IParser, IParse} from '../../uniconfig-core/src/interface' -const type = 'json' +export const type = 'json' export const parse: IParse = (data: string): IAny => JSON.parse(data) -const parser: IParser = {parse} +export const parser: IParser = {parse} export default ({ rollup(context: IContext): void {