@wyxos/zephyr 0.4.0 → 0.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wyxos/zephyr",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "A streamlined deployment tool for web applications with intelligent Laravel project detection",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -338,11 +338,43 @@ async function resolveMaintenanceModePlan({
338
338
  })
339
339
  }
340
340
 
341
+ export async function resolveRemoteDeploymentState({
342
+ snapshot,
343
+ executionMode = {},
344
+ ssh,
345
+ remoteCwd,
346
+ runPrompt,
347
+ logSuccess,
348
+ logWarning
349
+ } = {}) {
350
+ const remoteIsLaravel = await detectRemoteLaravelProject(ssh, remoteCwd)
351
+
352
+ if (remoteIsLaravel) {
353
+ logSuccess?.('Laravel project detected.')
354
+ } else {
355
+ logWarning?.('Laravel project not detected; skipping Laravel-specific maintenance tasks.')
356
+ }
357
+
358
+ const maintenanceModeEnabled = await resolveMaintenanceMode({
359
+ snapshot,
360
+ remoteIsLaravel,
361
+ runPrompt,
362
+ executionMode
363
+ })
364
+
365
+ return {
366
+ remoteIsLaravel,
367
+ maintenanceModeEnabled
368
+ }
369
+ }
370
+
341
371
  export async function buildRemoteDeploymentPlan({
342
372
  config,
343
373
  snapshot = null,
344
374
  requiredPhpVersion = null,
345
375
  executionMode = {},
376
+ remoteIsLaravel = null,
377
+ maintenanceModeEnabled = null,
346
378
  ssh,
347
379
  remoteCwd,
348
380
  executeRemote,
@@ -351,18 +383,26 @@ export async function buildRemoteDeploymentPlan({
351
383
  logWarning,
352
384
  runPrompt
353
385
  } = {}) {
354
- const remoteIsLaravel = await detectRemoteLaravelProject(ssh, remoteCwd)
355
-
356
- if (remoteIsLaravel) {
357
- logSuccess?.('Laravel project detected.')
358
- } else {
359
- logWarning?.('Laravel project not detected; skipping Laravel-specific maintenance tasks.')
360
- }
386
+ const remoteState = typeof remoteIsLaravel === 'boolean' &&
387
+ (remoteIsLaravel === false || typeof maintenanceModeEnabled === 'boolean')
388
+ ? {
389
+ remoteIsLaravel,
390
+ maintenanceModeEnabled: remoteIsLaravel ? maintenanceModeEnabled : false
391
+ }
392
+ : await resolveRemoteDeploymentState({
393
+ snapshot,
394
+ executionMode,
395
+ ssh,
396
+ remoteCwd,
397
+ runPrompt,
398
+ logSuccess,
399
+ logWarning
400
+ })
361
401
 
362
402
  const changedFiles = await collectChangedFiles({
363
403
  config,
364
404
  snapshot,
365
- remoteIsLaravel,
405
+ remoteIsLaravel: remoteState.remoteIsLaravel,
366
406
  executeRemote,
367
407
  logProcessing
368
408
  })
@@ -370,7 +410,7 @@ export async function buildRemoteDeploymentPlan({
370
410
  const horizonConfigured = await detectHorizonConfiguration({
371
411
  ssh,
372
412
  remoteCwd,
373
- remoteIsLaravel,
413
+ remoteIsLaravel: remoteState.remoteIsLaravel,
374
414
  changedFiles
375
415
  })
376
416
 
@@ -382,18 +422,11 @@ export async function buildRemoteDeploymentPlan({
382
422
  logWarning
383
423
  })
384
424
 
385
- const maintenanceModeEnabled = await resolveMaintenanceMode({
386
- snapshot,
387
- remoteIsLaravel,
388
- runPrompt,
389
- executionMode
390
- })
391
-
392
425
  const maintenanceModePlan = await resolveMaintenanceModePlan({
393
426
  snapshot,
394
- remoteIsLaravel,
427
+ remoteIsLaravel: remoteState.remoteIsLaravel,
395
428
  remoteCwd,
396
- maintenanceModeEnabled,
429
+ maintenanceModeEnabled: remoteState.maintenanceModeEnabled,
397
430
  phpCommand,
398
431
  ssh,
399
432
  executeRemote,
@@ -403,7 +436,7 @@ export async function buildRemoteDeploymentPlan({
403
436
 
404
437
  const steps = planLaravelDeploymentTasks({
405
438
  branch: config.branch,
406
- isLaravel: remoteIsLaravel,
439
+ isLaravel: remoteState.remoteIsLaravel,
407
440
  changedFiles,
408
441
  horizonConfigured,
409
442
  phpCommand,
@@ -438,7 +471,7 @@ export async function buildRemoteDeploymentPlan({
438
471
  }
439
472
 
440
473
  return {
441
- remoteIsLaravel,
474
+ remoteIsLaravel: remoteState.remoteIsLaravel,
442
475
  changedFiles,
443
476
  horizonConfigured,
444
477
  phpCommand,
@@ -12,7 +12,7 @@ import {createRemoteExecutor} from '../../deploy/remote-exec.mjs'
12
12
  import {resolveSshKeyPath} from '../../ssh/keys.mjs'
13
13
  import {cleanupOldLogs, closeLogFile, getLogFilePath, writeToLogFile} from '../../utils/log-file.mjs'
14
14
  import {resolveRemotePath} from '../../utils/remote-path.mjs'
15
- import {buildRemoteDeploymentPlan} from './build-remote-deployment-plan.mjs'
15
+ import {buildRemoteDeploymentPlan, resolveRemoteDeploymentState} from './build-remote-deployment-plan.mjs'
16
16
  import {executeRemoteDeploymentPlan} from './execute-remote-deployment-plan.mjs'
17
17
  import {prepareLocalDeployment} from './prepare-local-deployment.mjs'
18
18
 
@@ -21,6 +21,37 @@ async function resolveRemoteHome(ssh, sshUser) {
21
21
  return remoteHomeResult.stdout.trim() || `/home/${sshUser}`
22
22
  }
23
23
 
24
+ async function connectToRemoteDeploymentTarget({
25
+ config,
26
+ createSshClient,
27
+ sshUser,
28
+ privateKey,
29
+ remoteCwd = null,
30
+ logProcessing,
31
+ message
32
+ } = {}) {
33
+ const ssh = createSshClient()
34
+
35
+ logProcessing?.(`\n${message ?? `Connecting to ${config.serverIp} as ${sshUser}...`}`)
36
+
37
+ await ssh.connect({
38
+ host: config.serverIp,
39
+ username: sshUser,
40
+ privateKey
41
+ })
42
+
43
+ if (remoteCwd) {
44
+ return {ssh, remoteCwd}
45
+ }
46
+
47
+ const remoteHome = await resolveRemoteHome(ssh, sshUser)
48
+
49
+ return {
50
+ ssh,
51
+ remoteCwd: resolveRemotePath(config.projectPath, remoteHome)
52
+ }
53
+ }
54
+
24
55
  async function maybeRecoverLaravelMaintenanceMode({
25
56
  remotePlan,
26
57
  executionState,
@@ -105,43 +136,66 @@ export async function runDeployment(config, options = {}) {
105
136
 
106
137
  await cleanupOldLogs(rootDir)
107
138
 
108
- const {requiredPhpVersion} = await prepareLocalDeployment(config, {
109
- snapshot,
110
- rootDir,
111
- versionArg,
112
- runPrompt,
113
- runCommand,
114
- runCommandCapture: context.runCommandCapture,
115
- logProcessing,
116
- logSuccess,
117
- logWarning
118
- })
119
-
120
- const ssh = createSshClient()
121
139
  const sshUser = config.sshUser || os.userInfo().username
122
140
  const privateKeyPath = await resolveSshKeyPath(config.sshKey)
123
141
  const privateKey = await fs.readFile(privateKeyPath, 'utf8')
142
+ let ssh = null
124
143
  let remoteCwd = null
125
144
  let executeRemote = null
126
145
  let remotePlan = null
146
+ let remoteState = null
127
147
  const executionState = {
128
148
  enteredMaintenanceMode: false,
129
149
  exitedMaintenanceMode: false
130
150
  }
131
151
 
132
- logProcessing(`\nConnecting to ${config.serverIp} as ${sshUser}...`)
133
-
134
152
  let lockAcquired = false
135
153
 
136
154
  try {
137
- await ssh.connect({
138
- host: config.serverIp,
139
- username: sshUser,
140
- privateKey
155
+ ({ssh, remoteCwd} = await connectToRemoteDeploymentTarget({
156
+ config,
157
+ createSshClient,
158
+ sshUser,
159
+ privateKey,
160
+ logProcessing,
161
+ message: `Connecting to ${config.serverIp} as ${sshUser} to inspect remote deployment state...`
162
+ }))
163
+
164
+ remoteState = await resolveRemoteDeploymentState({
165
+ snapshot,
166
+ executionMode,
167
+ ssh,
168
+ remoteCwd,
169
+ runPrompt,
170
+ logSuccess,
171
+ logWarning
172
+ })
173
+
174
+ // Reconnect after local checks so long-running validation does not hold a stale SSH session open.
175
+ await ssh.dispose()
176
+ ssh = null
177
+
178
+ const {requiredPhpVersion} = await prepareLocalDeployment(config, {
179
+ snapshot,
180
+ rootDir,
181
+ versionArg,
182
+ runPrompt,
183
+ runCommand,
184
+ runCommandCapture: context.runCommandCapture,
185
+ logProcessing,
186
+ logSuccess,
187
+ logWarning
141
188
  })
142
189
 
143
- const remoteHome = await resolveRemoteHome(ssh, sshUser)
144
- remoteCwd = resolveRemotePath(config.projectPath, remoteHome)
190
+ ;({ssh} = await connectToRemoteDeploymentTarget({
191
+ config,
192
+ createSshClient,
193
+ sshUser,
194
+ privateKey,
195
+ remoteCwd,
196
+ logProcessing,
197
+ message: `Reconnecting to ${config.serverIp} as ${sshUser}...`
198
+ }))
145
199
 
146
200
  logProcessing('Connection established. Acquiring deployment lock on server...')
147
201
  await acquireRemoteLock(ssh, remoteCwd, rootDir, {
@@ -168,6 +222,8 @@ export async function runDeployment(config, options = {}) {
168
222
  rootDir,
169
223
  requiredPhpVersion,
170
224
  executionMode,
225
+ remoteIsLaravel: remoteState?.remoteIsLaravel,
226
+ maintenanceModeEnabled: remoteState?.maintenanceModeEnabled,
171
227
  ssh,
172
228
  remoteCwd,
173
229
  executeRemote,