js-build-tools 5.6.0 → 6.0.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 (65) hide show
  1. package/README.md +98 -3
  2. package/dist/build.js +65 -64
  3. package/dist/build.min.js +1 -1
  4. package/dist/bundle.js +40 -40
  5. package/dist/bundleMinify.js +18 -18
  6. package/dist/compileReadme.js +7 -7
  7. package/dist/copyFonts.js +10 -10
  8. package/dist/default.js +12 -12
  9. package/dist/dist.js +7 -7
  10. package/dist/distMinify.js +10 -10
  11. package/dist/images.js +7 -7
  12. package/dist/partials/addToReadme.js +14 -3
  13. package/dist/partials/imagesFor.js +42 -42
  14. package/dist/partials/loadPeer.js +70 -70
  15. package/dist/partials/neostandardLint.js +144 -0
  16. package/dist/partials/tsFor.js +70 -70
  17. package/dist/partials/typeDocsFor.js +202 -202
  18. package/dist/partials.js +136 -136
  19. package/dist/partials.min.js +1 -1
  20. package/dist/sass.js +7 -7
  21. package/dist/sourceLint.js +33 -0
  22. package/dist/sourceLint.min.js +1 -0
  23. package/dist/test-helpers/neostandardLintWithRealNeostandard.js +48 -0
  24. package/dist/test-helpers/setUp.js +5 -0
  25. package/dist/testFull.js +13 -13
  26. package/dist/testHelpers.js +8 -8
  27. package/dist/testQuick.js +13 -13
  28. package/dist/typeDocs.js +9 -9
  29. package/dist/typeScript.js +6 -6
  30. package/dist/utilities/convertMjsFilesForDocs.js +48 -0
  31. package/dist/utilities/esmNamedExportsToCjs.js +33 -0
  32. package/dist/utilities/getBundleConfigs.js +27 -27
  33. package/dist/watchFull.js +7 -7
  34. package/dist/watchTest.js +11 -11
  35. package/functions/build.mjs +9 -8
  36. package/functions/build.test.mjs +18 -25
  37. package/functions/partials/addToReadme.mjs +14 -5
  38. package/functions/partials/neostandardLint.mjs +118 -0
  39. package/functions/partials/neostandardLint.test.mjs +109 -0
  40. package/functions/partials.mjs +1 -1
  41. package/functions/sourceLint.mjs +24 -0
  42. package/functions/sourceLint.test.mjs +73 -0
  43. package/functions/test-helpers/neostandardLintWithRealNeostandard.mjs +38 -0
  44. package/functions/test-helpers/setUp.mjs +5 -0
  45. package/functions/utilities/convertMjsFilesForDocs.mjs +33 -0
  46. package/functions/utilities/convertMjsFilesForDocs.test.mjs +57 -0
  47. package/functions/utilities/esmNamedExportsToCjs.mjs +29 -0
  48. package/functions/utilities/esmNamedExportsToCjs.test.mjs +47 -0
  49. package/gulp.config.mjs +4 -0
  50. package/gulpfile.base.js +2 -4
  51. package/gulpfile.base.mjs +1 -2
  52. package/package.json +7 -6
  53. package/dist/bundleLint.js +0 -28
  54. package/dist/bundleLint.min.js +0 -1
  55. package/dist/distLint.js +0 -20
  56. package/dist/distLint.min.js +0 -1
  57. package/dist/partials/standardLint.js +0 -94
  58. package/dist/test-helpers/standardLintWithRealStandard.js +0 -28
  59. package/functions/bundleLint.mjs +0 -18
  60. package/functions/bundleLint.test.mjs +0 -85
  61. package/functions/distLint.mjs +0 -12
  62. package/functions/distLint.test.mjs +0 -62
  63. package/functions/partials/standardLint.mjs +0 -78
  64. package/functions/partials/standardLint.test.mjs +0 -99
  65. package/functions/test-helpers/standardLintWithRealStandard.mjs +0 -21
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.esmNamedExportsToCjs = void 0;
7
+ /**
8
+ * Rewrites named `export const`/`export function`/`export class` declarations to plain declarations plus a
9
+ * trailing `module.exports = { ... }` - the same shape jsdoc-to-markdown already documents cleanly (a plain
10
+ * declaration with its own `@memberOf` tag, exported all at once at the bottom), rather than the "exports.name"
11
+ * longname jsdoc-to-markdown's own ESM-compat layer produces for an inline `export const name = ...`.
12
+ *
13
+ * This is a narrow, text-level rewrite for documentation purposes only - it does not execute the module, so it
14
+ * only has to look enough like the real thing for jsdoc-to-markdown's parser. It handles the common named-export
15
+ * forms (`export const`, `export function`, `export class`) and leaves everything else (including `export
16
+ * default`, re-exports, and `export { a, b }` lists) untouched in place - a file using one of those keeps its
17
+ * own export line as-is, which jsdoc-to-markdown will document however it already does for ESM.
18
+ * @memberOf module:utilities
19
+ * @param {string} source - The raw ESM source text (never executed, just rewritten as text).
20
+ * @returns {string} The rewritten text, safe to write out with a `.js` extension for jsdoc-to-markdown to read.
21
+ */
22
+ const esmNamedExportsToCjs = source => {
23
+ const exportedNames = [];
24
+ const rewritten = source.replace(/^export (const|function|class) (\w+)/gm, (match, keyword, name) => {
25
+ exportedNames.push(name);
26
+ return `${keyword} ${name}`;
27
+ });
28
+ if (exportedNames.length === 0) {
29
+ return rewritten;
30
+ }
31
+ return `${rewritten}\n\nmodule.exports = { ${exportedNames.join(', ')} }\n`;
32
+ };
33
+ exports.esmNamedExportsToCjs = esmNamedExportsToCjs;
@@ -1,14 +1,14 @@
1
- "use strict";
2
-
3
- require("core-js/modules/esnext.weak-map.delete-all.js");
4
- Object.defineProperty(exports, "__esModule", {
5
- value: true
6
- });
7
- exports.getBundleConfigs = void 0;
8
- require("core-js/modules/esnext.iterator.constructor.js");
9
- require("core-js/modules/esnext.iterator.map.js");
10
- var gulpConfig = _interopRequireWildcard(require("../../gulp.config.js"));
11
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
1
+ "use strict";
2
+
3
+ require("core-js/modules/esnext.weak-map.delete-all.js");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.getBundleConfigs = void 0;
8
+ require("core-js/modules/esnext.iterator.constructor.js");
9
+ require("core-js/modules/esnext.iterator.map.js");
10
+ var gulpConfig = _interopRequireWildcard(require("../../gulp.config.js"));
11
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
12
12
  /**
13
13
  * Build the full list of bundles to produce for this project: the primary bundle (browser.name /
14
14
  * dist.main / browser.ignore / browser.exclude, exactly as before browser.bundles existed) plus
@@ -17,20 +17,20 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
17
17
  * specify what's actually different about it.
18
18
  * @memberOf module:utilities
19
19
  * @returns {Array<{name: string, main: string, ignore: Array, exclude: Array}>}
20
- */
21
- const getBundleConfigs = () => {
22
- const primary = {
23
- name: gulpConfig.get('browser.name'),
24
- main: gulpConfig.get('dist.main'),
25
- ignore: gulpConfig.get('browser.ignore'),
26
- exclude: gulpConfig.get('browser.exclude')
27
- };
28
- const additional = gulpConfig.get('browser.bundles') || [];
29
- return [primary, ...additional.map(bundleConfig => ({
30
- name: bundleConfig.name,
31
- main: bundleConfig.main || primary.main,
32
- ignore: bundleConfig.ignore || primary.ignore,
33
- exclude: bundleConfig.exclude || primary.exclude
34
- }))];
35
- };
20
+ */
21
+ const getBundleConfigs = () => {
22
+ const primary = {
23
+ name: gulpConfig.get('browser.name'),
24
+ main: gulpConfig.get('dist.main'),
25
+ ignore: gulpConfig.get('browser.ignore'),
26
+ exclude: gulpConfig.get('browser.exclude')
27
+ };
28
+ const additional = gulpConfig.get('browser.bundles') || [];
29
+ return [primary, ...additional.map(bundleConfig => ({
30
+ name: bundleConfig.name,
31
+ main: bundleConfig.main || primary.main,
32
+ ignore: bundleConfig.ignore || primary.ignore,
33
+ exclude: bundleConfig.exclude || primary.exclude
34
+ }))];
35
+ };
36
36
  exports.getBundleConfigs = getBundleConfigs;
package/dist/watchFull.js CHANGED
@@ -1,14 +1,14 @@
1
- 'use strict'
1
+ "use strict";
2
2
 
3
- Object.defineProperty(exports, '__esModule', {
3
+ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
- })
6
- exports.watchFull = void 0
7
- const _partials = require('./partials.js')
5
+ });
6
+ exports.watchFull = void 0;
7
+ var _partials = require("./partials.js");
8
8
  /**
9
9
  * Watch for changes and run the distribution for the changed files, then bundle and test the changed files.
10
10
  * @memberOf module:js-build-tools
11
11
  * @returns {FSWatcher}
12
12
  */
13
- const watchFull = () => (0, _partials.beginWatcher)().on('change', _partials.runOnChange)
14
- exports.watchFull = watchFull
13
+ const watchFull = () => (0, _partials.beginWatcher)().on('change', _partials.runOnChange);
14
+ exports.watchFull = watchFull;
package/dist/watchTest.js CHANGED
@@ -1,14 +1,14 @@
1
- 'use strict'
1
+ "use strict";
2
2
 
3
- require('core-js/modules/esnext.weak-map.delete-all.js')
4
- Object.defineProperty(exports, '__esModule', {
3
+ require("core-js/modules/esnext.weak-map.delete-all.js");
4
+ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
- })
7
- exports.watchTest = void 0
8
- const gulpConfig = _interopRequireWildcard(require('../gulp.config.js'))
9
- const _gulp = require('gulp')
10
- const _testQuick = require('./testQuick.js')
11
- function _interopRequireWildcard (e, t) { if (typeof WeakMap === 'function') var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; let o; let i; const f = { __proto__: null, default: e }; if (e === null || typeof e !== 'object' && typeof e !== 'function') return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f) } for (const t in e) t !== 'default' && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f })(e, t) }
6
+ });
7
+ exports.watchTest = void 0;
8
+ var gulpConfig = _interopRequireWildcard(require("../gulp.config.js"));
9
+ var _gulp = require("gulp");
10
+ var _testQuick = require("./testQuick.js");
11
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
12
12
  /**
13
13
  * Watch for changes and run the tests.
14
14
  * @memberOf module:js-build-tools
@@ -16,5 +16,5 @@ function _interopRequireWildcard (e, t) { if (typeof WeakMap === 'function') var
16
16
  */
17
17
  const watchTest = () => (0, _gulp.watch)(gulpConfig.get('test.watch'), {
18
18
  ignoreInitial: false
19
- }, _testQuick.testQuick)
20
- exports.watchTest = watchTest
19
+ }, _testQuick.testQuick);
20
+ exports.watchTest = watchTest;
@@ -1,11 +1,10 @@
1
1
  import { bundle } from './bundle.mjs'
2
- import { bundleLint } from './bundleLint.mjs'
3
2
  import { bundleMinify } from './bundleMinify.mjs'
4
3
  import { clean, distSeries } from './partials.mjs'
5
4
  import { compileReadme } from './compileReadme.mjs'
6
5
  import { typeDocs } from './typeDocs.mjs'
7
- import { distLint } from './distLint.mjs'
8
6
  import { distMinify } from './distMinify.mjs'
7
+ import { sourceLint } from './sourceLint.mjs'
9
8
  import { parallel, series } from 'gulp'
10
9
  import * as gulpConfig from '../gulp.config.mjs'
11
10
  import { copyFonts } from './copyFonts.mjs'
@@ -15,14 +14,13 @@ import { testFull } from './testFull.mjs'
15
14
 
16
15
  /**
17
16
  * Runs several processes to build and validate the project.
18
- * Cleans, distributes (lint and minify), bundles (lint and minify), creates the readme, then runs the tests.
17
+ * Cleans and lints the source, distributes and minifies, bundles and minifies, creates the readme, then runs the
18
+ * tests.
19
19
  * @memberOf module:js-build-tools
20
20
  * @returns {stream.Stream}
21
21
  */
22
22
  export const build = (done = null) => {
23
- const distLintMinify = parallel(distLint, distMinify)
24
- const bundleLintMinify = parallel(bundleLint, bundleMinify)
25
- const buildActions = [distSeries(), distLintMinify]
23
+ const buildActions = [distSeries(), distMinify]
26
24
  const generateDocs = gulpConfig.get('docs.enabled')
27
25
  if (generateDocs) {
28
26
  // The documentation is generated from the TypeScript source itself, the readme is written by hand
@@ -34,7 +32,7 @@ export const build = (done = null) => {
34
32
  if (gulpConfig.get('browser.enabled')) {
35
33
  // If not 'nodeOnly' then we also want to bundle for browser after we complete the dist directory
36
34
  buildActions.push(bundle)
37
- buildActions.push(bundleLintMinify)
35
+ buildActions.push(bundleMinify)
38
36
  }
39
37
  const runActions = [
40
38
  series(...buildActions),
@@ -58,8 +56,11 @@ export const build = (done = null) => {
58
56
  }
59
57
  // clean has to finish before anything writes into the output folders - images, fonts and sass all write into
60
58
  // browser/, so running clean in parallel with them races (ENOTEMPTY errors from clean, or output deleted mid-write).
59
+ // sourceLint fixes the real source in place before anything compiles from it (distSeries, tsFor inside it), so a
60
+ // build never reads a mix of pre- and post-fix content; clean only touches dist/browser output, so the two can
61
+ // run alongside each other.
61
62
  return series(
62
- clean,
63
+ parallel(clean, sourceLint),
63
64
  parallel(...runActions)
64
65
  )(done)
65
66
  }
@@ -6,8 +6,8 @@ import { testFull } from './testFull.mjs'
6
6
  import { typeDocs } from './typeDocs.mjs'
7
7
  import { countMatches } from 'test-filesystem'
8
8
 
9
- // Run the real Standard (in a subprocess) - Jest's CommonJS transform can't load the ESM-only package itself.
10
- jest.mock('./partials/standardLint.mjs', () => require('./test-helpers/standardLintWithRealStandard.mjs'))
9
+ // Run the real neostandard (in a subprocess) - Jest's CommonJS transform can't load the ESM-only package itself.
10
+ jest.mock('./partials/neostandardLint.mjs', () => require('./test-helpers/neostandardLintWithRealNeostandard.mjs'))
11
11
 
12
12
  setUp.setDefaults('test-build')
13
13
  const gulpConfig = setUp.gulpConfig
@@ -266,7 +266,7 @@ beforeEach(
266
266
  afterEach(setUp.afterEach)
267
267
 
268
268
  describe('build', () => {
269
- test('when browser.enabled false: calls clean, distSeries, distLint, distMinify, compileReadme, testFull', done => {
269
+ test('when browser.enabled false: calls clean, sourceLint, distSeries, distMinify, compileReadme, testFull', done => {
270
270
  const srcPath = gulpConfig.get('srcPath')
271
271
  const sourcesPath = `${srcPath}/sources`
272
272
  const distPath = gulpConfig.get('dist.to')
@@ -288,16 +288,15 @@ describe('build', () => {
288
288
  // Minify the dist files (distMinify)
289
289
  expect(fs.existsSync(`${distPath}/main.min.js`)).toBeTruthy()
290
290
  const babelifiedMain = fs.readFileSync(`${distPath}/main.js`).toString()
291
- // Used single quotes because of distLint
292
- expect(countMatches(babelifiedMain, '\'use strict\'')).toEqual(1)
291
+ expect(babelifiedMain.includes('use strict')).toBe(true)
293
292
 
294
293
  expect(fs.existsSync(`${distSourcesPath}/file1.min.js`)).toBeTruthy()
295
294
  const babelifiedFile1 = fs.readFileSync(`${distSourcesPath}/file1.js`).toString()
296
- expect(countMatches(babelifiedFile1, '\'use strict\'')).toEqual(1)
295
+ expect(babelifiedFile1.includes('use strict')).toBe(true)
297
296
 
298
297
  expect(fs.existsSync(`${distSourcesPath}/file2.min.js`)).toBeTruthy()
299
298
  const babelifiedFile2 = fs.readFileSync(`${distSourcesPath}/file2.js`).toString()
300
- expect(countMatches(babelifiedFile2, '\'use strict\'')).toEqual(1)
299
+ expect(babelifiedFile2.includes('use strict')).toBe(true)
301
300
 
302
301
  // Generated README.md (compileReadme)
303
302
  const readmeContents = fs.readFileSync(`${readmePath}/${readmeFile}`).toString()
@@ -312,7 +311,7 @@ describe('build', () => {
312
311
  })
313
312
  }, 30000)
314
313
 
315
- test('when browser.enabled true: calls clean, distSeries, distLint, distMinify, bundle, bundleLint, bundleMinify, compileReadme, testFull', done => {
314
+ test('when browser.enabled true: calls clean, sourceLint, distSeries, distMinify, bundle, bundleMinify, compileReadme, testFull', done => {
316
315
  const srcPath = gulpConfig.get('srcPath')
317
316
  const sourcesPath = `${srcPath}/sources`
318
317
  const distPath = gulpConfig.get('dist.to')
@@ -336,8 +335,7 @@ describe('build', () => {
336
335
  // Minify the dist files (distMinify)
337
336
  expect(fs.existsSync(`${distPath}/main.min.js`)).toBeTruthy()
338
337
  const babelifiedMain = fs.readFileSync(`${distPath}/main.js`).toString()
339
- // Used single quotes because of distLint
340
- expect(countMatches(babelifiedMain, '\'use strict\'')).toEqual(1)
338
+ expect(babelifiedMain.includes('use strict')).toBe(true)
341
339
 
342
340
  // Created browser directory (bundle)
343
341
  expect(fs.existsSync(browserPath)).toBeTruthy()
@@ -361,7 +359,7 @@ describe('build', () => {
361
359
  })
362
360
  }, 30000)
363
361
 
364
- test('when browser.enabled true and fonts.enabled true: calls clean, distSeries, distLint, distMinify, bundle, bundleLint, bundleMinify, compileReadme, testFull, copyFonts', done => {
362
+ test('when browser.enabled true and fonts.enabled true: calls clean, sourceLint, distSeries, distMinify, bundle, bundleMinify, compileReadme, testFull, copyFonts', done => {
365
363
  const srcPath = gulpConfig.get('srcPath')
366
364
  const sourcesPath = `${srcPath}/sources`
367
365
  const distPath = gulpConfig.get('dist.to')
@@ -388,8 +386,7 @@ describe('build', () => {
388
386
  // Minify the dist files (distMinify)
389
387
  expect(fs.existsSync(`${distPath}/main.min.js`)).toBeTruthy()
390
388
  const babelifiedMain = fs.readFileSync(`${distPath}/main.js`).toString()
391
- // Used single quotes because of distLint
392
- expect(countMatches(babelifiedMain, '\'use strict\'')).toEqual(1)
389
+ expect(babelifiedMain.includes('use strict')).toBe(true)
393
390
 
394
391
  // Created browser directory (bundle)
395
392
  expect(fs.existsSync(browserPath)).toBeTruthy()
@@ -419,7 +416,7 @@ describe('build', () => {
419
416
  })
420
417
  }, 30000)
421
418
 
422
- test('when browser.enabled true and images.enabled true: calls clean, distSeries, distLint, distMinify, bundle, bundleLint, bundleMinify, compileReadme, testFull, images', done => {
419
+ test('when browser.enabled true and images.enabled true: calls clean, sourceLint, distSeries, distMinify, bundle, bundleMinify, compileReadme, testFull, images', done => {
423
420
  const srcPath = gulpConfig.get('srcPath')
424
421
  const sourcesPath = `${srcPath}/sources`
425
422
  const distPath = gulpConfig.get('dist.to')
@@ -446,8 +443,7 @@ describe('build', () => {
446
443
  // Minify the dist files (distMinify)
447
444
  expect(fs.existsSync(`${distPath}/main.min.js`)).toBeTruthy()
448
445
  const babelifiedMain = fs.readFileSync(`${distPath}/main.js`).toString()
449
- // Used single quotes because of distLint
450
- expect(countMatches(babelifiedMain, '\'use strict\'')).toEqual(1)
446
+ expect(babelifiedMain.includes('use strict')).toBe(true)
451
447
 
452
448
  // Created browser directory (bundle)
453
449
  expect(fs.existsSync(browserPath)).toBeTruthy()
@@ -477,7 +473,7 @@ describe('build', () => {
477
473
  })
478
474
  }, 30000)
479
475
 
480
- test('when browser.enabled true and sass.enabled true: calls clean, distSeries, distLint, distMinify, bundle, bundleLint, bundleMinify, compileReadme, testFull, sass', done => {
476
+ test('when browser.enabled true and sass.enabled true: calls clean, sourceLint, distSeries, distMinify, bundle, bundleMinify, compileReadme, testFull, sass', done => {
481
477
  const srcPath = gulpConfig.get('srcPath')
482
478
  const sourcesPath = `${srcPath}/sources`
483
479
  const distPath = gulpConfig.get('dist.to')
@@ -504,8 +500,7 @@ describe('build', () => {
504
500
  // Minify the dist files (distMinify)
505
501
  expect(fs.existsSync(`${distPath}/main.min.js`)).toBeTruthy()
506
502
  const babelifiedMain = fs.readFileSync(`${distPath}/main.js`).toString()
507
- // Used single quotes because of distLint
508
- expect(countMatches(babelifiedMain, '\'use strict\'')).toEqual(1)
503
+ expect(babelifiedMain.includes('use strict')).toBe(true)
509
504
 
510
505
  // Created browser directory (bundle)
511
506
  expect(fs.existsSync(browserPath)).toBeTruthy()
@@ -537,7 +532,7 @@ describe('build', () => {
537
532
  })
538
533
  }, 30000)
539
534
 
540
- test('when browser.enabled false and typescript.enabled set: calls clean, tsFor, distFor, distLint, distMinify, compileReadme, testFull', done => {
535
+ test('when browser.enabled false and typescript.enabled set: calls clean, sourceLint, tsFor, distFor, distMinify, compileReadme, testFull', done => {
541
536
  const srcPath = gulpConfig.get('srcPath')
542
537
  const rootPath = gulpConfig.get('rootPath')
543
538
  const tsConfig = `${rootPath}/tsconfig.json`
@@ -563,8 +558,7 @@ describe('build', () => {
563
558
  // Minify the dist files (distMinify)
564
559
  expect(fs.existsSync(`${distPath}/main.min.js`)).toBeTruthy()
565
560
  const babelifiedMain = fs.readFileSync(`${distPath}/main.js`).toString()
566
- // Used single quotes because of distLint
567
- expect(countMatches(babelifiedMain, '\'use strict\'')).toEqual(1)
561
+ expect(babelifiedMain.includes('use strict')).toBe(true)
568
562
 
569
563
  // Generated README.md (compileReadme)
570
564
  const readmeContents = fs.readFileSync(`${readmePath}/${readmeFile}`).toString()
@@ -577,7 +571,7 @@ describe('build', () => {
577
571
  })
578
572
  }, 30000)
579
573
 
580
- test('when browser.enabled true and typescript.enabled set: calls clean, tsFor, distFor, distLint, distMinify, bundle, bundleLint, bundleMinify, compileReadme, testFull', done => {
574
+ test('when browser.enabled true and typescript.enabled set: calls clean, sourceLint, tsFor, distFor, distMinify, bundle, bundleMinify, compileReadme, testFull', done => {
581
575
  const srcPath = gulpConfig.get('srcPath')
582
576
  const rootPath = gulpConfig.get('rootPath')
583
577
  const tsConfig = `${rootPath}/tsconfig.json`
@@ -606,8 +600,7 @@ describe('build', () => {
606
600
  // Minify the dist files (distMinify)
607
601
  expect(fs.existsSync(`${distPath}/main.min.js`)).toBeTruthy()
608
602
  const babelifiedMain = fs.readFileSync(`${distPath}/main.js`).toString()
609
- // Used single quotes because of distLint
610
- expect(countMatches(babelifiedMain, '\'use strict\'')).toEqual(1)
603
+ expect(babelifiedMain.includes('use strict')).toBe(true)
611
604
 
612
605
  // Created browser directory (bundle)
613
606
  expect(fs.existsSync(browserPath)).toBeTruthy()
@@ -2,15 +2,24 @@ import fs from 'fs'
2
2
  import * as gulpConfig from '../../gulp.config.mjs'
3
3
  import jsdoc2md from 'jsdoc-to-markdown'
4
4
  import { globSync } from 'glob'
5
+ import { convertMjsFilesForDocs } from '../utilities/convertMjsFilesForDocs.mjs'
5
6
 
6
7
  /**
7
8
  * Appends all the jsdoc comments to the readme file. Assumes empty or templated file.
8
9
  * Configure this with 'readmeSearch', 'readmePath', 'readmeFile', and 'readmeOptions'.
10
+ *
11
+ * jsdoc-to-markdown only recognizes `.js` files, even though it parses real ESM syntax just fine once it reads
12
+ * one - `readme.fromEsm` (empty by default) names `.mjs` files to include anyway, converted on the fly (see
13
+ * {@link convertMjsFilesForDocs}) and cleaned up again once this has read them, win or lose.
9
14
  * @memberOf module:partials
10
15
  * @returns {string|Uint8Array}
11
16
  */
12
- export const addToReadme = () => jsdoc2md
13
- .render({ files: globSync(gulpConfig.get('readme.from')) })
14
- .then(
15
- readme => fs.appendFileSync(gulpConfig.get('readme.to') + gulpConfig.get('readme.file'), readme, gulpConfig.get('readmeOptions'))
16
- )
17
+ export const addToReadme = () => {
18
+ const { filePaths: esmFilePaths, cleanUp } = convertMjsFilesForDocs(globSync(gulpConfig.get('readme.fromEsm') || []))
19
+ return jsdoc2md
20
+ .render({ files: [...globSync(gulpConfig.get('readme.from')), ...esmFilePaths] })
21
+ .finally(cleanUp)
22
+ .then(
23
+ readme => fs.appendFileSync(gulpConfig.get('readme.to') + gulpConfig.get('readme.file'), readme, gulpConfig.get('readmeOptions'))
24
+ )
25
+ }
@@ -0,0 +1,118 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import through from 'through2'
4
+ import { ESLint } from 'eslint'
5
+ import { importModule } from './loadPeer.mjs'
6
+
7
+ // neostandard (js-build-tools' chosen style - a maintained, TypeScript-aware successor to `standard`, see
8
+ // https://github.com/neostandard/neostandard) ships ESM-only, see importModule for how it is imported.
9
+ const importNeostandard = () => importModule('neostandard').then(({ neostandard }) => neostandard)
10
+
11
+ /**
12
+ * Read the `env` a project already declared for Standard (the `standard` field in its package.json), so a project
13
+ * migrating from `standard` keeps its globals (jest, browser, ...) without touching package.json.
14
+ * @param {string} cwd
15
+ * @returns {Array<string>}
16
+ */
17
+ const readEnv = cwd => {
18
+ try {
19
+ return JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')).standard?.env ?? []
20
+ } catch {
21
+ return []
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Build an ESLint instance configured with neostandard, honoring the project's own package.json `standard.env`.
27
+ * @param {Object} options
28
+ * @param {string} options.cwd
29
+ * @param {boolean} options.ts
30
+ * @param {boolean} options.fix
31
+ * @returns {Promise<import('eslint').ESLint>}
32
+ */
33
+ const buildEslint = async ({ cwd, ts, fix }) => {
34
+ const neostandard = await importNeostandard()
35
+ return new ESLint({
36
+ cwd,
37
+ fix,
38
+ overrideConfigFile: true,
39
+ baseConfig: neostandard({ ts, env: readEnv(cwd) })
40
+ })
41
+ }
42
+
43
+ /**
44
+ * Print a file's lint messages, if it has any.
45
+ * @memberOf module:partials
46
+ * @param {import('vinyl')} file
47
+ * @param {{messages: Array<{line: number, column: number, message: string}>, errorCount: number, warningCount: number}} result
48
+ * @returns {undefined}
49
+ */
50
+ const printFileReport = (file, result) => {
51
+ if (!result.messages.length) {
52
+ return
53
+ }
54
+ console.log(path.relative(file.cwd, file.path))
55
+ result.messages.forEach(({ line, column, message }) => console.log(`line ${line}:${column}\t${message}`))
56
+ console.log(`✖ ${result.errorCount} error\t⚠ ${result.warningCount} warning\n`)
57
+ }
58
+
59
+ /**
60
+ * Lint every file streaming through with neostandard and, unless `fix` is false, apply the automatic fixes to the
61
+ * file's contents so a following `dest()` writes the fixed version. Remaining problems are printed along with a
62
+ * summary once the stream ends; they never fail the build.
63
+ *
64
+ * One ESLint instance is built from the first file's own cwd and reused for the rest of the stream, since every
65
+ * file in a given lint pass shares the same project (and so the same config).
66
+ * @memberOf module:partials
67
+ * @param {Object} [options={}]
68
+ * @param {boolean} [options.fix=true] - Apply neostandard's automatic fixes to the file contents.
69
+ * @param {boolean} [options.ts=false] - Lint `.ts` files too, with type-aware rules (see neostandard's `ts` option).
70
+ * @param {function(Object): Promise<{lintText: Function}>} [loadEslint] - Resolves an ESLint-shaped linter. Real
71
+ * callers should never need to pass this - it exists so tests can supply a stand-in, since this project's
72
+ * CommonJS-transform-based Jest setup cannot execute a real dynamic import() of neostandard (ESM-only).
73
+ * @returns {import('stream').Transform}
74
+ */
75
+ export const neostandardLint = (
76
+ { fix = true, ts = false } = {},
77
+ loadEslint = buildEslint
78
+ ) => {
79
+ const totals = { errors: 0, warnings: 0 }
80
+ let eslintPromise = null
81
+ return through.obj(
82
+ function (file, enc, callback) {
83
+ if (file.isNull()) {
84
+ callback(null, file)
85
+ return
86
+ }
87
+ if (file.isStream()) {
88
+ callback(new Error('neostandardLint: streaming file contents are not supported.'))
89
+ return
90
+ }
91
+ if (!eslintPromise) {
92
+ eslintPromise = loadEslint({ cwd: file.cwd, ts, fix })
93
+ }
94
+ eslintPromise
95
+ .then(eslint => eslint.lintText(file.contents.toString(), { filePath: file.path }))
96
+ .then(results => {
97
+ // No result at all means the file is ignored by the project's ESLint configuration.
98
+ const [result] = results
99
+ if (result) {
100
+ if (typeof result.output === 'string') {
101
+ file.contents = Buffer.from(result.output)
102
+ }
103
+ totals.errors += result.errorCount
104
+ totals.warnings += result.warningCount
105
+ printFileReport(file, result)
106
+ }
107
+ callback(null, file)
108
+ }, callback)
109
+ },
110
+ function (callback) {
111
+ console.log('neostandard linter results')
112
+ console.log('======================================')
113
+ console.log(`✖ Errors total: ${totals.errors}`)
114
+ console.log(`⚠ Warnings total: ${totals.warnings}\n`)
115
+ callback()
116
+ }
117
+ )
118
+ }
@@ -0,0 +1,109 @@
1
+ import Vinyl from 'vinyl'
2
+ import { Readable } from 'node:stream'
3
+ import { neostandardLint } from './neostandardLint.mjs'
4
+
5
+ const makeFile = (contents, filePath = '/project/src/example.js') => new Vinyl({
6
+ cwd: '/project',
7
+ base: '/project/src',
8
+ path: filePath,
9
+ contents
10
+ })
11
+
12
+ const run = (transform, files) => new Promise((resolve, reject) => {
13
+ const out = []
14
+ transform.on('data', file => out.push(file)).on('end', () => resolve(out)).on('error', reject)
15
+ files.forEach(file => transform.write(file))
16
+ transform.end()
17
+ })
18
+
19
+ const fakeEslint = (resultFor) => ({
20
+ lintText: jest.fn((text, options) => Promise.resolve(resultFor(text, options)))
21
+ })
22
+
23
+ let logSpy
24
+
25
+ beforeEach(() => {
26
+ logSpy = jest.spyOn(console, 'log').mockImplementation(() => {})
27
+ })
28
+
29
+ afterEach(() => {
30
+ logSpy.mockRestore()
31
+ })
32
+
33
+ describe('neostandardLint', () => {
34
+ test('replaces file contents with the fixed output and hands ESLint the file path', async () => {
35
+ expect.assertions(3)
36
+ const eslint = fakeEslint((text) => [{ messages: [], errorCount: 0, warningCount: 0, output: text.replace(/"/g, '\'') }])
37
+ const [linted] = await run(neostandardLint({ fix: true }, () => Promise.resolve(eslint)), [makeFile(Buffer.from('const a = "b"'))])
38
+ expect(linted.contents.toString()).toEqual('const a = \'b\'')
39
+ expect(eslint.lintText).toHaveBeenCalledWith('const a = "b"', { filePath: '/project/src/example.js' })
40
+ expect(linted.path).toEqual('/project/src/example.js')
41
+ })
42
+
43
+ test('leaves the contents alone when ESLint has no fixed output (nothing needed fixing)', async () => {
44
+ expect.assertions(1)
45
+ const eslint = fakeEslint(() => [{ messages: [], errorCount: 0, warningCount: 0 }])
46
+ const [linted] = await run(neostandardLint({}, () => Promise.resolve(eslint)), [makeFile(Buffer.from('const a = 1'))])
47
+ expect(linted.contents.toString()).toEqual('const a = 1')
48
+ })
49
+
50
+ test('builds the linter once per stream, from the first file, and reuses it for the rest', async () => {
51
+ expect.assertions(1)
52
+ const loadEslint = jest.fn(() => Promise.resolve(fakeEslint(() => [{ messages: [], errorCount: 0, warningCount: 0 }])))
53
+ await run(
54
+ neostandardLint({}, loadEslint),
55
+ [makeFile(Buffer.from('a'), '/project/src/a.js'), makeFile(Buffer.from('b'), '/project/src/b.js')]
56
+ )
57
+ expect(loadEslint).toHaveBeenCalledTimes(1)
58
+ })
59
+
60
+ test('passes fix and ts through to the loader', async () => {
61
+ expect.assertions(1)
62
+ const loadEslint = jest.fn(() => Promise.resolve(fakeEslint(() => [{ messages: [], errorCount: 0, warningCount: 0 }])))
63
+ await run(neostandardLint({ fix: false, ts: true }, loadEslint), [makeFile(Buffer.from('x'))])
64
+ expect(loadEslint).toHaveBeenCalledWith({ cwd: '/project', ts: true, fix: false })
65
+ })
66
+
67
+ test('passes a file through untouched when the project ignores it (ESLint returns no results)', async () => {
68
+ expect.assertions(2)
69
+ const eslint = fakeEslint(() => [])
70
+ const [linted] = await run(neostandardLint({}, () => Promise.resolve(eslint)), [makeFile(Buffer.from('var x=1'))])
71
+ expect(linted.contents.toString()).toEqual('var x=1')
72
+ expect(logSpy).toHaveBeenCalledWith('✖ Errors total: 0')
73
+ })
74
+
75
+ test('skips null-content files without linting them', async () => {
76
+ expect.assertions(2)
77
+ const eslint = fakeEslint(() => [])
78
+ const [passed] = await run(neostandardLint({}, () => Promise.resolve(eslint)), [makeFile(null)])
79
+ expect(passed.isNull()).toBeTruthy()
80
+ expect(eslint.lintText).not.toHaveBeenCalled()
81
+ })
82
+
83
+ test('errors on streaming file contents, which cannot be linted', async () => {
84
+ expect.assertions(1)
85
+ const streamFile = makeFile(Readable.from(['x']))
86
+ await expect(run(neostandardLint({}, () => Promise.resolve(fakeEslint(() => []))), [streamFile])).rejects.toThrow(/streaming/)
87
+ })
88
+
89
+ test('prints per-file problems and a summary of totals across all files, without failing', async () => {
90
+ expect.assertions(5)
91
+ const eslint = fakeEslint((text, { filePath }) => filePath.endsWith('a.js')
92
+ ? [{ messages: [{ line: 2, column: 7, message: 'unused b' }, { line: 3, column: 1, message: 'bad thing' }], errorCount: 2, warningCount: 0 }]
93
+ : [{ messages: [{ line: 1, column: 1, message: 'just a warning' }], errorCount: 0, warningCount: 1 }])
94
+ const out = await run(
95
+ neostandardLint({}, () => Promise.resolve(eslint)),
96
+ [makeFile(Buffer.from('a'), '/project/src/a.js'), makeFile(Buffer.from('b'), '/project/src/b.js')]
97
+ )
98
+ expect(out).toHaveLength(2)
99
+ expect(logSpy).toHaveBeenCalledWith('src/a.js')
100
+ expect(logSpy).toHaveBeenCalledWith('line 2:7\tunused b')
101
+ expect(logSpy).toHaveBeenCalledWith('✖ Errors total: 2')
102
+ expect(logSpy).toHaveBeenCalledWith('⚠ Warnings total: 1\n')
103
+ })
104
+
105
+ test('surfaces a failure to load or run the linter as a stream error', async () => {
106
+ expect.assertions(1)
107
+ await expect(run(neostandardLint({}, () => Promise.reject(new Error('cannot load'))), [makeFile(Buffer.from('x'))])).rejects.toThrow('cannot load')
108
+ })
109
+ })
@@ -17,10 +17,10 @@ export { distSeries } from './partials/distSeries.mjs'
17
17
  export { imagesFor } from './partials/imagesFor.mjs'
18
18
  export { importModule, importPeer, requirePeer } from './partials/loadPeer.mjs'
19
19
  export { minifyFor } from './partials/minifyFor.mjs'
20
+ export { neostandardLint } from './partials/neostandardLint.mjs'
20
21
  export { readmeTemplate } from './partials/readmeTemplate.mjs'
21
22
  export { removeDirectory } from './partials/removeDirectory.mjs'
22
23
  export { runOnChange } from './partials/runOnChange.mjs'
23
24
  export { sassFor } from './partials/sassFor.mjs'
24
- export { standardLint } from './partials/standardLint.mjs'
25
25
  export { tsFor } from './partials/tsFor.mjs'
26
26
  export { typeDocsFor } from './partials/typeDocsFor.mjs'