js-build-tools 5.2.0 → 5.3.1

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 (66) hide show
  1. package/README.md +101 -10
  2. package/dist/build.js +63 -58
  3. package/dist/build.min.js +1 -1
  4. package/dist/bundle.js +27 -27
  5. package/dist/bundleLint.js +16 -16
  6. package/dist/bundleMinify.js +13 -13
  7. package/dist/compileReadme.js +10 -10
  8. package/dist/copyFonts.js +13 -13
  9. package/dist/default.js +15 -15
  10. package/dist/dist.js +10 -10
  11. package/dist/distLint.js +16 -16
  12. package/dist/distMinify.js +13 -13
  13. package/dist/images.js +10 -10
  14. package/dist/partials/addToReadme.js +17 -17
  15. package/dist/partials/beginWatcher.js +12 -12
  16. package/dist/partials/clean.js +14 -14
  17. package/dist/partials/copyFor.js +12 -12
  18. package/dist/partials/distFor.js +21 -21
  19. package/dist/partials/distForSrc.js +11 -11
  20. package/dist/partials/distSeries.js +19 -19
  21. package/dist/partials/imagesFor.js +7 -13
  22. package/dist/partials/loadPeer.js +99 -0
  23. package/dist/partials/minifyFor.js +16 -16
  24. package/dist/partials/readmeTemplate.js +14 -14
  25. package/dist/partials/removeDirectory.js +29 -29
  26. package/dist/partials/runOnChange.js +28 -28
  27. package/dist/partials/sassFor.js +28 -28
  28. package/dist/partials/standardLint.js +3 -5
  29. package/dist/partials/tsFor.js +8 -20
  30. package/dist/partials/typeDocsFor.js +228 -0
  31. package/dist/partials.js +26 -0
  32. package/dist/partials.min.js +1 -1
  33. package/dist/sass.js +10 -10
  34. package/dist/test-helpers/setUp.js +89 -89
  35. package/dist/test-helpers/standardLintWithRealStandard.js +27 -27
  36. package/dist/testFull.js +20 -20
  37. package/dist/testHelpers.js +10 -10
  38. package/dist/testQuick.js +22 -22
  39. package/dist/typeDocs.js +14 -0
  40. package/dist/typeDocs.min.js +1 -0
  41. package/dist/typeScript.js +9 -9
  42. package/dist/utilities/dotGet.js +46 -46
  43. package/dist/utilities/dotNotate.js +46 -46
  44. package/dist/utilities/dotSet.js +37 -37
  45. package/dist/utilities/isObject.js +8 -8
  46. package/dist/utilities/strAfter.js +11 -11
  47. package/dist/utilities/strBefore.js +11 -11
  48. package/dist/watchFull.js +10 -10
  49. package/dist/watchTest.js +16 -16
  50. package/functions/build.mjs +7 -2
  51. package/functions/build.test.mjs +29 -0
  52. package/functions/partials/imagesFor.mjs +7 -13
  53. package/functions/partials/loadPeer.mjs +75 -0
  54. package/functions/partials/loadPeer.test.mjs +58 -0
  55. package/functions/partials/standardLint.mjs +3 -5
  56. package/functions/partials/tsFor.mjs +4 -20
  57. package/functions/partials/typeDocsFor.mjs +199 -0
  58. package/functions/partials/typeDocsFor.test.mjs +204 -0
  59. package/functions/partials.mjs +2 -0
  60. package/functions/partials.test.mjs +1 -1
  61. package/functions/typeDocs.mjs +9 -0
  62. package/functions/typeDocs.test.mjs +11 -0
  63. package/gulp.config.mjs +24 -2
  64. package/gulpfile.base.js +2 -0
  65. package/gulpfile.base.mjs +1 -0
  66. package/package.json +12 -7
@@ -1,12 +1,12 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.dotSet = void 0;
7
- var _isObject = require("./isObject.js");
8
- var _strAfter = require("./strAfter.js");
9
- var _strBefore = require("./strBefore.js");
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.dotSet = void 0;
7
+ var _isObject = require("./isObject.js");
8
+ var _strAfter = require("./strAfter.js");
9
+ var _strBefore = require("./strBefore.js");
10
10
  /**
11
11
  * Set a nested property value an object.
12
12
  * Original source concepts from {@link https://github.com/jheagle/si-funciona/blob/main/src/helpers/objects/dotSet.ts Sí, funciona}
@@ -14,32 +14,32 @@ var _strBefore = require("./strBefore.js");
14
14
  * @param {string} dotNotation - The path for the property
15
15
  * @param {*} value - The default value to return if the property is not found
16
16
  * @returns {Object} The modified object
17
- */
18
- const dotSet = (arrayObject, dotNotation, value = null) => {
19
- let key = (0, _strBefore.strBefore)(dotNotation, '.');
20
- const lastKey = !key;
21
- if (lastKey) {
22
- key = dotNotation;
23
- }
24
- if (key === '*') {
25
- for (let wildKey in arrayObject) {
26
- if (lastKey) {
27
- arrayObject[wildKey] = value;
28
- continue;
29
- }
30
- if (!(0, _isObject.isObject)(arrayObject[wildKey])) {
31
- continue;
32
- }
33
- dotSet(arrayObject[wildKey], (0, _strAfter.strAfter)(dotNotation, '.'), value);
34
- }
35
- return arrayObject;
36
- }
37
- if (lastKey) {
38
- arrayObject[dotNotation] = value;
39
- return arrayObject;
40
- }
41
- const next = arrayObject[key] ?? [];
42
- arrayObject[key] = dotSet(next, (0, _strAfter.strAfter)(dotNotation, '.'), value);
43
- return arrayObject;
44
- };
17
+ */
18
+ const dotSet = (arrayObject, dotNotation, value = null) => {
19
+ let key = (0, _strBefore.strBefore)(dotNotation, '.');
20
+ const lastKey = !key;
21
+ if (lastKey) {
22
+ key = dotNotation;
23
+ }
24
+ if (key === '*') {
25
+ for (let wildKey in arrayObject) {
26
+ if (lastKey) {
27
+ arrayObject[wildKey] = value;
28
+ continue;
29
+ }
30
+ if (!(0, _isObject.isObject)(arrayObject[wildKey])) {
31
+ continue;
32
+ }
33
+ dotSet(arrayObject[wildKey], (0, _strAfter.strAfter)(dotNotation, '.'), value);
34
+ }
35
+ return arrayObject;
36
+ }
37
+ if (lastKey) {
38
+ arrayObject[dotNotation] = value;
39
+ return arrayObject;
40
+ }
41
+ const next = arrayObject[key] ?? [];
42
+ arrayObject[key] = dotSet(next, (0, _strAfter.strAfter)(dotNotation, '.'), value);
43
+ return arrayObject;
44
+ };
45
45
  exports.dotSet = dotSet;
@@ -1,14 +1,14 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.isObject = void 0;
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isObject = void 0;
7
7
  /**
8
8
  * Check if the provided thing is an object / array.
9
9
  * Original source concepts from {@link https://github.com/jheagle/si-funciona/blob/main/src/helpers/objects/isObject.ts Sí, funciona}
10
10
  * @param {*} object
11
11
  * @returns {boolean}
12
- */
13
- const isObject = object => typeof object === 'object' && object !== null;
12
+ */
13
+ const isObject = object => typeof object === 'object' && object !== null;
14
14
  exports.isObject = isObject;
@@ -1,18 +1,18 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.strAfter = void 0;
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.strAfter = void 0;
7
7
  /**
8
8
  * Retrieve the string part after the search match.
9
9
  * Original source from {@link https://github.com/jheagle/si-funciona/blob/main/src/helpers/strings/strAfter.ts Sí, funciona}
10
10
  * @param {string} str
11
11
  * @param {string} search
12
12
  * @returns {string}
13
- */
14
- const strAfter = (str, search) => {
15
- const index = str.indexOf(search);
16
- return index === -1 ? '' : str.substring(index + search.length);
17
- };
13
+ */
14
+ const strAfter = (str, search) => {
15
+ const index = str.indexOf(search);
16
+ return index === -1 ? '' : str.substring(index + search.length);
17
+ };
18
18
  exports.strAfter = strAfter;
@@ -1,18 +1,18 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.strBefore = void 0;
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.strBefore = void 0;
7
7
  /**
8
8
  * Retrieve the string part before the search match.
9
9
  * Original source from {@link https://github.com/jheagle/si-funciona/blob/main/src/helpers/strings/strBefore.ts Sí, funciona}
10
10
  * @param {string} str
11
11
  * @param {string} search
12
12
  * @returns {string}
13
- */
14
- const strBefore = (str, search) => {
15
- const index = str.indexOf(search);
16
- return index === -1 ? '' : str.slice(0, index);
17
- };
13
+ */
14
+ const strBefore = (str, search) => {
15
+ const index = str.indexOf(search);
16
+ return index === -1 ? '' : str.slice(0, index);
17
+ };
18
18
  exports.strBefore = strBefore;
package/dist/watchFull.js CHANGED
@@ -1,14 +1,14 @@
1
- 'use strict'
2
-
3
- Object.defineProperty(exports, '__esModule', {
4
- value: true
5
- })
6
- exports.watchFull = void 0
7
- const _partials = require('./partials.js')
1
+ 'use strict'
2
+
3
+ Object.defineProperty(exports, '__esModule', {
4
+ value: true
5
+ })
6
+ exports.watchFull = void 0
7
+ const _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
- */
13
- const watchFull = () => (0, _partials.beginWatcher)().on('change', _partials.runOnChange)
14
- exports.watchFull = watchFull
12
+ */
13
+ const watchFull = () => (0, _partials.beginWatcher)().on('change', _partials.runOnChange)
14
+ exports.watchFull = watchFull
package/dist/watchTest.js CHANGED
@@ -1,20 +1,20 @@
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.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) }
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.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) }
12
12
  /**
13
13
  * Watch for changes and run the tests.
14
14
  * @memberOf module:js-build-tools
15
15
  * @returns {*}
16
- */
17
- const watchTest = () => (0, _gulp.watch)(gulpConfig.get('test.watch'), {
18
- ignoreInitial: false
19
- }, _testQuick.testQuick)
20
- exports.watchTest = watchTest
16
+ */
17
+ const watchTest = () => (0, _gulp.watch)(gulpConfig.get('test.watch'), {
18
+ ignoreInitial: false
19
+ }, _testQuick.testQuick)
20
+ exports.watchTest = watchTest
@@ -3,6 +3,7 @@ import { bundleLint } from './bundleLint.mjs'
3
3
  import { bundleMinify } from './bundleMinify.mjs'
4
4
  import { clean, distSeries } from './partials.mjs'
5
5
  import { compileReadme } from './compileReadme.mjs'
6
+ import { typeDocs } from './typeDocs.mjs'
6
7
  import { distLint } from './distLint.mjs'
7
8
  import { distMinify } from './distMinify.mjs'
8
9
  import { parallel, series } from 'gulp'
@@ -22,7 +23,11 @@ export const build = (done = null) => {
22
23
  const distLintMinify = parallel(distLint, distMinify)
23
24
  const bundleLintMinify = parallel(bundleLint, bundleMinify)
24
25
  const buildActions = [distSeries(), distLintMinify]
25
- if (gulpConfig.get('typescript.enabled')) {
26
+ const generateDocs = gulpConfig.get('docs.enabled')
27
+ if (generateDocs) {
28
+ // The documentation is generated from the TypeScript source itself, the readme is written by hand
29
+ buildActions.push(typeDocs)
30
+ } else if (gulpConfig.get('typescript.enabled')) {
26
31
  // For ts usage, we need to run the readme on the dist directly since that is where the .js files are located
27
32
  buildActions.push(compileReadme)
28
33
  }
@@ -34,7 +39,7 @@ export const build = (done = null) => {
34
39
  const runActions = [
35
40
  series(...buildActions),
36
41
  ]
37
- if (!gulpConfig.get('typescript.enabled')) {
42
+ if (!generateDocs && !gulpConfig.get('typescript.enabled')) {
38
43
  // Since we didn't run this in series after dist because of typescript, we need to run it now. Potentially faster here.
39
44
  runActions.push(compileReadme)
40
45
  }
@@ -3,6 +3,7 @@ import * as setUp from './test-helpers/setUp.mjs'
3
3
  import { build } from './build.mjs'
4
4
  import { clean } from './partials/clean.mjs'
5
5
  import { testFull } from './testFull.mjs'
6
+ import { typeDocs } from './typeDocs.mjs'
6
7
  import { countMatches } from 'test-filesystem'
7
8
 
8
9
  // Run the real Standard (in a subprocess) - Jest's CommonJS transform can't load the ESM-only package itself.
@@ -15,6 +16,8 @@ const genericFunction = () => Promise.resolve(true)
15
16
 
16
17
  jest.mock('./partials/clean.mjs', () => ({ clean: jest.fn(genericFunction) }))
17
18
  jest.mock('./testFull.mjs', () => ({ testFull: jest.fn(genericFunction) }))
19
+ // The documentation is generated by TypeDoc, whose own tests cover that (see typeDocsFor.test.mjs)
20
+ jest.mock('./typeDocs.mjs', () => ({ typeDocs: jest.fn(genericFunction) }))
18
21
  // tsFor's real implementation loads gulp-ts-compile (a genuine ESM-only package) via a dynamic import() hidden
19
22
  // from babel's static analysis - necessary for production correctness (see tsFor.mjs's own comments), but this
20
23
  // project's current CommonJS-transform-based Jest setup can't execute a real dynamic import at all: it doesn't
@@ -625,6 +628,31 @@ describe('build', () => {
625
628
  })
626
629
  }, 30000)
627
630
 
631
+ test('when docs.enabled is set: generates the documentation from the TypeScript source instead of the readme', done => {
632
+ const srcPath = gulpConfig.get('srcPath')
633
+ const rootPath = gulpConfig.get('rootPath')
634
+ const tsConfig = `${rootPath}/tsconfig.json`
635
+ const readmePath = gulpConfig.get('readme.to')
636
+ const readmeFile = gulpConfig.get('readme.file')
637
+ fs.writeFileSync(`${srcPath}/main.ts`, mainTypescript)
638
+ fs.writeFileSync(tsConfig, tsConfigContents)
639
+ fs.rmSync(`${readmePath}/${readmeFile}`, { force: true })
640
+ typeDocs.mockClear()
641
+ gulpConfig.set('browser.enabled', false)
642
+ gulpConfig.set('typescript.config', tsConfig)
643
+ gulpConfig.set('typescript.enabled', true)
644
+ gulpConfig.set('docs.enabled', true)
645
+ expect.assertions(3)
646
+ build(() => {
647
+ gulpConfig.set('docs.enabled', false)
648
+ expect(typeDocs).toHaveBeenCalledTimes(1)
649
+ // The readme is not generated, it is written by hand
650
+ expect(fs.existsSync(`${readmePath}/${readmeFile}`)).toBe(false)
651
+ expect(testFull).toHaveBeenCalled()
652
+ done()
653
+ })
654
+ }, 30000)
655
+
628
656
  test('finishes clean before starting the tasks that write into the output folders', done => {
629
657
  const srcPath = gulpConfig.get('srcPath')
630
658
  const sourcesPath = `${srcPath}/sources`
@@ -647,6 +675,7 @@ describe('build', () => {
647
675
  gulpConfig.set('images.enabled', false)
648
676
  gulpConfig.set('sass.enabled', false)
649
677
  gulpConfig.set('typescript.enabled', false)
678
+ gulpConfig.set('docs.enabled', false)
650
679
  build(() => {
651
680
  expect(events).toEqual(['clean finished', 'testFull started'])
652
681
  done()
@@ -1,5 +1,5 @@
1
1
  import cache from 'gulp-cache'
2
- import { createRequire } from 'node:module'
2
+ import { requirePeer } from './loadPeer.mjs'
3
3
  import { dest, src } from 'gulp'
4
4
  import path from 'node:path'
5
5
  import * as gulpConfig from '../../gulp.config.mjs'
@@ -17,18 +17,12 @@ const installHint = 'Add it to your project with: npm install --save-dev github:
17
17
  * @throws {Error} With install instructions when gulp-imagemin is missing or is the ESM-only upstream release.
18
18
  */
19
19
  export const loadImagemin = (projectPath = process.cwd()) => {
20
- let loaded
21
- try {
22
- loaded = createRequire(path.join(projectPath, 'package.json'))('gulp-imagemin')
23
- } catch (error) {
24
- if (error.code === 'MODULE_NOT_FOUND' && /Cannot find module 'gulp-imagemin'/.test(error.message)) {
25
- throw new Error(`The images task needs the optional peer dependency gulp-imagemin, which is not installed. ${installHint}`)
26
- }
27
- if (error.code === 'ERR_REQUIRE_ESM') {
28
- throw new Error(`gulp-imagemin could not be loaded because it is ESM-only. ${installHint}`)
29
- }
30
- throw error
31
- }
20
+ const loaded = requirePeer('gulp-imagemin', {
21
+ task: 'images',
22
+ installHint,
23
+ esmOnlyMessage: `gulp-imagemin could not be loaded because it is ESM-only. ${installHint}`,
24
+ projectPath
25
+ })
32
26
  return loaded && (loaded.__esModule || loaded.default) ? loaded.default : loaded
33
27
  }
34
28
 
@@ -0,0 +1,75 @@
1
+ import { createRequire } from 'node:module'
2
+ import path from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+
5
+ // Several dependencies ship ESM-only (standard, gulp-ts-compile, typedoc), but this package also publishes a CommonJS
6
+ // build (dist/*.js) whose babel transform would turn even a dynamic import() into a require() that cannot load an ES
7
+ // module. Hiding the import() inside a Function constructor keeps it a genuine dynamic import that works from either
8
+ // build, because babel cannot see (and so cannot rewrite) an import expression which only exists as a runtime string.
9
+ const dynamicImport = new Function('specifier', 'return import(specifier)')
10
+
11
+ /**
12
+ * Import an ES module (or any module) with a real dynamic import(), whichever build (ESM or CommonJS) this runs from.
13
+ * @memberOf module:partials
14
+ * @param {string} specifier - The package name, or the URL of a file.
15
+ * @returns {Promise<Object>} The module namespace.
16
+ */
17
+ export const importModule = specifier => dynamicImport(specifier)
18
+
19
+ const isMissing = (error, name) => error.code === 'MODULE_NOT_FOUND' && error.message.includes(`'${name}'`)
20
+
21
+ const notInstalled = (name, { task, installHint }) => new Error(
22
+ `The ${task} task needs the optional peer dependency ${name}, which is not installed. ${installHint}`
23
+ )
24
+
25
+ /**
26
+ * Load an optional peer dependency (one which is only needed once a task runs, so projects which never use that task
27
+ * do not have to install it) with require(), from the project being built rather than from this package. When it is
28
+ * missing the error explains how to install it.
29
+ * @memberOf module:partials
30
+ * @param {string} name - The package name.
31
+ * @param {Object} options
32
+ * @param {string} options.task - The task which needs the package, for the error message.
33
+ * @param {string} options.installHint - How to install it, for the error message.
34
+ * @param {string} [options.esmOnlyMessage] - The error message when the package turns out to be ESM-only (it cannot be require()d).
35
+ * @param {string} [options.projectPath=process.cwd()] - The folder of the project (which has the package.json) being built.
36
+ * @returns {*} Whatever the package exports.
37
+ * @throws {Error} With install instructions when the package is not installed.
38
+ */
39
+ export const requirePeer = (name, { task, installHint, esmOnlyMessage, projectPath = process.cwd() }) => {
40
+ try {
41
+ return createRequire(path.join(projectPath, 'package.json'))(name)
42
+ } catch (error) {
43
+ if (isMissing(error, name)) {
44
+ throw notInstalled(name, { task, installHint })
45
+ }
46
+ if (error.code === 'ERR_REQUIRE_ESM' && esmOnlyMessage) {
47
+ throw new Error(esmOnlyMessage)
48
+ }
49
+ throw error
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Import an optional peer dependency which ships ESM-only, from the project being built. See {@link requirePeer}.
55
+ * @memberOf module:partials
56
+ * @param {string} name - The package name.
57
+ * @param {Object} options
58
+ * @param {string} options.task - The task which needs the package, for the error message.
59
+ * @param {string} options.installHint - How to install it, for the error message.
60
+ * @param {string} [options.projectPath=process.cwd()] - The folder of the project (which has the package.json) being built.
61
+ * @returns {Promise<Object>} The module namespace.
62
+ * @throws {Error} With install instructions when the package is not installed.
63
+ */
64
+ export const importPeer = async (name, { task, installHint, projectPath = process.cwd() }) => {
65
+ let resolved
66
+ try {
67
+ resolved = createRequire(path.join(projectPath, 'package.json')).resolve(name)
68
+ } catch (error) {
69
+ if (isMissing(error, name)) {
70
+ throw notInstalled(name, { task, installHint })
71
+ }
72
+ throw error
73
+ }
74
+ return importModule(pathToFileURL(resolved).href)
75
+ }
@@ -0,0 +1,58 @@
1
+ import fs from 'fs'
2
+ import path from 'node:path'
3
+ import * as setUp from '../test-helpers/setUp.mjs'
4
+ import { importModule, importPeer, requirePeer } from './loadPeer.mjs'
5
+
6
+ setUp.setDefaults('test-load-peer')
7
+
8
+ const options = { task: 'example', installHint: 'Install it with: npm install --save-dev fake-peer' }
9
+
10
+ // A project with its own node_modules, so the packages are found from the project rather than from this package
11
+ const projectWith = (name, files) => {
12
+ const project = path.resolve('test-load-peer/project')
13
+ fs.mkdirSync(`${project}/node_modules/${name}`, { recursive: true })
14
+ fs.writeFileSync(`${project}/package.json`, '{}')
15
+ Object.entries(files).forEach(([file, contents]) => fs.writeFileSync(`${project}/node_modules/${name}/${file}`, contents))
16
+ return project
17
+ }
18
+
19
+ beforeEach(setUp.beforeEach)
20
+ afterEach(setUp.afterEach)
21
+
22
+ describe('requirePeer', () => {
23
+ test('loads the package from the project being built', () => {
24
+ const project = projectWith('fake-peer', { 'package.json': '{"name":"fake-peer","main":"index.js"}', 'index.js': 'module.exports = { found: true }' })
25
+ expect(requirePeer('fake-peer', { ...options, projectPath: project })).toEqual({ found: true })
26
+ })
27
+
28
+ test('explains how to install the package when the project does not have it', () => {
29
+ expect(() => requirePeer('fake-peer', { ...options, projectPath: '/' })).toThrow(
30
+ 'The example task needs the optional peer dependency fake-peer, which is not installed. Install it with: npm install --save-dev fake-peer'
31
+ )
32
+ })
33
+
34
+ test('does not hide other errors, and explains a package which is ESM-only when it has a message for that', () => {
35
+ const project = projectWith('broken-peer', { 'package.json': '{"name":"broken-peer","main":"index.js"}', 'index.js': 'throw new Error("boom")' })
36
+ expect(() => requirePeer('broken-peer', { ...options, projectPath: project })).toThrow('boom')
37
+ // A missing dependency of the peer is not the peer being missing
38
+ const inner = projectWith('inner-peer', { 'package.json': '{"name":"inner-peer","main":"index.js"}', 'index.js': 'require("something-else-missing")' })
39
+ expect(() => requirePeer('inner-peer', { ...options, projectPath: inner })).toThrow(/something-else-missing/)
40
+ expect(() => requirePeer('inner-peer', { ...options, projectPath: inner })).not.toThrow(/optional peer dependency/)
41
+ })
42
+ })
43
+
44
+ describe('importPeer', () => {
45
+ test('explains how to install the package when the project does not have it', async () => {
46
+ await expect(importPeer('fake-peer', { ...options, projectPath: '/' })).rejects.toThrow(
47
+ /needs the optional peer dependency fake-peer, which is not installed. Install it with/
48
+ )
49
+ })
50
+ })
51
+
52
+ describe('importModule', () => {
53
+ // The real import() cannot run under this project's CommonJS-transform-based Jest setup (see tsFor.mjs), so the
54
+ // loaders are checked by running the tasks for real, see the pull request.
55
+ test('is a function', () => {
56
+ expect(importModule).toBeInstanceOf(Function)
57
+ })
58
+ })
@@ -1,11 +1,9 @@
1
1
  import path from 'node:path'
2
2
  import through from 'through2'
3
+ import { importModule } from './loadPeer.mjs'
3
4
 
4
- // `standard` (17+) ships ESM-only, but this package also publishes a CommonJS build (dist/*.js) whose babel
5
- // transform would turn even a dynamic import() into a require() that cannot load an ES module. Hiding the import()
6
- // inside a Function constructor keeps it a genuine dynamic import that works from either build (same escape hatch
7
- // as tsFor.mjs uses for gulp-ts-compile).
8
- const importStandard = new Function('return import(\'standard\')')
5
+ // `standard` (17+) ships ESM-only, see importModule for how it is imported.
6
+ const importStandard = () => importModule('standard')
9
7
 
10
8
  /**
11
9
  * Print a file's lint messages, if it has any.
@@ -1,17 +1,10 @@
1
1
  import { dest, src } from 'gulp'
2
2
  import path from 'node:path'
3
3
  import * as gulpConfig from '../../gulp.config.mjs'
4
- import { createRequire } from 'node:module'
4
+ import { importModule, requirePeer } from './loadPeer.mjs'
5
5
 
6
- // gulp-ts-compile ships ESM-only (no CJS build - see its own package.json) since it's meant to be consumed by
7
- // gulpfiles running under a real ESM loader, same as this project's own gulpfile.mjs does. This project also
8
- // compiles itself to a CommonJS build (dist/*.js, this file's own compiled counterpart) for consumers using
9
- // require() though, and even a dynamic `import()` gets downleveled by babel's CommonJS transform into a
10
- // require() call - which can't load a real ES module. Hiding the import() call inside a Function constructor is
11
- // the standard escape hatch: babel can't see (and so can't rewrite) an import expression that only exists as a
12
- // runtime-evaluated string, so it survives as a genuine dynamic import, capable of loading gulp-ts-compile from
13
- // either build.
14
- const importGulpTsCompile = new Function('return import(\'gulp-ts-compile\')')
6
+ // gulp-ts-compile ships ESM-only (no CJS build - see its own package.json), see importModule for how it is imported.
7
+ const importGulpTsCompile = () => importModule('gulp-ts-compile')
15
8
 
16
9
  /**
17
10
  * Wrap a stream in a promise that resolves once it finishes writing.
@@ -29,16 +22,7 @@ const installHint = 'Install it in your project with: npm install --save-dev typ
29
22
  * @returns {Object} The TypeScript compiler API module.
30
23
  * @throws {Error} With install instructions when typescript is not installed.
31
24
  */
32
- export const loadTypescript = (projectPath = process.cwd()) => {
33
- try {
34
- return createRequire(path.join(projectPath, 'package.json'))('typescript')
35
- } catch (error) {
36
- if (error.code === 'MODULE_NOT_FOUND' && /Cannot find module 'typescript'/.test(error.message)) {
37
- throw new Error(`The typescript task needs the optional peer dependency typescript, which is not installed. ${installHint}`)
38
- }
39
- throw error
40
- }
41
- }
25
+ export const loadTypescript = (projectPath = process.cwd()) => requirePeer('typescript', { task: 'typescript', installHint, projectPath })
42
26
 
43
27
  const streamToPromise = (stream) => new Promise((resolve, reject) => {
44
28
  stream.on('finish', resolve)