js-build-tools 6.6.0 → 6.8.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/LICENSE +674 -674
- package/README.md +768 -701
- package/babel.config.js +28 -28
- package/dist/build.js +75 -75
- package/dist/bundle.js +19 -19
- package/dist/bundleMinify.js +7 -7
- package/dist/compileReadme.js +4 -4
- package/dist/copyFiles.js +17 -17
- package/dist/copyFonts.js +4 -4
- package/dist/default.js +5 -5
- package/dist/dist.js +4 -4
- package/dist/distMinify.js +4 -4
- package/dist/docs.js +7 -7
- package/dist/images.js +4 -4
- package/dist/jsdocDocs.js +6 -6
- package/dist/lint.js +16 -16
- package/dist/partials/addToReadme.js +9 -9
- package/dist/partials/beginWatcher.js +4 -4
- package/dist/partials/cacheFiles.js +121 -0
- package/dist/partials/clean.js +5 -5
- package/dist/partials/copyFor.js +9 -9
- package/dist/partials/distFor.js +6 -6
- package/dist/partials/distForSrc.js +5 -5
- package/dist/partials/distSeries.js +7 -7
- package/dist/partials/imagesFor.js +155 -93
- package/dist/partials/jsdocDocsFor.js +64 -51
- package/dist/partials/lintSource.js +51 -51
- package/dist/partials/loadPeer.js +99 -99
- package/dist/partials/loadPlaywright.js +14 -14
- package/dist/partials/minifyFor.js +20 -20
- package/dist/partials/neostandardLint.js +175 -175
- package/dist/partials/readmeTemplate.js +4 -4
- package/dist/partials/removeDirectory.js +11 -11
- package/dist/partials/resolveDocsMethod.js +6 -6
- package/dist/partials/runOnChange.js +31 -31
- package/dist/partials/sassFor.js +6 -6
- package/dist/partials/serveStatic.js +9 -9
- package/dist/partials/tsFor.js +32 -32
- package/dist/partials/typeDocsFor.js +264 -264
- package/dist/partials.js +183 -176
- package/dist/partials.min.js +1 -1
- package/dist/sourceLint.js +18 -18
- package/dist/test-helpers/setUp.js +6 -4
- package/dist/testFull.js +5 -5
- package/dist/testQuick.js +5 -5
- package/dist/typeDocs.js +5 -5
- package/dist/typeScript.js +4 -4
- package/dist/utilities/convertMjsFilesForDocs.js +12 -12
- package/dist/utilities/dotGet.js +54 -54
- package/dist/utilities/dotNotate.js +79 -79
- package/dist/utilities/dotSet.js +45 -45
- package/dist/utilities/esmNamedExportsToCjs.js +15 -15
- package/dist/utilities/getBundleConfigs.js +9 -9
- package/dist/utilities/isObject.js +6 -6
- package/dist/utilities/strAfter.js +7 -7
- package/dist/utilities/strBefore.js +7 -7
- package/dist/watchFull.js +4 -4
- package/dist/watchTest.js +4 -4
- package/functions/build.mjs +72 -72
- package/functions/build.test.mjs +760 -759
- package/functions/bundle.mjs +44 -44
- package/functions/bundle.test.mjs +219 -219
- package/functions/bundleMinify.mjs +16 -16
- package/functions/bundleMinify.test.mjs +37 -37
- package/functions/compileReadme.mjs +9 -9
- package/functions/compileReadme.test.mjs +18 -18
- package/functions/copyFiles.mjs +32 -32
- package/functions/copyFiles.test.mjs +78 -78
- package/functions/copyFonts.mjs +9 -9
- package/functions/copyFonts.test.mjs +32 -32
- package/functions/default.mjs +12 -12
- package/functions/default.test.mjs +33 -33
- package/functions/dist.mjs +8 -8
- package/functions/dist.test.mjs +44 -44
- package/functions/distMinify.mjs +9 -9
- package/functions/distMinify.test.mjs +15 -15
- package/functions/docs.mjs +13 -13
- package/functions/docs.test.mjs +45 -45
- package/functions/gulpConfigEntries.test.mjs +23 -23
- package/functions/images.mjs +8 -8
- package/functions/images.test.mjs +140 -142
- package/functions/jsdocDocs.mjs +10 -10
- package/functions/jsdocEsmExports.test.mjs +24 -24
- package/functions/lint.mjs +9 -9
- package/functions/partials/addToReadme.mjs +25 -25
- package/functions/partials/addToReadme.test.mjs +34 -34
- package/functions/partials/beginWatcher.mjs +9 -9
- package/functions/partials/beginWatcher.test.mjs +19 -19
- package/functions/partials/cacheFiles.mjs +114 -0
- package/functions/partials/cacheFiles.test.mjs +151 -0
- package/functions/partials/clean.mjs +13 -13
- package/functions/partials/clean.test.mjs +22 -22
- package/functions/partials/copyFor.mjs +15 -15
- package/functions/partials/copyFor.test.mjs +53 -53
- package/functions/partials/distFor.mjs +22 -22
- package/functions/partials/distFor.test.mjs +195 -195
- package/functions/partials/distForSrc.mjs +11 -11
- package/functions/partials/distForSrc.test.mjs +18 -18
- package/functions/partials/distSeries.mjs +21 -21
- package/functions/partials/distSeries.test.mjs +34 -34
- package/functions/partials/imagesFor.mjs +147 -83
- package/functions/partials/imagesFor.test.mjs +311 -287
- package/functions/partials/jsdocDocsFor.mjs +163 -152
- package/functions/partials/jsdocDocsFor.test.mjs +182 -152
- package/functions/partials/lintSource.mjs +41 -41
- package/functions/partials/loadPeer.mjs +76 -76
- package/functions/partials/loadPeer.test.mjs +58 -58
- package/functions/partials/loadPlaywright.mjs +23 -23
- package/functions/partials/loadPlaywright.test.mjs +70 -70
- package/functions/partials/minifyFor.mjs +15 -15
- package/functions/partials/minifyFor.test.mjs +49 -49
- package/functions/partials/neostandardLint.mjs +142 -142
- package/functions/partials/neostandardLint.test.mjs +131 -131
- package/functions/partials/readmeTemplate.mjs +12 -12
- package/functions/partials/readmeTemplate.test.mjs +34 -34
- package/functions/partials/removeDirectory.mjs +34 -34
- package/functions/partials/removeDirectory.test.mjs +72 -72
- package/functions/partials/resolveDocsMethod.mjs +10 -10
- package/functions/partials/resolveDocsMethod.test.mjs +33 -33
- package/functions/partials/runOnChange.mjs +54 -54
- package/functions/partials/runOnChange.test.mjs +95 -95
- package/functions/partials/sassFor.mjs +25 -25
- package/functions/partials/sassFor.test.mjs +81 -81
- package/functions/partials/serveStatic.mjs +47 -47
- package/functions/partials/serveStatic.test.mjs +71 -71
- package/functions/partials/tsFor.mjs +92 -92
- package/functions/partials/tsFor.test.mjs +88 -88
- package/functions/partials/typeDocsFor.mjs +231 -231
- package/functions/partials/typeDocsFor.test.mjs +263 -263
- package/functions/partials.mjs +32 -31
- package/functions/partials.test.mjs +7 -7
- package/functions/sass.mjs +4 -4
- package/functions/sass.test.mjs +31 -31
- package/functions/sourceLint.mjs +13 -13
- package/functions/sourceLint.test.mjs +166 -166
- package/functions/test-helpers/neostandardLintWithRealNeostandard.mjs +40 -40
- package/functions/test-helpers/setUp.mjs +102 -100
- package/functions/test-helpers/setUp.test.mjs +19 -19
- package/functions/testFull.mjs +17 -17
- package/functions/testFull.test.mjs +17 -17
- package/functions/testHelpers.mjs +10 -10
- package/functions/testHelpers.test.mjs +7 -7
- package/functions/testQuick.mjs +17 -17
- package/functions/testQuick.test.mjs +17 -17
- package/functions/typeDocs.mjs +9 -9
- package/functions/typeDocs.test.mjs +11 -11
- package/functions/typeScript.mjs +8 -8
- package/functions/typeScript.test.mjs +11 -11
- package/functions/utilities/convertMjsFilesForDocs.mjs +34 -34
- package/functions/utilities/convertMjsFilesForDocs.test.mjs +58 -58
- package/functions/utilities/dotGet.mjs +49 -49
- package/functions/utilities/dotNotate.mjs +73 -73
- package/functions/utilities/dotNotate.test.mjs +41 -41
- package/functions/utilities/dotSet.mjs +40 -40
- package/functions/utilities/esmNamedExportsToCjs.mjs +30 -30
- package/functions/utilities/esmNamedExportsToCjs.test.mjs +47 -47
- package/functions/utilities/getBundleConfigs.mjs +30 -30
- package/functions/utilities/isObject.mjs +8 -8
- package/functions/utilities/strAfter.mjs +12 -12
- package/functions/utilities/strBefore.mjs +12 -12
- package/functions/watchFull.mjs +9 -9
- package/functions/watchFull.test.mjs +41 -41
- package/functions/watchTest.mjs +10 -10
- package/functions/watchTest.test.mjs +49 -49
- package/gulp.config.js +4 -4
- package/gulp.config.mjs +349 -340
- package/gulpfile.base.js +51 -51
- package/gulpfile.base.mjs +29 -29
- package/jsdoc.base.js +16 -16
- package/jsdoc.esmExports.js +17 -17
- package/package.json +146 -145
- package/dist/test-helpers/isolateGulpCache.js +0 -25
- package/functions/test-helpers/isolateGulpCache.mjs +0 -16
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
/* eslint-disable no-template-curly-in-string -- fixture source code, the ${...} is meant to stay literal */
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import * as setUp from '../test-helpers/setUp.mjs'
|
|
4
|
-
import { addToReadme } from './addToReadme.mjs'
|
|
5
|
-
|
|
6
|
-
setUp.setDefaults('test-add-to-readme')
|
|
7
|
-
const gulpConfig = setUp.gulpConfig
|
|
8
|
-
gulpConfig.set('dist.from', 'test-temp/src/addToReadme.js')
|
|
9
|
-
|
|
10
|
-
const rawContents = '/**\n' +
|
|
11
|
-
' * Say hello with a given name.\n' +
|
|
12
|
-
' * @param {string} name\n' +
|
|
13
|
-
' * @returns {string}\n' +
|
|
14
|
-
' */\n' +
|
|
15
|
-
'const sayHello = name => `Hello from ${name}`\n' +
|
|
16
|
-
'\n' +
|
|
17
|
-
'module.exports = sayHello\n'
|
|
18
|
-
|
|
19
|
-
beforeEach(() => setUp.beforeEach()
|
|
20
|
-
.then(() => fs.writeFileSync(gulpConfig.get('srcPath') + '/addToReadme.js', rawContents)))
|
|
21
|
-
|
|
22
|
-
afterEach(setUp.afterEach)
|
|
23
|
-
|
|
24
|
-
describe('addToReadme', () => {
|
|
25
|
-
test('generates the documentation', async () => {
|
|
26
|
-
const readmeFilePath = gulpConfig.get('readme.to') + gulpConfig.get('readme.file')
|
|
27
|
-
expect.assertions(2)
|
|
28
|
-
await addToReadme()
|
|
29
|
-
await expect(fs.existsSync(readmeFilePath)).toBeTruthy()
|
|
30
|
-
const readme = await fs.readFileSync(readmeFilePath)
|
|
31
|
-
// At least the documentation will have the addToReadme documentation
|
|
32
|
-
await expect(readme.includes('sayHello')).toBeTruthy()
|
|
33
|
-
})
|
|
34
|
-
})
|
|
1
|
+
/* eslint-disable no-template-curly-in-string -- fixture source code, the ${...} is meant to stay literal */
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import * as setUp from '../test-helpers/setUp.mjs'
|
|
4
|
+
import { addToReadme } from './addToReadme.mjs'
|
|
5
|
+
|
|
6
|
+
setUp.setDefaults('test-add-to-readme')
|
|
7
|
+
const gulpConfig = setUp.gulpConfig
|
|
8
|
+
gulpConfig.set('dist.from', 'test-temp/src/addToReadme.js')
|
|
9
|
+
|
|
10
|
+
const rawContents = '/**\n' +
|
|
11
|
+
' * Say hello with a given name.\n' +
|
|
12
|
+
' * @param {string} name\n' +
|
|
13
|
+
' * @returns {string}\n' +
|
|
14
|
+
' */\n' +
|
|
15
|
+
'const sayHello = name => `Hello from ${name}`\n' +
|
|
16
|
+
'\n' +
|
|
17
|
+
'module.exports = sayHello\n'
|
|
18
|
+
|
|
19
|
+
beforeEach(() => setUp.beforeEach()
|
|
20
|
+
.then(() => fs.writeFileSync(gulpConfig.get('srcPath') + '/addToReadme.js', rawContents)))
|
|
21
|
+
|
|
22
|
+
afterEach(setUp.afterEach)
|
|
23
|
+
|
|
24
|
+
describe('addToReadme', () => {
|
|
25
|
+
test('generates the documentation', async () => {
|
|
26
|
+
const readmeFilePath = gulpConfig.get('readme.to') + gulpConfig.get('readme.file')
|
|
27
|
+
expect.assertions(2)
|
|
28
|
+
await addToReadme()
|
|
29
|
+
await expect(fs.existsSync(readmeFilePath)).toBeTruthy()
|
|
30
|
+
const readme = await fs.readFileSync(readmeFilePath)
|
|
31
|
+
// At least the documentation will have the addToReadme documentation
|
|
32
|
+
await expect(readme.includes('sayHello')).toBeTruthy()
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import * as gulpConfig from '../../gulp.config.mjs'
|
|
2
|
-
import { watch } from 'gulp'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Create a chokidar instance which watches and triggers change when the globed files are modified.
|
|
6
|
-
* @memberOf module:partials
|
|
7
|
-
* @returns {FSWatcher}
|
|
8
|
-
*/
|
|
9
|
-
export const beginWatcher = () => watch(gulpConfig.get('test.watch'))
|
|
1
|
+
import * as gulpConfig from '../../gulp.config.mjs'
|
|
2
|
+
import { watch } from 'gulp'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a chokidar instance which watches and triggers change when the globed files are modified.
|
|
6
|
+
* @memberOf module:partials
|
|
7
|
+
* @returns {FSWatcher}
|
|
8
|
+
*/
|
|
9
|
+
export const beginWatcher = () => watch(gulpConfig.get('test.watch'))
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import * as setUp from '../test-helpers/setUp.mjs'
|
|
2
|
-
import { beginWatcher } from './beginWatcher.mjs'
|
|
3
|
-
import { EventEmitter } from 'node:events'
|
|
4
|
-
|
|
5
|
-
setUp.setDefaults('test-begin-watcher')
|
|
6
|
-
const gulpConfig = setUp.gulpConfig
|
|
7
|
-
|
|
8
|
-
beforeEach(setUp.beforeEach)
|
|
9
|
-
|
|
10
|
-
afterEach(setUp.afterEach)
|
|
11
|
-
|
|
12
|
-
describe('beginWatcher', () => {
|
|
13
|
-
test('returns instance of EventEmitter / FSWatcher', () => {
|
|
14
|
-
gulpConfig.set('test.watch', 'test-begin-watcher/*.js')
|
|
15
|
-
const fsWatcher = beginWatcher()
|
|
16
|
-
expect(fsWatcher).toBeInstanceOf(EventEmitter)
|
|
17
|
-
fsWatcher.close()
|
|
18
|
-
})
|
|
19
|
-
})
|
|
1
|
+
import * as setUp from '../test-helpers/setUp.mjs'
|
|
2
|
+
import { beginWatcher } from './beginWatcher.mjs'
|
|
3
|
+
import { EventEmitter } from 'node:events'
|
|
4
|
+
|
|
5
|
+
setUp.setDefaults('test-begin-watcher')
|
|
6
|
+
const gulpConfig = setUp.gulpConfig
|
|
7
|
+
|
|
8
|
+
beforeEach(setUp.beforeEach)
|
|
9
|
+
|
|
10
|
+
afterEach(setUp.afterEach)
|
|
11
|
+
|
|
12
|
+
describe('beginWatcher', () => {
|
|
13
|
+
test('returns instance of EventEmitter / FSWatcher', () => {
|
|
14
|
+
gulpConfig.set('test.watch', 'test-begin-watcher/*.js')
|
|
15
|
+
const fsWatcher = beginWatcher()
|
|
16
|
+
expect(fsWatcher).toBeInstanceOf(EventEmitter)
|
|
17
|
+
fsWatcher.close()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'crypto'
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from 'fs/promises'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { Transform } from 'stream'
|
|
5
|
+
|
|
6
|
+
// Marks a file sent to the plugin with its cache file and its path, so its result is cached even if the plugin renames it
|
|
7
|
+
const cacheFile = Symbol('cacheFile')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The cache file for a file's contents: a hash of the cache name, the fingerprint and the contents, so the same file
|
|
11
|
+
* with other plugin settings or plugin versions gets a result of its own.
|
|
12
|
+
* @private
|
|
13
|
+
* @param {string} directory
|
|
14
|
+
* @param {string} name
|
|
15
|
+
* @param {string} fingerprint
|
|
16
|
+
* @param {Buffer} contents
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
const cachePath = (directory, name, fingerprint, contents) => path.join(
|
|
20
|
+
directory,
|
|
21
|
+
name,
|
|
22
|
+
createHash('sha256').update(name).update('\0').update(fingerprint).update('\0').update(contents).digest('hex')
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Write through a temporary file and rename it, so a reader (another build or test worker) never sees a half-written
|
|
27
|
+
* result.
|
|
28
|
+
* @private
|
|
29
|
+
* @param {string} file
|
|
30
|
+
* @param {Buffer} contents
|
|
31
|
+
* @returns {Promise<void>}
|
|
32
|
+
*/
|
|
33
|
+
const writeAtomic = async (file, contents) => {
|
|
34
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
35
|
+
const temporary = `${file}.${randomUUID()}.tmp`
|
|
36
|
+
await writeFile(temporary, contents)
|
|
37
|
+
await rename(temporary, file)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Run a gulp plugin only on files it hasn't processed before. A file whose contents (with this cache name and
|
|
42
|
+
* fingerprint) were processed by an earlier run gets that run's result from the cache instead, and skips the plugin.
|
|
43
|
+
* Replaces gulp-cache, which kept one cache for every process in the system's temp folder.
|
|
44
|
+
*
|
|
45
|
+
* A result is cached when the plugin passes on the file object it was given (as gulp plugins usually do); a plugin
|
|
46
|
+
* that makes new file objects still works, its results just aren't cached. When the plugin renames a file, the cache
|
|
47
|
+
* keeps the new name too (next to the result, as `<entry>.path`), so a cached file is renamed the same way. Entries
|
|
48
|
+
* are never pruned: delete the directory to start over.
|
|
49
|
+
* @memberOf module:partials
|
|
50
|
+
* @param {stream.Transform} plugin - The gulp plugin stream to run on uncached files (e.g. imagemin()).
|
|
51
|
+
* @param {Object} options
|
|
52
|
+
* @param {string} options.directory - Where cached results are kept (see images.cacheDir).
|
|
53
|
+
* @param {string} [options.name='default'] - The cache's folder; keeps results of different plugin settings apart.
|
|
54
|
+
* @param {string} [options.fingerprint=''] - Anything else that changes the plugin's output, such as its version and
|
|
55
|
+
* options: when it changes, earlier results are no longer used.
|
|
56
|
+
* @returns {stream.Transform}
|
|
57
|
+
*/
|
|
58
|
+
export const cacheFiles = (plugin, { directory, name = 'default', fingerprint = '' }) => {
|
|
59
|
+
const writes = []
|
|
60
|
+
const pluginDone = new Promise((resolve, reject) => {
|
|
61
|
+
plugin.on('end', resolve)
|
|
62
|
+
plugin.on('error', reject)
|
|
63
|
+
})
|
|
64
|
+
// The error also reaches the stream (below); this only stops it from being reported a second time as unhandled
|
|
65
|
+
pluginDone.catch(() => {})
|
|
66
|
+
|
|
67
|
+
const stream = new Transform({
|
|
68
|
+
objectMode: true,
|
|
69
|
+
transform (file, encoding, callback) {
|
|
70
|
+
if (!file.isBuffer()) {
|
|
71
|
+
callback(null, file)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
const cached = cachePath(directory, name, fingerprint, file.contents)
|
|
75
|
+
readFile(cached).then(
|
|
76
|
+
async contents => {
|
|
77
|
+
file.contents = contents
|
|
78
|
+
// Written before the result, so it's there whenever the result is
|
|
79
|
+
const renamed = await readFile(`${cached}.path`, 'utf8').catch(() => undefined)
|
|
80
|
+
if (renamed !== undefined) {
|
|
81
|
+
file.path = path.join(path.dirname(file.path), renamed)
|
|
82
|
+
}
|
|
83
|
+
callback(null, file)
|
|
84
|
+
},
|
|
85
|
+
() => {
|
|
86
|
+
file[cacheFile] = { cached, from: file.path }
|
|
87
|
+
plugin.write(file)
|
|
88
|
+
callback()
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
},
|
|
92
|
+
flush (callback) {
|
|
93
|
+
plugin.end()
|
|
94
|
+
pluginDone
|
|
95
|
+
.then(() => Promise.all(writes))
|
|
96
|
+
.then(() => callback(), callback)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
plugin.on('data', file => {
|
|
101
|
+
const tag = file[cacheFile]
|
|
102
|
+
delete file[cacheFile]
|
|
103
|
+
if (tag && file.isBuffer()) {
|
|
104
|
+
const { cached, from } = tag
|
|
105
|
+
const renamed = file.path === from ? Promise.resolve() : writeAtomic(`${cached}.path`, path.relative(path.dirname(from), file.path))
|
|
106
|
+
// The cache only saves time: a result that can't be written is processed again next run, not a failed build
|
|
107
|
+
writes.push(renamed.then(() => writeAtomic(cached, file.contents)).catch(() => {}))
|
|
108
|
+
}
|
|
109
|
+
stream.push(file)
|
|
110
|
+
})
|
|
111
|
+
plugin.on('error', error => stream.destroy(error))
|
|
112
|
+
|
|
113
|
+
return stream
|
|
114
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { Readable, Transform } from 'stream'
|
|
4
|
+
import * as setUp from '../test-helpers/setUp.mjs'
|
|
5
|
+
import { cacheFiles } from './cacheFiles.mjs'
|
|
6
|
+
|
|
7
|
+
setUp.setDefaults('test-cache-files')
|
|
8
|
+
const gulpConfig = setUp.gulpConfig
|
|
9
|
+
|
|
10
|
+
beforeEach(setUp.beforeEach)
|
|
11
|
+
|
|
12
|
+
afterEach(setUp.afterEach)
|
|
13
|
+
|
|
14
|
+
// Just enough of a Vinyl file for cacheFiles and the fake plugin
|
|
15
|
+
const file = (filePath, text) => ({
|
|
16
|
+
path: filePath,
|
|
17
|
+
contents: text === null ? null : Buffer.from(text),
|
|
18
|
+
isBuffer () {
|
|
19
|
+
return Buffer.isBuffer(this.contents)
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// A plugin that upper-cases each file, counting the files it's given
|
|
24
|
+
const upperCase = () => {
|
|
25
|
+
const plugin = new Transform({
|
|
26
|
+
objectMode: true,
|
|
27
|
+
transform (input, encoding, callback) {
|
|
28
|
+
plugin.calls++
|
|
29
|
+
input.contents = Buffer.from(input.contents.toString().toUpperCase())
|
|
30
|
+
callback(null, input)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
plugin.calls = 0
|
|
34
|
+
return plugin
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Run files through cacheFiles and collect what comes out, as text by path
|
|
38
|
+
const run = (files, plugin, options) => new Promise((resolve, reject) => {
|
|
39
|
+
const out = {}
|
|
40
|
+
Readable.from(files)
|
|
41
|
+
.pipe(cacheFiles(plugin, options))
|
|
42
|
+
.on('data', output => {
|
|
43
|
+
out[path.basename(output.path)] = output.contents === null ? null : output.contents.toString()
|
|
44
|
+
})
|
|
45
|
+
.on('end', () => resolve(out))
|
|
46
|
+
.on('error', reject)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const cacheDir = () => `${gulpConfig.get('rootPath')}cache`
|
|
50
|
+
|
|
51
|
+
describe('cacheFiles', () => {
|
|
52
|
+
test('a file it hasn\'t seen goes through the plugin, and the result is cached', async () => {
|
|
53
|
+
const plugin = upperCase()
|
|
54
|
+
expect(await run([file('/a.txt', 'one'), file('/b.txt', 'two')], plugin, { directory: cacheDir() }))
|
|
55
|
+
.toEqual({ 'a.txt': 'ONE', 'b.txt': 'TWO' })
|
|
56
|
+
expect(plugin.calls).toBe(2)
|
|
57
|
+
expect(fs.readdirSync(path.join(cacheDir(), 'default'))).toHaveLength(2)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('the same contents again come from the cache, without the plugin', async () => {
|
|
61
|
+
await run([file('/a.txt', 'one')], upperCase(), { directory: cacheDir() })
|
|
62
|
+
const plugin = upperCase()
|
|
63
|
+
// Another path with the same contents is the same result
|
|
64
|
+
expect(await run([file('/a.txt', 'one'), file('/copy.txt', 'one')], plugin, { directory: cacheDir() }))
|
|
65
|
+
.toEqual({ 'a.txt': 'ONE', 'copy.txt': 'ONE' })
|
|
66
|
+
expect(plugin.calls).toBe(0)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('changed contents, or another cache name, go through the plugin again', async () => {
|
|
70
|
+
await run([file('/a.txt', 'one')], upperCase(), { directory: cacheDir() })
|
|
71
|
+
const changed = upperCase()
|
|
72
|
+
expect(await run([file('/a.txt', 'one!')], changed, { directory: cacheDir() })).toEqual({ 'a.txt': 'ONE!' })
|
|
73
|
+
expect(changed.calls).toBe(1)
|
|
74
|
+
const otherName = upperCase()
|
|
75
|
+
expect(await run([file('/a.txt', 'one')], otherName, { directory: cacheDir(), name: 'other' })).toEqual({ 'a.txt': 'ONE' })
|
|
76
|
+
expect(otherName.calls).toBe(1)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('another fingerprint (say, a new plugin version) goes through the plugin again', async () => {
|
|
80
|
+
await run([file('/a.txt', 'one')], upperCase(), { directory: cacheDir(), fingerprint: 'v1' })
|
|
81
|
+
const sameVersion = upperCase()
|
|
82
|
+
await run([file('/a.txt', 'one')], sameVersion, { directory: cacheDir(), fingerprint: 'v1' })
|
|
83
|
+
expect(sameVersion.calls).toBe(0)
|
|
84
|
+
const newVersion = upperCase()
|
|
85
|
+
await run([file('/a.txt', 'one')], newVersion, { directory: cacheDir(), fingerprint: 'v2' })
|
|
86
|
+
expect(newVersion.calls).toBe(1)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('a plugin that renames files has its results cached, and a cached file is renamed the same way', async () => {
|
|
90
|
+
const renaming = () => {
|
|
91
|
+
const plugin = upperCase()
|
|
92
|
+
const transform = plugin._transform.bind(plugin)
|
|
93
|
+
plugin._transform = (input, encoding, callback) => transform(input, encoding, (error, output) => {
|
|
94
|
+
output.path = output.path.replace('.txt', '.md')
|
|
95
|
+
callback(error, output)
|
|
96
|
+
})
|
|
97
|
+
return plugin
|
|
98
|
+
}
|
|
99
|
+
expect(await run([file('/a.txt', 'one')], renaming(), { directory: cacheDir() })).toEqual({ 'a.md': 'ONE' })
|
|
100
|
+
const again = renaming()
|
|
101
|
+
// From the cache, renamed the same way, without the plugin
|
|
102
|
+
expect(await run([file('/a.txt', 'one')], again, { directory: cacheDir() })).toEqual({ 'a.md': 'ONE' })
|
|
103
|
+
expect(again.calls).toBe(0)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('a plugin that makes new file objects still works; its results just aren\'t cached', async () => {
|
|
107
|
+
const copying = new Transform({
|
|
108
|
+
objectMode: true,
|
|
109
|
+
transform (input, encoding, callback) {
|
|
110
|
+
callback(null, file(input.path, input.contents.toString().toUpperCase()))
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
expect(await run([file('/a.txt', 'one')], copying, { directory: cacheDir() })).toEqual({ 'a.txt': 'ONE' })
|
|
114
|
+
expect(fs.existsSync(path.join(cacheDir(), 'default'))).toBe(false)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('a file without contents (a directory) passes straight through', async () => {
|
|
118
|
+
const plugin = upperCase()
|
|
119
|
+
expect(await run([file('/folder', null)], plugin, { directory: cacheDir() })).toEqual({ folder: null })
|
|
120
|
+
expect(plugin.calls).toBe(0)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('a cache that can\'t be written still gives the plugin\'s results', async () => {
|
|
124
|
+
// A file where the cache directory should be: nothing can be written inside it
|
|
125
|
+
fs.writeFileSync(cacheDir(), 'not a directory')
|
|
126
|
+
const plugin = upperCase()
|
|
127
|
+
expect(await run([file('/a.txt', 'one')], plugin, { directory: cacheDir() })).toEqual({ 'a.txt': 'ONE' })
|
|
128
|
+
expect(plugin.calls).toBe(1)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('two runs at once into the same cache both get every result (the cache file is written whole)', async () => {
|
|
132
|
+
const files = () => Array.from({ length: 20 }, (_, index) => file(`/f${index}.txt`, `file ${index}`))
|
|
133
|
+
const expected = Object.fromEntries(Array.from({ length: 20 }, (_, index) => [`f${index}.txt`, `FILE ${index}`]))
|
|
134
|
+
const results = await Promise.all([
|
|
135
|
+
run(files(), upperCase(), { directory: cacheDir() }),
|
|
136
|
+
run(files(), upperCase(), { directory: cacheDir() })
|
|
137
|
+
])
|
|
138
|
+
expect(results).toEqual([expected, expected])
|
|
139
|
+
expect(fs.readdirSync(path.join(cacheDir(), 'default')).filter(name => name.endsWith('.tmp'))).toEqual([])
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('a plugin error fails the stream', async () => {
|
|
143
|
+
const failing = new Transform({
|
|
144
|
+
objectMode: true,
|
|
145
|
+
transform (input, encoding, callback) {
|
|
146
|
+
callback(new Error('plugin failed'))
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
await expect(run([file('/a.txt', 'one')], failing, { directory: cacheDir() })).rejects.toThrow('plugin failed')
|
|
150
|
+
})
|
|
151
|
+
})
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import * as gulpConfig from '../../gulp.config.mjs'
|
|
2
|
-
import { removeDirectory } from './removeDirectory.mjs'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Deletes all the distribution and browser files (used before create a new build).
|
|
6
|
-
* Configure array of directories to remove with 'cleanPaths'.
|
|
7
|
-
* @memberOf module:partials
|
|
8
|
-
* @returns {Promise<string[]> | *}
|
|
9
|
-
*/
|
|
10
|
-
export const clean = () => gulpConfig.get('cleanPaths').reduce(
|
|
11
|
-
(promise, path) => promise.then(() => removeDirectory(path)),
|
|
12
|
-
Promise.resolve()
|
|
13
|
-
)
|
|
1
|
+
import * as gulpConfig from '../../gulp.config.mjs'
|
|
2
|
+
import { removeDirectory } from './removeDirectory.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Deletes all the distribution and browser files (used before create a new build).
|
|
6
|
+
* Configure array of directories to remove with 'cleanPaths'.
|
|
7
|
+
* @memberOf module:partials
|
|
8
|
+
* @returns {Promise<string[]> | *}
|
|
9
|
+
*/
|
|
10
|
+
export const clean = () => gulpConfig.get('cleanPaths').reduce(
|
|
11
|
+
(promise, path) => promise.then(() => removeDirectory(path)),
|
|
12
|
+
Promise.resolve()
|
|
13
|
+
)
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import * as setUp from '../test-helpers/setUp.mjs'
|
|
3
|
-
import { clean } from './clean.mjs'
|
|
4
|
-
|
|
5
|
-
setUp.setDefaults('test-clean')
|
|
6
|
-
const gulpConfig = setUp.gulpConfig
|
|
7
|
-
const srcPath = gulpConfig.get('srcPath')
|
|
8
|
-
gulpConfig.set('cleanPaths', [srcPath])
|
|
9
|
-
|
|
10
|
-
beforeEach(setUp.beforeEach)
|
|
11
|
-
|
|
12
|
-
afterEach(setUp.afterEach)
|
|
13
|
-
|
|
14
|
-
describe('clean', () => {
|
|
15
|
-
test('removes all contents of directories specified', async () => {
|
|
16
|
-
expect.assertions(2)
|
|
17
|
-
expect(fs.readdirSync(srcPath)).toBeTruthy()
|
|
18
|
-
// Run the clean script to empty the directories
|
|
19
|
-
await clean()
|
|
20
|
-
expect(fs.existsSync(srcPath)).toBeFalsy()
|
|
21
|
-
})
|
|
22
|
-
})
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import * as setUp from '../test-helpers/setUp.mjs'
|
|
3
|
+
import { clean } from './clean.mjs'
|
|
4
|
+
|
|
5
|
+
setUp.setDefaults('test-clean')
|
|
6
|
+
const gulpConfig = setUp.gulpConfig
|
|
7
|
+
const srcPath = gulpConfig.get('srcPath')
|
|
8
|
+
gulpConfig.set('cleanPaths', [srcPath])
|
|
9
|
+
|
|
10
|
+
beforeEach(setUp.beforeEach)
|
|
11
|
+
|
|
12
|
+
afterEach(setUp.afterEach)
|
|
13
|
+
|
|
14
|
+
describe('clean', () => {
|
|
15
|
+
test('removes all contents of directories specified', async () => {
|
|
16
|
+
expect.assertions(2)
|
|
17
|
+
expect(fs.readdirSync(srcPath)).toBeTruthy()
|
|
18
|
+
// Run the clean script to empty the directories
|
|
19
|
+
await clean()
|
|
20
|
+
expect(fs.existsSync(srcPath)).toBeFalsy()
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { dest, src } from 'gulp'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Copy some files to a different location.
|
|
5
|
-
* @memberOf module:partials
|
|
6
|
-
* @param {string|array} srcPath
|
|
7
|
-
* @param {string} destPath
|
|
8
|
-
* @param {Object} [options]
|
|
9
|
-
* @param {string} [options.base] - Passed through to gulp's own src(pattern, {base}), to preserve srcPath's own
|
|
10
|
-
* subdirectory structure in destPath instead of flattening it.
|
|
11
|
-
* @returns {stream.Stream}
|
|
12
|
-
*/
|
|
13
|
-
// encoding: false copies the bytes untouched - Gulp 5's default utf8 handling corrupts binary files such as fonts.
|
|
14
|
-
export const copyFor = (srcPath, destPath, { base } = {}) => src(srcPath, { encoding: false, ...(base ? { base } : {}) })
|
|
15
|
-
.pipe(dest(destPath))
|
|
1
|
+
import { dest, src } from 'gulp'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copy some files to a different location.
|
|
5
|
+
* @memberOf module:partials
|
|
6
|
+
* @param {string|array} srcPath
|
|
7
|
+
* @param {string} destPath
|
|
8
|
+
* @param {Object} [options]
|
|
9
|
+
* @param {string} [options.base] - Passed through to gulp's own src(pattern, {base}), to preserve srcPath's own
|
|
10
|
+
* subdirectory structure in destPath instead of flattening it.
|
|
11
|
+
* @returns {stream.Stream}
|
|
12
|
+
*/
|
|
13
|
+
// encoding: false copies the bytes untouched - Gulp 5's default utf8 handling corrupts binary files such as fonts.
|
|
14
|
+
export const copyFor = (srcPath, destPath, { base } = {}) => src(srcPath, { encoding: false, ...(base ? { base } : {}) })
|
|
15
|
+
.pipe(dest(destPath))
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import * as setUp from '../test-helpers/setUp.mjs'
|
|
3
|
-
import { copyFor } from './copyFor.mjs'
|
|
4
|
-
|
|
5
|
-
setUp.setDefaults('test-copy-for')
|
|
6
|
-
const gulpConfig = setUp.gulpConfig
|
|
7
|
-
|
|
8
|
-
const fileContents = 'some file contents'
|
|
9
|
-
|
|
10
|
-
beforeEach(setUp.beforeEach)
|
|
11
|
-
|
|
12
|
-
afterEach(setUp.afterEach)
|
|
13
|
-
|
|
14
|
-
describe('copyFor', () => {
|
|
15
|
-
test('copies the file and ', done => {
|
|
16
|
-
const srcPath = gulpConfig.get('srcPath')
|
|
17
|
-
const copyFile = `${srcPath}/fileToCopy.txt`
|
|
18
|
-
fs.writeFileSync(copyFile, fileContents)
|
|
19
|
-
expect.assertions(3)
|
|
20
|
-
const oldContents = fs.readFileSync(copyFile).toString()
|
|
21
|
-
expect(oldContents).toEqual(fileContents)
|
|
22
|
-
const browserPath = gulpConfig.get('browser.to')
|
|
23
|
-
copyFor(copyFile, browserPath)
|
|
24
|
-
.on('finish', () => {
|
|
25
|
-
expect(fs.existsSync(browserPath)).toBeTruthy()
|
|
26
|
-
const movedContents = fs.readFileSync(`${browserPath}/fileToCopy.txt`).toString()
|
|
27
|
-
expect(movedContents).toEqual(fileContents)
|
|
28
|
-
done()
|
|
29
|
-
})
|
|
30
|
-
.on('error', error => {
|
|
31
|
-
console.error('Encountered error', error)
|
|
32
|
-
done()
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('copies binary files byte for byte, including a leading BOM and bytes above 0x7f', done => {
|
|
37
|
-
const srcPath = gulpConfig.get('srcPath')
|
|
38
|
-
const binaryFile = `${srcPath}/font.woff`
|
|
39
|
-
const original = Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), Buffer.from(Array.from({ length: 512 }, (_, i) => i % 256))])
|
|
40
|
-
fs.writeFileSync(binaryFile, original)
|
|
41
|
-
expect.assertions(1)
|
|
42
|
-
const browserPath = gulpConfig.get('browser.to')
|
|
43
|
-
copyFor(binaryFile, browserPath)
|
|
44
|
-
.on('finish', () => {
|
|
45
|
-
expect(fs.readFileSync(`${browserPath}/font.woff`).equals(original)).toBeTruthy()
|
|
46
|
-
done()
|
|
47
|
-
})
|
|
48
|
-
.on('error', error => {
|
|
49
|
-
console.error('Encountered error', error)
|
|
50
|
-
done()
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
})
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import * as setUp from '../test-helpers/setUp.mjs'
|
|
3
|
+
import { copyFor } from './copyFor.mjs'
|
|
4
|
+
|
|
5
|
+
setUp.setDefaults('test-copy-for')
|
|
6
|
+
const gulpConfig = setUp.gulpConfig
|
|
7
|
+
|
|
8
|
+
const fileContents = 'some file contents'
|
|
9
|
+
|
|
10
|
+
beforeEach(setUp.beforeEach)
|
|
11
|
+
|
|
12
|
+
afterEach(setUp.afterEach)
|
|
13
|
+
|
|
14
|
+
describe('copyFor', () => {
|
|
15
|
+
test('copies the file and ', done => {
|
|
16
|
+
const srcPath = gulpConfig.get('srcPath')
|
|
17
|
+
const copyFile = `${srcPath}/fileToCopy.txt`
|
|
18
|
+
fs.writeFileSync(copyFile, fileContents)
|
|
19
|
+
expect.assertions(3)
|
|
20
|
+
const oldContents = fs.readFileSync(copyFile).toString()
|
|
21
|
+
expect(oldContents).toEqual(fileContents)
|
|
22
|
+
const browserPath = gulpConfig.get('browser.to')
|
|
23
|
+
copyFor(copyFile, browserPath)
|
|
24
|
+
.on('finish', () => {
|
|
25
|
+
expect(fs.existsSync(browserPath)).toBeTruthy()
|
|
26
|
+
const movedContents = fs.readFileSync(`${browserPath}/fileToCopy.txt`).toString()
|
|
27
|
+
expect(movedContents).toEqual(fileContents)
|
|
28
|
+
done()
|
|
29
|
+
})
|
|
30
|
+
.on('error', error => {
|
|
31
|
+
console.error('Encountered error', error)
|
|
32
|
+
done()
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('copies binary files byte for byte, including a leading BOM and bytes above 0x7f', done => {
|
|
37
|
+
const srcPath = gulpConfig.get('srcPath')
|
|
38
|
+
const binaryFile = `${srcPath}/font.woff`
|
|
39
|
+
const original = Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), Buffer.from(Array.from({ length: 512 }, (_, i) => i % 256))])
|
|
40
|
+
fs.writeFileSync(binaryFile, original)
|
|
41
|
+
expect.assertions(1)
|
|
42
|
+
const browserPath = gulpConfig.get('browser.to')
|
|
43
|
+
copyFor(binaryFile, browserPath)
|
|
44
|
+
.on('finish', () => {
|
|
45
|
+
expect(fs.readFileSync(`${browserPath}/font.woff`).equals(original)).toBeTruthy()
|
|
46
|
+
done()
|
|
47
|
+
})
|
|
48
|
+
.on('error', error => {
|
|
49
|
+
console.error('Encountered error', error)
|
|
50
|
+
done()
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import babel from 'gulp-babel'
|
|
2
|
-
import { dest, src } from 'gulp'
|
|
3
|
-
import { distForSrc } from './distForSrc.mjs'
|
|
4
|
-
import * as gulpConfig from '../../gulp.config.mjs'
|
|
5
|
-
import through from 'through2'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Build the distribution for a given source pattern.
|
|
9
|
-
* @memberOf module:partials
|
|
10
|
-
* @param {string|array} [srcPath='src/config/path/dist/for']
|
|
11
|
-
* @param {string} [destPath='dist/config/path']
|
|
12
|
-
* @returns {stream.Stream}
|
|
13
|
-
*/
|
|
14
|
-
export const distFor = (srcPath = distForSrc(), destPath = gulpConfig.get('dist.to')) => src(srcPath)
|
|
15
|
-
.pipe(babel())
|
|
16
|
-
.pipe(through.obj(function (file, enc, callback) {
|
|
17
|
-
const regex = /\.mjs(['"]\s*\))/gi
|
|
18
|
-
file.contents = Buffer.from(file.contents.toString().replaceAll(regex, '.js$1'))
|
|
19
|
-
this.push(file)
|
|
20
|
-
callback()
|
|
21
|
-
}))
|
|
22
|
-
.pipe(dest(destPath))
|
|
1
|
+
import babel from 'gulp-babel'
|
|
2
|
+
import { dest, src } from 'gulp'
|
|
3
|
+
import { distForSrc } from './distForSrc.mjs'
|
|
4
|
+
import * as gulpConfig from '../../gulp.config.mjs'
|
|
5
|
+
import through from 'through2'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build the distribution for a given source pattern.
|
|
9
|
+
* @memberOf module:partials
|
|
10
|
+
* @param {string|array} [srcPath='src/config/path/dist/for']
|
|
11
|
+
* @param {string} [destPath='dist/config/path']
|
|
12
|
+
* @returns {stream.Stream}
|
|
13
|
+
*/
|
|
14
|
+
export const distFor = (srcPath = distForSrc(), destPath = gulpConfig.get('dist.to')) => src(srcPath)
|
|
15
|
+
.pipe(babel())
|
|
16
|
+
.pipe(through.obj(function (file, enc, callback) {
|
|
17
|
+
const regex = /\.mjs(['"]\s*\))/gi
|
|
18
|
+
file.contents = Buffer.from(file.contents.toString().replaceAll(regex, '.js$1'))
|
|
19
|
+
this.push(file)
|
|
20
|
+
callback()
|
|
21
|
+
}))
|
|
22
|
+
.pipe(dest(destPath))
|