@wyxos/zephyr 0.2.21 → 0.2.23
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 +144 -144
- package/bin/zephyr.mjs +29 -29
- package/package.json +58 -58
- package/src/config/project.mjs +118 -118
- package/src/config/servers.mjs +57 -57
- package/src/dependency-scanner.mjs +412 -433
- package/src/deploy/local-repo.mjs +215 -215
- package/src/deploy/locks.mjs +171 -171
- package/src/deploy/preflight.mjs +117 -117
- package/src/deploy/remote-exec.mjs +99 -99
- package/src/deploy/snapshots.mjs +35 -35
- package/src/index.mjs +91 -91
- package/src/main.mjs +677 -652
- package/src/project/bootstrap.mjs +147 -147
- package/src/runtime/local-command.mjs +18 -18
- package/src/runtime/prompt.mjs +14 -14
- package/src/runtime/ssh-client.mjs +14 -14
- package/src/ssh/index.mjs +8 -8
- package/src/ssh/keys.mjs +146 -146
- package/src/ssh/ssh.mjs +134 -134
- package/src/utils/command.mjs +92 -92
- package/src/utils/config-flow.mjs +284 -284
- package/src/utils/git.mjs +91 -91
- package/src/utils/id.mjs +6 -6
- package/src/utils/log-file.mjs +76 -76
- package/src/utils/output.mjs +29 -29
- package/src/utils/paths.mjs +28 -28
- package/src/utils/php-version.mjs +137 -0
- package/src/utils/remote-path.mjs +23 -23
- package/src/utils/task-planner.mjs +99 -96
- package/src/version-checker.mjs +162 -162
|
@@ -1,284 +1,284 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
import inquirer from 'inquirer'
|
|
3
|
-
|
|
4
|
-
export function defaultProjectPath(currentDir) {
|
|
5
|
-
return `~/webapps/${path.basename(currentDir)}`
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export async function listGitBranches(currentDir, { runCommandCapture, logWarning } = {}) {
|
|
9
|
-
try {
|
|
10
|
-
const output = await runCommandCapture(
|
|
11
|
-
'git',
|
|
12
|
-
['branch', '--format', '%(refname:short)'],
|
|
13
|
-
{ cwd: currentDir }
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
const branches = output
|
|
17
|
-
.split(/\r?\n/)
|
|
18
|
-
.map((line) => line.trim())
|
|
19
|
-
.filter(Boolean)
|
|
20
|
-
|
|
21
|
-
return branches.length ? branches : ['master']
|
|
22
|
-
} catch (_error) {
|
|
23
|
-
logWarning?.('Unable to read git branches; defaulting to master.')
|
|
24
|
-
return ['master']
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function promptServerDetails(existingServers = [], { runPrompt, generateId } = {}) {
|
|
29
|
-
const defaults = {
|
|
30
|
-
serverName: existingServers.length === 0 ? 'home' : `server-${existingServers.length + 1}`,
|
|
31
|
-
serverIp: '1.1.1.1'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const answers = await runPrompt([
|
|
35
|
-
{
|
|
36
|
-
type: 'input',
|
|
37
|
-
name: 'serverName',
|
|
38
|
-
message: 'Server name',
|
|
39
|
-
default: defaults.serverName
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
type: 'input',
|
|
43
|
-
name: 'serverIp',
|
|
44
|
-
message: 'Server IP address',
|
|
45
|
-
default: defaults.serverIp
|
|
46
|
-
}
|
|
47
|
-
])
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
id: generateId(),
|
|
51
|
-
serverName: answers.serverName.trim() || defaults.serverName,
|
|
52
|
-
serverIp: answers.serverIp.trim() || defaults.serverIp
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function selectServer(servers, { runPrompt, logProcessing, logSuccess, saveServers, promptServerDetails: promptServerDetailsFn } = {}) {
|
|
57
|
-
if (servers.length === 0) {
|
|
58
|
-
logProcessing?.("No servers configured. Let's create one.")
|
|
59
|
-
const server = await promptServerDetailsFn()
|
|
60
|
-
servers.push(server)
|
|
61
|
-
await saveServers(servers)
|
|
62
|
-
logSuccess?.('Saved server configuration to ~/.config/zephyr/servers.json')
|
|
63
|
-
return server
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const choices = servers.map((server, index) => ({
|
|
67
|
-
name: `${server.serverName} (${server.serverIp})`,
|
|
68
|
-
value: index
|
|
69
|
-
}))
|
|
70
|
-
|
|
71
|
-
choices.push(new inquirer.Separator(), {
|
|
72
|
-
name: '➕ Register a new server',
|
|
73
|
-
value: 'create'
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
const { selection } = await runPrompt([
|
|
77
|
-
{
|
|
78
|
-
type: 'list',
|
|
79
|
-
name: 'selection',
|
|
80
|
-
message: 'Select server or register new',
|
|
81
|
-
choices,
|
|
82
|
-
default: 0
|
|
83
|
-
}
|
|
84
|
-
])
|
|
85
|
-
|
|
86
|
-
if (selection === 'create') {
|
|
87
|
-
const server = await promptServerDetailsFn(servers)
|
|
88
|
-
servers.push(server)
|
|
89
|
-
await saveServers(servers)
|
|
90
|
-
logSuccess?.('Appended server configuration to ~/.config/zephyr/servers.json')
|
|
91
|
-
return server
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return servers[selection]
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export async function promptAppDetails(currentDir, existing = {}, {
|
|
98
|
-
runPrompt,
|
|
99
|
-
listGitBranches,
|
|
100
|
-
defaultProjectPath,
|
|
101
|
-
promptSshDetails
|
|
102
|
-
} = {}) {
|
|
103
|
-
const branches = await listGitBranches(currentDir)
|
|
104
|
-
const defaultBranch = existing.branch || (branches.includes('master') ? 'master' : branches[0])
|
|
105
|
-
const defaults = {
|
|
106
|
-
projectPath: existing.projectPath || defaultProjectPath(currentDir),
|
|
107
|
-
branch: defaultBranch
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const answers = await runPrompt([
|
|
111
|
-
{
|
|
112
|
-
type: 'input',
|
|
113
|
-
name: 'projectPath',
|
|
114
|
-
message: 'Remote project path',
|
|
115
|
-
default: defaults.projectPath
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
type: 'list',
|
|
119
|
-
name: 'branchSelection',
|
|
120
|
-
message: 'Branch to deploy',
|
|
121
|
-
choices: [
|
|
122
|
-
...branches.map((branch) => ({ name: branch, value: branch })),
|
|
123
|
-
new inquirer.Separator(),
|
|
124
|
-
{ name: 'Enter custom branch…', value: '__custom' }
|
|
125
|
-
],
|
|
126
|
-
default: defaults.branch
|
|
127
|
-
}
|
|
128
|
-
])
|
|
129
|
-
|
|
130
|
-
let branch = answers.branchSelection
|
|
131
|
-
|
|
132
|
-
if (branch === '__custom') {
|
|
133
|
-
const { customBranch } = await runPrompt([
|
|
134
|
-
{
|
|
135
|
-
type: 'input',
|
|
136
|
-
name: 'customBranch',
|
|
137
|
-
message: 'Custom branch name',
|
|
138
|
-
default: defaults.branch
|
|
139
|
-
}
|
|
140
|
-
])
|
|
141
|
-
|
|
142
|
-
branch = customBranch.trim() || defaults.branch
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const sshDetails = await promptSshDetails(currentDir, existing)
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
projectPath: answers.projectPath.trim() || defaults.projectPath,
|
|
149
|
-
branch,
|
|
150
|
-
...sshDetails
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export async function selectApp(projectConfig, server, currentDir, {
|
|
155
|
-
runPrompt,
|
|
156
|
-
logWarning,
|
|
157
|
-
logProcessing,
|
|
158
|
-
logSuccess,
|
|
159
|
-
saveProjectConfig,
|
|
160
|
-
generateId,
|
|
161
|
-
promptAppDetails
|
|
162
|
-
} = {}) {
|
|
163
|
-
const apps = projectConfig.apps ?? []
|
|
164
|
-
const matches = apps
|
|
165
|
-
.map((app, index) => ({ app, index }))
|
|
166
|
-
.filter(({ app }) => app.serverId === server.id || app.serverName === server.serverName)
|
|
167
|
-
|
|
168
|
-
if (matches.length === 0) {
|
|
169
|
-
if (apps.length > 0) {
|
|
170
|
-
const availableServers = [...new Set(apps.map((app) => app.serverName).filter(Boolean))]
|
|
171
|
-
if (availableServers.length > 0) {
|
|
172
|
-
logWarning?.(
|
|
173
|
-
`No applications configured for server "${server.serverName}". Available servers: ${availableServers.join(', ')}`
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
logProcessing?.(`No applications configured for ${server.serverName}. Let's create one.`)
|
|
178
|
-
const appDetails = await promptAppDetails(currentDir)
|
|
179
|
-
const appConfig = {
|
|
180
|
-
id: generateId(),
|
|
181
|
-
serverId: server.id,
|
|
182
|
-
serverName: server.serverName,
|
|
183
|
-
...appDetails
|
|
184
|
-
}
|
|
185
|
-
projectConfig.apps.push(appConfig)
|
|
186
|
-
await saveProjectConfig(currentDir, projectConfig)
|
|
187
|
-
logSuccess?.('Saved deployment configuration to .zephyr/config.json')
|
|
188
|
-
return appConfig
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const choices = matches.map(({ app }, matchIndex) => ({
|
|
192
|
-
name: `${app.projectPath} (${app.branch})`,
|
|
193
|
-
value: matchIndex
|
|
194
|
-
}))
|
|
195
|
-
|
|
196
|
-
choices.push(new inquirer.Separator(), {
|
|
197
|
-
name: '➕ Configure new application for this server',
|
|
198
|
-
value: 'create'
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
const { selection } = await runPrompt([
|
|
202
|
-
{
|
|
203
|
-
type: 'list',
|
|
204
|
-
name: 'selection',
|
|
205
|
-
message: `Select application for ${server.serverName}`,
|
|
206
|
-
choices,
|
|
207
|
-
default: 0
|
|
208
|
-
}
|
|
209
|
-
])
|
|
210
|
-
|
|
211
|
-
if (selection === 'create') {
|
|
212
|
-
const appDetails = await promptAppDetails(currentDir)
|
|
213
|
-
const appConfig = {
|
|
214
|
-
id: generateId(),
|
|
215
|
-
serverId: server.id,
|
|
216
|
-
serverName: server.serverName,
|
|
217
|
-
...appDetails
|
|
218
|
-
}
|
|
219
|
-
projectConfig.apps.push(appConfig)
|
|
220
|
-
await saveProjectConfig(currentDir, projectConfig)
|
|
221
|
-
logSuccess?.('Appended deployment configuration to .zephyr/config.json')
|
|
222
|
-
return appConfig
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return matches[selection].app
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export async function selectPreset(projectConfig, servers, { runPrompt } = {}) {
|
|
229
|
-
const presets = projectConfig.presets ?? []
|
|
230
|
-
const apps = projectConfig.apps ?? []
|
|
231
|
-
|
|
232
|
-
if (presets.length === 0) {
|
|
233
|
-
return null
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const choices = presets.map((preset, index) => {
|
|
237
|
-
let displayName = preset.name
|
|
238
|
-
|
|
239
|
-
if (preset.appId) {
|
|
240
|
-
// New format: look up app by ID
|
|
241
|
-
const app = apps.find((a) => a.id === preset.appId)
|
|
242
|
-
if (app) {
|
|
243
|
-
const server = servers.find((s) => s.id === app.serverId || s.serverName === app.serverName)
|
|
244
|
-
const serverName = server?.serverName || 'unknown'
|
|
245
|
-
const branch = preset.branch || app.branch || 'unknown'
|
|
246
|
-
displayName = `${preset.name} (${serverName} → ${app.projectPath} [${branch}])`
|
|
247
|
-
}
|
|
248
|
-
} else if (preset.key) {
|
|
249
|
-
// Legacy format: parse from key
|
|
250
|
-
const keyParts = preset.key.split(':')
|
|
251
|
-
const serverName = keyParts[0]
|
|
252
|
-
const projectPath = keyParts[1]
|
|
253
|
-
const branch = preset.branch || (keyParts.length === 3 ? keyParts[2] : 'unknown')
|
|
254
|
-
displayName = `${preset.name} (${serverName} → ${projectPath} [${branch}])`
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
name: displayName,
|
|
259
|
-
value: index
|
|
260
|
-
}
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
choices.push(new inquirer.Separator(), {
|
|
264
|
-
name: '➕ Create new preset',
|
|
265
|
-
value: 'create'
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
const { selection } = await runPrompt([
|
|
269
|
-
{
|
|
270
|
-
type: 'list',
|
|
271
|
-
name: 'selection',
|
|
272
|
-
message: 'Select preset or create new',
|
|
273
|
-
choices,
|
|
274
|
-
default: 0
|
|
275
|
-
}
|
|
276
|
-
])
|
|
277
|
-
|
|
278
|
-
if (selection === 'create') {
|
|
279
|
-
return 'create'
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return presets[selection]
|
|
283
|
-
}
|
|
284
|
-
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import inquirer from 'inquirer'
|
|
3
|
+
|
|
4
|
+
export function defaultProjectPath(currentDir) {
|
|
5
|
+
return `~/webapps/${path.basename(currentDir)}`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function listGitBranches(currentDir, { runCommandCapture, logWarning } = {}) {
|
|
9
|
+
try {
|
|
10
|
+
const output = await runCommandCapture(
|
|
11
|
+
'git',
|
|
12
|
+
['branch', '--format', '%(refname:short)'],
|
|
13
|
+
{ cwd: currentDir }
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const branches = output
|
|
17
|
+
.split(/\r?\n/)
|
|
18
|
+
.map((line) => line.trim())
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
|
|
21
|
+
return branches.length ? branches : ['master']
|
|
22
|
+
} catch (_error) {
|
|
23
|
+
logWarning?.('Unable to read git branches; defaulting to master.')
|
|
24
|
+
return ['master']
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function promptServerDetails(existingServers = [], { runPrompt, generateId } = {}) {
|
|
29
|
+
const defaults = {
|
|
30
|
+
serverName: existingServers.length === 0 ? 'home' : `server-${existingServers.length + 1}`,
|
|
31
|
+
serverIp: '1.1.1.1'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const answers = await runPrompt([
|
|
35
|
+
{
|
|
36
|
+
type: 'input',
|
|
37
|
+
name: 'serverName',
|
|
38
|
+
message: 'Server name',
|
|
39
|
+
default: defaults.serverName
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'input',
|
|
43
|
+
name: 'serverIp',
|
|
44
|
+
message: 'Server IP address',
|
|
45
|
+
default: defaults.serverIp
|
|
46
|
+
}
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
id: generateId(),
|
|
51
|
+
serverName: answers.serverName.trim() || defaults.serverName,
|
|
52
|
+
serverIp: answers.serverIp.trim() || defaults.serverIp
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function selectServer(servers, { runPrompt, logProcessing, logSuccess, saveServers, promptServerDetails: promptServerDetailsFn } = {}) {
|
|
57
|
+
if (servers.length === 0) {
|
|
58
|
+
logProcessing?.("No servers configured. Let's create one.")
|
|
59
|
+
const server = await promptServerDetailsFn()
|
|
60
|
+
servers.push(server)
|
|
61
|
+
await saveServers(servers)
|
|
62
|
+
logSuccess?.('Saved server configuration to ~/.config/zephyr/servers.json')
|
|
63
|
+
return server
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const choices = servers.map((server, index) => ({
|
|
67
|
+
name: `${server.serverName} (${server.serverIp})`,
|
|
68
|
+
value: index
|
|
69
|
+
}))
|
|
70
|
+
|
|
71
|
+
choices.push(new inquirer.Separator(), {
|
|
72
|
+
name: '➕ Register a new server',
|
|
73
|
+
value: 'create'
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const { selection } = await runPrompt([
|
|
77
|
+
{
|
|
78
|
+
type: 'list',
|
|
79
|
+
name: 'selection',
|
|
80
|
+
message: 'Select server or register new',
|
|
81
|
+
choices,
|
|
82
|
+
default: 0
|
|
83
|
+
}
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
if (selection === 'create') {
|
|
87
|
+
const server = await promptServerDetailsFn(servers)
|
|
88
|
+
servers.push(server)
|
|
89
|
+
await saveServers(servers)
|
|
90
|
+
logSuccess?.('Appended server configuration to ~/.config/zephyr/servers.json')
|
|
91
|
+
return server
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return servers[selection]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function promptAppDetails(currentDir, existing = {}, {
|
|
98
|
+
runPrompt,
|
|
99
|
+
listGitBranches,
|
|
100
|
+
defaultProjectPath,
|
|
101
|
+
promptSshDetails
|
|
102
|
+
} = {}) {
|
|
103
|
+
const branches = await listGitBranches(currentDir)
|
|
104
|
+
const defaultBranch = existing.branch || (branches.includes('master') ? 'master' : branches[0])
|
|
105
|
+
const defaults = {
|
|
106
|
+
projectPath: existing.projectPath || defaultProjectPath(currentDir),
|
|
107
|
+
branch: defaultBranch
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const answers = await runPrompt([
|
|
111
|
+
{
|
|
112
|
+
type: 'input',
|
|
113
|
+
name: 'projectPath',
|
|
114
|
+
message: 'Remote project path',
|
|
115
|
+
default: defaults.projectPath
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'list',
|
|
119
|
+
name: 'branchSelection',
|
|
120
|
+
message: 'Branch to deploy',
|
|
121
|
+
choices: [
|
|
122
|
+
...branches.map((branch) => ({ name: branch, value: branch })),
|
|
123
|
+
new inquirer.Separator(),
|
|
124
|
+
{ name: 'Enter custom branch…', value: '__custom' }
|
|
125
|
+
],
|
|
126
|
+
default: defaults.branch
|
|
127
|
+
}
|
|
128
|
+
])
|
|
129
|
+
|
|
130
|
+
let branch = answers.branchSelection
|
|
131
|
+
|
|
132
|
+
if (branch === '__custom') {
|
|
133
|
+
const { customBranch } = await runPrompt([
|
|
134
|
+
{
|
|
135
|
+
type: 'input',
|
|
136
|
+
name: 'customBranch',
|
|
137
|
+
message: 'Custom branch name',
|
|
138
|
+
default: defaults.branch
|
|
139
|
+
}
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
branch = customBranch.trim() || defaults.branch
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const sshDetails = await promptSshDetails(currentDir, existing)
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
projectPath: answers.projectPath.trim() || defaults.projectPath,
|
|
149
|
+
branch,
|
|
150
|
+
...sshDetails
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function selectApp(projectConfig, server, currentDir, {
|
|
155
|
+
runPrompt,
|
|
156
|
+
logWarning,
|
|
157
|
+
logProcessing,
|
|
158
|
+
logSuccess,
|
|
159
|
+
saveProjectConfig,
|
|
160
|
+
generateId,
|
|
161
|
+
promptAppDetails
|
|
162
|
+
} = {}) {
|
|
163
|
+
const apps = projectConfig.apps ?? []
|
|
164
|
+
const matches = apps
|
|
165
|
+
.map((app, index) => ({ app, index }))
|
|
166
|
+
.filter(({ app }) => app.serverId === server.id || app.serverName === server.serverName)
|
|
167
|
+
|
|
168
|
+
if (matches.length === 0) {
|
|
169
|
+
if (apps.length > 0) {
|
|
170
|
+
const availableServers = [...new Set(apps.map((app) => app.serverName).filter(Boolean))]
|
|
171
|
+
if (availableServers.length > 0) {
|
|
172
|
+
logWarning?.(
|
|
173
|
+
`No applications configured for server "${server.serverName}". Available servers: ${availableServers.join(', ')}`
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
logProcessing?.(`No applications configured for ${server.serverName}. Let's create one.`)
|
|
178
|
+
const appDetails = await promptAppDetails(currentDir)
|
|
179
|
+
const appConfig = {
|
|
180
|
+
id: generateId(),
|
|
181
|
+
serverId: server.id,
|
|
182
|
+
serverName: server.serverName,
|
|
183
|
+
...appDetails
|
|
184
|
+
}
|
|
185
|
+
projectConfig.apps.push(appConfig)
|
|
186
|
+
await saveProjectConfig(currentDir, projectConfig)
|
|
187
|
+
logSuccess?.('Saved deployment configuration to .zephyr/config.json')
|
|
188
|
+
return appConfig
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const choices = matches.map(({ app }, matchIndex) => ({
|
|
192
|
+
name: `${app.projectPath} (${app.branch})`,
|
|
193
|
+
value: matchIndex
|
|
194
|
+
}))
|
|
195
|
+
|
|
196
|
+
choices.push(new inquirer.Separator(), {
|
|
197
|
+
name: '➕ Configure new application for this server',
|
|
198
|
+
value: 'create'
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const { selection } = await runPrompt([
|
|
202
|
+
{
|
|
203
|
+
type: 'list',
|
|
204
|
+
name: 'selection',
|
|
205
|
+
message: `Select application for ${server.serverName}`,
|
|
206
|
+
choices,
|
|
207
|
+
default: 0
|
|
208
|
+
}
|
|
209
|
+
])
|
|
210
|
+
|
|
211
|
+
if (selection === 'create') {
|
|
212
|
+
const appDetails = await promptAppDetails(currentDir)
|
|
213
|
+
const appConfig = {
|
|
214
|
+
id: generateId(),
|
|
215
|
+
serverId: server.id,
|
|
216
|
+
serverName: server.serverName,
|
|
217
|
+
...appDetails
|
|
218
|
+
}
|
|
219
|
+
projectConfig.apps.push(appConfig)
|
|
220
|
+
await saveProjectConfig(currentDir, projectConfig)
|
|
221
|
+
logSuccess?.('Appended deployment configuration to .zephyr/config.json')
|
|
222
|
+
return appConfig
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return matches[selection].app
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function selectPreset(projectConfig, servers, { runPrompt } = {}) {
|
|
229
|
+
const presets = projectConfig.presets ?? []
|
|
230
|
+
const apps = projectConfig.apps ?? []
|
|
231
|
+
|
|
232
|
+
if (presets.length === 0) {
|
|
233
|
+
return null
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const choices = presets.map((preset, index) => {
|
|
237
|
+
let displayName = preset.name
|
|
238
|
+
|
|
239
|
+
if (preset.appId) {
|
|
240
|
+
// New format: look up app by ID
|
|
241
|
+
const app = apps.find((a) => a.id === preset.appId)
|
|
242
|
+
if (app) {
|
|
243
|
+
const server = servers.find((s) => s.id === app.serverId || s.serverName === app.serverName)
|
|
244
|
+
const serverName = server?.serverName || 'unknown'
|
|
245
|
+
const branch = preset.branch || app.branch || 'unknown'
|
|
246
|
+
displayName = `${preset.name} (${serverName} → ${app.projectPath} [${branch}])`
|
|
247
|
+
}
|
|
248
|
+
} else if (preset.key) {
|
|
249
|
+
// Legacy format: parse from key
|
|
250
|
+
const keyParts = preset.key.split(':')
|
|
251
|
+
const serverName = keyParts[0]
|
|
252
|
+
const projectPath = keyParts[1]
|
|
253
|
+
const branch = preset.branch || (keyParts.length === 3 ? keyParts[2] : 'unknown')
|
|
254
|
+
displayName = `${preset.name} (${serverName} → ${projectPath} [${branch}])`
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
name: displayName,
|
|
259
|
+
value: index
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
choices.push(new inquirer.Separator(), {
|
|
264
|
+
name: '➕ Create new preset',
|
|
265
|
+
value: 'create'
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const { selection } = await runPrompt([
|
|
269
|
+
{
|
|
270
|
+
type: 'list',
|
|
271
|
+
name: 'selection',
|
|
272
|
+
message: 'Select preset or create new',
|
|
273
|
+
choices,
|
|
274
|
+
default: 0
|
|
275
|
+
}
|
|
276
|
+
])
|
|
277
|
+
|
|
278
|
+
if (selection === 'create') {
|
|
279
|
+
return 'create'
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return presets[selection]
|
|
283
|
+
}
|
|
284
|
+
|