diff --git a/cmds/ts.js b/cmds/ts.js new file mode 100644 index 000000000..ed168b315 --- /dev/null +++ b/cmds/ts.js @@ -0,0 +1,30 @@ +'use strict' + +const EPILOG = ` +Presets: +\`check\` Checks src and test folders for Typescript errors +\`types\` Generates types declarations inline with the source files +\`types-clean\` Deletes all the *.d.ts files +\`docs\` Generates documentation based on the type declarations + +Supports options forwarding with '--' for more info check https://www.typescriptlang.org/v2/docs/handbook/compiler-options.html +` +module.exports = { + command: 'ts', + desc: 'Typescript command with presets for specific tasks.', + builder: (yargs) => { + yargs + .epilog(EPILOG) + .options({ + preset: { + type: 'string', + choices: ['check', 'types', 'types-clean', 'docs'], + describe: 'Preset to run' + } + }) + }, + handler (argv) { + const ts = require('../src/ts') + return ts(argv) + } +} diff --git a/package.json b/package.json index a97c85ead..13f13b365 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,9 @@ "@commitlint/travis-cli": "^8.3.5", "@electron/get": "^1.10.0", "@polka/send-type": "^0.5.2", + "@types/mocha": "^7.0.2", + "@types/node": "^14.0.9", + "aegir-typedoc-theme": "~0.0.1", "babel-loader": "^8.0.5", "babel-plugin-transform-flow-comments": "^6.22.0", "buffer": "^5.6.0", @@ -76,6 +79,7 @@ "eslint": "^6.3.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jsdoc": "^30.4.2", "eslint-plugin-no-only-tests": "^2.4.0", "eslint-plugin-node": "^11.0.0", "eslint-plugin-promise": "^4.2.1", @@ -120,6 +124,8 @@ "stream-array": "^1.1.2", "terser-webpack-plugin": "^2.3.6", "transform-loader": "~0.2.4", + "typedoc": "^0.17.7", + "typescript": "^3.9.3", "update-notifier": "^4.0.0", "vinyl-fs": "^3.0.3", "webpack": "^4.43.0", @@ -139,6 +145,9 @@ "node": ">=10.0.0", "npm": ">=6.0.0" }, + "eslintConfig": { + "extends": "./src/config/eslintrc.js" + }, "browserslist": [ ">1%", "last 2 versions", diff --git a/src/config/aegir-tsconfig.json b/src/config/aegir-tsconfig.json new file mode 100644 index 000000000..04a6c3fb4 --- /dev/null +++ b/src/config/aegir-tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "target": "es2018", + "module": "CommonJS", + "moduleResolution": "node", + "resolveJsonModule": true, + "stripInternal": true, + "strict": false, + "alwaysStrict": true, + "strictNullChecks": true, + "noFallthroughCasesInSwitch": true, + "preserveConstEnums": true, + "noEmitOnError": true, + "noEmit": true, + "esModuleInterop": true, + "declaration": true, + "removeComments": false, + "skipLibCheck": true, + "lib": [ + "ES2018", + "DOM" + ] + } +} \ No newline at end of file diff --git a/src/config/eslintrc.js b/src/config/eslintrc.js index 33c6d4909..59c6fe561 100644 --- a/src/config/eslintrc.js +++ b/src/config/eslintrc.js @@ -5,11 +5,18 @@ module.exports = { parserOptions: { sourceType: 'script' }, + settings: { + jsdoc: { mode: 'typescript' } + }, + env: { + browser: true + }, globals: { self: true }, plugins: [ - 'no-only-tests' + 'no-only-tests', + 'jsdoc' ], rules: { strict: [2, 'safe'], @@ -40,11 +47,32 @@ module.exports = { 'require-yield': 2, 'max-nested-callbacks': [2, 4], 'max-depth': [2, 4], - 'valid-jsdoc': [2, { - requireReturn: false, - requireParamDescription: false, - requireReturnDescription: false - }], - 'require-await': 2 + 'require-await': 2, + 'jsdoc/check-alignment': 2, + 'jsdoc/check-examples': 0, + 'jsdoc/check-indentation': 2, + 'jsdoc/check-param-names': 2, + 'jsdoc/check-syntax': 2, + 'jsdoc/check-tag-names': [2, { definedTags: ['internal', 'packageDocumentation'] }], + 'jsdoc/check-types': 2, + 'jsdoc/implements-on-classes': 2, + 'jsdoc/match-description': 0, + 'jsdoc/newline-after-description': 2, + 'jsdoc/no-types': 0, + 'jsdoc/no-undefined-types': 2, + 'jsdoc/require-description': 0, + 'jsdoc/require-description-complete-sentence': 0, + 'jsdoc/require-example': 0, + 'jsdoc/require-hyphen-before-param-description': 2, + 'jsdoc/require-jsdoc': 0, + 'jsdoc/require-param': 2, + 'jsdoc/require-param-description': 1, + 'jsdoc/require-param-name': 2, + 'jsdoc/require-param-type': 2, + 'jsdoc/require-returns': 2, + 'jsdoc/require-returns-check': 2, + 'jsdoc/require-returns-description': 1, + 'jsdoc/require-returns-type': 2, + 'jsdoc/valid-types': 2 } } diff --git a/src/dependency-check.js b/src/dependency-check.js index f3eec88ce..90853a445 100644 --- a/src/dependency-check.js +++ b/src/dependency-check.js @@ -17,8 +17,8 @@ const defaultInput = [ /** * Check dependencies * - * @param {Object} argv - * @param {ExecaOptions} execaOptions + * @param {object} argv + * @param {ExecaOptions} execaOptions - execa options * @returns {ExecaChildProcess} */ const check = (argv = { _: [] }, execaOptions) => { diff --git a/src/ts/index.js b/src/ts/index.js new file mode 100644 index 000000000..584918443 --- /dev/null +++ b/src/ts/index.js @@ -0,0 +1,153 @@ +'use strict' + +const path = require('path') +const execa = require('execa') +const fs = require('fs-extra') +const rimraf = require('rimraf') +const merge = require('merge-options') +const { fromRoot, fromAegir, repoDirectory, hasFile } = require('../utils') +let baseTsConfig = require('./../config/aegir-tsconfig.json') + +if (hasFile('tsconfig.json')) { + baseTsConfig = require(fromRoot('tsconfig.json')) +} + +module.exports = (argv) => { + const forwardOptions = argv['--'] ? argv['--'] : [] + + if (argv.preset === 'check') { + return check(forwardOptions) + } + + if (argv.preset === 'types') { + return types(forwardOptions) + } + + if (argv.preset === 'types-clean') { + return typesClean() + } + + if (argv.preset === 'docs') { + return docs(forwardOptions) + } + + if (!argv.preset) { + return execa('tsc', [ + ...forwardOptions + ], { + localDir: path.join(__dirname, '../..'), + preferLocal: true, + stdio: 'inherit' + }) + } +} + +const check = async (forwardOptions) => { + const configPath = fromRoot('tsconfig-check.json') + try { + fs.writeJsonSync( + configPath, + merge(baseTsConfig, { + include: ['src/**/*', 'test/**/*'] + }) + ) + await execa('tsc', [ + '-p', configPath, + ...forwardOptions + ], { + localDir: path.join(__dirname, '../..'), + preferLocal: true, + stdio: 'inherit' + }) + } finally { + fs.removeSync(configPath) + } +} + +const typesClean = () => { + rimraf.sync(path.join(repoDirectory, 'src/**/*.d.ts')) +} + +const types = async (forwardOptions) => { + const configPath = fromRoot('tsconfig-types.json') + typesClean() + try { + fs.writeJsonSync( + configPath, + merge(baseTsConfig, { + compilerOptions: { + noEmit: false, + emitDeclarationOnly: true + // outDir: 'types' + }, + include: [ + 'src/**/*' + ] + }) + ) + await execa('tsc', [ + '-p', configPath, + ...forwardOptions + ], { + localDir: path.join(__dirname, '../..'), + preferLocal: true, + stdio: 'inherit' + }) + } finally { + fs.removeSync(configPath) + } +} + +const docs = async (forwardOptions) => { + const configPath = fromRoot('tsconfig-docs.json') + try { + fs.writeJsonSync( + configPath, + merge(baseTsConfig, { + compilerOptions: { + noEmit: false, + emitDeclarationOnly: true, + outDir: 'types' + }, + include: ['src/**/*'] + }) + ) + + // run tsc + await execa('tsc', [ + '-p', configPath, + ...forwardOptions + ], { + localDir: path.join(__dirname, '../..'), + preferLocal: true, + stdio: 'inherit' + }) + + // run typedoc + await execa('typedoc', [ + '--inputfiles', fromRoot('types'), + '--mode', 'modules', + '--out', 'docs', + '--excludeExternals', + '--excludeNotDocumented', + // '--excludeNotExported', + '--excludePrivate', + '--excludeProtected', + '--includeDeclarations', + '--hideGenerator', + '--includeVersion', + '--gitRevision', 'master', + '--disableSources', + '--tsconfig', configPath, + '--plugin', fromAegir('src/ts/typedoc-plugin.js'), + '--theme', fromAegir('./../../node_modules/aegir-typedoc-theme/bin/default') + ], { + localDir: path.join(__dirname, '..'), + preferLocal: true, + stdio: 'inherit' + }) + } finally { + fs.removeSync(configPath) + fs.removeSync(fromRoot('types')) + } +} diff --git a/src/ts/typedoc-plugin.js b/src/ts/typedoc-plugin.js new file mode 100644 index 000000000..b438fca2c --- /dev/null +++ b/src/ts/typedoc-plugin.js @@ -0,0 +1,35 @@ +'use strict' +const { Converter } = require('typedoc/dist/lib/converter') +const path = require('path') +const fs = require('fs') + +module.exports = function (PluginHost) { + const app = PluginHost.owner + const pkg = path.join(process.cwd(), 'package.json') + let pkgJson + let main + try { + pkgJson = JSON.parse(fs.readFileSync(pkg).toString()) + main = path.join(process.cwd(), pkgJson.main) + } catch (err) { + throw new Error('cant find package.json') + } + + app.converter.on(Converter.EVENT_CREATE_DECLARATION, (context, reflection, node) => { + if (reflection.kind === 1 && node) { + // entry point + if (pkgJson && reflection.name === main) { + reflection.name = '\u0000' + pkgJson.name.charAt(0).toUpperCase() + pkgJson.name.slice(1) + // reflection.kind = 2 + } + + if (pkgJson && reflection.name.includes('types/index.d.ts')) { + reflection.name = '\u0000' + pkgJson.name.charAt(0) + pkgJson.name.slice(1) + } + + if (pkgJson && reflection.name.includes('.d.ts')) { + reflection.name = reflection.name.replace('.d.ts', '.js') + } + } + }) +} diff --git a/src/utils.js b/src/utils.js index 4bbaa4699..56c405faa 100644 --- a/src/utils.js +++ b/src/utils.js @@ -38,43 +38,28 @@ exports.fromAegir = (...p) => path.join(__dirname, '..', ...p) /** * Gets the top level path of the project aegir is executed in. * - * @returns {string} + * @returns {string} - base path */ exports.getBasePath = () => { return process.cwd() } -/** - * @returns {string} - */ exports.getPathToPkg = () => { return path.join(exports.getBasePath(), PKG_FILE) } -/** - * @returns {Promise} - */ exports.getPkg = () => { return fs.readJson(exports.getPathToPkg()) } -/** - * @returns {string} - */ exports.getPathToDist = () => { return path.join(exports.getBasePath(), DIST_FOLDER) } -/** - * @returns {string} - */ exports.getUserConfigPath = () => { return findUp('.aegir.js') } -/** - * @returns {Object} - */ exports.getUserConfig = () => { let conf = {} try { @@ -92,9 +77,9 @@ exports.getUserConfig = () => { /** * Converts the given name from something like `peer-id` to `PeerId`. * - * @param {string} name + * @param {string} name - lib name in kebab * - * @returns {string} + * @returns {string} - lib name in pascal */ exports.getLibraryName = (name) => { return pascalcase(name) @@ -103,7 +88,7 @@ exports.getLibraryName = (name) => { /** * Get the absolute path to `node_modules` for aegir itself * - * @returns {string} + * @returns {string} - path to node modules */ exports.getPathToNodeModules = () => { return path.resolve(__dirname, '../node_modules') @@ -112,7 +97,7 @@ exports.getPathToNodeModules = () => { /** * Get the config for Listr. * - * @returns {Object} + * @returns {object} */ exports.getListrConfig = () => { return {