prjct-cli 0.4.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/CHANGELOG.md +312 -0
- package/CLAUDE.md +300 -0
- package/LICENSE +21 -0
- package/README.md +424 -0
- package/bin/prjct +214 -0
- package/core/agent-detector.js +249 -0
- package/core/agents/claude-agent.js +250 -0
- package/core/agents/codex-agent.js +256 -0
- package/core/agents/terminal-agent.js +465 -0
- package/core/analyzer.js +596 -0
- package/core/animations-simple.js +240 -0
- package/core/animations.js +277 -0
- package/core/author-detector.js +218 -0
- package/core/capability-installer.js +190 -0
- package/core/command-installer.js +775 -0
- package/core/commands.js +2050 -0
- package/core/config-manager.js +335 -0
- package/core/migrator.js +784 -0
- package/core/path-manager.js +324 -0
- package/core/project-capabilities.js +144 -0
- package/core/session-manager.js +439 -0
- package/core/version.js +107 -0
- package/core/workflow-engine.js +213 -0
- package/core/workflow-prompts.js +192 -0
- package/core/workflow-rules.js +147 -0
- package/package.json +80 -0
- package/scripts/install.sh +433 -0
- package/scripts/verify-installation.sh +158 -0
- package/templates/agents/AGENTS.md +164 -0
- package/templates/commands/analyze.md +125 -0
- package/templates/commands/cleanup.md +102 -0
- package/templates/commands/context.md +105 -0
- package/templates/commands/design.md +113 -0
- package/templates/commands/done.md +44 -0
- package/templates/commands/fix.md +87 -0
- package/templates/commands/git.md +79 -0
- package/templates/commands/help.md +72 -0
- package/templates/commands/idea.md +50 -0
- package/templates/commands/init.md +237 -0
- package/templates/commands/next.md +74 -0
- package/templates/commands/now.md +35 -0
- package/templates/commands/progress.md +92 -0
- package/templates/commands/recap.md +86 -0
- package/templates/commands/roadmap.md +107 -0
- package/templates/commands/ship.md +41 -0
- package/templates/commands/stuck.md +48 -0
- package/templates/commands/task.md +97 -0
- package/templates/commands/test.md +94 -0
- package/templates/commands/workflow.md +224 -0
- package/templates/examples/natural-language-examples.md +320 -0
- package/templates/mcp-config.json +8 -0
- package/templates/workflows/analyze.md +159 -0
- package/templates/workflows/cleanup.md +73 -0
- package/templates/workflows/context.md +72 -0
- package/templates/workflows/design.md +88 -0
- package/templates/workflows/done.md +20 -0
- package/templates/workflows/fix.md +201 -0
- package/templates/workflows/git.md +192 -0
- package/templates/workflows/help.md +13 -0
- package/templates/workflows/idea.md +22 -0
- package/templates/workflows/init.md +80 -0
- package/templates/workflows/natural-language-handler.md +183 -0
- package/templates/workflows/next.md +44 -0
- package/templates/workflows/now.md +19 -0
- package/templates/workflows/progress.md +113 -0
- package/templates/workflows/recap.md +66 -0
- package/templates/workflows/roadmap.md +95 -0
- package/templates/workflows/ship.md +18 -0
- package/templates/workflows/stuck.md +25 -0
- package/templates/workflows/task.md +109 -0
- package/templates/workflows/test.md +243 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const crypto = require('crypto')
|
|
4
|
+
const os = require('os')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PathManager - Manages project paths between local and global storage
|
|
8
|
+
*
|
|
9
|
+
* Key responsibilities:
|
|
10
|
+
* - Generate unique project identifiers from project paths
|
|
11
|
+
* - Manage paths between local project and global storage
|
|
12
|
+
* - Ensure directory structures exist
|
|
13
|
+
*
|
|
14
|
+
* @version 0.2.1
|
|
15
|
+
*/
|
|
16
|
+
class PathManager {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.globalBaseDir = path.join(os.homedir(), '.prjct-cli')
|
|
19
|
+
this.globalProjectsDir = path.join(this.globalBaseDir, 'projects')
|
|
20
|
+
this.globalConfigDir = path.join(this.globalBaseDir, 'config')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate a unique project ID from the absolute project path
|
|
25
|
+
* Uses SHA-256 hash of the absolute path for consistency
|
|
26
|
+
*
|
|
27
|
+
* @param {string} projectPath - Absolute path to the project
|
|
28
|
+
* @returns {string} - 12-character hash ID
|
|
29
|
+
*/
|
|
30
|
+
generateProjectId(projectPath) {
|
|
31
|
+
const absolutePath = path.resolve(projectPath)
|
|
32
|
+
const hash = crypto.createHash('sha256').update(absolutePath).digest('hex')
|
|
33
|
+
return hash.substring(0, 12) // Use first 12 chars for readability
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the base global storage path
|
|
38
|
+
*
|
|
39
|
+
* @returns {string} - Path to global base directory (~/.prjct-cli)
|
|
40
|
+
*/
|
|
41
|
+
getGlobalBasePath() {
|
|
42
|
+
return this.globalBaseDir
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the global storage path for a project
|
|
47
|
+
*
|
|
48
|
+
* @param {string} projectId - The project identifier
|
|
49
|
+
* @returns {string} - Path to global project storage
|
|
50
|
+
*/
|
|
51
|
+
getGlobalProjectPath(projectId) {
|
|
52
|
+
return path.join(this.globalProjectsDir, projectId)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the local config file path for a project
|
|
57
|
+
*
|
|
58
|
+
* @param {string} projectPath - Path to the project
|
|
59
|
+
* @returns {string} - Path to .prjct/prjct.config.json
|
|
60
|
+
*/
|
|
61
|
+
getLocalConfigPath(projectPath) {
|
|
62
|
+
return path.join(projectPath, '.prjct', 'prjct.config.json')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the global config file path for a project
|
|
67
|
+
* This file stores authors and other system data that shouldn't be versioned
|
|
68
|
+
*
|
|
69
|
+
* @param {string} projectId - The project identifier
|
|
70
|
+
* @returns {string} - Path to ~/.prjct-cli/projects/{id}/project.json
|
|
71
|
+
*/
|
|
72
|
+
getGlobalProjectConfigPath(projectId) {
|
|
73
|
+
return path.join(this.getGlobalProjectPath(projectId), 'project.json')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the legacy .prjct directory path
|
|
78
|
+
*
|
|
79
|
+
* @param {string} projectPath - Path to the project
|
|
80
|
+
* @returns {string} - Path to legacy .prjct directory
|
|
81
|
+
*/
|
|
82
|
+
getLegacyPrjctPath(projectPath) {
|
|
83
|
+
return path.join(projectPath, '.prjct')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if a project has legacy .prjct directory
|
|
88
|
+
*
|
|
89
|
+
* @param {string} projectPath - Path to the project
|
|
90
|
+
* @returns {Promise<boolean>} - True if legacy directory exists
|
|
91
|
+
*/
|
|
92
|
+
async hasLegacyStructure(projectPath) {
|
|
93
|
+
try {
|
|
94
|
+
const legacyPath = this.getLegacyPrjctPath(projectPath)
|
|
95
|
+
await fs.access(legacyPath)
|
|
96
|
+
return true
|
|
97
|
+
} catch {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a project has the new config file
|
|
104
|
+
*
|
|
105
|
+
* @param {string} projectPath - Path to the project
|
|
106
|
+
* @returns {Promise<boolean>} - True if config exists
|
|
107
|
+
*/
|
|
108
|
+
async hasConfig(projectPath) {
|
|
109
|
+
try {
|
|
110
|
+
const configPath = this.getLocalConfigPath(projectPath)
|
|
111
|
+
await fs.access(configPath)
|
|
112
|
+
return true
|
|
113
|
+
} catch {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Ensure the global directory structure exists
|
|
120
|
+
* Creates all necessary directories in ~/.prjct-cli/
|
|
121
|
+
*
|
|
122
|
+
* @returns {Promise<void>}
|
|
123
|
+
*/
|
|
124
|
+
async ensureGlobalStructure() {
|
|
125
|
+
await fs.mkdir(this.globalBaseDir, { recursive: true })
|
|
126
|
+
await fs.mkdir(this.globalProjectsDir, { recursive: true })
|
|
127
|
+
await fs.mkdir(this.globalConfigDir, { recursive: true })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Ensure the project-specific global structure exists
|
|
132
|
+
* Creates the layered directory structure for a project
|
|
133
|
+
*
|
|
134
|
+
* @param {string} projectId - The project identifier
|
|
135
|
+
* @returns {Promise<string>} - Path to the project's global storage
|
|
136
|
+
*/
|
|
137
|
+
async ensureProjectStructure(projectId) {
|
|
138
|
+
await this.ensureGlobalStructure()
|
|
139
|
+
|
|
140
|
+
const projectPath = this.getGlobalProjectPath(projectId)
|
|
141
|
+
|
|
142
|
+
const layers = ['core', 'progress', 'planning', 'analysis', 'memory']
|
|
143
|
+
|
|
144
|
+
for (const layer of layers) {
|
|
145
|
+
await fs.mkdir(path.join(projectPath, layer), { recursive: true })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await fs.mkdir(path.join(projectPath, 'planning', 'tasks'), { recursive: true })
|
|
149
|
+
|
|
150
|
+
await fs.mkdir(path.join(projectPath, 'sessions'), { recursive: true })
|
|
151
|
+
|
|
152
|
+
return projectPath
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get session directory path for a specific date
|
|
157
|
+
* Creates hierarchical structure: sessions/YYYY/MM/DD/
|
|
158
|
+
*
|
|
159
|
+
* @param {string} projectId - The project identifier
|
|
160
|
+
* @param {Date} date - Date for the session (defaults to today)
|
|
161
|
+
* @returns {string} - Path to session directory
|
|
162
|
+
*/
|
|
163
|
+
getSessionPath(projectId, date = new Date()) {
|
|
164
|
+
const year = date.getFullYear().toString()
|
|
165
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
166
|
+
const day = date.getDate().toString().padStart(2, '0')
|
|
167
|
+
|
|
168
|
+
return path.join(
|
|
169
|
+
this.getGlobalProjectPath(projectId),
|
|
170
|
+
'sessions',
|
|
171
|
+
year,
|
|
172
|
+
month,
|
|
173
|
+
day,
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get current session directory path (today)
|
|
179
|
+
*
|
|
180
|
+
* @param {string} projectId - The project identifier
|
|
181
|
+
* @returns {string} - Path to today's session directory
|
|
182
|
+
*/
|
|
183
|
+
getCurrentSessionPath(projectId) {
|
|
184
|
+
return this.getSessionPath(projectId, new Date())
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Ensure session directory exists for a specific date
|
|
189
|
+
*
|
|
190
|
+
* @param {string} projectId - The project identifier
|
|
191
|
+
* @param {Date} date - Date for the session (defaults to today)
|
|
192
|
+
* @returns {Promise<string>} - Path to session directory
|
|
193
|
+
*/
|
|
194
|
+
async ensureSessionPath(projectId, date = new Date()) {
|
|
195
|
+
const sessionPath = this.getSessionPath(projectId, date)
|
|
196
|
+
await fs.mkdir(sessionPath, { recursive: true })
|
|
197
|
+
return sessionPath
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* List all session dates for a project
|
|
202
|
+
*
|
|
203
|
+
* @param {string} projectId - The project identifier
|
|
204
|
+
* @param {number} year - Optional year filter
|
|
205
|
+
* @param {number} month - Optional month filter (1-12)
|
|
206
|
+
* @returns {Promise<Array<{year: string, month: string, day: string, path: string}>>} - Array of session info
|
|
207
|
+
*/
|
|
208
|
+
async listSessions(projectId, year = null, month = null) {
|
|
209
|
+
const sessionsDir = path.join(this.getGlobalProjectPath(projectId), 'sessions')
|
|
210
|
+
const sessions = []
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const years = await fs.readdir(sessionsDir, { withFileTypes: true })
|
|
214
|
+
|
|
215
|
+
for (const yearEntry of years) {
|
|
216
|
+
if (!yearEntry.isDirectory()) continue
|
|
217
|
+
if (year && yearEntry.name !== year.toString()) continue
|
|
218
|
+
|
|
219
|
+
const yearPath = path.join(sessionsDir, yearEntry.name)
|
|
220
|
+
const months = await fs.readdir(yearPath, { withFileTypes: true })
|
|
221
|
+
|
|
222
|
+
for (const monthEntry of months) {
|
|
223
|
+
if (!monthEntry.isDirectory()) continue
|
|
224
|
+
if (month && monthEntry.name !== month.toString().padStart(2, '0')) continue
|
|
225
|
+
|
|
226
|
+
const monthPath = path.join(yearPath, monthEntry.name)
|
|
227
|
+
const days = await fs.readdir(monthPath, { withFileTypes: true })
|
|
228
|
+
|
|
229
|
+
for (const dayEntry of days) {
|
|
230
|
+
if (!dayEntry.isDirectory()) continue
|
|
231
|
+
|
|
232
|
+
sessions.push({
|
|
233
|
+
year: yearEntry.name,
|
|
234
|
+
month: monthEntry.name,
|
|
235
|
+
day: dayEntry.name,
|
|
236
|
+
path: path.join(monthPath, dayEntry.name),
|
|
237
|
+
date: new Date(`${yearEntry.name}-${monthEntry.name}-${dayEntry.name}`),
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
sessions.sort((a, b) => b.date - a.date)
|
|
244
|
+
return sessions
|
|
245
|
+
} catch {
|
|
246
|
+
return []
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get sessions within a date range
|
|
252
|
+
*
|
|
253
|
+
* @param {string} projectId - The project identifier
|
|
254
|
+
* @param {Date} fromDate - Start date (inclusive)
|
|
255
|
+
* @param {Date} toDate - End date (inclusive, defaults to today)
|
|
256
|
+
* @returns {Promise<Array>} - Array of session info within range
|
|
257
|
+
*/
|
|
258
|
+
async getSessionsInRange(projectId, fromDate, toDate = new Date()) {
|
|
259
|
+
const allSessions = await this.listSessions(projectId)
|
|
260
|
+
|
|
261
|
+
return allSessions.filter(session => session.date >= fromDate && session.date <= toDate)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get the path for a specific file in the global structure
|
|
266
|
+
*
|
|
267
|
+
* @param {string} projectId - The project identifier
|
|
268
|
+
* @param {string} layer - The layer (core, progress, planning, analysis, memory)
|
|
269
|
+
* @param {string} filename - The filename
|
|
270
|
+
* @returns {string} - Full path to the file
|
|
271
|
+
*/
|
|
272
|
+
getFilePath(projectId, layer, filename) {
|
|
273
|
+
return path.join(this.getGlobalProjectPath(projectId), layer, filename)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get all project IDs in global storage
|
|
278
|
+
*
|
|
279
|
+
* @returns {Promise<string[]>} - Array of project IDs
|
|
280
|
+
*/
|
|
281
|
+
async listProjects() {
|
|
282
|
+
try {
|
|
283
|
+
await this.ensureGlobalStructure()
|
|
284
|
+
const entries = await fs.readdir(this.globalProjectsDir, { withFileTypes: true })
|
|
285
|
+
return entries
|
|
286
|
+
.filter(entry => entry.isDirectory())
|
|
287
|
+
.map(entry => entry.name)
|
|
288
|
+
} catch {
|
|
289
|
+
return []
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Check if a project exists in global storage
|
|
295
|
+
*
|
|
296
|
+
* @param {string} projectId - The project identifier
|
|
297
|
+
* @returns {Promise<boolean>} - True if project exists
|
|
298
|
+
*/
|
|
299
|
+
async projectExists(projectId) {
|
|
300
|
+
try {
|
|
301
|
+
const projectPath = this.getGlobalProjectPath(projectId)
|
|
302
|
+
await fs.access(projectPath)
|
|
303
|
+
return true
|
|
304
|
+
} catch {
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get the relative path from home directory for display
|
|
311
|
+
*
|
|
312
|
+
* @param {string} absolutePath - Absolute path
|
|
313
|
+
* @returns {string} - Path with ~ notation
|
|
314
|
+
*/
|
|
315
|
+
getDisplayPath(absolutePath) {
|
|
316
|
+
const homeDir = os.homedir()
|
|
317
|
+
if (absolutePath.startsWith(homeDir)) {
|
|
318
|
+
return absolutePath.replace(homeDir, '~')
|
|
319
|
+
}
|
|
320
|
+
return absolutePath
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = new PathManager()
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Capabilities Detector
|
|
3
|
+
* Detects ONLY what exists - no assumptions, no hallucinations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs').promises
|
|
7
|
+
const path = require('path')
|
|
8
|
+
|
|
9
|
+
class ProjectCapabilities {
|
|
10
|
+
/**
|
|
11
|
+
* Detect project capabilities
|
|
12
|
+
* @param {string} projectPath - Project root path
|
|
13
|
+
* @returns {Promise<Object>} Capabilities object
|
|
14
|
+
*/
|
|
15
|
+
async detect(projectPath = process.cwd()) {
|
|
16
|
+
return {
|
|
17
|
+
design: await this.hasDesign(projectPath),
|
|
18
|
+
test: await this.hasTest(projectPath),
|
|
19
|
+
docs: await this.hasDocs(projectPath),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if project has design system
|
|
25
|
+
*/
|
|
26
|
+
async hasDesign(projectPath) {
|
|
27
|
+
return (
|
|
28
|
+
await this.hasFolder(projectPath, 'design') ||
|
|
29
|
+
await this.hasFolder(projectPath, 'designs') ||
|
|
30
|
+
await this.hasFolder(projectPath, '.storybook') ||
|
|
31
|
+
await this.hasDep(projectPath, '@storybook/react') ||
|
|
32
|
+
await this.hasDep(projectPath, '@storybook/vue') ||
|
|
33
|
+
await this.hasFiles(projectPath, '**/*.figma')
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if project has test framework
|
|
39
|
+
*/
|
|
40
|
+
async hasTest(projectPath) {
|
|
41
|
+
return (
|
|
42
|
+
await this.hasDep(projectPath, 'jest') ||
|
|
43
|
+
await this.hasDep(projectPath, 'vitest') ||
|
|
44
|
+
await this.hasDep(projectPath, 'mocha') ||
|
|
45
|
+
await this.hasDep(projectPath, '@jest/core') ||
|
|
46
|
+
await this.hasFiles(projectPath, '**/*.{test,spec}.{js,ts,jsx,tsx}') ||
|
|
47
|
+
await this.hasFile(projectPath, 'jest.config.js') ||
|
|
48
|
+
await this.hasFile(projectPath, 'vitest.config.js')
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if project has documentation system
|
|
54
|
+
*/
|
|
55
|
+
async hasDocs(projectPath) {
|
|
56
|
+
return (
|
|
57
|
+
await this.hasFolder(projectPath, 'docs') ||
|
|
58
|
+
await this.hasFolder(projectPath, 'documentation') ||
|
|
59
|
+
await this.hasFile(projectPath, 'README.md') ||
|
|
60
|
+
await this.hasDep(projectPath, 'typedoc') ||
|
|
61
|
+
await this.hasDep(projectPath, 'jsdoc')
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if folder exists
|
|
67
|
+
*/
|
|
68
|
+
async hasFolder(projectPath, folderName) {
|
|
69
|
+
try {
|
|
70
|
+
const folderPath = path.join(projectPath, folderName)
|
|
71
|
+
const stat = await fs.stat(folderPath)
|
|
72
|
+
return stat.isDirectory()
|
|
73
|
+
} catch {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if file exists
|
|
80
|
+
*/
|
|
81
|
+
async hasFile(projectPath, fileName) {
|
|
82
|
+
try {
|
|
83
|
+
const filePath = path.join(projectPath, fileName)
|
|
84
|
+
await fs.access(filePath)
|
|
85
|
+
return true
|
|
86
|
+
} catch {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if files matching pattern exist
|
|
93
|
+
*/
|
|
94
|
+
async hasFiles(projectPath, pattern) {
|
|
95
|
+
try {
|
|
96
|
+
// Convert glob pattern to regex
|
|
97
|
+
const regex = this.globToRegex(pattern)
|
|
98
|
+
const files = await fs.readdir(projectPath, { recursive: true })
|
|
99
|
+
|
|
100
|
+
// Filter by pattern and ignore node_modules, dist, build
|
|
101
|
+
return files.some(file => {
|
|
102
|
+
const skip = file.includes('node_modules/') ||
|
|
103
|
+
file.includes('dist/') ||
|
|
104
|
+
file.includes('build/')
|
|
105
|
+
return !skip && regex.test(file)
|
|
106
|
+
})
|
|
107
|
+
} catch {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Convert simple glob pattern to regex
|
|
114
|
+
*/
|
|
115
|
+
globToRegex(pattern) {
|
|
116
|
+
// Convert **/*.{test,spec}.{js,ts,jsx,tsx} to regex
|
|
117
|
+
const escaped = pattern
|
|
118
|
+
.replace(/\./g, '\\.')
|
|
119
|
+
.replace(/\*\*/g, '.*')
|
|
120
|
+
.replace(/\*/g, '[^/]*')
|
|
121
|
+
.replace(/\{([^}]+)\}/g, (_, group) => `(${group.split(',').join('|')})`)
|
|
122
|
+
return new RegExp(escaped)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if dependency exists in package.json
|
|
127
|
+
*/
|
|
128
|
+
async hasDep(projectPath, depName) {
|
|
129
|
+
try {
|
|
130
|
+
const pkgPath = path.join(projectPath, 'package.json')
|
|
131
|
+
const content = await fs.readFile(pkgPath, 'utf8')
|
|
132
|
+
const pkg = JSON.parse(content)
|
|
133
|
+
|
|
134
|
+
return !!(
|
|
135
|
+
(pkg.dependencies && pkg.dependencies[depName]) ||
|
|
136
|
+
(pkg.devDependencies && pkg.devDependencies[depName])
|
|
137
|
+
)
|
|
138
|
+
} catch {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = new ProjectCapabilities()
|