@hatchway/cli 0.50.73 → 0.50.76

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.
@@ -0,0 +1,75 @@
1
+ // Hatchway CLI - Built with Rollup
2
+ import { spawn } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+
6
+ /**
7
+ * Sandbox sync (Sandbox execution mode).
8
+ *
9
+ * In sandbox mode the runner builds locally as usual, then — instead of starting
10
+ * a local dev server + tunnel — ships the built workspace to the backend, which
11
+ * runs it inside a Railway sandbox and exposes it via railgate. The Railway
12
+ * token stays server-side; the runner only uploads a tarball over its existing
13
+ * authenticated HTTP channel.
14
+ */
15
+ /** gzip the workspace (excluding heavy/derived dirs) and base64-encode it. */
16
+ function tarWorkspaceBase64(dir) {
17
+ return new Promise((resolve, reject) => {
18
+ const chunks = [];
19
+ const errChunks = [];
20
+ const tar = spawn('tar', [
21
+ '-czf', '-',
22
+ '--exclude=node_modules',
23
+ '--exclude=.git',
24
+ '--exclude=.next',
25
+ '--exclude=dist',
26
+ '--exclude=.turbo',
27
+ '-C', dir,
28
+ '.',
29
+ ]);
30
+ tar.stdout.on('data', (c) => chunks.push(c));
31
+ tar.stderr.on('data', (c) => errChunks.push(c));
32
+ tar.on('error', reject);
33
+ tar.on('close', (code) => {
34
+ if (code === 0)
35
+ resolve(Buffer.concat(chunks).toString('base64'));
36
+ else
37
+ reject(new Error(`tar exited ${code}: ${Buffer.concat(errChunks).toString().slice(0, 300)}`));
38
+ });
39
+ });
40
+ }
41
+ /** Pick the install command from whichever lockfile the workspace ships. */
42
+ function detectInstallCommand(dir) {
43
+ if (existsSync(join(dir, 'pnpm-lock.yaml')))
44
+ return 'pnpm install';
45
+ if (existsSync(join(dir, 'yarn.lock')))
46
+ return 'yarn install';
47
+ if (existsSync(join(dir, 'bun.lockb')))
48
+ return 'bun install';
49
+ return 'npm install';
50
+ }
51
+ async function syncToSandbox(opts) {
52
+ const tarballBase64 = await tarWorkspaceBase64(opts.dir);
53
+ const installCommand = opts.installCommand || detectInstallCommand(opts.dir);
54
+ const res = await fetch(`${opts.apiBaseUrl}/api/projects/${opts.projectId}/sandbox/sync`, {
55
+ method: 'POST',
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ Authorization: `Bearer ${opts.sharedSecret}`,
59
+ },
60
+ body: JSON.stringify({
61
+ tarballBase64,
62
+ port: opts.port,
63
+ installCommand,
64
+ runCommand: opts.runCommand,
65
+ }),
66
+ });
67
+ if (!res.ok) {
68
+ const detail = await res.text().catch(() => '');
69
+ throw new Error(`sandbox sync failed: ${res.status} ${detail.slice(0, 300)}`);
70
+ }
71
+ return (await res.json());
72
+ }
73
+
74
+ export { detectInstallCommand, syncToSandbox, tarWorkspaceBase64 };
75
+ //# sourceMappingURL=sandbox-sync-CmOAFUo_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-sync-CmOAFUo_.js","sources":["../../src/lib/sandbox-sync.ts"],"sourcesContent":["/**\n * Sandbox sync (Sandbox execution mode).\n *\n * In sandbox mode the runner builds locally as usual, then — instead of starting\n * a local dev server + tunnel — ships the built workspace to the backend, which\n * runs it inside a Railway sandbox and exposes it via railgate. The Railway\n * token stays server-side; the runner only uploads a tarball over its existing\n * authenticated HTTP channel.\n */\nimport { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** gzip the workspace (excluding heavy/derived dirs) and base64-encode it. */\nexport function tarWorkspaceBase64(dir: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n const errChunks: Buffer[] = [];\n const tar = spawn('tar', [\n '-czf', '-',\n '--exclude=node_modules',\n '--exclude=.git',\n '--exclude=.next',\n '--exclude=dist',\n '--exclude=.turbo',\n '-C', dir,\n '.',\n ]);\n tar.stdout.on('data', (c: Buffer) => chunks.push(c));\n tar.stderr.on('data', (c: Buffer) => errChunks.push(c));\n tar.on('error', reject);\n tar.on('close', (code) => {\n if (code === 0) resolve(Buffer.concat(chunks).toString('base64'));\n else reject(new Error(`tar exited ${code}: ${Buffer.concat(errChunks).toString().slice(0, 300)}`));\n });\n });\n}\n\n/** Pick the install command from whichever lockfile the workspace ships. */\nexport function detectInstallCommand(dir: string): string {\n if (existsSync(join(dir, 'pnpm-lock.yaml'))) return 'pnpm install';\n if (existsSync(join(dir, 'yarn.lock'))) return 'yarn install';\n if (existsSync(join(dir, 'bun.lockb'))) return 'bun install';\n return 'npm install';\n}\n\nexport interface SyncToSandboxOptions {\n apiBaseUrl: string;\n sharedSecret: string;\n projectId: string;\n dir: string;\n port: number;\n runCommand: string;\n installCommand?: string;\n}\n\nexport async function syncToSandbox(opts: SyncToSandboxOptions): Promise<{ previewUrl: string; sandboxId: string }> {\n const tarballBase64 = await tarWorkspaceBase64(opts.dir);\n const installCommand = opts.installCommand || detectInstallCommand(opts.dir);\n\n const res = await fetch(`${opts.apiBaseUrl}/api/projects/${opts.projectId}/sandbox/sync`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${opts.sharedSecret}`,\n },\n body: JSON.stringify({\n tarballBase64,\n port: opts.port,\n installCommand,\n runCommand: opts.runCommand,\n }),\n });\n\n if (!res.ok) {\n const detail = await res.text().catch(() => '');\n throw new Error(`sandbox sync failed: ${res.status} ${detail.slice(0, 300)}`);\n }\n return (await res.json()) as { previewUrl: string; sandboxId: string };\n}\n"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -11460,7 +11460,43 @@ async function startRunner(options = {}) {
11460
11460
  }
11461
11461
  case "start-dev-server": {
11462
11462
  try {
11463
- const { runCommand: runCmd, workingDirectory, env = {}, preferredPort, } = command.payload;
11463
+ const { runCommand: runCmd, workingDirectory, env = {}, preferredPort, executionMode = 'local', } = command.payload;
11464
+ // Sandbox mode: ship the locally-built workspace to the backend-managed
11465
+ // Railway sandbox and run it there (railgate preview), rather than
11466
+ // starting a local dev server + tunnel.
11467
+ if (executionMode === 'sandbox') {
11468
+ const sandboxPort = preferredPort ?? 3000;
11469
+ log(`📦 Sandbox mode: syncing workspace to backend sandbox (project ${command.projectId}, port ${sandboxPort})...`);
11470
+ try {
11471
+ const { syncToSandbox } = await import('./chunks/sandbox-sync-CmOAFUo_.js');
11472
+ const { previewUrl } = await syncToSandbox({
11473
+ apiBaseUrl,
11474
+ sharedSecret: runnerSharedSecret,
11475
+ projectId: command.projectId,
11476
+ dir: workingDirectory,
11477
+ port: sandboxPort,
11478
+ runCommand: runCmd,
11479
+ });
11480
+ log(`✅ Sandbox preview live at ${previewUrl}`);
11481
+ sendEvent({
11482
+ type: "tunnel-created",
11483
+ ...buildEventBase(command.projectId, command.id),
11484
+ port: sandboxPort,
11485
+ tunnelUrl: previewUrl,
11486
+ });
11487
+ }
11488
+ catch (syncErr) {
11489
+ const message = syncErr instanceof Error ? syncErr.message : String(syncErr);
11490
+ log(`❌ Sandbox sync failed: ${message}`);
11491
+ sendEvent({
11492
+ type: "error",
11493
+ ...buildEventBase(command.projectId, command.id),
11494
+ error: `Sandbox sync failed: ${message}`,
11495
+ stack: syncErr instanceof Error ? syncErr.stack : undefined,
11496
+ });
11497
+ }
11498
+ break;
11499
+ }
11464
11500
  // Port is pre-allocated in the database by the API route
11465
11501
  // We use the port from the payload (which includes env vars with PORT set)
11466
11502
  const allocatedPort = preferredPort ?? null;