Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: esm json import #416

Merged
merged 10 commits into from Nov 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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