Skip to content

Commit

Permalink
feat: Re-Add the docs command (#1107)
Browse files Browse the repository at this point in the history
Re-introducing the `docs` command to generate Docs for a project and publish them to [gh pages](https://docs.github.com/en/pages)

Closes #1105

Co-authored-by: achingbrain <[email protected]>
  • Loading branch information
maschad and achingbrain authored Dec 6, 2022
1 parent 39638eb commit 830e826
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 1 deletion.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
"build": "node src/index.js build --no-bundle",
"lint": "node src/index.js lint",
"test": "node src/index.js test",
"docs": "node src/index.js docs",
"test:node": "node src/index.js test -t node",
"test:chrome": "node src/index.js test -t browser",
"test:chrome-webworker": "node src/index.js test -t webworker",
Expand Down Expand Up @@ -243,6 +244,7 @@
"execa": "^6.1.0",
"extract-zip": "^2.0.1",
"fs-extra": "^11.1.0",
"gh-pages": "^4.0.0",
"globby": "^13.1.1",
"it-glob": "^1.0.1",
"kleur": "^4.1.4",
Expand Down Expand Up @@ -281,6 +283,8 @@
"strip-bom": "^5.0.0",
"strip-json-comments": "^5.0.0",
"tempy": "^2.0.0",
"typedoc": "^0.23.21",
"typedoc-theme-hierarchy": "^3.0.0",
"typescript": "^4.6.3",
"uint8arrays": "^4.0.2",
"undici": "^5.0.0",
Expand Down
47 changes: 47 additions & 0 deletions src/cmds/docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { loadUserConfig } from '../config/user.js'
import docsCmd from '../docs.js'

/**
* @typedef {import("yargs").Argv} Argv
* @typedef {import("yargs").ArgumentsCamelCase} Arguments
* @typedef {import("yargs").CommandModule} CommandModule
*/
const EPILOG = `
Typescript config file is required to generated docs. Please create a \`tsconfig.json\` file in the root of your project.
`
/** @type {CommandModule} */
export default {
command: 'docs',
describe: 'Generate documentation from TS type declarations.',
/**
* @param {Argv} yargs
*/
builder: async (yargs) => {
const userConfig = await loadUserConfig()

return yargs
.epilog(EPILOG)
.example('aegir docs', 'Build HTML documentation.')
.example('aegir docs -p', 'Build HTML documentation and publish to Github Pages.')
.options({
publish: {
alias: 'p',
type: 'boolean',
describe: 'Publish to GitHub Pages',
default: userConfig.docs.publish
},
entryPoint: {
type: 'string',
describe:
'Specifies the entry points to be documented by TypeDoc. TypeDoc will examine the exports of these files and create documentation according to the exports. Either files or directories may be specified. If a directory is specified, all source files within the directory will be included as an entry point.',
default: userConfig.docs.entryPoint
}
})
},
/**
* @param {any} argv
*/
async handler (argv) {
await docsCmd.run(argv)
}
}
3 changes: 2 additions & 1 deletion src/config/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { lilconfig } from 'lilconfig'
import merge from 'merge-options'
import { pathToFileURL } from 'url'
import { isTypescript } from '../utils.js'

/**
* @typedef {import("./../types").Options} Options
Expand Down Expand Up @@ -61,7 +62,7 @@ const defaults = {
// docs cmd options
docs: {
publish: false,
entryPoint: 'src/index.js'
entryPoint: isTypescript ? 'src/index.ts' : 'src/index.js'
},
// ts cmd options
ts: {
Expand Down
166 changes: 166 additions & 0 deletions src/docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { hasTsconfig, fromAegir, fromRoot, readJson } from './utils.js'
import Listr from 'listr'
import kleur from 'kleur'
import fs from 'fs-extra'
import path from 'path'
import { execa } from 'execa'
import { promisify } from 'util'
import ghPages from 'gh-pages'
import { premove as del } from 'premove/sync'
import { fileURLToPath } from 'url'

const publishPages = promisify(ghPages.publish)

const __dirname = path.dirname(fileURLToPath(import.meta.url))

/**
* @typedef {import("./types").GlobalOptions} GlobalOptions
* @typedef {import("./types").DocsOptions} DocsOptions
* @typedef {import("listr").ListrTaskWrapper} Task
*
* @typedef {object} Options
* @property {string} entryPoint - Entry point for typedoc (defaults: 'src/index.js')
* @property {string[]} forwardOptions - Extra options to forward to the backend
*/

/**
* Docs command
*
* @param {GlobalOptions & DocsOptions} ctx
* @param {Task} task
*/
const docs = async (ctx, task) => {
const userTSConfig = readJson(fromRoot('tsconfig.json'))
const configPath = fromRoot('tsconfig-docs.aegir.json')
const exportsMap = readJson(fromRoot('package.json')).exports

try {
const config = {
...userTSConfig
}

if (config.compilerOptions) {
// remove config options that cause tsdoc to fail
delete config.compilerOptions.emitDeclarationOnly
}

fs.writeJsonSync(configPath, config, {
spaces: 2
})

/** @type {Options} */
const opts = {
forwardOptions: ctx['--'] ? ctx['--'] : [],
entryPoint: ctx.entryPoint
}

if (!hasTsconfig) {
// eslint-disable-next-line no-console
console.error(
kleur.yellow('Documentation requires typescript config.')
)
return
}

/** @type {string[]} */
const entryPoints = []

if (exportsMap != null) {
Object.values(exportsMap).forEach(map => {
const path = map.import

if (path == null) {
return
}

if (path.includes('./dist/src')) {
// transform `./dist/src/index.js` to `./src/index.ts`
entryPoints.push(`.${path.match(/\.\/dist(\/src\/.*).js/)[1]}.ts`)
} else {
entryPoints.push(path)
}
})
} else {
entryPoints.push(opts.entryPoint)
}

// run typedoc
const proc = execa(
'typedoc',
[
...entryPoints,
'--tsconfig',
configPath,
'--out',
'docs',
'--hideGenerator',
'--includeVersion',
'--gitRevision',
'master',
'--plugin',
'typedoc-theme-hierarchy',
'--theme',
'hierarchy',
'--plugin',
fromAegir('src/docs/typedoc-plugin.cjs'),
...opts.forwardOptions
],
{
localDir: path.join(__dirname, '..'),
preferLocal: true,
all: true
}
)
proc.all?.on('data', (chunk) => {
task.output = chunk.toString().replace('\n', '')
})
await proc

// write .nojekyll file
fs.writeFileSync('docs/.nojekyll', '')
} finally {
fs.removeSync(configPath)
}
}

const publishDocs = () => {
return publishPages(
'docs',
// @ts-ignore - promisify returns wrong type
{
dotfiles: true,
message: 'chore: update documentation'
}
)
}

const tasks = new Listr(
[
{
title: 'Clean ./docs',
task: () => {
del('docs')
del('dist')
}
},
{
title: 'Generating documentation',
/**
*
* @param {GlobalOptions & DocsOptions} ctx
* @param {Task} task
*/
task: docs
},
{
title: 'Publish to GitHub Pages',
task: publishDocs,
enabled: (ctx) => ctx.publish && hasTsconfig
}
],
{
renderer: 'verbose'
}
)

export default tasks
42 changes: 42 additions & 0 deletions src/docs/typedoc-plugin.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { Converter } = require('typedoc')
const path = require('path')
const fs = require('fs')

/**
* @param {import("typedoc/dist/lib/application").Application} Application
*/
const plugin = function (Application) {
const app = Application.owner
const pkg = path.join(process.cwd(), 'package.json')
/** @type {any} */
let pkgJson

try {
pkgJson = JSON.parse(fs.readFileSync(pkg).toString())
} catch (err) {
throw new Error('cant find package.json')
}

/**
* @param {import("typedoc/dist/lib/converter/context").Context} context
* @param {import("typedoc/dist/lib/models/reflections/abstract").Reflection} reflection
* @param {import("typescript").Node} node
*/
const cb = (context, reflection, node) => {

if (pkgJson && reflection.name === 'export=') {
let name
if (node) {
// @ts-ignore
name = node.symbol.escapedName
}
reflection.name = `${name || 'default'}`
}
}

app.converter.on(Converter.EVENT_CREATE_DECLARATION, cb)
}

module.exports = {
load: plugin
}
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import lintCmd from './cmds/lint.js'
import releaseCmd from './cmds/release.js'
import testDependantCmd from './cmds/test-dependant.js'
import testCmd from './cmds/test.js'
import docsCmd from './cmds/docs.js'

/**
* @typedef {import('./types').BuildOptions} BuildOptions
Expand Down Expand Up @@ -82,6 +83,7 @@ async function main () {
res.command(checkCmd)
res.command(cleanCmd)
res.command(dependencyCheckCmd)
res.command(docsCmd)
res.command(lintPackageJsonCmd)
res.command(lintCmd)
res.command(releaseCmd)
Expand Down

0 comments on commit 830e826

Please sign in to comment.