Skip to content

Commit 90f970a

Browse files
authored
fix: esm json import (#416)
1 parent cca1f6f commit 90f970a

File tree

6 files changed

+272
-5
lines changed

6 files changed

+272
-5
lines changed

lib/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
*/
99

1010
import { format } from 'util'
11-
import { readFileSync } from 'fs'
1211
import { normalize, resolve } from 'path'
1312
import { ArgsInput, Arguments, Parser, Options, DetailedArguments } from './yargs-parser-types.js'
1413
import { camelCase, decamelize, looksLikeNumber } from './string-utils.js'
1514
import { YargsParser } from './yargs-parser.js'
15+
import { readFileSync } from 'fs'
1616

1717
// See https://github.com/yargs/yargs-parser#supported-nodejs-versions for our
1818
// version support policy. The YARGS_MIN_NODE_VERSION is used for testing only.
@@ -42,7 +42,8 @@ const parser = new YargsParser({
4242
if (typeof require !== 'undefined') {
4343
return require(path)
4444
} else if (path.match(/\.json$/)) {
45-
return readFileSync(path, 'utf8')
45+
// Addresses: https://github.com/yargs/yargs/issues/2040
46+
return JSON.parse(readFileSync(path, 'utf8'))
4647
} else {
4748
throw Error('only .json config files are supported in ESM')
4849
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"fix": "standardx --fix '**/*.ts' && standardx --fix '**/*.js' && standardx --fix '**/*.cjs'",
2020
"pretest": "rimraf build && tsc -p tsconfig.test.json && cross-env NODE_ENV=test npm run build:cjs",
2121
"test": "c8 --reporter=text --reporter=html mocha test/*.cjs",
22+
"test:esm": "c8 --reporter=text --reporter=html mocha test/*.mjs",
2223
"test:browser": "start-server-and-test 'serve ./ -p 8080' http://127.0.0.1:8080/package.json 'node ./test/browser/yargs-test.cjs'",
2324
"pretest:typescript": "npm run pretest",
2425
"test:typescript": "c8 mocha ./build/test/typescript/*.js",
@@ -49,7 +50,7 @@
4950
"devDependencies": {
5051
"@types/chai": "^4.2.11",
5152
"@types/mocha": "^9.0.0",
52-
"@types/node": "^16.0.0",
53+
"@types/node": "^16.11.4",
5354
"@typescript-eslint/eslint-plugin": "^3.10.1",
5455
"@typescript-eslint/parser": "^3.10.1",
5556
"@wessberg/rollup-plugin-ts": "^1.2.28",

test/tscc/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"version": "0.0.0",
44
"dependencies": {
55
"@tscc/tscc": "^0.7.4",
6-
"@types/node": "^10.0.3"
6+
"@types/node": "^16.11.4"
77
}
88
}

test/tscc/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"module": "es2020",
34
"target": "es2017",
45
"moduleResolution": "node"
56
},

test/yargs-parser.mjs

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import { should, expect } from 'chai'
2+
import parser from '../build/lib/index.js'
3+
import path from 'path'
4+
import { fileURLToPath } from 'url'
5+
import { readFileSync } from 'fs'
6+
7+
should()
8+
9+
describe('yargs-parser (esm)', function () {
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
11+
const jsonPath = path.resolve(__dirname, './fixtures/config.json')
12+
describe('config', function () {
13+
it('should load options and values from default config if specified', function () {
14+
const argv = parser(['--foo', 'bar'], {
15+
alias: {
16+
z: 'zoom'
17+
},
18+
default: {
19+
settings: jsonPath
20+
},
21+
config: 'settings'
22+
})
23+
24+
argv.should.have.property('herp', 'derp')
25+
argv.should.have.property('zoom', 55)
26+
argv.should.have.property('foo').and.deep.equal('bar')
27+
})
28+
29+
it('should use value from config file, if argv value is using default value', function () {
30+
const argv = parser([], {
31+
alias: {
32+
z: 'zoom'
33+
},
34+
config: ['settings'],
35+
default: {
36+
settings: jsonPath,
37+
foo: 'banana'
38+
}
39+
})
40+
41+
argv.should.have.property('herp', 'derp')
42+
argv.should.have.property('zoom', 55)
43+
argv.should.have.property('foo').and.deep.equal('baz')
44+
})
45+
46+
it('should combine values from config file and argv, if argv value is an array', function () {
47+
const argv = parser(['--foo', 'bar'], {
48+
config: ['settings'],
49+
array: ['foo'],
50+
default: {
51+
settings: jsonPath
52+
},
53+
configuration: {
54+
'combine-arrays': true
55+
}
56+
})
57+
58+
argv.should.have.property('foo').and.deep.equal(['bar', 'baz'])
59+
})
60+
61+
it('should use value from config file, if argv key is a boolean', function () {
62+
const argv = parser([], {
63+
config: ['settings'],
64+
default: {
65+
settings: jsonPath
66+
},
67+
boolean: ['truthy']
68+
})
69+
70+
argv.should.have.property('truthy', true)
71+
})
72+
73+
it('should use value from cli, if cli overrides boolean argv key', function () {
74+
const argv = parser(['--no-truthy'], {
75+
config: ['settings'],
76+
default: {
77+
settings: jsonPath
78+
},
79+
boolean: ['truthy']
80+
})
81+
82+
argv.should.have.property('truthy', false)
83+
})
84+
85+
it('should use cli value, if cli value is set and both cli and default value match', function () {
86+
const argv = parser(['--foo', 'banana'], {
87+
alias: {
88+
z: 'zoom'
89+
},
90+
config: ['settings'],
91+
default: {
92+
settings: jsonPath,
93+
foo: 'banana'
94+
}
95+
})
96+
97+
argv.should.have.property('herp', 'derp')
98+
argv.should.have.property('zoom', 55)
99+
argv.should.have.property('foo').and.deep.equal('banana')
100+
})
101+
102+
it("should allow config to be set as flag in 'option'", function () {
103+
const argv = parser(['--settings', jsonPath, '--foo', 'bar'], {
104+
alias: {
105+
z: 'zoom'
106+
},
107+
config: ['settings']
108+
})
109+
110+
argv.should.have.property('herp', 'derp')
111+
argv.should.have.property('zoom', 55)
112+
argv.should.have.property('foo').and.deep.equal('bar')
113+
})
114+
115+
// for esm, only support importing json files
116+
it('should fail to load options and values from a JS file when config has .js extention', function () {
117+
const jsPath = path.resolve(__dirname, './fixtures/settings.cjs')
118+
const argv = parser.detailed(['--settings', jsPath, '--foo', 'bar'], {
119+
config: ['settings']
120+
})
121+
122+
argv.error.message.should.include('Invalid JSON config file')
123+
})
124+
125+
it('should raise an appropriate error if JSON file is not found', function () {
126+
const argv = parser.detailed(['--settings', 'fake.json', '--foo', 'bar'], {
127+
alias: {
128+
z: 'zoom'
129+
},
130+
config: ['settings']
131+
})
132+
133+
argv.error.message.should.equal('Invalid JSON config file: fake.json')
134+
})
135+
136+
// see: https://github.com/bcoe/yargs/issues/172
137+
it('should not raise an exception if config file is set as default argument value', function () {
138+
const argv = parser.detailed([], {
139+
default: {
140+
config: 'foo.json'
141+
},
142+
config: ['config']
143+
})
144+
145+
expect(argv.error).to.equal(null)
146+
})
147+
148+
it('should load nested options from config file', function () {
149+
const jsonPath = path.resolve(__dirname, './fixtures/nested_config.json')
150+
const argv = parser(['--settings', jsonPath, '--nested.foo', 'bar'], {
151+
config: ['settings']
152+
})
153+
154+
argv.should.have.property('a', 'a')
155+
argv.should.have.property('b', 'b')
156+
argv.should.have.property('nested').and.deep.equal({
157+
foo: 'bar',
158+
bar: 'bar'
159+
})
160+
})
161+
162+
it('should use nested value from config file, if argv value is using default value', function () {
163+
const jsonPath = path.resolve(__dirname, './fixtures/nested_config.json')
164+
const argv = parser(['--settings', jsonPath], {
165+
config: ['settings'],
166+
default: {
167+
'nested.foo': 'banana'
168+
}
169+
})
170+
171+
argv.should.have.property('a', 'a')
172+
argv.should.have.property('b', 'b')
173+
argv.should.have.property('nested').and.deep.equal({
174+
foo: 'baz',
175+
bar: 'bar'
176+
})
177+
})
178+
179+
it('allows a custom parsing function to be provided', function () {
180+
const jsPath = path.resolve(__dirname, './fixtures/config.txt')
181+
const argv = parser(['--settings', jsPath, '--foo', 'bar'], {
182+
config: {
183+
settings: function (configPath) {
184+
// as an example, parse an environment
185+
// variable style config:
186+
// FOO=99
187+
// BATMAN=grumpy
188+
const config = {}
189+
const txt = readFileSync(configPath, 'utf-8')
190+
txt.split(/\r?\n/).forEach(function (l) {
191+
const kv = l.split('=')
192+
config[kv[0].toLowerCase()] = kv[1]
193+
})
194+
return config
195+
}
196+
}
197+
})
198+
199+
argv.batman.should.equal('grumpy')
200+
argv.awesome.should.equal('banana')
201+
argv.foo.should.equal('bar')
202+
})
203+
204+
it('allows a custom parsing function to be provided as an alias', function () {
205+
const jsPath = path.resolve(__dirname, './fixtures/config.json')
206+
const argv = parser(['--settings', jsPath, '--foo', 'bar'], {
207+
config: {
208+
s: function (configPath) {
209+
return JSON.parse(readFileSync(configPath, 'utf-8'))
210+
}
211+
},
212+
alias: {
213+
s: ['settings']
214+
}
215+
})
216+
217+
argv.should.have.property('herp', 'derp')
218+
argv.should.have.property('foo', 'bar')
219+
})
220+
221+
it('outputs an error returned by the parsing function', function () {
222+
const argv = parser.detailed(['--settings=./package.json'], {
223+
config: {
224+
settings: function (configPath) {
225+
return Error('someone set us up the bomb')
226+
}
227+
}
228+
})
229+
230+
argv.error.message.should.equal('someone set us up the bomb')
231+
})
232+
233+
it('outputs an error if thrown by the parsing function', function () {
234+
const argv = parser.detailed(['--settings=./package.json'], {
235+
config: {
236+
settings: function (configPath) {
237+
throw Error('someone set us up the bomb')
238+
}
239+
}
240+
})
241+
242+
argv.error.message.should.equal('someone set us up the bomb')
243+
})
244+
245+
it('should not pollute the prototype', function () {
246+
const argv = parser(['--foo', 'bar'], {
247+
alias: {
248+
z: 'zoom'
249+
},
250+
default: {
251+
settings: jsonPath
252+
},
253+
config: 'settings'
254+
})
255+
256+
argv.should.have.property('herp', 'derp')
257+
argv.should.have.property('zoom', 55)
258+
argv.should.have.property('foo').and.deep.equal('bar')
259+
260+
expect({}.bbb).to.equal(undefined)
261+
expect({}.aaa).to.equal(undefined)
262+
})
263+
})
264+
})

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"sourceMap": false,
77
"target": "es2017",
88
"moduleResolution": "node",
9-
"module": "es2015"
9+
"module": "es2020"
1010
},
1111
"include": [
1212
"lib/**/*.ts"

0 commit comments

Comments
 (0)