@wyxos/zephyr 0.2.5 → 0.2.7

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.2.5",
3
+ "version": "0.2.7",
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",
@@ -221,15 +221,43 @@ async function runTests(skipTests, pkg, rootDir = process.cwd()) {
221
221
 
222
222
  logStep('Running test suite...')
223
223
 
224
- // Prefer test:run if available, otherwise use test with --run flag
225
- if (hasScript(pkg, 'test:run')) {
226
- await runCommand('npm', ['run', 'test:run'], { cwd: rootDir })
227
- } else {
228
- // For test script, try to pass --run flag (works with vitest)
229
- await runCommand('npm', ['test', '--', '--run'], { cwd: rootDir })
230
- }
224
+ let dotInterval = null
225
+ try {
226
+ // Capture output and show dots as progress
227
+ process.stdout.write(' ')
228
+ dotInterval = setInterval(() => {
229
+ process.stdout.write('.')
230
+ }, 200)
231
+
232
+ // Prefer test:run if available, otherwise use test with --run flag
233
+ if (hasScript(pkg, 'test:run')) {
234
+ await runCommand('npm', ['run', 'test:run'], { capture: true, cwd: rootDir })
235
+ } else {
236
+ // For test script, try to pass --run flag (works with vitest)
237
+ await runCommand('npm', ['test', '--', '--run'], { capture: true, cwd: rootDir })
238
+ }
231
239
 
232
- logSuccess('Tests passed.')
240
+ if (dotInterval) {
241
+ clearInterval(dotInterval)
242
+ dotInterval = null
243
+ }
244
+ process.stdout.write('\n')
245
+ logSuccess('Tests passed.')
246
+ } catch (error) {
247
+ // Clear dots and show error output
248
+ if (dotInterval) {
249
+ clearInterval(dotInterval)
250
+ dotInterval = null
251
+ }
252
+ process.stdout.write('\n')
253
+ if (error.stdout) {
254
+ console.error(error.stdout)
255
+ }
256
+ if (error.stderr) {
257
+ console.error(error.stderr)
258
+ }
259
+ throw error
260
+ }
233
261
  }
234
262
 
235
263
  async function runBuild(skipBuild, pkg, rootDir = process.cwd()) {
@@ -337,7 +365,10 @@ async function publishPackage(pkg, rootDir = process.cwd()) {
337
365
  const publishArgs = ['publish', '--ignore-scripts'] // Skip prepublishOnly since we already built lib
338
366
 
339
367
  if (pkg.name.startsWith('@')) {
340
- publishArgs.push('--access', 'public')
368
+ // For scoped packages, determine access level from publishConfig
369
+ // Default to 'public' for scoped packages if not specified (free npm accounts require public for scoped packages)
370
+ const access = pkg.publishConfig?.access || 'public'
371
+ publishArgs.push('--access', access)
341
372
  }
342
373
 
343
374
  logStep(`Publishing ${pkg.name}@${pkg.version} to npm...`)
package/src/ssh-utils.mjs CHANGED
@@ -92,11 +92,11 @@ export async function connectToServer(config, rootDir) {
92
92
  * @param {NodeSSH} ssh - SSH client instance
93
93
  * @param {string} label - Human-readable label for the command
94
94
  * @param {string} command - Command to execute
95
- * @param {Object} options - Options: { cwd, allowFailure, printStdout, bootstrapEnv, rootDir, writeToLogFile }
95
+ * @param {Object} options - Options: { cwd, allowFailure, printStdout, bootstrapEnv, rootDir, writeToLogFile, env }
96
96
  * @returns {Promise<Object>} Command result
97
97
  */
98
98
  export async function executeRemoteCommand(ssh, label, command, options = {}) {
99
- const { cwd, allowFailure = false, printStdout = true, bootstrapEnv = true, rootDir = null, writeToLogFile = null } = options
99
+ const { cwd, allowFailure = false, printStdout = true, bootstrapEnv = true, rootDir = null, writeToLogFile = null, env = {} } = options
100
100
 
101
101
  logProcessing(`\n→ ${label}`)
102
102
 
@@ -120,14 +120,27 @@ export async function executeRemoteCommand(ssh, label, command, options = {}) {
120
120
  ].join('; ')
121
121
 
122
122
  const escapeForDoubleQuotes = (value) => value.replace(/(["\\$`])/g, '\\$1')
123
+ const escapeForSingleQuotes = (value) => value.replace(/'/g, "'\\''")
124
+
125
+ // Build environment variable exports
126
+ let envExports = ''
127
+ if (Object.keys(env).length > 0) {
128
+ const envPairs = Object.entries(env).map(([key, value]) => {
129
+ const escapedValue = escapeForSingleQuotes(String(value))
130
+ return `${key}='${escapedValue}'`
131
+ })
132
+ envExports = envPairs.join(' ') + ' '
133
+ }
123
134
 
124
135
  let wrappedCommand = command
125
136
  let execOptions = { cwd }
126
137
 
127
138
  if (bootstrapEnv && cwd) {
128
139
  const cwdForShell = escapeForDoubleQuotes(cwd)
129
- wrappedCommand = `${profileBootstrap}; cd "${cwdForShell}" && ${command}`
140
+ wrappedCommand = `${profileBootstrap}; cd "${cwdForShell}" && ${envExports}${command}`
130
141
  execOptions = {}
142
+ } else if (Object.keys(env).length > 0) {
143
+ wrappedCommand = `${envExports}${command}`
131
144
  }
132
145
 
133
146
  const result = await ssh.execCommand(wrappedCommand, execOptions)
@@ -191,7 +204,13 @@ export async function readRemoteFile(ssh, filePath, remoteCwd) {
191
204
  }
192
205
 
193
206
  /**
194
- * Download file from remote server via SFTP
207
+ * Download file from remote server via SFTP with progress
208
+ *
209
+ * Note: Currently uses single-stream download (most reliable).
210
+ * Multi-streaming is technically possible with ssh2-sftp-client's fastGet,
211
+ * but it's unreliable on many servers and can cause data corruption.
212
+ * Single-stream ensures data integrity at the cost of potentially slower speeds.
213
+ *
195
214
  * @param {NodeSSH} ssh - SSH client instance
196
215
  * @param {string} remotePath - Path to file on remote server
197
216
  * @param {string} localPath - Local path to save file
@@ -206,8 +225,26 @@ export async function downloadRemoteFile(ssh, remotePath, localPath, remoteCwd)
206
225
 
207
226
  logProcessing(`Downloading ${absoluteRemotePath} to ${localPath}...`)
208
227
 
209
- await ssh.getFile(localPath, absoluteRemotePath)
228
+ let transferred = 0
229
+ const startTime = Date.now()
230
+
231
+ // Single-stream download (most reliable for data integrity)
232
+ await ssh.getFile(localPath, absoluteRemotePath, null, {
233
+ step: (totalTransferred, chunk, total) => {
234
+ transferred = totalTransferred
235
+ const percent = total > 0 ? Math.round((transferred / total) * 100) : 0
236
+ const elapsed = (Date.now() - startTime) / 1000
237
+ const speed = elapsed > 0 ? (transferred / elapsed / 1024 / 1024).toFixed(2) : 0
238
+ const sizeMB = (transferred / 1024 / 1024).toFixed(2)
239
+ const totalMB = total > 0 ? (total / 1024 / 1024).toFixed(2) : '?'
240
+
241
+ // Update progress on same line
242
+ process.stdout.write(`\r Progress: ${percent}% (${sizeMB}MB / ${totalMB}MB) - ${speed} MB/s`)
243
+ }
244
+ })
210
245
 
246
+ // Clear progress line and show completion
247
+ process.stdout.write('\r' + ' '.repeat(80) + '\r')
211
248
  logSuccess(`Downloaded ${absoluteRemotePath} to ${localPath}`)
212
249
  }
213
250