servyx-cli 0.1.4 → 0.1.5

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": "servyx-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Servyx CLI — Upload local projects and deploy via the Servyx platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@ import pc from 'picocolors';
2
2
  import { getDeployTarget, getDeployment, startDeployment } from '../lib/api.js';
3
3
  import { getToken } from '../lib/config.js';
4
4
  import { loadProjectConfig, loadServyxIgnore } from '../lib/project.js';
5
- import { rsyncDeploy, sshPreflight, DEFAULT_RSYNC_EXCLUDES, preserveServerDotfiles } from '../lib/rsync.js';
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
8
 
@@ -65,6 +65,18 @@ export async function runDeploy(options = {}) {
65
65
  ];
66
66
 
67
67
  console.log(pc.bold('Syncing files via rsync...'));
68
+ ensureRemoteDir({
69
+ host: target.ssh_host,
70
+ port: target.ssh_port || 22,
71
+ user: target.ssh_user,
72
+ remotePath: target.root_path,
73
+ });
74
+ ensureRemoteDir({
75
+ host: target.ssh_host,
76
+ port: target.ssh_port || 22,
77
+ user: target.ssh_user,
78
+ remotePath: target.staging_path,
79
+ });
68
80
  rsyncDeploy({
69
81
  localDir: root,
70
82
  host: target.ssh_host,
@@ -73,6 +85,21 @@ export async function runDeploy(options = {}) {
73
85
  remotePath: target.staging_path,
74
86
  excludes,
75
87
  });
88
+
89
+ const stagingCheck = verifyRemoteStagingDir({
90
+ host: target.ssh_host,
91
+ port: target.ssh_port || 22,
92
+ user: target.ssh_user,
93
+ remotePath: target.staging_path,
94
+ });
95
+ if (!stagingCheck.ok) {
96
+ console.error(pc.red(`Staging directory is missing or empty after rsync: ${target.staging_path}`));
97
+ if (stagingCheck.stderr) {
98
+ console.error(pc.dim(stagingCheck.stderr));
99
+ }
100
+ console.error(pc.yellow('Ensure the app system user exists on the server and can write under /srv/users/<user>/apps/.'));
101
+ process.exit(1);
102
+ }
76
103
  console.log(pc.green('Rsync complete.'));
77
104
 
78
105
  const publicPath = target.public_path || `${target.root_path}/public`;
@@ -24,7 +24,7 @@ export async function runLink(options = {}) {
24
24
  name: 'appId',
25
25
  message: 'Select an app to link',
26
26
  choices: apps.map((app) => ({
27
- title: `${app.name} (${app.slug}) — ${app.server_name || app.hostname}`,
27
+ title: `${app.name} (${app.system_username}) — ${app.server_name || app.hostname}`,
28
28
  value: app.id,
29
29
  })),
30
30
  });
@@ -45,10 +45,10 @@ export async function runLink(options = {}) {
45
45
  saveProjectConfig(cwd, {
46
46
  appId: app.id,
47
47
  appName: app.name,
48
- appSlug: app.slug,
48
+ appSlug: app.system_username,
49
49
  linkedAt: new Date().toISOString(),
50
50
  });
51
51
 
52
- console.log(pc.green(`Linked ${app.name} (${app.slug})`));
52
+ console.log(pc.green(`Linked ${app.name} (${app.system_username})`));
53
53
  console.log(pc.dim(`Wrote servyx.json in ${cwd}`));
54
54
  }
package/src/lib/rsync.js CHANGED
@@ -64,6 +64,48 @@ export function rsyncDeploy({ localDir, host, port = 22, user, remotePath, exclu
64
64
  return { target };
65
65
  }
66
66
 
67
+ export function ensureRemoteDir({ host, port = 22, user, remotePath }) {
68
+ requireBinary('ssh');
69
+ const target = `${user}@${host}`;
70
+ const cmd = `mkdir -p ${shellQuote(remotePath)}`;
71
+ const args = [
72
+ '-o', 'BatchMode=yes',
73
+ '-o', 'StrictHostKeyChecking=accept-new',
74
+ '-o', 'ConnectTimeout=10',
75
+ '-p', String(port),
76
+ target,
77
+ cmd,
78
+ ];
79
+ const result = spawnSync('ssh', args, { encoding: 'utf8' });
80
+ if (result.status !== 0) {
81
+ const err = new Error(`Failed to create remote directory ${remotePath}: ${(result.stderr || '').trim() || 'unknown error'}`);
82
+ err.exitCode = result.status;
83
+ throw err;
84
+ }
85
+ }
86
+
87
+ export function verifyRemoteStagingDir({ host, port = 22, user, remotePath }) {
88
+ requireBinary('ssh');
89
+ const target = `${user}@${host}`;
90
+ const cmd = [
91
+ `test -d ${shellQuote(remotePath)}`,
92
+ `&& [ -n "$(ls -A ${shellQuote(remotePath)} 2>/dev/null | head -1)" ]`,
93
+ ].join(' ');
94
+ const args = [
95
+ '-o', 'BatchMode=yes',
96
+ '-o', 'StrictHostKeyChecking=accept-new',
97
+ '-o', 'ConnectTimeout=10',
98
+ '-p', String(port),
99
+ target,
100
+ cmd,
101
+ ];
102
+ const result = spawnSync('ssh', args, { encoding: 'utf8' });
103
+ return {
104
+ ok: result.status === 0,
105
+ stderr: (result.stderr || '').trim(),
106
+ };
107
+ }
108
+
67
109
  /** Copy live dotfiles from public into staging so the agent's staging→public sync does not delete them. */
68
110
  export function preserveServerDotfiles({ host, port = 22, user, publicPath, stagingPath }) {
69
111
  requireBinary('ssh');