@vibecuting/component-project-helper 0.1.16 → 0.1.17

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.
@@ -1,112 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
-
4
- import { discoverComponentProjectComponents } from './discover-components.mjs'
5
- import { ensureComponentProjectSkillsScript } from './package-json.mjs'
6
- import {
7
- resolveComponentProjectGeneratedDocsDir,
8
- resolveComponentProjectGeneratedManifestPath,
9
- resolveComponentProjectInstallRoot,
10
- } from './runtime-root.mjs'
11
- import { renderComponentProjectMarkdown } from './render-markdown.mjs'
12
- import { ComponentProjectGeneratedManifestSchema } from './schemas.mjs'
13
-
14
- const supportsColor =
15
- Boolean(process.stdout.isTTY) && process.env.NO_COLOR !== '1' && process.env.CI !== 'true'
16
-
17
- const color = {
18
- cyan: (value) => (supportsColor ? `\u001b[36m${value}\u001b[0m` : value),
19
- green: (value) => (supportsColor ? `\u001b[32m${value}\u001b[0m` : value),
20
- yellow: (value) => (supportsColor ? `\u001b[33m${value}\u001b[0m` : value),
21
- dim: (value) => (supportsColor ? `\u001b[2m${value}\u001b[0m` : value),
22
- }
23
-
24
- async function ensureDirectory(dir) {
25
- await fs.mkdir(dir, { recursive: true })
26
- }
27
-
28
- async function removeStaleGeneratedFiles(manifestPath, nextFiles) {
29
- try {
30
- const previous = ComponentProjectGeneratedManifestSchema.parse(
31
- JSON.parse(await fs.readFile(manifestPath, 'utf8')),
32
- )
33
- const staleFiles = previous.files.filter((filePath) => !nextFiles.includes(filePath))
34
- await Promise.all(staleFiles.map((filePath) => fs.rm(filePath, { force: true })))
35
- } catch (error) {
36
- if (error?.code !== 'ENOENT') {
37
- throw error
38
- }
39
- }
40
- }
41
-
42
- export async function runComponentProjectPostinstall(projectRoot = resolveComponentProjectInstallRoot()) {
43
- const docsDir = resolveComponentProjectGeneratedDocsDir(projectRoot)
44
- const manifestPath = resolveComponentProjectGeneratedManifestPath(projectRoot)
45
- const components = await discoverComponentProjectComponents(projectRoot)
46
-
47
- console.log(color.cyan('[component-project-helper] postinstall start'))
48
- console.log(color.dim(`[component-project-helper] project root: ${projectRoot}`))
49
- console.log(color.dim(`[component-project-helper] docs dir: ${docsDir}`))
50
- console.log(color.dim(`[component-project-helper] manifest path: ${manifestPath}`))
51
- console.log(
52
- color.green(
53
- `[component-project-helper] discovered ${components.length} component(s): ${
54
- components.map((component) => component.name).join(', ') || '(none)'
55
- }`,
56
- ),
57
- )
58
-
59
- await ensureDirectory(docsDir)
60
-
61
- const files = []
62
- for (const component of components) {
63
- const filePath = path.join(docsDir, `${component.name}.md`)
64
- await fs.writeFile(filePath, renderComponentProjectMarkdown(component), 'utf8')
65
- files.push(filePath)
66
- console.log(
67
- color.green(
68
- `[component-project-helper] wrote ${path.relative(projectRoot, filePath)} <- ${component.sourceFile}`,
69
- ),
70
- )
71
- }
72
-
73
- console.log(
74
- color.yellow(
75
- `[component-project-helper] cleaning stale docs relative to manifest ${path.relative(
76
- projectRoot,
77
- manifestPath,
78
- )}`,
79
- ),
80
- )
81
- await removeStaleGeneratedFiles(manifestPath, files)
82
- await ensureComponentProjectSkillsScript(projectRoot)
83
- console.log(color.green('[component-project-helper] ensured update-component-skills script'))
84
- console.log('[component-project-helper] ensured stale docs cleaned')
85
-
86
- const manifest = ComponentProjectGeneratedManifestSchema.parse({
87
- projectRoot,
88
- generatedAt: new Date().toISOString(),
89
- docsDir,
90
- files,
91
- })
92
- await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
93
- console.log(
94
- color.cyan(
95
- `[component-project-helper] postinstall complete, manifest files: ${files.length}`,
96
- ),
97
- )
98
- }
99
-
100
- async function isDirectRun() {
101
- if (!process.argv[1]) {
102
- return false
103
- }
104
-
105
- const entryPath = await fs.realpath(process.argv[1])
106
- const currentPath = await fs.realpath(new URL(import.meta.url).pathname)
107
- return entryPath === currentPath
108
- }
109
-
110
- if (await isDirectRun()) {
111
- await runComponentProjectPostinstall()
112
- }
@@ -1,45 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import { fileURLToPath } from 'node:url'
3
-
4
- import { resolveComponentProjectGeneratedManifestPath } from './runtime-root.mjs'
5
- import { ComponentProjectGeneratedManifestSchema } from './schemas.mjs'
6
- import { removeComponentProjectSkillsScript } from './package-json.mjs'
7
-
8
- export async function runComponentProjectPreuninstall(
9
- projectRoot = process.env.INIT_CWD ?? process.env.npm_config_local_prefix ?? process.cwd(),
10
- ) {
11
- const manifestPath = resolveComponentProjectGeneratedManifestPath(projectRoot)
12
-
13
- try {
14
- const manifest = ComponentProjectGeneratedManifestSchema.parse(
15
- JSON.parse(await fs.readFile(manifestPath, 'utf8')),
16
- )
17
-
18
- for (const filePath of manifest.files) {
19
- await fs.rm(filePath, { force: true })
20
- }
21
-
22
- await fs.rm(manifestPath, { force: true })
23
- await fs.rm(manifest.docsDir, { recursive: true, force: true })
24
- } catch (error) {
25
- if (error?.code !== 'ENOENT') {
26
- throw error
27
- }
28
- }
29
-
30
- await removeComponentProjectSkillsScript(projectRoot)
31
- }
32
-
33
- async function isDirectRun() {
34
- if (!process.argv[1]) {
35
- return false
36
- }
37
-
38
- const entryPath = await fs.realpath(process.argv[1])
39
- const currentPath = await fs.realpath(fileURLToPath(import.meta.url))
40
- return entryPath === currentPath
41
- }
42
-
43
- if (await isDirectRun()) {
44
- await runComponentProjectPreuninstall()
45
- }
@@ -1,36 +0,0 @@
1
- import { ComponentProjectComponentMetadataSchema } from './schemas.mjs'
2
-
3
- function escapeInline(value) {
4
- return value.replaceAll('`', '\\`')
5
- }
6
-
7
- export function renderComponentProjectMarkdown(input) {
8
- const metadata = ComponentProjectComponentMetadataSchema.parse(input)
9
- const tags = metadata.tags.length > 0 ? metadata.tags : ['component']
10
-
11
- return [
12
- '---',
13
- `name: ${metadata.name}`,
14
- `description: ${metadata.description}`,
15
- `sourceFile: ${metadata.sourceFile}`,
16
- `aspectRatio: ${metadata.aspectRatio}`,
17
- `sceneType: ${metadata.sceneType ?? ''}`,
18
- `propsTypeName: ${metadata.propsTypeName ?? ''}`,
19
- 'tags:',
20
- ...tags.map((tag) => ` - ${tag}`),
21
- '---',
22
- '',
23
- `# ${metadata.name}`,
24
- '',
25
- metadata.description,
26
- '',
27
- '## Metadata',
28
- '',
29
- `- Source file: \`${escapeInline(metadata.sourceFile)}\``,
30
- `- Aspect ratio: \`${escapeInline(metadata.aspectRatio)}\``,
31
- `- Scene type: \`${escapeInline(metadata.sceneType ?? 'unknown')}\``,
32
- `- Props type: \`${escapeInline(metadata.propsTypeName ?? 'unknown')}\``,
33
- `- Tags: ${tags.map((tag) => `\`${escapeInline(tag)}\``).join(', ')}`,
34
- '',
35
- ].join('\n')
36
- }
@@ -1,63 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
-
4
- export const componentProjectSkillsScriptName = 'update-component-skills' as const
5
- export const componentProjectSkillsScriptCommand =
6
- 'node ./node_modules/@vibecuting/component-project-helper/scripts/postinstall.mjs' as const
7
-
8
- async function readPackageJson(projectRoot: string): Promise<Record<string, unknown>> {
9
- const packageJsonPath = path.join(projectRoot, 'package.json')
10
- try {
11
- return JSON.parse(await fs.readFile(packageJsonPath, 'utf8'))
12
- } catch (error) {
13
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
14
- return undefined as never
15
- }
16
-
17
- throw error
18
- }
19
- }
20
-
21
- async function writePackageJson(
22
- projectRoot: string,
23
- packageJson: Record<string, unknown>,
24
- ): Promise<void> {
25
- const packageJsonPath = path.join(projectRoot, 'package.json')
26
- await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8')
27
- }
28
-
29
- export async function ensureComponentProjectSkillsScript(projectRoot: string): Promise<void> {
30
- const packageJson = await readPackageJson(projectRoot)
31
- if (!packageJson) {
32
- return
33
- }
34
-
35
- const scripts = (packageJson.scripts as Record<string, string> | undefined) ?? {}
36
-
37
- if (scripts[componentProjectSkillsScriptName] !== componentProjectSkillsScriptCommand) {
38
- packageJson.scripts = {
39
- ...scripts,
40
- [componentProjectSkillsScriptName]: componentProjectSkillsScriptCommand,
41
- }
42
- await writePackageJson(projectRoot, packageJson)
43
- }
44
- }
45
-
46
- export async function removeComponentProjectSkillsScript(projectRoot: string): Promise<void> {
47
- const packageJson = await readPackageJson(projectRoot)
48
- if (!packageJson) {
49
- return
50
- }
51
-
52
- const scripts = (packageJson.scripts as Record<string, string> | undefined) ?? {}
53
-
54
- if (scripts[componentProjectSkillsScriptName] !== componentProjectSkillsScriptCommand) {
55
- return
56
- }
57
-
58
- const nextScripts = { ...scripts }
59
- delete nextScripts[componentProjectSkillsScriptName]
60
- packageJson.scripts = nextScripts
61
-
62
- await writePackageJson(projectRoot, packageJson)
63
- }
@@ -1,114 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
- import { tmpdir } from 'node:os'
4
-
5
- import { expect, test } from 'vitest'
6
-
7
- import { runComponentProjectPostinstall } from './postinstall'
8
- import { runComponentProjectPreuninstall } from './preuninstall'
9
-
10
- async function createFixtureProjectRoot(): Promise<string> {
11
- const projectRoot = await fs.mkdtemp(path.join(tmpdir(), 'component-project-helper-'))
12
- await fs.mkdir(path.join(projectRoot, 'src', 'components', 'nested'), { recursive: true })
13
- await fs.writeFile(
14
- path.join(projectRoot, 'package.json'),
15
- JSON.stringify(
16
- {
17
- name: 'component-project-fixture',
18
- private: true,
19
- scripts: {
20
- build: 'echo build',
21
- },
22
- },
23
- null,
24
- 2,
25
- ),
26
- 'utf8',
27
- )
28
-
29
- await fs.writeFile(
30
- path.join(projectRoot, 'src', 'components', 'SceneFrame.tsx'),
31
- `
32
- @VideoComponent({
33
- name: 'SceneFrame',
34
- description: 'Wraps the scene content in a consistent frame',
35
- sourceFile: 'src/components/SceneFrame.tsx',
36
- aspectRatio: '16:9',
37
- sceneType: 'scene',
38
- tags: ['layout', 'frame'],
39
- propsTypeName: 'SceneFrameProps',
40
- })
41
- export const SceneFrame = () => null
42
- `,
43
- 'utf8',
44
- )
45
-
46
- await fs.writeFile(
47
- path.join(projectRoot, 'src', 'components', 'nested', 'SceneTitle.tsx'),
48
- `
49
- @VideoComponent({
50
- name: 'SceneTitle',
51
- description: 'Renders the title block for scene hero content',
52
- sourceFile: 'src/components/nested/SceneTitle.tsx',
53
- aspectRatio: '9:16',
54
- sceneType: 'intro',
55
- tags: ['title'],
56
- propsTypeName: 'SceneTitleProps',
57
- })
58
- export const SceneTitle = () => null
59
- `,
60
- 'utf8',
61
- )
62
-
63
- return projectRoot
64
- }
65
-
66
- test('postinstall generates markdown and preuninstall removes it', async () => {
67
- const projectRoot = await createFixtureProjectRoot()
68
- const docsDir = path.join(
69
- projectRoot,
70
- '.agents',
71
- 'skills',
72
- 'project-allow-component',
73
- 'components',
74
- )
75
- const manifestPath = path.join(
76
- projectRoot,
77
- '.agents',
78
- 'skills',
79
- 'project-allow-component',
80
- 'component-project-helper.manifest.json',
81
- )
82
-
83
- await runComponentProjectPostinstall(projectRoot)
84
-
85
- await expect(fs.stat(path.join(docsDir, 'SceneFrame.md'))).resolves.toBeDefined()
86
- await expect(fs.stat(path.join(docsDir, 'SceneTitle.md'))).resolves.toBeDefined()
87
- await expect(fs.stat(manifestPath)).resolves.toBeDefined()
88
- await expect(await fs.readFile(path.join(docsDir, 'SceneTitle.md'), 'utf8')).toContain(
89
- 'src/components/nested/SceneTitle.tsx',
90
- )
91
- await expect(await fs.readFile(path.join(docsDir, 'SceneTitle.md'), 'utf8')).toContain(
92
- '9:16',
93
- )
94
- await expect(
95
- JSON.parse(await fs.readFile(path.join(projectRoot, 'package.json'), 'utf8')).scripts[
96
- 'update-component-skills'
97
- ],
98
- ).toBe('node ./node_modules/@vibecuting/component-project-helper/scripts/postinstall.mjs')
99
-
100
- await runComponentProjectPreuninstall(projectRoot)
101
-
102
- await expect(fs.stat(path.join(docsDir, 'SceneFrame.md'))).rejects.toMatchObject({
103
- code: 'ENOENT',
104
- })
105
- await expect(fs.stat(path.join(docsDir, 'SceneTitle.md'))).rejects.toMatchObject({
106
- code: 'ENOENT',
107
- })
108
- await expect(fs.stat(manifestPath)).rejects.toMatchObject({ code: 'ENOENT' })
109
- await expect(
110
- JSON.parse(await fs.readFile(path.join(projectRoot, 'package.json'), 'utf8')).scripts[
111
- 'update-component-skills'
112
- ],
113
- ).toBeUndefined()
114
- })
@@ -1,127 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
-
5
- import { ComponentProjectGeneratedManifestSchema } from '../schemas'
6
- import { discoverComponentProjectComponents } from '../discovery'
7
- import { renderComponentProjectMarkdown } from '../markdown'
8
- import {
9
- resolveComponentProjectGeneratedDocsDir,
10
- resolveComponentProjectGeneratedManifestPath,
11
- resolveComponentProjectInstallRoot,
12
- } from '../runtime'
13
- import { ensureComponentProjectSkillsScript } from './package-json'
14
-
15
- const supportsColor =
16
- Boolean(process.stdout.isTTY) && process.env.NO_COLOR !== '1' && process.env.CI !== 'true'
17
-
18
- const color = {
19
- cyan: (value: string): string => (supportsColor ? `\u001b[36m${value}\u001b[0m` : value),
20
- green: (value: string): string => (supportsColor ? `\u001b[32m${value}\u001b[0m` : value),
21
- yellow: (value: string): string => (supportsColor ? `\u001b[33m${value}\u001b[0m` : value),
22
- dim: (value: string): string => (supportsColor ? `\u001b[2m${value}\u001b[0m` : value),
23
- }
24
-
25
- async function ensureDirectory(dir: string): Promise<void> {
26
- await fs.mkdir(dir, { recursive: true })
27
- }
28
-
29
- async function removeStaleGeneratedFiles(
30
- manifestPath: string,
31
- nextFiles: string[],
32
- ): Promise<void> {
33
- try {
34
- const previous = ComponentProjectGeneratedManifestSchema.parse(
35
- JSON.parse(await fs.readFile(manifestPath, 'utf8')),
36
- )
37
- const staleFiles = previous.files.filter((filePath) => !nextFiles.includes(filePath))
38
- await Promise.all(staleFiles.map((filePath) => fs.rm(filePath, { force: true })))
39
- } catch (error) {
40
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
41
- throw error
42
- }
43
- }
44
- }
45
-
46
- export async function runComponentProjectPostinstall(
47
- projectRoot = resolveComponentProjectInstallRoot(),
48
- ): Promise<void> {
49
- const docsDir = resolveComponentProjectGeneratedDocsDir(projectRoot)
50
- const manifestPath = resolveComponentProjectGeneratedManifestPath(projectRoot)
51
- const components = await discoverComponentProjectComponents(projectRoot)
52
-
53
- console.log(color.cyan('[component-project-helper] postinstall start'))
54
- console.log(color.dim(`[component-project-helper] project root: ${projectRoot}`))
55
- console.log(color.dim(`[component-project-helper] docs dir: ${docsDir}`))
56
- console.log(color.dim(`[component-project-helper] manifest path: ${manifestPath}`))
57
- console.log(
58
- color.green(
59
- `[component-project-helper] discovered ${components.length} component(s): ${
60
- components.map((component) => component.name).join(', ') || '(none)'
61
- }`,
62
- ),
63
- )
64
-
65
- await ensureDirectory(docsDir)
66
-
67
- const files: string[] = []
68
-
69
- for (const component of components) {
70
- const filePath = path.join(docsDir, `${component.name}.md`)
71
- const markdown = renderComponentProjectMarkdown(component)
72
- await fs.writeFile(filePath, markdown, 'utf8')
73
- files.push(filePath)
74
- console.log(
75
- color.green(
76
- `[component-project-helper] wrote ${path.relative(projectRoot, filePath)} <- ${component.sourceFile}`,
77
- ),
78
- )
79
- }
80
-
81
- console.log(
82
- color.yellow(
83
- `[component-project-helper] cleaning stale docs relative to manifest ${path.relative(
84
- projectRoot,
85
- manifestPath,
86
- )}`,
87
- ),
88
- )
89
- await removeStaleGeneratedFiles(manifestPath, files)
90
- await ensureComponentProjectSkillsScript(projectRoot)
91
- console.log(color.green('[component-project-helper] ensured update-component-skills script'))
92
-
93
- const manifest = ComponentProjectGeneratedManifestSchema.parse({
94
- projectRoot,
95
- generatedAt: new Date().toISOString(),
96
- docsDir,
97
- files,
98
- })
99
-
100
- await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
101
- console.log(
102
- color.cyan(
103
- `[component-project-helper] postinstall complete, manifest files: ${files.length}`,
104
- ),
105
- )
106
- }
107
-
108
- async function isDirectRun(): Promise<boolean> {
109
- if (!process.argv[1]) {
110
- return false
111
- }
112
-
113
- const [entryPath, currentPath] = await Promise.all([
114
- fs.realpath(process.argv[1]),
115
- fs.realpath(fileURLToPath(import.meta.url)),
116
- ])
117
-
118
- return entryPath === currentPath
119
- }
120
-
121
- async function main(): Promise<void> {
122
- await runComponentProjectPostinstall()
123
- }
124
-
125
- if (await isDirectRun()) {
126
- await main()
127
- }
@@ -1,54 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import { fileURLToPath } from 'node:url'
3
-
4
- import { ComponentProjectGeneratedManifestSchema } from '../schemas'
5
- import { resolveComponentProjectGeneratedManifestPath } from '../runtime'
6
- import { removeComponentProjectSkillsScript } from './package-json'
7
-
8
- export async function runComponentProjectPreuninstall(
9
- projectRoot = process.env.INIT_CWD ??
10
- process.env.npm_config_local_prefix ??
11
- process.cwd(),
12
- ): Promise<void> {
13
- const manifestPath = resolveComponentProjectGeneratedManifestPath(projectRoot)
14
-
15
- try {
16
- const manifest = ComponentProjectGeneratedManifestSchema.parse(
17
- JSON.parse(await fs.readFile(manifestPath, 'utf8')),
18
- )
19
-
20
- for (const filePath of manifest.files) {
21
- await fs.rm(filePath, { force: true })
22
- }
23
-
24
- await fs.rm(manifestPath, { force: true })
25
- await fs.rm(manifest.docsDir, { recursive: true, force: true })
26
- } catch (error) {
27
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
28
- throw error
29
- }
30
- }
31
-
32
- await removeComponentProjectSkillsScript(projectRoot)
33
- }
34
-
35
- async function main(): Promise<void> {
36
- await runComponentProjectPreuninstall()
37
- }
38
-
39
- async function isDirectRun(): Promise<boolean> {
40
- if (!process.argv[1]) {
41
- return false
42
- }
43
-
44
- const [entryPath, currentPath] = await Promise.all([
45
- fs.realpath(process.argv[1]),
46
- fs.realpath(fileURLToPath(import.meta.url)),
47
- ])
48
-
49
- return entryPath === currentPath
50
- }
51
-
52
- if (await isDirectRun()) {
53
- await main()
54
- }
@@ -1,20 +0,0 @@
1
- import { expect, test } from 'vitest'
2
-
3
- import { renderComponentProjectMarkdown } from './index'
4
-
5
- test('renders markdown with metadata fields', () => {
6
- const markdown = renderComponentProjectMarkdown({
7
- name: 'SceneFrame',
8
- description: 'Wraps the scene content in a consistent frame',
9
- sourceFile: 'src/components/SceneFrame.tsx',
10
- aspectRatio: '16:9',
11
- sceneType: 'scene',
12
- tags: ['layout', 'frame'],
13
- propsTypeName: 'SceneFrameProps',
14
- })
15
-
16
- expect(markdown).toContain('# SceneFrame')
17
- expect(markdown).toContain('SceneFrameProps')
18
- expect(markdown).toContain('src/components/SceneFrame.tsx')
19
- expect(markdown).toContain('16:9')
20
- })
@@ -1,43 +0,0 @@
1
- import {
2
- ComponentProjectComponentMetadataSchema,
3
- type ComponentProjectComponentMetadata,
4
- } from '../schemas'
5
-
6
- export type ComponentProjectMarkdownDocument = ComponentProjectComponentMetadata
7
-
8
- function escapeInline(value: string): string {
9
- return value.replaceAll('`', '\\`')
10
- }
11
-
12
- export function renderComponentProjectMarkdown(
13
- input: ComponentProjectMarkdownDocument,
14
- ): string {
15
- const metadata = ComponentProjectComponentMetadataSchema.parse(input)
16
- const tags = metadata.tags.length > 0 ? metadata.tags : ['component']
17
-
18
- return [
19
- '---',
20
- `name: ${metadata.name}`,
21
- `description: ${metadata.description}`,
22
- `sourceFile: ${metadata.sourceFile}`,
23
- `aspectRatio: ${metadata.aspectRatio}`,
24
- `sceneType: ${metadata.sceneType ?? ''}`,
25
- `propsTypeName: ${metadata.propsTypeName ?? ''}`,
26
- 'tags:',
27
- ...tags.map((tag) => ` - ${tag}`),
28
- '---',
29
- '',
30
- `# ${metadata.name}`,
31
- '',
32
- metadata.description,
33
- '',
34
- '## Metadata',
35
- '',
36
- `- Source file: \`${escapeInline(metadata.sourceFile)}\``,
37
- `- Aspect ratio: \`${escapeInline(metadata.aspectRatio)}\``,
38
- `- Scene type: \`${escapeInline(metadata.sceneType ?? 'unknown')}\``,
39
- `- Props type: \`${escapeInline(metadata.propsTypeName ?? 'unknown')}\``,
40
- `- Tags: ${tags.map((tag) => `\`${escapeInline(tag)}\``).join(', ')}`,
41
- '',
42
- ].join('\n')
43
- }