@vibecuting/component-project-helper 0.1.15 → 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.
- package/bin/component-project-helper.mjs +3 -3
- package/package.json +4 -3
- package/scripts/package-json.mjs +1 -1
- package/scripts/update-component-meta.mjs +60 -0
- package/src/decorators/index.test.ts +23 -0
- package/src/decorators/index.ts +95 -19
- package/src/discovery/index.test.ts +86 -159
- package/src/discovery/index.ts +646 -102
- package/src/index.ts +49 -10
- package/src/meta/generate-video-resource-meta.ts +198 -0
- package/src/meta/update-component-meta.test.ts +64 -0
- package/src/meta/update-component-meta.ts +47 -0
- package/src/resources/resource-descriptors.ts +47 -0
- package/src/resources/video-resource-descriptors.json +17 -0
- package/src/resources/video-resource.ts +143 -0
- package/src/schemas/index.ts +24 -2
- package/scripts/postinstall.mjs +0 -112
- package/scripts/preuninstall.mjs +0 -45
- package/scripts/render-markdown.mjs +0 -36
- package/src/lifecycle/package-json.ts +0 -63
- package/src/lifecycle/postinstall.test.ts +0 -114
- package/src/lifecycle/postinstall.ts +0 -127
- package/src/lifecycle/preuninstall.ts +0 -54
- package/src/markdown/index.test.ts +0 -20
- package/src/markdown/index.ts +0 -43
package/scripts/postinstall.mjs
DELETED
|
@@ -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
|
-
}
|
package/scripts/preuninstall.mjs
DELETED
|
@@ -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
|
-
})
|
package/src/markdown/index.ts
DELETED
|
@@ -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
|
-
}
|