diff --git a/.gitignore b/.gitignore index f62f570e7480..ab1492530c26 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,14 @@ benchmark/dist **/package .sandbox packages/cli/generators/datasource/connectors.json -docs/site/readmes +packages/tsdocs/fixtures/monorepo/docs +/docs/site/readmes +/docs/apidocs/reports-temp +/docs/apidocs/models + +# TBD: Exclude api reports from git for now +/docs/apidocs/reports +/docs/site/apidocs # Exclude all files under sandbox except README.md and example /sandbox/* diff --git a/.prettierignore b/.prettierignore index e8c9b7b64619..403a1a808525 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,10 @@ /coverage **/*/api-docs +/docs/apidocs +/docs/site/apidocs packages/cli/generators/*/templates +packages/tsdocs/fixtures/monorepo/docs +packages/tsdocs/fixtures/monorepo/**/dist **/.sandbox packages/*/dist examples/*/dist diff --git a/CODEOWNERS b/CODEOWNERS index c3719e6f698f..7f758ca30a3c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -25,6 +25,7 @@ packages/repository-json-schema/* @bajtos packages/rest/* @bajtos @raymondfeng packages/service-proxy/* @raymondfeng packages/testlab/* @bajtos +packages/tsdocs/* @raymondfeng examples/todo/* @bajtos @hacksparrow examples/express-composition/* @nabdelgadir examples/greeter-extension/* @raymondfeng diff --git a/bin/build-docs-site.sh b/bin/build-docs-site.sh index a105c4f0e157..016932b5680d 100755 --- a/bin/build-docs-site.sh +++ b/bin/build-docs-site.sh @@ -21,8 +21,9 @@ DIR=`dirname $0` REPO_ROOT=$DIR/.. pushd $REPO_ROOT >/dev/null -# Update README duplicates inside docs/site/readmes -node docs/bin/copy-readmes.js +# Update apidocs +lerna bootstrap --scope @loopback/tsdocs +lerna run --scope @loopback/docs prepack # Clean up sandbox/loopback.io directory rm -rf sandbox/loopback.io/ diff --git a/docs/package.json b/docs/package.json index 5b7b83c0884b..b49e9269479f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,8 +16,8 @@ ], "scripts": { "build:apidocs": "lb-apidocs --html-file=index.html", - "prepublishOnly": "node ./bin/copy-readmes", - "clean": "lb-clean loopback-docs*.tgz package api-docs site/readmes" + "prepack": "node ./bin/copy-readmes && cd .. && npm run tsdocs", + "clean": "lb-clean loopback-docs*.tgz package api-docs apidocs site/readmes site/apidocs" }, "devDependencies": { "@loopback/build": "^1.5.5" diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md index daa15801ec23..413629dceeae 100644 --- a/docs/site/MONOREPO.md +++ b/docs/site/MONOREPO.md @@ -36,6 +36,7 @@ The [loopback-next](https://github.com/strongloop/loopback-next) repository uses | [rest](https://github.com/strongloop/loopback-next/tree/master/packages/rest) | @loopback/rest | Expose controllers as REST endpoints and route REST API requests to controller methods | | [service-proxy](https://github.com/strongloop/loopback-next/tree/master/packages/service-proxy) | @loopback/service-proxy | A common set of interfaces for interacting with service oriented backends such as REST APIs, SOAP Web Services, and gRPC microservices | | [testlab](https://github.com/strongloop/loopback-next/tree/master/packages/testlab) | @loopback/testlab | A collection of test utilities we use to write LoopBack tests | +| [tsdocs](https://github.com/strongloop/loopback-next/tree/master/packages/tsdocs) | @loopback/tsdocs | An internal package to generate api docs using Microsoft api-extractor and api-documenter | | [tslint-config](https://github.com/strongloop/loopback-next/tree/master/packages/tslint-config) | @loopback/tslint-config | Shared TSLint config to enforce a consistent code style for LoopBack development | We use npm scripts declared in diff --git a/docs/site/sidebars/lb4_sidebar.yml b/docs/site/sidebars/lb4_sidebar.yml index a46186fbd3a5..4a0448c426b7 100644 --- a/docs/site/sidebars/lb4_sidebar.yml +++ b/docs/site/sidebars/lb4_sidebar.yml @@ -393,6 +393,10 @@ children: url: FAQ.html output: 'web, pdf' +- title: 'API docs' + url: apidocs.index.html + output: 'web, pdf' + - title: 'Reference' url: Reference.html output: 'web, pdf' diff --git a/greenkeeper.json b/greenkeeper.json index 4d90d0017ab1..3cdf168f017e 100644 --- a/greenkeeper.json +++ b/greenkeeper.json @@ -34,6 +34,7 @@ "packages/rest/package.json", "packages/service-proxy/package.json", "packages/testlab/package.json", + "packages/tsdocs/package.json", "packages/tslint-config/package.json" ] } diff --git a/package.json b/package.json index 3d70e8df154d..4490a2b4b6fe 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "version": "npm run update-packages && git add greenkeeper.json \"**/package-lock.json\" && npm run update-template-deps && npm run apidocs", "outdated": "npm outdated --depth 0 && lerna exec --no-bail \"npm outdated --depth 0\"", "apidocs": "node bin/run-lerna run build:apidocs", + "tsdocs": "lerna run --scope @loopback/tsdocs build:tsdocs", "coverage:ci": "node packages/build/bin/run-nyc report --reporter=text-lcov | coveralls", "precoverage": "npm test", "coverage": "open coverage/index.html", diff --git a/packages/build/index.js b/packages/build/index.js index 28b65e257b02..c5241d3527d8 100644 --- a/packages/build/index.js +++ b/packages/build/index.js @@ -16,3 +16,9 @@ exports.clean = require('./bin/run-clean'); const utils = require('./bin/utils'); exports.runCLI = utils.runCLI; exports.runShell = utils.runShell; + +const path = require('path'); +exports.typeScriptPath = path.resolve( + require.resolve('typescript/package.json'), + '..', +); diff --git a/packages/tsdocs/.npmrc b/packages/tsdocs/.npmrc new file mode 100644 index 000000000000..cafe685a112d --- /dev/null +++ b/packages/tsdocs/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/packages/tsdocs/LICENSE b/packages/tsdocs/LICENSE new file mode 100644 index 000000000000..3bd183dad026 --- /dev/null +++ b/packages/tsdocs/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2019. All Rights Reserved. +Node module: @loopback/tsdocs +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/tsdocs/README.md b/packages/tsdocs/README.md new file mode 100644 index 000000000000..f965977f826d --- /dev/null +++ b/packages/tsdocs/README.md @@ -0,0 +1,73 @@ +# @loopback/tsdocs + +This module provides [API docs](https://github.com/Microsoft/tsdoc) generation +for `@loopback/*` packages. + +It's built on top of https://api-extractor.com/: + +- https://github.com/Microsoft/web-build-tools/tree/master/apps/api-extractor +- https://github.com/Microsoft/web-build-tools/tree/master/apps/api-documenter + +## Basic Use + +### Build api reports and doc models + +```sh +npm run extract-apidocs -- --report +``` + +The command above will traverse all TypeScript packages in the monorepo and run +`api-extractor` to generate api reports and doc models into +`loopback-next/docs/apidocs`: + +- `reports`: api reports +- `reports-temp`: temporary api reports +- `models`: doc models + +### Generate api docs as markdown files + +```sh +npm run document-apidocs +``` + +It runs `api-documenter` to generate markdown files into +`loopback-next/docs/site/apidocs`. + +### Update api docs for Jekyll site + +```sh +npm run update-apidocs +``` + +It adds Jekyll metadata to markdown files in `loopback-next/docs/site/apidocs` +and generates `loopback-next/docs/site/apidocs/index.md` as the index page. + +To run all steps together: + +```sh +npm run build:apidocs +``` + +## Installation + +```sh +npm install --save @loopback/tsdocs +``` + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/packages/tsdocs/bin/extract-apis.js b/packages/tsdocs/bin/extract-apis.js new file mode 100644 index 000000000000..878c603611f7 --- /dev/null +++ b/packages/tsdocs/bin/extract-apis.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * Run api-extractor against the monorepo + */ +const runExtractorForMonorepo = require('..').runExtractorForMonorepo; + +const silent = process.argv.includes('--silent'); +const dryRun = process.argv.includes('--dry-run'); + +/** + * The option to control if reports are generated by api-extractor + */ +const apiReportEnabled = process.argv.includes('--report'); + +async function main() { + await runExtractorForMonorepo({silent, dryRun, apiReportEnabled}); +} + +main(); diff --git a/packages/tsdocs/bin/update-apidocs.js b/packages/tsdocs/bin/update-apidocs.js new file mode 100644 index 000000000000..5795fd146c57 --- /dev/null +++ b/packages/tsdocs/bin/update-apidocs.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * Update generated api md files with Jekyll macros and create index page + */ +const updateApiDocs = require('..').updateApiDocs; +const silent = process.argv.includes('--silent'); +const dryRun = process.argv.includes('--dry-run'); + +async function main() { + await updateApiDocs({silent, dryRun}); +} + +main(); diff --git a/packages/tsdocs/fixtures/README.md b/packages/tsdocs/fixtures/README.md new file mode 100644 index 000000000000..d76f22edbb1d --- /dev/null +++ b/packages/tsdocs/fixtures/README.md @@ -0,0 +1,13 @@ +# fixtures/monorepo + +This `monorepo` directory contains a simplified monorepo managed by lerna. It's +used to mimic `loopback-next` to test `@loopback/tsdocs`. + +# Rebuild + +If you have to change the source code for the fixture, run the following command +to rebuild. + +```sh +npm run build +``` diff --git a/packages/tsdocs/fixtures/monorepo/lerna.json b/packages/tsdocs/fixtures/monorepo/lerna.json new file mode 100644 index 000000000000..a07b44d28508 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/lerna.json @@ -0,0 +1,17 @@ +{ + "lerna": "3.3.0", + "packages": [ + "packages/*", + "docs" + ], + "version": "independent", + "command": { + "run": { + "prefix": false, + "loglevel": "silent", + "stream": true, + "concurrency": 8, + "sort": true + } + } +} diff --git a/packages/tsdocs/fixtures/monorepo/package.json b/packages/tsdocs/fixtures/monorepo/package.json new file mode 100644 index 000000000000..8b8b3094ef52 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/package.json @@ -0,0 +1,15 @@ +{ + "name": "root", + "version": "1.0.0", + "description": "Root", + "scripts": { + "clean": "../../../../node_modules/.bin/lerna run clean", + "bootstrap": "../../../../node_modules/.bin/lerna bootstrap", + "build": "npm -s run bootstrap && npm -s run clean && ../../../../node_modules/.bin/lerna run build" + }, + "author": "", + "license": "MIT", + "devDependencies": { + "@loopback/build": "file:../../../build" + } +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.d.ts b/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.d.ts new file mode 100644 index 000000000000..cc100e22af31 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.d.ts @@ -0,0 +1,19 @@ +/** + * Base class for pets + */ +export declare class Pet { + readonly name: string; + readonly kind: string; + /** + * Create a pet + * @param name - Name of the pet + * @param kind - Kind of the pet + */ + constructor(name: string, kind: string); + /** + * Greet the pet + * @param msg - Message for the greeting + * @returns The description + */ + greet(msg: string): string; +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.js b/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.js new file mode 100644 index 000000000000..497429d080e0 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.js @@ -0,0 +1,29 @@ +"use strict"; +// Copyright IBM Corp. 2019. All Rights Reserved. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Base class for pets + */ +class Pet { + /** + * Create a pet + * @param name - Name of the pet + * @param kind - Kind of the pet + */ + constructor(name, kind) { + this.name = name; + this.kind = kind; + } + /** + * Greet the pet + * @param msg - Message for the greeting + * @returns The description + */ + greet(msg) { + return `[${msg}] ${this.name}:${this.kind}`; + } +} +exports.Pet = Pet; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.js.map b/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.js.map new file mode 100644 index 000000000000..38ea8ae9ae2a --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg1/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,+CAA+C;AAC/C,gEAAgE;;AAEhE;;GAEG;AACH,MAAa,GAAG;IACd;;;;OAIG;IACH,YAA4B,IAAY,EAAkB,IAAY;QAA1C,SAAI,GAAJ,IAAI,CAAQ;QAAkB,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAE1E;;;;OAIG;IACH,KAAK,CAAC,GAAW;QACf,OAAO,IAAI,GAAG,KAAK,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;IAC9C,CAAC;CACF;AAhBD,kBAgBC"} \ No newline at end of file diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg1/package.json b/packages/tsdocs/fixtures/monorepo/packages/pkg1/package.json new file mode 100644 index 000000000000..7668f72976a2 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg1/package.json @@ -0,0 +1,12 @@ +{ + "name": "pkg1", + "version": "1.0.0", + "description": "Test package 1", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "MIT", + "scripts": { + "build": "lb-tsc es2017 --outDir dist", + "clean": "lb-clean dist" + } +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg1/src/index.ts b/packages/tsdocs/fixtures/monorepo/packages/pkg1/src/index.ts new file mode 100644 index 000000000000..3b92cc926a62 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg1/src/index.ts @@ -0,0 +1,24 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * Base class for pets + */ +export class Pet { + /** + * Create a pet + * @param name - Name of the pet + * @param kind - Kind of the pet + */ + constructor(public readonly name: string, public readonly kind: string) {} + + /** + * Greet the pet + * @param msg - Message for the greeting + * @returns The description + */ + greet(msg: string) { + return `[${msg}] ${this.name}:${this.kind}`; + } +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg1/tsconfig.build.json b/packages/tsdocs/fixtures/monorepo/packages/pkg1/tsconfig.build.json new file mode 100644 index 000000000000..6e15e4be4f6f --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg1/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.d.ts b/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.d.ts new file mode 100644 index 000000000000..973542e026de --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.d.ts @@ -0,0 +1,7 @@ +import { Pet } from 'pkg1'; +/** + * Dog + */ +export declare class Dog extends Pet { + kind: string; +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.js b/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.js new file mode 100644 index 000000000000..dec349ecbf80 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.js @@ -0,0 +1,17 @@ +"use strict"; +// Copyright IBM Corp. 2019. All Rights Reserved. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +Object.defineProperty(exports, "__esModule", { value: true }); +const pkg1_1 = require("pkg1"); +/** + * Dog + */ +class Dog extends pkg1_1.Pet { + constructor() { + super(...arguments); + this.kind = 'Dog'; + } +} +exports.Dog = Dog; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.js.map b/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.js.map new file mode 100644 index 000000000000..ae81d59c7bd0 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg2/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,+CAA+C;AAC/C,gEAAgE;;AAEhE,+BAAyB;AAEzB;;GAEG;AACH,MAAa,GAAI,SAAQ,UAAG;IAA5B;;QACE,SAAI,GAAG,KAAK,CAAC;IACf,CAAC;CAAA;AAFD,kBAEC"} \ No newline at end of file diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg2/package.json b/packages/tsdocs/fixtures/monorepo/packages/pkg2/package.json new file mode 100644 index 000000000000..e2ee8b16be4d --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg2/package.json @@ -0,0 +1,15 @@ +{ + "name": "pkg2", + "version": "1.0.0", + "description": "Test package 2", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "license": "MIT", + "dependencies": { + "pkg1": "*" + }, + "scripts": { + "build": "lb-tsc es2017 --outDir dist", + "clean": "lb-clean dist" + } +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg2/src/index.ts b/packages/tsdocs/fixtures/monorepo/packages/pkg2/src/index.ts new file mode 100644 index 000000000000..247e3882ab72 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg2/src/index.ts @@ -0,0 +1,12 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Pet} from 'pkg1'; + +/** + * Dog + */ +export class Dog extends Pet { + kind = 'Dog'; +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg2/tsconfig.build.json b/packages/tsdocs/fixtures/monorepo/packages/pkg2/tsconfig.build.json new file mode 100644 index 000000000000..6e15e4be4f6f --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg2/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg3-non-ts/index.js b/packages/tsdocs/fixtures/monorepo/packages/pkg3-non-ts/index.js new file mode 100644 index 000000000000..9e442bd611a9 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg3-non-ts/index.js @@ -0,0 +1,4 @@ +module.exports = { + package: 'pkg3', + version: '1.0.0', +}; diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg3-non-ts/package.json b/packages/tsdocs/fixtures/monorepo/packages/pkg3-non-ts/package.json new file mode 100644 index 000000000000..72f40daf3e27 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg3-non-ts/package.json @@ -0,0 +1,7 @@ +{ + "name": "pkg3", + "version": "1.0.0", + "description": "Test package 3", + "main": "index.js", + "license": "MIT" +} diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg4-private/index.js b/packages/tsdocs/fixtures/monorepo/packages/pkg4-private/index.js new file mode 100644 index 000000000000..58a35e7de180 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg4-private/index.js @@ -0,0 +1,4 @@ +module.exports = { + package: 'pkg4', + version: '1.0.0', +}; diff --git a/packages/tsdocs/fixtures/monorepo/packages/pkg4-private/package.json b/packages/tsdocs/fixtures/monorepo/packages/pkg4-private/package.json new file mode 100644 index 000000000000..9688fdfc0f86 --- /dev/null +++ b/packages/tsdocs/fixtures/monorepo/packages/pkg4-private/package.json @@ -0,0 +1,8 @@ +{ + "name": "pkg4", + "version": "1.0.0", + "description": "Test package 4", + "private": true, + "main": "index.js", + "license": "MIT" +} diff --git a/packages/tsdocs/index.d.ts b/packages/tsdocs/index.d.ts new file mode 100644 index 000000000000..e8feeeea65a4 --- /dev/null +++ b/packages/tsdocs/index.d.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './dist'; diff --git a/packages/tsdocs/index.js b/packages/tsdocs/index.js new file mode 100644 index 000000000000..48f716449928 --- /dev/null +++ b/packages/tsdocs/index.js @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +module.exports = require('./dist'); diff --git a/packages/tsdocs/index.ts b/packages/tsdocs/index.ts new file mode 100644 index 000000000000..34eff4118239 --- /dev/null +++ b/packages/tsdocs/index.ts @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// DO NOT EDIT THIS FILE +// Add any additional (re)exports to src/index.ts instead. +export * from './src'; diff --git a/packages/tsdocs/package-lock.json b/packages/tsdocs/package-lock.json new file mode 100644 index 000000000000..8340b7088331 --- /dev/null +++ b/packages/tsdocs/package-lock.json @@ -0,0 +1,315 @@ +{ + "name": "@loopback/tsdocs", + "version": "1.0.0-1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@microsoft/api-documenter": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.2.1.tgz", + "integrity": "sha512-o5SjVRovTWL9NwZdQimhXdWEKkYdxlrRDy+Y4GDvx9R5vObS2ptKZoC0bdHT5R9Febw9I/io8YVcQOzFHxKuqw==", + "requires": { + "@microsoft/api-extractor-model": "7.1.1", + "@microsoft/node-core-library": "3.13.0", + "@microsoft/ts-command-line": "4.2.5", + "@microsoft/tsdoc": "0.12.9", + "colors": "~1.2.1", + "js-yaml": "~3.13.1" + } + }, + "@microsoft/api-extractor": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.1.6.tgz", + "integrity": "sha512-0oXmPnJyc7OXA/+TKHUPW/HWG9qLhjoz5uAvKoI/9csnH5a0mzO+5k1LitPhY/O0rWPz5lghX16JnSm6G4mJ2Q==", + "requires": { + "@microsoft/api-extractor-model": "7.1.1", + "@microsoft/node-core-library": "3.13.0", + "@microsoft/ts-command-line": "4.2.5", + "@microsoft/tsdoc": "0.12.9", + "colors": "~1.2.1", + "lodash": "~4.17.5", + "resolve": "1.8.1", + "source-map": "~0.6.1", + "typescript": "~3.4.3" + } + }, + "@microsoft/api-extractor-model": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.1.1.tgz", + "integrity": "sha512-RiQJA8JpBbRxqUfro8SxZGLjeCFH8HUUJvD5VTUrDrgdt13omx7d0bsGN0lf+AT63yF+RX4R2+Jd2b0SaAO/sQ==", + "requires": { + "@microsoft/node-core-library": "3.13.0", + "@microsoft/tsdoc": "0.12.9", + "@types/node": "8.5.8" + }, + "dependencies": { + "@types/node": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.8.tgz", + "integrity": "sha512-8KmlRxwbKZfjUHFIt3q8TF5S2B+/E5BaAoo/3mgc5h6FJzqxXkCK/VMetO+IRDtwtU6HUvovHMBn+XRj7SV9Qg==" + } + } + }, + "@microsoft/node-core-library": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@microsoft/node-core-library/-/node-core-library-3.13.0.tgz", + "integrity": "sha512-mnsL/1ikVWHl8sPNssavaAgtUaIM3hkQ8zeySuApU5dNmsMPzovJPfx9m5JGiMvs1v5QNAIVeiS9jnWwe/7anw==", + "requires": { + "@types/fs-extra": "5.0.4", + "@types/jju": "~1.4.0", + "@types/node": "8.5.8", + "@types/z-schema": "3.16.31", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "jju": "~1.4.0", + "z-schema": "~3.18.3" + }, + "dependencies": { + "@types/fs-extra": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.4.tgz", + "integrity": "sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.8.tgz", + "integrity": "sha512-8KmlRxwbKZfjUHFIt3q8TF5S2B+/E5BaAoo/3mgc5h6FJzqxXkCK/VMetO+IRDtwtU6HUvovHMBn+XRj7SV9Qg==" + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "@microsoft/ts-command-line": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@microsoft/ts-command-line/-/ts-command-line-4.2.5.tgz", + "integrity": "sha512-QyTfbfpx6o+1cnjx5GubqKSRMDxBBMXABSa8wmqRa/A1K99FEBZfLIO6GmaY0s7rNYEchfR1VcVS/hV2VW+6hw==", + "requires": { + "@types/argparse": "1.0.33", + "@types/node": "8.5.8", + "argparse": "~1.0.9", + "colors": "~1.2.1" + }, + "dependencies": { + "@types/node": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.8.tgz", + "integrity": "sha512-8KmlRxwbKZfjUHFIt3q8TF5S2B+/E5BaAoo/3mgc5h6FJzqxXkCK/VMetO+IRDtwtU6HUvovHMBn+XRj7SV9Qg==" + } + } + }, + "@microsoft/tsdoc": { + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.9.tgz", + "integrity": "sha512-sDhulvuVk65eMppYOE6fr6mMI6RUqs53KUg9deYzNCBpP+2FhR0OFB5innEfdtSedk0LK+1Ppu6MxkfiNjS/Cw==" + }, + "@types/argparse": { + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.33.tgz", + "integrity": "sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ==" + }, + "@types/debug": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.4.tgz", + "integrity": "sha512-D9MyoQFI7iP5VdpEyPZyjjqIJ8Y8EDNQFIFVLOmeg1rI1xiHOChyUPMPRUVfqFCerxfE+yS3vMyj37F6IdtOoQ==", + "dev": true + }, + "@types/fs-extra": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-7.0.0.tgz", + "integrity": "sha512-ndoMMbGyuToTy4qB6Lex/inR98nPiNHacsgMPvy+zqMLgSxbt8VtWpDArpGp69h1fEDQHn1KB+9DWD++wgbwYA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/jju": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/jju/-/jju-1.4.1.tgz", + "integrity": "sha512-LFt+YA7Lv2IZROMwokZKiPNORAV5N3huMs3IKnzlE430HWhWYZ8b+78HiwJXJJP1V2IEjinyJURuRJfGoaFSIA==" + }, + "@types/node": { + "version": "10.14.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.6.tgz", + "integrity": "sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg==", + "dev": true + }, + "@types/z-schema": { + "version": "3.16.31", + "resolved": "https://registry.npmjs.org/@types/z-schema/-/z-schema-3.16.31.tgz", + "integrity": "sha1-LrHQCl5Ow/pYx2r94S4YK2bcXBw=" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "colors": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", + "integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==" + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "optional": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "fs-extra": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.0.1.tgz", + "integrity": "sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "p-event": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", + "dev": true, + "requires": { + "p-timeout": "^2.0.1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "requires": { + "path-parse": "^1.0.5" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "typescript": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", + "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "validator": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-8.2.0.tgz", + "integrity": "sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA==" + }, + "z-schema": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.18.4.tgz", + "integrity": "sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw==", + "requires": { + "commander": "^2.7.1", + "lodash.get": "^4.0.0", + "lodash.isequal": "^4.0.0", + "validator": "^8.0.0" + } + } + } +} diff --git a/packages/tsdocs/package.json b/packages/tsdocs/package.json new file mode 100644 index 000000000000..b6c40728de42 --- /dev/null +++ b/packages/tsdocs/package.json @@ -0,0 +1,59 @@ +{ + "name": "@loopback/tsdocs", + "version": "1.0.0-1", + "description": "LoopBack's TypeScript docs based on Microsoft api-extractor and api-documenter", + "engines": { + "node": ">=8.9" + }, + "private": true, + "scripts": { + "build:tsdocs": "npm run build && npm run -s extract-apidocs && npm run -s document-apidocs && npm run -s update-apidocs", + "extract-apidocs": "node bin/extract-apis", + "document-apidocs": "api-documenter markdown -i ../../docs/apidocs/models -o ../../docs/site/apidocs", + "update-apidocs": "node bin/update-apidocs", + "build": "lb-tsc es2017 --outDir dist", + "clean": "lb-clean loopback-tsdocs*.tgz dist package api-docs", + "pretest": "npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "verify": "npm pack && tar xf loopback-tsdocs*.tgz && tree package && npm run clean" + }, + "author": "IBM Corp.", + "copyright.owner": "IBM Corp.", + "license": "MIT", + "dependencies": { + "@microsoft/api-documenter": "^7.2.1", + "@microsoft/api-extractor": "^7.1.6", + "debug": "^4.0.1", + "fs-extra": "^8.0.1" + }, + "devDependencies": { + "@loopback/build": "^1.5.0", + "@loopback/testlab": "^1.2.5", + "@loopback/tslint-config": "^2.0.4", + "@types/debug": "^4.1.4", + "@types/fs-extra": "^7.0.0", + "@types/node": "^10.11.2", + "p-event": "^4.1.0" + }, + "keywords": [ + "LoopBack", + "Apidocs", + "Reflect" + ], + "files": [ + "README.md", + "index.js", + "index.d.ts", + "dist", + "src", + "!*/__tests__", + "!/fixtures" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git" + } +} diff --git a/packages/tsdocs/src/__tests__/acceptance/tsdocs.acceptance.ts b/packages/tsdocs/src/__tests__/acceptance/tsdocs.acceptance.ts new file mode 100644 index 000000000000..e067b5b24943 --- /dev/null +++ b/packages/tsdocs/src/__tests__/acceptance/tsdocs.acceptance.ts @@ -0,0 +1,108 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {expect} from '@loopback/testlab'; +import * as fs from 'fs-extra'; +import pEvent from 'p-event'; +import * as path from 'path'; +import {runExtractorForMonorepo, updateApiDocs} from '../..'; + +const runCLI = require('@loopback/build').runCLI; + +const MONOREPO_ROOT = path.join(__dirname, '../../../fixtures/monorepo'); +const APIDOCS_ROOT = path.join(MONOREPO_ROOT, 'docs/apidocs'); +const SITE_APIDOCS_ROOT = path.join(MONOREPO_ROOT, 'docs/site/apidocs'); + +describe('tsdocs', function() { + // tslint:disable-next-line:no-invalid-this + this.timeout(10000); + + const API_MD_FILES = [ + 'pkg1.md', + 'pkg1.pet.(constructor).md', + 'pkg1.pet.greet.md', + 'pkg1.pet.kind.md', + 'pkg1.pet.md', + 'pkg1.pet.name.md', + 'pkg2.dog.kind.md', + 'pkg2.dog.md', + 'pkg2.md', + ]; + + before('remove apidocs', () => { + fs.emptyDirSync(APIDOCS_ROOT); + fs.emptyDirSync(SITE_APIDOCS_ROOT); + }); + + it('runs api-extractor', async () => { + await runExtractorForMonorepo({ + rootDir: MONOREPO_ROOT, + silent: true, + apiDocsGenerationPath: 'docs/apidocs', + apiReportEnabled: true, + }); + + const dirs = await fs.readdir(APIDOCS_ROOT); + expect(dirs.sort()).eql(['models', 'reports', 'reports-temp']); + + const models = await fs.readdir(path.join(APIDOCS_ROOT, 'models')); + expect(models.sort()).to.eql(['pkg1.api.json', 'pkg2.api.json']); + + const reports = await fs.readdir(path.join(APIDOCS_ROOT, 'reports')); + expect(reports.sort()).to.eql(['pkg1.api.md', 'pkg2.api.md']); + }); + + it('runs api-documenter', async () => { + const args = [ + 'markdown', + '-i', + path.join(APIDOCS_ROOT, 'models'), + '-o', + SITE_APIDOCS_ROOT, + ]; + process.chdir(path.join(__dirname, '../../..')); + const child = runCLI('@microsoft/api-documenter/lib/start', args, { + stdio: 'ignore', + }); + await pEvent(child, 'close'); + const files = await fs.readdir(SITE_APIDOCS_ROOT); + expect(files.sort()).eql(API_MD_FILES); + }); + + it('updates apidocs for site', async () => { + await updateApiDocs({ + rootDir: MONOREPO_ROOT, + silent: true, + apiDocsGenerationPath: 'docs/site/apidocs', + }); + + const files = await fs.readdir(SITE_APIDOCS_ROOT); + expect(files.sort()).to.eql(['index.md', ...API_MD_FILES]); + + for (const f of files) { + const md = await fs.readFile(path.join(SITE_APIDOCS_ROOT, f), 'utf-8'); + expect(md).to.match(/lang\: en/); + expect(md).to.match(/sidebar\: lb4_sidebar/); + } + + const index = await fs.readFile( + path.join(SITE_APIDOCS_ROOT, 'index.md'), + 'utf-8', + ); + expect(index).to.eql(`--- +lang: en +title: 'API docs' +keywords: LoopBack 4.0, LoopBack 4 +sidebar: lb4_sidebar +permalink: /doc/en/lb4/apidocs.index.html +--- + +## API Docs +- [pkg1](pkg1.md) +- [pkg2](pkg2.md) + +`); + }); +}); diff --git a/packages/tsdocs/src/helper.ts b/packages/tsdocs/src/helper.ts new file mode 100644 index 000000000000..4c9ce44c1aed --- /dev/null +++ b/packages/tsdocs/src/helper.ts @@ -0,0 +1,178 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {IConfigFile} from '@microsoft/api-extractor'; +import * as fs from 'fs-extra'; +import * as path from 'path'; + +const Project = require('@lerna/project'); + +/** + * TypeScript definition for + * {@link https://github.com/lerna/lerna/blob/master/core/package/index.js | Lerna Package) + */ +export interface LernaPackage { + /** + * Package name + */ + name: string; + /** + * Location of the package + */ + location: string; + /** + * Root directory of the monorepo + */ + rootPath: string; + /** + * Location of `package.json` + */ + manifestLocation: string; + /** + * Is it a private package? + */ + private: boolean; +} + +/** + * Get un-scoped package name + * + * @example + * - '@loopback/context' => 'context' + * - 'express' => 'express' + * + * @param name Name of the npm package + */ +export function getUnscopedPackageName(name: string) { + if (name.startsWith('@')) { + return name.split('/')[1]; + } + return name; +} + +/** + * Get lerna packages and sorted them by location + * + * @param rootDir Root directory to find lerna.json + */ +export async function getPackages( + rootDir = process.cwd(), +): Promise { + const project = new Project(rootDir); + const packages: LernaPackage[] = await project.getPackages(); + packages.sort((p1, p2) => p1.location.localeCompare(p2.location)); + return packages; +} + +/** + * Check if a package should be processed for tsdocs + * + * @param pkg Lerna package + */ +export function shouldGenerateTsDocs(pkg: LernaPackage) { + // We generate tsdocs for `@loopback/tsdocs` even it's private at this moment + if (pkg.name === '@loopback/tsdocs') return true; + + if (pkg.private && pkg.name !== '@loopback/tsdocs') return false; + + /* istanbul ignore if */ + if (pkg.name.startsWith('@loopback/example-')) return false; + + if ( + !fs.existsSync(path.join(pkg.location, 'tsconfig.build.json')) && + !fs.existsSync(path.join(pkg.location, 'tsconfig.json')) + ) { + return false; + } + + /* istanbul ignore if */ + if (!fs.existsSync(path.join(pkg.location, 'dist/index.d.ts'))) return false; + + return true; +} + +/** + * Get an array of lerna-managed TypeScript packages to generate tsdocs + * + * @param rootDir Root directory to find the monorepo + */ +export async function getPackagesWithTsDocs( + rootDir = process.cwd(), +): Promise { + const packages = await getPackages(rootDir); + return packages.filter(shouldGenerateTsDocs); +} + +/** + * Default path for apidocs to be generated for loopback.io site + */ +export const DEFAULT_APIDOCS_GENERATION_PATH = 'docs/site/apidocs'; + +/** + * Options for api docs + */ +export interface ApiDocsOptions { + /** + * To have a dry-run without generating api reports/doc models + */ + dryRun?: boolean; + /** + * If `true`, do not print messages to console + */ + silent?: boolean; + /** + * Root directory for the lerna-managed monorepo, default to current dir + */ + rootDir?: string; + /** + * Path to tsdocs reports/models + */ + apiDocsExtractionPath?: string; + /** + * Path to target directory to generate apidocs + */ + apiDocsGenerationPath?: string; + + /** + * A flag to generate default package documentation + */ + generateDefaultPackageDoc?: boolean; +} + +/** + * Options to run api-extractor against the lerna repo + */ +export interface ExtractorOptions extends ApiDocsOptions { + /** + * Configuration for api-extractor + */ + config?: IConfigFile; + /** + * Custom TypeScript compiler dir + */ + typescriptCompilerFolder?: string; + /** + * Path for tsconfig + */ + tsconfigFilePath?: string; + /** + * mainEntryPointFilePath + */ + mainEntryPointFilePath?: string; + /** + * A flag to control if `apiReport` should be enabled + */ + apiReportEnabled?: boolean; +} + +/** + * Default path as the output directory for extracted api reports and models + */ +export const DEFAULT_APIDOCS_EXTRACTION_PATH = 'docs/apidocs'; + +/** + * Export the TypeScript path from `@loopback/build` + */ +export const typeScriptPath = require('@loopback/build').typeScriptPath; diff --git a/packages/tsdocs/src/index.ts b/packages/tsdocs/src/index.ts new file mode 100644 index 000000000000..4cedd3ce46f9 --- /dev/null +++ b/packages/tsdocs/src/index.ts @@ -0,0 +1,23 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * @packageDocumentation + * + * The `@loopback/tsdocs` package is an internal module to generate + * {@link https://github.com/Microsoft/tsdoc | tsdoc} based API docs + * for `@loopback/*` packages within + * {@link https://github.com/strongloop/loopback-next | loopback-next} monorepo + * managed by {@link https://github.com/lerna/lerna | lerna}. + * + * It's built on top of {@link https://api-extractor.com | MicroSoft AP Extractor}: + * + * - {@link https://github.com/Microsoft/web-build-tools/tree/master/apps/api-extractor | api-extractor} + * - {@link https://github.com/Microsoft/web-build-tools/tree/master/apps/api-documenter | api-documenter} + * + */ +export * from './helper'; +export * from './monorepo-api-extractor'; +export * from './update-api-md-docs'; diff --git a/packages/tsdocs/src/monorepo-api-extractor.ts b/packages/tsdocs/src/monorepo-api-extractor.ts new file mode 100644 index 000000000000..28a0fd8fe466 --- /dev/null +++ b/packages/tsdocs/src/monorepo-api-extractor.ts @@ -0,0 +1,201 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + CompilerState, + ConsoleMessageId, + Extractor, + ExtractorConfig, + ExtractorLogLevel, + ExtractorMessage, + ExtractorResult, + IConfigFile, +} from '@microsoft/api-extractor'; +import * as debugFactory from 'debug'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { + DEFAULT_APIDOCS_EXTRACTION_PATH, + ExtractorOptions, + getPackagesWithTsDocs, + LernaPackage, + typeScriptPath, +} from './helper'; +const debug = debugFactory('loopback:tsdocs'); + +/** + * Run api-extractor for a lerna-managed monrepo + * + * @remarks + * The function performs the following steps: + * 1. Discover packages with tsdocs from the monorepo + * 2. Iterate through each package to run `api-extractor` + * + * @param options Options for running api-extractor + */ +export async function runExtractorForMonorepo(options: ExtractorOptions = {}) { + debug('Extractor options:', options); + + options = Object.assign( + { + rootDir: process.cwd(), + apiDocsExtractionPath: DEFAULT_APIDOCS_EXTRACTION_PATH, + typescriptCompilerFolder: typeScriptPath, + tsconfigFilePath: 'tsconfig.build.json', + mainEntryPointFilePath: 'dist/index.d.ts', + }, + options, + ); + + const packages = await getPackagesWithTsDocs(options.rootDir); + + /* istanbul ignore if */ + if (!packages.length) return; + + const lernaRootDir = packages[0].rootPath; + + /* istanbul ignore if */ + if (!options.silent) { + console.log('Running api-extractor for lerna repo: %s', lernaRootDir); + } + + setupApiDocsDirs(lernaRootDir, options); + + for (const pkg of packages) { + /* istanbul ignore if */ + if (!options.silent) { + console.log('> %s', pkg.name); + } + debug('Package: %s (%s)', pkg.name, pkg.location); + + process.chdir(pkg.location); + + const extractorConfig = buildExtractorConfig(pkg, options); + + debug('Resolved extractor config:', extractorConfig); + invokeExtractor(extractorConfig, options); + } +} + +/** + * Set up dirs for apidocs + * + * @param lernaRootDir Root dir of the monorepo + * @param options Extractor options + */ +function setupApiDocsDirs(lernaRootDir: string, options: ExtractorOptions) { + /* istanbul ignore if */ + if (options.dryRun) return; + const apiDocsExtractionPath = options.apiDocsExtractionPath!; + + fs.emptyDirSync(path.join(lernaRootDir, `${apiDocsExtractionPath}/models`)); + + if (!options.apiReportEnabled) return; + + fs.ensureDirSync(path.join(lernaRootDir, `${apiDocsExtractionPath}/reports`)); + fs.emptyDirSync( + path.join(lernaRootDir, `${apiDocsExtractionPath}/reports-temp`), + ); +} + +/** + * Build extractor configuration object for the given package + * + * @param pkg Lerna managed package + * @param options Extractor options + */ +function createRawExtractorConfig( + pkg: LernaPackage, + options: ExtractorOptions, +) { + const entryPoint = path.join(pkg.location, options.mainEntryPointFilePath!); + const apiDocsExtractionPath = options.apiDocsExtractionPath!; + let configObj: IConfigFile = { + projectFolder: pkg.location, + mainEntryPointFilePath: entryPoint, + apiReport: { + enabled: !!options.apiReportEnabled, + reportFolder: path.join(pkg.rootPath, `${apiDocsExtractionPath}/reports`), + reportTempFolder: path.join( + pkg.rootPath, + `${apiDocsExtractionPath}/reports-temp`, + ), + reportFileName: '.api.md', + }, + docModel: { + enabled: true, + apiJsonFilePath: path.join( + pkg.rootPath, + `${apiDocsExtractionPath}/models/.api.json`, + ), + }, + messages: { + extractorMessageReporting: { + 'ae-missing-release-tag': { + logLevel: ExtractorLogLevel.None, + addToApiReportFile: false, + }, + }, + }, + compiler: { + tsconfigFilePath: options.tsconfigFilePath!, + }, + }; + /* istanbul ignore if */ + if (options.config) { + configObj = Object.assign(configObj, options.config); + } + debug('Extractor config options:', configObj); + return configObj; +} + +/** + * Create and prepare the extractor config for invocation + * + * @param pkg Lerna package + * @param options Extractor options + */ +function buildExtractorConfig(pkg: LernaPackage, options: ExtractorOptions) { + const configObj: IConfigFile = createRawExtractorConfig(pkg, options); + const extractorConfig = ExtractorConfig.prepare({ + configObject: configObj, + configObjectFullPath: '', + packageJsonFullPath: pkg.manifestLocation, + }); + return extractorConfig; +} + +/** + * Invoke the extractor + * + * @param extractorConfig Resolved config + * @param options Extractor options + */ +function invokeExtractor( + extractorConfig: ExtractorConfig, + options: ExtractorOptions, +) { + const compilerState = CompilerState.create(extractorConfig, { + // typescriptCompilerFolder: options.typescriptCompilerFolder, + }); + + /* istanbul ignore if */ + if (options.dryRun) return; + + const extractorResult: ExtractorResult = Extractor.invoke(extractorConfig, { + typescriptCompilerFolder: options.typescriptCompilerFolder, + localBuild: true, + showVerboseMessages: !options.silent, + messageCallback: (message: ExtractorMessage) => { + if (message.messageId === ConsoleMessageId.ApiReportCreated) { + // This script deletes the outputs for a clean build, + // so don't issue a warning if the file gets created + message.logLevel = ExtractorLogLevel.None; + } + }, + compilerState, + }); + debug(extractorResult); +} diff --git a/packages/tsdocs/src/update-api-md-docs.ts b/packages/tsdocs/src/update-api-md-docs.ts new file mode 100644 index 000000000000..1d87142eef77 --- /dev/null +++ b/packages/tsdocs/src/update-api-md-docs.ts @@ -0,0 +1,165 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/tsdocs +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import * as debugFactory from 'debug'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import { + ApiDocsOptions, + DEFAULT_APIDOCS_EXTRACTION_PATH, + DEFAULT_APIDOCS_GENERATION_PATH, + getPackagesWithTsDocs, + getUnscopedPackageName, + LernaPackage, +} from './helper'; + +const debug = debugFactory('loopback:tsdocs'); + +/** + * Update markdown files generated by api-documenter to prepend Jekyll metadata + * and generate `apidocs/index.md`. + * + * @param options Options for api docs + */ +export async function updateApiDocs(options: ApiDocsOptions = {}) { + options = Object.assign( + { + rootDir: process.cwd(), + apiDocsGenerationPath: DEFAULT_APIDOCS_GENERATION_PATH, + apiDocsExtractionPath: DEFAULT_APIDOCS_EXTRACTION_PATH, + generateDefaultPackageDoc: true, + }, + options, + ); + const packages = await getPackagesWithTsDocs(options.rootDir); + + /* istanbul ignore if */ + if (!packages.length) return; + + await addJekyllMetadata(packages[0].rootPath, options); + await generateIndex(packages, options); +} + +/** + * Generate `index.md` for apidocs + * + * @param packages Lerna packages + * @param options Apidocs options + */ +async function generateIndex( + packages: LernaPackage[], + options: ApiDocsOptions, +) { + const lernaRootDir = packages[0].rootPath; + const apiDocs = [ + `--- +lang: en +title: 'API docs' +keywords: LoopBack 4.0, LoopBack 4 +sidebar: lb4_sidebar +permalink: /doc/en/lb4/apidocs.index.html +--- + +## API Docs`, + ]; + + for (const pkg of packages) { + const pkgName = getUnscopedPackageName(pkg.name); + apiDocs.push(`- [${pkg.name}](${pkgName}.md)`); + } + + apiDocs.push('\n'); + + /* istanbul ignore if */ + if (options.dryRun) { + console.log(apiDocs.join('\n')); + return; + } + + const apiDocsIndex = path.join( + lernaRootDir, + options.apiDocsGenerationPath!, + 'index.md', + ); + + /* istanbul ignore if */ + if (!options.silent) { + console.log('Generating %s', path.relative(process.cwd(), apiDocsIndex)); + } + await fs.ensureDir(path.resolve(apiDocsIndex, '..')); + await fs.writeFile(apiDocsIndex, apiDocs.join('\n')); +} + +/** + * Prepend Jekyll metadata to markdown files + * + * @param lernaRootDir Root directory for the monorepo + * @param options Options for api docs + */ +async function addJekyllMetadata( + lernaRootDir: string, + options: ApiDocsOptions, +) { + const apiDocsRoot = path.join(lernaRootDir, options.apiDocsGenerationPath!); + const apiFiles = await fs.readdir(apiDocsRoot); + for (const f of apiFiles) { + /* istanbul ignore if */ + if (!f.endsWith('.md') || f === 'index.md') continue; + const name = f.replace(/\.md$/, ''); + const isPackage = f.match(/^[^\.]+.md$/); + + /* istanbul ignore if */ + if (!options.silent) { + // Only print the package level name + if (isPackage) { + console.log('Updating *.md files for %s', name); + } + } + + const docFile = path.join(apiDocsRoot, f); + let doc = await fs.readFile(docFile, 'utf-8'); + + if (isPackage && options.generateDefaultPackageDoc) { + const modelFile = path.join( + path.join( + lernaRootDir, + options.apiDocsExtractionPath!, + `models/${name}.api.json`, + ), + ); + /** + * "kind": "Package", + * "canonicalReference": "@loopback/authentication", + * "docComment": "", + * "name": "@loopback/authentication", + */ + const model = await fs.readJson(modelFile, {encoding: 'utf-8'}); + debug('Package %s', name, model); + if (model.kind === 'Package' && !model.docComment) { + const pkgDoc = `[${ + model.canonicalReference + }](https://github.com/strongloop/loopback-next/tree/master/packages/${name})`; + doc = doc.replace( + `## ${name} package`, + `## ${name} package\n\n${pkgDoc}`, + ); + } + } + + doc = `--- +lang: en +title: 'API docs: ${name}' +keywords: LoopBack 4.0, LoopBack 4 +sidebar: lb4_sidebar +permalink: /doc/en/lb4/apidocs.${name}.html +--- + +${doc} +`; + if (!options.dryRun) { + await fs.writeFile(docFile, doc, 'utf-8'); + } + } +} diff --git a/packages/tsdocs/tsconfig.build.json b/packages/tsdocs/tsconfig.build.json new file mode 100644 index 000000000000..6e15e4be4f6f --- /dev/null +++ b/packages/tsdocs/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src"] +}