prjct-cli 0.8.4 → 0.8.6
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 +39 -0
- package/bin/prjct +19 -166
- package/core/index.js +178 -0
- package/core/infrastructure/command-installer.js +9 -26
- package/core/infrastructure/setup.js +167 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +12 -203
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.6] - 2025-10-05
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Critical: Command update system** - Fixed command synchronization to ALWAYS update to latest version
|
|
15
|
+
- ✅ Removed mtime comparison (was causing commands to stay outdated)
|
|
16
|
+
- ✅ ALWAYS overwrites existing commands with latest templates
|
|
17
|
+
- ✅ Guarantees all clients get updated commands on `npm update`
|
|
18
|
+
- ✅ Preserves legacy commands (removed: always 0)
|
|
19
|
+
- 🐛 **Bug fixed**: `now.md`, `done.md` and other commands now update correctly across all client installations
|
|
20
|
+
|
|
21
|
+
- **Setup architecture redesigned** - Dual-defense strategy for reliable setup
|
|
22
|
+
- ✅ First-use setup check in `bin/prjct` (guaranteed execution)
|
|
23
|
+
- ✅ Optional postinstall optimization (when scripts enabled)
|
|
24
|
+
- ✅ Works in CI/CD environments with `--ignore-scripts`
|
|
25
|
+
- ✅ Version tracking via `editors-config.js`
|
|
26
|
+
- ✅ Zero manual steps required
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Command sync behavior** (`syncCommands()`)
|
|
31
|
+
- Previous: Compared mtime, only updated if source newer → ❌ Unreliable
|
|
32
|
+
- Now: ALWAYS overwrites existing commands → ✅ Always latest version
|
|
33
|
+
- Preserves legacy/custom commands not in templates
|
|
34
|
+
- Result: `{ added: X, updated: Y, removed: 0 }`
|
|
35
|
+
|
|
36
|
+
- **Setup flow**
|
|
37
|
+
- Centralized all logic in `core/infrastructure/setup.js`
|
|
38
|
+
- `bin/prjct` checks version on every execution
|
|
39
|
+
- `scripts/postinstall.js` simplified to optional helper
|
|
40
|
+
- Auto-setup triggers on version mismatch or first use
|
|
41
|
+
|
|
42
|
+
### Technical
|
|
43
|
+
|
|
44
|
+
- Reduced `scripts/postinstall.js` from 250 → 69 lines
|
|
45
|
+
- Created `core/infrastructure/setup.js` (167 lines) - single source of truth
|
|
46
|
+
- Modified `bin/prjct` to include version check (34 lines)
|
|
47
|
+
- Removed `.setup-complete` marker (using JSON version tracking instead)
|
|
48
|
+
|
|
10
49
|
## [0.8.2] - 2025-10-05
|
|
11
50
|
|
|
12
51
|
### Changed
|
package/bin/prjct
CHANGED
|
@@ -1,181 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* prjct CLI
|
|
4
|
+
* prjct CLI entry point
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* Zero hardcoded logic - all commands resolved dynamically.
|
|
6
|
+
* Auto-setup on first use (like Astro, Vite, etc.)
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
async function main() {
|
|
14
|
-
const [commandName, ...rawArgs] = process.argv.slice(2)
|
|
15
|
-
|
|
16
|
-
// === SPECIAL COMMANDS (version, help) ===
|
|
17
|
-
|
|
18
|
-
if (['-v', '--version', 'version'].includes(commandName)) {
|
|
19
|
-
const packageJson = require('../package.json')
|
|
20
|
-
console.log(`prjct-cli v${packageJson.version}`)
|
|
21
|
-
process.exit(0)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (['-h', '--help', 'help', undefined].includes(commandName)) {
|
|
25
|
-
displayHelp()
|
|
26
|
-
process.exit(0)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// === DYNAMIC COMMAND EXECUTION ===
|
|
9
|
+
const { VERSION } = require('../core/utils/version')
|
|
10
|
+
const editorsConfig = require('../core/infrastructure/editors-config')
|
|
30
11
|
|
|
12
|
+
// Ensure setup has run for this version
|
|
13
|
+
;(async function ensureSetup() {
|
|
31
14
|
try {
|
|
32
|
-
|
|
33
|
-
const cmd = registry.getByName(commandName)
|
|
34
|
-
|
|
35
|
-
if (!cmd) {
|
|
36
|
-
console.error(`Unknown command: ${commandName}`)
|
|
37
|
-
console.error(`\nUse 'prjct --help' to see available commands.`)
|
|
38
|
-
process.exit(1)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 2. Check if deprecated (before checking implemented)
|
|
42
|
-
if (cmd.deprecated) {
|
|
43
|
-
console.error(`Command '${commandName}' is deprecated.`)
|
|
44
|
-
if (cmd.replacedBy) {
|
|
45
|
-
console.error(`Use 'prjct ${cmd.replacedBy}' instead.`)
|
|
46
|
-
}
|
|
47
|
-
process.exit(1)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 3. Check if implemented
|
|
51
|
-
if (!cmd.implemented) {
|
|
52
|
-
console.error(`Command '${commandName}' exists but is not yet implemented.`)
|
|
53
|
-
console.error(`Check the roadmap or contribute: https://github.com/jlopezlira/prjct-cli`)
|
|
54
|
-
console.error(`\nUse 'prjct --help' to see available commands.`)
|
|
55
|
-
process.exit(1)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 4. Parse arguments based on command definition
|
|
59
|
-
const { parsedArgs, options } = parseCommandArgs(cmd, rawArgs)
|
|
60
|
-
|
|
61
|
-
// 5. Instantiate commands handler
|
|
62
|
-
const commands = new PrjctCommands()
|
|
15
|
+
const lastVersion = await editorsConfig.getLastVersion()
|
|
63
16
|
|
|
64
|
-
|
|
65
|
-
|
|
17
|
+
if (!lastVersion || lastVersion !== VERSION) {
|
|
18
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
19
|
+
console.log('🔧 One-time setup (v' + VERSION + ')...')
|
|
20
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
|
|
66
21
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const target = parsedArgs.join(' ')
|
|
70
|
-
result = await commands.design(target, options)
|
|
71
|
-
} else if (commandName === 'analyze') {
|
|
72
|
-
result = await commands.analyze(options)
|
|
73
|
-
} else if (commandName === 'cleanup') {
|
|
74
|
-
result = await commands.cleanup(options)
|
|
75
|
-
} else if (commandName === 'setup') {
|
|
76
|
-
result = await commands.setup(options)
|
|
77
|
-
} else if (commandName === 'migrate-all') {
|
|
78
|
-
result = await commands.migrateAll(options)
|
|
79
|
-
} else if (commandName === 'progress') {
|
|
80
|
-
const period = parsedArgs[0] || 'week'
|
|
81
|
-
result = await commands.progress(period)
|
|
82
|
-
} else if (commandName === 'build') {
|
|
83
|
-
const taskOrNumber = parsedArgs.join(' ')
|
|
84
|
-
result = await commands.build(taskOrNumber)
|
|
85
|
-
} else {
|
|
86
|
-
// Standard commands (most common case)
|
|
87
|
-
const param = parsedArgs.join(' ') || null
|
|
88
|
-
result = await commands[commandName](param)
|
|
89
|
-
}
|
|
22
|
+
const setup = require('../core/infrastructure/setup')
|
|
23
|
+
await setup.run()
|
|
90
24
|
|
|
91
|
-
|
|
92
|
-
if (result && result.message) {
|
|
93
|
-
console.log(result.message)
|
|
25
|
+
console.log('✓ Setup complete!\n')
|
|
94
26
|
}
|
|
95
|
-
|
|
96
|
-
process.exit(result && result.success ? 0 : 1)
|
|
97
27
|
} catch (error) {
|
|
98
|
-
console.error('
|
|
99
|
-
|
|
100
|
-
console.error(error.stack)
|
|
101
|
-
}
|
|
102
|
-
process.exit(1)
|
|
28
|
+
console.error('\n⚠️ Setup warning:', error.message)
|
|
29
|
+
console.error('You can run setup manually: prjct setup\n')
|
|
103
30
|
}
|
|
104
|
-
}
|
|
105
31
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
function parseCommandArgs(cmd, rawArgs) {
|
|
110
|
-
const parsedArgs = []
|
|
111
|
-
const options = {}
|
|
112
|
-
|
|
113
|
-
for (let i = 0; i < rawArgs.length; i++) {
|
|
114
|
-
const arg = rawArgs[i]
|
|
115
|
-
|
|
116
|
-
if (arg.startsWith('--')) {
|
|
117
|
-
// Handle flags
|
|
118
|
-
const flagName = arg.slice(2)
|
|
119
|
-
|
|
120
|
-
// Check if next arg is a value (doesn't start with --)
|
|
121
|
-
if (i + 1 < rawArgs.length && !rawArgs[i + 1].startsWith('--')) {
|
|
122
|
-
options[flagName] = rawArgs[++i]
|
|
123
|
-
} else {
|
|
124
|
-
options[flagName] = true
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
parsedArgs.push(arg)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return { parsedArgs, options }
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Display help using registry
|
|
136
|
-
*/
|
|
137
|
-
function displayHelp() {
|
|
138
|
-
const categories = registry.getCategories()
|
|
139
|
-
const categorizedCommands = {}
|
|
140
|
-
|
|
141
|
-
// Group commands by category (exclude deprecated)
|
|
142
|
-
registry.getTerminalCommands().forEach((cmd) => {
|
|
143
|
-
if (cmd.deprecated) return
|
|
144
|
-
|
|
145
|
-
if (!categorizedCommands[cmd.category]) {
|
|
146
|
-
categorizedCommands[cmd.category] = []
|
|
147
|
-
}
|
|
148
|
-
categorizedCommands[cmd.category].push(cmd)
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
console.log('prjct - Developer momentum tool for solo builders')
|
|
152
|
-
console.log('\nAvailable commands:\n')
|
|
153
|
-
|
|
154
|
-
// Display commands by category
|
|
155
|
-
Object.entries(categorizedCommands).forEach(([categoryKey, cmds]) => {
|
|
156
|
-
const categoryInfo = categories[categoryKey]
|
|
157
|
-
console.log(` ${categoryInfo.title}:`)
|
|
158
|
-
|
|
159
|
-
cmds.forEach((cmd) => {
|
|
160
|
-
const params = cmd.params ? ` ${cmd.params}` : ''
|
|
161
|
-
const spacing = ' '.repeat(Math.max(20 - cmd.name.length - params.length, 1))
|
|
162
|
-
const impl = cmd.implemented ? '' : ' (not implemented)'
|
|
163
|
-
console.log(` ${cmd.name}${params}${spacing}${cmd.description}${impl}`)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
console.log('')
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
const stats = registry.getStats()
|
|
170
|
-
console.log(`Total: ${stats.implemented} implemented / ${stats.total} commands`)
|
|
171
|
-
console.log('\nFor more info: https://prjct.app')
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Run CLI
|
|
175
|
-
main().catch((error) => {
|
|
176
|
-
console.error('Fatal error:', error.message)
|
|
177
|
-
if (process.env.DEBUG) {
|
|
178
|
-
console.error(error.stack)
|
|
179
|
-
}
|
|
180
|
-
process.exit(1)
|
|
181
|
-
})
|
|
32
|
+
// Continue to main CLI logic
|
|
33
|
+
require('../core/index')
|
|
34
|
+
})()
|
package/core/index.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prjct CLI - Main Entry Point
|
|
3
|
+
*
|
|
4
|
+
* This file is required by bin/prjct after setup verification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const PrjctCommands = require('./commands')
|
|
8
|
+
const registry = require('./command-registry')
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const [commandName, ...rawArgs] = process.argv.slice(2)
|
|
12
|
+
|
|
13
|
+
// === SPECIAL COMMANDS (version, help) ===
|
|
14
|
+
|
|
15
|
+
if (['-v', '--version', 'version'].includes(commandName)) {
|
|
16
|
+
const packageJson = require('../package.json')
|
|
17
|
+
console.log(`prjct-cli v${packageJson.version}`)
|
|
18
|
+
process.exit(0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (['-h', '--help', 'help', undefined].includes(commandName)) {
|
|
22
|
+
displayHelp()
|
|
23
|
+
process.exit(0)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// === DYNAMIC COMMAND EXECUTION ===
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// 1. Find command in registry
|
|
30
|
+
const cmd = registry.getByName(commandName)
|
|
31
|
+
|
|
32
|
+
if (!cmd) {
|
|
33
|
+
console.error(`Unknown command: ${commandName}`)
|
|
34
|
+
console.error(`\nUse 'prjct --help' to see available commands.`)
|
|
35
|
+
process.exit(1)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Check if deprecated
|
|
39
|
+
if (cmd.deprecated) {
|
|
40
|
+
console.error(`Command '${commandName}' is deprecated.`)
|
|
41
|
+
if (cmd.replacedBy) {
|
|
42
|
+
console.error(`Use 'prjct ${cmd.replacedBy}' instead.`)
|
|
43
|
+
}
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. Check if implemented
|
|
48
|
+
if (!cmd.implemented) {
|
|
49
|
+
console.error(`Command '${commandName}' exists but is not yet implemented.`)
|
|
50
|
+
console.error(`Check the roadmap or contribute: https://github.com/jlopezlira/prjct-cli`)
|
|
51
|
+
console.error(`\nUse 'prjct --help' to see available commands.`)
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 4. Parse arguments
|
|
56
|
+
const { parsedArgs, options } = parseCommandArgs(cmd, rawArgs)
|
|
57
|
+
|
|
58
|
+
// 5. Instantiate commands handler
|
|
59
|
+
const commands = new PrjctCommands()
|
|
60
|
+
|
|
61
|
+
// 6. Execute command
|
|
62
|
+
let result
|
|
63
|
+
|
|
64
|
+
// Commands with special option handling
|
|
65
|
+
if (commandName === 'design') {
|
|
66
|
+
const target = parsedArgs.join(' ')
|
|
67
|
+
result = await commands.design(target, options)
|
|
68
|
+
} else if (commandName === 'analyze') {
|
|
69
|
+
result = await commands.analyze(options)
|
|
70
|
+
} else if (commandName === 'cleanup') {
|
|
71
|
+
result = await commands.cleanup(options)
|
|
72
|
+
} else if (commandName === 'setup') {
|
|
73
|
+
result = await commands.setup(options)
|
|
74
|
+
} else if (commandName === 'migrate-all') {
|
|
75
|
+
result = await commands.migrateAll(options)
|
|
76
|
+
} else if (commandName === 'progress') {
|
|
77
|
+
const period = parsedArgs[0] || 'week'
|
|
78
|
+
result = await commands.progress(period)
|
|
79
|
+
} else if (commandName === 'build') {
|
|
80
|
+
const taskOrNumber = parsedArgs.join(' ')
|
|
81
|
+
result = await commands.build(taskOrNumber)
|
|
82
|
+
} else {
|
|
83
|
+
// Standard commands
|
|
84
|
+
const param = parsedArgs.join(' ') || null
|
|
85
|
+
result = await commands[commandName](param)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 7. Display result
|
|
89
|
+
if (result && result.message) {
|
|
90
|
+
console.log(result.message)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
process.exit(result && result.success ? 0 : 1)
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('Error:', error.message)
|
|
96
|
+
if (process.env.DEBUG) {
|
|
97
|
+
console.error(error.stack)
|
|
98
|
+
}
|
|
99
|
+
process.exit(1)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse command arguments dynamically
|
|
105
|
+
*/
|
|
106
|
+
function parseCommandArgs(cmd, rawArgs) {
|
|
107
|
+
const parsedArgs = []
|
|
108
|
+
const options = {}
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
111
|
+
const arg = rawArgs[i]
|
|
112
|
+
|
|
113
|
+
if (arg.startsWith('--')) {
|
|
114
|
+
// Handle flags
|
|
115
|
+
const flagName = arg.slice(2)
|
|
116
|
+
|
|
117
|
+
// Check if next arg is a value
|
|
118
|
+
if (i + 1 < rawArgs.length && !rawArgs[i + 1].startsWith('--')) {
|
|
119
|
+
options[flagName] = rawArgs[++i]
|
|
120
|
+
} else {
|
|
121
|
+
options[flagName] = true
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
parsedArgs.push(arg)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { parsedArgs, options }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Display help using registry
|
|
133
|
+
*/
|
|
134
|
+
function displayHelp() {
|
|
135
|
+
const categories = registry.getCategories()
|
|
136
|
+
const categorizedCommands = {}
|
|
137
|
+
|
|
138
|
+
// Group commands by category (exclude deprecated)
|
|
139
|
+
registry.getTerminalCommands().forEach((cmd) => {
|
|
140
|
+
if (cmd.deprecated) return
|
|
141
|
+
|
|
142
|
+
if (!categorizedCommands[cmd.category]) {
|
|
143
|
+
categorizedCommands[cmd.category] = []
|
|
144
|
+
}
|
|
145
|
+
categorizedCommands[cmd.category].push(cmd)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
console.log('prjct - Developer momentum tool for solo builders')
|
|
149
|
+
console.log('\nAvailable commands:\n')
|
|
150
|
+
|
|
151
|
+
// Display commands by category
|
|
152
|
+
Object.entries(categorizedCommands).forEach(([categoryKey, cmds]) => {
|
|
153
|
+
const categoryInfo = categories[categoryKey]
|
|
154
|
+
console.log(` ${categoryInfo.title}:`)
|
|
155
|
+
|
|
156
|
+
cmds.forEach((cmd) => {
|
|
157
|
+
const params = cmd.params ? ` ${cmd.params}` : ''
|
|
158
|
+
const spacing = ' '.repeat(Math.max(20 - cmd.name.length - params.length, 1))
|
|
159
|
+
const impl = cmd.implemented ? '' : ' (not implemented)'
|
|
160
|
+
console.log(` ${cmd.name}${params}${spacing}${cmd.description}${impl}`)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
console.log('')
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const stats = registry.getStats()
|
|
167
|
+
console.log(`Total: ${stats.implemented} implemented / ${stats.total} commands`)
|
|
168
|
+
console.log('\nFor more info: https://prjct.app')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Run CLI
|
|
172
|
+
main().catch((error) => {
|
|
173
|
+
console.error('Fatal error:', error.message)
|
|
174
|
+
if (process.env.DEBUG) {
|
|
175
|
+
console.error(error.stack)
|
|
176
|
+
}
|
|
177
|
+
process.exit(1)
|
|
178
|
+
})
|
|
@@ -280,7 +280,7 @@ class CommandInstaller {
|
|
|
280
280
|
errors: [],
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
//
|
|
283
|
+
// Install/update all template files (always overwrite)
|
|
284
284
|
for (const file of templateFiles) {
|
|
285
285
|
try {
|
|
286
286
|
const sourcePath = path.join(this.templatesDir, file)
|
|
@@ -289,40 +289,23 @@ class CommandInstaller {
|
|
|
289
289
|
// Check if file exists in installed location
|
|
290
290
|
const exists = installedFiles.includes(file)
|
|
291
291
|
|
|
292
|
+
// Read and write (always overwrite to ensure latest version)
|
|
293
|
+
const content = await fs.readFile(sourcePath, 'utf-8')
|
|
294
|
+
await fs.writeFile(destPath, content, 'utf-8')
|
|
295
|
+
|
|
292
296
|
if (!exists) {
|
|
293
|
-
// New file
|
|
294
|
-
const content = await fs.readFile(sourcePath, 'utf-8')
|
|
295
|
-
await fs.writeFile(destPath, content, 'utf-8')
|
|
296
297
|
results.added++
|
|
297
298
|
} else {
|
|
298
|
-
|
|
299
|
-
const sourceStats = await fs.stat(sourcePath)
|
|
300
|
-
const destStats = await fs.stat(destPath)
|
|
301
|
-
|
|
302
|
-
if (sourceStats.mtime > destStats.mtime) {
|
|
303
|
-
// Updated file
|
|
304
|
-
const content = await fs.readFile(sourcePath, 'utf-8')
|
|
305
|
-
await fs.writeFile(destPath, content, 'utf-8')
|
|
306
|
-
results.updated++
|
|
307
|
-
}
|
|
299
|
+
results.updated++
|
|
308
300
|
}
|
|
309
301
|
} catch (error) {
|
|
310
302
|
results.errors.push({ file, error: error.message })
|
|
311
303
|
}
|
|
312
304
|
}
|
|
313
305
|
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
for (const orphan of orphans) {
|
|
318
|
-
try {
|
|
319
|
-
const orphanPath = path.join(this.claudeCommandsPath, orphan)
|
|
320
|
-
await fs.unlink(orphanPath)
|
|
321
|
-
results.removed++
|
|
322
|
-
} catch (error) {
|
|
323
|
-
results.errors.push({ file: orphan, error: error.message })
|
|
324
|
-
}
|
|
325
|
-
}
|
|
306
|
+
// Note: We do NOT remove orphaned files
|
|
307
|
+
// Legacy commands from older versions are preserved
|
|
308
|
+
// to avoid breaking existing workflows
|
|
326
309
|
|
|
327
310
|
return results
|
|
328
311
|
} catch (error) {
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const { execSync } = require('child_process')
|
|
2
|
+
const installer = require('./command-installer')
|
|
3
|
+
const migrator = require('./migrator')
|
|
4
|
+
const editorsConfig = require('./editors-config')
|
|
5
|
+
const { VERSION } = require('../utils/version')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Setup Module - Core installation logic
|
|
9
|
+
*
|
|
10
|
+
* Ejecuta TODO el setup necesario para prjct-cli:
|
|
11
|
+
* 1. Instalar Claude Code CLI si falta
|
|
12
|
+
* 2. Sincronizar comandos a ~/.claude/commands/p/
|
|
13
|
+
* 3. Instalar configuración global ~/.claude/CLAUDE.md
|
|
14
|
+
* 4. Migrar proyectos legacy automáticamente
|
|
15
|
+
* 5. Guardar versión en editors-config
|
|
16
|
+
*
|
|
17
|
+
* Este módulo es llamado desde:
|
|
18
|
+
* - core/index.js (en primer uso del CLI)
|
|
19
|
+
* - scripts/postinstall.js (si npm scripts están habilitados)
|
|
20
|
+
*
|
|
21
|
+
* @version 0.8.5
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// Colors
|
|
25
|
+
const CYAN = '\x1b[36m'
|
|
26
|
+
const GREEN = '\x1b[32m'
|
|
27
|
+
const YELLOW = '\x1b[33m'
|
|
28
|
+
const MAGENTA = '\x1b[35m'
|
|
29
|
+
const BOLD = '\x1b[1m'
|
|
30
|
+
const DIM = '\x1b[2m'
|
|
31
|
+
const NC = '\x1b[0m'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if Claude Code CLI is installed
|
|
35
|
+
*/
|
|
36
|
+
async function hasClaudeCodeCLI() {
|
|
37
|
+
try {
|
|
38
|
+
execSync('which claude', { stdio: 'ignore' })
|
|
39
|
+
return true
|
|
40
|
+
} catch {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Install Claude Code CLI
|
|
47
|
+
*/
|
|
48
|
+
async function installClaudeCode() {
|
|
49
|
+
try {
|
|
50
|
+
console.log(`${YELLOW}📦 Claude Code not found. Installing...${NC}`)
|
|
51
|
+
console.log('')
|
|
52
|
+
execSync('npm install -g @anthropic-ai/claude-code', { stdio: 'inherit' })
|
|
53
|
+
console.log('')
|
|
54
|
+
console.log(`${GREEN}✓${NC} Claude Code installed successfully`)
|
|
55
|
+
console.log('')
|
|
56
|
+
return true
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.log(`${YELLOW}⚠️ Failed to install Claude Code: ${error.message}${NC}`)
|
|
59
|
+
console.log(`${DIM}Please install manually: npm install -g @anthropic-ai/claude-code${NC}`)
|
|
60
|
+
console.log('')
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Main setup function
|
|
67
|
+
*/
|
|
68
|
+
async function run() {
|
|
69
|
+
const results = {
|
|
70
|
+
claudeInstalled: false,
|
|
71
|
+
commandsAdded: 0,
|
|
72
|
+
commandsUpdated: 0,
|
|
73
|
+
configAction: null,
|
|
74
|
+
projectsMigrated: 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Step 1: Ensure Claude Code CLI is installed
|
|
78
|
+
const hasClaude = await hasClaudeCodeCLI()
|
|
79
|
+
|
|
80
|
+
if (!hasClaude) {
|
|
81
|
+
const installed = await installClaudeCode()
|
|
82
|
+
if (installed) {
|
|
83
|
+
results.claudeInstalled = true
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error('Claude Code installation failed')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Step 2: Detect Claude directory (for commands)
|
|
90
|
+
const claudeDetected = await installer.detectClaude()
|
|
91
|
+
|
|
92
|
+
if (claudeDetected) {
|
|
93
|
+
// Step 3: Sync commands
|
|
94
|
+
const syncResult = await installer.syncCommands()
|
|
95
|
+
|
|
96
|
+
if (syncResult.success) {
|
|
97
|
+
results.commandsAdded = syncResult.added
|
|
98
|
+
results.commandsUpdated = syncResult.updated
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Step 4: Install global configuration
|
|
102
|
+
const configResult = await installer.installGlobalConfig()
|
|
103
|
+
|
|
104
|
+
if (configResult.success) {
|
|
105
|
+
results.configAction = configResult.action
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Step 5: Migrate legacy projects automatically
|
|
109
|
+
const migrationResult = await migrator.migrateAll({
|
|
110
|
+
deepScan: false,
|
|
111
|
+
cleanupLegacy: true,
|
|
112
|
+
dryRun: false
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
if (migrationResult.successfullyMigrated > 0) {
|
|
116
|
+
results.projectsMigrated = migrationResult.successfullyMigrated
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Step 6: Save version in editors-config
|
|
121
|
+
await editorsConfig.saveConfig(VERSION, installer.getInstallPath())
|
|
122
|
+
|
|
123
|
+
// Show results
|
|
124
|
+
showResults(results)
|
|
125
|
+
|
|
126
|
+
return results
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Show setup results
|
|
131
|
+
*/
|
|
132
|
+
function showResults(results) {
|
|
133
|
+
console.log('')
|
|
134
|
+
|
|
135
|
+
// Show what was done
|
|
136
|
+
if (results.claudeInstalled) {
|
|
137
|
+
console.log(` ${GREEN}✓${NC} Claude Code CLI installed`)
|
|
138
|
+
} else {
|
|
139
|
+
console.log(` ${GREEN}✓${NC} Claude Code CLI found`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const totalCommands = results.commandsAdded + results.commandsUpdated
|
|
143
|
+
if (totalCommands > 0) {
|
|
144
|
+
const parts = []
|
|
145
|
+
if (results.commandsAdded > 0) parts.push(`${results.commandsAdded} new`)
|
|
146
|
+
if (results.commandsUpdated > 0) parts.push(`${results.commandsUpdated} updated`)
|
|
147
|
+
console.log(` ${GREEN}✓${NC} Commands synced (${parts.join(', ')})`)
|
|
148
|
+
} else {
|
|
149
|
+
console.log(` ${GREEN}✓${NC} Commands up to date`)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (results.configAction === 'created') {
|
|
153
|
+
console.log(` ${GREEN}✓${NC} Global config created`)
|
|
154
|
+
} else if (results.configAction === 'updated') {
|
|
155
|
+
console.log(` ${GREEN}✓${NC} Global config updated`)
|
|
156
|
+
} else if (results.configAction === 'appended') {
|
|
157
|
+
console.log(` ${GREEN}✓${NC} Global config merged`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (results.projectsMigrated > 0) {
|
|
161
|
+
console.log(` ${GREEN}✓${NC} ${results.projectsMigrated} projects migrated to global storage`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = { run }
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -1,114 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Post-install hook for prjct-cli
|
|
4
|
+
* Post-install hook for prjct-cli (OPTIONAL)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Intenta ejecutar setup si npm scripts están habilitados.
|
|
7
|
+
* Si falla o no se ejecuta, no hay problema - el setup se ejecutará
|
|
8
|
+
* automáticamente en el primer uso del CLI (como Astro, Vite, etc.)
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
* 2. Installs Claude Code CLI if not found (automatic)
|
|
10
|
-
* 3. Installs/syncs commands to ~/.claude/commands/p/
|
|
11
|
-
* 4. Installs/updates global config to ~/.claude/CLAUDE.md
|
|
12
|
-
* 5. Migrates all legacy projects automatically
|
|
13
|
-
* 6. Shows success message with results
|
|
10
|
+
* Este hook es una optimización pero NO es crítico.
|
|
14
11
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @version 0.8.4
|
|
12
|
+
* @version 0.8.5
|
|
18
13
|
*/
|
|
19
14
|
|
|
20
15
|
const path = require('path')
|
|
21
|
-
const
|
|
22
|
-
const installer = require('../core/infrastructure/command-installer')
|
|
23
|
-
const migrator = require('../core/infrastructure/migrator')
|
|
24
|
-
const { VERSION } = require('../core/utils/version')
|
|
25
|
-
|
|
26
|
-
// Colors for terminal output
|
|
27
|
-
const CYAN = '\x1b[36m'
|
|
28
|
-
const GREEN = '\x1b[32m'
|
|
29
|
-
const YELLOW = '\x1b[33m'
|
|
30
|
-
const MAGENTA = '\x1b[35m'
|
|
31
|
-
const WHITE = '\x1b[37m'
|
|
32
|
-
const BOLD = '\x1b[1m'
|
|
33
|
-
const DIM = '\x1b[2m'
|
|
34
|
-
const NC = '\x1b[0m' // No Color
|
|
16
|
+
const setup = require('../core/infrastructure/setup')
|
|
35
17
|
|
|
36
18
|
async function main() {
|
|
37
19
|
try {
|
|
38
20
|
// Detect if this is a global install
|
|
39
|
-
const isGlobal =
|
|
21
|
+
const isGlobal = detectGlobalInstall()
|
|
40
22
|
|
|
41
23
|
if (!isGlobal) {
|
|
42
24
|
// Skip postinstall for local development installs
|
|
43
|
-
console.log(`${DIM}Skipping post-install (local development)${NC}`)
|
|
44
25
|
return
|
|
45
26
|
}
|
|
46
27
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
console.log(`${BOLD}${CYAN}🚀 Setting up prjct-cli v${VERSION}${NC}`)
|
|
50
|
-
console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`)
|
|
51
|
-
console.log('')
|
|
52
|
-
|
|
53
|
-
const results = {
|
|
54
|
-
claudeInstalled: false,
|
|
55
|
-
commandsAdded: 0,
|
|
56
|
-
commandsUpdated: 0,
|
|
57
|
-
configAction: null,
|
|
58
|
-
projectsMigrated: 0
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Step 1: Ensure Claude Code CLI is installed
|
|
62
|
-
const hasClaude = await hasClaudeCodeCLI()
|
|
63
|
-
|
|
64
|
-
if (!hasClaude) {
|
|
65
|
-
const installed = await installClaudeCode()
|
|
66
|
-
if (installed) {
|
|
67
|
-
results.claudeInstalled = true
|
|
68
|
-
} else {
|
|
69
|
-
// Cannot continue without Claude Code
|
|
70
|
-
showFailureMessage()
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Step 2: Detect Claude directory (for commands)
|
|
76
|
-
const claudeDetected = await installer.detectClaude()
|
|
77
|
-
|
|
78
|
-
if (claudeDetected) {
|
|
79
|
-
// Step 3: Sync commands
|
|
80
|
-
const syncResult = await installer.syncCommands()
|
|
81
|
-
|
|
82
|
-
if (syncResult.success) {
|
|
83
|
-
results.commandsAdded = syncResult.added
|
|
84
|
-
results.commandsUpdated = syncResult.updated
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Step 4: Install global configuration
|
|
88
|
-
const configResult = await installer.installGlobalConfig()
|
|
89
|
-
|
|
90
|
-
if (configResult.success) {
|
|
91
|
-
results.configAction = configResult.action
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Step 5: Migrate legacy projects automatically
|
|
95
|
-
const migrationResult = await migrator.migrateAll({
|
|
96
|
-
deepScan: false,
|
|
97
|
-
cleanupLegacy: true,
|
|
98
|
-
dryRun: false
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
if (migrationResult.successfullyMigrated > 0) {
|
|
102
|
-
results.projectsMigrated = migrationResult.successfullyMigrated
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Show final success message
|
|
107
|
-
showSuccessMessage(results)
|
|
28
|
+
// Run setup (all logic is in core/infrastructure/setup.js)
|
|
29
|
+
await setup.run()
|
|
108
30
|
|
|
109
31
|
} catch (error) {
|
|
110
|
-
|
|
111
|
-
console.error(`${DIM}You can run 'prjct setup' manually later${NC}`)
|
|
32
|
+
// Silent failure - setup will run on first use anyway
|
|
112
33
|
// Don't fail the install if post-install has issues
|
|
113
34
|
process.exit(0)
|
|
114
35
|
}
|
|
@@ -117,7 +38,7 @@ async function main() {
|
|
|
117
38
|
/**
|
|
118
39
|
* Detect if this is a global npm install
|
|
119
40
|
*/
|
|
120
|
-
|
|
41
|
+
function detectGlobalInstall() {
|
|
121
42
|
// Check if we're being installed globally
|
|
122
43
|
const npmConfig = process.env.npm_config_global
|
|
123
44
|
if (npmConfig === 'true') {
|
|
@@ -137,118 +58,6 @@ async function detectGlobalInstall() {
|
|
|
137
58
|
return globalPaths.some(globalPath => installPath.includes(globalPath))
|
|
138
59
|
}
|
|
139
60
|
|
|
140
|
-
/**
|
|
141
|
-
* Check if Claude Code CLI is installed
|
|
142
|
-
*/
|
|
143
|
-
async function hasClaudeCodeCLI() {
|
|
144
|
-
try {
|
|
145
|
-
execSync('which claude', { stdio: 'ignore' })
|
|
146
|
-
return true
|
|
147
|
-
} catch {
|
|
148
|
-
return false
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Install Claude Code CLI
|
|
154
|
-
*/
|
|
155
|
-
async function installClaudeCode() {
|
|
156
|
-
try {
|
|
157
|
-
console.log(`${YELLOW}📦 Claude Code not found. Installing...${NC}`)
|
|
158
|
-
console.log('')
|
|
159
|
-
execSync('npm install -g @anthropic-ai/claude-code', { stdio: 'inherit' })
|
|
160
|
-
console.log('')
|
|
161
|
-
console.log(`${GREEN}✓${NC} Claude Code installed successfully`)
|
|
162
|
-
console.log('')
|
|
163
|
-
return true
|
|
164
|
-
} catch (error) {
|
|
165
|
-
console.log(`${YELLOW}⚠️ Failed to install Claude Code: ${error.message}${NC}`)
|
|
166
|
-
console.log(`${DIM}Please install manually: npm install -g @anthropic-ai/claude-code${NC}`)
|
|
167
|
-
console.log('')
|
|
168
|
-
return false
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Show success message with results
|
|
174
|
-
*/
|
|
175
|
-
function showSuccessMessage(results) {
|
|
176
|
-
console.log('')
|
|
177
|
-
console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`)
|
|
178
|
-
console.log('')
|
|
179
|
-
console.log(` ${BOLD}${CYAN}██████╗ ██████╗ ██╗ ██████╗████████╗${NC}`)
|
|
180
|
-
console.log(` ${BOLD}${CYAN}██╔══██╗██╔══██╗ ██║██╔════╝╚══██╔══╝${NC}`)
|
|
181
|
-
console.log(` ${BOLD}${CYAN}██████╔╝██████╔╝ ██║██║ ██║${NC}`)
|
|
182
|
-
console.log(` ${BOLD}${CYAN}██╔═══╝ ██╔══██╗██ ██║██║ ██║${NC}`)
|
|
183
|
-
console.log(` ${BOLD}${CYAN}██║ ██║ ██║╚█████╔╝╚██████╗ ██║${NC}`)
|
|
184
|
-
console.log(` ${BOLD}${CYAN}╚═╝ ╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═╝${NC}`)
|
|
185
|
-
console.log('')
|
|
186
|
-
console.log(` ${BOLD}${GREEN}v${VERSION} READY!${NC}`)
|
|
187
|
-
console.log('')
|
|
188
|
-
console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`)
|
|
189
|
-
console.log('')
|
|
190
|
-
|
|
191
|
-
// Show what was done
|
|
192
|
-
if (results.claudeInstalled) {
|
|
193
|
-
console.log(` ${GREEN}✓${NC} Claude Code CLI installed`)
|
|
194
|
-
} else {
|
|
195
|
-
console.log(` ${GREEN}✓${NC} Claude Code CLI found`)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const totalCommands = results.commandsAdded + results.commandsUpdated
|
|
199
|
-
if (totalCommands > 0) {
|
|
200
|
-
const parts = []
|
|
201
|
-
if (results.commandsAdded > 0) parts.push(`${results.commandsAdded} new`)
|
|
202
|
-
if (results.commandsUpdated > 0) parts.push(`${results.commandsUpdated} updated`)
|
|
203
|
-
console.log(` ${GREEN}✓${NC} Commands synced (${parts.join(', ')})`)
|
|
204
|
-
} else {
|
|
205
|
-
console.log(` ${GREEN}✓${NC} Commands up to date`)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (results.configAction === 'created') {
|
|
209
|
-
console.log(` ${GREEN}✓${NC} Global config created`)
|
|
210
|
-
} else if (results.configAction === 'updated') {
|
|
211
|
-
console.log(` ${GREEN}✓${NC} Global config updated`)
|
|
212
|
-
} else if (results.configAction === 'appended') {
|
|
213
|
-
console.log(` ${GREEN}✓${NC} Global config merged`)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (results.projectsMigrated > 0) {
|
|
217
|
-
console.log(` ${GREEN}✓${NC} ${results.projectsMigrated} projects migrated to global storage`)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
console.log('')
|
|
221
|
-
console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`)
|
|
222
|
-
console.log('')
|
|
223
|
-
console.log(`${BOLD}Next steps:${NC}`)
|
|
224
|
-
console.log(` ${DIM}cd your-project${NC}`)
|
|
225
|
-
console.log(` ${GREEN}/p:init${NC}`)
|
|
226
|
-
console.log('')
|
|
227
|
-
console.log(`${BOLD}${MAGENTA}Happy shipping! 🚀${NC}`)
|
|
228
|
-
console.log('')
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Show failure message
|
|
233
|
-
*/
|
|
234
|
-
function showFailureMessage() {
|
|
235
|
-
console.log('')
|
|
236
|
-
console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`)
|
|
237
|
-
console.log('')
|
|
238
|
-
console.log(`${YELLOW}⚠️ Setup incomplete${NC}`)
|
|
239
|
-
console.log('')
|
|
240
|
-
console.log(`Claude Code is required for prjct-cli.`)
|
|
241
|
-
console.log('')
|
|
242
|
-
console.log(`Please install manually:`)
|
|
243
|
-
console.log(` ${GREEN}npm install -g @anthropic-ai/claude-code${NC}`)
|
|
244
|
-
console.log('')
|
|
245
|
-
console.log(`Then run:`)
|
|
246
|
-
console.log(` ${GREEN}prjct setup${NC}`)
|
|
247
|
-
console.log('')
|
|
248
|
-
console.log(`${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}`)
|
|
249
|
-
console.log('')
|
|
250
|
-
}
|
|
251
|
-
|
|
252
61
|
// Run if executed directly
|
|
253
62
|
if (require.main === module) {
|
|
254
63
|
main().catch(error => {
|