@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.
@@ -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
+