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.
Files changed (173) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +768 -701
  3. package/babel.config.js +28 -28
  4. package/dist/build.js +75 -75
  5. package/dist/bundle.js +19 -19
  6. package/dist/bundleMinify.js +7 -7
  7. package/dist/compileReadme.js +4 -4
  8. package/dist/copyFiles.js +17 -17
  9. package/dist/copyFonts.js +4 -4
  10. package/dist/default.js +5 -5
  11. package/dist/dist.js +4 -4
  12. package/dist/distMinify.js +4 -4
  13. package/dist/docs.js +7 -7
  14. package/dist/images.js +4 -4
  15. package/dist/jsdocDocs.js +6 -6
  16. package/dist/lint.js +16 -16
  17. package/dist/partials/addToReadme.js +9 -9
  18. package/dist/partials/beginWatcher.js +4 -4
  19. package/dist/partials/cacheFiles.js +121 -0
  20. package/dist/partials/clean.js +5 -5
  21. package/dist/partials/copyFor.js +9 -9
  22. package/dist/partials/distFor.js +6 -6
  23. package/dist/partials/distForSrc.js +5 -5
  24. package/dist/partials/distSeries.js +7 -7
  25. package/dist/partials/imagesFor.js +155 -93
  26. package/dist/partials/jsdocDocsFor.js +64 -51
  27. package/dist/partials/lintSource.js +51 -51
  28. package/dist/partials/loadPeer.js +99 -99
  29. package/dist/partials/loadPlaywright.js +14 -14
  30. package/dist/partials/minifyFor.js +20 -20
  31. package/dist/partials/neostandardLint.js +175 -175
  32. package/dist/partials/readmeTemplate.js +4 -4
  33. package/dist/partials/removeDirectory.js +11 -11
  34. package/dist/partials/resolveDocsMethod.js +6 -6
  35. package/dist/partials/runOnChange.js +31 -31
  36. package/dist/partials/sassFor.js +6 -6
  37. package/dist/partials/serveStatic.js +9 -9
  38. package/dist/partials/tsFor.js +32 -32
  39. package/dist/partials/typeDocsFor.js +264 -264
  40. package/dist/partials.js +183 -176
  41. package/dist/partials.min.js +1 -1
  42. package/dist/sourceLint.js +18 -18
  43. package/dist/test-helpers/setUp.js +6 -4
  44. package/dist/testFull.js +5 -5
  45. package/dist/testQuick.js +5 -5
  46. package/dist/typeDocs.js +5 -5
  47. package/dist/typeScript.js +4 -4
  48. package/dist/utilities/convertMjsFilesForDocs.js +12 -12
  49. package/dist/utilities/dotGet.js +54 -54
  50. package/dist/utilities/dotNotate.js +79 -79
  51. package/dist/utilities/dotSet.js +45 -45
  52. package/dist/utilities/esmNamedExportsToCjs.js +15 -15
  53. package/dist/utilities/getBundleConfigs.js +9 -9
  54. package/dist/utilities/isObject.js +6 -6
  55. package/dist/utilities/strAfter.js +7 -7
  56. package/dist/utilities/strBefore.js +7 -7
  57. package/dist/watchFull.js +4 -4
  58. package/dist/watchTest.js +4 -4
  59. package/functions/build.mjs +72 -72
  60. package/functions/build.test.mjs +760 -759
  61. package/functions/bundle.mjs +44 -44
  62. package/functions/bundle.test.mjs +219 -219
  63. package/functions/bundleMinify.mjs +16 -16
  64. package/functions/bundleMinify.test.mjs +37 -37
  65. package/functions/compileReadme.mjs +9 -9
  66. package/functions/compileReadme.test.mjs +18 -18
  67. package/functions/copyFiles.mjs +32 -32
  68. package/functions/copyFiles.test.mjs +78 -78
  69. package/functions/copyFonts.mjs +9 -9
  70. package/functions/copyFonts.test.mjs +32 -32
  71. package/functions/default.mjs +12 -12
  72. package/functions/default.test.mjs +33 -33
  73. package/functions/dist.mjs +8 -8
  74. package/functions/dist.test.mjs +44 -44
  75. package/functions/distMinify.mjs +9 -9
  76. package/functions/distMinify.test.mjs +15 -15
  77. package/functions/docs.mjs +13 -13
  78. package/functions/docs.test.mjs +45 -45
  79. package/functions/gulpConfigEntries.test.mjs +23 -23
  80. package/functions/images.mjs +8 -8
  81. package/functions/images.test.mjs +140 -142
  82. package/functions/jsdocDocs.mjs +10 -10
  83. package/functions/jsdocEsmExports.test.mjs +24 -24
  84. package/functions/lint.mjs +9 -9
  85. package/functions/partials/addToReadme.mjs +25 -25
  86. package/functions/partials/addToReadme.test.mjs +34 -34
  87. package/functions/partials/beginWatcher.mjs +9 -9
  88. package/functions/partials/beginWatcher.test.mjs +19 -19
  89. package/functions/partials/cacheFiles.mjs +114 -0
  90. package/functions/partials/cacheFiles.test.mjs +151 -0
  91. package/functions/partials/clean.mjs +13 -13
  92. package/functions/partials/clean.test.mjs +22 -22
  93. package/functions/partials/copyFor.mjs +15 -15
  94. package/functions/partials/copyFor.test.mjs +53 -53
  95. package/functions/partials/distFor.mjs +22 -22
  96. package/functions/partials/distFor.test.mjs +195 -195
  97. package/functions/partials/distForSrc.mjs +11 -11
  98. package/functions/partials/distForSrc.test.mjs +18 -18
  99. package/functions/partials/distSeries.mjs +21 -21
  100. package/functions/partials/distSeries.test.mjs +34 -34
  101. package/functions/partials/imagesFor.mjs +147 -83
  102. package/functions/partials/imagesFor.test.mjs +311 -287
  103. package/functions/partials/jsdocDocsFor.mjs +163 -152
  104. package/functions/partials/jsdocDocsFor.test.mjs +182 -152
  105. package/functions/partials/lintSource.mjs +41 -41
  106. package/functions/partials/loadPeer.mjs +76 -76
  107. package/functions/partials/loadPeer.test.mjs +58 -58
  108. package/functions/partials/loadPlaywright.mjs +23 -23
  109. package/functions/partials/loadPlaywright.test.mjs +70 -70
  110. package/functions/partials/minifyFor.mjs +15 -15
  111. package/functions/partials/minifyFor.test.mjs +49 -49
  112. package/functions/partials/neostandardLint.mjs +142 -142
  113. package/functions/partials/neostandardLint.test.mjs +131 -131
  114. package/functions/partials/readmeTemplate.mjs +12 -12
  115. package/functions/partials/readmeTemplate.test.mjs +34 -34
  116. package/functions/partials/removeDirectory.mjs +34 -34
  117. package/functions/partials/removeDirectory.test.mjs +72 -72
  118. package/functions/partials/resolveDocsMethod.mjs +10 -10
  119. package/functions/partials/resolveDocsMethod.test.mjs +33 -33
  120. package/functions/partials/runOnChange.mjs +54 -54
  121. package/functions/partials/runOnChange.test.mjs +95 -95
  122. package/functions/partials/sassFor.mjs +25 -25
  123. package/functions/partials/sassFor.test.mjs +81 -81
  124. package/functions/partials/serveStatic.mjs +47 -47
  125. package/functions/partials/serveStatic.test.mjs +71 -71
  126. package/functions/partials/tsFor.mjs +92 -92
  127. package/functions/partials/tsFor.test.mjs +88 -88
  128. package/functions/partials/typeDocsFor.mjs +231 -231
  129. package/functions/partials/typeDocsFor.test.mjs +263 -263
  130. package/functions/partials.mjs +32 -31
  131. package/functions/partials.test.mjs +7 -7
  132. package/functions/sass.mjs +4 -4
  133. package/functions/sass.test.mjs +31 -31
  134. package/functions/sourceLint.mjs +13 -13
  135. package/functions/sourceLint.test.mjs +166 -166
  136. package/functions/test-helpers/neostandardLintWithRealNeostandard.mjs +40 -40
  137. package/functions/test-helpers/setUp.mjs +102 -100
  138. package/functions/test-helpers/setUp.test.mjs +19 -19
  139. package/functions/testFull.mjs +17 -17
  140. package/functions/testFull.test.mjs +17 -17
  141. package/functions/testHelpers.mjs +10 -10
  142. package/functions/testHelpers.test.mjs +7 -7
  143. package/functions/testQuick.mjs +17 -17
  144. package/functions/testQuick.test.mjs +17 -17
  145. package/functions/typeDocs.mjs +9 -9
  146. package/functions/typeDocs.test.mjs +11 -11
  147. package/functions/typeScript.mjs +8 -8
  148. package/functions/typeScript.test.mjs +11 -11
  149. package/functions/utilities/convertMjsFilesForDocs.mjs +34 -34
  150. package/functions/utilities/convertMjsFilesForDocs.test.mjs +58 -58
  151. package/functions/utilities/dotGet.mjs +49 -49
  152. package/functions/utilities/dotNotate.mjs +73 -73
  153. package/functions/utilities/dotNotate.test.mjs +41 -41
  154. package/functions/utilities/dotSet.mjs +40 -40
  155. package/functions/utilities/esmNamedExportsToCjs.mjs +30 -30
  156. package/functions/utilities/esmNamedExportsToCjs.test.mjs +47 -47
  157. package/functions/utilities/getBundleConfigs.mjs +30 -30
  158. package/functions/utilities/isObject.mjs +8 -8
  159. package/functions/utilities/strAfter.mjs +12 -12
  160. package/functions/utilities/strBefore.mjs +12 -12
  161. package/functions/watchFull.mjs +9 -9
  162. package/functions/watchFull.test.mjs +41 -41
  163. package/functions/watchTest.mjs +10 -10
  164. package/functions/watchTest.test.mjs +49 -49
  165. package/gulp.config.js +4 -4
  166. package/gulp.config.mjs +349 -340
  167. package/gulpfile.base.js +51 -51
  168. package/gulpfile.base.mjs +29 -29
  169. package/jsdoc.base.js +16 -16
  170. package/jsdoc.esmExports.js +17 -17
  171. package/package.json +146 -145
  172. package/dist/test-helpers/isolateGulpCache.js +0 -25
  173. 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))