js-build-tools 6.3.0 → 6.4.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.
Files changed (49) hide show
  1. package/README.md +10 -3
  2. package/dist/lint.js +17 -0
  3. package/dist/lint.min.js +1 -0
  4. package/dist/partials/imagesFor.js +42 -43
  5. package/dist/partials/jsdocDocsFor.js +5 -3
  6. package/dist/partials/lintSource.js +41 -0
  7. package/dist/partials/loadPeer.js +71 -70
  8. package/dist/partials/minifyFor.js +16 -16
  9. package/dist/partials/neostandardLint.js +130 -122
  10. package/dist/partials.js +176 -169
  11. package/dist/partials.min.js +1 -1
  12. package/dist/sourceLint.js +11 -27
  13. package/dist/sourceLint.min.js +1 -1
  14. package/dist/test-helpers/isolateGulpCache.js +25 -0
  15. package/dist/utilities/dotGet.js +46 -46
  16. package/dist/utilities/dotNotate.js +48 -48
  17. package/dist/utilities/dotSet.js +37 -37
  18. package/functions/build.test.mjs +17 -7
  19. package/functions/bundle.test.mjs +0 -1
  20. package/functions/images.test.mjs +4 -1
  21. package/functions/lint.mjs +9 -0
  22. package/functions/partials/addToReadme.test.mjs +2 -1
  23. package/functions/partials/imagesFor.mjs +0 -1
  24. package/functions/partials/imagesFor.test.mjs +5 -2
  25. package/functions/partials/jsdocDocsFor.mjs +11 -3
  26. package/functions/partials/jsdocDocsFor.test.mjs +5 -2
  27. package/functions/partials/lintSource.mjs +27 -0
  28. package/functions/partials/loadPeer.mjs +1 -0
  29. package/functions/partials/loadPlaywright.test.mjs +1 -2
  30. package/functions/partials/minifyFor.mjs +1 -1
  31. package/functions/partials/neostandardLint.mjs +9 -2
  32. package/functions/partials/neostandardLint.test.mjs +14 -0
  33. package/functions/partials/runOnChange.test.mjs +5 -4
  34. package/functions/partials/tsFor.test.mjs +1 -0
  35. package/functions/partials.mjs +1 -0
  36. package/functions/partials.test.mjs +1 -1
  37. package/functions/sourceLint.mjs +4 -15
  38. package/functions/sourceLint.test.mjs +49 -0
  39. package/functions/test-helpers/isolateGulpCache.mjs +16 -0
  40. package/functions/test-helpers/setUp.mjs +7 -7
  41. package/functions/testFull.test.mjs +1 -1
  42. package/functions/testQuick.test.mjs +1 -1
  43. package/functions/utilities/convertMjsFilesForDocs.test.mjs +1 -0
  44. package/functions/utilities/dotGet.mjs +2 -2
  45. package/functions/utilities/dotNotate.mjs +2 -2
  46. package/functions/utilities/dotNotate.test.mjs +7 -0
  47. package/functions/utilities/dotSet.mjs +1 -1
  48. package/gulpfile.base.mjs +1 -0
  49. package/package.json +12 -2
@@ -92,14 +92,15 @@ describe('jsdocDocsFor', () => {
92
92
  expect(['a.test.mjs', 'a.min.js', 'a.min.mjs', 'dir/_private.mjs', 'a.ts'].filter(documented)).toEqual([])
93
93
  })
94
94
 
95
- test('passes docs.title to the theme as a link to the home page of the docs, and leaves it out when unset', async () => {
95
+ test('passes docs.title to the theme as a link to the home page of the docs and as its page title, and leaves it out when unset', async () => {
96
96
  gulpConfig.set('docs.enabled', true)
97
97
  const srcPath = gulpConfig.get('srcPath')
98
98
  const calls = {}
99
99
  gulpConfig.set('docs.title', 'Tom & Jerry <Tools>')
100
100
  await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
101
101
  expect(calls.config.opts.theme_opts).toEqual({
102
- title: '<a href="index.html" class="sidebar-title sidebar-title-anchor">Tom &amp; Jerry &lt;Tools&gt;</a>'
102
+ title: '<a href="index.html" class="sidebar-title sidebar-title-anchor">Tom &amp; Jerry &lt;Tools&gt;</a>',
103
+ homepageTitle: 'Tom &amp; Jerry &lt;Tools&gt;'
103
104
  })
104
105
 
105
106
  gulpConfig.set('docs.title', '')
@@ -139,11 +140,13 @@ describe('jsdocDocsFor', () => {
139
140
  describe('jsdocDocsFor, end to end with the real jsdoc CLI and clean-jsdoc-theme', () => {
140
141
  test('produces a real browsable HTML page from real JSDoc comments', async () => {
141
142
  gulpConfig.set('docs.enabled', true)
143
+ gulpConfig.set('docs.title', 'Tom & Jerry')
142
144
  const outPath = `${gulpConfig.get('srcPath')}/../docs`
143
145
  await jsdocDocsFor(gulpConfig.get('srcPath'), outPath)()
144
146
  // clean-jsdoc-theme nests its real output under its own route folders (global/, source/...), not flat at
145
147
  // outPath's own root - confirmed by actually running it, not assumed from the classic jsdoc template shape.
146
148
  const files = fs.readdirSync(outPath, { recursive: true })
147
149
  expect(files.some(file => file.endsWith('.html'))).toBe(true)
150
+ expect(fs.readFileSync(`${outPath}/index.html`, 'utf8')).toMatch(/<title>\s*Tom &amp; Jerry\s*<\/title>/)
148
151
  }, 30000)
149
152
  })
@@ -0,0 +1,27 @@
1
+ import * as gulpConfig from '../../gulp.config.mjs'
2
+ import { dest, src } from 'gulp'
3
+ import { neostandardLint } from './neostandardLint.mjs'
4
+
5
+ /**
6
+ * Lint the project's source (srcPath: .js, .mjs and .cjs, plus .ts when typescript.enabled) with neostandard and
7
+ * write the automatic fixes back in place. Shared by the sourceLint (build) and lint (standalone) tasks.
8
+ * @memberOf module:partials
9
+ * @param {Object} [options={}]
10
+ * @param {boolean} [options.failOnError=false] - Fail once linting finishes if any errors could not be fixed.
11
+ * @returns {stream.Stream}
12
+ */
13
+ export const lintSource = ({ failOnError = false } = {}) => {
14
+ const srcPath = gulpConfig.get('srcPath')
15
+ const ts = gulpConfig.get('typescript.enabled')
16
+ const patterns = [`${srcPath}/**/*.{js,mjs,cjs}`]
17
+ if (ts) {
18
+ patterns.push(`${srcPath}/**/*.ts`)
19
+ }
20
+ const linter = neostandardLint({ fix: true, ts, rules: gulpConfig.get('lint.rules'), failOnError })
21
+ const written = src(patterns)
22
+ .pipe(linter)
23
+ .pipe(dest(srcPath))
24
+ // pipe() does not forward errors, and gulp only watches the returned stream, so pass the linter's failure along.
25
+ linter.on('error', error => written.emit('error', error))
26
+ return written
27
+ }
@@ -6,6 +6,7 @@ import { pathToFileURL } from 'node:url'
6
6
  // build (dist/*.js) whose babel transform would turn even a dynamic import() into a require() that cannot load an ES
7
7
  // module. Hiding the import() inside a Function constructor keeps it a genuine dynamic import that works from either
8
8
  // build, because babel cannot see (and so cannot rewrite) an import expression which only exists as a runtime string.
9
+ // eslint-disable-next-line no-new-func -- deliberate, see above
9
10
  const dynamicImport = new Function('specifier', 'return import(specifier)')
10
11
 
11
12
  /**
@@ -1,5 +1,4 @@
1
1
  import fs from 'fs'
2
- import path from 'node:path'
3
2
  import * as setUp from '../test-helpers/setUp.mjs'
4
3
  import { loadPlaywright, launchBrowser } from './loadPlaywright.mjs'
5
4
  import { serveStatic } from './serveStatic.mjs'
@@ -61,7 +60,7 @@ describe('a real page, served and loaded end to end', () => {
61
60
  const page = await browser.newPage()
62
61
  await page.goto(`${url}/index.html`)
63
62
  expect(await page.title()).toBe('loaded via a real module script')
64
- const backgroundColor = await page.evaluate(() => getComputedStyle(document.body).backgroundColor)
63
+ const backgroundColor = await page.evaluate(() => globalThis.getComputedStyle(document.body).backgroundColor)
65
64
  expect(backgroundColor).toBe('rgb(1, 2, 3)')
66
65
  } finally {
67
66
  await browser.close()
@@ -2,7 +2,7 @@ import { dest, src } from 'gulp'
2
2
  import { default as gulpUglify } from 'gulp-uglify-es'
3
3
  import rename from 'gulp-rename'
4
4
 
5
- const uglify = gulpUglify.hasOwnProperty('default') ? gulpUglify.default : gulpUglify
5
+ const uglify = Object.hasOwn(gulpUglify, 'default') ? gulpUglify.default : gulpUglify
6
6
 
7
7
  /**
8
8
  * Minify files and rename the output with '.min' extension.
@@ -71,7 +71,8 @@ const printFileReport = (file, result) => {
71
71
  /**
72
72
  * Lint every file streaming through with neostandard and, unless `fix` is false, apply the automatic fixes to the
73
73
  * file's contents so a following `dest()` writes the fixed version. Remaining problems are printed along with a
74
- * summary once the stream ends; they never fail the build.
74
+ * summary once the stream ends; they only fail the stream when `failOnError` is set (the standalone `lint` task),
75
+ * never during `build`.
75
76
  *
76
77
  * One ESLint instance is built from the first file's own cwd and reused for the rest of the stream, since every
77
78
  * file in a given lint pass shares the same project (and so the same config).
@@ -81,13 +82,15 @@ const printFileReport = (file, result) => {
81
82
  * @param {boolean} [options.ts=false] - Lint `.ts` files too, with type-aware rules (see neostandard's `ts` option).
82
83
  * @param {Object<string, *>} [options.rules={}] - ESLint rule overrides layered on top of neostandard's own rules
83
84
  * (see gulp.config.mjs's `lint.rules`).
85
+ * @param {boolean} [options.failOnError=false] - Emit an error once the stream ends if any lint errors remain after
86
+ * fixing, so a command-line run exits non-zero.
84
87
  * @param {function(Object): Promise<{lintText: Function}>} [loadEslint] - Resolves an ESLint-shaped linter. Real
85
88
  * callers should never need to pass this - it exists so tests can supply a stand-in, since this project's
86
89
  * CommonJS-transform-based Jest setup cannot execute a real dynamic import() of neostandard (ESM-only).
87
90
  * @returns {stream.Transform}
88
91
  */
89
92
  export const neostandardLint = (
90
- { fix = true, ts = false, rules = {} } = {},
93
+ { fix = true, ts = false, rules = {}, failOnError = false } = {},
91
94
  loadEslint = buildEslint
92
95
  ) => {
93
96
  const totals = { errors: 0, warnings: 0 }
@@ -126,6 +129,10 @@ export const neostandardLint = (
126
129
  console.log('======================================')
127
130
  console.log(`✖ Errors total: ${totals.errors}`)
128
131
  console.log(`⚠ Warnings total: ${totals.warnings}\n`)
132
+ if (failOnError && totals.errors) {
133
+ callback(new Error(`neostandard found ${totals.errors} lint error(s) that could not be fixed automatically.`))
134
+ return
135
+ }
129
136
  callback()
130
137
  }
131
138
  )
@@ -110,6 +110,20 @@ describe('neostandardLint', () => {
110
110
  expect(logSpy).toHaveBeenCalledWith('⚠ Warnings total: 1\n')
111
111
  })
112
112
 
113
+ test('with failOnError, fails the stream once it ends if any errors remain', async () => {
114
+ expect.assertions(2)
115
+ const eslint = fakeEslint(() => [{ messages: [{ line: 1, column: 1, message: 'bad thing' }], errorCount: 1, warningCount: 0 }])
116
+ await expect(run(neostandardLint({ failOnError: true }, () => Promise.resolve(eslint)), [makeFile(Buffer.from('a'))]))
117
+ .rejects.toThrow('neostandard found 1 lint error(s)')
118
+ expect(logSpy).toHaveBeenCalledWith('✖ Errors total: 1')
119
+ })
120
+
121
+ test('with failOnError, still passes when there are only warnings', async () => {
122
+ expect.assertions(1)
123
+ const eslint = fakeEslint(() => [{ messages: [{ line: 1, column: 1, message: 'just a warning' }], errorCount: 0, warningCount: 1 }])
124
+ await expect(run(neostandardLint({ failOnError: true }, () => Promise.resolve(eslint)), [makeFile(Buffer.from('a'))])).resolves.toHaveLength(1)
125
+ })
126
+
113
127
  test('surfaces a failure to load or run the linter as a stream error', async () => {
114
128
  expect.assertions(1)
115
129
  await expect(run(neostandardLint({}, () => Promise.reject(new Error('cannot load'))), [makeFile(Buffer.from('x'))])).rejects.toThrow('cannot load')
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-template-curly-in-string -- fixture source code, the ${...} is meant to stay literal */
1
2
  import fs from 'fs'
2
3
  import * as setUp from '../test-helpers/setUp.mjs'
3
4
  import { bundle } from '../bundle.mjs'
@@ -26,10 +27,10 @@ const srcFile = `${srcPath}/runOnChange.js`
26
27
  const srcTsFile = `${srcPath}/runOnChange.ts`
27
28
 
28
29
  beforeEach(() => {
29
- return setUp.beforeEach()
30
- .then(() => fs.writeFileSync(srcTsFile, rawContents))
31
- .then(() => fs.writeFileSync(srcFile, rawContents))
32
- }
30
+ return setUp.beforeEach()
31
+ .then(() => fs.writeFileSync(srcTsFile, rawContents))
32
+ .then(() => fs.writeFileSync(srcFile, rawContents))
33
+ }
33
34
  )
34
35
 
35
36
  afterEach(() => {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-template-curly-in-string -- fixture source code, the ${...} is meant to stay literal */
1
2
  import fs from 'fs'
2
3
  import { Transform, PassThrough } from 'node:stream'
3
4
  import ts from 'typescript'
@@ -18,6 +18,7 @@ export { imagesFor } from './partials/imagesFor.mjs'
18
18
  export { importModule, importPeer, requirePeer } from './partials/loadPeer.mjs'
19
19
  export { jsdocDocsFor } from './partials/jsdocDocsFor.mjs'
20
20
  export { loadPlaywright, launchBrowser } from './partials/loadPlaywright.mjs'
21
+ export { lintSource } from './partials/lintSource.mjs'
21
22
  export { minifyFor } from './partials/minifyFor.mjs'
22
23
  export { neostandardLint } from './partials/neostandardLint.mjs'
23
24
  export { readmeTemplate } from './partials/readmeTemplate.mjs'
@@ -2,6 +2,6 @@ import * as partials from './partials.mjs'
2
2
 
3
3
  describe('partials', () => {
4
4
  test('is object with all partial exports', () => {
5
- expect(Object.keys(partials).length).toBe(24)
5
+ expect(Object.keys(partials).length).toBe(25)
6
6
  })
7
7
  })
@@ -1,24 +1,13 @@
1
- import * as gulpConfig from '../gulp.config.mjs'
2
- import { dest, src } from 'gulp'
3
- import { neostandardLint } from './partials/neostandardLint.mjs'
1
+ import { lintSource } from './partials/lintSource.mjs'
4
2
 
5
3
  /**
6
4
  * Applies neostandard linting (js-build-tools' chosen style) to the real, hand-written source - not the compiled
7
5
  * dist/browser output, which is generated and so was never going to pass a hand-authored-code style check (every
8
6
  * compiler/bundler emits its own boilerplate - import aliasing, interop headers - that only looks like a style
9
7
  * violation). Includes test files: there is no reason to hold tests to a lower style bar than the rest of the
10
- * source. When typescript.enabled, the real TypeScript source is linted too, with type-aware rules.
8
+ * source. When typescript.enabled, the real TypeScript source is linted too, with type-aware rules. Runs as part of
9
+ * `build`, where remaining errors are reported but never fail it.
11
10
  * @memberOf module:js-build-tools
12
11
  * @returns {*}
13
12
  */
14
- export const sourceLint = () => {
15
- const srcPath = gulpConfig.get('srcPath')
16
- const ts = gulpConfig.get('typescript.enabled')
17
- const patterns = [`${srcPath}/**/*.js`]
18
- if (ts) {
19
- patterns.push(`${srcPath}/**/*.ts`)
20
- }
21
- return src(patterns)
22
- .pipe(neostandardLint({ fix: true, ts, rules: gulpConfig.get('lint.rules') }))
23
- .pipe(dest(srcPath))
24
- }
13
+ export const sourceLint = () => lintSource()
@@ -1,5 +1,7 @@
1
+ /* eslint-disable no-template-curly-in-string -- fixture source code, the ${...} is meant to stay literal */
1
2
  import fs from 'fs'
2
3
  import * as setUp from './test-helpers/setUp.mjs'
4
+ import { lint } from './lint.mjs'
3
5
  import { sourceLint } from './sourceLint.mjs'
4
6
  import { countMatches } from 'test-filesystem'
5
7
 
@@ -51,6 +53,23 @@ describe('sourceLint', () => {
51
53
  })
52
54
  }, 20000)
53
55
 
56
+ test('also lints .mjs and .cjs source, not just .js', done => {
57
+ const srcPath = gulpConfig.get('srcPath')
58
+ fs.writeFileSync(`${srcPath}/example.mjs`, 'const m = "d";\n\nexport default m;\n')
59
+ fs.writeFileSync(`${srcPath}/example.cjs`, 'const c = "e";\n\nmodule.exports = c;\n')
60
+ expect.assertions(2)
61
+ sourceLint()
62
+ .on('finish', () => {
63
+ expect(fs.readFileSync(`${srcPath}/example.mjs`).toString()).toEqual('const m = \'d\'\n\nexport default m\n')
64
+ expect(fs.readFileSync(`${srcPath}/example.cjs`).toString()).toEqual('const c = \'e\'\n\nmodule.exports = c\n')
65
+ done()
66
+ })
67
+ .on('error', error => {
68
+ console.error('Encountered error', error)
69
+ done(error)
70
+ })
71
+ }, 20000)
72
+
54
73
  test('lint.rules actually suppresses a real neostandard finding', done => {
55
74
  const srcPath = gulpConfig.get('srcPath')
56
75
  // A fixture string containing literal ${...} text (simulating real module source, not an accidental
@@ -95,3 +114,33 @@ describe('sourceLint', () => {
95
114
  })
96
115
  }, 20000)
97
116
  })
117
+
118
+ describe('lint', () => {
119
+ test('fixes the source and finishes without error when nothing unfixable remains', done => {
120
+ const srcPath = gulpConfig.get('srcPath')
121
+ expect.assertions(1)
122
+ lint()
123
+ .on('finish', () => {
124
+ expect(countMatches(fs.readFileSync(`${srcPath}/example.js`).toString(), ';')).toEqual(0)
125
+ done()
126
+ })
127
+ .on('error', error => done(error))
128
+ }, 20000)
129
+
130
+ test('fails when an error remains that neostandard cannot fix', done => {
131
+ const srcPath = gulpConfig.get('srcPath')
132
+ fs.writeFileSync(`${srcPath}/unfixable.js`, 'const unused = 1\n')
133
+ const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
134
+ expect.assertions(1)
135
+ lint()
136
+ .on('error', error => {
137
+ logSpy.mockRestore()
138
+ expect(error.message).toMatch('neostandard found 1 lint error(s)')
139
+ done()
140
+ })
141
+ .on('finish', () => {
142
+ logSpy.mockRestore()
143
+ done(new Error('lint should have failed on the unused variable'))
144
+ })
145
+ }, 20000)
146
+ })
@@ -0,0 +1,16 @@
1
+ import cache from 'gulp-cache'
2
+
3
+ /**
4
+ * Give this test file its own gulp-cache folder, and return a function that clears only that folder. gulp-cache
5
+ * otherwise keeps one cache in os.tmpdir() for every process, so a cache.clearAll() in one Jest worker could wipe it
6
+ * while another worker's images task was still using it, and gulp-cache then silently dropped that worker's
7
+ * images. Jest loads gulp-cache separately for each test file, so this only changes the copy that file uses.
8
+ * @memberOf module:testHelpers
9
+ * @param {string} name - Unique per test file, used in the cache folder name.
10
+ * @returns {function(): Promise<void>} Clears this test file's cache.
11
+ */
12
+ export const isolateGulpCache = name => {
13
+ const fileCache = new cache.Cache({ cacheDirName: `js-build-tools-test-${name}` })
14
+ cache.defaultOptions.fileCache = fileCache
15
+ return () => new Promise((resolve, reject) => fileCache.clear(null, error => error ? reject(error) : resolve()))
16
+ }
@@ -60,41 +60,41 @@ export {
60
60
  * @param {boolean} [exists=true]
61
61
  * @returns {Promise<*|void>}
62
62
  */
63
- createTempDir,
63
+ createTempDir,
64
64
  /**
65
65
  * In the Jest.beforeEach function call this one to set up the temp directory.
66
66
  * @memberOf module:testHelpers
67
67
  * @returns {Promise<*|void>}
68
68
  */
69
- beforeEach,
69
+ beforeEach,
70
70
  /**
71
71
  * In the Jest.afterEach function call this one to clean up and remove the temp directory.
72
72
  * @memberOf module:testHelpers
73
73
  * @returns {Promise<*>}
74
74
  */
75
- afterEach,
75
+ afterEach,
76
76
  /**
77
77
  * Return number of occurrences of substring in another string.
78
78
  * @memberOf module:testHelpers
79
79
  * @returns {int}
80
80
  */
81
- countMatches,
81
+ countMatches,
82
82
  /**
83
83
  * Returns true if the given file or directory exists, otherwise false.
84
84
  * @memberOf module:testHelpers
85
85
  * @returns {boolean}
86
86
  */
87
- fileExists,
87
+ fileExists,
88
88
  /**
89
89
  * Console log objects in a readable format, returns the given object.
90
90
  * @memberOf module:testHelpers
91
91
  * @returns {*}
92
92
  */
93
- logObject,
93
+ logObject,
94
94
  /**
95
95
  * Delete a directory and subcontents.
96
96
  * @memberOf module:testHelpers
97
97
  * @returns {Promise<*>}
98
98
  */
99
- removeDirectory,
99
+ removeDirectory,
100
100
  } from 'test-filesystem'
@@ -5,7 +5,7 @@ import { testFull } from './testFull.mjs'
5
5
  const gulpConfig = testHelpers.gulpConfig
6
6
 
7
7
  jest.mock('jest', () => ({
8
- 'runCLI': jest.fn(() => Promise.resolve(true))
8
+ runCLI: jest.fn(() => Promise.resolve(true))
9
9
  }))
10
10
 
11
11
  describe('testFull', () => {
@@ -5,7 +5,7 @@ import { testQuick } from './testQuick.mjs'
5
5
  const gulpConfig = testHelpers.gulpConfig
6
6
 
7
7
  jest.mock('jest', () => ({
8
- 'runCLI': jest.fn(() => Promise.resolve(true))
8
+ runCLI: jest.fn(() => Promise.resolve(true))
9
9
  }))
10
10
 
11
11
  describe('testQuick', () => {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-template-curly-in-string -- fixture source code, the ${...} is meant to stay literal */
1
2
  import fs from 'fs'
2
3
  import * as setUp from '../test-helpers/setUp.mjs'
3
4
  import { convertMjsFilesForDocs } from './convertMjsFilesForDocs.mjs'
@@ -11,7 +11,7 @@ import { strBefore } from './strBefore.mjs'
11
11
  * @returns {*} The value of the property
12
12
  * @private
13
13
  */
14
- export const dotGet = (arrayObject = {}, dotNotation= '', defaultValue = null) => {
14
+ export const dotGet = (arrayObject = {}, dotNotation = '', defaultValue = null) => {
15
15
  if (!dotNotation) {
16
16
  return arrayObject
17
17
  }
@@ -22,7 +22,7 @@ export const dotGet = (arrayObject = {}, dotNotation= '', defaultValue = null) =
22
22
  }
23
23
  if (key === '*') {
24
24
  const result = []
25
- for (let wildKey in arrayObject) {
25
+ for (const wildKey in arrayObject) {
26
26
  const wildValue = arrayObject[wildKey]
27
27
  if (lastKey) {
28
28
  result[wildKey] = wildValue
@@ -15,7 +15,7 @@ const handleRetainObjects = (retainObjects = []) => {
15
15
  */
16
16
  return (currentKey, value, results) => false
17
17
  }
18
- retainObjects = retainObjects.map(key => key.replace('\.', '\\.'))
18
+ retainObjects = retainObjects.map(key => key.replaceAll('.', '\\.'))
19
19
  const retainRegex = new RegExp(`(${retainObjects.join('|')})$`)
20
20
  /**
21
21
  * Test if a key should be retained as an object.
@@ -44,7 +44,7 @@ const handleRetainObjects = (retainObjects = []) => {
44
44
  * @private
45
45
  */
46
46
  const performDotNotate = (arrayObject, didRetain, prepend = '', results = {}) => {
47
- for (let key in arrayObject) {
47
+ for (const key in arrayObject) {
48
48
  const value = arrayObject[key]
49
49
  const currentKey = `${prepend}${key}`
50
50
  if (didRetain(currentKey, value, results)) {
@@ -31,4 +31,11 @@ describe('dotNotate', () => {
31
31
  options: { fix: true }
32
32
  })
33
33
  })
34
+
35
+ test('treats every dot in a retained key literally, not as a regex wildcard', () => {
36
+ expect(dotNotate({ a: { bXc: { d: 1 }, b: { c: { d: 2 } } } }, ['a.b.c'])).toEqual({
37
+ 'a.bXc.d': 1,
38
+ 'a.b.c': { d: 2 }
39
+ })
40
+ })
34
41
  })
@@ -18,7 +18,7 @@ export const dotSet = (arrayObject, dotNotation, value = null) => {
18
18
  key = dotNotation
19
19
  }
20
20
  if (key === '*') {
21
- for (let wildKey in arrayObject) {
21
+ for (const wildKey in arrayObject) {
22
22
  if (lastKey) {
23
23
  arrayObject[wildKey] = value
24
24
  continue
package/gulpfile.base.mjs CHANGED
@@ -16,6 +16,7 @@ export { distMinify } from './functions/distMinify.mjs'
16
16
  export * as gulpConfig from './gulp.config.mjs'
17
17
  export { images } from './functions/images.mjs'
18
18
  export { jsdocDocs } from './functions/jsdocDocs.mjs'
19
+ export { lint } from './functions/lint.mjs'
19
20
  export * as partials from './functions/partials.mjs'
20
21
  export { sass } from './functions/sass.mjs'
21
22
  export { sourceLint } from './functions/sourceLint.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-build-tools",
3
- "version": "6.3.0",
3
+ "version": "6.4.0",
4
4
  "description": "Standardised build process with gulp.",
5
5
  "main": "gulpfile.base.js",
6
6
  "exports": {
@@ -21,6 +21,7 @@
21
21
  "build": "gulp build",
22
22
  "dev": "gulp",
23
23
  "docs": "gulp docs",
24
+ "lint": "gulp lint",
24
25
  "readme": "gulp readme",
25
26
  "release": "release-it",
26
27
  "release:dry-run": "release-it --dry-run",
@@ -124,12 +125,21 @@
124
125
  "clean-jsdoc-theme": "^4.3.3",
125
126
  "gulp-imagemin": "github:jheagle/gulp-imagemin#common-js-compatibility",
126
127
  "jsdoc": "^4.0.5",
127
- "playwright": "~1.62.1",
128
+ "playwright": "^1.64.1",
128
129
  "release-it": "^21.1.1",
129
130
  "typedoc": "^0.28.20",
130
131
  "typescript": "^6.0.3"
131
132
  },
132
133
  "engines": {
133
134
  "node": ">=22.12.0"
135
+ },
136
+ "allowScripts": {
137
+ "gifsicle@5.3.0": true,
138
+ "mozjpeg@8.0.0": true,
139
+ "optipng-bin@7.0.1": true,
140
+ "core-js": false,
141
+ "core-js-pure": false,
142
+ "@parcel/watcher": false,
143
+ "unrs-resolver": false
134
144
  }
135
145
  }