@wyxos/zephyr 0.2.24 → 0.2.25

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.24",
3
+ "version": "0.2.25",
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",
@@ -56,67 +56,130 @@ export async function getPhpVersionRequirement(rootDir) {
56
56
  }
57
57
  }
58
58
 
59
+ const RUNCLOUD_PACKAGES = '/RunCloud/Packages'
60
+
61
+ function satisfiesVersion(actualVersionStr, requiredVersion) {
62
+ const normalized = semver.coerce(actualVersionStr)
63
+ const required = semver.coerce(requiredVersion)
64
+ return normalized && required && semver.gte(normalized, required)
65
+ }
66
+
67
+ async function tryPhpPath(ssh, remoteCwd, pathOrCommand) {
68
+ const versionCheck = await ssh.execCommand(`${pathOrCommand} -r "echo PHP_VERSION;"`, { cwd: remoteCwd })
69
+ return versionCheck.code === 0 ? versionCheck.stdout.trim() : null
70
+ }
71
+
72
+ /**
73
+ * Discovers PHP binaries under RunCloud Packages (e.g. /RunCloud/Packages/php84rc/bin/php).
74
+ * Lists the directory and tries each php*rc/bin/php, returning the path that satisfies the version.
75
+ */
76
+ async function findRunCloudPhp(ssh, remoteCwd, requiredVersion) {
77
+ const listResult = await ssh.execCommand(`ls -1 ${RUNCLOUD_PACKAGES} 2>/dev/null || true`, { cwd: remoteCwd })
78
+ if (listResult.code !== 0 || !listResult.stdout.trim()) {
79
+ return null
80
+ }
81
+ const entries = listResult.stdout.trim().split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
82
+ // e.g. php74rc, php80rc, php84rc
83
+ const phpDirs = entries.filter((name) => /^php\d+rc$/.test(name))
84
+ const majorMinor = semver.major(requiredVersion) + '.' + semver.minor(requiredVersion)
85
+ const targetSuffix = `php${majorMinor.replace('.', '')}rc` // php84rc for 8.4
86
+
87
+ // Prefer exact match (php84rc for 8.4), then try any that might satisfy
88
+ const toTry = phpDirs.filter((d) => d === targetSuffix).concat(phpDirs.filter((d) => d !== targetSuffix))
89
+
90
+ for (const dir of toTry) {
91
+ const binPath = `${RUNCLOUD_PACKAGES}/${dir}/bin/php`
92
+ const actualVersion = await tryPhpPath(ssh, remoteCwd, binPath)
93
+ if (actualVersion && satisfiesVersion(actualVersion, requiredVersion)) {
94
+ return binPath
95
+ }
96
+ }
97
+ return null
98
+ }
99
+
59
100
  /**
60
- * Finds the appropriate PHP binary command for a given version
61
- * Tries common patterns: php8.4, php8.3, etc.
101
+ * Resolves a command (e.g. php84) via login shell so aliases are expanded; returns the path if it runs and satisfies version.
102
+ */
103
+ async function resolveViaLoginShell(ssh, remoteCwd, commandName, requiredVersion) {
104
+ const whichResult = await ssh.execCommand(`bash -lc 'command -v ${commandName}' 2>/dev/null || true`, { cwd: remoteCwd })
105
+ if (whichResult.code !== 0 || !whichResult.stdout.trim()) {
106
+ return null
107
+ }
108
+ const pathOrCommand = whichResult.stdout.trim()
109
+ const actualVersion = await tryPhpPath(ssh, remoteCwd, pathOrCommand)
110
+ if (actualVersion && satisfiesVersion(actualVersion, requiredVersion)) {
111
+ return pathOrCommand
112
+ }
113
+ return null
114
+ }
115
+
116
+ /**
117
+ * Finds the appropriate PHP binary command for a given version.
118
+ * Tries RunCloud paths, login-shell alias resolution, then common names (php8.4, php84), then default php.
62
119
  * @param {object} ssh - SSH client instance
63
120
  * @param {string} remoteCwd - Remote working directory
64
121
  * @param {string} requiredVersion - Required PHP version (e.g., "8.4.0")
65
- * @returns {Promise<string>} - PHP command prefix (e.g., "php8.4" or "php")
122
+ * @returns {Promise<string>} - PHP command or path (e.g., "php8.4", "/RunCloud/Packages/php84rc/bin/php", or "php")
66
123
  */
67
124
  export async function findPhpBinary(ssh, remoteCwd, requiredVersion) {
68
125
  if (!requiredVersion) {
69
126
  return 'php'
70
127
  }
71
128
 
72
- // Extract major.minor version (e.g., "8.4" from "8.4.0")
73
129
  const majorMinor = semver.major(requiredVersion) + '.' + semver.minor(requiredVersion)
74
130
  const versionedPhp = `php${majorMinor.replace('.', '')}` // e.g., "php84"
75
131
 
76
- // Try versioned PHP binary first (e.g., php8.4, php84)
77
- const candidates = [
78
- `php${majorMinor}`, // php8.4
79
- versionedPhp, // php84
80
- 'php' // fallback
81
- ]
132
+ // 1. RunCloud: discover /RunCloud/Packages/php*rc/bin/php
133
+ try {
134
+ const runcloudPath = await findRunCloudPhp(ssh, remoteCwd, requiredVersion)
135
+ if (runcloudPath) {
136
+ return runcloudPath
137
+ }
138
+ } catch {
139
+ // Ignore
140
+ }
82
141
 
142
+ // 2. Resolve alias via login shell (e.g. php84 -> real path)
143
+ try {
144
+ const resolved = await resolveViaLoginShell(ssh, remoteCwd, versionedPhp, requiredVersion)
145
+ if (resolved) {
146
+ return resolved
147
+ }
148
+ const resolvedDot = await resolveViaLoginShell(ssh, remoteCwd, `php${majorMinor}`, requiredVersion)
149
+ if (resolvedDot) {
150
+ return resolvedDot
151
+ }
152
+ } catch {
153
+ // Ignore
154
+ }
155
+
156
+ // 3. Try common names in current PATH (non-login shell)
157
+ const candidates = [`php${majorMinor}`, versionedPhp]
83
158
  for (const candidate of candidates) {
84
159
  try {
85
160
  const result = await ssh.execCommand(`command -v ${candidate}`, { cwd: remoteCwd })
86
161
  if (result.code === 0 && result.stdout.trim()) {
87
- // Verify it's actually the right version
88
- const versionCheck = await ssh.execCommand(`${candidate} -r "echo PHP_VERSION;"`, { cwd: remoteCwd })
89
- if (versionCheck.code === 0) {
90
- const actualVersion = versionCheck.stdout.trim()
91
- // Normalize version and check if it satisfies the requirement
92
- const normalizedVersion = semver.coerce(actualVersion)
93
- if (normalizedVersion && semver.gte(normalizedVersion, semver.coerce(requiredVersion))) {
94
- return candidate
95
- }
162
+ const actualVersion = await tryPhpPath(ssh, remoteCwd, candidate)
163
+ if (actualVersion && satisfiesVersion(actualVersion, requiredVersion)) {
164
+ return candidate
96
165
  }
97
166
  }
98
167
  } catch {
99
- // Continue to next candidate
168
+ // Continue
100
169
  }
101
170
  }
102
171
 
103
- // Fallback: try to use default php and check version
172
+ // 4. Default php
104
173
  try {
105
- const versionCheck = await ssh.execCommand('php -r "echo PHP_VERSION;"', { cwd: remoteCwd })
106
- if (versionCheck.code === 0) {
107
- const actualVersion = versionCheck.stdout.trim()
108
- const normalizedVersion = semver.coerce(actualVersion)
109
- if (normalizedVersion && semver.gte(normalizedVersion, semver.coerce(requiredVersion))) {
110
- return 'php'
111
- }
174
+ const actualVersion = await tryPhpPath(ssh, remoteCwd, 'php')
175
+ if (actualVersion && satisfiesVersion(actualVersion, requiredVersion)) {
176
+ return 'php'
112
177
  }
113
178
  } catch {
114
179
  // Ignore
115
180
  }
116
181
 
117
- // If we can't find a suitable version, return the versioned command anyway
118
- // The error will be clearer when the command fails
119
- return `php${majorMinor}`
182
+ return 'php'
120
183
  }
121
184
 
122
185
  /**