js-build-tools 5.2.0 → 5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -10
- package/dist/build.js +63 -58
- package/dist/build.min.js +1 -1
- package/dist/bundle.js +27 -27
- package/dist/bundleLint.js +16 -16
- package/dist/bundleMinify.js +13 -13
- package/dist/compileReadme.js +10 -10
- package/dist/copyFonts.js +13 -13
- package/dist/default.js +15 -15
- package/dist/dist.js +10 -10
- package/dist/distLint.js +16 -16
- package/dist/distMinify.js +13 -13
- package/dist/images.js +10 -10
- package/dist/partials/addToReadme.js +17 -17
- package/dist/partials/beginWatcher.js +12 -12
- package/dist/partials/clean.js +14 -14
- package/dist/partials/copyFor.js +12 -12
- package/dist/partials/distFor.js +21 -21
- package/dist/partials/distForSrc.js +11 -11
- package/dist/partials/distSeries.js +19 -19
- package/dist/partials/imagesFor.js +7 -13
- package/dist/partials/loadPeer.js +99 -0
- package/dist/partials/minifyFor.js +16 -16
- package/dist/partials/readmeTemplate.js +14 -14
- package/dist/partials/removeDirectory.js +29 -29
- package/dist/partials/runOnChange.js +28 -28
- package/dist/partials/sassFor.js +28 -28
- package/dist/partials/standardLint.js +3 -5
- package/dist/partials/tsFor.js +8 -20
- package/dist/partials/typeDocsFor.js +228 -0
- package/dist/partials.js +26 -0
- package/dist/partials.min.js +1 -1
- package/dist/sass.js +10 -10
- package/dist/test-helpers/setUp.js +89 -89
- package/dist/test-helpers/standardLintWithRealStandard.js +27 -27
- package/dist/testFull.js +20 -20
- package/dist/testHelpers.js +10 -10
- package/dist/testQuick.js +22 -22
- package/dist/typeDocs.js +14 -0
- package/dist/typeDocs.min.js +1 -0
- package/dist/typeScript.js +9 -9
- package/dist/utilities/dotGet.js +46 -46
- package/dist/utilities/dotNotate.js +46 -46
- package/dist/utilities/dotSet.js +37 -37
- package/dist/utilities/isObject.js +8 -8
- package/dist/utilities/strAfter.js +11 -11
- package/dist/utilities/strBefore.js +11 -11
- package/dist/watchFull.js +10 -10
- package/dist/watchTest.js +16 -16
- package/functions/build.mjs +7 -2
- package/functions/build.test.mjs +29 -0
- package/functions/partials/imagesFor.mjs +7 -13
- package/functions/partials/loadPeer.mjs +75 -0
- package/functions/partials/loadPeer.test.mjs +58 -0
- package/functions/partials/standardLint.mjs +3 -5
- package/functions/partials/tsFor.mjs +4 -20
- package/functions/partials/typeDocsFor.mjs +199 -0
- package/functions/partials/typeDocsFor.test.mjs +204 -0
- package/functions/partials.mjs +2 -0
- package/functions/partials.test.mjs +1 -1
- package/functions/typeDocs.mjs +9 -0
- package/functions/typeDocs.test.mjs +11 -0
- package/gulp.config.mjs +24 -2
- package/gulpfile.base.js +2 -0
- package/gulpfile.base.mjs +1 -0
- package/package.json +12 -7
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import * as gulpConfig from '../../gulp.config.mjs'
|
|
5
|
+
import { importPeer } from './loadPeer.mjs'
|
|
6
|
+
import { loadTypescript } from './tsFor.mjs'
|
|
7
|
+
import { removeDirectory } from './removeDirectory.mjs'
|
|
8
|
+
|
|
9
|
+
const installHint = 'Install it in your project with: npm install --save-dev typedoc'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Find the TypeDoc of the project being built (an optional peer dependency, only needed when the docs task is
|
|
13
|
+
* enabled) and load it, so projects which do not document TypeScript never need it installed.
|
|
14
|
+
* @memberOf module:partials
|
|
15
|
+
* @param {string} [projectPath=process.cwd()] The folder of the project (which has the package.json) being built.
|
|
16
|
+
* @returns {Promise<Object>} The TypeDoc module.
|
|
17
|
+
* @throws {Error} With install instructions when typedoc is not installed.
|
|
18
|
+
*/
|
|
19
|
+
export const loadTypeDoc = (projectPath = process.cwd()) => importPeer('typedoc', { task: 'docs', installHint, projectPath })
|
|
20
|
+
|
|
21
|
+
const isSource = fileName => /\.(ts|tsx|mts|cts)$/.test(fileName) && !/\.d\.[cm]?ts$/.test(fileName) && !/\.(test|spec)\./.test(fileName)
|
|
22
|
+
|
|
23
|
+
const listSources = dirPath => fs.readdirSync(dirPath, { withFileTypes: true }).flatMap(entry => {
|
|
24
|
+
const entryPath = path.join(dirPath, entry.name)
|
|
25
|
+
if (entry.isDirectory()) {
|
|
26
|
+
return entry.name === 'node_modules' ? [] : listSources(entryPath)
|
|
27
|
+
}
|
|
28
|
+
return isSource(entry.name) ? [entryPath] : []
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const toIdentifier = fileName => fileName.replace(/[^\w$]+(\w)?/g, (match, letter) => (letter || '').toUpperCase()).replace(/^(\d)/, '_$1')
|
|
32
|
+
|
|
33
|
+
const toModulePath = filePath => filePath.replace(/\.[cm]?tsx?$/, '').split(path.sep).join('/')
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Describe what a source file exports: whether it has a default export, and whether it has any named exports.
|
|
37
|
+
* @memberOf module:partials
|
|
38
|
+
* @param {Object} ts - The TypeScript compiler API.
|
|
39
|
+
* @param {string} filePath
|
|
40
|
+
* @returns {{hasDefault: boolean, hasNamed: boolean}}
|
|
41
|
+
*/
|
|
42
|
+
export const describeExports = (ts, filePath) => {
|
|
43
|
+
const source = ts.createSourceFile(filePath, fs.readFileSync(filePath, 'utf8'), ts.ScriptTarget.Latest)
|
|
44
|
+
let hasDefault = false
|
|
45
|
+
let hasNamed = false
|
|
46
|
+
source.statements.forEach(statement => {
|
|
47
|
+
if (ts.isExportAssignment(statement)) {
|
|
48
|
+
hasDefault = hasDefault || !statement.isExportEquals
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
if (ts.isExportDeclaration(statement)) {
|
|
52
|
+
const names = statement.exportClause && ts.isNamedExports(statement.exportClause) ? statement.exportClause.elements : null
|
|
53
|
+
if (names && names.some(element => element.name.text === 'default')) {
|
|
54
|
+
hasDefault = true
|
|
55
|
+
}
|
|
56
|
+
if (!names || names.some(element => element.name.text !== 'default')) {
|
|
57
|
+
hasNamed = true
|
|
58
|
+
}
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) || [] : []
|
|
62
|
+
if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
63
|
+
if (modifiers.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)) {
|
|
64
|
+
hasDefault = true
|
|
65
|
+
} else {
|
|
66
|
+
hasNamed = true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
return { hasDefault, hasNamed }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The description of a folder for its module: the first doc comment of the folder's index file, without its tags
|
|
75
|
+
* (@module, @file, ...), or an empty string when there is none.
|
|
76
|
+
* @memberOf module:partials
|
|
77
|
+
* @param {string} folderPath
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export const describeFolder = folderPath => {
|
|
81
|
+
const indexFile = ['index.ts', 'index.tsx', 'index.mts', 'index.cts'].map(name => path.join(folderPath, name)).find(file => fs.existsSync(file))
|
|
82
|
+
const comment = indexFile && fs.readFileSync(indexFile, 'utf8').match(/^\s*\/\*\*([\s\S]*?)\*\//)
|
|
83
|
+
if (!comment) {
|
|
84
|
+
return ''
|
|
85
|
+
}
|
|
86
|
+
const lines = comment[1].split('\n').map(line => line.replace(/^\s*\* ?/, '').trimEnd())
|
|
87
|
+
const tagAt = lines.findIndex(line => /^@\w+/.test(line))
|
|
88
|
+
return (tagAt === -1 ? lines : lines.slice(0, tagAt)).join('\n').trim()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Write one entry file per folder of the source directory into the entry directory. TypeDoc makes a module of every
|
|
93
|
+
* entry file, so the modules of the documentation mirror the folders. The default export of each source file is
|
|
94
|
+
* re-exported under the name of the file (the source files hold one function each), and anything else a file exports
|
|
95
|
+
* is re-exported as it is (that is where the types.ts files go). Index files (the barrels), tests and declaration
|
|
96
|
+
* files are left out, but the first doc comment of a folder's index file becomes the description of its module. A source directory with no folders gets a single entry called index.
|
|
97
|
+
* @memberOf module:partials
|
|
98
|
+
* @param {Object} ts - The TypeScript compiler API.
|
|
99
|
+
* @param {string} srcDir - The directory holding the TypeScript source.
|
|
100
|
+
* @param {string} entryDir - The directory to write the entry files into.
|
|
101
|
+
* @returns {Array<string>} The paths of the entry files.
|
|
102
|
+
*/
|
|
103
|
+
export const writeDocEntries = (ts, srcDir, entryDir) => {
|
|
104
|
+
const absoluteSrc = path.resolve(srcDir)
|
|
105
|
+
fs.mkdirSync(entryDir, { recursive: true })
|
|
106
|
+
const folders = fs.readdirSync(absoluteSrc, { withFileTypes: true })
|
|
107
|
+
.filter(entry => entry.isDirectory() && entry.name !== 'node_modules')
|
|
108
|
+
.map(entry => ({ name: entry.name, path: path.join(absoluteSrc, entry.name), files: listSources(path.join(absoluteSrc, entry.name)) }))
|
|
109
|
+
.filter(folder => folder.files.length)
|
|
110
|
+
const groups = folders.length
|
|
111
|
+
? folders
|
|
112
|
+
: [{ name: 'index', path: absoluteSrc, files: listSources(absoluteSrc).filter(file => !/^(index|main)\./.test(path.basename(file))) }]
|
|
113
|
+
return groups.map(group => {
|
|
114
|
+
const lines = group.files
|
|
115
|
+
.filter(file => !/^index\./.test(path.basename(file)))
|
|
116
|
+
.sort()
|
|
117
|
+
.flatMap(file => {
|
|
118
|
+
const modulePath = toModulePath(file)
|
|
119
|
+
const { hasDefault, hasNamed } = describeExports(ts, file)
|
|
120
|
+
const fileName = path.basename(file).replace(/\.[cm]?tsx?$/, '')
|
|
121
|
+
return [
|
|
122
|
+
...(hasDefault ? [`export { default as ${toIdentifier(fileName)} } from '${modulePath}'`] : []),
|
|
123
|
+
...(hasNamed ? [`export * from '${modulePath}'`] : [])
|
|
124
|
+
]
|
|
125
|
+
})
|
|
126
|
+
const description = group.path ? describeFolder(group.path) : ''
|
|
127
|
+
// TypeDoc takes the first comment of an entry file which has the @module tag as the description of the module
|
|
128
|
+
const moduleComment = description ? `/**\n${description.split('\n').map(line => ` * ${line}`.trimEnd()).join('\n')}\n * @module\n */\n\n` : ''
|
|
129
|
+
const entryPath = path.join(entryDir, `${group.name}.ts`)
|
|
130
|
+
fs.writeFileSync(entryPath, moduleComment + lines.join('\n') + '\n')
|
|
131
|
+
return entryPath
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const projectName = () => {
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')).name
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return path.basename(process.cwd())
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate the HTML documentation of a TypeScript project with TypeDoc, straight from the TypeScript source, so the
|
|
145
|
+
* types (and the comments on them) are documented from where they are defined. The modules mirror the folders of the
|
|
146
|
+
* source, see {@link writeDocEntries}. Configure this with 'docs.from', 'docs.to', 'docs.index', 'docs.title',
|
|
147
|
+
* 'docs.tsconfig' and 'docs.logLevel'.
|
|
148
|
+
* @memberOf module:partials
|
|
149
|
+
* @param {string} [srcPath=gulpConfig.get('docs.from')] - The directory holding the TypeScript source.
|
|
150
|
+
* @param {string} [outPath=gulpConfig.get('docs.to')] - The directory the documentation is generated into (cleared first).
|
|
151
|
+
* @param {function(string): Promise<Object>} [loadDocs] - Resolves the TypeDoc module, tests can inject a stand-in.
|
|
152
|
+
* @returns {Function} A gulp task, it accepts an optional callback and returns a promise.
|
|
153
|
+
*/
|
|
154
|
+
export const typeDocsFor = (
|
|
155
|
+
srcPath = gulpConfig.get('docs.from'),
|
|
156
|
+
outPath = gulpConfig.get('docs.to'),
|
|
157
|
+
loadDocs = loadTypeDoc
|
|
158
|
+
) => {
|
|
159
|
+
if (gulpConfig.get('docs.enabled') === false) {
|
|
160
|
+
return () => {}
|
|
161
|
+
}
|
|
162
|
+
return (done) => {
|
|
163
|
+
const generate = async () => {
|
|
164
|
+
const ts = loadTypescript()
|
|
165
|
+
const { Application } = await loadDocs()
|
|
166
|
+
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'typedocs-'))
|
|
167
|
+
try {
|
|
168
|
+
const entryPoints = writeDocEntries(ts, srcPath, path.join(workDir, 'entries'))
|
|
169
|
+
// The entry files live outside the project, so the tsconfig which is used for the documentation extends the
|
|
170
|
+
// one of the project and adds the entries to the files it includes.
|
|
171
|
+
const projectConfig = gulpConfig.get('docs.tsconfig') || gulpConfig.get('typescript.config')
|
|
172
|
+
const tsconfig = path.join(workDir, 'tsconfig.json')
|
|
173
|
+
fs.writeFileSync(tsconfig, JSON.stringify({
|
|
174
|
+
...(projectConfig ? { extends: path.resolve(projectConfig) } : {}),
|
|
175
|
+
include: [path.join(workDir, 'entries', '*.ts').split(path.sep).join('/'), path.resolve(srcPath).split(path.sep).join('/') + '/**/*.ts'],
|
|
176
|
+
exclude: [path.resolve(srcPath).split(path.sep).join('/') + '/**/*.test.*']
|
|
177
|
+
}))
|
|
178
|
+
const indexFile = gulpConfig.get('docs.index')
|
|
179
|
+
await removeDirectory(outPath)
|
|
180
|
+
const app = await Application.bootstrap({
|
|
181
|
+
entryPoints,
|
|
182
|
+
tsconfig,
|
|
183
|
+
name: gulpConfig.get('docs.title') || projectName(),
|
|
184
|
+
readme: indexFile && fs.existsSync(indexFile) ? indexFile : 'none',
|
|
185
|
+
logLevel: gulpConfig.get('docs.logLevel') || 'Warn',
|
|
186
|
+
skipErrorChecking: true
|
|
187
|
+
})
|
|
188
|
+
const project = await app.convert()
|
|
189
|
+
if (!project) {
|
|
190
|
+
throw new Error('TypeDoc could not read the TypeScript source, see the errors above.')
|
|
191
|
+
}
|
|
192
|
+
await app.generateDocs(project, outPath)
|
|
193
|
+
} finally {
|
|
194
|
+
await removeDirectory(workDir)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return typeof done === 'function' ? generate().then(() => done(), done) : generate()
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import ts from 'typescript'
|
|
4
|
+
import * as setUp from '../test-helpers/setUp.mjs'
|
|
5
|
+
import { describeExports, describeFolder, loadTypeDoc, typeDocsFor, writeDocEntries } from './typeDocsFor.mjs'
|
|
6
|
+
|
|
7
|
+
setUp.setDefaults('test-type-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}/main.ts`, "import a from './point/add'\nexport default { a }\n")
|
|
19
|
+
write(`${srcPath}/point/add.ts`, '/** Add two numbers. */\nconst add = (a: number, b: number): number => a + b\n\nexport default add\n')
|
|
20
|
+
write(`${srcPath}/point/get-name.ts`, 'export default function getName (): string { return "name" }\n')
|
|
21
|
+
write(`${srcPath}/point/types.ts`, 'export interface Point { x: number, y: number }\nexport type Axis = keyof Point\n')
|
|
22
|
+
write(`${srcPath}/point/index.ts`, "/**\n * Points of a matrix and the maths on them.\n * A second line.\n * @file Not part of the description\n * @module point\n */\nimport add from './add'\nexport default { add }\n")
|
|
23
|
+
write(`${srcPath}/point/add.test.ts`, "test('adds', () => {})\n")
|
|
24
|
+
write(`${srcPath}/point/add.d.ts`, 'export {}\n')
|
|
25
|
+
write(`${srcPath}/line/length.ts`, 'export const length = 5\nexport default length\n')
|
|
26
|
+
write(`${srcPath}/empty/README.md`, 'nothing to document\n')
|
|
27
|
+
})
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
afterEach(setUp.afterEach)
|
|
31
|
+
|
|
32
|
+
describe('describeExports', () => {
|
|
33
|
+
const inSrc = name => `${gulpConfig.get('srcPath')}/${name}`
|
|
34
|
+
|
|
35
|
+
test('finds a default export', () => {
|
|
36
|
+
expect(describeExports(ts, inSrc('point/add.ts'))).toEqual({ hasDefault: true, hasNamed: false })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('finds a default function declaration', () => {
|
|
40
|
+
expect(describeExports(ts, inSrc('point/get-name.ts'))).toEqual({ hasDefault: true, hasNamed: false })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('finds named exports (types)', () => {
|
|
44
|
+
expect(describeExports(ts, inSrc('point/types.ts'))).toEqual({ hasDefault: false, hasNamed: true })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('finds both when a file has both', () => {
|
|
48
|
+
expect(describeExports(ts, inSrc('line/length.ts'))).toEqual({ hasDefault: true, hasNamed: true })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('understands export lists and re-exports', () => {
|
|
52
|
+
write(inSrc('lists.ts'), "const a = 1\nconst b = 2\nexport { a as default, b }\nexport * from './point/types'\n")
|
|
53
|
+
expect(describeExports(ts, inSrc('lists.ts'))).toEqual({ hasDefault: true, hasNamed: true })
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('describeFolder', () => {
|
|
58
|
+
test('is the text of the first comment of the index file, without its tags', () => {
|
|
59
|
+
expect(describeFolder(`${gulpConfig.get('srcPath')}/point`)).toBe('Points of a matrix and the maths on them.\nA second line.')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('is empty without an index file, or a comment in it', () => {
|
|
63
|
+
expect(describeFolder(`${gulpConfig.get('srcPath')}/line`)).toBe('')
|
|
64
|
+
write(`${gulpConfig.get('srcPath')}/bare/index.ts`, "import a from './a'\nexport default { a }\n")
|
|
65
|
+
expect(describeFolder(`${gulpConfig.get('srcPath')}/bare`)).toBe('')
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('writeDocEntries', () => {
|
|
70
|
+
test('writes one entry file for each folder which has source', () => {
|
|
71
|
+
const entryDir = `${gulpConfig.get('srcPath')}/../entries`
|
|
72
|
+
const entries = writeDocEntries(ts, gulpConfig.get('srcPath'), entryDir)
|
|
73
|
+
expect(entries.map(entry => path.basename(entry)).sort()).toEqual(['line.ts', 'point.ts'])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('re-exports the default of each file by its name and the named exports as they are', () => {
|
|
77
|
+
const entryDir = `${gulpConfig.get('srcPath')}/../entries`
|
|
78
|
+
writeDocEntries(ts, gulpConfig.get('srcPath'), entryDir)
|
|
79
|
+
const absoluteSrc = path.resolve(gulpConfig.get('srcPath')).split(path.sep).join('/')
|
|
80
|
+
const exportLines = file => fs.readFileSync(file, 'utf8').trim().split('\n').filter(line => line.startsWith('export'))
|
|
81
|
+
expect(exportLines(`${entryDir}/point.ts`)).toEqual([
|
|
82
|
+
`export { default as add } from '${absoluteSrc}/point/add'`,
|
|
83
|
+
`export { default as getName } from '${absoluteSrc}/point/get-name'`,
|
|
84
|
+
`export * from '${absoluteSrc}/point/types'`
|
|
85
|
+
])
|
|
86
|
+
expect(exportLines(`${entryDir}/line.ts`)).toEqual([
|
|
87
|
+
`export { default as length } from '${absoluteSrc}/line/length'`,
|
|
88
|
+
`export * from '${absoluteSrc}/line/length'`
|
|
89
|
+
])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('leaves out index files, tests and declaration files', () => {
|
|
93
|
+
const entryDir = `${gulpConfig.get('srcPath')}/../entries`
|
|
94
|
+
writeDocEntries(ts, gulpConfig.get('srcPath'), entryDir)
|
|
95
|
+
const entry = fs.readFileSync(`${entryDir}/point.ts`, 'utf8')
|
|
96
|
+
expect(entry).not.toMatch(/from '.*(index|\.test|add\.d)/)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('describes the module with the first comment of the index file of the folder', () => {
|
|
100
|
+
const entryDir = `${gulpConfig.get('srcPath')}/../entries`
|
|
101
|
+
writeDocEntries(ts, gulpConfig.get('srcPath'), entryDir)
|
|
102
|
+
expect(fs.readFileSync(`${entryDir}/point.ts`, 'utf8')).toMatch(/^\/\*\*\n \* Points of a matrix and the maths on them\.\n \* A second line\.\n \* @module\n \*\/\n\nexport/)
|
|
103
|
+
// A folder without an index file has no comment
|
|
104
|
+
expect(fs.readFileSync(`${entryDir}/line.ts`, 'utf8').startsWith('export')).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('makes one entry called index when the source has no folders', () => {
|
|
108
|
+
const flat = `${gulpConfig.get('srcPath')}/../flat`
|
|
109
|
+
write(`${flat}/one.ts`, 'export default 1\n')
|
|
110
|
+
write(`${flat}/main.ts`, 'export default 2\n')
|
|
111
|
+
const entries = writeDocEntries(ts, flat, `${flat}/../flat-entries`)
|
|
112
|
+
expect(entries.map(entry => path.basename(entry))).toEqual(['index.ts'])
|
|
113
|
+
expect(fs.readFileSync(entries[0], 'utf8')).toMatch(/default as one/)
|
|
114
|
+
expect(fs.readFileSync(entries[0], 'utf8')).not.toMatch(/main/)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('typeDocsFor', () => {
|
|
119
|
+
const fakeTypeDoc = calls => () => Promise.resolve({
|
|
120
|
+
Application: {
|
|
121
|
+
bootstrap: async options => {
|
|
122
|
+
calls.options = options
|
|
123
|
+
calls.entryContents = options.entryPoints.map(entry => fs.readFileSync(entry, 'utf8'))
|
|
124
|
+
calls.tsconfig = JSON.parse(fs.readFileSync(options.tsconfig, 'utf8'))
|
|
125
|
+
return {
|
|
126
|
+
convert: async () => ({ name: 'project' }),
|
|
127
|
+
generateDocs: async (project, out) => {
|
|
128
|
+
calls.generated = { project, out }
|
|
129
|
+
fs.mkdirSync(out, { recursive: true })
|
|
130
|
+
fs.writeFileSync(`${out}/index.html`, '<html></html>')
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('generates the documentation into the output directory, clearing what was there', done => {
|
|
138
|
+
gulpConfig.set('docs.enabled', true)
|
|
139
|
+
const outPath = `${gulpConfig.get('srcPath')}/../docs`
|
|
140
|
+
write(`${outPath}/stale.html`, 'old')
|
|
141
|
+
const calls = {}
|
|
142
|
+
typeDocsFor(gulpConfig.get('srcPath'), outPath, fakeTypeDoc(calls))(() => {
|
|
143
|
+
expect(fs.existsSync(`${outPath}/stale.html`)).toBe(false)
|
|
144
|
+
expect(fs.existsSync(`${outPath}/index.html`)).toBe(true)
|
|
145
|
+
expect(calls.generated.out).toBe(outPath)
|
|
146
|
+
expect(calls.options.entryPoints.map(entry => path.basename(entry)).sort()).toEqual(['line.ts', 'point.ts'])
|
|
147
|
+
expect(calls.options.skipErrorChecking).toBe(true)
|
|
148
|
+
done()
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('uses the front page and the title from the config, and none when the front page is missing', async () => {
|
|
153
|
+
gulpConfig.set('docs.enabled', true)
|
|
154
|
+
gulpConfig.set('docs.title', 'my-title')
|
|
155
|
+
gulpConfig.set('docs.index', `${gulpConfig.get('srcPath')}/../MAIN.md`)
|
|
156
|
+
write(`${gulpConfig.get('srcPath')}/../MAIN.md`, '# hello\n')
|
|
157
|
+
const calls = {}
|
|
158
|
+
await typeDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeTypeDoc(calls))()
|
|
159
|
+
expect(calls.options.name).toBe('my-title')
|
|
160
|
+
expect(calls.options.readme).toBe(`${gulpConfig.get('srcPath')}/../MAIN.md`)
|
|
161
|
+
gulpConfig.set('docs.index', 'does-not-exist.md')
|
|
162
|
+
await typeDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeTypeDoc(calls))()
|
|
163
|
+
expect(calls.options.readme).toBe('none')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('reads the source with the tsconfig of the project, adding the entries to it', async () => {
|
|
167
|
+
gulpConfig.set('docs.enabled', true)
|
|
168
|
+
gulpConfig.set('typescript.config', 'tsconfig.json')
|
|
169
|
+
const calls = {}
|
|
170
|
+
await typeDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeTypeDoc(calls))()
|
|
171
|
+
expect(calls.tsconfig.extends).toBe(path.resolve('tsconfig.json'))
|
|
172
|
+
expect(calls.tsconfig.include).toHaveLength(2)
|
|
173
|
+
expect(calls.tsconfig.exclude[0]).toMatch(/\*\*\/\*\.test\.\*$/)
|
|
174
|
+
gulpConfig.set('docs.tsconfig', 'other.json')
|
|
175
|
+
await typeDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, fakeTypeDoc(calls))()
|
|
176
|
+
expect(calls.tsconfig.extends).toBe(path.resolve('other.json'))
|
|
177
|
+
gulpConfig.set('docs.tsconfig', false)
|
|
178
|
+
gulpConfig.set('typescript.config', false)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('fails with a clear error when TypeDoc could not read the source', async () => {
|
|
182
|
+
gulpConfig.set('docs.enabled', true)
|
|
183
|
+
const failing = () => Promise.resolve({
|
|
184
|
+
Application: { bootstrap: async () => ({ convert: async () => undefined }) }
|
|
185
|
+
})
|
|
186
|
+
await expect(typeDocsFor(gulpConfig.get('srcPath'), `${gulpConfig.get('srcPath')}/../docs`, failing)()).rejects.toThrow(/could not read the TypeScript source/)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('skips if docs.enabled is false', () => {
|
|
190
|
+
gulpConfig.set('docs.enabled', false)
|
|
191
|
+
const result = typeDocsFor()
|
|
192
|
+
expect(result).toBeInstanceOf(Function)
|
|
193
|
+
expect(result()).toBeUndefined()
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('loadTypeDoc', () => {
|
|
198
|
+
// The real import() of the ESM-only TypeDoc cannot run in this project's CommonJS-transform-based Jest setup (see
|
|
199
|
+
// tsFor.mjs), so loading it for real is checked by generating the documentation of a project, see the pull request.
|
|
200
|
+
|
|
201
|
+
test('explains how to install typedoc when the project does not have it', async () => {
|
|
202
|
+
await expect(loadTypeDoc('/')).rejects.toThrow(/optional peer dependency typedoc.*npm install --save-dev typedoc/)
|
|
203
|
+
})
|
|
204
|
+
})
|
package/functions/partials.mjs
CHANGED
|
@@ -15,6 +15,7 @@ export { distFor } from './partials/distFor.mjs'
|
|
|
15
15
|
export { distForSrc } from './partials/distForSrc.mjs'
|
|
16
16
|
export { distSeries } from './partials/distSeries.mjs'
|
|
17
17
|
export { imagesFor } from './partials/imagesFor.mjs'
|
|
18
|
+
export { importModule, importPeer, requirePeer } from './partials/loadPeer.mjs'
|
|
18
19
|
export { minifyFor } from './partials/minifyFor.mjs'
|
|
19
20
|
export { readmeTemplate } from './partials/readmeTemplate.mjs'
|
|
20
21
|
export { removeDirectory } from './partials/removeDirectory.mjs'
|
|
@@ -22,3 +23,4 @@ export { runOnChange } from './partials/runOnChange.mjs'
|
|
|
22
23
|
export { sassFor } from './partials/sassFor.mjs'
|
|
23
24
|
export { standardLint } from './partials/standardLint.mjs'
|
|
24
25
|
export { tsFor } from './partials/tsFor.mjs'
|
|
26
|
+
export { typeDocsFor } from './partials/typeDocsFor.mjs'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { typeDocsFor } from './partials/typeDocsFor.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate the HTML documentation of a TypeScript project from its source, with the modules mirroring the source
|
|
5
|
+
* folders. Turn it on with docs.enabled, see {@link module:partials.typeDocsFor}.
|
|
6
|
+
* @memberOf module:js-build-tools
|
|
7
|
+
* @returns {Function}
|
|
8
|
+
*/
|
|
9
|
+
export const typeDocs = typeDocsFor()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { typeDocs } from './typeDocs.mjs'
|
|
2
|
+
import { typeDocsFor } from './partials/typeDocsFor.mjs'
|
|
3
|
+
|
|
4
|
+
jest.mock('./partials/typeDocsFor.mjs', () => ({ typeDocsFor: jest.fn(() => () => null) }))
|
|
5
|
+
|
|
6
|
+
describe('typeDocs', () => {
|
|
7
|
+
test('is the task made by typeDocsFor with no parameters', () => {
|
|
8
|
+
expect(typeDocsFor).toHaveBeenCalledWith()
|
|
9
|
+
expect(typeDocs).toBeInstanceOf(Function)
|
|
10
|
+
})
|
|
11
|
+
})
|
package/gulp.config.mjs
CHANGED
|
@@ -49,8 +49,8 @@ import { fileExists } from 'test-filesystem'
|
|
|
49
49
|
* Configurations for building the browser files.
|
|
50
50
|
* @typedef {Object<string, Setting>} module:gulpConfig~BrowserConfig
|
|
51
51
|
* @property {BooleanSetting} enabled - 'true' to generate browser bundled files; 'false' for node environment only
|
|
52
|
-
* @property {StringSetting} from - The
|
|
53
|
-
* @property {StringSetting} name - The
|
|
52
|
+
* @property {StringSetting} from - The search pattern used for retrieving compiled distribution files.
|
|
53
|
+
* @property {StringSetting} name - The name to use for the browser-bundled output file (.js will be appended).
|
|
54
54
|
* @property {StringSetting} to - The output directory for browser-bundled files.
|
|
55
55
|
* @property {ArrayableSetting} ignore - Module names or file paths which resolve to an empty object ({}) in the
|
|
56
56
|
* bundle instead of their real contents. For a dependency which is only reached because it is the default value of
|
|
@@ -91,6 +91,18 @@ import { fileExists } from 'test-filesystem'
|
|
|
91
91
|
* @property {StringSetting} template - The file which will be pre-fixed to your README.md output.
|
|
92
92
|
* @property {StringSetting} to - The directory to output the readme file in.
|
|
93
93
|
*/
|
|
94
|
+
/**
|
|
95
|
+
* Configurations for generating the HTML documentation of a TypeScript project (with TypeDoc, from the source).
|
|
96
|
+
* @typedef {Object<string, Setting>} module:gulpConfig~DocsConfig
|
|
97
|
+
* @property {BooleanSetting} enabled - Toggle generating the documentation from the TypeScript source. When it is on the
|
|
98
|
+
* build does this instead of generating the readme from the jsdoc comments of the compiled files.
|
|
99
|
+
* @property {StringSetting} from - The directory holding the TypeScript source, each folder in it becomes a module.
|
|
100
|
+
* @property {StringSetting} index - The markdown file used as the front page of the documentation.
|
|
101
|
+
* @property {StringSetting} logLevel - How much TypeDoc reports: Verbose, Info, Warn, Error or None.
|
|
102
|
+
* @property {StringSetting} title - The name of the documentation, defaults to the name in the package.json.
|
|
103
|
+
* @property {StringSetting} to - The directory to generate the documentation in (it is cleared first).
|
|
104
|
+
* @property {FlagStringSetting} tsconfig - The tsconfig file to read the source with, defaults to the one of typescript.config.
|
|
105
|
+
*/
|
|
94
106
|
/**
|
|
95
107
|
* Configurations to compile and copy the sass files into css.
|
|
96
108
|
* @typedef {Object<string, Setting>} module:gulpConfig~SassConfig
|
|
@@ -121,6 +133,7 @@ import { fileExists } from 'test-filesystem'
|
|
|
121
133
|
* @property {BrowserConfig} browser - Browser bundling configuration group.
|
|
122
134
|
* @property {ArrayableSetting} cleanPaths - The paths for directories to delete before build.
|
|
123
135
|
* @property {DistConfig} dist - Distribution file generation configuration group.
|
|
136
|
+
* @property {DocsConfig} docs - Generate the documentation of a TypeScript project configuration group.
|
|
124
137
|
* @property {FontConfig} fonts - Fonts copy configuration group.
|
|
125
138
|
* @property {ImageConfig} images - Minify and copy the images configuration.
|
|
126
139
|
* @property {ReadmeConfig} readme - Build readme files configuration.
|
|
@@ -150,6 +163,15 @@ const setDefaults = {
|
|
|
150
163
|
main: 'dist/main',
|
|
151
164
|
to: 'dist',
|
|
152
165
|
},
|
|
166
|
+
docs: {
|
|
167
|
+
enabled: false,
|
|
168
|
+
from: 'src',
|
|
169
|
+
index: 'MAIN.md',
|
|
170
|
+
logLevel: 'Warn',
|
|
171
|
+
title: '',
|
|
172
|
+
to: 'docs',
|
|
173
|
+
tsconfig: false,
|
|
174
|
+
},
|
|
153
175
|
fonts: {
|
|
154
176
|
enabled: false,
|
|
155
177
|
from: 'src/fonts/**/*',
|
package/gulpfile.base.js
CHANGED
|
@@ -23,6 +23,7 @@ const sass = require('./dist/sass.js')
|
|
|
23
23
|
const testFull = require('./dist/testFull.js')
|
|
24
24
|
const testHelpers = require('./dist/testHelpers.js')
|
|
25
25
|
const testQuick = require('./dist/testQuick.js')
|
|
26
|
+
const typeDocs = require('./dist/typeDocs.js')
|
|
26
27
|
const typescript = require('./dist/typeScript')
|
|
27
28
|
const watchFull = require('./dist/watchFull.js')
|
|
28
29
|
const watchTest = require('./dist/watchTest.js')
|
|
@@ -34,6 +35,7 @@ module.exports = {
|
|
|
34
35
|
bundleMinify,
|
|
35
36
|
copyFonts,
|
|
36
37
|
defaultCmd,
|
|
38
|
+
docs: typeDocs,
|
|
37
39
|
dist,
|
|
38
40
|
distLint,
|
|
39
41
|
distMinify,
|
package/gulpfile.base.mjs
CHANGED
|
@@ -22,6 +22,7 @@ export { sass } from './functions/sass.mjs'
|
|
|
22
22
|
export { testFull } from './functions/testFull.mjs'
|
|
23
23
|
export * as testHelpers from './functions/testHelpers.mjs'
|
|
24
24
|
export { testQuick } from './functions/testQuick.mjs'
|
|
25
|
+
export { typeDocs as docs } from './functions/typeDocs.mjs'
|
|
25
26
|
export { typeScript as typescript } from './functions/typeScript.mjs'
|
|
26
27
|
export { watchFull } from './functions/watchFull.mjs'
|
|
27
28
|
export { watchTest } from './functions/watchTest.mjs'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js-build-tools",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.1",
|
|
4
4
|
"description": "Standardised build process with gulp.",
|
|
5
5
|
"main": "gulpfile.base.js",
|
|
6
6
|
"exports": {
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"@babel/preset-typescript": "^7.29.7",
|
|
53
53
|
"@babel/runtime-corejs3": "^8.0.6",
|
|
54
54
|
"autoprefixer": "^10.6.1",
|
|
55
|
-
"babel-jest": "^30.
|
|
55
|
+
"babel-jest": "^30.5.2",
|
|
56
56
|
"browserify": "^17.0.1",
|
|
57
|
-
"core-js": "^3.
|
|
57
|
+
"core-js": "^3.50.0",
|
|
58
58
|
"cssnano": "^6.1.2",
|
|
59
59
|
"glob": "^13.0.6",
|
|
60
60
|
"gulp": "^5.0.1",
|
|
@@ -63,27 +63,31 @@
|
|
|
63
63
|
"gulp-postcss": "^10.0.0",
|
|
64
64
|
"gulp-rename": "^2.1.0",
|
|
65
65
|
"gulp-sass": "^6.0.1",
|
|
66
|
-
"gulp-ts-compile": "^1.0.
|
|
66
|
+
"gulp-ts-compile": "^1.0.1",
|
|
67
67
|
"gulp-uglify-es": "^3.0.0",
|
|
68
68
|
"jest": "^30.5.2",
|
|
69
|
-
"jest-environment-jsdom": "^30.
|
|
69
|
+
"jest-environment-jsdom": "^30.5.2",
|
|
70
70
|
"jsdoc-to-markdown": "^9.1.3",
|
|
71
71
|
"postcss": "^8.5.28",
|
|
72
72
|
"regenerator-runtime": "^0.14.1",
|
|
73
|
-
"sass": "^1.
|
|
73
|
+
"sass": "^1.105.0",
|
|
74
74
|
"standard": "^17.1.2",
|
|
75
|
-
"test-filesystem": "^1.5.
|
|
75
|
+
"test-filesystem": "^1.5.1",
|
|
76
76
|
"through2": "^4.0.2",
|
|
77
77
|
"vinyl-source-stream": "^2.0.0"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
80
|
"gulp-imagemin": ">=9.0.0",
|
|
81
|
+
"typedoc": ">=0.28.0",
|
|
81
82
|
"typescript": ">=5.0.0 <7.0.0"
|
|
82
83
|
},
|
|
83
84
|
"peerDependenciesMeta": {
|
|
84
85
|
"gulp-imagemin": {
|
|
85
86
|
"optional": true
|
|
86
87
|
},
|
|
88
|
+
"typedoc": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
87
91
|
"typescript": {
|
|
88
92
|
"optional": true
|
|
89
93
|
}
|
|
@@ -101,6 +105,7 @@
|
|
|
101
105
|
"funding": "https://www.paypal.com/donate/?business=LRYR6SZRWKXVY&no_recurring=0&item_name=Let%27s+build+together+today+to+simplify+our+jobs+tomorrow.¤cy_code=CAD",
|
|
102
106
|
"devDependencies": {
|
|
103
107
|
"gulp-imagemin": "github:jheagle/gulp-imagemin#common-js-compatibility",
|
|
108
|
+
"typedoc": "^0.28.20",
|
|
104
109
|
"typescript": "^6.0.3"
|
|
105
110
|
}
|
|
106
111
|
}
|