js-build-tools 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -2
- package/dist/build.js +63 -62
- package/dist/build.min.js +1 -1
- package/dist/bundle.js +40 -39
- package/dist/bundle.min.js +1 -1
- package/dist/bundleMinify.js +18 -17
- package/dist/bundleMinify.min.js +1 -1
- package/dist/copyFonts.js +13 -12
- package/dist/copyFonts.min.js +1 -1
- package/dist/default.js +15 -14
- package/dist/default.min.js +1 -1
- package/dist/distMinify.js +13 -12
- package/dist/distMinify.min.js +1 -1
- package/dist/partials/addToReadme.js +25 -24
- package/dist/partials/beginWatcher.js +13 -12
- package/dist/partials/clean.js +15 -14
- package/dist/partials/distFor.js +22 -21
- package/dist/partials/distForSrc.js +12 -11
- package/dist/partials/distSeries.js +20 -19
- package/dist/partials/imagesFor.js +43 -42
- package/dist/partials/neostandardLint.js +130 -111
- package/dist/partials/readmeTemplate.js +15 -14
- package/dist/partials/runOnChange.js +29 -28
- package/dist/partials/sassFor.js +29 -28
- package/dist/partials/tsFor.js +71 -70
- package/dist/partials/typeDocsFor.js +203 -202
- package/dist/sourceLint.js +26 -24
- package/dist/sourceLint.min.js +1 -1
- package/dist/test-helpers/neostandardLintWithRealNeostandard.js +49 -47
- package/dist/test-helpers/setUp.js +95 -94
- package/dist/testFull.js +20 -19
- package/dist/testFull.min.js +1 -1
- package/dist/testHelpers.js +11 -10
- package/dist/testHelpers.min.js +1 -1
- package/dist/testQuick.js +22 -21
- package/dist/testQuick.min.js +1 -1
- package/dist/utilities/dotNotate.js +48 -46
- package/dist/utilities/getBundleConfigs.js +28 -27
- package/dist/watchTest.js +16 -15
- package/dist/watchTest.min.js +1 -1
- package/functions/bundle.mjs +2 -2
- package/functions/partials/addToReadme.mjs +1 -1
- package/functions/partials/neostandardLint.mjs +19 -7
- package/functions/partials/neostandardLint.test.mjs +9 -1
- package/functions/sourceLint.mjs +1 -1
- package/functions/sourceLint.test.mjs +24 -0
- package/functions/test-helpers/neostandardLintWithRealNeostandard.mjs +7 -5
- package/functions/utilities/dotNotate.mjs +3 -1
- package/functions/utilities/dotNotate.test.mjs +34 -0
- package/functions/utilities/getBundleConfigs.mjs +1 -1
- package/gulp.config.mjs +14 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import { importModule } from './loadPeer.mjs'
|
|
|
6
6
|
|
|
7
7
|
// neostandard (js-build-tools' chosen style - a maintained, TypeScript-aware successor to `standard`, see
|
|
8
8
|
// https://github.com/neostandard/neostandard) ships ESM-only, see importModule for how it is imported.
|
|
9
|
-
const importNeostandard = () => importModule('neostandard')
|
|
9
|
+
const importNeostandard = () => importModule('neostandard')
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Read the `env` a project already declared for Standard (the `standard` field in its package.json), so a project
|
|
@@ -23,20 +23,30 @@ const readEnv = cwd => {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Build an ESLint instance configured with neostandard, honoring the project's own package.json `standard.env
|
|
26
|
+
* Build an ESLint instance configured with neostandard, honoring the project's own package.json `standard.env`,
|
|
27
|
+
* plus an optional layer of rule overrides (see gulp.config.mjs's `lint.rules`) for a rule that is wrong for this
|
|
28
|
+
* project specifically rather than something to fix in its code - for example a test fixture whose content
|
|
29
|
+
* neostandard mistakes for a real syntax problem. Scoped to the same files neostandard itself lints (its own
|
|
30
|
+
* `defaultFilePatterns.all`), per neostandard's own guidance: an unscoped rule-tweak layer would otherwise apply
|
|
31
|
+
* to every lintable file, including ones from an unrelated plugin this project never defined.
|
|
27
32
|
* @param {Object} options
|
|
28
33
|
* @param {string} options.cwd
|
|
29
34
|
* @param {boolean} options.ts
|
|
30
35
|
* @param {boolean} options.fix
|
|
36
|
+
* @param {Object<string, *>} [options.rules={}]
|
|
31
37
|
* @returns {Promise<import('eslint').ESLint>}
|
|
32
38
|
*/
|
|
33
|
-
const buildEslint = async ({ cwd, ts, fix }) => {
|
|
34
|
-
const neostandard = await importNeostandard()
|
|
39
|
+
const buildEslint = async ({ cwd, ts, fix, rules = {} }) => {
|
|
40
|
+
const { neostandard, defaultFilePatterns } = await importNeostandard()
|
|
41
|
+
const baseConfig = neostandard({ ts, env: readEnv(cwd) })
|
|
42
|
+
if (Object.keys(rules).length) {
|
|
43
|
+
baseConfig.push({ files: defaultFilePatterns.all, rules })
|
|
44
|
+
}
|
|
35
45
|
return new ESLint({
|
|
36
46
|
cwd,
|
|
37
47
|
fix,
|
|
38
48
|
overrideConfigFile: true,
|
|
39
|
-
baseConfig
|
|
49
|
+
baseConfig
|
|
40
50
|
})
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -67,13 +77,15 @@ const printFileReport = (file, result) => {
|
|
|
67
77
|
* @param {Object} [options={}]
|
|
68
78
|
* @param {boolean} [options.fix=true] - Apply neostandard's automatic fixes to the file contents.
|
|
69
79
|
* @param {boolean} [options.ts=false] - Lint `.ts` files too, with type-aware rules (see neostandard's `ts` option).
|
|
80
|
+
* @param {Object<string, *>} [options.rules={}] - ESLint rule overrides layered on top of neostandard's own rules
|
|
81
|
+
* (see gulp.config.mjs's `lint.rules`).
|
|
70
82
|
* @param {function(Object): Promise<{lintText: Function}>} [loadEslint] - Resolves an ESLint-shaped linter. Real
|
|
71
83
|
* callers should never need to pass this - it exists so tests can supply a stand-in, since this project's
|
|
72
84
|
* CommonJS-transform-based Jest setup cannot execute a real dynamic import() of neostandard (ESM-only).
|
|
73
85
|
* @returns {import('stream').Transform}
|
|
74
86
|
*/
|
|
75
87
|
export const neostandardLint = (
|
|
76
|
-
{ fix = true, ts = false } = {},
|
|
88
|
+
{ fix = true, ts = false, rules = {} } = {},
|
|
77
89
|
loadEslint = buildEslint
|
|
78
90
|
) => {
|
|
79
91
|
const totals = { errors: 0, warnings: 0 }
|
|
@@ -89,7 +101,7 @@ export const neostandardLint = (
|
|
|
89
101
|
return
|
|
90
102
|
}
|
|
91
103
|
if (!eslintPromise) {
|
|
92
|
-
eslintPromise = loadEslint({ cwd: file.cwd, ts, fix })
|
|
104
|
+
eslintPromise = loadEslint({ cwd: file.cwd, ts, fix, rules })
|
|
93
105
|
}
|
|
94
106
|
eslintPromise
|
|
95
107
|
.then(eslint => eslint.lintText(file.contents.toString(), { filePath: file.path }))
|
|
@@ -61,7 +61,15 @@ describe('neostandardLint', () => {
|
|
|
61
61
|
expect.assertions(1)
|
|
62
62
|
const loadEslint = jest.fn(() => Promise.resolve(fakeEslint(() => [{ messages: [], errorCount: 0, warningCount: 0 }])))
|
|
63
63
|
await run(neostandardLint({ fix: false, ts: true }, loadEslint), [makeFile(Buffer.from('x'))])
|
|
64
|
-
expect(loadEslint).toHaveBeenCalledWith({ cwd: '/project', ts: true, fix: false })
|
|
64
|
+
expect(loadEslint).toHaveBeenCalledWith({ cwd: '/project', ts: true, fix: false, rules: {} })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('passes rule overrides through to the loader', async () => {
|
|
68
|
+
expect.assertions(1)
|
|
69
|
+
const loadEslint = jest.fn(() => Promise.resolve(fakeEslint(() => [{ messages: [], errorCount: 0, warningCount: 0 }])))
|
|
70
|
+
const rules = { 'no-template-curly-in-string': 'off' }
|
|
71
|
+
await run(neostandardLint({ rules }, loadEslint), [makeFile(Buffer.from('x'))])
|
|
72
|
+
expect(loadEslint).toHaveBeenCalledWith({ cwd: '/project', ts: false, fix: true, rules })
|
|
65
73
|
})
|
|
66
74
|
|
|
67
75
|
test('passes a file through untouched when the project ignores it (ESLint returns no results)', async () => {
|
package/functions/sourceLint.mjs
CHANGED
|
@@ -51,6 +51,30 @@ describe('sourceLint', () => {
|
|
|
51
51
|
})
|
|
52
52
|
}, 20000)
|
|
53
53
|
|
|
54
|
+
test('lint.rules actually suppresses a real neostandard finding', done => {
|
|
55
|
+
const srcPath = gulpConfig.get('srcPath')
|
|
56
|
+
// A fixture string containing literal ${...} text (simulating real module source, not an accidental
|
|
57
|
+
// non-template string) - neostandard's no-template-curly-in-string rule flags this as an error by default.
|
|
58
|
+
const fixtureContents = 'const fixture = \'import(`imagemin-${pluginName}`)\'\n\nmodule.exports = fixture\n'
|
|
59
|
+
fs.writeFileSync(`${srcPath}/fixture.js`, fixtureContents)
|
|
60
|
+
gulpConfig.set('lint.rules', { 'no-template-curly-in-string': 'off' })
|
|
61
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
|
|
62
|
+
expect.assertions(1)
|
|
63
|
+
sourceLint()
|
|
64
|
+
.on('finish', () => {
|
|
65
|
+
gulpConfig.set('lint.rules', {})
|
|
66
|
+
expect(logSpy.mock.calls.flat().join('\n')).not.toMatch(/no-template-curly-in-string|template string expression/)
|
|
67
|
+
logSpy.mockRestore()
|
|
68
|
+
done()
|
|
69
|
+
})
|
|
70
|
+
.on('error', error => {
|
|
71
|
+
gulpConfig.set('lint.rules', {})
|
|
72
|
+
logSpy.mockRestore()
|
|
73
|
+
console.error('Encountered error', error)
|
|
74
|
+
done(error)
|
|
75
|
+
})
|
|
76
|
+
}, 20000)
|
|
77
|
+
|
|
54
78
|
test('also lints .ts source when typescript.enabled', done => {
|
|
55
79
|
const srcPath = gulpConfig.get('srcPath')
|
|
56
80
|
gulpConfig.set('typescript.enabled', true)
|
|
@@ -6,9 +6,11 @@ import path from 'node:path'
|
|
|
6
6
|
// genuine neostandard behavior run the actual package in a plain Node subprocess instead of faking it.
|
|
7
7
|
const script = 'import { readFileSync } from \'node:fs\'\n' +
|
|
8
8
|
'import { ESLint } from \'eslint\'\n' +
|
|
9
|
-
'import { neostandard } from \'neostandard\'\n' +
|
|
10
|
-
'const { text, filePath, cwd, fix, ts, env } = JSON.parse(readFileSync(0, \'utf8\'))\n' +
|
|
11
|
-
'const
|
|
9
|
+
'import { neostandard, defaultFilePatterns } from \'neostandard\'\n' +
|
|
10
|
+
'const { text, filePath, cwd, fix, ts, env, rules } = JSON.parse(readFileSync(0, \'utf8\'))\n' +
|
|
11
|
+
'const baseConfig = neostandard({ ts, env })\n' +
|
|
12
|
+
'if (Object.keys(rules).length) { baseConfig.push({ files: defaultFilePatterns.all, rules }) }\n' +
|
|
13
|
+
'const eslint = new ESLint({ cwd, fix, overrideConfigFile: true, baseConfig })\n' +
|
|
12
14
|
'process.stdout.write(JSON.stringify(await eslint.lintText(text, { filePath })))\n'
|
|
13
15
|
|
|
14
16
|
const readEnv = cwd => {
|
|
@@ -19,12 +21,12 @@ const readEnv = cwd => {
|
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
export const loadEslintViaSubprocess = ({ cwd, ts, fix }) => Promise.resolve({
|
|
24
|
+
export const loadEslintViaSubprocess = ({ cwd, ts, fix, rules = {} }) => Promise.resolve({
|
|
23
25
|
lintText: (text, { filePath }) => Promise.resolve().then(() => JSON.parse(execFileSync(
|
|
24
26
|
process.execPath,
|
|
25
27
|
['--input-type=module', '--eval', script],
|
|
26
28
|
{
|
|
27
|
-
input: JSON.stringify({ text, filePath, cwd, fix, ts, env: readEnv(cwd) }),
|
|
29
|
+
input: JSON.stringify({ text, filePath, cwd, fix, ts, env: readEnv(cwd), rules }),
|
|
28
30
|
encoding: 'utf8',
|
|
29
31
|
cwd,
|
|
30
32
|
maxBuffer: 256 * 1024 * 1024
|
|
@@ -46,7 +46,9 @@ const performDotNotate = (arrayObject, didRetain, prepend = '', results = {}) =>
|
|
|
46
46
|
if (didRetain(currentKey, value, results)) {
|
|
47
47
|
continue
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
// An empty object/array has no keys to flatten into - recursing into one would silently drop currentKey
|
|
50
|
+
// from the result entirely, rather than keeping it as the empty value it actually is. Treat it as a leaf.
|
|
51
|
+
if (isObject(value) && Object.keys(value).length) {
|
|
50
52
|
performDotNotate(value, didRetain, `${currentKey}.`, results)
|
|
51
53
|
continue
|
|
52
54
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { dotNotate } from './dotNotate.mjs'
|
|
2
|
+
|
|
3
|
+
describe('dotNotate', () => {
|
|
4
|
+
test('flattens a nested object into dot-notation leaves', () => {
|
|
5
|
+
expect(dotNotate({ a: { b: 'c', d: { e: 'f' } } })).toEqual({ 'a.b': 'c', 'a.d.e': 'f' })
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test('flattens a non-empty array into index-numbered leaves, same as a plain object', () => {
|
|
9
|
+
expect(dotNotate({ a: ['x', 'y'] })).toEqual({ 'a.0': 'x', 'a.1': 'y' })
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('keeps a primitive value (including null) as its own leaf, unflattened', () => {
|
|
13
|
+
expect(dotNotate({ a: 1, b: 'two', c: null })).toEqual({ a: 1, b: 'two', c: null })
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('keeps an empty object as a leaf value, rather than silently dropping its key', () => {
|
|
17
|
+
expect(dotNotate({ a: {}, b: 'c' })).toEqual({ a: {}, b: 'c' })
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('keeps an empty array as a leaf value, rather than silently dropping its key', () => {
|
|
21
|
+
expect(dotNotate({ a: [], b: 'c' })).toEqual({ a: [], b: 'c' })
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('keeps an empty object/array as a leaf even nested inside other objects', () => {
|
|
25
|
+
expect(dotNotate({ a: { b: [], c: {} } })).toEqual({ 'a.b': [], 'a.c': {} })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('retains a matching key as a whole object instead of flattening into it', () => {
|
|
29
|
+
expect(dotNotate({ a: { b: 'c' }, options: { fix: true } }, ['options'])).toEqual({
|
|
30
|
+
'a.b': 'c',
|
|
31
|
+
options: { fix: true }
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -16,7 +16,7 @@ export const getBundleConfigs = () => {
|
|
|
16
16
|
ignore: gulpConfig.get('browser.ignore'),
|
|
17
17
|
exclude: gulpConfig.get('browser.exclude')
|
|
18
18
|
}
|
|
19
|
-
const additional = gulpConfig.get('browser.bundles')
|
|
19
|
+
const additional = gulpConfig.get('browser.bundles')
|
|
20
20
|
return [
|
|
21
21
|
primary,
|
|
22
22
|
...additional.map((bundleConfig) => ({
|
package/gulp.config.mjs
CHANGED
|
@@ -96,6 +96,16 @@ import { fileExists } from 'test-filesystem'
|
|
|
96
96
|
* @property {StringSetting} from - Path to search for images.
|
|
97
97
|
* @property {StringSetting} to - Path to output images.
|
|
98
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* Configurations for linting (see {@link module:partials.neostandardLint}).
|
|
101
|
+
* @typedef {Object<string, Setting>} module:gulpConfig~LintConfig
|
|
102
|
+
* @property {Object<string, *>} rules - ESLint rule overrides layered on top of neostandard's own config, scoped
|
|
103
|
+
* to the same files neostandard itself lints. For a rule that is wrong for your project specifically (for example
|
|
104
|
+
* a test fixture that deliberately contains text neostandard mistakes for a real syntax problem), rather than
|
|
105
|
+
* something to fix in your code. Empty by default: a project that never sets this gets exactly neostandard's own
|
|
106
|
+
* rules, unchanged. Keyed and valued exactly as ESLint's own `rules` object - see
|
|
107
|
+
* https://eslint.org/docs/latest/use/configure/rules.
|
|
108
|
+
*/
|
|
99
109
|
/**
|
|
100
110
|
* Configurations to compile and generate the Readme file.
|
|
101
111
|
* @typedef {Object<string, Setting>} module:gulpConfig~ReadmeConfig
|
|
@@ -153,6 +163,7 @@ import { fileExists } from 'test-filesystem'
|
|
|
153
163
|
* @property {DocsConfig} docs - Generate the documentation of a TypeScript project configuration group.
|
|
154
164
|
* @property {FontConfig} fonts - Fonts copy configuration group.
|
|
155
165
|
* @property {ImageConfig} images - Minify and copy the images configuration.
|
|
166
|
+
* @property {LintConfig} lint - Linting configuration group.
|
|
156
167
|
* @property {ReadmeConfig} readme - Build readme files configuration.
|
|
157
168
|
* @property {StringSetting} rootPath - Base directory of the project.
|
|
158
169
|
* @property {SassConfig} sass - Compile CSS from SASS configuration.
|
|
@@ -200,6 +211,9 @@ const setDefaults = {
|
|
|
200
211
|
from: 'src/img/**/*.+(png|jpg|jpeg|gif|svg)',
|
|
201
212
|
to: 'browser/img',
|
|
202
213
|
},
|
|
214
|
+
lint: {
|
|
215
|
+
rules: {},
|
|
216
|
+
},
|
|
203
217
|
readme: {
|
|
204
218
|
file: 'README.md',
|
|
205
219
|
from: 'src/**/!(*.test).js',
|