@hatchway/cli 0.50.53

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 (80) hide show
  1. package/README.md +274 -0
  2. package/bin/hatchway.js +31 -0
  3. package/dist/chunks/Banner-DL1Fpz_g.js +115 -0
  4. package/dist/chunks/Banner-DL1Fpz_g.js.map +1 -0
  5. package/dist/chunks/auto-update-Ddo5Ntt7.js +264 -0
  6. package/dist/chunks/auto-update-Ddo5Ntt7.js.map +1 -0
  7. package/dist/chunks/build-V8_D-JHF.js +116 -0
  8. package/dist/chunks/build-V8_D-JHF.js.map +1 -0
  9. package/dist/chunks/cleanup-BNuJNSve.js +141 -0
  10. package/dist/chunks/cleanup-BNuJNSve.js.map +1 -0
  11. package/dist/chunks/cli-auth-B4Do-N8Y.js +340 -0
  12. package/dist/chunks/cli-auth-B4Do-N8Y.js.map +1 -0
  13. package/dist/chunks/cli-error-1drkrXNn.js +140 -0
  14. package/dist/chunks/cli-error-1drkrXNn.js.map +1 -0
  15. package/dist/chunks/config-hFJA7z5y.js +167 -0
  16. package/dist/chunks/config-hFJA7z5y.js.map +1 -0
  17. package/dist/chunks/config-manager-DST6RbP8.js +133 -0
  18. package/dist/chunks/config-manager-DST6RbP8.js.map +1 -0
  19. package/dist/chunks/database-YGb1Lzim.js +68 -0
  20. package/dist/chunks/database-YGb1Lzim.js.map +1 -0
  21. package/dist/chunks/database-setup-U31oEs90.js +253 -0
  22. package/dist/chunks/database-setup-U31oEs90.js.map +1 -0
  23. package/dist/chunks/devtools-CPruVlOo.js +75 -0
  24. package/dist/chunks/devtools-CPruVlOo.js.map +1 -0
  25. package/dist/chunks/index-DCC6HGdr.js +119 -0
  26. package/dist/chunks/index-DCC6HGdr.js.map +1 -0
  27. package/dist/chunks/init-DkXJVFFx.js +472 -0
  28. package/dist/chunks/init-DkXJVFFx.js.map +1 -0
  29. package/dist/chunks/init-tui-D2VOVdeK.js +1131 -0
  30. package/dist/chunks/init-tui-D2VOVdeK.js.map +1 -0
  31. package/dist/chunks/logger-6V5cBxba.js +38 -0
  32. package/dist/chunks/logger-6V5cBxba.js.map +1 -0
  33. package/dist/chunks/login-CA1XWUEM.js +63 -0
  34. package/dist/chunks/login-CA1XWUEM.js.map +1 -0
  35. package/dist/chunks/logout-BC4VFt8f.js +40 -0
  36. package/dist/chunks/logout-BC4VFt8f.js.map +1 -0
  37. package/dist/chunks/main-tui-D8KkJRd_.js +648 -0
  38. package/dist/chunks/main-tui-D8KkJRd_.js.map +1 -0
  39. package/dist/chunks/manager-DjVI7erc.js +1161 -0
  40. package/dist/chunks/manager-DjVI7erc.js.map +1 -0
  41. package/dist/chunks/port-allocator-BENntRMG.js +864 -0
  42. package/dist/chunks/port-allocator-BENntRMG.js.map +1 -0
  43. package/dist/chunks/process-killer-ChXAqhfm.js +87 -0
  44. package/dist/chunks/process-killer-ChXAqhfm.js.map +1 -0
  45. package/dist/chunks/prompts-Beijr8dm.js +128 -0
  46. package/dist/chunks/prompts-Beijr8dm.js.map +1 -0
  47. package/dist/chunks/repo-cloner-UY3L2X7h.js +219 -0
  48. package/dist/chunks/repo-cloner-UY3L2X7h.js.map +1 -0
  49. package/dist/chunks/repo-detector-36VydrlB.js +66 -0
  50. package/dist/chunks/repo-detector-36VydrlB.js.map +1 -0
  51. package/dist/chunks/run-Du6dvTJL.js +697 -0
  52. package/dist/chunks/run-Du6dvTJL.js.map +1 -0
  53. package/dist/chunks/runner-logger-instance-Dj_JMznn.js +899 -0
  54. package/dist/chunks/runner-logger-instance-Dj_JMznn.js.map +1 -0
  55. package/dist/chunks/spinner-DTH0QZQw.js +53 -0
  56. package/dist/chunks/spinner-DTH0QZQw.js.map +1 -0
  57. package/dist/chunks/start-Dkuro1jp.js +1713 -0
  58. package/dist/chunks/start-Dkuro1jp.js.map +1 -0
  59. package/dist/chunks/start-traditional-7wlD2f2H.js +255 -0
  60. package/dist/chunks/start-traditional-7wlD2f2H.js.map +1 -0
  61. package/dist/chunks/status-BU3cFJm1.js +97 -0
  62. package/dist/chunks/status-BU3cFJm1.js.map +1 -0
  63. package/dist/chunks/theme-NAQBkisB.js +40222 -0
  64. package/dist/chunks/theme-NAQBkisB.js.map +1 -0
  65. package/dist/chunks/upgrade-BBpJirEu.js +455 -0
  66. package/dist/chunks/upgrade-BBpJirEu.js.map +1 -0
  67. package/dist/chunks/use-app-Ct3w2jLI.js +10 -0
  68. package/dist/chunks/use-app-Ct3w2jLI.js.map +1 -0
  69. package/dist/chunks/useBuildState-Dy7pRR8Z.js +330 -0
  70. package/dist/chunks/useBuildState-Dy7pRR8Z.js.map +1 -0
  71. package/dist/cli/index.js +712 -0
  72. package/dist/cli/index.js.map +1 -0
  73. package/dist/index.js +13625 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/instrument.js +45 -0
  76. package/dist/instrument.js.map +1 -0
  77. package/dist/templates.json +295 -0
  78. package/package.json +87 -0
  79. package/templates/config.template.json +18 -0
  80. package/templates.json +295 -0
@@ -0,0 +1,340 @@
1
+ // Hatchway CLI - Built with Rollup
2
+ import http from 'node:http';
3
+ import { URL } from 'node:url';
4
+ import { hostname, platform } from 'node:os';
5
+ import { exec } from 'node:child_process';
6
+ import chalk from 'chalk';
7
+ import { c as configManager } from './config-manager-DST6RbP8.js';
8
+
9
+ /**
10
+ * Open a URL in the default browser (cross-platform)
11
+ */
12
+ async function openBrowser(url) {
13
+ const os = platform();
14
+ return new Promise((resolve, reject) => {
15
+ let command;
16
+ switch (os) {
17
+ case 'darwin':
18
+ command = `open "${url}"`;
19
+ break;
20
+ case 'win32':
21
+ command = `start "" "${url}"`;
22
+ break;
23
+ default: // Linux and others
24
+ command = `xdg-open "${url}"`;
25
+ }
26
+ exec(command, (error) => {
27
+ if (error) {
28
+ reject(error);
29
+ }
30
+ else {
31
+ resolve();
32
+ }
33
+ });
34
+ });
35
+ }
36
+ /**
37
+ * Find an available port for the callback server
38
+ */
39
+ async function findAvailablePort(startPort = 9876, endPort = 9999) {
40
+ for (let port = startPort; port <= endPort; port++) {
41
+ const isAvailable = await new Promise((resolve) => {
42
+ const server = http.createServer();
43
+ server.once('error', () => resolve(false));
44
+ server.once('listening', () => {
45
+ server.close();
46
+ resolve(true);
47
+ });
48
+ server.listen(port, '127.0.0.1');
49
+ });
50
+ if (isAvailable) {
51
+ return port;
52
+ }
53
+ }
54
+ throw new Error('No available ports found for OAuth callback server');
55
+ }
56
+ /**
57
+ * Get a device name for the runner key
58
+ */
59
+ function getDeviceName() {
60
+ const host = hostname();
61
+ const date = new Date().toLocaleDateString();
62
+ return `CLI - ${host} - ${date}`;
63
+ }
64
+ /**
65
+ * Start a local HTTP server to receive the OAuth callback
66
+ */
67
+ function startCallbackServer(port) {
68
+ return new Promise((resolve) => {
69
+ // Track active connections so we can forcefully close them
70
+ const connections = new Set();
71
+ const server = http.createServer((req, res) => {
72
+ const url = new URL(req.url || '/', `http://localhost:${port}`);
73
+ if (url.pathname === '/callback') {
74
+ const token = url.searchParams.get('token');
75
+ const status = url.searchParams.get('status');
76
+ const error = url.searchParams.get('error');
77
+ // Send response to browser
78
+ res.writeHead(200, { 'Content-Type': 'text/html' });
79
+ if (status === 'success' && token) {
80
+ res.end(`
81
+ <!DOCTYPE html>
82
+ <html>
83
+ <head>
84
+ <title>Authentication Successful</title>
85
+ <style>
86
+ body {
87
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
88
+ display: flex;
89
+ justify-content: center;
90
+ align-items: center;
91
+ min-height: 100vh;
92
+ margin: 0;
93
+ background: #09090b;
94
+ color: white;
95
+ }
96
+ .container {
97
+ text-align: center;
98
+ padding: 40px;
99
+ }
100
+ .success {
101
+ color: #22c55e;
102
+ font-size: 48px;
103
+ margin-bottom: 20px;
104
+ }
105
+ h1 { margin: 0 0 10px; }
106
+ p { color: #a1a1aa; }
107
+ </style>
108
+ </head>
109
+ <body>
110
+ <div class="container">
111
+ <div class="success">&#10003;</div>
112
+ <h1>Authentication Successful!</h1>
113
+ <p>You can close this window and return to your terminal.</p>
114
+ </div>
115
+ </body>
116
+ </html>
117
+ `);
118
+ // Close server and resolve - destroy all connections to ensure clean exit
119
+ server.close();
120
+ connections.forEach(conn => conn.destroy());
121
+ resolve({ success: true, token });
122
+ }
123
+ else {
124
+ res.end(`
125
+ <!DOCTYPE html>
126
+ <html>
127
+ <head>
128
+ <title>Authentication Failed</title>
129
+ <style>
130
+ body {
131
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
132
+ display: flex;
133
+ justify-content: center;
134
+ align-items: center;
135
+ min-height: 100vh;
136
+ margin: 0;
137
+ background: #09090b;
138
+ color: white;
139
+ }
140
+ .container {
141
+ text-align: center;
142
+ padding: 40px;
143
+ }
144
+ .error {
145
+ color: #ef4444;
146
+ font-size: 48px;
147
+ margin-bottom: 20px;
148
+ }
149
+ h1 { margin: 0 0 10px; }
150
+ p { color: #a1a1aa; }
151
+ </style>
152
+ </head>
153
+ <body>
154
+ <div class="container">
155
+ <div class="error">&#10007;</div>
156
+ <h1>Authentication Failed</h1>
157
+ <p>${error || 'An error occurred during authentication.'}</p>
158
+ <p>Please close this window and try again.</p>
159
+ </div>
160
+ </body>
161
+ </html>
162
+ `);
163
+ server.close();
164
+ connections.forEach(conn => conn.destroy());
165
+ resolve({ success: false, error: error || 'Authentication failed' });
166
+ }
167
+ }
168
+ else {
169
+ // Handle other paths
170
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
171
+ res.end('Not found');
172
+ }
173
+ });
174
+ // Timeout after 5 minutes
175
+ const timeout = setTimeout(() => {
176
+ server.close();
177
+ connections.forEach(conn => conn.destroy());
178
+ resolve({ success: false, error: 'Authentication timed out' });
179
+ }, 5 * 60 * 1000);
180
+ server.on('close', () => {
181
+ clearTimeout(timeout);
182
+ });
183
+ // Handle server errors (e.g., port became unavailable due to race condition)
184
+ server.on('error', (err) => {
185
+ clearTimeout(timeout);
186
+ if (err.code === 'EADDRINUSE') {
187
+ resolve({ success: false, error: `Port ${port} is no longer available. Please try again.` });
188
+ }
189
+ else {
190
+ resolve({ success: false, error: `Server error: ${err.message}` });
191
+ }
192
+ });
193
+ // Track connections for cleanup
194
+ server.on('connection', (conn) => {
195
+ connections.add(conn);
196
+ conn.on('close', () => connections.delete(conn));
197
+ });
198
+ server.listen(port, '127.0.0.1');
199
+ });
200
+ }
201
+ /**
202
+ * Perform CLI OAuth authentication flow
203
+ *
204
+ * 1. Find an available port
205
+ * 2. Start local callback server
206
+ * 3. Request auth session from API
207
+ * 4. Open browser to auth page
208
+ * 5. Wait for callback
209
+ * 6. Return token
210
+ */
211
+ async function performOAuthLogin(options = {}) {
212
+ const apiUrl = options.apiUrl || 'https://hatchway.sh';
213
+ const deviceName = options.deviceName || getDeviceName();
214
+ const silent = options.silent || false;
215
+ try {
216
+ // Find available port
217
+ if (!silent) {
218
+ console.log(chalk.dim('Finding available port for callback...'));
219
+ }
220
+ const port = await findAvailablePort();
221
+ // Start callback server before requesting auth (so it's ready when browser redirects)
222
+ const callbackPromise = startCallbackServer(port);
223
+ // Request auth session from API
224
+ if (!silent) {
225
+ console.log(chalk.dim('Initiating authentication...'));
226
+ }
227
+ const response = await fetch(`${apiUrl}/api/auth/cli/start`, {
228
+ method: 'POST',
229
+ headers: {
230
+ 'Content-Type': 'application/json',
231
+ },
232
+ body: JSON.stringify({
233
+ callbackPort: port,
234
+ callbackHost: 'localhost',
235
+ deviceName,
236
+ }),
237
+ });
238
+ if (!response.ok) {
239
+ const data = await response.json();
240
+ return { success: false, error: data.error || 'Failed to start authentication' };
241
+ }
242
+ const { authUrl } = await response.json();
243
+ // Open browser
244
+ if (!silent) {
245
+ console.log('');
246
+ console.log(chalk.cyan('Opening browser for authentication...'));
247
+ console.log(chalk.dim(`If the browser doesn't open, visit:`));
248
+ console.log(chalk.underline(authUrl));
249
+ console.log('');
250
+ }
251
+ try {
252
+ await openBrowser(authUrl);
253
+ }
254
+ catch {
255
+ // Browser failed to open - user will need to copy/paste URL
256
+ if (!silent) {
257
+ console.log(chalk.yellow('Could not open browser automatically.'));
258
+ }
259
+ }
260
+ if (!silent) {
261
+ console.log(chalk.yellow('Waiting for authentication...'));
262
+ console.log(chalk.dim('(Press Ctrl+C to cancel)'));
263
+ }
264
+ // Wait for callback
265
+ const result = await callbackPromise;
266
+ return result;
267
+ }
268
+ catch (error) {
269
+ return {
270
+ success: false,
271
+ error: error instanceof Error ? error.message : 'Authentication failed',
272
+ };
273
+ }
274
+ }
275
+ /**
276
+ * Check if we have a valid stored token
277
+ */
278
+ function hasStoredToken() {
279
+ const token = getStoredToken();
280
+ return token !== null && token.startsWith('sv_');
281
+ }
282
+ /**
283
+ * Get the stored runner token
284
+ */
285
+ function getStoredToken() {
286
+ const config = configManager.get();
287
+ // Check for runner token in config
288
+ if (config.server?.secret && config.server.secret.startsWith('sv_')) {
289
+ return config.server.secret;
290
+ }
291
+ // Legacy: check broker config
292
+ if (config.broker?.secret && config.broker.secret.startsWith('sv_')) {
293
+ return config.broker.secret;
294
+ }
295
+ return null;
296
+ }
297
+ /**
298
+ * Store the runner token in config
299
+ */
300
+ function storeToken(token, apiUrl) {
301
+ // Determine the WebSocket URL from the API URL
302
+ let wsUrl = 'wss://hatchway.sh/ws/runner';
303
+ if (apiUrl) {
304
+ const url = new URL(apiUrl);
305
+ const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
306
+ wsUrl = `${protocol}//${url.host}/ws/runner`;
307
+ }
308
+ // Store in the server config
309
+ configManager.set('server', {
310
+ wsUrl,
311
+ secret: token,
312
+ });
313
+ // Also set the apiUrl if provided
314
+ if (apiUrl) {
315
+ configManager.set('apiUrl', apiUrl);
316
+ }
317
+ }
318
+ /**
319
+ * Clear the stored token
320
+ */
321
+ function clearToken() {
322
+ const config = configManager.get();
323
+ // Clear server secret
324
+ if (config.server) {
325
+ configManager.set('server', {
326
+ ...config.server,
327
+ secret: '',
328
+ });
329
+ }
330
+ // Clear legacy broker secret
331
+ if (config.broker) {
332
+ configManager.set('broker', {
333
+ ...config.broker,
334
+ secret: '',
335
+ });
336
+ }
337
+ }
338
+
339
+ export { clearToken as c, getStoredToken as g, hasStoredToken as h, openBrowser as o, performOAuthLogin as p, storeToken as s };
340
+ //# sourceMappingURL=cli-auth-B4Do-N8Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-auth-B4Do-N8Y.js","sources":["../../src/cli/utils/cli-auth.ts"],"sourcesContent":["import http from 'node:http';\nimport { URL } from 'node:url';\nimport { hostname, platform } from 'node:os';\nimport { exec } from 'node:child_process';\nimport chalk from 'chalk';\nimport { configManager } from './config-manager.js';\n\n/**\n * Open a URL in the default browser (cross-platform)\n */\nexport async function openBrowser(url: string): Promise<void> {\n const os = platform();\n \n return new Promise((resolve, reject) => {\n let command: string;\n \n switch (os) {\n case 'darwin':\n command = `open \"${url}\"`;\n break;\n case 'win32':\n command = `start \"\" \"${url}\"`;\n break;\n default: // Linux and others\n command = `xdg-open \"${url}\"`;\n }\n \n exec(command, (error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n });\n}\n\ninterface AuthResult {\n success: boolean;\n token?: string;\n error?: string;\n}\n\ninterface CLIAuthOptions {\n apiUrl?: string;\n deviceName?: string;\n silent?: boolean;\n}\n\n/**\n * Find an available port for the callback server\n */\nasync function findAvailablePort(startPort: number = 9876, endPort: number = 9999): Promise<number> {\n for (let port = startPort; port <= endPort; port++) {\n const isAvailable = await new Promise<boolean>((resolve) => {\n const server = http.createServer();\n server.once('error', () => resolve(false));\n server.once('listening', () => {\n server.close();\n resolve(true);\n });\n server.listen(port, '127.0.0.1');\n });\n \n if (isAvailable) {\n return port;\n }\n }\n \n throw new Error('No available ports found for OAuth callback server');\n}\n\n/**\n * Get a device name for the runner key\n */\nfunction getDeviceName(): string {\n const host = hostname();\n const date = new Date().toLocaleDateString();\n return `CLI - ${host} - ${date}`;\n}\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nfunction startCallbackServer(port: number): Promise<AuthResult> {\n return new Promise((resolve) => {\n // Track active connections so we can forcefully close them\n const connections = new Set<import('net').Socket>();\n \n const server = http.createServer((req, res) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`);\n \n if (url.pathname === '/callback') {\n const token = url.searchParams.get('token');\n const status = url.searchParams.get('status');\n const error = url.searchParams.get('error');\n \n // Send response to browser\n res.writeHead(200, { 'Content-Type': 'text/html' });\n \n if (status === 'success' && token) {\n res.end(`\n <!DOCTYPE html>\n <html>\n <head>\n <title>Authentication Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n margin: 0;\n background: #09090b;\n color: white;\n }\n .container {\n text-align: center;\n padding: 40px;\n }\n .success {\n color: #22c55e;\n font-size: 48px;\n margin-bottom: 20px;\n }\n h1 { margin: 0 0 10px; }\n p { color: #a1a1aa; }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <div class=\"success\">&#10003;</div>\n <h1>Authentication Successful!</h1>\n <p>You can close this window and return to your terminal.</p>\n </div>\n </body>\n </html>\n `);\n \n // Close server and resolve - destroy all connections to ensure clean exit\n server.close();\n connections.forEach(conn => conn.destroy());\n resolve({ success: true, token });\n } else {\n res.end(`\n <!DOCTYPE html>\n <html>\n <head>\n <title>Authentication Failed</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n margin: 0;\n background: #09090b;\n color: white;\n }\n .container {\n text-align: center;\n padding: 40px;\n }\n .error {\n color: #ef4444;\n font-size: 48px;\n margin-bottom: 20px;\n }\n h1 { margin: 0 0 10px; }\n p { color: #a1a1aa; }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <div class=\"error\">&#10007;</div>\n <h1>Authentication Failed</h1>\n <p>${error || 'An error occurred during authentication.'}</p>\n <p>Please close this window and try again.</p>\n </div>\n </body>\n </html>\n `);\n \n server.close();\n connections.forEach(conn => conn.destroy());\n resolve({ success: false, error: error || 'Authentication failed' });\n }\n } else {\n // Handle other paths\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n }\n });\n \n // Timeout after 5 minutes\n const timeout = setTimeout(() => {\n server.close();\n connections.forEach(conn => conn.destroy());\n resolve({ success: false, error: 'Authentication timed out' });\n }, 5 * 60 * 1000);\n \n server.on('close', () => {\n clearTimeout(timeout);\n });\n \n // Handle server errors (e.g., port became unavailable due to race condition)\n server.on('error', (err: NodeJS.ErrnoException) => {\n clearTimeout(timeout);\n if (err.code === 'EADDRINUSE') {\n resolve({ success: false, error: `Port ${port} is no longer available. Please try again.` });\n } else {\n resolve({ success: false, error: `Server error: ${err.message}` });\n }\n });\n \n // Track connections for cleanup\n server.on('connection', (conn) => {\n connections.add(conn);\n conn.on('close', () => connections.delete(conn));\n });\n \n server.listen(port, '127.0.0.1');\n });\n}\n\n/**\n * Perform CLI OAuth authentication flow\n * \n * 1. Find an available port\n * 2. Start local callback server\n * 3. Request auth session from API\n * 4. Open browser to auth page\n * 5. Wait for callback\n * 6. Return token\n */\nexport async function performOAuthLogin(options: CLIAuthOptions = {}): Promise<AuthResult> {\n const apiUrl = options.apiUrl || 'https://hatchway.sh';\n const deviceName = options.deviceName || getDeviceName();\n const silent = options.silent || false;\n \n try {\n // Find available port\n if (!silent) {\n console.log(chalk.dim('Finding available port for callback...'));\n }\n const port = await findAvailablePort();\n \n // Start callback server before requesting auth (so it's ready when browser redirects)\n const callbackPromise = startCallbackServer(port);\n \n // Request auth session from API\n if (!silent) {\n console.log(chalk.dim('Initiating authentication...'));\n }\n \n const response = await fetch(`${apiUrl}/api/auth/cli/start`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n callbackPort: port,\n callbackHost: 'localhost',\n deviceName,\n }),\n });\n \n if (!response.ok) {\n const data = await response.json();\n return { success: false, error: data.error || 'Failed to start authentication' };\n }\n \n const { authUrl } = await response.json();\n \n // Open browser\n if (!silent) {\n console.log('');\n console.log(chalk.cyan('Opening browser for authentication...'));\n console.log(chalk.dim(`If the browser doesn't open, visit:`));\n console.log(chalk.underline(authUrl));\n console.log('');\n }\n \n try {\n await openBrowser(authUrl);\n } catch {\n // Browser failed to open - user will need to copy/paste URL\n if (!silent) {\n console.log(chalk.yellow('Could not open browser automatically.'));\n }\n }\n \n if (!silent) {\n console.log(chalk.yellow('Waiting for authentication...'));\n console.log(chalk.dim('(Press Ctrl+C to cancel)'));\n }\n \n // Wait for callback\n const result = await callbackPromise;\n \n return result;\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Authentication failed',\n };\n }\n}\n\n/**\n * Check if we have a valid stored token\n */\nexport function hasStoredToken(): boolean {\n const token = getStoredToken();\n return token !== null && token.startsWith('sv_');\n}\n\n/**\n * Get the stored runner token\n */\nexport function getStoredToken(): string | null {\n const config = configManager.get();\n \n // Check for runner token in config\n if (config.server?.secret && config.server.secret.startsWith('sv_')) {\n return config.server.secret;\n }\n \n // Legacy: check broker config\n if (config.broker?.secret && config.broker.secret.startsWith('sv_')) {\n return config.broker.secret;\n }\n \n return null;\n}\n\n/**\n * Store the runner token in config\n */\nexport function storeToken(token: string, apiUrl?: string): void {\n // Determine the WebSocket URL from the API URL\n let wsUrl = 'wss://hatchway.sh/ws/runner';\n if (apiUrl) {\n const url = new URL(apiUrl);\n const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n wsUrl = `${protocol}//${url.host}/ws/runner`;\n }\n \n // Store in the server config\n configManager.set('server', {\n wsUrl,\n secret: token,\n });\n \n // Also set the apiUrl if provided\n if (apiUrl) {\n configManager.set('apiUrl', apiUrl);\n }\n}\n\n/**\n * Clear the stored token\n */\nexport function clearToken(): void {\n const config = configManager.get();\n \n // Clear server secret\n if (config.server) {\n configManager.set('server', {\n ...config.server,\n secret: '',\n });\n }\n \n // Clear legacy broker secret\n if (config.broker) {\n configManager.set('broker', {\n ...config.broker,\n secret: '',\n });\n }\n}\n\n/**\n * Validate the stored token against the server\n */\nexport async function validateToken(token: string, apiUrl: string): Promise<boolean> {\n try {\n // Try to fetch user's runner keys - this will fail if token is invalid\n const response = await fetch(`${apiUrl}/api/runner-keys`, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n \n return response.ok;\n } catch {\n return false;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ // Hatchway CLI - Built with Rollup
2
+ /**
3
+ * CLI Error handling with context and actionable suggestions
4
+ * Replaces scattered process.exit() calls throughout the codebase
5
+ */
6
+ /**
7
+ * Custom error class for CLI operations
8
+ * Provides context, suggestions, and proper exit codes
9
+ */
10
+ class CLIError extends Error {
11
+ constructor(options) {
12
+ super(options.message);
13
+ this.name = 'CLIError';
14
+ this.code = options.code;
15
+ this.context = options.context;
16
+ this.suggestions = options.suggestions || [];
17
+ this.fatal = options.fatal !== false; // Default true
18
+ this.cause = options.cause;
19
+ this.docs = options.docs;
20
+ // Maintains proper stack trace for where error was thrown
21
+ Error.captureStackTrace(this, this.constructor);
22
+ }
23
+ /**
24
+ * Get exit code based on error type
25
+ */
26
+ getExitCode() {
27
+ // Map error codes to exit codes
28
+ const exitCodes = {
29
+ 'CONFIG_INVALID': 78, // EX_CONFIG
30
+ 'CONFIG_NOT_FOUND': 78,
31
+ 'PERMISSION_DENIED': 77, // EX_NOPERM
32
+ 'INVALID_ARGUMENT': 64, // EX_USAGE
33
+ 'DB_CONNECTION_FAILED': 69, // EX_UNAVAILABLE
34
+ 'BROKER_CONNECTION_FAILED': 69,
35
+ 'PORT_IN_USE': 69,
36
+ };
37
+ return exitCodes[this.code] || 1; // Default generic error
38
+ }
39
+ }
40
+ /**
41
+ * Common error factory functions
42
+ * Use these instead of throwing raw CLIError
43
+ */
44
+ const errors = {
45
+ portInUse: (port, process) => {
46
+ return new CLIError({
47
+ code: 'PORT_IN_USE',
48
+ message: `Port ${port} is already in use`,
49
+ context: { port, process },
50
+ suggestions: [
51
+ `Stop the existing process: lsof -ti:${port} | xargs kill`,
52
+ `Or let Hatchway kill it: hatchway run --force`,
53
+ process ? `The port is being used by: ${process}` : 'Check what process is using the port: lsof -i:' + port,
54
+ ],
55
+ });
56
+ },
57
+ databaseConnectionFailed: (url, cause) => {
58
+ // Parse URL to get host (safely)
59
+ let host = 'database server';
60
+ try {
61
+ const parsed = new URL(url);
62
+ host = parsed.host;
63
+ }
64
+ catch {
65
+ // URL might be invalid
66
+ }
67
+ return new CLIError({
68
+ code: 'DB_CONNECTION_FAILED',
69
+ message: 'Could not connect to database',
70
+ context: { host, error: cause.message },
71
+ cause,
72
+ suggestions: [
73
+ 'Verify your connection string: hatchway config get databaseUrl',
74
+ 'Test the connection manually: psql <connection-string>',
75
+ 'Reset database setup: hatchway db setup --force',
76
+ ],
77
+ docs: 'https://github.com/codyde/hatchway#database-setup',
78
+ });
79
+ },
80
+ monorepoNotFound: (searchedPaths) => {
81
+ return new CLIError({
82
+ code: 'MONOREPO_NOT_FOUND',
83
+ message: 'Hatchway monorepo not found',
84
+ context: { searchedPaths },
85
+ suggestions: [
86
+ 'Run initialization: hatchway init',
87
+ 'Or specify path: hatchway run --monorepo ~/hatchway',
88
+ ],
89
+ });
90
+ },
91
+ configNotFound: () => {
92
+ return new CLIError({
93
+ code: 'CONFIG_NOT_FOUND',
94
+ message: 'Configuration not found',
95
+ suggestions: [
96
+ 'Initialize Hatchway: hatchway init',
97
+ 'This will create your configuration file',
98
+ ],
99
+ docs: 'https://github.com/codyde/hatchway#getting-started',
100
+ });
101
+ },
102
+ workspaceNotFound: (path) => {
103
+ return new CLIError({
104
+ code: 'WORKSPACE_NOT_FOUND',
105
+ message: `Workspace directory does not exist: ${path}`,
106
+ context: { path },
107
+ suggestions: [
108
+ 'Create the directory: mkdir -p ' + path,
109
+ 'Or reconfigure workspace: hatchway config set workspace <path>',
110
+ 'Or re-run init: hatchway init',
111
+ ],
112
+ });
113
+ },
114
+ serviceStartFailed: (service, cause) => {
115
+ return new CLIError({
116
+ code: 'SERVICE_START_FAILED',
117
+ message: `Failed to start ${service}`,
118
+ context: { service },
119
+ cause,
120
+ suggestions: [
121
+ 'Check if dependencies are installed: cd ~/.hatchway-monorepo && pnpm install',
122
+ 'Check if ports are available: lsof -i:3000 -i:4000',
123
+ 'Try restarting: hatchway run --force',
124
+ ],
125
+ });
126
+ },
127
+ invalidArgument: (arg, reason) => {
128
+ return new CLIError({
129
+ code: 'INVALID_ARGUMENT',
130
+ message: `Invalid argument: ${arg}`,
131
+ context: { argument: arg, reason },
132
+ suggestions: [
133
+ 'Check the command usage: hatchway --help',
134
+ ],
135
+ });
136
+ },
137
+ };
138
+
139
+ export { CLIError as C, errors as e };
140
+ //# sourceMappingURL=cli-error-1drkrXNn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-error-1drkrXNn.js","sources":["../../src/cli/utils/cli-error.ts"],"sourcesContent":["/**\n * CLI Error handling with context and actionable suggestions\n * Replaces scattered process.exit() calls throughout the codebase\n */\n\nexport type ErrorCode =\n // Initialization errors\n | 'INIT_FAILED'\n | 'MONOREPO_NOT_FOUND'\n | 'MONOREPO_CLONE_FAILED'\n | 'DEPENDENCIES_INSTALL_FAILED'\n | 'BUILD_FAILED'\n\n // Configuration errors\n | 'CONFIG_INVALID'\n | 'CONFIG_NOT_FOUND'\n | 'WORKSPACE_NOT_FOUND'\n | 'WORKSPACE_CREATE_FAILED'\n\n // Database errors\n | 'DB_CONNECTION_FAILED'\n | 'DB_MIGRATION_FAILED'\n | 'DB_URL_INVALID'\n\n // Network/Port errors\n | 'PORT_IN_USE'\n | 'BROKER_CONNECTION_FAILED'\n | 'TUNNEL_CREATION_FAILED'\n\n // Runtime errors\n | 'SERVICE_START_FAILED'\n | 'SERVICE_CRASHED'\n | 'PROCESS_KILL_FAILED'\n\n // User errors\n | 'INVALID_ARGUMENT'\n | 'MISSING_REQUIRED_CONFIG'\n | 'PERMISSION_DENIED'\n\n // Upgrade errors\n | 'UPGRADE_NOT_IN_REPO'\n | 'UPGRADE_CLONE_FAILED'\n | 'UPGRADE_INSTALL_FAILED'\n | 'UPGRADE_BUILD_FAILED'\n\n // Unknown\n | 'UNKNOWN_ERROR';\n\nexport interface CLIErrorOptions {\n code: ErrorCode;\n message: string;\n context?: Record<string, unknown>;\n suggestions?: string[];\n fatal?: boolean;\n cause?: Error;\n docs?: string; // Link to documentation\n}\n\n/**\n * Custom error class for CLI operations\n * Provides context, suggestions, and proper exit codes\n */\nexport class CLIError extends Error {\n public readonly code: ErrorCode;\n public readonly context?: Record<string, unknown>;\n public readonly suggestions: string[];\n public readonly fatal: boolean;\n public readonly cause?: Error;\n public readonly docs?: string;\n\n constructor(options: CLIErrorOptions) {\n super(options.message);\n this.name = 'CLIError';\n this.code = options.code;\n this.context = options.context;\n this.suggestions = options.suggestions || [];\n this.fatal = options.fatal !== false; // Default true\n this.cause = options.cause;\n this.docs = options.docs;\n\n // Maintains proper stack trace for where error was thrown\n Error.captureStackTrace(this, this.constructor);\n }\n\n /**\n * Get exit code based on error type\n */\n getExitCode(): number {\n // Map error codes to exit codes\n const exitCodes: Partial<Record<ErrorCode, number>> = {\n 'CONFIG_INVALID': 78, // EX_CONFIG\n 'CONFIG_NOT_FOUND': 78,\n 'PERMISSION_DENIED': 77, // EX_NOPERM\n 'INVALID_ARGUMENT': 64, // EX_USAGE\n 'DB_CONNECTION_FAILED': 69, // EX_UNAVAILABLE\n 'BROKER_CONNECTION_FAILED': 69,\n 'PORT_IN_USE': 69,\n };\n\n return exitCodes[this.code] || 1; // Default generic error\n }\n}\n\n/**\n * Common error factory functions\n * Use these instead of throwing raw CLIError\n */\nexport const errors = {\n portInUse: (port: number, process?: string): CLIError => {\n return new CLIError({\n code: 'PORT_IN_USE',\n message: `Port ${port} is already in use`,\n context: { port, process },\n suggestions: [\n `Stop the existing process: lsof -ti:${port} | xargs kill`,\n `Or let Hatchway kill it: hatchway run --force`,\n process ? `The port is being used by: ${process}` : 'Check what process is using the port: lsof -i:' + port,\n ],\n });\n },\n\n databaseConnectionFailed: (url: string, cause: Error): CLIError => {\n // Parse URL to get host (safely)\n let host = 'database server';\n try {\n const parsed = new URL(url);\n host = parsed.host;\n } catch {\n // URL might be invalid\n }\n\n return new CLIError({\n code: 'DB_CONNECTION_FAILED',\n message: 'Could not connect to database',\n context: { host, error: cause.message },\n cause,\n suggestions: [\n 'Verify your connection string: hatchway config get databaseUrl',\n 'Test the connection manually: psql <connection-string>',\n 'Reset database setup: hatchway db setup --force',\n ],\n docs: 'https://github.com/codyde/hatchway#database-setup',\n });\n },\n\n monorepoNotFound: (searchedPaths: string[]): CLIError => {\n return new CLIError({\n code: 'MONOREPO_NOT_FOUND',\n message: 'Hatchway monorepo not found',\n context: { searchedPaths },\n suggestions: [\n 'Run initialization: hatchway init',\n 'Or specify path: hatchway run --monorepo ~/hatchway',\n ],\n });\n },\n\n configNotFound: (): CLIError => {\n return new CLIError({\n code: 'CONFIG_NOT_FOUND',\n message: 'Configuration not found',\n suggestions: [\n 'Initialize Hatchway: hatchway init',\n 'This will create your configuration file',\n ],\n docs: 'https://github.com/codyde/hatchway#getting-started',\n });\n },\n\n workspaceNotFound: (path: string): CLIError => {\n return new CLIError({\n code: 'WORKSPACE_NOT_FOUND',\n message: `Workspace directory does not exist: ${path}`,\n context: { path },\n suggestions: [\n 'Create the directory: mkdir -p ' + path,\n 'Or reconfigure workspace: hatchway config set workspace <path>',\n 'Or re-run init: hatchway init',\n ],\n });\n },\n\n serviceStartFailed: (service: string, cause: Error): CLIError => {\n return new CLIError({\n code: 'SERVICE_START_FAILED',\n message: `Failed to start ${service}`,\n context: { service },\n cause,\n suggestions: [\n 'Check if dependencies are installed: cd ~/.hatchway-monorepo && pnpm install',\n 'Check if ports are available: lsof -i:3000 -i:4000',\n 'Try restarting: hatchway run --force',\n ],\n });\n },\n\n invalidArgument: (arg: string, reason: string): CLIError => {\n return new CLIError({\n code: 'INVALID_ARGUMENT',\n message: `Invalid argument: ${arg}`,\n context: { argument: arg, reason },\n suggestions: [\n 'Check the command usage: hatchway --help',\n ],\n });\n },\n};\n"],"names":[],"mappings":"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"}