@vibecuting/component-project-helper 0.1.8 → 0.1.10
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/README.md +1 -1
- package/package.json +2 -1
- package/scripts/discover-components.mjs +33 -2
- package/src/discovery/index.test.ts +83 -0
- package/src/discovery/index.ts +33 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ NPM 地址:
|
|
|
10
10
|
|
|
11
11
|
- 作为可发布的 npm 包承载组件工程辅助逻辑。
|
|
12
12
|
- 提供组件注解、schema、发现器、Markdown 生成和安装期生命周期逻辑。
|
|
13
|
-
- 安装时扫描 `src/components/**/*.tsx`,把组件元数据生成到 `.agents/skills/project-allow-component/components/`。
|
|
13
|
+
- 安装时扫描 `src/components/**/*.tsx`、`src/video/chapters/**/*.tsx` 以及 `node_modules/@vibecuting/*/src/**/*.tsx`,把组件元数据生成到 `.agents/skills/project-allow-component/components/`。
|
|
14
14
|
- 通过 `postinstall` / `preuninstall` 维护生成文件和清理清单。
|
|
15
15
|
- 安装时会给宿主工程的 `package.json` 补上 `update-component-skills`,它会调用同一套 `postinstall` 逻辑。
|
|
16
16
|
- 宿主工程也可以直接运行 `pnpm update-component-skills` 手动刷新同一套生成结果。
|
package/package.json
CHANGED
|
@@ -97,6 +97,38 @@ async function collectComponentSourceFiles(directory) {
|
|
|
97
97
|
return files
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
async function collectNamespacedPackageSourceFiles(projectRoot) {
|
|
101
|
+
const namespaceRoot = path.join(projectRoot, 'node_modules', '@vibecuting')
|
|
102
|
+
let entries = []
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
entries = await fs.readdir(namespaceRoot, { withFileTypes: true })
|
|
106
|
+
} catch {
|
|
107
|
+
return []
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const packageRoots = entries
|
|
111
|
+
.filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
|
|
112
|
+
.map((entry) => path.join(namespaceRoot, entry.name, 'src'))
|
|
113
|
+
|
|
114
|
+
const files = await Promise.all(packageRoots.map((directory) => collectComponentSourceFiles(directory)))
|
|
115
|
+
return [...new Set(files.flat())]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function collectDiscoverableSourceFiles(projectRoot) {
|
|
119
|
+
const sourceRoots = [
|
|
120
|
+
path.join(projectRoot, 'src', 'components'),
|
|
121
|
+
path.join(projectRoot, 'src', 'video', 'chapters'),
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
const localFiles = await Promise.all(
|
|
125
|
+
sourceRoots.map((directory) => collectComponentSourceFiles(directory)),
|
|
126
|
+
)
|
|
127
|
+
const packageFiles = await collectNamespacedPackageSourceFiles(projectRoot)
|
|
128
|
+
|
|
129
|
+
return [...new Set([...localFiles.flat(), ...packageFiles])]
|
|
130
|
+
}
|
|
131
|
+
|
|
100
132
|
function readMetadataFromSource(source) {
|
|
101
133
|
const decoratorMatch = /@VideoComponent\s*\((\{[\s\S]*?\})\)/m.exec(source)
|
|
102
134
|
if (decoratorMatch) {
|
|
@@ -135,9 +167,8 @@ function readMetadataFromSource(source) {
|
|
|
135
167
|
}
|
|
136
168
|
|
|
137
169
|
export async function discoverComponentProjectComponents(projectRoot) {
|
|
138
|
-
const componentsDir = path.join(projectRoot, 'src', 'components')
|
|
139
170
|
const discovered = []
|
|
140
|
-
const sourceFiles = await
|
|
171
|
+
const sourceFiles = await collectDiscoverableSourceFiles(projectRoot)
|
|
141
172
|
|
|
142
173
|
for (const absolutePath of sourceFiles) {
|
|
143
174
|
const source = await fs.readFile(absolutePath, 'utf8')
|
|
@@ -68,3 +68,86 @@ test('discovers decorated components recursively', async () => {
|
|
|
68
68
|
},
|
|
69
69
|
])
|
|
70
70
|
})
|
|
71
|
+
|
|
72
|
+
test('discovers decorated components from installed video-project-core', async () => {
|
|
73
|
+
const projectRoot = await fs.mkdtemp(path.join(tmpdir(), 'component-project-helper-package-'))
|
|
74
|
+
const videoProjectCoreComponentDir = path.join(
|
|
75
|
+
projectRoot,
|
|
76
|
+
'node_modules',
|
|
77
|
+
'@vibecuting',
|
|
78
|
+
'video-project-core',
|
|
79
|
+
'src',
|
|
80
|
+
'core',
|
|
81
|
+
'intro',
|
|
82
|
+
)
|
|
83
|
+
await fs.mkdir(videoProjectCoreComponentDir, { recursive: true })
|
|
84
|
+
|
|
85
|
+
await fs.writeFile(
|
|
86
|
+
path.join(videoProjectCoreComponentDir, 'DefaultIntroBumper.tsx'),
|
|
87
|
+
`
|
|
88
|
+
@VideoComponent({
|
|
89
|
+
name: 'DefaultIntroBumper',
|
|
90
|
+
description: 'Default remotion intro bumper',
|
|
91
|
+
sourceFile: 'node_modules/@vibecuting/video-project-core/src/core/intro/DefaultIntroBumper.tsx',
|
|
92
|
+
aspectRatio: '16:9',
|
|
93
|
+
sceneType: 'intro',
|
|
94
|
+
tags: ['intro', 'core'],
|
|
95
|
+
propsTypeName: 'DefaultIntroBumperProps',
|
|
96
|
+
})
|
|
97
|
+
export const DefaultIntroBumper = () => null
|
|
98
|
+
`,
|
|
99
|
+
'utf8',
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const videoProjectHelperComponentDir = path.join(
|
|
103
|
+
projectRoot,
|
|
104
|
+
'node_modules',
|
|
105
|
+
'@vibecuting',
|
|
106
|
+
'video-project-helper',
|
|
107
|
+
'src',
|
|
108
|
+
'core',
|
|
109
|
+
'scene',
|
|
110
|
+
)
|
|
111
|
+
await fs.mkdir(videoProjectHelperComponentDir, { recursive: true })
|
|
112
|
+
|
|
113
|
+
await fs.writeFile(
|
|
114
|
+
path.join(videoProjectHelperComponentDir, 'DefaultSceneFrame.tsx'),
|
|
115
|
+
`
|
|
116
|
+
@VideoComponent({
|
|
117
|
+
name: 'DefaultSceneFrame',
|
|
118
|
+
description: 'Default helper scene frame',
|
|
119
|
+
sourceFile: 'node_modules/@vibecuting/video-project-helper/src/core/scene/DefaultSceneFrame.tsx',
|
|
120
|
+
aspectRatio: '16:9',
|
|
121
|
+
sceneType: 'scene',
|
|
122
|
+
tags: ['scene', 'helper'],
|
|
123
|
+
propsTypeName: 'DefaultSceneFrameProps',
|
|
124
|
+
})
|
|
125
|
+
export const DefaultSceneFrame = () => null
|
|
126
|
+
`,
|
|
127
|
+
'utf8',
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const discovered = await discoverComponentProjectComponents(projectRoot)
|
|
131
|
+
|
|
132
|
+
expect(discovered).toEqual([
|
|
133
|
+
{
|
|
134
|
+
name: 'DefaultIntroBumper',
|
|
135
|
+
description: 'Default remotion intro bumper',
|
|
136
|
+
sourceFile: 'node_modules/@vibecuting/video-project-core/src/core/intro/DefaultIntroBumper.tsx',
|
|
137
|
+
aspectRatio: '16:9',
|
|
138
|
+
sceneType: 'intro',
|
|
139
|
+
tags: ['intro', 'core'],
|
|
140
|
+
propsTypeName: 'DefaultIntroBumperProps',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'DefaultSceneFrame',
|
|
144
|
+
description: 'Default helper scene frame',
|
|
145
|
+
sourceFile:
|
|
146
|
+
'node_modules/@vibecuting/video-project-helper/src/core/scene/DefaultSceneFrame.tsx',
|
|
147
|
+
aspectRatio: '16:9',
|
|
148
|
+
sceneType: 'scene',
|
|
149
|
+
tags: ['scene', 'helper'],
|
|
150
|
+
propsTypeName: 'DefaultSceneFrameProps',
|
|
151
|
+
},
|
|
152
|
+
])
|
|
153
|
+
})
|
package/src/discovery/index.ts
CHANGED
|
@@ -44,6 +44,38 @@ async function collectComponentSourceFiles(directory: string): Promise<string[]>
|
|
|
44
44
|
return files
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
async function collectNamespacedPackageSourceFiles(projectRoot: string): Promise<string[]> {
|
|
48
|
+
const namespaceRoot = path.join(projectRoot, 'node_modules', '@vibecuting')
|
|
49
|
+
let entries: Dirent[] = []
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
entries = await fs.readdir(namespaceRoot, { withFileTypes: true })
|
|
53
|
+
} catch {
|
|
54
|
+
return []
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const packageRoots = entries
|
|
58
|
+
.filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
|
|
59
|
+
.map((entry) => path.join(namespaceRoot, entry.name, 'src'))
|
|
60
|
+
|
|
61
|
+
const files = await Promise.all(packageRoots.map((directory) => collectComponentSourceFiles(directory)))
|
|
62
|
+
return [...new Set(files.flat())]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function collectDiscoverableSourceFiles(projectRoot: string): Promise<string[]> {
|
|
66
|
+
const sourceRoots = [
|
|
67
|
+
path.join(projectRoot, 'src', 'components'),
|
|
68
|
+
path.join(projectRoot, 'src', 'video', 'chapters'),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
const localFiles = await Promise.all(
|
|
72
|
+
sourceRoots.map((directory) => collectComponentSourceFiles(directory)),
|
|
73
|
+
)
|
|
74
|
+
const packageFiles = await collectNamespacedPackageSourceFiles(projectRoot)
|
|
75
|
+
|
|
76
|
+
return [...new Set([...localFiles.flat(), ...packageFiles])]
|
|
77
|
+
}
|
|
78
|
+
|
|
47
79
|
function extractBalancedObjectLiteral(source: string, startIndex: number): string | undefined {
|
|
48
80
|
let depth = 0
|
|
49
81
|
let inString = false
|
|
@@ -143,9 +175,8 @@ function readMetadataFromSource(source: string): ComponentProjectComponentMetada
|
|
|
143
175
|
export async function discoverComponentProjectComponents(
|
|
144
176
|
projectRoot: string,
|
|
145
177
|
): Promise<ComponentProjectComponentDiscovery[]> {
|
|
146
|
-
const componentsDir = path.join(projectRoot, 'src', 'components')
|
|
147
178
|
const discovered: ComponentProjectComponentDiscovery[] = []
|
|
148
|
-
const sourceFiles = await
|
|
179
|
+
const sourceFiles = await collectDiscoverableSourceFiles(projectRoot)
|
|
149
180
|
|
|
150
181
|
for (const absolutePath of sourceFiles) {
|
|
151
182
|
const source = await fs.readFile(absolutePath, 'utf8')
|