servyx-cli 0.1.7 → 0.1.9

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 CHANGED
@@ -13,14 +13,32 @@ Deploy local projects to Servyx by rsyncing files over SSH, then triggering the
13
13
 
14
14
  ```bash
15
15
  npm install -g servyx-cli
16
- # or from source:
16
+ ```
17
+
18
+ This installs the `latest` published version (no version pin needed). To upgrade an existing install:
19
+
20
+ ```bash
21
+ npm update -g servyx-cli
22
+ # or explicitly:
23
+ npm install -g servyx-cli@latest
24
+ ```
25
+
26
+ Check your installed version:
27
+
28
+ ```bash
29
+ servyx --version
30
+ ```
31
+
32
+ From source:
33
+
34
+ ```bash
17
35
  cd servyx-cli && npm install && npm link
18
36
  ```
19
37
 
20
38
  ## Quick start
21
39
 
22
40
  ```bash
23
- # 1. Authenticate (defaults to https://cli.servyxhq.com)
41
+ # 1. Authenticate
24
42
  servyx login
25
43
 
26
44
  # 2. Link your project directory to an app
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "servyx-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Servyx CLI — Upload local projects and deploy via the Servyx platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,12 @@
15
15
  "node": ">=18.0.0"
16
16
  },
17
17
  "scripts": {
18
- "start": "node bin/servyx.js"
18
+ "start": "node bin/servyx.js",
19
+ "prepublishOnly": "node -e \"const p=require('./package.json');const l=require('./package-lock.json');if(l.version!==p.version||l.packages[''].version!==p.version){console.error('package-lock.json version mismatch — run npm install');process.exit(1)}\""
20
+ },
21
+ "publishConfig": {
22
+ "tag": "latest",
23
+ "access": "public"
19
24
  },
20
25
  "keywords": [
21
26
  "servyx",
@@ -5,11 +5,23 @@ import { loadProjectConfig, loadServyxIgnore } from '../lib/project.js';
5
5
  import { rsyncDeploy, sshPreflight, DEFAULT_RSYNC_EXCLUDES, preserveServerDotfiles, ensureRemoteDir, verifyRemoteStagingDir } from '../lib/rsync.js';
6
6
 
7
7
  const TERMINAL_STATUSES = new Set(['success', 'failure', 'cancelled']);
8
+ const MIN_POLL_MS = 3000;
9
+ const MAX_POLL_MS = 10000;
8
10
 
9
11
  function sleep(ms) {
10
12
  return new Promise((resolve) => setTimeout(resolve, ms));
11
13
  }
12
14
 
15
+ function deploymentSnapshot(deployment) {
16
+ return [
17
+ deployment.status,
18
+ deployment.phase,
19
+ deployment.percent,
20
+ deployment.message,
21
+ deployment.error_message,
22
+ ].join('|');
23
+ }
24
+
13
25
  function formatDeploymentLine(deployment) {
14
26
  const pct = deployment.percent != null ? `${deployment.percent}%` : '';
15
27
  const phase = deployment.phase || deployment.status || 'unknown';
@@ -149,15 +161,36 @@ export async function runDeploy(options = {}) {
149
161
 
150
162
  console.log(pc.dim(`Deployment ID: ${deploymentId}`));
151
163
 
164
+ let pollIntervalMs = MIN_POLL_MS;
165
+ let lastSnapshot = null;
166
+
152
167
  while (true) {
153
- const statusResult = await getDeployment(deploymentId);
168
+ let statusResult;
169
+ try {
170
+ statusResult = await getDeployment(deploymentId);
171
+ pollIntervalMs = Math.min(MAX_POLL_MS, pollIntervalMs + 500);
172
+ } catch (err) {
173
+ if (err.status === 429 || err.code === 'RATE_LIMITED') {
174
+ const retryMs = (err.retryAfterSeconds || Math.ceil(pollIntervalMs / 1000)) * 1000;
175
+ pollIntervalMs = Math.min(MAX_POLL_MS * 2, Math.max(retryMs, pollIntervalMs * 2));
176
+ console.log(pc.yellow(`Rate limited — retrying in ${Math.ceil(pollIntervalMs / 1000)}s...`));
177
+ await sleep(pollIntervalMs);
178
+ continue;
179
+ }
180
+ throw err;
181
+ }
182
+
154
183
  const current = statusResult?.data?.deployment;
155
184
  if (!current) {
156
185
  console.error(pc.red('Deployment not found while polling.'));
157
186
  process.exit(1);
158
187
  }
159
188
 
160
- console.log(formatDeploymentLine(current));
189
+ const snapshot = deploymentSnapshot(current);
190
+ if (snapshot !== lastSnapshot) {
191
+ console.log(formatDeploymentLine(current));
192
+ lastSnapshot = snapshot;
193
+ }
161
194
 
162
195
  if (TERMINAL_STATUSES.has(current.status)) {
163
196
  if (current.status === 'success') {
@@ -172,6 +205,6 @@ export async function runDeploy(options = {}) {
172
205
  process.exit(1);
173
206
  }
174
207
 
175
- await sleep(2000);
208
+ await sleep(pollIntervalMs);
176
209
  }
177
210
  }
package/src/index.js CHANGED
@@ -4,13 +4,14 @@ import { runLogin } from './commands/login.js';
4
4
  import { runLink } from './commands/link.js';
5
5
  import { runDeploy } from './commands/deploy.js';
6
6
  import { runStatus } from './commands/status.js';
7
+ import { getCliVersion } from './lib/version.js';
7
8
 
8
9
  const program = new Command();
9
10
 
10
11
  program
11
12
  .name('servyx')
12
13
  .description('Servyx CLI — rsync and deploy applications')
13
- .version('0.1.0');
14
+ .version(getCliVersion());
14
15
 
15
16
  program
16
17
  .command('login')
package/src/lib/api.js CHANGED
@@ -34,6 +34,13 @@ function unwrap(response) {
34
34
  err.status = response.status;
35
35
  err.code = response.data?.code;
36
36
  err.data = response.data;
37
+ const retryAfter = response.headers?.['retry-after'];
38
+ if (retryAfter) {
39
+ const seconds = Number.parseInt(retryAfter, 10);
40
+ if (!Number.isNaN(seconds)) {
41
+ err.retryAfterSeconds = seconds;
42
+ }
43
+ }
37
44
  throw err;
38
45
  }
39
46
 
@@ -0,0 +1,9 @@
1
+ import { readFileSync } from 'fs';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
6
+
7
+ export function getCliVersion() {
8
+ return JSON.parse(readFileSync(packagePath, 'utf8')).version;
9
+ }