@coreui/astro-docs-api-generator 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/bin/coreui-docs-api.mjs +7 -0
- package/package.json +43 -0
- package/src/cli.mjs +71 -0
- package/src/react.mjs +27 -0
- package/src/vue.mjs +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 creativeLabs Łukasz Holeczek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @coreui/astro-docs-api-generator
|
|
2
|
+
|
|
3
|
+
Generates API documentation JSON for [@coreui/astro-docs](https://www.npmjs.com/package/@coreui/astro-docs).
|
|
4
|
+
It reads your component sources with `react-docgen-typescript` (React) or
|
|
5
|
+
`vue-docgen-api` (Vue) and emits one `<Name>.api.json` per component in a
|
|
6
|
+
consistent `ApiData` shape, consumed by the docs engine's `<Api>` component.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install --save-dev @coreui/astro-docs-api-generator
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
Point the CLI at a config module (defaults to `api.config.mjs` in the current
|
|
17
|
+
directory):
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
coreui-docs-api [path/to/api.config.mjs]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`api.config.mjs` lists the framework and the components to document:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
export default {
|
|
27
|
+
framework: 'react', // 'react' | 'vue'
|
|
28
|
+
outDir: 'src/api', // where the *.api.json files are written
|
|
29
|
+
importPackage: '@coreui/react', // shown in the generated import snippet
|
|
30
|
+
components: {
|
|
31
|
+
CButton: '../coreui-react/src/components/button/CButton.tsx',
|
|
32
|
+
CAlert: '../coreui-react/src/components/alert/CAlert.tsx',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Paths are resolved relative to the config file. The React extractor loads
|
|
38
|
+
eagerly; the Vue one lazily, so a React-only run never pulls in `vue-docgen-api`.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
[MIT](./LICENSE) © [CoreUI](https://coreui.io)
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coreui/astro-docs-api-generator",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "API documentation JSON generator for @coreui/astro-docs (React + Vue), emitting a consistent ApiData shape.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "The CoreUI Team (https://github.com/orgs/coreui/people)",
|
|
8
|
+
"homepage": "https://coreui.io",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/coreui/astro-docs.git",
|
|
12
|
+
"directory": "packages/api-generator"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/coreui/astro-docs/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"coreui",
|
|
19
|
+
"docs",
|
|
20
|
+
"api",
|
|
21
|
+
"react-docgen",
|
|
22
|
+
"vue-docgen"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"coreui-docs-api": "bin/coreui-docs-api.mjs"
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./src/cli.mjs",
|
|
32
|
+
"./react": "./src/react.mjs",
|
|
33
|
+
"./vue": "./src/vue.mjs"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"src",
|
|
37
|
+
"bin"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"react-docgen-typescript": "^2.4.0",
|
|
41
|
+
"vue-docgen-api": "^4.79.2"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Config-driven API generator. The consumer points it at a config module listing
|
|
2
|
+
// components -> source files plus the framework + import paths; this runs the
|
|
3
|
+
// matching extractor and writes one `<Name>.api.json` (the shared ApiData shape)
|
|
4
|
+
// per component. The React extractor is loaded eagerly; the Vue one lazily, so a
|
|
5
|
+
// React-only consumer doesn't pay for vue-docgen-api.
|
|
6
|
+
import { writeFileSync, mkdirSync } from 'node:fs'
|
|
7
|
+
import { resolve, dirname } from 'node:path'
|
|
8
|
+
import { pathToFileURL } from 'node:url'
|
|
9
|
+
import { extractReact } from './react.mjs'
|
|
10
|
+
|
|
11
|
+
function importSnippet({ name, rel, importPackage, importSrcBase }) {
|
|
12
|
+
if (!importPackage) return undefined
|
|
13
|
+
const lines = [`import { ${name} } from '${importPackage}'`]
|
|
14
|
+
// The deep source import (`…/src/components/…`) is opt-in via `importSrcBase`; most
|
|
15
|
+
// docs only need the package import (ESM tree-shakes it anyway).
|
|
16
|
+
if (importSrcBase) {
|
|
17
|
+
const srcPath = rel.replace(/^.*\/src\//, '').replace(/\.(tsx?|jsx?|vue)$/, '')
|
|
18
|
+
lines.push('// or', `import ${name} from '${importSrcBase}/${srcPath}'`)
|
|
19
|
+
}
|
|
20
|
+
return lines.join('\n')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function run(configPath = 'api.config.mjs') {
|
|
24
|
+
const configFile = resolve(process.cwd(), configPath)
|
|
25
|
+
const config = (await import(pathToFileURL(configFile).href)).default
|
|
26
|
+
const baseDir = dirname(configFile)
|
|
27
|
+
const outDir = resolve(baseDir, config.outDir ?? 'src/api')
|
|
28
|
+
mkdirSync(outDir, { recursive: true })
|
|
29
|
+
|
|
30
|
+
const extract =
|
|
31
|
+
config.framework === 'vue'
|
|
32
|
+
? (await import('./vue.mjs')).extractVue
|
|
33
|
+
: extractReact
|
|
34
|
+
|
|
35
|
+
let failed = 0
|
|
36
|
+
for (const [name, rel] of Object.entries(config.components)) {
|
|
37
|
+
const file = resolve(baseDir, rel)
|
|
38
|
+
let data
|
|
39
|
+
try {
|
|
40
|
+
data = await extract({ name, file })
|
|
41
|
+
} catch (error) {
|
|
42
|
+
// One unparseable component (e.g. an old-style `<Type>value` cast that the docgen
|
|
43
|
+
// parser rejects) must not abort the whole run — log it and keep going.
|
|
44
|
+
console.error(`Skipped ${name}: ${error.message} (${file})`)
|
|
45
|
+
failed += 1
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
if (!data) {
|
|
49
|
+
console.error(`No component found: ${name} (${file})`)
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const imports = importSnippet({
|
|
54
|
+
name,
|
|
55
|
+
rel,
|
|
56
|
+
importPackage: config.importPackage,
|
|
57
|
+
importSrcBase: config.importSrcBase,
|
|
58
|
+
})
|
|
59
|
+
if (imports) data.imports = imports
|
|
60
|
+
|
|
61
|
+
writeFileSync(resolve(outDir, `${name}.api.json`), JSON.stringify(data, null, 2))
|
|
62
|
+
const counts = [
|
|
63
|
+
`${data.props.length} props`,
|
|
64
|
+
data.events?.length ? `${data.events.length} events` : null,
|
|
65
|
+
data.slots?.length ? `${data.slots.length} slots` : null,
|
|
66
|
+
]
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.join(', ')
|
|
69
|
+
console.log(`generated ${name}.api.json (${counts})`)
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/react.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// React API extractor: react-docgen-typescript -> the shared ApiData shape.
|
|
2
|
+
// React documents everything as props, so there are no events/slots.
|
|
3
|
+
import { parse } from 'react-docgen-typescript'
|
|
4
|
+
|
|
5
|
+
export function extractReact({ name, file }) {
|
|
6
|
+
const components = parse(file, { shouldIncludePropTagMap: true })
|
|
7
|
+
const component = components.find((c) => c.displayName === name) || components[0]
|
|
8
|
+
if (!component) return null
|
|
9
|
+
|
|
10
|
+
const props = Object.entries(component.props)
|
|
11
|
+
.filter(([, info]) => {
|
|
12
|
+
const parentFile = info.parent?.fileName
|
|
13
|
+
return !parentFile || !parentFile.includes('@types/react')
|
|
14
|
+
})
|
|
15
|
+
.filter(([, info]) => info.tags?.ignore !== '')
|
|
16
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
17
|
+
.map(([propName, info]) => ({
|
|
18
|
+
name: propName,
|
|
19
|
+
type: info.type?.name?.includes('ReactElement') ? 'ReactElement' : info.type?.name || '',
|
|
20
|
+
default: info.defaultValue?.value ?? null,
|
|
21
|
+
description: info.description || '',
|
|
22
|
+
since: info.tags?.since || null,
|
|
23
|
+
deprecated: info.tags?.deprecated || null,
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
return { name: component.displayName, props }
|
|
27
|
+
}
|
package/src/vue.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Vue API extractor: vue-docgen-api -> the shared ApiData shape. Vue exports props,
|
|
2
|
+
// events and slots separately (events carry `properties`, slots carry `bindings`),
|
|
3
|
+
// all normalized here to the same shape React props use, so the engine's <Api>
|
|
4
|
+
// renders them uniformly.
|
|
5
|
+
import { parse } from 'vue-docgen-api'
|
|
6
|
+
|
|
7
|
+
const firstTag = (tags, key) => {
|
|
8
|
+
const entry = tags?.[key]
|
|
9
|
+
if (!entry) return null
|
|
10
|
+
return Array.isArray(entry) ? entry[0]?.description || entry[0]?.name || null : entry
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const typeName = (type) => {
|
|
14
|
+
if (!type) return ''
|
|
15
|
+
if (type.names) return type.names.join(', ')
|
|
16
|
+
if (type.name === 'union' && type.elements) return type.elements.map((e) => e.name).join(', ')
|
|
17
|
+
return type.name || ''
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function extractVue({ name, file }) {
|
|
21
|
+
// CoreUI Vue components are render-function `.ts` (no JSX), and some use the old
|
|
22
|
+
// angle-bracket cast `<Type>value` which the JSX-enabled babel parser rejects. Disable
|
|
23
|
+
// JSX so those `.ts` files parse.
|
|
24
|
+
const doc = await parse(file, { jsx: false })
|
|
25
|
+
|
|
26
|
+
const props = (doc.props || []).map((prop) => ({
|
|
27
|
+
name: prop.name,
|
|
28
|
+
type: typeName(prop.type),
|
|
29
|
+
default: prop.defaultValue?.value ?? null,
|
|
30
|
+
description: prop.description || '',
|
|
31
|
+
since: firstTag(prop.tags, 'since'),
|
|
32
|
+
deprecated: firstTag(prop.tags, 'deprecated'),
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
const events = (doc.events || []).map((event) => ({
|
|
36
|
+
name: event.name,
|
|
37
|
+
description: event.description || '',
|
|
38
|
+
properties: (event.properties || []).map((property) => ({
|
|
39
|
+
name: property.name,
|
|
40
|
+
type: typeName(property.type),
|
|
41
|
+
description: property.description || '',
|
|
42
|
+
})),
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
const slots = (doc.slots || []).map((slot) => ({
|
|
46
|
+
name: slot.name,
|
|
47
|
+
description: slot.description || '',
|
|
48
|
+
bindings: (slot.bindings || []).map((binding) => ({
|
|
49
|
+
name: binding.name,
|
|
50
|
+
type: typeName(binding.type),
|
|
51
|
+
description: binding.description || '',
|
|
52
|
+
})),
|
|
53
|
+
}))
|
|
54
|
+
|
|
55
|
+
return { name: name || doc.displayName, props, events, slots }
|
|
56
|
+
}
|