Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix: esm json import (#416)
  • Loading branch information
jly36963 committed Nov 14, 2021
1 parent cca1f6f commit 90f970a
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 5 deletions.
5 changes: 3 additions & 2 deletions lib/index.ts
Expand Up @@ -8,11 +8,11 @@
*/

import { format } from 'util'
import { readFileSync } from 'fs'
import { normalize, resolve } from 'path'
import { ArgsInput, Arguments, Parser, Options, DetailedArguments } from './yargs-parser-types.js'
import { camelCase, decamelize, looksLikeNumber } from './string-utils.js'
import { YargsParser } from './yargs-parser.js'
import { readFileSync } from 'fs'

// See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our
// version support policy. The YARGS_MIN_NODE_VERSION is used for testing only.
Expand Down Expand Up @@ -42,7 +42,8 @@ const parser = new YargsParser({
if (typeof require !== 'undefined') {
return require(path)
} else if (path.match(/\.json$/)) {
return readFileSync(path, 'utf8')
// Addresses: https://github.com/yargs/yargs/issues/2040
return JSON.parse(readFileSync(path, 'utf8'))
} else {
throw Error('only .json config files are supported in ESM')
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -19,6 +19,7 @@
"fix": "standardx --fix '**/*.ts' && standardx --fix '**/*.js' && standardx --fix '**/*.cjs'",
"pretest": "rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs",
"test": "c8 --reporter=text --reporter=html mocha test/*.cjs",
"test:esm": "c8 --reporter=text --reporter=html mocha test/*.mjs",
"test:browser": "start-server-and-test 'serve ./ -p 8080' http://127.0.0.1:8080/package.json 'node ./test/browser/yargs-test.cjs'",
"pretest:typescript": "npm run pretest",
"test:typescript": "c8 mocha ./build/test/typescript/*.js",
Expand Down Expand Up @@ -49,7 +50,7 @@
"devDependencies": {
"@types/chai": "^4.2.11",
"@types/mocha": "^9.0.0",
"@types/node": "^16.0.0",
"@types/node": "^16.11.4",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"@wessberg/rollup-plugin-ts": "^1.2.28",
Expand Down
2 changes: 1 addition & 1 deletion test/tscc/package.json
Expand Up @@ -3,6 +3,6 @@
"version": "0.0.0",
"dependencies": {
"@tscc/tscc": "^0.7.4",
"@types/node": "^10.0.3"
"@types/node": "^16.11.4"
}
}
1 change: 1 addition & 0 deletions test/tscc/tsconfig.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"module": "es2020",
"target": "es2017",
"moduleResolution": "node"
},
Expand Down
264 changes: 264 additions & 0 deletions test/yargs-parser.mjs
@@ -0,0 +1,264 @@
import { should, expect } from 'chai'
import parser from '../build/lib/index.js'
import path from 'path'
import { fileURLToPath } from 'url'
import { readFileSync } from 'fs'

should()

describe('yargs-parser (esm)', function () {
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const jsonPath = path.resolve(__dirname, './fixtures/config.json')
describe('config', function () {
it('should load options and values from default config if specified', function () {
const argv = parser(['--foo', 'bar'], {
alias: {
z: 'zoom'
},
default: {
settings: jsonPath
},
config: 'settings'
})

argv.should.have.property('herp', 'derp')
argv.should.have.property('zoom', 55)
argv.should.have.property('foo').and.deep.equal('bar')
})

it('should use value from config file, if argv value is using default value', function () {
const argv = parser([], {
alias: {
z: 'zoom'
},
config: ['settings'],
default: {
settings: jsonPath,
foo: 'banana'
}
})

argv.should.have.property('herp', 'derp')
argv.should.have.property('zoom', 55)
argv.should.have.property('foo').and.deep.equal('baz')
})

it('should combine values from config file and argv, if argv value is an array', function () {
const argv = parser(['--foo', 'bar'], {
config: ['settings'],
array: ['foo'],
default: {
settings: jsonPath
},
configuration: {
'combine-arrays': true
}
})

argv.should.have.property('foo').and.deep.equal(['bar', 'baz'])
})

it('should use value from config file, if argv key is a boolean', function () {
const argv = parser([], {
config: ['settings'],
default: {
settings: jsonPath
},
boolean: ['truthy']
})

argv.should.have.property('truthy', true)
})

it('should use value from cli, if cli overrides boolean argv key', function () {
const argv = parser(['--no-truthy'], {
config: ['settings'],
default: {
settings: jsonPath
},
boolean: ['truthy']
})

argv.should.have.property('truthy', false)
})

it('should use cli value, if cli value is set and both cli and default value match', function () {
const argv = parser(['--foo', 'banana'], {
alias: {
z: 'zoom'
},
config: ['settings'],
default: {
settings: jsonPath,
foo: 'banana'
}
})

argv.should.have.property('herp', 'derp')
argv.should.have.property('zoom', 55)
argv.should.have.property('foo').and.deep.equal('banana')
})

it("should allow config to be set as flag in 'option'", function () {
const argv = parser(['--settings', jsonPath, '--foo', 'bar'], {
alias: {
z: 'zoom'
},
config: ['settings']
})

argv.should.have.property('herp', 'derp')
argv.should.have.property('zoom', 55)
argv.should.have.property('foo').and.deep.equal('bar')
})

// for esm, only support importing json files
it('should fail to load options and values from a JS file when config has .js extention', function () {
const jsPath = path.resolve(__dirname, './fixtures/settings.cjs')
const argv = parser.detailed(['--settings', jsPath, '--foo', 'bar'], {
config: ['settings']
})

argv.error.message.should.include('Invalid JSON config file')
})

it('should raise an appropriate error if JSON file is not found', function () {
const argv = parser.detailed(['--settings', 'fake.json', '--foo', 'bar'], {
alias: {
z: 'zoom'
},
config: ['settings']
})

argv.error.message.should.equal('Invalid JSON config file: fake.json')
})

// see: https://github.com/bcoe/yargs/issues/172
it('should not raise an exception if config file is set as default argument value', function () {
const argv = parser.detailed([], {
default: {
config: 'foo.json'
},
config: ['config']
})

expect(argv.error).to.equal(null)
})

it('should load nested options from config file', function () {
const jsonPath = path.resolve(__dirname, './fixtures/nested_config.json')
const argv = parser(['--settings', jsonPath, '--nested.foo', 'bar'], {
config: ['settings']
})

argv.should.have.property('a', 'a')
argv.should.have.property('b', 'b')
argv.should.have.property('nested').and.deep.equal({
foo: 'bar',
bar: 'bar'
})
})

it('should use nested value from config file, if argv value is using default value', function () {
const jsonPath = path.resolve(__dirname, './fixtures/nested_config.json')
const argv = parser(['--settings', jsonPath], {
config: ['settings'],
default: {
'nested.foo': 'banana'
}
})

argv.should.have.property('a', 'a')
argv.should.have.property('b', 'b')
argv.should.have.property('nested').and.deep.equal({
foo: 'baz',
bar: 'bar'
})
})

it('allows a custom parsing function to be provided', function () {
const jsPath = path.resolve(__dirname, './fixtures/config.txt')
const argv = parser(['--settings', jsPath, '--foo', 'bar'], {
config: {
settings: function (configPath) {
// as an example, parse an environment
// variable style config:
// FOO=99
// BATMAN=grumpy
const config = {}
const txt = readFileSync(configPath, 'utf-8')
txt.split(/\r?\n/).forEach(function (l) {
const kv = l.split('=')
config[kv[0].toLowerCase()] = kv[1]
})
return config
}
}
})

argv.batman.should.equal('grumpy')
argv.awesome.should.equal('banana')
argv.foo.should.equal('bar')
})

it('allows a custom parsing function to be provided as an alias', function () {
const jsPath = path.resolve(__dirname, './fixtures/config.json')
const argv = parser(['--settings', jsPath, '--foo', 'bar'], {
config: {
s: function (configPath) {
return JSON.parse(readFileSync(configPath, 'utf-8'))
}
},
alias: {
s: ['settings']
}
})

argv.should.have.property('herp', 'derp')
argv.should.have.property('foo', 'bar')
})

it('outputs an error returned by the parsing function', function () {
const argv = parser.detailed(['--settings=./package.json'], {
config: {
settings: function (configPath) {
return Error('someone set us up the bomb')
}
}
})

argv.error.message.should.equal('someone set us up the bomb')
})

it('outputs an error if thrown by the parsing function', function () {
const argv = parser.detailed(['--settings=./package.json'], {
config: {
settings: function (configPath) {
throw Error('someone set us up the bomb')
}
}
})

argv.error.message.should.equal('someone set us up the bomb')
})

it('should not pollute the prototype', function () {
const argv = parser(['--foo', 'bar'], {
alias: {
z: 'zoom'
},
default: {
settings: jsonPath
},
config: 'settings'
})

argv.should.have.property('herp', 'derp')
argv.should.have.property('zoom', 55)
argv.should.have.property('foo').and.deep.equal('bar')

expect({}.bbb).to.equal(undefined)
expect({}.aaa).to.equal(undefined)
})
})
})
2 changes: 1 addition & 1 deletion tsconfig.json
Expand Up @@ -6,7 +6,7 @@
"sourceMap": false,
"target": "es2017",
"moduleResolution": "node",
"module": "es2015"
"module": "es2020"
},
"include": [
"lib/**/*.ts"
Expand Down

0 comments on commit 90f970a

Please sign in to comment.