js-build-tools 6.4.1 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -90,6 +90,8 @@ provided are the defaults):
90
90
  "enabled": false,
91
91
  // Path to search for images
92
92
  "from": "src/img/**/*.+(png|jpg|jpeg|gif|svg)",
93
+ // The JPEG quality (0 to 100) to compress to, false keeps mozjpeg's own default (75)
94
+ "jpegQuality": false,
93
95
  // Path to output images
94
96
  "to": "browser/img"
95
97
  },
@@ -439,6 +441,9 @@ Add the following to the exports in your `build-tools.config.json`:
439
441
  "enabled": false,
440
442
  // Search pattern to find your images
441
443
  "from": "src/img/**/*.+(png|jpg|jpeg|gif|svg)",
444
+ // The JPEG quality (0 to 100) to compress to. Left false, mozjpeg uses its own default of 75, which can
445
+ // visibly band smooth gradients in photos; 85 is usually indistinguishable from the original
446
+ "jpegQuality": false,
442
447
  // Output directory for your images files
443
448
  "to": "browser/img"
444
449
  }
@@ -561,6 +566,13 @@ Add the following to the exports in your `build-tools.config.json`:
561
566
  // The directory holding the source, each folder in it becomes a module (TypeDoc) or is scanned recursively (jsdoc).
562
567
  // jsdoc also takes a list of directories and files, for example ["functions", "gulpfile.base.mjs"]
563
568
  "from": "src",
569
+ // TypeDoc only: the git revision the "Defined in" source links point to. Left false, TypeDoc uses the current
570
+ // commit, so every commit changes every page; a branch name such as "main" keeps them the same until the
571
+ // documentation itself changes
572
+ "gitRevision": false,
573
+ // Generate the documentation as part of build (the default). Set it to false to only generate it when you run
574
+ // the docs task yourself (see below)
575
+ "inBuild": true,
564
576
  // The markdown file used as the front page of the documentation (a short overview of your project)
565
577
  "index": "MAIN.md",
566
578
  // 'typedoc' or 'jsdoc'. Left out (or false), it's inferred from typescript.enabled - 'typedoc' when your
@@ -582,6 +594,20 @@ While `docs.enabled` is on, `gulp build` generates the documentation instead of
582
594
  hand-written (a short overview which links to the documentation). If you also export `docs` from your gulpfile you can
583
595
  run it on its own with `gulp docs`, and can then add the script `"docs": "gulp docs"`.
584
596
 
597
+ To make generating the documentation a deliberate step instead, set `docs.inBuild` to `false`: `gulp build` then
598
+ generates neither the documentation nor the readme, and `gulp docs` is the only thing that writes to `docs.to`. This
599
+ suits a `docs.to` which points into another repository (a website, say): a build on a work-in-progress branch no
600
+ longer rewrites the published documentation, which you regenerate when you release instead. For example with
601
+ release-it, in `.release-it.json`:
602
+
603
+ ```json
604
+ {
605
+ "hooks": {
606
+ "after:bump": "npm run docs"
607
+ }
608
+ }
609
+ ```
610
+
585
611
  ### Browser automation (optional)
586
612
 
587
613
  Unlike the rest of this file, this isn't a `build-tools.config.json`-driven feature with its own task - it's a pair
package/dist/build.js CHANGED
@@ -1,73 +1,76 @@
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.build = void 0;
8
- var _bundle = require("./bundle.js");
9
- var _bundleMinify = require("./bundleMinify.js");
10
- var _partials = require("./partials.js");
11
- var _compileReadme = require("./compileReadme.js");
12
- var _typeDocs = require("./typeDocs.js");
13
- var _jsdocDocs = require("./jsdocDocs.js");
14
- var _distMinify = require("./distMinify.js");
15
- var _sourceLint = require("./sourceLint.js");
16
- var _gulp = require("gulp");
17
- var gulpConfig = _interopRequireWildcard(require("../gulp.config.js"));
18
- var _copyFonts = require("./copyFonts.js");
19
- var _images = require("./images.js");
20
- var _resolveDocsMethod = require("./partials/resolveDocsMethod.js");
21
- var _sass = require("./sass.js");
22
- var _testFull = require("./testFull.js");
23
- var cache;
24
- function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var r, c, a = Object.defineProperty, n = { __proto__: null, default: e }; if (Object(e) !== e) return n; if (cache || "function" != typeof WeakMap || (cache = new WeakMap()), cache) { if (cache.has(e)) return cache.get(e); cache.set(e, n); } for (c in e) "default" !== c && {}.hasOwnProperty.call(e, c) && ((r = a && Object.getOwnPropertyDescriptor(e, c)) && (r.get || r.set) ? a(n, c, r) : n[c] = e[c]); return n; }
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.build = void 0;
8
+ var _bundle = require("./bundle.js");
9
+ var _bundleMinify = require("./bundleMinify.js");
10
+ var _partials = require("./partials.js");
11
+ var _compileReadme = require("./compileReadme.js");
12
+ var _typeDocs = require("./typeDocs.js");
13
+ var _jsdocDocs = require("./jsdocDocs.js");
14
+ var _distMinify = require("./distMinify.js");
15
+ var _sourceLint = require("./sourceLint.js");
16
+ var _gulp = require("gulp");
17
+ var gulpConfig = _interopRequireWildcard(require("../gulp.config.js"));
18
+ var _copyFonts = require("./copyFonts.js");
19
+ var _images = require("./images.js");
20
+ var _resolveDocsMethod = require("./partials/resolveDocsMethod.js");
21
+ var _sass = require("./sass.js");
22
+ var _testFull = require("./testFull.js");
23
+ var cache;
24
+ function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var r, c, a = Object.defineProperty, n = { __proto__: null, default: e }; if (Object(e) !== e) return n; if (cache || "function" != typeof WeakMap || (cache = new WeakMap()), cache) { if (cache.has(e)) return cache.get(e); cache.set(e, n); } for (c in e) "default" !== c && {}.hasOwnProperty.call(e, c) && ((r = a && Object.getOwnPropertyDescriptor(e, c)) && (r.get || r.set) ? a(n, c, r) : n[c] = e[c]); return n; }
25
25
  /**
26
26
  * Runs several processes to build and validate the project.
27
27
  * Cleans and lints the source, distributes and minifies, bundles and minifies, creates the readme, then runs the
28
28
  * tests.
29
29
  * @memberOf module:js-build-tools
30
30
  * @returns {stream.Stream}
31
- */
32
- const build = (done = null) => {
33
- const buildActions = [(0, _partials.distSeries)(), _distMinify.distMinify];
34
- const generateDocs = gulpConfig.get('docs.enabled');
35
- const docsMethod = (0, _resolveDocsMethod.resolveDocsMethod)();
36
- if (generateDocs) {
37
- // The documentation is generated from the source itself, the readme is written by hand
38
- buildActions.push(docsMethod === 'jsdoc' ? _jsdocDocs.jsdocDocs : _typeDocs.typeDocs);
39
- } else if (gulpConfig.get('typescript.enabled')) {
40
- // For ts usage, we need to run the readme on the dist directly since that is where the .js files are located
41
- buildActions.push(_compileReadme.compileReadme);
42
- }
43
- if (gulpConfig.get('browser.enabled')) {
44
- // If not 'nodeOnly' then we also want to bundle for browser after we complete the dist directory
45
- buildActions.push(_bundle.bundle);
46
- buildActions.push(_bundleMinify.bundleMinify);
47
- }
48
- const runActions = [(0, _gulp.series)(...buildActions)];
49
- if (!generateDocs && !gulpConfig.get('typescript.enabled')) {
50
- // Since we didn't run this in series after dist because of typescript, we need to run it now. Potentially faster here.
51
- runActions.push(_compileReadme.compileReadme);
52
- }
53
- runActions.push(_testFull.testFull);
54
- if (gulpConfig.get('fonts.enabled')) {
55
- // Conditionally add Fonts process
56
- runActions.push(_copyFonts.copyFonts);
57
- }
58
- if (gulpConfig.get('images.enabled')) {
59
- // Conditionally add Images process
60
- runActions.push(_images.images);
61
- }
62
- if (gulpConfig.get('sass.enabled')) {
63
- // Conditionally add SASS process
64
- runActions.push(_sass.sass);
65
- }
66
- // clean has to finish before anything writes into the output folders - images, fonts and sass all write into
67
- // browser/, so running clean in parallel with them races (ENOTEMPTY errors from clean, or output deleted mid-write).
68
- // sourceLint fixes the real source in place before anything compiles from it (distSeries, tsFor inside it), so a
69
- // build never reads a mix of pre- and post-fix content; clean only touches dist/browser output, so the two can
70
- // run alongside each other.
71
- return (0, _gulp.series)((0, _gulp.parallel)(_partials.clean, _sourceLint.sourceLint), (0, _gulp.parallel)(...runActions))(done);
72
- };
31
+ */
32
+ const build = (done = null) => {
33
+ const buildActions = [(0, _partials.distSeries)(), _distMinify.distMinify];
34
+ const generateDocs = gulpConfig.get('docs.enabled');
35
+ const docsMethod = (0, _resolveDocsMethod.resolveDocsMethod)();
36
+ if (generateDocs) {
37
+ // The documentation is generated from the source itself, the readme is written by hand. With docs.inBuild off,
38
+ // generating it is left to the standalone docs task (gulp docs), so a build never rewrites it.
39
+ if (gulpConfig.get('docs.inBuild') !== false) {
40
+ buildActions.push(docsMethod === 'jsdoc' ? _jsdocDocs.jsdocDocs : _typeDocs.typeDocs);
41
+ }
42
+ } else if (gulpConfig.get('typescript.enabled')) {
43
+ // For ts usage, we need to run the readme on the dist directly since that is where the .js files are located
44
+ buildActions.push(_compileReadme.compileReadme);
45
+ }
46
+ if (gulpConfig.get('browser.enabled')) {
47
+ // If not 'nodeOnly' then we also want to bundle for browser after we complete the dist directory
48
+ buildActions.push(_bundle.bundle);
49
+ buildActions.push(_bundleMinify.bundleMinify);
50
+ }
51
+ const runActions = [(0, _gulp.series)(...buildActions)];
52
+ if (!generateDocs && !gulpConfig.get('typescript.enabled')) {
53
+ // Since we didn't run this in series after dist because of typescript, we need to run it now. Potentially faster here.
54
+ runActions.push(_compileReadme.compileReadme);
55
+ }
56
+ runActions.push(_testFull.testFull);
57
+ if (gulpConfig.get('fonts.enabled')) {
58
+ // Conditionally add Fonts process
59
+ runActions.push(_copyFonts.copyFonts);
60
+ }
61
+ if (gulpConfig.get('images.enabled')) {
62
+ // Conditionally add Images process
63
+ runActions.push(_images.images);
64
+ }
65
+ if (gulpConfig.get('sass.enabled')) {
66
+ // Conditionally add SASS process
67
+ runActions.push(_sass.sass);
68
+ }
69
+ // clean has to finish before anything writes into the output folders - images, fonts and sass all write into
70
+ // browser/, so running clean in parallel with them races (ENOTEMPTY errors from clean, or output deleted mid-write).
71
+ // sourceLint fixes the real source in place before anything compiles from it (distSeries, tsFor inside it), so a
72
+ // build never reads a mix of pre- and post-fix content; clean only touches dist/browser output, so the two can
73
+ // run alongside each other.
74
+ return (0, _gulp.series)((0, _gulp.parallel)(_partials.clean, _sourceLint.sourceLint), (0, _gulp.parallel)(...runActions))(done);
75
+ };
73
76
  exports.build = build;
package/dist/build.min.js CHANGED
@@ -1 +1 @@
1
- "use strict";require("core-js/modules/esnext.weak-map.delete-all.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.build=void 0;var cache,_bundle=require("./bundle.js"),_bundleMinify=require("./bundleMinify.js"),_partials=require("./partials.js"),_compileReadme=require("./compileReadme.js"),_typeDocs=require("./typeDocs.js"),_jsdocDocs=require("./jsdocDocs.js"),_distMinify=require("./distMinify.js"),_sourceLint=require("./sourceLint.js"),_gulp=require("gulp"),gulpConfig=_interopRequireWildcard(require("../gulp.config.js")),_copyFonts=require("./copyFonts.js"),_images=require("./images.js"),_resolveDocsMethod=require("./partials/resolveDocsMethod.js"),_sass=require("./sass.js"),_testFull=require("./testFull.js");function _interopRequireWildcard(e,s){if(!s&&e&&e.__esModule)return e;var i,r,t=Object.defineProperty,l={__proto__:null,default:e};if(Object(e)!==e)return l;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,l)}for(r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((i=t&&Object.getOwnPropertyDescriptor(e,r))&&(i.get||i.set)?t(l,r,i):l[r]=e[r]);return l}const build=(e=null)=>{const s=[(0,_partials.distSeries)(),_distMinify.distMinify],i=gulpConfig.get("docs.enabled"),r=(0,_resolveDocsMethod.resolveDocsMethod)();i?s.push("jsdoc"===r?_jsdocDocs.jsdocDocs:_typeDocs.typeDocs):gulpConfig.get("typescript.enabled")&&s.push(_compileReadme.compileReadme),gulpConfig.get("browser.enabled")&&(s.push(_bundle.bundle),s.push(_bundleMinify.bundleMinify));const t=[(0,_gulp.series)(...s)];return i||gulpConfig.get("typescript.enabled")||t.push(_compileReadme.compileReadme),t.push(_testFull.testFull),gulpConfig.get("fonts.enabled")&&t.push(_copyFonts.copyFonts),gulpConfig.get("images.enabled")&&t.push(_images.images),gulpConfig.get("sass.enabled")&&t.push(_sass.sass),(0,_gulp.series)((0,_gulp.parallel)(_partials.clean,_sourceLint.sourceLint),(0,_gulp.parallel)(...t))(e)};exports.build=build;
1
+ "use strict";require("core-js/modules/esnext.weak-map.delete-all.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.build=void 0;var cache,_bundle=require("./bundle.js"),_bundleMinify=require("./bundleMinify.js"),_partials=require("./partials.js"),_compileReadme=require("./compileReadme.js"),_typeDocs=require("./typeDocs.js"),_jsdocDocs=require("./jsdocDocs.js"),_distMinify=require("./distMinify.js"),_sourceLint=require("./sourceLint.js"),_gulp=require("gulp"),gulpConfig=_interopRequireWildcard(require("../gulp.config.js")),_copyFonts=require("./copyFonts.js"),_images=require("./images.js"),_resolveDocsMethod=require("./partials/resolveDocsMethod.js"),_sass=require("./sass.js"),_testFull=require("./testFull.js");function _interopRequireWildcard(e,s){if(!s&&e&&e.__esModule)return e;var i,r,t=Object.defineProperty,l={__proto__:null,default:e};if(Object(e)!==e)return l;if(cache||"function"!=typeof WeakMap||(cache=new WeakMap),cache){if(cache.has(e))return cache.get(e);cache.set(e,l)}for(r in e)"default"!==r&&{}.hasOwnProperty.call(e,r)&&((i=t&&Object.getOwnPropertyDescriptor(e,r))&&(i.get||i.set)?t(l,r,i):l[r]=e[r]);return l}const build=(e=null)=>{const s=[(0,_partials.distSeries)(),_distMinify.distMinify],i=gulpConfig.get("docs.enabled"),r=(0,_resolveDocsMethod.resolveDocsMethod)();i?!1!==gulpConfig.get("docs.inBuild")&&s.push("jsdoc"===r?_jsdocDocs.jsdocDocs:_typeDocs.typeDocs):gulpConfig.get("typescript.enabled")&&s.push(_compileReadme.compileReadme),gulpConfig.get("browser.enabled")&&(s.push(_bundle.bundle),s.push(_bundleMinify.bundleMinify));const t=[(0,_gulp.series)(...s)];return i||gulpConfig.get("typescript.enabled")||t.push(_compileReadme.compileReadme),t.push(_testFull.testFull),gulpConfig.get("fonts.enabled")&&t.push(_copyFonts.copyFonts),gulpConfig.get("images.enabled")&&t.push(_images.images),gulpConfig.get("sass.enabled")&&t.push(_sass.sass),(0,_gulp.series)((0,_gulp.parallel)(_partials.clean,_sourceLint.sourceLint),(0,_gulp.parallel)(...t))(e)};exports.build=build;
@@ -4,7 +4,7 @@ require("core-js/modules/esnext.weak-map.delete-all.js");
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.loadImagemin = exports.imagesFor = void 0;
7
+ exports.loadImageminPlugins = exports.loadImagemin = exports.imagesFor = exports.imageminPlugins = void 0;
8
8
  var _gulpCache = _interopRequireDefault(require("gulp-cache"));
9
9
  var _loadPeer = require("./loadPeer.js");
10
10
  var _gulp = require("gulp");
@@ -33,6 +33,36 @@ const loadImagemin = (projectPath = process.cwd()) => {
33
33
  return loaded && (loaded.__esModule || loaded.default) ? loaded.default : loaded;
34
34
  };
35
35
 
36
+ /**
37
+ * Load the plugins `gulp-imagemin` exports by name (gifsicle, mozjpeg, optipng and svgo), each a promise of a plugin
38
+ * factory, for when they need options of their own (see images.jpegQuality). Resolved like {@link loadImagemin}.
39
+ * @memberOf module:partials
40
+ * @param {string} [projectPath=process.cwd()] - The project directory to resolve `gulp-imagemin` from.
41
+ * @returns {Object<string, Promise<Function>>} The plugin factories, by name.
42
+ */
43
+ exports.loadImagemin = loadImagemin;
44
+ const loadImageminPlugins = (projectPath = process.cwd()) => (0, _loadPeer.requirePeer)('gulp-imagemin', {
45
+ task: 'images',
46
+ installHint,
47
+ esmOnlyMessage: `gulp-imagemin could not be loaded because it is ESM-only. ${installHint}`,
48
+ projectPath
49
+ });
50
+
51
+ // The plugins imagemin runs when it isn't given any, in its own order
52
+ exports.loadImageminPlugins = loadImageminPlugins;
53
+ const defaultPluginNames = ['gifsicle', 'mozjpeg', 'optipng', 'svgo'];
54
+
55
+ /**
56
+ * The plugins imagemin uses by default, with mozjpeg set to the given JPEG quality instead of its own default (75).
57
+ * @memberOf module:partials
58
+ * @param {number} jpegQuality - The mozjpeg quality, 0 to 100.
59
+ * @param {Object<string, Promise<Function>>} [plugins=loadImageminPlugins()] - The plugin factories, by name.
60
+ * @returns {Array<Promise<Function>>} The plugins, in imagemin's default order.
61
+ */
62
+ const imageminPlugins = (jpegQuality, plugins = loadImageminPlugins()) => defaultPluginNames.map(name => Promise.resolve(plugins[name]).then(makePlugin => name === 'mozjpeg' ? makePlugin({
63
+ quality: jpegQuality
64
+ }) : makePlugin()));
65
+
36
66
  /**
37
67
  * Move and optimize images into the browser directory.
38
68
  * @memberOf module:partials
@@ -40,18 +70,25 @@ const loadImagemin = (projectPath = process.cwd()) => {
40
70
  * @param {string} [imageDest=dest/image/folder]
41
71
  * @return {stream.Stream}
42
72
  */
43
- exports.loadImagemin = loadImagemin;
73
+ exports.imageminPlugins = imageminPlugins;
44
74
  const imagesFor = (imageSrc = gulpConfig.get('images.from'), imageDest = gulpConfig.get('images.to')) => {
45
75
  const imagemin = loadImagemin();
76
+ const jpegQuality = gulpConfig.get('images.jpegQuality');
77
+ // Left unset, imagemin runs its default plugins with their own defaults
78
+ const plugins = jpegQuality ? imageminPlugins(jpegQuality) : undefined;
46
79
  // Gulp 5 reads files as utf8 text unless told otherwise, which corrupts binary images (and makes gulp-cache drop them).
47
80
  return (0, _gulp.src)(imageSrc, {
48
81
  encoding: false
49
82
  }).pipe(
50
83
  // Caching images that ran through imagemin
51
- (0, _gulpCache.default)(imagemin({
84
+ (0, _gulpCache.default)(imagemin(plugins, {
52
85
  interlaced: true,
53
86
  silent: true,
54
87
  verbose: false
55
- }))).pipe((0, _gulp.dest)(imageDest));
88
+ }),
89
+ // gulp-cache keys on the file's contents alone, so keep each quality's results apart
90
+ {
91
+ name: jpegQuality ? `jpeg-quality-${jpegQuality}` : 'default'
92
+ })).pipe((0, _gulp.dest)(imageDest));
56
93
  };
57
94
  exports.imagesFor = imagesFor;
@@ -1,31 +1,31 @@
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.writeDocEntries = exports.typeDocsFor = exports.loadTypeDoc = exports.describeFolder = exports.describeFile = exports.describeExports = void 0;
8
- require("core-js/modules/es.array.includes.js");
9
- require("core-js/modules/es.json.stringify.js");
10
- require("core-js/modules/esnext.iterator.constructor.js");
11
- require("core-js/modules/esnext.iterator.filter.js");
12
- require("core-js/modules/esnext.iterator.find.js");
13
- require("core-js/modules/esnext.iterator.flat-map.js");
14
- require("core-js/modules/esnext.iterator.for-each.js");
15
- require("core-js/modules/esnext.iterator.map.js");
16
- require("core-js/modules/esnext.iterator.some.js");
17
- var _nodeFs = _interopRequireDefault(require("node:fs"));
18
- var _nodeOs = _interopRequireDefault(require("node:os"));
19
- var _nodePath = _interopRequireDefault(require("node:path"));
20
- var gulpConfig = _interopRequireWildcard(require("../../gulp.config.js"));
21
- var _loadPeer = require("./loadPeer.js");
22
- var _tsFor = require("./tsFor.js");
23
- var _removeDirectory = require("./removeDirectory.js");
24
- var cache;
25
- function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var r, c, a = Object.defineProperty, n = { __proto__: null, default: e }; if (Object(e) !== e) return n; if (cache || "function" != typeof WeakMap || (cache = new WeakMap()), cache) { if (cache.has(e)) return cache.get(e); cache.set(e, n); } for (c in e) "default" !== c && {}.hasOwnProperty.call(e, c) && ((r = a && Object.getOwnPropertyDescriptor(e, c)) && (r.get || r.set) ? a(n, c, r) : n[c] = e[c]); return n; }
26
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
27
- const installHint = 'Install it in your project with: npm install --save-dev typedoc';
28
-
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.writeDocEntries = exports.typeDocsFor = exports.loadTypeDoc = exports.describeFolder = exports.describeFile = exports.describeExports = void 0;
8
+ require("core-js/modules/es.array.includes.js");
9
+ require("core-js/modules/es.json.stringify.js");
10
+ require("core-js/modules/esnext.iterator.constructor.js");
11
+ require("core-js/modules/esnext.iterator.filter.js");
12
+ require("core-js/modules/esnext.iterator.find.js");
13
+ require("core-js/modules/esnext.iterator.flat-map.js");
14
+ require("core-js/modules/esnext.iterator.for-each.js");
15
+ require("core-js/modules/esnext.iterator.map.js");
16
+ require("core-js/modules/esnext.iterator.some.js");
17
+ var _nodeFs = _interopRequireDefault(require("node:fs"));
18
+ var _nodeOs = _interopRequireDefault(require("node:os"));
19
+ var _nodePath = _interopRequireDefault(require("node:path"));
20
+ var gulpConfig = _interopRequireWildcard(require("../../gulp.config.js"));
21
+ var _loadPeer = require("./loadPeer.js");
22
+ var _tsFor = require("./tsFor.js");
23
+ var _removeDirectory = require("./removeDirectory.js");
24
+ var cache;
25
+ function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var r, c, a = Object.defineProperty, n = { __proto__: null, default: e }; if (Object(e) !== e) return n; if (cache || "function" != typeof WeakMap || (cache = new WeakMap()), cache) { if (cache.has(e)) return cache.get(e); cache.set(e, n); } for (c in e) "default" !== c && {}.hasOwnProperty.call(e, c) && ((r = a && Object.getOwnPropertyDescriptor(e, c)) && (r.get || r.set) ? a(n, c, r) : n[c] = e[c]); return n; }
26
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
27
+ const installHint = 'Install it in your project with: npm install --save-dev typedoc';
28
+
29
29
  /**
30
30
  * Find the TypeDoc of the project being built (an optional peer dependency, only needed when the docs task is
31
31
  * enabled) and load it, so projects which do not document TypeScript never need it installed.
@@ -33,87 +33,87 @@ const installHint = 'Install it in your project with: npm install --save-dev typ
33
33
  * @param {string} [projectPath=process.cwd()] The folder of the project (which has the package.json) being built.
34
34
  * @returns {Promise<Object>} The TypeDoc module.
35
35
  * @throws {Error} With install instructions when typedoc is not installed.
36
- */
37
- const loadTypeDoc = (projectPath = process.cwd()) => (0, _loadPeer.importPeer)('typedoc', {
38
- task: 'docs',
39
- installHint,
40
- projectPath
41
- });
42
- exports.loadTypeDoc = loadTypeDoc;
43
- const isSource = fileName => /\.(ts|tsx|mts|cts)$/.test(fileName) && !/\.d\.[cm]?ts$/.test(fileName) && !/\.(test|spec)\./.test(fileName);
44
- const listSources = dirPath => _nodeFs.default.readdirSync(dirPath, {
45
- withFileTypes: true
46
- }).filter(entry => entry.isFile() && isSource(entry.name)).map(entry => _nodePath.default.join(dirPath, entry.name));
47
-
48
- // Every folder below dirPath (at any depth), named by its path from dirPath, with the source files directly in it
49
- const listFolders = (dirPath, prefix = '') => _nodeFs.default.readdirSync(dirPath, {
50
- withFileTypes: true
51
- }).filter(entry => entry.isDirectory() && entry.name !== 'node_modules').flatMap(entry => {
52
- const folderPath = _nodePath.default.join(dirPath, entry.name);
53
- const name = `${prefix}${entry.name}`;
54
- return [{
55
- name,
56
- path: folderPath,
57
- files: listSources(folderPath)
58
- }, ...listFolders(folderPath, `${name}/`)];
59
- });
60
- const toIdentifier = fileName => fileName.replace(/[^\w$]+(\w)?/g, (match, letter) => (letter || '').toUpperCase()).replace(/^(\d)/, '_$1');
61
- const toModulePath = filePath => filePath.replace(/\.[cm]?tsx?$/, '').split(_nodePath.default.sep).join('/');
62
-
36
+ */
37
+ const loadTypeDoc = (projectPath = process.cwd()) => (0, _loadPeer.importPeer)('typedoc', {
38
+ task: 'docs',
39
+ installHint,
40
+ projectPath
41
+ });
42
+ exports.loadTypeDoc = loadTypeDoc;
43
+ const isSource = fileName => /\.(ts|tsx|mts|cts)$/.test(fileName) && !/\.d\.[cm]?ts$/.test(fileName) && !/\.(test|spec)\./.test(fileName);
44
+ const listSources = dirPath => _nodeFs.default.readdirSync(dirPath, {
45
+ withFileTypes: true
46
+ }).filter(entry => entry.isFile() && isSource(entry.name)).map(entry => _nodePath.default.join(dirPath, entry.name));
47
+
48
+ // Every folder below dirPath (at any depth), named by its path from dirPath, with the source files directly in it
49
+ const listFolders = (dirPath, prefix = '') => _nodeFs.default.readdirSync(dirPath, {
50
+ withFileTypes: true
51
+ }).filter(entry => entry.isDirectory() && entry.name !== 'node_modules').flatMap(entry => {
52
+ const folderPath = _nodePath.default.join(dirPath, entry.name);
53
+ const name = `${prefix}${entry.name}`;
54
+ return [{
55
+ name,
56
+ path: folderPath,
57
+ files: listSources(folderPath)
58
+ }, ...listFolders(folderPath, `${name}/`)];
59
+ });
60
+ const toIdentifier = fileName => fileName.replace(/[^\w$]+(\w)?/g, (match, letter) => (letter || '').toUpperCase()).replace(/^(\d)/, '_$1');
61
+ const toModulePath = filePath => filePath.replace(/\.[cm]?tsx?$/, '').split(_nodePath.default.sep).join('/');
62
+
63
63
  /**
64
64
  * Describe what a source file exports: whether it has a default export, and whether it has any named exports.
65
65
  * @memberOf module:partials
66
66
  * @param {Object} ts - The TypeScript compiler API.
67
67
  * @param {string} filePath
68
68
  * @returns {{hasDefault: boolean, hasNamed: boolean}}
69
- */
70
- const describeExports = (ts, filePath) => {
71
- const source = ts.createSourceFile(filePath, _nodeFs.default.readFileSync(filePath, 'utf8'), ts.ScriptTarget.Latest);
72
- let hasDefault = false;
73
- let hasNamed = false;
74
- source.statements.forEach(statement => {
75
- if (ts.isExportAssignment(statement)) {
76
- hasDefault = hasDefault || !statement.isExportEquals;
77
- return;
78
- }
79
- if (ts.isExportDeclaration(statement)) {
80
- const names = statement.exportClause && ts.isNamedExports(statement.exportClause) ? statement.exportClause.elements : null;
81
- if (names && names.some(element => element.name.text === 'default')) {
82
- hasDefault = true;
83
- }
84
- if (!names || names.some(element => element.name.text !== 'default')) {
85
- hasNamed = true;
86
- }
87
- return;
88
- }
89
- const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) || [] : [];
90
- if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
91
- if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)) {
92
- hasDefault = true;
93
- } else {
94
- hasNamed = true;
95
- }
96
- }
97
- });
98
- return {
99
- hasDefault,
100
- hasNamed
101
- };
102
- };
103
-
69
+ */
70
+ const describeExports = (ts, filePath) => {
71
+ const source = ts.createSourceFile(filePath, _nodeFs.default.readFileSync(filePath, 'utf8'), ts.ScriptTarget.Latest);
72
+ let hasDefault = false;
73
+ let hasNamed = false;
74
+ source.statements.forEach(statement => {
75
+ if (ts.isExportAssignment(statement)) {
76
+ hasDefault = hasDefault || !statement.isExportEquals;
77
+ return;
78
+ }
79
+ if (ts.isExportDeclaration(statement)) {
80
+ const names = statement.exportClause && ts.isNamedExports(statement.exportClause) ? statement.exportClause.elements : null;
81
+ if (names && names.some(element => element.name.text === 'default')) {
82
+ hasDefault = true;
83
+ }
84
+ if (!names || names.some(element => element.name.text !== 'default')) {
85
+ hasNamed = true;
86
+ }
87
+ return;
88
+ }
89
+ const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) || [] : [];
90
+ if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
91
+ if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)) {
92
+ hasDefault = true;
93
+ } else {
94
+ hasNamed = true;
95
+ }
96
+ }
97
+ });
98
+ return {
99
+ hasDefault,
100
+ hasNamed
101
+ };
102
+ };
103
+
104
104
  /**
105
105
  * The description of a folder for its module: the first doc comment of the folder's index file, without its tags
106
106
  * (@module, @file, ...), or an empty string when there is none.
107
107
  * @memberOf module:partials
108
108
  * @param {string} folderPath
109
109
  * @returns {string}
110
- */
111
- exports.describeExports = describeExports;
112
- const describeFolder = folderPath => {
113
- const indexFile = ['index.ts', 'index.tsx', 'index.mts', 'index.cts'].map(name => _nodePath.default.join(folderPath, name)).find(file => _nodeFs.default.existsSync(file));
114
- return indexFile ? describeFile(indexFile) : '';
115
- };
116
-
110
+ */
111
+ exports.describeExports = describeExports;
112
+ const describeFolder = folderPath => {
113
+ const indexFile = ['index.ts', 'index.tsx', 'index.mts', 'index.cts'].map(name => _nodePath.default.join(folderPath, name)).find(file => _nodeFs.default.existsSync(file));
114
+ return indexFile ? describeFile(indexFile) : '';
115
+ };
116
+
117
117
  /**
118
118
  * The description of a source file for its module: its first doc comment without the tags (@module, @file, ...), when
119
119
  * that comment is a header (it is followed by an import or export, so it does not document a declaration itself), or
@@ -121,18 +121,18 @@ const describeFolder = folderPath => {
121
121
  * @memberOf module:partials
122
122
  * @param {string} filePath
123
123
  * @returns {string}
124
- */
125
- exports.describeFolder = describeFolder;
126
- const describeFile = filePath => {
127
- const comment = _nodeFs.default.readFileSync(filePath, 'utf8').match(/^\s*\/\*\*([\s\S]*?)\*\/\s*(import\b|export\s+(\*|\{[^}]*\}\s+from)|$)/);
128
- if (!comment) {
129
- return '';
130
- }
131
- const lines = comment[1].split('\n').map(line => line.replace(/^\s*\* ?/, '').trimEnd());
132
- const tagAt = lines.findIndex(line => /^@\w+/.test(line));
133
- return (tagAt === -1 ? lines : lines.slice(0, tagAt)).join('\n').trim();
134
- };
135
-
124
+ */
125
+ exports.describeFolder = describeFolder;
126
+ const describeFile = filePath => {
127
+ const comment = _nodeFs.default.readFileSync(filePath, 'utf8').match(/^\s*\/\*\*([\s\S]*?)\*\/\s*(import\b|export\s+(\*|\{[^}]*\}\s+from)|$)/);
128
+ if (!comment) {
129
+ return '';
130
+ }
131
+ const lines = comment[1].split('\n').map(line => line.replace(/^\s*\* ?/, '').trimEnd());
132
+ const tagAt = lines.findIndex(line => /^@\w+/.test(line));
133
+ return (tagAt === -1 ? lines : lines.slice(0, tagAt)).join('\n').trim();
134
+ };
135
+
136
136
  /**
137
137
  * Write one entry file per folder of the source directory into the entry directory. TypeDoc makes a module of every
138
138
  * entry file, so the modules of the documentation mirror the folders. Nested folders are modules of their own, named
@@ -147,58 +147,58 @@ const describeFile = filePath => {
147
147
  * @param {string} srcDir - The directory holding the TypeScript source.
148
148
  * @param {string} entryDir - The directory to write the entry files into.
149
149
  * @returns {Array<string>} The paths of the entry files.
150
- */
151
- exports.describeFile = describeFile;
152
- const writeDocEntries = (ts, srcDir, entryDir) => {
153
- const absoluteSrc = _nodePath.default.resolve(srcDir);
154
- _nodeFs.default.mkdirSync(entryDir, {
155
- recursive: true
156
- });
157
- const folders = listFolders(absoluteSrc).filter(folder => folder.files.some(file => !/^index\./.test(_nodePath.default.basename(file))));
158
- const rootFiles = listSources(absoluteSrc).filter(file => !/^(index|main|browser)\./.test(_nodePath.default.basename(file)));
159
- // Folders make modules, and so do the files which sit beside them (except the barrels: index, main and browser, the
160
- // browser bundle's entry). A source directory with no folders gets a single module called index which holds its files.
161
- const groups = folders.length ? [...folders, ...rootFiles.map(file => ({
162
- name: toIdentifier(_nodePath.default.basename(file).replace(/\.[cm]?tsx?$/, '')),
163
- files: [file],
164
- description: describeFile(file)
165
- })).filter(group => !folders.some(folder => folder.name === group.name))] : [{
166
- name: 'index',
167
- path: absoluteSrc,
168
- files: rootFiles
169
- }];
170
- return groups.map(group => {
171
- const lines = group.files.filter(file => !/^index\./.test(_nodePath.default.basename(file))).sort().flatMap(file => {
172
- const modulePath = toModulePath(file);
173
- const {
174
- hasDefault,
175
- hasNamed
176
- } = describeExports(ts, file);
177
- const fileName = _nodePath.default.basename(file).replace(/\.[cm]?tsx?$/, '');
178
- return [...(hasDefault ? [`export { default as ${toIdentifier(fileName)} } from '${modulePath}'`] : []), ...(hasNamed ? [`export * from '${modulePath}'`] : [])];
179
- });
180
- const description = group.description ?? (group.path ? describeFolder(group.path) : '');
181
- // TypeDoc takes the first comment of an entry file which has the @module tag as the description of the module
182
- // A nested folder's module is named by its path (utilities/parsers), so it is not mistaken for its parent's
183
- const moduleName = group.name.includes('/') ? ` ${group.name}` : '';
184
- const moduleComment = description || moduleName ? `/**\n${description ? description.split('\n').map(line => ` * ${line}`.trimEnd()).join('\n') + '\n' : ''} * @module${moduleName}\n */\n\n` : '';
185
- const entryPath = _nodePath.default.join(entryDir, `${group.name}.ts`);
186
- _nodeFs.default.mkdirSync(_nodePath.default.dirname(entryPath), {
187
- recursive: true
188
- });
189
- _nodeFs.default.writeFileSync(entryPath, moduleComment + lines.join('\n') + '\n');
190
- return entryPath;
191
- });
192
- };
193
- exports.writeDocEntries = writeDocEntries;
194
- const projectName = () => {
195
- try {
196
- return JSON.parse(_nodeFs.default.readFileSync(_nodePath.default.join(process.cwd(), 'package.json'), 'utf8')).name;
197
- } catch (error) {
198
- return _nodePath.default.basename(process.cwd());
199
- }
200
- };
201
-
150
+ */
151
+ exports.describeFile = describeFile;
152
+ const writeDocEntries = (ts, srcDir, entryDir) => {
153
+ const absoluteSrc = _nodePath.default.resolve(srcDir);
154
+ _nodeFs.default.mkdirSync(entryDir, {
155
+ recursive: true
156
+ });
157
+ const folders = listFolders(absoluteSrc).filter(folder => folder.files.some(file => !/^index\./.test(_nodePath.default.basename(file))));
158
+ const rootFiles = listSources(absoluteSrc).filter(file => !/^(index|main|browser)\./.test(_nodePath.default.basename(file)));
159
+ // Folders make modules, and so do the files which sit beside them (except the barrels: index, main and browser, the
160
+ // browser bundle's entry). A source directory with no folders gets a single module called index which holds its files.
161
+ const groups = folders.length ? [...folders, ...rootFiles.map(file => ({
162
+ name: toIdentifier(_nodePath.default.basename(file).replace(/\.[cm]?tsx?$/, '')),
163
+ files: [file],
164
+ description: describeFile(file)
165
+ })).filter(group => !folders.some(folder => folder.name === group.name))] : [{
166
+ name: 'index',
167
+ path: absoluteSrc,
168
+ files: rootFiles
169
+ }];
170
+ return groups.map(group => {
171
+ const lines = group.files.filter(file => !/^index\./.test(_nodePath.default.basename(file))).sort().flatMap(file => {
172
+ const modulePath = toModulePath(file);
173
+ const {
174
+ hasDefault,
175
+ hasNamed
176
+ } = describeExports(ts, file);
177
+ const fileName = _nodePath.default.basename(file).replace(/\.[cm]?tsx?$/, '');
178
+ return [...(hasDefault ? [`export { default as ${toIdentifier(fileName)} } from '${modulePath}'`] : []), ...(hasNamed ? [`export * from '${modulePath}'`] : [])];
179
+ });
180
+ const description = group.description ?? (group.path ? describeFolder(group.path) : '');
181
+ // TypeDoc takes the first comment of an entry file which has the @module tag as the description of the module
182
+ // A nested folder's module is named by its path (utilities/parsers), so it is not mistaken for its parent's
183
+ const moduleName = group.name.includes('/') ? ` ${group.name}` : '';
184
+ const moduleComment = description || moduleName ? `/**\n${description ? description.split('\n').map(line => ` * ${line}`.trimEnd()).join('\n') + '\n' : ''} * @module${moduleName}\n */\n\n` : '';
185
+ const entryPath = _nodePath.default.join(entryDir, `${group.name}.ts`);
186
+ _nodeFs.default.mkdirSync(_nodePath.default.dirname(entryPath), {
187
+ recursive: true
188
+ });
189
+ _nodeFs.default.writeFileSync(entryPath, moduleComment + lines.join('\n') + '\n');
190
+ return entryPath;
191
+ });
192
+ };
193
+ exports.writeDocEntries = writeDocEntries;
194
+ const projectName = () => {
195
+ try {
196
+ return JSON.parse(_nodeFs.default.readFileSync(_nodePath.default.join(process.cwd(), 'package.json'), 'utf8')).name;
197
+ } catch (error) {
198
+ return _nodePath.default.basename(process.cwd());
199
+ }
200
+ };
201
+
202
202
  /**
203
203
  * Generate the HTML documentation of a TypeScript project with TypeDoc, straight from the TypeScript source, so the
204
204
  * types (and the comments on them) are documented from where they are defined. The modules mirror the folders of the
@@ -209,51 +209,57 @@ const projectName = () => {
209
209
  * @param {string} [outPath=gulpConfig.get('docs.to')] - The directory the documentation is generated into (cleared first).
210
210
  * @param {function(string): Promise<Object>} [loadDocs] - Resolves the TypeDoc module, tests can inject a stand-in.
211
211
  * @returns {Function} A gulp task, it accepts an optional callback and returns a promise.
212
- */
213
- const typeDocsFor = (srcPath = gulpConfig.get('docs.from'), outPath = gulpConfig.get('docs.to'), loadDocs = loadTypeDoc) => {
214
- if (gulpConfig.get('docs.enabled') === false) {
215
- return () => {};
216
- }
217
- return done => {
218
- const generate = async () => {
219
- const ts = (0, _tsFor.loadTypescript)();
220
- const {
221
- Application
222
- } = await loadDocs();
223
- const workDir = _nodeFs.default.mkdtempSync(_nodePath.default.join(_nodeOs.default.tmpdir(), 'typedocs-'));
224
- try {
225
- const entryPoints = writeDocEntries(ts, srcPath, _nodePath.default.join(workDir, 'entries'));
226
- // The entry files live outside the project, so the tsconfig which is used for the documentation extends the
227
- // one of the project and adds the entries to the files it includes.
228
- const projectConfig = gulpConfig.get('docs.tsconfig') || gulpConfig.get('typescript.config');
229
- const tsconfig = _nodePath.default.join(workDir, 'tsconfig.json');
230
- _nodeFs.default.writeFileSync(tsconfig, JSON.stringify({
231
- ...(projectConfig ? {
232
- extends: _nodePath.default.resolve(projectConfig)
233
- } : {}),
234
- include: [_nodePath.default.join(workDir, 'entries', '**', '*.ts').split(_nodePath.default.sep).join('/'), _nodePath.default.resolve(srcPath).split(_nodePath.default.sep).join('/') + '/**/*.ts'],
235
- exclude: [_nodePath.default.resolve(srcPath).split(_nodePath.default.sep).join('/') + '/**/*.test.*']
236
- }));
237
- const indexFile = gulpConfig.get('docs.index');
238
- await (0, _removeDirectory.removeDirectory)(outPath);
239
- const app = await Application.bootstrap({
240
- entryPoints,
241
- tsconfig,
242
- name: gulpConfig.get('docs.title') || projectName(),
243
- readme: indexFile && _nodeFs.default.existsSync(indexFile) ? indexFile : 'none',
244
- logLevel: gulpConfig.get('docs.logLevel') || 'Warn',
245
- skipErrorChecking: true
246
- });
247
- const project = await app.convert();
248
- if (!project) {
249
- throw new Error('TypeDoc could not read the TypeScript source, see the errors above.');
250
- }
251
- await app.generateDocs(project, outPath);
252
- } finally {
253
- await (0, _removeDirectory.removeDirectory)(workDir);
254
- }
255
- };
256
- return typeof done === 'function' ? generate().then(() => done(), done) : generate();
257
- };
258
- };
212
+ */
213
+ const typeDocsFor = (srcPath = gulpConfig.get('docs.from'), outPath = gulpConfig.get('docs.to'), loadDocs = loadTypeDoc) => {
214
+ if (gulpConfig.get('docs.enabled') === false) {
215
+ return () => {};
216
+ }
217
+ return done => {
218
+ const generate = async () => {
219
+ const ts = (0, _tsFor.loadTypescript)();
220
+ const {
221
+ Application
222
+ } = await loadDocs();
223
+ const workDir = _nodeFs.default.mkdtempSync(_nodePath.default.join(_nodeOs.default.tmpdir(), 'typedocs-'));
224
+ try {
225
+ const entryPoints = writeDocEntries(ts, srcPath, _nodePath.default.join(workDir, 'entries'));
226
+ // The entry files live outside the project, so the tsconfig which is used for the documentation extends the
227
+ // one of the project and adds the entries to the files it includes.
228
+ const projectConfig = gulpConfig.get('docs.tsconfig') || gulpConfig.get('typescript.config');
229
+ const tsconfig = _nodePath.default.join(workDir, 'tsconfig.json');
230
+ _nodeFs.default.writeFileSync(tsconfig, JSON.stringify({
231
+ ...(projectConfig ? {
232
+ extends: _nodePath.default.resolve(projectConfig)
233
+ } : {}),
234
+ include: [_nodePath.default.join(workDir, 'entries', '**', '*.ts').split(_nodePath.default.sep).join('/'), _nodePath.default.resolve(srcPath).split(_nodePath.default.sep).join('/') + '/**/*.ts'],
235
+ exclude: [_nodePath.default.resolve(srcPath).split(_nodePath.default.sep).join('/') + '/**/*.test.*']
236
+ }));
237
+ const indexFile = gulpConfig.get('docs.index');
238
+ const gitRevision = gulpConfig.get('docs.gitRevision');
239
+ await (0, _removeDirectory.removeDirectory)(outPath);
240
+ const app = await Application.bootstrap({
241
+ entryPoints,
242
+ tsconfig,
243
+ name: gulpConfig.get('docs.title') || projectName(),
244
+ readme: indexFile && _nodeFs.default.existsSync(indexFile) ? indexFile : 'none',
245
+ logLevel: gulpConfig.get('docs.logLevel') || 'Warn',
246
+ skipErrorChecking: true,
247
+ // Left out, TypeDoc links each declaration to its source at the current commit, so every commit changes
248
+ // every page; a branch name keeps the pages the same until the documentation itself changes.
249
+ ...(gitRevision ? {
250
+ gitRevision
251
+ } : {})
252
+ });
253
+ const project = await app.convert();
254
+ if (!project) {
255
+ throw new Error('TypeDoc could not read the TypeScript source, see the errors above.');
256
+ }
257
+ await app.generateDocs(project, outPath);
258
+ } finally {
259
+ await (0, _removeDirectory.removeDirectory)(workDir);
260
+ }
261
+ };
262
+ return typeof done === 'function' ? generate().then(() => done(), done) : generate();
263
+ };
264
+ };
259
265
  exports.typeDocsFor = typeDocsFor;
@@ -26,8 +26,11 @@ export const build = (done = null) => {
26
26
  const generateDocs = gulpConfig.get('docs.enabled')
27
27
  const docsMethod = resolveDocsMethod()
28
28
  if (generateDocs) {
29
- // The documentation is generated from the source itself, the readme is written by hand
30
- buildActions.push(docsMethod === 'jsdoc' ? jsdocDocs : typeDocs)
29
+ // The documentation is generated from the source itself, the readme is written by hand. With docs.inBuild off,
30
+ // generating it is left to the standalone docs task (gulp docs), so a build never rewrites it.
31
+ if (gulpConfig.get('docs.inBuild') !== false) {
32
+ buildActions.push(docsMethod === 'jsdoc' ? jsdocDocs : typeDocs)
33
+ }
31
34
  } else if (gulpConfig.get('typescript.enabled')) {
32
35
  // For ts usage, we need to run the readme on the dist directly since that is where the .js files are located
33
36
  buildActions.push(compileReadme)
@@ -676,6 +676,29 @@ describe('build', () => {
676
676
  })
677
677
  }, 30000)
678
678
 
679
+ test('with docs.inBuild off, build generates neither the documentation nor the readme', done => {
680
+ const readmePath = gulpConfig.get('readme.to')
681
+ const readmeFile = gulpConfig.get('readme.file')
682
+ fs.rmSync(`${readmePath}/${readmeFile}`, { force: true })
683
+ typeDocs.mockClear()
684
+ jsdocDocs.mockClear()
685
+ gulpConfig.set('browser.enabled', false)
686
+ gulpConfig.set('typescript.enabled', false)
687
+ gulpConfig.set('docs.enabled', true)
688
+ gulpConfig.set('docs.inBuild', false)
689
+ expect.assertions(4)
690
+ build(() => {
691
+ gulpConfig.set('docs.enabled', false)
692
+ gulpConfig.set('docs.inBuild', true)
693
+ expect(jsdocDocs).not.toHaveBeenCalled()
694
+ expect(typeDocs).not.toHaveBeenCalled()
695
+ // The readme is still written by hand while docs.enabled is on
696
+ expect(fs.existsSync(`${readmePath}/${readmeFile}`)).toBe(false)
697
+ expect(testFull).toHaveBeenCalled()
698
+ done()
699
+ })
700
+ }, 30000)
701
+
679
702
  test('docs.method overrides the typescript.enabled inference either way', done => {
680
703
  const srcPath = gulpConfig.get('srcPath')
681
704
  const rootPath = gulpConfig.get('rootPath')
@@ -25,6 +25,34 @@ export const loadImagemin = (projectPath = process.cwd()) => {
25
25
  return loaded && (loaded.__esModule || loaded.default) ? loaded.default : loaded
26
26
  }
27
27
 
28
+ /**
29
+ * Load the plugins `gulp-imagemin` exports by name (gifsicle, mozjpeg, optipng and svgo), each a promise of a plugin
30
+ * factory, for when they need options of their own (see images.jpegQuality). Resolved like {@link loadImagemin}.
31
+ * @memberOf module:partials
32
+ * @param {string} [projectPath=process.cwd()] - The project directory to resolve `gulp-imagemin` from.
33
+ * @returns {Object<string, Promise<Function>>} The plugin factories, by name.
34
+ */
35
+ export const loadImageminPlugins = (projectPath = process.cwd()) => requirePeer('gulp-imagemin', {
36
+ task: 'images',
37
+ installHint,
38
+ esmOnlyMessage: `gulp-imagemin could not be loaded because it is ESM-only. ${installHint}`,
39
+ projectPath
40
+ })
41
+
42
+ // The plugins imagemin runs when it isn't given any, in its own order
43
+ const defaultPluginNames = ['gifsicle', 'mozjpeg', 'optipng', 'svgo']
44
+
45
+ /**
46
+ * The plugins imagemin uses by default, with mozjpeg set to the given JPEG quality instead of its own default (75).
47
+ * @memberOf module:partials
48
+ * @param {number} jpegQuality - The mozjpeg quality, 0 to 100.
49
+ * @param {Object<string, Promise<Function>>} [plugins=loadImageminPlugins()] - The plugin factories, by name.
50
+ * @returns {Array<Promise<Function>>} The plugins, in imagemin's default order.
51
+ */
52
+ export const imageminPlugins = (jpegQuality, plugins = loadImageminPlugins()) => defaultPluginNames.map(
53
+ name => Promise.resolve(plugins[name]).then(makePlugin => name === 'mozjpeg' ? makePlugin({ quality: jpegQuality }) : makePlugin())
54
+ )
55
+
28
56
  /**
29
57
  * Move and optimize images into the browser directory.
30
58
  * @memberOf module:partials
@@ -34,16 +62,21 @@ export const loadImagemin = (projectPath = process.cwd()) => {
34
62
  */
35
63
  export const imagesFor = (imageSrc = gulpConfig.get('images.from'), imageDest = gulpConfig.get('images.to')) => {
36
64
  const imagemin = loadImagemin()
65
+ const jpegQuality = gulpConfig.get('images.jpegQuality')
66
+ // Left unset, imagemin runs its default plugins with their own defaults
67
+ const plugins = jpegQuality ? imageminPlugins(jpegQuality) : undefined
37
68
  // Gulp 5 reads files as utf8 text unless told otherwise, which corrupts binary images (and makes gulp-cache drop them).
38
69
  return src(imageSrc, { encoding: false })
39
70
  .pipe(
40
71
  // Caching images that ran through imagemin
41
72
  cache(
42
- imagemin({
73
+ imagemin(plugins, {
43
74
  interlaced: true,
44
75
  silent: true,
45
76
  verbose: false,
46
- })
77
+ }),
78
+ // gulp-cache keys on the file's contents alone, so keep each quality's results apart
79
+ { name: jpegQuality ? `jpeg-quality-${jpegQuality}` : 'default' }
47
80
  )
48
81
  )
49
82
  .pipe(dest(imageDest))
@@ -1,8 +1,10 @@
1
+ import { execFileSync } from 'child_process'
1
2
  import fs from 'fs'
2
3
  import * as setUp from '../test-helpers/setUp.mjs'
3
4
  import os from 'os'
5
+ import path from 'path'
4
6
  import zlib from 'zlib'
5
- import { imagesFor, loadImagemin } from './imagesFor.mjs'
7
+ import { imageminPlugins, imagesFor, loadImagemin } from './imagesFor.mjs'
6
8
  import { isolateGulpCache } from '../test-helpers/isolateGulpCache.mjs'
7
9
 
8
10
  setUp.setDefaults('test-images-for')
@@ -158,6 +160,25 @@ const makePng = (size = 32) => {
158
160
  ])
159
161
  }
160
162
 
163
+ // A JPEG at quality 100, encoded from a generated image by the cjpeg binary the mozjpeg dependency installs
164
+ const makeJpeg = (size = 64) => {
165
+ const pixels = Buffer.alloc(size * size * 3)
166
+ for (let i = 0; i < pixels.length; i++) {
167
+ pixels[i] = (i * 37 + Math.floor(i / (size * 3)) * 91) % 256
168
+ }
169
+ const ppm = Buffer.concat([Buffer.from(`P6\n${size} ${size}\n255\n`), pixels])
170
+ // mozjpeg's exports field hides its files, and Jest runs from the project root
171
+ const cjpeg = path.resolve('node_modules', 'mozjpeg', 'vendor', 'cjpeg')
172
+ return execFileSync(cjpeg, ['-quality', '100'], { input: ppm })
173
+ }
174
+
175
+ // Run imagesFor over one file and resolve with the written output
176
+ const optimize = (file, to) => new Promise((resolve, reject) => {
177
+ imagesFor(file, to)
178
+ .on('finish', () => resolve(fs.readFileSync(`${to}/${path.basename(file)}`)))
179
+ .on('error', reject)
180
+ })
181
+
161
182
  // The cache outlives a run, so clear it or a stale result from an older imagemin can pass
162
183
  beforeEach(() => clearImageCache().then(setUp.beforeEach))
163
184
 
@@ -210,6 +231,43 @@ describe('imagesFor', () => {
210
231
  })
211
232
  })
212
233
 
234
+ describe('images.jpegQuality', () => {
235
+ test('compresses JPEGs to the configured quality, never reusing another quality\'s cached result', async () => {
236
+ expect.assertions(4)
237
+ const srcPath = gulpConfig.get('srcPath')
238
+ const jpegFile = `${srcPath}/photo.jpg`
239
+ fs.writeFileSync(jpegFile, makeJpeg())
240
+ const browserPath = gulpConfig.get('browser.to')
241
+ try {
242
+ const atDefault = await optimize(jpegFile, `${browserPath}/default`)
243
+ gulpConfig.set('images.jpegQuality', 95)
244
+ const at95 = await optimize(jpegFile, `${browserPath}/q95`)
245
+ // Both still JPEGs (start-of-image marker)
246
+ expect(atDefault.subarray(0, 2).equals(Buffer.from([0xff, 0xd8]))).toBeTruthy()
247
+ expect(at95.subarray(0, 2).equals(Buffer.from([0xff, 0xd8]))).toBeTruthy()
248
+ // Same input, so a shared cache entry would make these identical: 95 keeps more detail than mozjpeg's 75
249
+ expect(at95.equals(atDefault)).toBe(false)
250
+ expect(at95.length).toBeGreaterThan(atDefault.length)
251
+ } finally {
252
+ gulpConfig.set('images.jpegQuality', false)
253
+ }
254
+ }, 30000)
255
+ })
256
+
257
+ describe('imageminPlugins', () => {
258
+ test('builds imagemin\'s default plugins in order, passing the quality to mozjpeg only', async () => {
259
+ expect.assertions(2)
260
+ const calls = []
261
+ const factory = name => Promise.resolve((...args) => {
262
+ calls.push([name, ...args])
263
+ return name
264
+ })
265
+ const plugins = { gifsicle: factory('gifsicle'), mozjpeg: factory('mozjpeg'), optipng: factory('optipng'), svgo: factory('svgo') }
266
+ expect(await Promise.all(imageminPlugins(85, plugins))).toEqual(['gifsicle', 'mozjpeg', 'optipng', 'svgo'])
267
+ expect(calls).toEqual([['gifsicle'], ['mozjpeg', { quality: 85 }], ['optipng'], ['svgo']])
268
+ })
269
+ })
270
+
213
271
  describe('loadImagemin', () => {
214
272
  test('loads the gulp-imagemin plugin factory from the project directory', () => {
215
273
  expect.assertions(1)
@@ -204,6 +204,7 @@ export const typeDocsFor = (
204
204
  exclude: [path.resolve(srcPath).split(path.sep).join('/') + '/**/*.test.*']
205
205
  }))
206
206
  const indexFile = gulpConfig.get('docs.index')
207
+ const gitRevision = gulpConfig.get('docs.gitRevision')
207
208
  await removeDirectory(outPath)
208
209
  const app = await Application.bootstrap({
209
210
  entryPoints,
@@ -211,7 +212,10 @@ export const typeDocsFor = (
211
212
  name: gulpConfig.get('docs.title') || projectName(),
212
213
  readme: indexFile && fs.existsSync(indexFile) ? indexFile : 'none',
213
214
  logLevel: gulpConfig.get('docs.logLevel') || 'Warn',
214
- skipErrorChecking: true
215
+ skipErrorChecking: true,
216
+ // Left out, TypeDoc links each declaration to its source at the current commit, so every commit changes
217
+ // every page; a branch name keeps the pages the same until the documentation itself changes.
218
+ ...(gitRevision ? { gitRevision } : {})
215
219
  })
216
220
  const project = await app.convert()
217
221
  if (!project) {
@@ -211,6 +211,17 @@ describe('typeDocsFor', () => {
211
211
  expect(calls.options.readme).toBe('none')
212
212
  })
213
213
 
214
+ test('passes docs.gitRevision on to TypeDoc, and leaves TypeDoc\'s own default when it is false', async () => {
215
+ gulpConfig.set('docs.enabled', true)
216
+ const calls = {}
217
+ await typeDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeTypeDoc(calls))()
218
+ expect('gitRevision' in calls.options).toBe(false)
219
+ gulpConfig.set('docs.gitRevision', 'main')
220
+ await typeDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeTypeDoc(calls))()
221
+ expect(calls.options.gitRevision).toBe('main')
222
+ gulpConfig.set('docs.gitRevision', false)
223
+ })
224
+
214
225
  test('reads the source with the tsconfig of the project, adding the entries to it', async () => {
215
226
  gulpConfig.set('docs.enabled', true)
216
227
  gulpConfig.set('typescript.config', 'tsconfig.json')
package/gulp.config.mjs CHANGED
@@ -16,6 +16,10 @@ import { fileExists } from 'test-filesystem'
16
16
  * A setting that may be true or false.
17
17
  * @typedef {boolean} module:gulpConfig~BooleanSetting
18
18
  */
19
+ /**
20
+ * A setting that may be flag 'false' or provide a number
21
+ * @typedef {false|number} module:gulpConfig~FlagNumberSetting
22
+ */
19
23
  /**
20
24
  * A setting that may be flag 'false' or provide a StringSetting
21
25
  * @typedef {false|StringSetting} module:gulpConfig~FlagStringSetting
@@ -43,7 +47,7 @@ import { fileExists } from 'test-filesystem'
43
47
  */
44
48
  /**
45
49
  * Any single configuration option is a Setting.
46
- * @typedef {ArrayableSetting|BooleanSetting|FlagsSetting|StringSetting} Setting
50
+ * @typedef {ArrayableSetting|BooleanSetting|FlagNumberSetting|FlagsSetting|StringSetting} Setting
47
51
  */
48
52
  /**
49
53
  * One copy-files entry: a glob (or array of globs) to copy, and where to copy it.
@@ -103,6 +107,9 @@ import { fileExists } from 'test-filesystem'
103
107
  * @typedef {Object<string, Setting>} module:gulpConfig~ImageConfig
104
108
  * @property {BooleanSetting} enabled - Toggle image minify and copy process.
105
109
  * @property {StringSetting} from - Path to search for images.
110
+ * @property {FlagNumberSetting} jpegQuality - The JPEG quality (0 to 100) mozjpeg compresses to. False (the
111
+ * default) keeps mozjpeg's own default, 75. Raise it (85 is usually indistinguishable from the original) when 75
112
+ * visibly bands smooth gradients.
106
113
  * @property {StringSetting} to - Path to output images.
107
114
  */
108
115
  /**
@@ -135,6 +142,13 @@ import { fileExists } from 'test-filesystem'
135
142
  * build does this instead of generating the readme from the jsdoc comments of the compiled files.
136
143
  * @property {ArrayableSetting} from - The directory holding the source, each folder in it becomes a module (TypeDoc) or
137
144
  * is scanned recursively (jsdoc). jsdoc also takes a list of directories and files.
145
+ * @property {FlagStringSetting} gitRevision - The git revision TypeDoc's "Defined in" source links point to (TypeDoc
146
+ * only). False (the default) is TypeDoc's own: the current commit, which changes every page on every commit. A
147
+ * branch name such as 'main' keeps the pages unchanged until the documentation itself changes; '{branch}' is the
148
+ * branch being built.
149
+ * @property {BooleanSetting} inBuild - Generate the documentation as part of build (the default). Turn it off to only
150
+ * generate it when you run the docs task yourself (gulp docs), for example when docs.to points into another
151
+ * repository and you only want to publish the documentation of a release.
138
152
  * @property {StringSetting} index - The markdown file used as the front page of the documentation.
139
153
  * @property {StringSetting} logLevel - How much TypeDoc reports: Verbose, Info, Warn, Error or None. Not used by jsdoc.
140
154
  * @property {FlagStringSetting} method - 'typedoc' or 'jsdoc'. False (the default) infers it from typescript.enabled -
@@ -216,6 +230,8 @@ const setDefaults = {
216
230
  docs: {
217
231
  enabled: false,
218
232
  from: 'src',
233
+ gitRevision: false,
234
+ inBuild: true,
219
235
  index: 'MAIN.md',
220
236
  logLevel: 'Warn',
221
237
  method: false,
@@ -232,6 +248,7 @@ const setDefaults = {
232
248
  images: {
233
249
  enabled: false,
234
250
  from: 'src/img/**/*.+(png|jpg|jpeg|gif|svg)',
251
+ jpegQuality: false,
235
252
  to: 'browser/img',
236
253
  },
237
254
  lint: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-build-tools",
3
- "version": "6.4.1",
3
+ "version": "6.6.0",
4
4
  "description": "Standardised build process with gulp.",
5
5
  "main": "gulpfile.base.js",
6
6
  "exports": {