@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 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)
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { run } from '../src/cli.mjs'
3
+
4
+ run(process.argv[2]).catch((error) => {
5
+ console.error(error)
6
+ process.exit(1)
7
+ })
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
+ }