hostfn 0.1.1 → 0.1.2

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.
Files changed (179) hide show
  1. package/dist/__tests__/core/backup.test.d.ts +2 -0
  2. package/dist/__tests__/core/backup.test.d.ts.map +1 -0
  3. package/dist/__tests__/core/backup.test.js +108 -0
  4. package/dist/__tests__/core/backup.test.js.map +1 -0
  5. package/dist/__tests__/core/health.test.d.ts +2 -0
  6. package/dist/__tests__/core/health.test.d.ts.map +1 -0
  7. package/dist/__tests__/core/health.test.js +97 -0
  8. package/dist/__tests__/core/health.test.js.map +1 -0
  9. package/dist/__tests__/core/lock.test.d.ts +2 -0
  10. package/dist/__tests__/core/lock.test.d.ts.map +1 -0
  11. package/dist/__tests__/core/lock.test.js +136 -0
  12. package/dist/__tests__/core/lock.test.js.map +1 -0
  13. package/dist/__tests__/core/nginx-multi-domain.test.d.ts +2 -0
  14. package/dist/__tests__/core/nginx-multi-domain.test.d.ts.map +1 -0
  15. package/dist/__tests__/core/nginx-multi-domain.test.js +158 -0
  16. package/dist/__tests__/core/nginx-multi-domain.test.js.map +1 -0
  17. package/dist/__tests__/runtimes/pm2.test.d.ts +2 -0
  18. package/dist/__tests__/runtimes/pm2.test.d.ts.map +1 -0
  19. package/dist/__tests__/runtimes/pm2.test.js +111 -0
  20. package/dist/__tests__/runtimes/pm2.test.js.map +1 -0
  21. package/dist/__tests__/utils/validation.test.d.ts +2 -0
  22. package/dist/__tests__/utils/validation.test.d.ts.map +1 -0
  23. package/dist/__tests__/utils/validation.test.js +136 -0
  24. package/dist/__tests__/utils/validation.test.js.map +1 -0
  25. package/dist/commands/deploy.d.ts +11 -0
  26. package/dist/commands/deploy.d.ts.map +1 -0
  27. package/dist/commands/deploy.js +636 -0
  28. package/dist/commands/deploy.js.map +1 -0
  29. package/dist/commands/env.d.ts +21 -0
  30. package/dist/commands/env.d.ts.map +1 -0
  31. package/dist/commands/env.js +317 -0
  32. package/dist/commands/env.js.map +1 -0
  33. package/dist/commands/expose.d.ts +6 -0
  34. package/dist/commands/expose.d.ts.map +1 -0
  35. package/dist/commands/expose.js +379 -0
  36. package/dist/commands/expose.js.map +1 -0
  37. package/dist/commands/init.d.ts +2 -0
  38. package/dist/commands/init.d.ts.map +1 -0
  39. package/dist/commands/init.js +175 -0
  40. package/dist/commands/init.js.map +1 -0
  41. package/dist/commands/logs.d.ts +10 -0
  42. package/dist/commands/logs.d.ts.map +1 -0
  43. package/dist/commands/logs.js +75 -0
  44. package/dist/commands/logs.js.map +1 -0
  45. package/dist/commands/rollback.d.ts +6 -0
  46. package/dist/commands/rollback.d.ts.map +1 -0
  47. package/dist/commands/rollback.js +113 -0
  48. package/dist/commands/rollback.js.map +1 -0
  49. package/dist/commands/server/info.d.ts +2 -0
  50. package/dist/commands/server/info.d.ts.map +1 -0
  51. package/dist/commands/server/info.js +104 -0
  52. package/dist/commands/server/info.js.map +1 -0
  53. package/dist/commands/server/setup.d.ts +11 -0
  54. package/dist/commands/server/setup.d.ts.map +1 -0
  55. package/dist/commands/server/setup.js +161 -0
  56. package/dist/commands/server/setup.js.map +1 -0
  57. package/dist/commands/status.d.ts +6 -0
  58. package/dist/commands/status.d.ts.map +1 -0
  59. package/dist/commands/status.js +120 -0
  60. package/dist/commands/status.js.map +1 -0
  61. package/dist/config/loader.d.ts +21 -0
  62. package/dist/config/loader.d.ts.map +1 -0
  63. package/dist/config/loader.js +54 -0
  64. package/dist/config/loader.js.map +1 -0
  65. package/dist/config/schema.d.ts +323 -0
  66. package/dist/config/schema.d.ts.map +1 -0
  67. package/dist/config/schema.js +108 -0
  68. package/dist/config/schema.js.map +1 -0
  69. package/dist/core/backup.d.ts +34 -0
  70. package/dist/core/backup.d.ts.map +1 -0
  71. package/dist/core/backup.js +95 -0
  72. package/dist/core/backup.js.map +1 -0
  73. package/dist/core/health.d.ts +31 -0
  74. package/dist/core/health.d.ts.map +1 -0
  75. package/dist/core/health.js +78 -0
  76. package/dist/core/health.js.map +1 -0
  77. package/dist/core/local.d.ts +19 -0
  78. package/dist/core/local.d.ts.map +1 -0
  79. package/dist/core/local.js +50 -0
  80. package/dist/core/local.js.map +1 -0
  81. package/dist/core/lock.d.ts +28 -0
  82. package/dist/core/lock.d.ts.map +1 -0
  83. package/dist/core/lock.js +89 -0
  84. package/dist/core/lock.js.map +1 -0
  85. package/dist/core/nginx.d.ts +43 -0
  86. package/dist/core/nginx.d.ts.map +1 -0
  87. package/dist/core/nginx.js +131 -0
  88. package/dist/core/nginx.js.map +1 -0
  89. package/dist/core/ssh.d.ts +79 -0
  90. package/dist/core/ssh.d.ts.map +1 -0
  91. package/dist/core/ssh.js +264 -0
  92. package/dist/core/ssh.js.map +1 -0
  93. package/dist/core/sync.d.ts +25 -0
  94. package/dist/core/sync.d.ts.map +1 -0
  95. package/dist/core/sync.js +117 -0
  96. package/dist/core/sync.js.map +1 -0
  97. package/dist/core/workspace.d.ts +13 -0
  98. package/dist/core/workspace.d.ts.map +1 -0
  99. package/dist/core/workspace.js +141 -0
  100. package/dist/core/workspace.js.map +1 -0
  101. package/dist/index.d.ts +3 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +232 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/runtimes/base.d.ts +115 -0
  106. package/dist/runtimes/base.d.ts.map +1 -0
  107. package/dist/runtimes/base.js +16 -0
  108. package/dist/runtimes/base.js.map +1 -0
  109. package/dist/runtimes/nodejs/detector.d.ts +47 -0
  110. package/dist/runtimes/nodejs/detector.d.ts.map +1 -0
  111. package/dist/runtimes/nodejs/detector.js +143 -0
  112. package/dist/runtimes/nodejs/detector.js.map +1 -0
  113. package/dist/runtimes/nodejs/index.d.ts +14 -0
  114. package/dist/runtimes/nodejs/index.d.ts.map +1 -0
  115. package/dist/runtimes/nodejs/index.js +213 -0
  116. package/dist/runtimes/nodejs/index.js.map +1 -0
  117. package/dist/runtimes/nodejs/pm2.d.ts +17 -0
  118. package/dist/runtimes/nodejs/pm2.d.ts.map +1 -0
  119. package/dist/runtimes/nodejs/pm2.js +60 -0
  120. package/dist/runtimes/nodejs/pm2.js.map +1 -0
  121. package/dist/runtimes/registry.d.ts +34 -0
  122. package/dist/runtimes/registry.d.ts.map +1 -0
  123. package/dist/runtimes/registry.js +58 -0
  124. package/dist/runtimes/registry.js.map +1 -0
  125. package/dist/utils/logger.d.ts +47 -0
  126. package/dist/utils/logger.d.ts.map +1 -0
  127. package/dist/utils/logger.js +76 -0
  128. package/dist/utils/logger.js.map +1 -0
  129. package/dist/utils/validation.d.ts +32 -0
  130. package/dist/utils/validation.d.ts.map +1 -0
  131. package/dist/utils/validation.js +125 -0
  132. package/dist/utils/validation.js.map +1 -0
  133. package/package.json +33 -16
  134. package/LICENSE +0 -21
  135. package/README.md +0 -1136
  136. package/_conduct/specs/1.v0.spec.md +0 -1041
  137. package/examples/express-api/package.json +0 -22
  138. package/examples/express-api/src/index.ts +0 -16
  139. package/examples/express-api/tsconfig.json +0 -11
  140. package/examples/github-actions-deploy.yml +0 -40
  141. package/examples/monorepo-config.json +0 -76
  142. package/examples/monorepo-multi-server-config.json +0 -74
  143. package/packages/cli/package.json +0 -40
  144. package/turbo.json +0 -24
  145. /package/{packages/cli/src → src}/__tests__/core/backup.test.ts +0 -0
  146. /package/{packages/cli/src → src}/__tests__/core/health.test.ts +0 -0
  147. /package/{packages/cli/src → src}/__tests__/core/lock.test.ts +0 -0
  148. /package/{packages/cli/src → src}/__tests__/core/nginx-multi-domain.test.ts +0 -0
  149. /package/{packages/cli/src → src}/__tests__/runtimes/pm2.test.ts +0 -0
  150. /package/{packages/cli/src → src}/__tests__/utils/validation.test.ts +0 -0
  151. /package/{packages/cli/src → src}/commands/deploy.ts +0 -0
  152. /package/{packages/cli/src → src}/commands/env.ts +0 -0
  153. /package/{packages/cli/src → src}/commands/expose.ts +0 -0
  154. /package/{packages/cli/src → src}/commands/init.ts +0 -0
  155. /package/{packages/cli/src → src}/commands/logs.ts +0 -0
  156. /package/{packages/cli/src → src}/commands/rollback.ts +0 -0
  157. /package/{packages/cli/src → src}/commands/server/info.ts +0 -0
  158. /package/{packages/cli/src → src}/commands/server/setup.ts +0 -0
  159. /package/{packages/cli/src → src}/commands/status.ts +0 -0
  160. /package/{packages/cli/src → src}/config/loader.ts +0 -0
  161. /package/{packages/cli/src → src}/config/schema.ts +0 -0
  162. /package/{packages/cli/src → src}/core/backup.ts +0 -0
  163. /package/{packages/cli/src → src}/core/health.ts +0 -0
  164. /package/{packages/cli/src → src}/core/local.ts +0 -0
  165. /package/{packages/cli/src → src}/core/lock.ts +0 -0
  166. /package/{packages/cli/src → src}/core/nginx.ts +0 -0
  167. /package/{packages/cli/src → src}/core/ssh.ts +0 -0
  168. /package/{packages/cli/src → src}/core/sync.ts +0 -0
  169. /package/{packages/cli/src → src}/core/workspace.ts +0 -0
  170. /package/{packages/cli/src → src}/index.ts +0 -0
  171. /package/{packages/cli/src → src}/runtimes/base.ts +0 -0
  172. /package/{packages/cli/src → src}/runtimes/nodejs/detector.ts +0 -0
  173. /package/{packages/cli/src → src}/runtimes/nodejs/index.ts +0 -0
  174. /package/{packages/cli/src → src}/runtimes/nodejs/pm2.ts +0 -0
  175. /package/{packages/cli/src → src}/runtimes/registry.ts +0 -0
  176. /package/{packages/cli/src → src}/utils/logger.ts +0 -0
  177. /package/{packages/cli/src → src}/utils/validation.ts +0 -0
  178. /package/{packages/cli/tsconfig.json → tsconfig.json} +0 -0
  179. /package/{packages/cli/vitest.config.ts → vitest.config.ts} +0 -0
@@ -0,0 +1,79 @@
1
+ export interface SSHConnectionOptions {
2
+ host: string;
3
+ port?: number;
4
+ username: string;
5
+ password?: string;
6
+ privateKeyPath?: string;
7
+ passphrase?: string;
8
+ }
9
+ export interface SSHCommandResult {
10
+ stdout: string;
11
+ stderr: string;
12
+ exitCode: number;
13
+ }
14
+ /**
15
+ * SSH Connection Manager
16
+ */
17
+ export declare class SSHConnection {
18
+ private client;
19
+ private config;
20
+ private connected;
21
+ constructor(options: SSHConnectionOptions);
22
+ /**
23
+ * Get default SSH key path (~/.ssh/id_rsa)
24
+ */
25
+ private getDefaultKeyPath;
26
+ /**
27
+ * Connect to SSH server
28
+ */
29
+ connect(): Promise<void>;
30
+ /**
31
+ * Execute command on remote server
32
+ */
33
+ exec(command: string, options?: {
34
+ streaming?: boolean;
35
+ cwd?: string;
36
+ skipNvmSetup?: boolean;
37
+ }): Promise<SSHCommandResult>;
38
+ /**
39
+ * Upload file to remote server (SCP)
40
+ */
41
+ uploadFile(localPath: string, remotePath: string): Promise<void>;
42
+ /**
43
+ * Download file from remote server
44
+ */
45
+ downloadFile(remotePath: string, localPath: string): Promise<void>;
46
+ /**
47
+ * Check if file/directory exists on remote
48
+ */
49
+ exists(remotePath: string): Promise<boolean>;
50
+ /**
51
+ * Create directory on remote server
52
+ */
53
+ mkdir(remotePath: string, recursive?: boolean): Promise<void>;
54
+ /**
55
+ * Disconnect from server
56
+ */
57
+ disconnect(): void;
58
+ /**
59
+ * Check if connected
60
+ */
61
+ isConnected(): boolean;
62
+ }
63
+ /**
64
+ * Parse SSH connection string (user@host)
65
+ */
66
+ export declare function parseSSHConnection(connectionString: string): {
67
+ username: string;
68
+ host: string;
69
+ port?: number;
70
+ };
71
+ /**
72
+ * Create SSH connection from connection string
73
+ */
74
+ export declare function createSSHConnection(connectionString: string, options?: {
75
+ password?: string;
76
+ privateKeyPath?: string;
77
+ passphrase?: string;
78
+ }): Promise<SSHConnection>;
79
+ //# sourceMappingURL=ssh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.d.ts","sourceRoot":"","sources":["../../src/core/ssh.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,SAAS,CAAkB;gBAEvB,OAAO,EAAE,oBAAoB;IAoDzC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAc9B;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QACpC,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsD7B;;OAEG;IACG,UAAU,CACd,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAuBhB;;OAEG;IACG,YAAY,CAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAuBhB;;OAEG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASlD;;OAEG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,OAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IASzE;;OAEG;IACH,UAAU,IAAI,IAAI;IAOlB;;OAEG;IACH,WAAW,IAAI,OAAO;CAGvB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,gBAAgB,EAAE,MAAM,GAAG;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAkBA;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,gBAAgB,EAAE,MAAM,EACxB,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,aAAa,CAAC,CAgBxB"}
@@ -0,0 +1,264 @@
1
+ import { Client } from 'ssh2';
2
+ import { readFileSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { resolve } from 'path';
5
+ /**
6
+ * SSH Connection Manager
7
+ */
8
+ export class SSHConnection {
9
+ client;
10
+ config;
11
+ connected = false;
12
+ constructor(options) {
13
+ this.client = new Client();
14
+ // Build SSH config
15
+ this.config = {
16
+ host: options.host,
17
+ port: options.port || 22,
18
+ username: options.username,
19
+ };
20
+ // Add authentication method
21
+ if (options.password) {
22
+ this.config.password = options.password;
23
+ }
24
+ else if (process.env.HOSTFN_SSH_KEY) {
25
+ // CI/CD mode: Load key from environment variable
26
+ try {
27
+ this.config.privateKey = Buffer.from(process.env.HOSTFN_SSH_KEY, 'base64');
28
+ if (options.passphrase || process.env.HOSTFN_SSH_PASSPHRASE) {
29
+ this.config.passphrase = options.passphrase || process.env.HOSTFN_SSH_PASSPHRASE;
30
+ }
31
+ }
32
+ catch (error) {
33
+ throw new Error(`Failed to decode HOSTFN_SSH_KEY from environment variable\n` +
34
+ `Make sure it's base64 encoded: cat ~/.ssh/id_rsa | base64`);
35
+ }
36
+ }
37
+ else {
38
+ // Enable SSH agent for passphrase-protected keys
39
+ if (process.env.SSH_AUTH_SOCK) {
40
+ this.config.agent = process.env.SSH_AUTH_SOCK;
41
+ }
42
+ // Try to load private key from file
43
+ const keyPath = options.privateKeyPath || this.getDefaultKeyPath();
44
+ try {
45
+ this.config.privateKey = readFileSync(keyPath);
46
+ if (options.passphrase) {
47
+ this.config.passphrase = options.passphrase;
48
+ }
49
+ }
50
+ catch (error) {
51
+ throw new Error(`Failed to load SSH key from ${keyPath}\n` +
52
+ `Make sure the file exists or set HOSTFN_SSH_KEY environment variable\n` +
53
+ `For CI/CD: export HOSTFN_SSH_KEY=$(cat ~/.ssh/id_rsa | base64)`);
54
+ }
55
+ }
56
+ }
57
+ /**
58
+ * Get default SSH key path (~/.ssh/id_rsa)
59
+ */
60
+ getDefaultKeyPath() {
61
+ const sshDir = resolve(homedir(), '.ssh');
62
+ // Try common key names
63
+ const keyNames = ['id_rsa', 'id_ed25519', 'id_ecdsa'];
64
+ for (const keyName of keyNames) {
65
+ const keyPath = resolve(sshDir, keyName);
66
+ try {
67
+ readFileSync(keyPath);
68
+ return keyPath;
69
+ }
70
+ catch {
71
+ continue;
72
+ }
73
+ }
74
+ // Default to id_rsa even if it doesn't exist (will error later)
75
+ return resolve(sshDir, 'id_rsa');
76
+ }
77
+ /**
78
+ * Connect to SSH server
79
+ */
80
+ async connect() {
81
+ return new Promise((resolve, reject) => {
82
+ this.client
83
+ .on('ready', () => {
84
+ this.connected = true;
85
+ resolve();
86
+ })
87
+ .on('error', (err) => {
88
+ reject(new Error(`SSH connection failed: ${err.message}`));
89
+ })
90
+ .connect(this.config);
91
+ });
92
+ }
93
+ /**
94
+ * Execute command on remote server
95
+ */
96
+ async exec(command, options) {
97
+ if (!this.connected) {
98
+ throw new Error('Not connected. Call connect() first.');
99
+ }
100
+ // Source nvm to ensure Node.js/npm are in PATH for non-interactive sessions
101
+ // Skip for setup scripts that install nvm
102
+ const nvmSetup = options?.skipNvmSetup
103
+ ? ''
104
+ : 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && ';
105
+ // Add cd command if cwd provided
106
+ const fullCommand = options?.cwd
107
+ ? `${nvmSetup}cd ${options.cwd} && ${command}`
108
+ : `${nvmSetup}${command}`;
109
+ return new Promise((resolve, reject) => {
110
+ this.client.exec(fullCommand, (err, stream) => {
111
+ if (err) {
112
+ reject(new Error(`Failed to execute command: ${err.message}`));
113
+ return;
114
+ }
115
+ let stdout = '';
116
+ let stderr = '';
117
+ stream
118
+ .on('close', (code) => {
119
+ resolve({
120
+ stdout,
121
+ stderr,
122
+ exitCode: code,
123
+ });
124
+ })
125
+ .on('data', (data) => {
126
+ const output = data.toString();
127
+ stdout += output;
128
+ if (options?.streaming) {
129
+ process.stdout.write(output);
130
+ }
131
+ })
132
+ .stderr.on('data', (data) => {
133
+ const output = data.toString();
134
+ stderr += output;
135
+ if (options?.streaming) {
136
+ process.stderr.write(output);
137
+ }
138
+ });
139
+ });
140
+ });
141
+ }
142
+ /**
143
+ * Upload file to remote server (SCP)
144
+ */
145
+ async uploadFile(localPath, remotePath) {
146
+ if (!this.connected) {
147
+ throw new Error('Not connected. Call connect() first.');
148
+ }
149
+ return new Promise((resolve, reject) => {
150
+ this.client.sftp((err, sftp) => {
151
+ if (err) {
152
+ reject(new Error(`SFTP session failed: ${err.message}`));
153
+ return;
154
+ }
155
+ sftp.fastPut(localPath, remotePath, (err) => {
156
+ if (err) {
157
+ reject(new Error(`Failed to upload file: ${err.message}`));
158
+ }
159
+ else {
160
+ resolve();
161
+ }
162
+ });
163
+ });
164
+ });
165
+ }
166
+ /**
167
+ * Download file from remote server
168
+ */
169
+ async downloadFile(remotePath, localPath) {
170
+ if (!this.connected) {
171
+ throw new Error('Not connected. Call connect() first.');
172
+ }
173
+ return new Promise((resolve, reject) => {
174
+ this.client.sftp((err, sftp) => {
175
+ if (err) {
176
+ reject(new Error(`SFTP session failed: ${err.message}`));
177
+ return;
178
+ }
179
+ sftp.fastGet(remotePath, localPath, (err) => {
180
+ if (err) {
181
+ reject(new Error(`Failed to download file: ${err.message}`));
182
+ }
183
+ else {
184
+ resolve();
185
+ }
186
+ });
187
+ });
188
+ });
189
+ }
190
+ /**
191
+ * Check if file/directory exists on remote
192
+ */
193
+ async exists(remotePath) {
194
+ try {
195
+ const result = await this.exec(`test -e ${remotePath} && echo "exists"`);
196
+ return result.stdout.trim() === 'exists';
197
+ }
198
+ catch {
199
+ return false;
200
+ }
201
+ }
202
+ /**
203
+ * Create directory on remote server
204
+ */
205
+ async mkdir(remotePath, recursive = true) {
206
+ const flag = recursive ? '-p' : '';
207
+ const result = await this.exec(`mkdir ${flag} ${remotePath}`);
208
+ if (result.exitCode !== 0) {
209
+ throw new Error(`Failed to create directory: ${result.stderr}`);
210
+ }
211
+ }
212
+ /**
213
+ * Disconnect from server
214
+ */
215
+ disconnect() {
216
+ if (this.connected) {
217
+ this.client.end();
218
+ this.connected = false;
219
+ }
220
+ }
221
+ /**
222
+ * Check if connected
223
+ */
224
+ isConnected() {
225
+ return this.connected;
226
+ }
227
+ }
228
+ /**
229
+ * Parse SSH connection string (user@host)
230
+ */
231
+ export function parseSSHConnection(connectionString) {
232
+ // Support formats:
233
+ // - user@host
234
+ // - user@host:port
235
+ const match = connectionString.match(/^([^@]+)@([^:]+)(?::(\d+))?$/);
236
+ if (!match) {
237
+ throw new Error(`Invalid SSH connection string: ${connectionString}\n` +
238
+ `Expected format: user@host or user@host:port`);
239
+ }
240
+ return {
241
+ username: match[1],
242
+ host: match[2],
243
+ port: match[3] ? parseInt(match[3]) : undefined,
244
+ };
245
+ }
246
+ /**
247
+ * Create SSH connection from connection string
248
+ */
249
+ export async function createSSHConnection(connectionString, options) {
250
+ // Support HOSTFN_HOST env var for CI/CD
251
+ const hostToUse = process.env.HOSTFN_HOST || connectionString;
252
+ const { username, host, port } = parseSSHConnection(hostToUse);
253
+ const ssh = new SSHConnection({
254
+ host,
255
+ port,
256
+ username,
257
+ password: options?.password || process.env.HOSTFN_SSH_PASSWORD,
258
+ privateKeyPath: options?.privateKeyPath,
259
+ passphrase: options?.passphrase || process.env.HOSTFN_SSH_PASSPHRASE,
260
+ });
261
+ await ssh.connect();
262
+ return ssh;
263
+ }
264
+ //# sourceMappingURL=ssh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../src/core/ssh.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export interface SyncOptions {
2
+ exclude?: string[];
3
+ include?: string[];
4
+ dryRun?: boolean;
5
+ verbose?: boolean;
6
+ }
7
+ /**
8
+ * Sync files to remote server using rsync
9
+ */
10
+ export declare class FileSync {
11
+ /**
12
+ * Sync local directory to remote server
13
+ */
14
+ static sync(localPath: string, remotePath: string, sshConnection: string, // user@host
15
+ options?: SyncOptions): Promise<void>;
16
+ /**
17
+ * Check if rsync is available
18
+ */
19
+ static isRsyncAvailable(): Promise<boolean>;
20
+ /**
21
+ * Get rsync version
22
+ */
23
+ static getRsyncVersion(): Promise<string>;
24
+ }
25
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/core/sync.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB;;OAEG;WACU,IAAI,CACf,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EAAE,YAAY;IACnC,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,IAAI,CAAC;IA2FhB;;OAEG;WACU,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IASjD;;OAEG;WACU,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;CAShD"}
@@ -0,0 +1,117 @@
1
+ import { execa } from 'execa';
2
+ /**
3
+ * Sync files to remote server using rsync
4
+ */
5
+ export class FileSync {
6
+ /**
7
+ * Sync local directory to remote server
8
+ */
9
+ static async sync(localPath, remotePath, sshConnection, // user@host
10
+ options = {}) {
11
+ const args = [
12
+ '-avz', // archive, verbose, compress
13
+ '--delete', // delete files on remote that don't exist locally
14
+ ];
15
+ // Handle SSH authentication for CI/CD mode
16
+ let tempKeyPath;
17
+ if (process.env.HOSTFN_SSH_KEY) {
18
+ // CI/CD mode: create temporary key file from base64-encoded env var
19
+ const { writeFileSync, mkdtempSync } = await import('fs');
20
+ const { join } = await import('path');
21
+ const { tmpdir } = await import('os');
22
+ const tempDir = mkdtempSync(join(tmpdir(), 'hostfn-ssh-'));
23
+ tempKeyPath = join(tempDir, 'id_rsa');
24
+ // Decode and write the SSH key
25
+ const keyBuffer = Buffer.from(process.env.HOSTFN_SSH_KEY, 'base64');
26
+ writeFileSync(tempKeyPath, keyBuffer, { mode: 0o600 });
27
+ // Build SSH command with the temporary key
28
+ const sshCmd = `ssh -i ${tempKeyPath} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes`;
29
+ args.push('-e', sshCmd);
30
+ }
31
+ else {
32
+ // Local mode: use default SSH config (respects ~/.ssh/config and ssh-agent)
33
+ args.push('-e', 'ssh -o StrictHostKeyChecking=no -o BatchMode=yes');
34
+ }
35
+ // Add dry-run flag
36
+ if (options.dryRun) {
37
+ args.push('--dry-run');
38
+ }
39
+ // Add exclude patterns
40
+ if (options.exclude && options.exclude.length > 0) {
41
+ for (const pattern of options.exclude) {
42
+ args.push('--exclude', pattern);
43
+ }
44
+ }
45
+ // Add include patterns
46
+ if (options.include && options.include.length > 0) {
47
+ for (const pattern of options.include) {
48
+ args.push('--include', pattern);
49
+ }
50
+ }
51
+ // Ensure trailing slash on source (rsync behavior)
52
+ const source = localPath.endsWith('/') ? localPath : `${localPath}/`;
53
+ const destination = `${sshConnection}:${remotePath}`;
54
+ args.push(source, destination);
55
+ try {
56
+ const result = await execa('rsync', args, {
57
+ stdio: options.verbose ? 'inherit' : 'pipe',
58
+ });
59
+ if (result.exitCode !== 0) {
60
+ throw new Error(`rsync failed with exit code ${result.exitCode}`);
61
+ }
62
+ }
63
+ catch (error) {
64
+ if (error instanceof Error) {
65
+ // Check if rsync is not installed
66
+ if (error.message.includes('ENOENT') || error.message.includes('not found')) {
67
+ throw new Error('rsync is not installed on your system.\n' +
68
+ 'Please install it:\n' +
69
+ ' - macOS: brew install rsync\n' +
70
+ ' - Ubuntu/Debian: apt-get install rsync\n' +
71
+ ' - Windows: Install via WSL or use Git Bash');
72
+ }
73
+ }
74
+ throw error;
75
+ }
76
+ finally {
77
+ // Cleanup temporary key file if it was created
78
+ if (tempKeyPath) {
79
+ try {
80
+ const { unlinkSync, rmdirSync } = await import('fs');
81
+ const { dirname } = await import('path');
82
+ unlinkSync(tempKeyPath);
83
+ rmdirSync(dirname(tempKeyPath));
84
+ }
85
+ catch {
86
+ // Ignore cleanup errors
87
+ }
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * Check if rsync is available
93
+ */
94
+ static async isRsyncAvailable() {
95
+ try {
96
+ await execa('rsync', ['--version']);
97
+ return true;
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ /**
104
+ * Get rsync version
105
+ */
106
+ static async getRsyncVersion() {
107
+ try {
108
+ const result = await execa('rsync', ['--version']);
109
+ const match = result.stdout.match(/rsync\s+version\s+([\d.]+)/);
110
+ return match ? match[1] : 'unknown';
111
+ }
112
+ catch {
113
+ return 'not installed';
114
+ }
115
+ }
116
+ }
117
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/core/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export declare class WorkspaceManager {
2
+ private workspaceRoot;
3
+ private workspacePackages;
4
+ detectWorkspace(cwd: string): Promise<boolean>;
5
+ private indexWorkspacePackages;
6
+ getWorkspaceDependencies(servicePath: string): string[];
7
+ bundleWorkspaceDependencies(servicePath: string, targetDir: string): Promise<void>;
8
+ rewritePackageJson(servicePath: string, targetDir: string): void;
9
+ private readPackageJson;
10
+ getWorkspaceRoot(): string | null;
11
+ isInWorkspace(): boolean;
12
+ }
13
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/core/workspace.ts"],"names":[],"mappings":"AAaA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,iBAAiB,CAAkC;IAErD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAuBtC,sBAAsB;IA0BpC,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE;IAsBjD,2BAA2B,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4CxF,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IA6BhE,OAAO,CAAC,eAAe;IAWvB,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAIjC,aAAa,IAAI,OAAO;CAGzB"}
@@ -0,0 +1,141 @@
1
+ import { readFileSync, writeFileSync, existsSync, cpSync, mkdirSync } from 'fs';
2
+ import { join, dirname, resolve } from 'path';
3
+ import { Logger } from '../utils/logger.js';
4
+ export class WorkspaceManager {
5
+ workspaceRoot = null;
6
+ workspacePackages = new Map();
7
+ async detectWorkspace(cwd) {
8
+ let currentDir = cwd;
9
+ const root = resolve('/');
10
+ while (currentDir !== root) {
11
+ const pkgPath = join(currentDir, 'package.json');
12
+ if (existsSync(pkgPath)) {
13
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
14
+ if (pkg.workspaces) {
15
+ this.workspaceRoot = currentDir;
16
+ await this.indexWorkspacePackages();
17
+ return true;
18
+ }
19
+ }
20
+ currentDir = dirname(currentDir);
21
+ }
22
+ return false;
23
+ }
24
+ async indexWorkspacePackages() {
25
+ if (!this.workspaceRoot)
26
+ return;
27
+ const rootPkg = this.readPackageJson(this.workspaceRoot);
28
+ if (!rootPkg?.workspaces)
29
+ return;
30
+ const workspaces = Array.isArray(rootPkg.workspaces)
31
+ ? rootPkg.workspaces
32
+ : rootPkg.workspaces.packages;
33
+ const glob = await import('fast-glob');
34
+ for (const pattern of workspaces) {
35
+ const matches = await glob.default(join(this.workspaceRoot, pattern, 'package.json'), {
36
+ absolute: true,
37
+ });
38
+ for (const pkgPath of matches) {
39
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
40
+ if (pkg.name) {
41
+ this.workspacePackages.set(pkg.name, dirname(pkgPath));
42
+ }
43
+ }
44
+ }
45
+ }
46
+ getWorkspaceDependencies(servicePath) {
47
+ const pkg = this.readPackageJson(servicePath);
48
+ if (!pkg)
49
+ return [];
50
+ const allDeps = {
51
+ ...pkg.dependencies,
52
+ ...pkg.devDependencies,
53
+ };
54
+ const workspaceDeps = [];
55
+ for (const [depName, depVersion] of Object.entries(allDeps)) {
56
+ if (depVersion === '*' || depVersion === 'workspace:*' || depVersion.startsWith('workspace:')) {
57
+ if (this.workspacePackages.has(depName)) {
58
+ workspaceDeps.push(depName);
59
+ }
60
+ }
61
+ }
62
+ return workspaceDeps;
63
+ }
64
+ async bundleWorkspaceDependencies(servicePath, targetDir) {
65
+ const workspaceDeps = this.getWorkspaceDependencies(servicePath);
66
+ if (workspaceDeps.length === 0) {
67
+ return;
68
+ }
69
+ Logger.info(`Bundling ${workspaceDeps.length} workspace dependencies...`);
70
+ const nodeModulesDir = join(targetDir, 'node_modules');
71
+ mkdirSync(nodeModulesDir, { recursive: true });
72
+ for (const depName of workspaceDeps) {
73
+ const depPath = this.workspacePackages.get(depName);
74
+ if (!depPath)
75
+ continue;
76
+ const depPkg = this.readPackageJson(depPath);
77
+ if (depPkg?.scripts?.build) {
78
+ Logger.log(` → Building ${depName}...`);
79
+ try {
80
+ const { execSync } = await import('child_process');
81
+ execSync('npm run build', { cwd: depPath, stdio: 'ignore' });
82
+ }
83
+ catch (error) {
84
+ Logger.warn(` ⚠ Failed to build ${depName}, bundling as-is`);
85
+ }
86
+ }
87
+ const targetDepDir = join(nodeModulesDir, depName);
88
+ mkdirSync(dirname(targetDepDir), { recursive: true });
89
+ cpSync(depPath, targetDepDir, {
90
+ recursive: true,
91
+ filter: (src) => {
92
+ const relativePath = src.replace(depPath, '');
93
+ return !relativePath.includes('node_modules') &&
94
+ !relativePath.includes('.git');
95
+ },
96
+ });
97
+ Logger.log(` ✓ Bundled ${depName}`);
98
+ }
99
+ }
100
+ rewritePackageJson(servicePath, targetDir) {
101
+ const workspaceDeps = this.getWorkspaceDependencies(servicePath);
102
+ if (workspaceDeps.length === 0) {
103
+ return;
104
+ }
105
+ const pkgPath = join(targetDir, 'package.json');
106
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
107
+ if (pkg.dependencies) {
108
+ for (const depName of workspaceDeps) {
109
+ if (pkg.dependencies[depName]) {
110
+ pkg.dependencies[depName] = `file:./node_modules/${depName}`;
111
+ }
112
+ }
113
+ }
114
+ if (pkg.devDependencies) {
115
+ for (const depName of workspaceDeps) {
116
+ if (pkg.devDependencies[depName]) {
117
+ delete pkg.devDependencies[depName];
118
+ }
119
+ }
120
+ }
121
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
122
+ }
123
+ readPackageJson(dir) {
124
+ const pkgPath = join(dir, 'package.json');
125
+ if (!existsSync(pkgPath))
126
+ return null;
127
+ try {
128
+ return JSON.parse(readFileSync(pkgPath, 'utf-8'));
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ }
134
+ getWorkspaceRoot() {
135
+ return this.workspaceRoot;
136
+ }
137
+ isInWorkspace() {
138
+ return this.workspaceRoot !== null;
139
+ }
140
+ }
141
+ //# sourceMappingURL=workspace.js.map