js-build-tools 5.2.0 → 5.3.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 +75 -10
- package/dist/build.js +7 -2
- 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 +48 -48
- 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 +75 -75
- package/dist/partials/tsFor.js +82 -82
- package/dist/partials/typeDocsFor.js +214 -0
- package/dist/partials.js +7 -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/typeDocsFor.mjs +194 -0
- package/functions/partials/typeDocsFor.test.mjs +183 -0
- package/functions/partials.mjs +1 -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 +22 -0
- 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()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
6
|
+
import * as gulpConfig from '../../gulp.config.mjs'
|
|
7
|
+
import { loadTypescript } from './tsFor.mjs'
|
|
8
|
+
import { removeDirectory } from './removeDirectory.mjs'
|
|
9
|
+
|
|
10
|
+
// TypeDoc ships ESM-only, and this project also compiles itself to a CommonJS build (see tsFor.mjs, which hides its
|
|
11
|
+
// own import of gulp-ts-compile the same way), so the import() is hidden from babel's CommonJS transform.
|
|
12
|
+
const importModule = new Function('specifier', 'return import(specifier)')
|
|
13
|
+
|
|
14
|
+
const installHint = 'Install it in your project with: npm install --save-dev typedoc'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find the TypeDoc of the project being built (an optional peer dependency, only needed when the docs task is
|
|
18
|
+
* enabled) and load it, so projects which do not document TypeScript never need it installed.
|
|
19
|
+
* @memberOf module:partials
|
|
20
|
+
* @param {string} [projectPath=process.cwd()] The folder of the project (which has the package.json) being built.
|
|
21
|
+
* @returns {Promise<Object>} The TypeDoc module.
|
|
22
|
+
* @throws {Error} With install instructions when typedoc is not installed.
|
|
23
|
+
*/
|
|
24
|
+
export const loadTypeDoc = async (projectPath = process.cwd()) => {
|
|
25
|
+
let typeDocPath
|
|
26
|
+
try {
|
|
27
|
+
typeDocPath = createRequire(path.join(projectPath, 'package.json')).resolve('typedoc')
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
30
|
+
throw new Error(`The docs task needs the optional peer dependency typedoc, which is not installed. ${installHint}`)
|
|
31
|
+
}
|
|
32
|
+
throw error
|
|
33
|
+
}
|
|
34
|
+
return importModule(pathToFileURL(typeDocPath).href)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isSource = fileName => /\.(ts|tsx|mts|cts)$/.test(fileName) && !/\.d\.[cm]?ts$/.test(fileName) && !/\.(test|spec)\./.test(fileName)
|
|
38
|
+
|
|
39
|
+
const listSources = dirPath => fs.readdirSync(dirPath, { withFileTypes: true }).flatMap(entry => {
|
|
40
|
+
const entryPath = path.join(dirPath, entry.name)
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
return entry.name === 'node_modules' ? [] : listSources(entryPath)
|
|
43
|
+
}
|
|
44
|
+
return isSource(entry.name) ? [entryPath] : []
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const toIdentifier = fileName => fileName.replace(/[^\w$]+(\w)?/g, (match, letter) => (letter || '').toUpperCase()).replace(/^(\d)/, '_$1')
|
|
48
|
+
|
|
49
|
+
const toModulePath = filePath => filePath.replace(/\.[cm]?tsx?$/, '').split(path.sep).join('/')
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Describe what a source file exports: whether it has a default export, and whether it has any named exports.
|
|
53
|
+
* @memberOf module:partials
|
|
54
|
+
* @param {Object} ts - The TypeScript compiler API.
|
|
55
|
+
* @param {string} filePath
|
|
56
|
+
* @returns {{hasDefault: boolean, hasNamed: boolean}}
|
|
57
|
+
*/
|
|
58
|
+
export const describeExports = (ts, filePath) => {
|
|
59
|
+
const source = ts.createSourceFile(filePath, fs.readFileSync(filePath, 'utf8'), ts.ScriptTarget.Latest)
|
|
60
|
+
let hasDefault = false
|
|
61
|
+
let hasNamed = false
|
|
62
|
+
source.statements.forEach(statement => {
|
|
63
|
+
if (ts.isExportAssignment(statement)) {
|
|
64
|
+
hasDefault = hasDefault || !statement.isExportEquals
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
if (ts.isExportDeclaration(statement)) {
|
|
68
|
+
const names = statement.exportClause && ts.isNamedExports(statement.exportClause) ? statement.exportClause.elements : null
|
|
69
|
+
if (names && names.some(element => element.name.text === 'default')) {
|
|
70
|
+
hasDefault = true
|
|
71
|
+
}
|
|
72
|
+
if (!names || names.some(element => element.name.text !== 'default')) {
|
|
73
|
+
hasNamed = true
|
|
74
|
+
}
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) || [] : []
|
|
78
|
+
if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
79
|
+
if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)) {
|
|
80
|
+
hasDefault = true
|
|
81
|
+
} else {
|
|
82
|
+
hasNamed = true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
return { hasDefault, hasNamed }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Write one entry file per folder of the source directory into the entry directory. TypeDoc makes a module of every
|
|
91
|
+
* entry file, so the modules of the documentation mirror the folders. The default export of each source file is
|
|
92
|
+
* re-exported under the name of the file (the source files hold one function each), and anything else a file exports
|
|
93
|
+
* is re-exported as it is (that is where the types.ts files go). Index files (the barrels), tests and declaration
|
|
94
|
+
* files are left out. A source directory with no folders gets a single entry called index.
|
|
95
|
+
* @memberOf module:partials
|
|
96
|
+
* @param {Object} ts - The TypeScript compiler API.
|
|
97
|
+
* @param {string} srcDir - The directory holding the TypeScript source.
|
|
98
|
+
* @param {string} entryDir - The directory to write the entry files into.
|
|
99
|
+
* @returns {Array<string>} The paths of the entry files.
|
|
100
|
+
*/
|
|
101
|
+
export const writeDocEntries = (ts, srcDir, entryDir) => {
|
|
102
|
+
const absoluteSrc = path.resolve(srcDir)
|
|
103
|
+
fs.mkdirSync(entryDir, { recursive: true })
|
|
104
|
+
const folders = fs.readdirSync(absoluteSrc, { withFileTypes: true })
|
|
105
|
+
.filter(entry => entry.isDirectory() && entry.name !== 'node_modules')
|
|
106
|
+
.map(entry => ({ name: entry.name, files: listSources(path.join(absoluteSrc, entry.name)) }))
|
|
107
|
+
.filter(folder => folder.files.length)
|
|
108
|
+
const groups = folders.length
|
|
109
|
+
? folders
|
|
110
|
+
: [{ name: 'index', files: listSources(absoluteSrc).filter(file => !/^(index|main)\./.test(path.basename(file))) }]
|
|
111
|
+
return groups.map(group => {
|
|
112
|
+
const lines = group.files
|
|
113
|
+
.filter(file => !/^index\./.test(path.basename(file)))
|
|
114
|
+
.sort()
|
|
115
|
+
.flatMap(file => {
|
|
116
|
+
const modulePath = toModulePath(file)
|
|
117
|
+
const { hasDefault, hasNamed } = describeExports(ts, file)
|
|
118
|
+
const fileName = path.basename(file).replace(/\.[cm]?tsx?$/, '')
|
|
119
|
+
return [
|
|
120
|
+
...(hasDefault ? [`export { default as ${toIdentifier(fileName)} } from '${modulePath}'`] : []),
|
|
121
|
+
...(hasNamed ? [`export * from '${modulePath}'`] : [])
|
|
122
|
+
]
|
|
123
|
+
})
|
|
124
|
+
const entryPath = path.join(entryDir, `${group.name}.ts`)
|
|
125
|
+
fs.writeFileSync(entryPath, lines.join('\n') + '\n')
|
|
126
|
+
return entryPath
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const projectName = () => {
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')).name
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return path.basename(process.cwd())
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Generate the HTML documentation of a TypeScript project with TypeDoc, straight from the TypeScript source, so the
|
|
140
|
+
* types (and the comments on them) are documented from where they are defined. The modules mirror the folders of the
|
|
141
|
+
* source, see {@link writeDocEntries}. Configure this with 'docs.from', 'docs.to', 'docs.index', 'docs.title',
|
|
142
|
+
* 'docs.tsconfig' and 'docs.logLevel'.
|
|
143
|
+
* @memberOf module:partials
|
|
144
|
+
* @param {string} [srcPath=gulpConfig.get('docs.from')] - The directory holding the TypeScript source.
|
|
145
|
+
* @param {string} [outPath=gulpConfig.get('docs.to')] - The directory the documentation is generated into (cleared first).
|
|
146
|
+
* @param {function(string): Promise<Object>} [loadDocs] - Resolves the TypeDoc module, tests can inject a stand-in.
|
|
147
|
+
* @returns {Function} A gulp task, it accepts an optional callback and returns a promise.
|
|
148
|
+
*/
|
|
149
|
+
export const typeDocsFor = (
|
|
150
|
+
srcPath = gulpConfig.get('docs.from'),
|
|
151
|
+
outPath = gulpConfig.get('docs.to'),
|
|
152
|
+
loadDocs = loadTypeDoc
|
|
153
|
+
) => {
|
|
154
|
+
if (gulpConfig.get('docs.enabled') === false) {
|
|
155
|
+
return () => {}
|
|
156
|
+
}
|
|
157
|
+
return (done) => {
|
|
158
|
+
const generate = async () => {
|
|
159
|
+
const ts = loadTypescript()
|
|
160
|
+
const { Application } = await loadDocs()
|
|
161
|
+
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'typedocs-'))
|
|
162
|
+
try {
|
|
163
|
+
const entryPoints = writeDocEntries(ts, srcPath, path.join(workDir, 'entries'))
|
|
164
|
+
// The entry files live outside the project, so the tsconfig which is used for the documentation extends the
|
|
165
|
+
// one of the project and adds the entries to the files it includes.
|
|
166
|
+
const projectConfig = gulpConfig.get('docs.tsconfig') || gulpConfig.get('typescript.config')
|
|
167
|
+
const tsconfig = path.join(workDir, 'tsconfig.json')
|
|
168
|
+
fs.writeFileSync(tsconfig, JSON.stringify({
|
|
169
|
+
...(projectConfig ? { extends: path.resolve(projectConfig) } : {}),
|
|
170
|
+
include: [path.join(workDir, 'entries', '*.ts').split(path.sep).join('/'), path.resolve(srcPath).split(path.sep).join('/') + '/**/*.ts'],
|
|
171
|
+
exclude: [path.resolve(srcPath).split(path.sep).join('/') + '/**/*.test.*']
|
|
172
|
+
}))
|
|
173
|
+
const indexFile = gulpConfig.get('docs.index')
|
|
174
|
+
await removeDirectory(outPath)
|
|
175
|
+
const app = await Application.bootstrap({
|
|
176
|
+
entryPoints,
|
|
177
|
+
tsconfig,
|
|
178
|
+
name: gulpConfig.get('docs.title') || projectName(),
|
|
179
|
+
readme: indexFile && fs.existsSync(indexFile) ? indexFile : 'none',
|
|
180
|
+
logLevel: gulpConfig.get('docs.logLevel') || 'Warn',
|
|
181
|
+
skipErrorChecking: true
|
|
182
|
+
})
|
|
183
|
+
const project = await app.convert()
|
|
184
|
+
if (!project) {
|
|
185
|
+
throw new Error('TypeDoc could not read the TypeScript source, see the errors above.')
|
|
186
|
+
}
|
|
187
|
+
await app.generateDocs(project, outPath)
|
|
188
|
+
} finally {
|
|
189
|
+
await removeDirectory(workDir)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return typeof done === 'function' ? generate().then(() => done(), done) : generate()
|
|
193
|
+
}
|
|
194
|
+
}
|