From d56b40b91e8f09e60791399adde459e6f263a691 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Sat, 10 Jul 2021 22:38:13 +0300 Subject: [PATCH] feat(analyzer): add --readme flag --- .../fixtures/cli-readme-flag/README.md | 36 +++++++++ .../cli-readme-flag/custom-elements.json | 76 +++++++++++++++++++ .../cli-readme-flag/fixture/README.md | 36 +++++++++ .../fixture/custom-elements.json | 76 +++++++++++++++++++ .../cli-readme-flag/package/README.md | 36 +++++++++ .../cli-readme-flag/package/my-element.js | 12 +++ .../cli-readme-flag/package/package.json | 4 + packages/analyzer/index.js | 35 +++++---- packages/analyzer/package.json | 1 + packages/analyzer/src/utils/cli.js | 66 +++++++++++----- packages/analyzer/test/cli.test.js | 55 ++++++++++++++ packages/analyzer/test/integration.test.js | 15 +++- plugins/readme/README.md | 14 ++-- plugins/readme/index.js | 12 +-- yarn.lock | 7 ++ 15 files changed, 430 insertions(+), 51 deletions(-) create mode 100644 packages/analyzer/fixtures/cli-readme-flag/README.md create mode 100644 packages/analyzer/fixtures/cli-readme-flag/custom-elements.json create mode 100644 packages/analyzer/fixtures/cli-readme-flag/fixture/README.md create mode 100644 packages/analyzer/fixtures/cli-readme-flag/fixture/custom-elements.json create mode 100644 packages/analyzer/fixtures/cli-readme-flag/package/README.md create mode 100644 packages/analyzer/fixtures/cli-readme-flag/package/my-element.js create mode 100644 packages/analyzer/fixtures/cli-readme-flag/package/package.json create mode 100644 packages/analyzer/test/cli.test.js diff --git a/packages/analyzer/fixtures/cli-readme-flag/README.md b/packages/analyzer/fixtures/cli-readme-flag/README.md new file mode 100644 index 00000000..659391ac --- /dev/null +++ b/packages/analyzer/fixtures/cli-readme-flag/README.md @@ -0,0 +1,36 @@ +# `my-element.js`: + +## class: `MyElement`, `my-element` + +### Superclass + +| Name | Module | Package | +| ------------- | ------ | ------- | +| `HTMLElement` | | | + +### Fields + +| Name | Privacy | Type | Default | Description | Inherited From | +| ----- | ------- | -------- | ------- | ----------- | -------------- | +| `foo` | | `string` | `'bar'` | | | + +
+ +## class: `MyWindow`, `my-window` + +### Superclass + +| Name | Module | Package | +| ------------- | ------ | ------- | +| `HTMLElement` | | | + +
+ +## Exports + +| Kind | Name | Declaration | Module | Package | +| --------------------------- | ------------ | ----------- | ------------- | ------- | +| `custom-element-definition` | `my-foo` | MyFoo | /foo.js | | +| `custom-element-definition` | `my-bar` | MyBar | | foo | +| `custom-element-definition` | `my-element` | MyElement | my-element.js | | +| `custom-element-definition` | `my-window` | MyWindow | my-element.js | | diff --git a/packages/analyzer/fixtures/cli-readme-flag/custom-elements.json b/packages/analyzer/fixtures/cli-readme-flag/custom-elements.json new file mode 100644 index 00000000..ec9872be --- /dev/null +++ b/packages/analyzer/fixtures/cli-readme-flag/custom-elements.json @@ -0,0 +1,76 @@ +{ + "schemaVersion": "1.0.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "my-element.js", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "MyElement", + "members": [ + { + "kind": "field", + "name": "foo", + "type": { + "text": "string" + }, + "default": "'bar'" + } + ], + "superclass": { + "name": "HTMLElement" + }, + "tagName": "my-element", + "customElement": true + }, + { + "kind": "class", + "description": "", + "name": "MyWindow", + "superclass": { + "name": "HTMLElement" + }, + "tagName": "my-window", + "customElement": true + } + ], + "exports": [ + { + "kind": "custom-element-definition", + "name": "my-foo", + "declaration": { + "name": "MyFoo", + "module": "/foo.js" + } + }, + { + "kind": "custom-element-definition", + "name": "my-bar", + "declaration": { + "name": "MyBar", + "package": "foo" + } + }, + { + "kind": "custom-element-definition", + "name": "my-element", + "declaration": { + "name": "MyElement", + "module": "my-element.js" + } + }, + { + "kind": "custom-element-definition", + "name": "my-window", + "declaration": { + "name": "MyWindow", + "module": "my-element.js" + } + } + ] + } + ] +} diff --git a/packages/analyzer/fixtures/cli-readme-flag/fixture/README.md b/packages/analyzer/fixtures/cli-readme-flag/fixture/README.md new file mode 100644 index 00000000..659391ac --- /dev/null +++ b/packages/analyzer/fixtures/cli-readme-flag/fixture/README.md @@ -0,0 +1,36 @@ +# `my-element.js`: + +## class: `MyElement`, `my-element` + +### Superclass + +| Name | Module | Package | +| ------------- | ------ | ------- | +| `HTMLElement` | | | + +### Fields + +| Name | Privacy | Type | Default | Description | Inherited From | +| ----- | ------- | -------- | ------- | ----------- | -------------- | +| `foo` | | `string` | `'bar'` | | | + +
+ +## class: `MyWindow`, `my-window` + +### Superclass + +| Name | Module | Package | +| ------------- | ------ | ------- | +| `HTMLElement` | | | + +
+ +## Exports + +| Kind | Name | Declaration | Module | Package | +| --------------------------- | ------------ | ----------- | ------------- | ------- | +| `custom-element-definition` | `my-foo` | MyFoo | /foo.js | | +| `custom-element-definition` | `my-bar` | MyBar | | foo | +| `custom-element-definition` | `my-element` | MyElement | my-element.js | | +| `custom-element-definition` | `my-window` | MyWindow | my-element.js | | diff --git a/packages/analyzer/fixtures/cli-readme-flag/fixture/custom-elements.json b/packages/analyzer/fixtures/cli-readme-flag/fixture/custom-elements.json new file mode 100644 index 00000000..ec9872be --- /dev/null +++ b/packages/analyzer/fixtures/cli-readme-flag/fixture/custom-elements.json @@ -0,0 +1,76 @@ +{ + "schemaVersion": "1.0.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "my-element.js", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "MyElement", + "members": [ + { + "kind": "field", + "name": "foo", + "type": { + "text": "string" + }, + "default": "'bar'" + } + ], + "superclass": { + "name": "HTMLElement" + }, + "tagName": "my-element", + "customElement": true + }, + { + "kind": "class", + "description": "", + "name": "MyWindow", + "superclass": { + "name": "HTMLElement" + }, + "tagName": "my-window", + "customElement": true + } + ], + "exports": [ + { + "kind": "custom-element-definition", + "name": "my-foo", + "declaration": { + "name": "MyFoo", + "module": "/foo.js" + } + }, + { + "kind": "custom-element-definition", + "name": "my-bar", + "declaration": { + "name": "MyBar", + "package": "foo" + } + }, + { + "kind": "custom-element-definition", + "name": "my-element", + "declaration": { + "name": "MyElement", + "module": "my-element.js" + } + }, + { + "kind": "custom-element-definition", + "name": "my-window", + "declaration": { + "name": "MyWindow", + "module": "my-element.js" + } + } + ] + } + ] +} diff --git a/packages/analyzer/fixtures/cli-readme-flag/package/README.md b/packages/analyzer/fixtures/cli-readme-flag/package/README.md new file mode 100644 index 00000000..3527bdb4 --- /dev/null +++ b/packages/analyzer/fixtures/cli-readme-flag/package/README.md @@ -0,0 +1,36 @@ +## `my-element.js`: + +### class: `MyElement`, `my-element` + +#### Superclass + +| Name | Module | Package | +| ------------- | ------ | ------- | +| `HTMLElement` | | | + +#### Fields + +| Name | Privacy | Type | Default | Description | Inherited From | +| ----- | ------- | -------- | ------- | ----------- | -------------- | +| `foo` | | `string` | `'bar'` | | | + +
+ +### class: `MyWindow`, `my-window` + +#### Superclass + +| Name | Module | Package | +| ------------- | ------ | ------- | +| `HTMLElement` | | | + +
+ +### Exports + +| Kind | Name | Declaration | Module | Package | +| --------------------------- | ------------ | ----------- | ------------- | ------- | +| `custom-element-definition` | `my-foo` | MyFoo | /foo.js | | +| `custom-element-definition` | `my-bar` | MyBar | | foo | +| `custom-element-definition` | `my-element` | MyElement | my-element.js | | +| `custom-element-definition` | `my-window` | MyWindow | my-element.js | | diff --git a/packages/analyzer/fixtures/cli-readme-flag/package/my-element.js b/packages/analyzer/fixtures/cli-readme-flag/package/my-element.js new file mode 100644 index 00000000..01fc0c90 --- /dev/null +++ b/packages/analyzer/fixtures/cli-readme-flag/package/my-element.js @@ -0,0 +1,12 @@ +import {MyFoo} from './foo.js'; +import {MyBar} from 'foo'; + +class MyElement extends HTMLElement { + foo = 'bar'; +} +class MyWindow extends HTMLElement {} + +customElements.define('my-foo', MyFoo); +customElements.define('my-bar', MyBar); +customElements.define('my-element', MyElement); +window.customElements.define('my-window', MyWindow); \ No newline at end of file diff --git a/packages/analyzer/fixtures/cli-readme-flag/package/package.json b/packages/analyzer/fixtures/cli-readme-flag/package/package.json new file mode 100644 index 00000000..93b73a80 --- /dev/null +++ b/packages/analyzer/fixtures/cli-readme-flag/package/package.json @@ -0,0 +1,4 @@ +{ + "name": "my-el", + "customElements": "../custom-elements.json" +} diff --git a/packages/analyzer/index.js b/packages/analyzer/index.js index 284205b3..7ea9addf 100755 --- a/packages/analyzer/index.js +++ b/packages/analyzer/index.js @@ -9,10 +9,11 @@ import chokidar from 'chokidar'; import debounce from 'debounce'; import { create } from './src/create.js'; -import { - getUserConfig, - getCliConfig, - addFrameworkPlugins, +import { + getUserConfig, + getCliConfig, + addFrameworkPlugins, + addReadmePlugin, addCustomElementsPropertyToPackageJson, mergeGlobsAndExcludes, timestamp, @@ -24,7 +25,7 @@ import { const mainDefinitions = [{ name: 'command', defaultOption: true }]; const mainOptions = commandLineArgs(mainDefinitions, { stopAtFirstUnknown: true }); const argv = mainOptions._unknown || []; - + if (mainOptions.command === 'analyze') { const cliConfig = getCliConfig(argv) @@ -41,20 +42,20 @@ import { async function run() { /** * Create modules for `create()` - * + * * By default, the analyzer doesn't actually compile a users source code with the TS compiler * API. This means that by default, the typeChecker is not available in plugins. - * + * * If users want to use the typeChecker, they can do so by adding a `overrideModuleCreation` property * in their custom-elements-manifest.config.js. `overrideModuleCreation` is a function that should return * an array of sourceFiles. */ - const modules = userConfig?.overrideModuleCreation + const modules = userConfig?.overrideModuleCreation ? userConfig.overrideModuleCreation({ts, globs}) : globs.map(glob => { const relativeModulePath = path.relative(process.cwd(), glob); const source = fs.readFileSync(relativeModulePath).toString(); - + return ts.createSourceFile( relativeModulePath, source, @@ -62,10 +63,14 @@ import { true, ); }); - + let plugins = await addFrameworkPlugins(mergedOptions); - plugins = [...plugins, ...(userConfig?.plugins || [])]; - + plugins = [ + ...plugins, + ...await addReadmePlugin(mergedOptions), + ...(userConfig?.plugins || []) + ]; + /** * Create the manifest */ @@ -93,9 +98,9 @@ import { */ if(mergedOptions.watch) { const fileWatcher = chokidar.watch(globs); - + const onChange = debounce(run, 100); - + fileWatcher.addListener('change', onChange); fileWatcher.addListener('unlink', onChange); } @@ -108,4 +113,4 @@ import { } else { console.log(MENU); } -})(); \ No newline at end of file +})(); diff --git a/packages/analyzer/package.json b/packages/analyzer/package.json index 950f947d..591872cc 100644 --- a/packages/analyzer/package.json +++ b/packages/analyzer/package.json @@ -43,6 +43,7 @@ "command-line-args": "^5.1.2", "comment-parser": "^1.1.5", "custom-elements-manifest": "^1.0.0", + "cem-plugin-readme": "0.1.2", "debounce": "^1.2.1", "globby": "^11.0.4", "typescript": "^4.3.2" diff --git a/packages/analyzer/src/utils/cli.js b/packages/analyzer/src/utils/cli.js index e1bbaee2..18c06f6d 100644 --- a/packages/analyzer/src/utils/cli.js +++ b/packages/analyzer/src/utils/cli.js @@ -5,10 +5,10 @@ import commandLineArgs from 'command-line-args'; import { has } from './index.js'; const IGNORE = [ - '!node_modules/**/*.*', - '!bower_components/**/*.*', - '!**/*.test.{js,ts}', - '!**/*.suite.{js,ts}', + '!node_modules/**/*.*', + '!bower_components/**/*.*', + '!**/*.test.{js,ts}', + '!**/*.suite.{js,ts}', '!**/*.config.{js,ts}' ]; @@ -54,7 +54,11 @@ export const DEFAULTS = { litelement: false, stencil: false, fast: false, - catalyst: false + catalyst: false, + readme: true, + header: '', + footer: '', + headingOffset: 0, } export function getCliConfig(argv) { @@ -68,8 +72,12 @@ export function getCliConfig(argv) { { name: 'stencil', type: Boolean }, { name: 'fast', type: Boolean }, { name: 'catalyst', type: Boolean }, + { name: 'readme', type: Boolean }, + { name: 'header', type: String }, + { name: 'footer', type: String }, + { name: 'headingOffset', type: Number }, ]; - + return commandLineArgs(optionDefinitions, { argv }); } @@ -103,6 +111,22 @@ export function timestamp() { return date.toLocaleTimeString(); } +export async function addReadmePlugin({ readme, ...options }) { + if (!readme) + return []; + else { + const { headingOffset, header, footer, outdir } = options; + return [ + await import('cem-plugin-readme').then(m => m.readmePlugin({ + footer, + from: outdir, + header, + headingOffset, + })), + ]; + } +} + export function addCustomElementsPropertyToPackageJson(outdir) { const packageJsonPath = `${process.cwd()}${path.sep}package.json`; const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString()); @@ -123,19 +147,23 @@ export const MENU = ` @custom-elements-manifest/analyzer Available commands: - | Command/option | Type | Description | Example | - | ---------------- | ---------- | ----------------------------------------------------------- | --------------------- | - | analyze | | Analyze your components | | - | --globs | string[] | Globs to analyze | \`--globs "foo.js"\` | - | --exclude | string[] | Globs to exclude | \`--exclude "foo.js"\` | - | --outdir | string | Directory to output the Manifest to | \`--outdir dist\` | - | --watch | boolean | Enables watch mode, generates a new manifest on file change | \`--watch\` | - | --dev | boolean | Enables extra logging for debugging | \`--dev\` | - | --litelement | boolean | Enable special handling for LitElement syntax | \`--litelement\` | - | --fast | boolean | Enable special handling for FASTElement syntax | \`--fast\` | - | --stencil | boolean | Enable special handling for Stencil syntax | \`--stencil\` | - | --catalyst | boolean | Enable special handling for Catalyst syntax | \`--catalyst\` | + | Command/option | Type | Description | Example | + | ---------------- | ---------- | ----------------------------------------------------------- | ---------------------------------- | + | analyze | | Analyze your components | | + | --globs | string[] | Globs to analyze | \`--globs "foo.js"\` | + | --exclude | string[] | Globs to exclude | \`--exclude "foo.js"\` | + | --outdir | string | Directory to output the Manifest to | \`--outdir dist\` | + | --watch | boolean | Enables watch mode, generates a new manifest on file change | \`--watch\` | + | --dev | boolean | Enables extra logging for debugging | \`--dev\` | + | --litelement | boolean | Enable special handling for LitElement syntax | \`--litelement\` | + | --fast | boolean | Enable special handling for FASTElement syntax | \`--fast\` | + | --stencil | boolean | Enable special handling for Stencil syntax | \`--stencil\` | + | --catalyst | boolean | Enable special handling for Catalyst syntax | \`--catalyst\` | + | --readme | boolean | Create a README.md file | \`--readme\` | + | --header | string | Markdown header file for the README.md | \`--header README.head.md\` | + | --footer | string | Markdown footer file for the README.md | \`--footer README.foot.md\` | + | --headingOffset | number | Number of levels to offset headings in the README.md | \`--headingOffset 1\` | Example: custom-elements-manifest analyze --litelement --globs "**/*.js" --exclude "foo.js" "bar.js" -` \ No newline at end of file +` diff --git a/packages/analyzer/test/cli.test.js b/packages/analyzer/test/cli.test.js new file mode 100644 index 00000000..efda10d9 --- /dev/null +++ b/packages/analyzer/test/cli.test.js @@ -0,0 +1,55 @@ +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; +import path from 'path'; +import fs from 'fs'; +import * as child_process from 'child_process'; +import { promisify } from 'util' + +const exec = promisify(child_process.exec); + +const fixturesDir = path.join(process.cwd(), 'fixtures'); +let testCases = fs.readdirSync(fixturesDir).filter(x => x.startsWith('cli-')); + +const runSingle = testCases.find(_case => _case.startsWith('+')); +if (runSingle) { + testCases = [runSingle]; +} + +testCases.forEach(testCase => { + test(`Testcase ${testCase}`, async () => { + // skips tests + if (testCase.startsWith('-')) { + assert.equal(true, true); + return; + } + + const fixturePath = path.join(fixturesDir, testCase, 'fixture'); + const fixture = JSON.parse(fs.readFileSync(path.join(fixturePath, 'custom-elements.json'), 'utf-8')); + + const packagePath = path.join(fixturesDir, testCase, 'package'); + + const outputPath = path.join(fixturesDir, testCase); + + try { + const { stdout, stderr } = await exec('node ../../../index.js analyze --readme --outdir ..', { cwd: packagePath }); + console.log(stdout) + if (stderr) + throw new Error(stderr); + } catch (e) { + console.error(e); // should contain code (exit code) and signal (that caused the termination). + } + + const result = JSON.parse(fs.readFileSync(path.join(outputPath, 'custom-elements.json'), 'utf-8')); + + assert.equal(result, fixture); + + if (testCase.includes('readme')) { + assert.equal( + fs.readFileSync(path.join(outputPath, 'README.md'), 'utf8'), + fs.readFileSync(path.join(fixturePath, 'README.md'), 'utf8'), + ); + } + }); +}); + +test.run(); diff --git a/packages/analyzer/test/integration.test.js b/packages/analyzer/test/integration.test.js index e42e4dc3..1eed70b1 100644 --- a/packages/analyzer/test/integration.test.js +++ b/packages/analyzer/test/integration.test.js @@ -9,7 +9,7 @@ import ts from 'typescript'; import { create } from '../src/create.js'; const fixturesDir = path.join(process.cwd(), 'fixtures'); -let testCases = fs.readdirSync(fixturesDir); +let testCases = fs.readdirSync(fixturesDir).filter(x => !x.startsWith('cli-')); const runSingle = testCases.find(_case => _case.startsWith('+')); if (runSingle) { @@ -37,7 +37,7 @@ testCases.forEach(testCase => { .map(glob => { const relativeModulePath = `.${path.sep}${path.relative(process.cwd(), glob)}`; const source = fs.readFileSync(relativeModulePath).toString(); - + return ts.createSourceFile( relativeModulePath, source, @@ -52,13 +52,20 @@ testCases.forEach(testCase => { const config = await import(manifestPathFileURL); plugins = [...config.default.plugins]; } catch {} - + const result = create({modules, plugins}); fs.writeFileSync(outputPath, JSON.stringify(result, null, 2)); assert.equal(result, fixture); + + if (testCase === 'readme-flag') + assert.equal( + fs.readFileSync(path.join(path.dirname(outputPath), 'README.md')), + fs.readFileSync(path.join(path.dirname(fixturePath), 'README.md')), + '--readme flag' + ) }); }); -test.run(); \ No newline at end of file +test.run(); diff --git a/plugins/readme/README.md b/plugins/readme/README.md index 82ef29e8..4ed9033c 100644 --- a/plugins/readme/README.md +++ b/plugins/readme/README.md @@ -4,13 +4,13 @@ Generates a `README.md` file for a custom element package ## Options -| Option | Type | Description | Default | -| ------------- | ------- | ------------------------------------------------------ | ----------------------- | -| from | string | absolute path to package root | 2 dirs above the plugin | -| to | string | relative path from package root | `'README.md'` | -| headingOffset | integer | number of levels to offset generated markdown headings | `1` | -| header | string | relative path to header file | `undefined` | -| footer | string | relative path to footer file | `undefined` | +| Option | Type | Description | Default | +| ------------- | ------- | ------------------------------------------------------ | ------------- | +| from | string | Path to package root (relative to current workign dir) | `.` | +| to | string | relative path from package root | `'README.md'` | +| headingOffset | integer | number of levels to offset generated markdown headings | `1` | +| header | string | relative path to header file | `undefined` | +| footer | string | relative path to footer file | `undefined` | ## Example diff --git a/plugins/readme/index.js b/plugins/readme/index.js index a262f86d..e701f594 100644 --- a/plugins/readme/index.js +++ b/plugins/readme/index.js @@ -1,12 +1,10 @@ import { customElementsManifestToMarkdown } from '@custom-elements-manifest/to-markdown'; - import { mkdirSync, readFileSync, writeFileSync } from 'fs'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; +import { dirname, isAbsolute, join, resolve } from 'path'; /** * @typedef {import('@custom-elements-manifest/to-markdown').Options} Options - * @property {string} [from] absolute path to package root + * @property {string} [from] path to package root, relative to current working directory * @property {string} [to="README.md"] relative path from package root to output file * @property {number} [headingOffset=1] offset for markdown heading level * @property {string} [header] relative path to header file @@ -21,7 +19,7 @@ export function readmePlugin(options) { const { header, footer, - from = join(dirname(fileURLToPath(import.meta.url)), '..', '..'), + from = '.', to = 'README.md', headingOffset = 1, exportKinds, @@ -29,7 +27,9 @@ export function readmePlugin(options) { return { name: 'readme', packageLinkPhase({ customElementsManifest }) { - const outPath = join(from, to); + const absPath = isAbsolute(from) ? from : resolve(process.cwd(), from); + + const outPath = join(absPath, to); try { const head = header && readFileSync(join(from, header)); diff --git a/yarn.lock b/yarn.lock index 0efb13ff..578f578d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -298,6 +298,13 @@ ccount@^2.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.0.tgz#3d6fb55803832766a24c6f339abc507297eb5d25" integrity sha512-VOR0NWFYX65n9gELQdcpqsie5L5ihBXuZGAgaPEp/U7IOSjnPMEH6geE+2f6lcekaNEfWzAHS45mPvSo5bqsUA== +cem-plugin-readme@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/cem-plugin-readme/-/cem-plugin-readme-0.1.2.tgz#65a4d6c8973bb9c42e2a68fb3053a353e5475379" + integrity sha512-K28QXc6nG22p+aHzdPnQQi3uM02HLkbERCqDCwLT11mmQMr+WS5sbXwnR4YYUhPc/kUbASUu+OoFLlgmQ+7RgQ== + dependencies: + "@custom-elements-manifest/to-markdown" "^0.0.9" + chalk@^2.0.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"