js-build-tools 6.5.0 → 6.7.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 (169) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +762 -692
  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/clean.js +5 -5
  20. package/dist/partials/copyFor.js +9 -9
  21. package/dist/partials/distFor.js +6 -6
  22. package/dist/partials/distForSrc.js +5 -5
  23. package/dist/partials/distSeries.js +7 -7
  24. package/dist/partials/imagesFor.js +93 -56
  25. package/dist/partials/jsdocDocsFor.js +46 -46
  26. package/dist/partials/lintSource.js +51 -51
  27. package/dist/partials/loadPeer.js +99 -99
  28. package/dist/partials/loadPlaywright.js +14 -14
  29. package/dist/partials/minifyFor.js +20 -20
  30. package/dist/partials/neostandardLint.js +175 -175
  31. package/dist/partials/readmeTemplate.js +4 -4
  32. package/dist/partials/removeDirectory.js +11 -11
  33. package/dist/partials/resolveDocsMethod.js +6 -6
  34. package/dist/partials/runOnChange.js +31 -31
  35. package/dist/partials/sassFor.js +6 -6
  36. package/dist/partials/serveStatic.js +9 -9
  37. package/dist/partials/tsFor.js +32 -32
  38. package/dist/partials/typeDocsFor.js +57 -51
  39. package/dist/partials.js +176 -176
  40. package/dist/sourceLint.js +18 -18
  41. package/dist/test-helpers/isolateGulpCache.js +24 -24
  42. package/dist/test-helpers/setUp.js +4 -4
  43. package/dist/testFull.js +5 -5
  44. package/dist/testQuick.js +5 -5
  45. package/dist/typeDocs.js +5 -5
  46. package/dist/typeScript.js +4 -4
  47. package/dist/utilities/convertMjsFilesForDocs.js +12 -12
  48. package/dist/utilities/dotGet.js +54 -54
  49. package/dist/utilities/dotNotate.js +79 -79
  50. package/dist/utilities/dotSet.js +45 -45
  51. package/dist/utilities/esmNamedExportsToCjs.js +15 -15
  52. package/dist/utilities/getBundleConfigs.js +9 -9
  53. package/dist/utilities/isObject.js +6 -6
  54. package/dist/utilities/strAfter.js +7 -7
  55. package/dist/utilities/strBefore.js +7 -7
  56. package/dist/watchFull.js +4 -4
  57. package/dist/watchTest.js +4 -4
  58. package/functions/build.mjs +72 -72
  59. package/functions/build.test.mjs +763 -759
  60. package/functions/bundle.mjs +44 -44
  61. package/functions/bundle.test.mjs +219 -219
  62. package/functions/bundleMinify.mjs +16 -16
  63. package/functions/bundleMinify.test.mjs +37 -37
  64. package/functions/compileReadme.mjs +9 -9
  65. package/functions/compileReadme.test.mjs +18 -18
  66. package/functions/copyFiles.mjs +32 -32
  67. package/functions/copyFiles.test.mjs +78 -78
  68. package/functions/copyFonts.mjs +9 -9
  69. package/functions/copyFonts.test.mjs +32 -32
  70. package/functions/default.mjs +12 -12
  71. package/functions/default.test.mjs +33 -33
  72. package/functions/dist.mjs +8 -8
  73. package/functions/dist.test.mjs +44 -44
  74. package/functions/distMinify.mjs +9 -9
  75. package/functions/distMinify.test.mjs +15 -15
  76. package/functions/docs.mjs +13 -13
  77. package/functions/docs.test.mjs +45 -45
  78. package/functions/gulpConfigEntries.test.mjs +23 -23
  79. package/functions/images.mjs +8 -8
  80. package/functions/images.test.mjs +142 -142
  81. package/functions/jsdocDocs.mjs +10 -10
  82. package/functions/jsdocEsmExports.test.mjs +24 -24
  83. package/functions/lint.mjs +9 -9
  84. package/functions/partials/addToReadme.mjs +25 -25
  85. package/functions/partials/addToReadme.test.mjs +34 -34
  86. package/functions/partials/beginWatcher.mjs +9 -9
  87. package/functions/partials/beginWatcher.test.mjs +19 -19
  88. package/functions/partials/clean.mjs +13 -13
  89. package/functions/partials/clean.test.mjs +22 -22
  90. package/functions/partials/copyFor.mjs +15 -15
  91. package/functions/partials/copyFor.test.mjs +53 -53
  92. package/functions/partials/distFor.mjs +22 -22
  93. package/functions/partials/distFor.test.mjs +195 -195
  94. package/functions/partials/distForSrc.mjs +11 -11
  95. package/functions/partials/distForSrc.test.mjs +18 -18
  96. package/functions/partials/distSeries.mjs +21 -21
  97. package/functions/partials/distSeries.test.mjs +34 -34
  98. package/functions/partials/imagesFor.mjs +83 -50
  99. package/functions/partials/imagesFor.test.mjs +287 -229
  100. package/functions/partials/jsdocDocsFor.mjs +163 -152
  101. package/functions/partials/jsdocDocsFor.test.mjs +182 -152
  102. package/functions/partials/lintSource.mjs +41 -41
  103. package/functions/partials/loadPeer.mjs +76 -76
  104. package/functions/partials/loadPeer.test.mjs +58 -58
  105. package/functions/partials/loadPlaywright.mjs +23 -23
  106. package/functions/partials/loadPlaywright.test.mjs +70 -70
  107. package/functions/partials/minifyFor.mjs +15 -15
  108. package/functions/partials/minifyFor.test.mjs +49 -49
  109. package/functions/partials/neostandardLint.mjs +142 -142
  110. package/functions/partials/neostandardLint.test.mjs +131 -131
  111. package/functions/partials/readmeTemplate.mjs +12 -12
  112. package/functions/partials/readmeTemplate.test.mjs +34 -34
  113. package/functions/partials/removeDirectory.mjs +34 -34
  114. package/functions/partials/removeDirectory.test.mjs +72 -72
  115. package/functions/partials/resolveDocsMethod.mjs +10 -10
  116. package/functions/partials/resolveDocsMethod.test.mjs +33 -33
  117. package/functions/partials/runOnChange.mjs +54 -54
  118. package/functions/partials/runOnChange.test.mjs +95 -95
  119. package/functions/partials/sassFor.mjs +25 -25
  120. package/functions/partials/sassFor.test.mjs +81 -81
  121. package/functions/partials/serveStatic.mjs +47 -47
  122. package/functions/partials/serveStatic.test.mjs +71 -71
  123. package/functions/partials/tsFor.mjs +92 -92
  124. package/functions/partials/tsFor.test.mjs +88 -88
  125. package/functions/partials/typeDocsFor.mjs +231 -227
  126. package/functions/partials/typeDocsFor.test.mjs +263 -252
  127. package/functions/partials.mjs +31 -31
  128. package/functions/partials.test.mjs +7 -7
  129. package/functions/sass.mjs +4 -4
  130. package/functions/sass.test.mjs +31 -31
  131. package/functions/sourceLint.mjs +13 -13
  132. package/functions/sourceLint.test.mjs +166 -166
  133. package/functions/test-helpers/isolateGulpCache.mjs +16 -16
  134. package/functions/test-helpers/neostandardLintWithRealNeostandard.mjs +40 -40
  135. package/functions/test-helpers/setUp.mjs +100 -100
  136. package/functions/test-helpers/setUp.test.mjs +19 -19
  137. package/functions/testFull.mjs +17 -17
  138. package/functions/testFull.test.mjs +17 -17
  139. package/functions/testHelpers.mjs +10 -10
  140. package/functions/testHelpers.test.mjs +7 -7
  141. package/functions/testQuick.mjs +17 -17
  142. package/functions/testQuick.test.mjs +17 -17
  143. package/functions/typeDocs.mjs +9 -9
  144. package/functions/typeDocs.test.mjs +11 -11
  145. package/functions/typeScript.mjs +8 -8
  146. package/functions/typeScript.test.mjs +11 -11
  147. package/functions/utilities/convertMjsFilesForDocs.mjs +34 -34
  148. package/functions/utilities/convertMjsFilesForDocs.test.mjs +58 -58
  149. package/functions/utilities/dotGet.mjs +49 -49
  150. package/functions/utilities/dotNotate.mjs +73 -73
  151. package/functions/utilities/dotNotate.test.mjs +41 -41
  152. package/functions/utilities/dotSet.mjs +40 -40
  153. package/functions/utilities/esmNamedExportsToCjs.mjs +30 -30
  154. package/functions/utilities/esmNamedExportsToCjs.test.mjs +47 -47
  155. package/functions/utilities/getBundleConfigs.mjs +30 -30
  156. package/functions/utilities/isObject.mjs +8 -8
  157. package/functions/utilities/strAfter.mjs +12 -12
  158. package/functions/utilities/strBefore.mjs +12 -12
  159. package/functions/watchFull.mjs +9 -9
  160. package/functions/watchFull.test.mjs +41 -41
  161. package/functions/watchTest.mjs +10 -10
  162. package/functions/watchTest.test.mjs +49 -49
  163. package/gulp.config.js +4 -4
  164. package/gulp.config.mjs +344 -327
  165. package/gulpfile.base.js +51 -51
  166. package/gulpfile.base.mjs +29 -29
  167. package/jsdoc.base.js +16 -16
  168. package/jsdoc.esmExports.js +17 -17
  169. package/package.json +146 -145
@@ -1,152 +1,182 @@
1
- import fs from 'fs'
2
- import path from 'node:path'
3
- import * as setUp from '../test-helpers/setUp.mjs'
4
- import { jsdocDocsFor, resolveJsdocBin, resolveJsdocTheme } from './jsdocDocsFor.mjs'
5
- import jsdocBase from '../../jsdoc.base.js'
6
-
7
- setUp.setDefaults('test-jsdoc-docs-for')
8
- const gulpConfig = setUp.gulpConfig
9
-
10
- const write = (file, contents) => {
11
- fs.mkdirSync(path.dirname(file), { recursive: true })
12
- fs.writeFileSync(file, contents)
13
- }
14
-
15
- beforeEach(
16
- () => setUp.beforeEach().then(() => {
17
- const srcPath = gulpConfig.get('srcPath')
18
- write(`${srcPath}/add.js`, '/**\n * Add two numbers.\n * @param {number} a\n * @param {number} b\n * @returns {number}\n */\nconst add = (a, b) => a + b\n\nmodule.exports = add\n')
19
- })
20
- )
21
-
22
- afterEach(setUp.afterEach)
23
-
24
- describe('resolveJsdocBin / resolveJsdocTheme', () => {
25
- test('resolves the real installed jsdoc CLI entry', () => {
26
- const bin = resolveJsdocBin()
27
- expect(fs.existsSync(bin)).toBe(true)
28
- expect(path.basename(bin)).toBe('jsdoc.js')
29
- })
30
-
31
- test('resolves the real installed theme directory', () => {
32
- const themeDir = resolveJsdocTheme('clean-jsdoc-theme')
33
- expect(fs.existsSync(themeDir)).toBe(true)
34
- })
35
-
36
- test('throws a clear, install-hint error for a package that is not installed', () => {
37
- expect(() => resolveJsdocTheme('not-a-real-jsdoc-theme-package')).toThrow(/npm install --save-dev/)
38
- })
39
- })
40
-
41
- describe('jsdocDocsFor', () => {
42
- const fakeRunJsdoc = calls => (bin, args) => {
43
- calls.bin = bin
44
- calls.args = args
45
- calls.config = JSON.parse(fs.readFileSync(args[args.indexOf('-c') + 1], 'utf8'))
46
- fs.mkdirSync(calls.config.opts.destination, { recursive: true })
47
- fs.writeFileSync(`${calls.config.opts.destination}/index.html`, '<html></html>')
48
- return Promise.resolve()
49
- }
50
-
51
- test('generates the documentation into the output directory, clearing what was there', async () => {
52
- gulpConfig.set('docs.enabled', true)
53
- const outPath = `${gulpConfig.get('srcPath')}/../docs`
54
- write(`${outPath}/stale.html`, 'old')
55
- const calls = {}
56
- await jsdocDocsFor(gulpConfig.get('srcPath'), outPath, fakeRunJsdoc(calls))()
57
- expect(fs.existsSync(`${outPath}/stale.html`)).toBe(false)
58
- expect(fs.existsSync(`${outPath}/index.html`)).toBe(true)
59
- expect(calls.config.opts.destination).toBe(path.resolve(outPath))
60
- expect(calls.config.source.include).toEqual([path.resolve(gulpConfig.get('srcPath'))])
61
- // Headings of the front page get ids, so the theme's table of contents can follow them
62
- expect(calls.config.markdown).toEqual({ idInHeadings: true })
63
- })
64
-
65
- test('passes on what jsdoc reports', async () => {
66
- gulpConfig.set('docs.enabled', true)
67
- const srcPath = gulpConfig.get('srcPath')
68
- const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
69
- const calls = {}
70
- const reporting = (bin, args) => fakeRunJsdoc(calls)(bin, args).then(() => ({ stdout: '', stderr: 'WARNING: a page failed\n' }))
71
- await jsdocDocsFor(srcPath, `${srcPath}/../docs`, reporting)()
72
- expect(warn).toHaveBeenCalledWith('WARNING: a page failed')
73
- warn.mockClear()
74
- await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
75
- expect(warn).not.toHaveBeenCalled()
76
- warn.mockRestore()
77
- })
78
-
79
- test('takes a list of directories and files to document', async () => {
80
- gulpConfig.set('docs.enabled', true)
81
- const srcPath = gulpConfig.get('srcPath')
82
- const calls = {}
83
- await jsdocDocsFor([srcPath, `${srcPath}/../gulp.config.mjs`], `${srcPath}/../docs`, fakeRunJsdoc(calls))()
84
- expect(calls.config.source.include).toEqual([path.resolve(srcPath), path.resolve(`${srcPath}/../gulp.config.mjs`)])
85
- })
86
-
87
- test('documents .js, .mjs and .cjs files, but not tests or minified copies', () => {
88
- const include = new RegExp(jsdocBase.source.includePattern)
89
- const exclude = new RegExp(jsdocBase.source.excludePattern)
90
- const documented = file => include.test(file) && !exclude.test(file)
91
- expect(['a.js', 'a.mjs', 'a.cjs', 'dir/a.mjs'].filter(documented)).toEqual(['a.js', 'a.mjs', 'a.cjs', 'dir/a.mjs'])
92
- expect(['a.test.mjs', 'a.min.js', 'a.min.mjs', 'dir/_private.mjs', 'a.ts'].filter(documented)).toEqual([])
93
- })
94
-
95
- test('passes docs.title to the theme as a link to the home page of the docs and as its page title, and leaves it out when unset', async () => {
96
- gulpConfig.set('docs.enabled', true)
97
- const srcPath = gulpConfig.get('srcPath')
98
- const calls = {}
99
- gulpConfig.set('docs.title', 'Tom & Jerry <Tools>')
100
- await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
101
- expect(calls.config.opts.theme_opts).toEqual({
102
- title: '<a href="index.html" class="sidebar-title sidebar-title-anchor">Tom &amp; Jerry &lt;Tools&gt;</a>',
103
- homepageTitle: 'Tom &amp; Jerry &lt;Tools&gt;'
104
- })
105
-
106
- gulpConfig.set('docs.title', '')
107
- await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
108
- expect(calls.config.opts.theme_opts).toBeUndefined()
109
- })
110
-
111
- test('uses the configured theme as the template directory', async () => {
112
- gulpConfig.set('docs.enabled', true)
113
- gulpConfig.set('docs.theme', 'clean-jsdoc-theme')
114
- const calls = {}
115
- await jsdocDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeRunJsdoc(calls))()
116
- expect(calls.config.opts.template).toBe(resolveJsdocTheme('clean-jsdoc-theme'))
117
- })
118
-
119
- test('uses the front page from docs.index when it exists, and leaves readme unset when it does not', async () => {
120
- gulpConfig.set('docs.enabled', true)
121
- gulpConfig.set('docs.index', `${gulpConfig.get('srcPath')}/../MAIN.md`)
122
- write(`${gulpConfig.get('srcPath')}/../MAIN.md`, '# hello\n')
123
- const calls = {}
124
- await jsdocDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeRunJsdoc(calls))()
125
- expect(calls.config.opts.readme).toBe(path.resolve(`${gulpConfig.get('srcPath')}/../MAIN.md`))
126
-
127
- gulpConfig.set('docs.index', 'does-not-exist.md')
128
- await jsdocDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeRunJsdoc(calls))()
129
- expect(calls.config.opts.readme).toBeUndefined()
130
- })
131
-
132
- test('skips if docs.enabled is false', () => {
133
- gulpConfig.set('docs.enabled', false)
134
- const result = jsdocDocsFor()
135
- expect(result).toBeInstanceOf(Function)
136
- expect(result.length).toBe(0)
137
- })
138
- })
139
-
140
- describe('jsdocDocsFor, end to end with the real jsdoc CLI and clean-jsdoc-theme', () => {
141
- test('produces a real browsable HTML page from real JSDoc comments', async () => {
142
- gulpConfig.set('docs.enabled', true)
143
- gulpConfig.set('docs.title', 'Tom & Jerry')
144
- const outPath = `${gulpConfig.get('srcPath')}/../docs`
145
- await jsdocDocsFor(gulpConfig.get('srcPath'), outPath)()
146
- // clean-jsdoc-theme nests its real output under its own route folders (global/, source/...), not flat at
147
- // outPath's own root - confirmed by actually running it, not assumed from the classic jsdoc template shape.
148
- const files = fs.readdirSync(outPath, { recursive: true })
149
- expect(files.some(file => file.endsWith('.html'))).toBe(true)
150
- expect(fs.readFileSync(`${outPath}/index.html`, 'utf8')).toMatch(/<title>\s*Tom &amp; Jerry\s*<\/title>/)
151
- }, 30000)
152
- })
1
+ import fs from 'fs'
2
+ import path from 'node:path'
3
+ import * as setUp from '../test-helpers/setUp.mjs'
4
+ import { jsdocDocsFor, resolveJsdocBin, resolveJsdocTheme } from './jsdocDocsFor.mjs'
5
+ import jsdocBase from '../../jsdoc.base.js'
6
+
7
+ setUp.setDefaults('test-jsdoc-docs-for')
8
+ const gulpConfig = setUp.gulpConfig
9
+
10
+ const write = (file, contents) => {
11
+ fs.mkdirSync(path.dirname(file), { recursive: true })
12
+ fs.writeFileSync(file, contents)
13
+ }
14
+
15
+ beforeEach(
16
+ () => setUp.beforeEach().then(() => {
17
+ const srcPath = gulpConfig.get('srcPath')
18
+ write(`${srcPath}/add.js`, '/**\n * Add two numbers.\n * @param {number} a\n * @param {number} b\n * @returns {number}\n */\nconst add = (a, b) => a + b\n\nmodule.exports = add\n')
19
+ })
20
+ )
21
+
22
+ afterEach(setUp.afterEach)
23
+
24
+ describe('resolveJsdocBin / resolveJsdocTheme', () => {
25
+ test('resolves the real installed jsdoc CLI entry', () => {
26
+ const bin = resolveJsdocBin()
27
+ expect(fs.existsSync(bin)).toBe(true)
28
+ expect(path.basename(bin)).toBe('jsdoc.js')
29
+ })
30
+
31
+ test('resolves the real installed theme directory', () => {
32
+ const themeDir = resolveJsdocTheme('clean-jsdoc-theme')
33
+ expect(fs.existsSync(themeDir)).toBe(true)
34
+ })
35
+
36
+ test('throws a clear, install-hint error for a package that is not installed', () => {
37
+ expect(() => resolveJsdocTheme('not-a-real-jsdoc-theme-package')).toThrow(/npm install --save-dev/)
38
+ })
39
+ })
40
+
41
+ describe('jsdocDocsFor', () => {
42
+ const fakeRunJsdoc = calls => (bin, args) => {
43
+ calls.bin = bin
44
+ calls.args = args
45
+ calls.config = JSON.parse(fs.readFileSync(args[args.indexOf('-c') + 1], 'utf8'))
46
+ calls.readme = fs.readFileSync(calls.config.opts.readme, 'utf8')
47
+ fs.mkdirSync(calls.config.opts.destination, { recursive: true })
48
+ fs.writeFileSync(`${calls.config.opts.destination}/index.html`, '<html></html>')
49
+ return Promise.resolve()
50
+ }
51
+
52
+ test('generates the documentation into the output directory, clearing what was there', async () => {
53
+ gulpConfig.set('docs.enabled', true)
54
+ const outPath = `${gulpConfig.get('srcPath')}/../docs`
55
+ write(`${outPath}/stale.html`, 'old')
56
+ const calls = {}
57
+ await jsdocDocsFor(gulpConfig.get('srcPath'), outPath, fakeRunJsdoc(calls))()
58
+ expect(fs.existsSync(`${outPath}/stale.html`)).toBe(false)
59
+ expect(fs.existsSync(`${outPath}/index.html`)).toBe(true)
60
+ expect(calls.config.opts.destination).toBe(path.resolve(outPath))
61
+ expect(calls.config.source.include).toEqual([path.resolve(gulpConfig.get('srcPath'))])
62
+ // Headings of the front page get ids, so the theme's table of contents can follow them
63
+ expect(calls.config.markdown).toEqual({ idInHeadings: true })
64
+ })
65
+
66
+ test('passes on what jsdoc reports', async () => {
67
+ gulpConfig.set('docs.enabled', true)
68
+ const srcPath = gulpConfig.get('srcPath')
69
+ const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
70
+ const calls = {}
71
+ const reporting = (bin, args) => fakeRunJsdoc(calls)(bin, args).then(() => ({ stdout: '', stderr: 'WARNING: a page failed\n' }))
72
+ await jsdocDocsFor(srcPath, `${srcPath}/../docs`, reporting)()
73
+ expect(warn).toHaveBeenCalledWith('WARNING: a page failed')
74
+ warn.mockClear()
75
+ await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
76
+ expect(warn).not.toHaveBeenCalled()
77
+ warn.mockRestore()
78
+ })
79
+
80
+ test('takes a list of directories and files to document', async () => {
81
+ gulpConfig.set('docs.enabled', true)
82
+ const srcPath = gulpConfig.get('srcPath')
83
+ const calls = {}
84
+ await jsdocDocsFor([srcPath, `${srcPath}/../gulp.config.mjs`], `${srcPath}/../docs`, fakeRunJsdoc(calls))()
85
+ expect(calls.config.source.include).toEqual([path.resolve(srcPath), path.resolve(`${srcPath}/../gulp.config.mjs`)])
86
+ })
87
+
88
+ test('documents .js, .mjs and .cjs files, but not tests or minified copies', () => {
89
+ const include = new RegExp(jsdocBase.source.includePattern)
90
+ const exclude = new RegExp(jsdocBase.source.excludePattern)
91
+ const documented = file => include.test(file) && !exclude.test(file)
92
+ expect(['a.js', 'a.mjs', 'a.cjs', 'dir/a.mjs'].filter(documented)).toEqual(['a.js', 'a.mjs', 'a.cjs', 'dir/a.mjs'])
93
+ expect(['a.test.mjs', 'a.min.js', 'a.min.mjs', 'dir/_private.mjs', 'a.ts'].filter(documented)).toEqual([])
94
+ })
95
+
96
+ test('passes docs.title to the theme as a link to the home page of the docs and as its page title, and leaves it out when unset', async () => {
97
+ gulpConfig.set('docs.enabled', true)
98
+ const srcPath = gulpConfig.get('srcPath')
99
+ const calls = {}
100
+ gulpConfig.set('docs.title', 'Tom & Jerry <Tools>')
101
+ await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
102
+ expect(calls.config.opts.theme_opts).toEqual({
103
+ title: '<a href="index.html" class="sidebar-title sidebar-title-anchor">Tom &amp; Jerry &lt;Tools&gt;</a>',
104
+ homepageTitle: 'Tom &amp; Jerry &lt;Tools&gt;'
105
+ })
106
+
107
+ expect(calls.config.opts.siteName).toBe('Tom & Jerry <Tools>')
108
+
109
+ gulpConfig.set('docs.title', '')
110
+ await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
111
+ expect(calls.config.opts.theme_opts).toBeUndefined()
112
+ expect(calls.config.opts.siteName).toBeUndefined()
113
+ })
114
+
115
+ test('passes docs.basePath to the theme, and leaves it out when unset', async () => {
116
+ gulpConfig.set('docs.enabled', true)
117
+ const srcPath = gulpConfig.get('srcPath')
118
+ const calls = {}
119
+ gulpConfig.set('docs.basePath', '/projects/tools/docs/')
120
+ await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
121
+ expect(calls.config.opts.basePath).toBe('/projects/tools/docs/')
122
+
123
+ gulpConfig.set('docs.basePath', false)
124
+ await jsdocDocsFor(srcPath, `${srcPath}/../docs`, fakeRunJsdoc(calls))()
125
+ expect(calls.config.opts.basePath).toBeUndefined()
126
+ })
127
+
128
+ test('uses the configured theme as the template directory', async () => {
129
+ gulpConfig.set('docs.enabled', true)
130
+ gulpConfig.set('docs.theme', 'clean-jsdoc-theme')
131
+ const calls = {}
132
+ await jsdocDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeRunJsdoc(calls))()
133
+ expect(calls.config.opts.template).toBe(resolveJsdocTheme('clean-jsdoc-theme'))
134
+ })
135
+
136
+ test('uses the front page from docs.index when it exists, and falls back to one with the title when it does not', async () => {
137
+ gulpConfig.set('docs.enabled', true)
138
+ gulpConfig.set('docs.index', `${gulpConfig.get('srcPath')}/../MAIN.md`)
139
+ write(`${gulpConfig.get('srcPath')}/../MAIN.md`, '# hello\n')
140
+ const calls = {}
141
+ await jsdocDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeRunJsdoc(calls))()
142
+ expect(calls.config.opts.readme).toBe(path.resolve(`${gulpConfig.get('srcPath')}/../MAIN.md`))
143
+
144
+ gulpConfig.set('docs.index', 'does-not-exist.md')
145
+ gulpConfig.set('docs.title', 'Tom & Jerry')
146
+ await jsdocDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeRunJsdoc(calls))()
147
+ expect(calls.readme).toBe('# Tom & Jerry\n')
148
+ // The fallback is written to a temporary folder, which is removed afterwards
149
+ expect(fs.existsSync(calls.config.opts.readme)).toBe(false)
150
+ })
151
+
152
+ test('skips if docs.enabled is false', () => {
153
+ gulpConfig.set('docs.enabled', false)
154
+ const result = jsdocDocsFor()
155
+ expect(result).toBeInstanceOf(Function)
156
+ expect(result.length).toBe(0)
157
+ })
158
+ })
159
+
160
+ describe('jsdocDocsFor, end to end with the real jsdoc CLI and clean-jsdoc-theme', () => {
161
+ test('produces a real browsable HTML page from real JSDoc comments', async () => {
162
+ gulpConfig.set('docs.enabled', true)
163
+ gulpConfig.set('docs.title', 'Tom & Jerry')
164
+ const outPath = `${gulpConfig.get('srcPath')}/../docs`
165
+ await jsdocDocsFor(gulpConfig.get('srcPath'), outPath)()
166
+ // clean-jsdoc-theme nests its real output under its own route folders (global/, source/...), not flat at
167
+ // outPath's own root - confirmed by actually running it, not assumed from the classic jsdoc template shape.
168
+ const files = fs.readdirSync(outPath, { recursive: true })
169
+ expect(files.some(file => file.endsWith('.html'))).toBe(true)
170
+ expect(fs.readFileSync(`${outPath}/index.html`, 'utf8')).toMatch(/<title>[^<]*Tom &amp; Jerry\s*<\/title>/)
171
+ }, 30000)
172
+
173
+ test('starts every link at docs.basePath, for documentation served below the site root', async () => {
174
+ gulpConfig.set('docs.enabled', true)
175
+ gulpConfig.set('docs.basePath', '/projects/tools/docs/')
176
+ const outPath = `${gulpConfig.get('srcPath')}/../docs`
177
+ await jsdocDocsFor(gulpConfig.get('srcPath'), outPath)()
178
+ const links = [...fs.readFileSync(`${outPath}/index.html`, 'utf8').matchAll(/(?:href|src)="(\/[^"]*)"/g)].map(match => match[1])
179
+ expect(links.length).toBeGreaterThan(0)
180
+ expect(links.filter(link => !link.startsWith('/projects/tools/docs'))).toEqual([])
181
+ }, 30000)
182
+ })
@@ -1,41 +1,41 @@
1
- import * as gulpConfig from '../../gulp.config.mjs'
2
- import { dest, src } from 'gulp'
3
- import { neostandardLint } from './neostandardLint.mjs'
4
- import { Transform } from 'node:stream'
5
-
6
- /**
7
- * Lint the project's source (srcPath: .js, .mjs and .cjs, plus .ts when typescript.enabled) with neostandard and
8
- * write the automatic fixes back in place. Shared by the sourceLint (build) and lint (standalone) tasks.
9
- * @memberOf module:partials
10
- * @param {Object} [options={}]
11
- * @param {boolean} [options.failOnError=false] - Fail once linting finishes if any errors could not be fixed.
12
- * @returns {stream.Stream}
13
- */
14
- export const lintSource = ({ failOnError = false } = {}) => {
15
- const srcPath = gulpConfig.get('srcPath')
16
- const ts = gulpConfig.get('typescript.enabled')
17
- const patterns = [`${srcPath}/**/*.{js,mjs,cjs}`]
18
- if (ts) {
19
- patterns.push(`${srcPath}/**/*.ts`)
20
- }
21
- const linter = neostandardLint({ fix: true, ts, rules: gulpConfig.get('lint.rules') })
22
- // Fail only once dest() has written every fixed file: gulp exits as soon as a task fails, so failing from the
23
- // linter itself (before dest() finished) could leave a file that dest() had opened, but not yet written, empty.
24
- // dest() passes each file on only after writing it, so this last step's flush runs after the final write. Files
25
- // are consumed here rather than passed on, so nothing downstream has to read them.
26
- const written = new Transform({
27
- objectMode: true,
28
- transform: (file, enc, callback) => callback(),
29
- flush: callback => failOnError && linter.totals.errors
30
- ? callback(new Error(`neostandard found ${linter.totals.errors} lint error(s) that could not be fixed automatically.`))
31
- : callback()
32
- })
33
- const saved = src(patterns)
34
- .pipe(linter)
35
- .pipe(dest(srcPath))
36
- saved.pipe(written)
37
- // pipe() does not forward errors, and gulp only watches the returned stream, so pass any failure along.
38
- linter.on('error', error => written.destroy(error))
39
- saved.on('error', error => written.destroy(error))
40
- return written
41
- }
1
+ import * as gulpConfig from '../../gulp.config.mjs'
2
+ import { dest, src } from 'gulp'
3
+ import { neostandardLint } from './neostandardLint.mjs'
4
+ import { Transform } from 'node:stream'
5
+
6
+ /**
7
+ * Lint the project's source (srcPath: .js, .mjs and .cjs, plus .ts when typescript.enabled) with neostandard and
8
+ * write the automatic fixes back in place. Shared by the sourceLint (build) and lint (standalone) tasks.
9
+ * @memberOf module:partials
10
+ * @param {Object} [options={}]
11
+ * @param {boolean} [options.failOnError=false] - Fail once linting finishes if any errors could not be fixed.
12
+ * @returns {stream.Stream}
13
+ */
14
+ export const lintSource = ({ failOnError = false } = {}) => {
15
+ const srcPath = gulpConfig.get('srcPath')
16
+ const ts = gulpConfig.get('typescript.enabled')
17
+ const patterns = [`${srcPath}/**/*.{js,mjs,cjs}`]
18
+ if (ts) {
19
+ patterns.push(`${srcPath}/**/*.ts`)
20
+ }
21
+ const linter = neostandardLint({ fix: true, ts, rules: gulpConfig.get('lint.rules') })
22
+ // Fail only once dest() has written every fixed file: gulp exits as soon as a task fails, so failing from the
23
+ // linter itself (before dest() finished) could leave a file that dest() had opened, but not yet written, empty.
24
+ // dest() passes each file on only after writing it, so this last step's flush runs after the final write. Files
25
+ // are consumed here rather than passed on, so nothing downstream has to read them.
26
+ const written = new Transform({
27
+ objectMode: true,
28
+ transform: (file, enc, callback) => callback(),
29
+ flush: callback => failOnError && linter.totals.errors
30
+ ? callback(new Error(`neostandard found ${linter.totals.errors} lint error(s) that could not be fixed automatically.`))
31
+ : callback()
32
+ })
33
+ const saved = src(patterns)
34
+ .pipe(linter)
35
+ .pipe(dest(srcPath))
36
+ saved.pipe(written)
37
+ // pipe() does not forward errors, and gulp only watches the returned stream, so pass any failure along.
38
+ linter.on('error', error => written.destroy(error))
39
+ saved.on('error', error => written.destroy(error))
40
+ return written
41
+ }
@@ -1,76 +1,76 @@
1
- import { createRequire } from 'node:module'
2
- import path from 'node:path'
3
- import { pathToFileURL } from 'node:url'
4
-
5
- // Several dependencies ship ESM-only (standard, gulp-ts-compile, typedoc), but this package also publishes a CommonJS
6
- // build (dist/*.js) whose babel transform would turn even a dynamic import() into a require() that cannot load an ES
7
- // module. Hiding the import() inside a Function constructor keeps it a genuine dynamic import that works from either
8
- // build, because babel cannot see (and so cannot rewrite) an import expression which only exists as a runtime string.
9
- // eslint-disable-next-line no-new-func -- deliberate, see above
10
- const dynamicImport = new Function('specifier', 'return import(specifier)')
11
-
12
- /**
13
- * Import an ES module (or any module) with a real dynamic import(), whichever build (ESM or CommonJS) this runs from.
14
- * @memberOf module:partials
15
- * @param {string} specifier - The package name, or the URL of a file.
16
- * @returns {Promise<Object>} The module namespace.
17
- */
18
- export const importModule = specifier => dynamicImport(specifier)
19
-
20
- const isMissing = (error, name) => error.code === 'MODULE_NOT_FOUND' && error.message.includes(`'${name}'`)
21
-
22
- const notInstalled = (name, { task, installHint }) => new Error(
23
- `The ${task} task needs the optional peer dependency ${name}, which is not installed. ${installHint}`
24
- )
25
-
26
- /**
27
- * Load an optional peer dependency (one which is only needed once a task runs, so projects which never use that task
28
- * do not have to install it) with require(), from the project being built rather than from this package. When it is
29
- * missing the error explains how to install it.
30
- * @memberOf module:partials
31
- * @param {string} name - The package name.
32
- * @param {Object} options
33
- * @param {string} options.task - The task which needs the package, for the error message.
34
- * @param {string} options.installHint - How to install it, for the error message.
35
- * @param {string} [options.esmOnlyMessage] - The error message when the package turns out to be ESM-only (it cannot be require()d).
36
- * @param {string} [options.projectPath=process.cwd()] - The folder of the project (which has the package.json) being built.
37
- * @returns {*} Whatever the package exports.
38
- * @throws {Error} With install instructions when the package is not installed.
39
- */
40
- export const requirePeer = (name, { task, installHint, esmOnlyMessage, projectPath = process.cwd() }) => {
41
- try {
42
- return createRequire(path.join(projectPath, 'package.json'))(name)
43
- } catch (error) {
44
- if (isMissing(error, name)) {
45
- throw notInstalled(name, { task, installHint })
46
- }
47
- if (error.code === 'ERR_REQUIRE_ESM' && esmOnlyMessage) {
48
- throw new Error(esmOnlyMessage)
49
- }
50
- throw error
51
- }
52
- }
53
-
54
- /**
55
- * Import an optional peer dependency which ships ESM-only, from the project being built. See {@link requirePeer}.
56
- * @memberOf module:partials
57
- * @param {string} name - The package name.
58
- * @param {Object} options
59
- * @param {string} options.task - The task which needs the package, for the error message.
60
- * @param {string} options.installHint - How to install it, for the error message.
61
- * @param {string} [options.projectPath=process.cwd()] - The folder of the project (which has the package.json) being built.
62
- * @returns {Promise<Object>} The module namespace.
63
- * @throws {Error} With install instructions when the package is not installed.
64
- */
65
- export const importPeer = async (name, { task, installHint, projectPath = process.cwd() }) => {
66
- let resolved
67
- try {
68
- resolved = createRequire(path.join(projectPath, 'package.json')).resolve(name)
69
- } catch (error) {
70
- if (isMissing(error, name)) {
71
- throw notInstalled(name, { task, installHint })
72
- }
73
- throw error
74
- }
75
- return importModule(pathToFileURL(resolved).href)
76
- }
1
+ import { createRequire } from 'node:module'
2
+ import path from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+
5
+ // Several dependencies ship ESM-only (standard, gulp-ts-compile, typedoc), but this package also publishes a CommonJS
6
+ // build (dist/*.js) whose babel transform would turn even a dynamic import() into a require() that cannot load an ES
7
+ // module. Hiding the import() inside a Function constructor keeps it a genuine dynamic import that works from either
8
+ // build, because babel cannot see (and so cannot rewrite) an import expression which only exists as a runtime string.
9
+ // eslint-disable-next-line no-new-func -- deliberate, see above
10
+ const dynamicImport = new Function('specifier', 'return import(specifier)')
11
+
12
+ /**
13
+ * Import an ES module (or any module) with a real dynamic import(), whichever build (ESM or CommonJS) this runs from.
14
+ * @memberOf module:partials
15
+ * @param {string} specifier - The package name, or the URL of a file.
16
+ * @returns {Promise<Object>} The module namespace.
17
+ */
18
+ export const importModule = specifier => dynamicImport(specifier)
19
+
20
+ const isMissing = (error, name) => error.code === 'MODULE_NOT_FOUND' && error.message.includes(`'${name}'`)
21
+
22
+ const notInstalled = (name, { task, installHint }) => new Error(
23
+ `The ${task} task needs the optional peer dependency ${name}, which is not installed. ${installHint}`
24
+ )
25
+
26
+ /**
27
+ * Load an optional peer dependency (one which is only needed once a task runs, so projects which never use that task
28
+ * do not have to install it) with require(), from the project being built rather than from this package. When it is
29
+ * missing the error explains how to install it.
30
+ * @memberOf module:partials
31
+ * @param {string} name - The package name.
32
+ * @param {Object} options
33
+ * @param {string} options.task - The task which needs the package, for the error message.
34
+ * @param {string} options.installHint - How to install it, for the error message.
35
+ * @param {string} [options.esmOnlyMessage] - The error message when the package turns out to be ESM-only (it cannot be require()d).
36
+ * @param {string} [options.projectPath=process.cwd()] - The folder of the project (which has the package.json) being built.
37
+ * @returns {*} Whatever the package exports.
38
+ * @throws {Error} With install instructions when the package is not installed.
39
+ */
40
+ export const requirePeer = (name, { task, installHint, esmOnlyMessage, projectPath = process.cwd() }) => {
41
+ try {
42
+ return createRequire(path.join(projectPath, 'package.json'))(name)
43
+ } catch (error) {
44
+ if (isMissing(error, name)) {
45
+ throw notInstalled(name, { task, installHint })
46
+ }
47
+ if (error.code === 'ERR_REQUIRE_ESM' && esmOnlyMessage) {
48
+ throw new Error(esmOnlyMessage)
49
+ }
50
+ throw error
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Import an optional peer dependency which ships ESM-only, from the project being built. See {@link requirePeer}.
56
+ * @memberOf module:partials
57
+ * @param {string} name - The package name.
58
+ * @param {Object} options
59
+ * @param {string} options.task - The task which needs the package, for the error message.
60
+ * @param {string} options.installHint - How to install it, for the error message.
61
+ * @param {string} [options.projectPath=process.cwd()] - The folder of the project (which has the package.json) being built.
62
+ * @returns {Promise<Object>} The module namespace.
63
+ * @throws {Error} With install instructions when the package is not installed.
64
+ */
65
+ export const importPeer = async (name, { task, installHint, projectPath = process.cwd() }) => {
66
+ let resolved
67
+ try {
68
+ resolved = createRequire(path.join(projectPath, 'package.json')).resolve(name)
69
+ } catch (error) {
70
+ if (isMissing(error, name)) {
71
+ throw notInstalled(name, { task, installHint })
72
+ }
73
+ throw error
74
+ }
75
+ return importModule(pathToFileURL(resolved).href)
76
+ }