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.
- package/README.md +101 -10
- package/dist/build.js +63 -58
- package/dist/build.min.js +1 -1
- package/dist/bundle.js +27 -27
- package/dist/bundleLint.js +16 -16
- package/dist/bundleMinify.js +13 -13
- package/dist/compileReadme.js +10 -10
- package/dist/copyFonts.js +13 -13
- package/dist/default.js +15 -15
- package/dist/dist.js +10 -10
- package/dist/distLint.js +16 -16
- package/dist/distMinify.js +13 -13
- package/dist/images.js +10 -10
- package/dist/partials/addToReadme.js +17 -17
- package/dist/partials/beginWatcher.js +12 -12
- package/dist/partials/clean.js +14 -14
- package/dist/partials/copyFor.js +12 -12
- package/dist/partials/distFor.js +21 -21
- package/dist/partials/distForSrc.js +11 -11
- package/dist/partials/distSeries.js +19 -19
- package/dist/partials/imagesFor.js +7 -13
- package/dist/partials/loadPeer.js +99 -0
- package/dist/partials/minifyFor.js +16 -16
- package/dist/partials/readmeTemplate.js +14 -14
- package/dist/partials/removeDirectory.js +29 -29
- package/dist/partials/runOnChange.js +28 -28
- package/dist/partials/sassFor.js +28 -28
- package/dist/partials/standardLint.js +3 -5
- package/dist/partials/tsFor.js +8 -20
- package/dist/partials/typeDocsFor.js +228 -0
- package/dist/partials.js +26 -0
- package/dist/partials.min.js +1 -1
- package/dist/sass.js +10 -10
- package/dist/test-helpers/setUp.js +89 -89
- package/dist/test-helpers/standardLintWithRealStandard.js +27 -27
- package/dist/testFull.js +20 -20
- package/dist/testHelpers.js +10 -10
- package/dist/testQuick.js +22 -22
- package/dist/typeDocs.js +14 -0
- package/dist/typeDocs.min.js +1 -0
- package/dist/typeScript.js +9 -9
- package/dist/utilities/dotGet.js +46 -46
- package/dist/utilities/dotNotate.js +46 -46
- package/dist/utilities/dotSet.js +37 -37
- package/dist/utilities/isObject.js +8 -8
- package/dist/utilities/strAfter.js +11 -11
- package/dist/utilities/strBefore.js +11 -11
- package/dist/watchFull.js +10 -10
- package/dist/watchTest.js +16 -16
- package/functions/build.mjs +7 -2
- package/functions/build.test.mjs +29 -0
- package/functions/partials/imagesFor.mjs +7 -13
- package/functions/partials/loadPeer.mjs +75 -0
- package/functions/partials/loadPeer.test.mjs +58 -0
- package/functions/partials/standardLint.mjs +3 -5
- package/functions/partials/tsFor.mjs +4 -20
- package/functions/partials/typeDocsFor.mjs +199 -0
- package/functions/partials/typeDocsFor.test.mjs +204 -0
- package/functions/partials.mjs +2 -0
- package/functions/partials.test.mjs +1 -1
- package/functions/typeDocs.mjs +9 -0
- package/functions/typeDocs.test.mjs +11 -0
- package/gulp.config.mjs +24 -2
- package/gulpfile.base.js +2 -0
- package/gulpfile.base.mjs +1 -0
- package/package.json +12 -7
package/dist/utilities/dotSet.js
CHANGED
|
@@ -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
|
package/functions/build.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/functions/build.test.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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,
|
|
5
|
-
|
|
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 {
|
|
4
|
+
import { importModule, requirePeer } from './loadPeer.mjs'
|
|
5
5
|
|
|
6
|
-
// gulp-ts-compile ships ESM-only (no CJS build - see its own package.json)
|
|
7
|
-
|
|
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)
|