@orkify/cli 1.0.0-beta.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.
Files changed (203) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +1701 -0
  3. package/bin/orkify +3 -0
  4. package/boot/systemd/orkify@.service +30 -0
  5. package/dist/agent-name.d.ts +4 -0
  6. package/dist/agent-name.js +42 -0
  7. package/dist/alerts/AlertEvaluator.d.ts +14 -0
  8. package/dist/alerts/AlertEvaluator.js +135 -0
  9. package/dist/cli/commands/autostart.d.ts +3 -0
  10. package/dist/cli/commands/autostart.js +11 -0
  11. package/dist/cli/commands/crash-test.d.ts +3 -0
  12. package/dist/cli/commands/crash-test.js +17 -0
  13. package/dist/cli/commands/daemon-reload.d.ts +3 -0
  14. package/dist/cli/commands/daemon-reload.js +72 -0
  15. package/dist/cli/commands/delete.d.ts +3 -0
  16. package/dist/cli/commands/delete.js +37 -0
  17. package/dist/cli/commands/deploy.d.ts +6 -0
  18. package/dist/cli/commands/deploy.js +266 -0
  19. package/dist/cli/commands/down.d.ts +3 -0
  20. package/dist/cli/commands/down.js +36 -0
  21. package/dist/cli/commands/flush.d.ts +3 -0
  22. package/dist/cli/commands/flush.js +28 -0
  23. package/dist/cli/commands/kill.d.ts +3 -0
  24. package/dist/cli/commands/kill.js +35 -0
  25. package/dist/cli/commands/list.d.ts +14 -0
  26. package/dist/cli/commands/list.js +361 -0
  27. package/dist/cli/commands/logs.d.ts +3 -0
  28. package/dist/cli/commands/logs.js +107 -0
  29. package/dist/cli/commands/mcp.d.ts +3 -0
  30. package/dist/cli/commands/mcp.js +151 -0
  31. package/dist/cli/commands/reload.d.ts +3 -0
  32. package/dist/cli/commands/reload.js +54 -0
  33. package/dist/cli/commands/restart.d.ts +3 -0
  34. package/dist/cli/commands/restart.js +43 -0
  35. package/dist/cli/commands/restore.d.ts +3 -0
  36. package/dist/cli/commands/restore.js +88 -0
  37. package/dist/cli/commands/run.d.ts +8 -0
  38. package/dist/cli/commands/run.js +212 -0
  39. package/dist/cli/commands/snap.d.ts +3 -0
  40. package/dist/cli/commands/snap.js +30 -0
  41. package/dist/cli/commands/up.d.ts +3 -0
  42. package/dist/cli/commands/up.js +125 -0
  43. package/dist/cli/crash-recovery.d.ts +2 -0
  44. package/dist/cli/crash-recovery.js +67 -0
  45. package/dist/cli/index.d.ts +3 -0
  46. package/dist/cli/index.js +46 -0
  47. package/dist/cli/parse.d.ts +28 -0
  48. package/dist/cli/parse.js +97 -0
  49. package/dist/cluster/ClusterWrapper.d.ts +18 -0
  50. package/dist/cluster/ClusterWrapper.js +602 -0
  51. package/dist/config/ConfigStore.d.ts +11 -0
  52. package/dist/config/ConfigStore.js +21 -0
  53. package/dist/config/schema.d.ts +103 -0
  54. package/dist/config/schema.js +49 -0
  55. package/dist/constants.d.ts +83 -0
  56. package/dist/constants.js +289 -0
  57. package/dist/cron/CronScheduler.d.ts +25 -0
  58. package/dist/cron/CronScheduler.js +149 -0
  59. package/dist/daemon/GracefulManager.d.ts +8 -0
  60. package/dist/daemon/GracefulManager.js +29 -0
  61. package/dist/daemon/ManagedProcess.d.ts +71 -0
  62. package/dist/daemon/ManagedProcess.js +1020 -0
  63. package/dist/daemon/Orchestrator.d.ts +51 -0
  64. package/dist/daemon/Orchestrator.js +416 -0
  65. package/dist/daemon/RotatingWriter.d.ts +27 -0
  66. package/dist/daemon/RotatingWriter.js +264 -0
  67. package/dist/daemon/index.d.ts +2 -0
  68. package/dist/daemon/index.js +106 -0
  69. package/dist/daemon/startDaemon.d.ts +30 -0
  70. package/dist/daemon/startDaemon.js +693 -0
  71. package/dist/deploy/CommandPoller.d.ts +13 -0
  72. package/dist/deploy/CommandPoller.js +53 -0
  73. package/dist/deploy/DeployExecutor.d.ts +33 -0
  74. package/dist/deploy/DeployExecutor.js +340 -0
  75. package/dist/deploy/config.d.ts +20 -0
  76. package/dist/deploy/config.js +161 -0
  77. package/dist/deploy/env.d.ts +2 -0
  78. package/dist/deploy/env.js +17 -0
  79. package/dist/deploy/tarball.d.ts +32 -0
  80. package/dist/deploy/tarball.js +243 -0
  81. package/dist/detect/framework.d.ts +2 -0
  82. package/dist/detect/framework.js +24 -0
  83. package/dist/ipc/DaemonClient.d.ts +31 -0
  84. package/dist/ipc/DaemonClient.js +248 -0
  85. package/dist/ipc/DaemonServer.d.ts +28 -0
  86. package/dist/ipc/DaemonServer.js +166 -0
  87. package/dist/ipc/MultiUserClient.d.ts +27 -0
  88. package/dist/ipc/MultiUserClient.js +203 -0
  89. package/dist/ipc/protocol.d.ts +7 -0
  90. package/dist/ipc/protocol.js +53 -0
  91. package/dist/ipc/restoreDaemon.d.ts +8 -0
  92. package/dist/ipc/restoreDaemon.js +19 -0
  93. package/dist/machine-id.d.ts +11 -0
  94. package/dist/machine-id.js +51 -0
  95. package/dist/mcp/auth.d.ts +118 -0
  96. package/dist/mcp/auth.js +245 -0
  97. package/dist/mcp/http.d.ts +20 -0
  98. package/dist/mcp/http.js +229 -0
  99. package/dist/mcp/index.d.ts +3 -0
  100. package/dist/mcp/index.js +8 -0
  101. package/dist/mcp/server.d.ts +37 -0
  102. package/dist/mcp/server.js +413 -0
  103. package/dist/probe/compute-fingerprint.d.ts +27 -0
  104. package/dist/probe/compute-fingerprint.js +65 -0
  105. package/dist/probe/parse-frames.d.ts +21 -0
  106. package/dist/probe/parse-frames.js +57 -0
  107. package/dist/probe/resolve-sourcemaps.d.ts +25 -0
  108. package/dist/probe/resolve-sourcemaps.js +281 -0
  109. package/dist/state/StateStore.d.ts +11 -0
  110. package/dist/state/StateStore.js +78 -0
  111. package/dist/telemetry/TelemetryReporter.d.ts +49 -0
  112. package/dist/telemetry/TelemetryReporter.js +451 -0
  113. package/dist/types/index.d.ts +373 -0
  114. package/dist/types/index.js +2 -0
  115. package/package.json +148 -0
  116. package/packages/cache/README.md +114 -0
  117. package/packages/cache/dist/CacheClient.d.ts +26 -0
  118. package/packages/cache/dist/CacheClient.d.ts.map +1 -0
  119. package/packages/cache/dist/CacheClient.js +174 -0
  120. package/packages/cache/dist/CacheClient.js.map +1 -0
  121. package/packages/cache/dist/CacheFileStore.d.ts +45 -0
  122. package/packages/cache/dist/CacheFileStore.d.ts.map +1 -0
  123. package/packages/cache/dist/CacheFileStore.js +446 -0
  124. package/packages/cache/dist/CacheFileStore.js.map +1 -0
  125. package/packages/cache/dist/CachePersistence.d.ts +9 -0
  126. package/packages/cache/dist/CachePersistence.d.ts.map +1 -0
  127. package/packages/cache/dist/CachePersistence.js +67 -0
  128. package/packages/cache/dist/CachePersistence.js.map +1 -0
  129. package/packages/cache/dist/CachePrimary.d.ts +25 -0
  130. package/packages/cache/dist/CachePrimary.d.ts.map +1 -0
  131. package/packages/cache/dist/CachePrimary.js +155 -0
  132. package/packages/cache/dist/CachePrimary.js.map +1 -0
  133. package/packages/cache/dist/CacheStore.d.ts +50 -0
  134. package/packages/cache/dist/CacheStore.d.ts.map +1 -0
  135. package/packages/cache/dist/CacheStore.js +271 -0
  136. package/packages/cache/dist/CacheStore.js.map +1 -0
  137. package/packages/cache/dist/constants.d.ts +6 -0
  138. package/packages/cache/dist/constants.d.ts.map +1 -0
  139. package/packages/cache/dist/constants.js +9 -0
  140. package/packages/cache/dist/constants.js.map +1 -0
  141. package/packages/cache/dist/index.d.ts +16 -0
  142. package/packages/cache/dist/index.d.ts.map +1 -0
  143. package/packages/cache/dist/index.js +86 -0
  144. package/packages/cache/dist/index.js.map +1 -0
  145. package/packages/cache/dist/serialize.d.ts +9 -0
  146. package/packages/cache/dist/serialize.d.ts.map +1 -0
  147. package/packages/cache/dist/serialize.js +40 -0
  148. package/packages/cache/dist/serialize.js.map +1 -0
  149. package/packages/cache/dist/types.d.ts +123 -0
  150. package/packages/cache/dist/types.d.ts.map +1 -0
  151. package/packages/cache/dist/types.js +2 -0
  152. package/packages/cache/dist/types.js.map +1 -0
  153. package/packages/cache/package.json +27 -0
  154. package/packages/cache/src/CacheClient.ts +227 -0
  155. package/packages/cache/src/CacheFileStore.ts +528 -0
  156. package/packages/cache/src/CachePersistence.ts +89 -0
  157. package/packages/cache/src/CachePrimary.ts +172 -0
  158. package/packages/cache/src/CacheStore.ts +308 -0
  159. package/packages/cache/src/constants.ts +10 -0
  160. package/packages/cache/src/index.ts +100 -0
  161. package/packages/cache/src/serialize.ts +49 -0
  162. package/packages/cache/src/types.ts +156 -0
  163. package/packages/cache/tsconfig.json +18 -0
  164. package/packages/cache/tsconfig.tsbuildinfo +1 -0
  165. package/packages/next/README.md +166 -0
  166. package/packages/next/dist/error-capture.d.ts +34 -0
  167. package/packages/next/dist/error-capture.d.ts.map +1 -0
  168. package/packages/next/dist/error-capture.js +130 -0
  169. package/packages/next/dist/error-capture.js.map +1 -0
  170. package/packages/next/dist/error-handler.d.ts +10 -0
  171. package/packages/next/dist/error-handler.d.ts.map +1 -0
  172. package/packages/next/dist/error-handler.js +186 -0
  173. package/packages/next/dist/error-handler.js.map +1 -0
  174. package/packages/next/dist/isr-cache.d.ts +9 -0
  175. package/packages/next/dist/isr-cache.d.ts.map +1 -0
  176. package/packages/next/dist/isr-cache.js +86 -0
  177. package/packages/next/dist/isr-cache.js.map +1 -0
  178. package/packages/next/dist/stream.d.ts +5 -0
  179. package/packages/next/dist/stream.d.ts.map +1 -0
  180. package/packages/next/dist/stream.js +22 -0
  181. package/packages/next/dist/stream.js.map +1 -0
  182. package/packages/next/dist/types.d.ts +33 -0
  183. package/packages/next/dist/types.d.ts.map +1 -0
  184. package/packages/next/dist/types.js +6 -0
  185. package/packages/next/dist/types.js.map +1 -0
  186. package/packages/next/dist/use-cache.d.ts +4 -0
  187. package/packages/next/dist/use-cache.d.ts.map +1 -0
  188. package/packages/next/dist/use-cache.js +86 -0
  189. package/packages/next/dist/use-cache.js.map +1 -0
  190. package/packages/next/dist/utils.d.ts +32 -0
  191. package/packages/next/dist/utils.d.ts.map +1 -0
  192. package/packages/next/dist/utils.js +88 -0
  193. package/packages/next/dist/utils.js.map +1 -0
  194. package/packages/next/package.json +52 -0
  195. package/packages/next/src/error-capture.ts +177 -0
  196. package/packages/next/src/error-handler.ts +221 -0
  197. package/packages/next/src/isr-cache.ts +100 -0
  198. package/packages/next/src/stream.ts +23 -0
  199. package/packages/next/src/types.ts +33 -0
  200. package/packages/next/src/use-cache.ts +99 -0
  201. package/packages/next/src/utils.ts +102 -0
  202. package/packages/next/tsconfig.json +19 -0
  203. package/packages/next/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,13 @@
1
+ import type { Orchestrator } from '../daemon/Orchestrator.js';
2
+ import type { TelemetryReporter } from '../telemetry/TelemetryReporter.js';
3
+ import type { TelemetryConfig } from '../types/index.js';
4
+ export declare class CommandPoller {
5
+ private config;
6
+ private orchestrator;
7
+ private telemetry;
8
+ private polling;
9
+ constructor(config: TelemetryConfig, orchestrator: Orchestrator, telemetry: TelemetryReporter);
10
+ start(): void;
11
+ fetchAndExecute(): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=CommandPoller.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { hostname } from 'node:os';
2
+ import { getAgentName } from '../agent-name.js';
3
+ import { DeployExecutor } from './DeployExecutor.js';
4
+ export class CommandPoller {
5
+ config;
6
+ orchestrator;
7
+ telemetry;
8
+ polling = false;
9
+ constructor(config, orchestrator, telemetry) {
10
+ this.config = config;
11
+ this.orchestrator = orchestrator;
12
+ this.telemetry = telemetry;
13
+ }
14
+ start() {
15
+ this.telemetry.on('commands:pending', () => {
16
+ void this.fetchAndExecute();
17
+ });
18
+ }
19
+ async fetchAndExecute() {
20
+ if (this.polling)
21
+ return;
22
+ this.polling = true;
23
+ try {
24
+ const hn = hostname();
25
+ const agentName = getAgentName();
26
+ const response = await fetch(`${this.config.apiHost}/api/v1/deploy/commands?hostname=${encodeURIComponent(hn)}&agentName=${encodeURIComponent(agentName)}`, {
27
+ headers: { Authorization: `Bearer ${this.config.apiKey}` },
28
+ signal: AbortSignal.timeout(10000),
29
+ });
30
+ if (!response.ok) {
31
+ console.error(`Command poll failed: ${response.status}`);
32
+ return;
33
+ }
34
+ const body = (await response.json());
35
+ for (const cmd of body.commands) {
36
+ if (cmd.type === 'deploy') {
37
+ const executor = new DeployExecutor(this.config, this.orchestrator, this.telemetry, cmd);
38
+ // Execute asynchronously — status reported via telemetry
39
+ executor.execute().catch((err) => {
40
+ console.error(`Deploy execution error: ${err.message}`);
41
+ });
42
+ }
43
+ }
44
+ }
45
+ catch (err) {
46
+ console.error(`Command poll error: ${err.message}`);
47
+ }
48
+ finally {
49
+ this.polling = false;
50
+ }
51
+ }
52
+ }
53
+ //# sourceMappingURL=CommandPoller.js.map
@@ -0,0 +1,33 @@
1
+ import type { Orchestrator } from '../daemon/Orchestrator.js';
2
+ import type { TelemetryReporter } from '../telemetry/TelemetryReporter.js';
3
+ import type { DeployCommand, DeployOptions, TelemetryConfig } from '../types/index.js';
4
+ export declare class DeployExecutor {
5
+ private config;
6
+ private orchestrator;
7
+ private telemetry;
8
+ private cmd;
9
+ private options;
10
+ private buildLog;
11
+ private buildLogLines;
12
+ private nextDeploymentId;
13
+ private onProgress?;
14
+ private onOutput?;
15
+ constructor(config: TelemetryConfig, orchestrator: Orchestrator, telemetry: TelemetryReporter, cmd: DeployCommand, options?: DeployOptions);
16
+ setProgressCallback(cb: (phase: string, error?: string) => void): void;
17
+ setOutputCallback(cb: (line: string) => void): void;
18
+ execute(): Promise<{
19
+ success: boolean;
20
+ error?: string;
21
+ }>;
22
+ private reportPhase;
23
+ private download;
24
+ private verifySha256;
25
+ private extract;
26
+ private fetchSecrets;
27
+ private runCommand;
28
+ private swapSymlink;
29
+ private monitorCrashWindow;
30
+ private reconcileProcesses;
31
+ private cleanupOldReleases;
32
+ }
33
+ //# sourceMappingURL=DeployExecutor.d.ts.map
@@ -0,0 +1,340 @@
1
+ import { execSync, spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { copyFileSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync, } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { Readable } from 'node:stream';
6
+ import { pipeline } from 'node:stream/promises';
7
+ import { DEPLOY_CRASH_WINDOW_DEFAULT, DEPLOY_META_FILE, ORKIFY_DEPLOYS_DIR } from '../constants.js';
8
+ import { detectFramework } from '../detect/framework.js';
9
+ import { getOrkifyConfig } from './config.js';
10
+ export class DeployExecutor {
11
+ config;
12
+ orchestrator;
13
+ telemetry;
14
+ cmd;
15
+ options;
16
+ buildLog = '';
17
+ buildLogLines = [];
18
+ nextDeploymentId;
19
+ onProgress;
20
+ onOutput;
21
+ constructor(config, orchestrator, telemetry, cmd, options) {
22
+ this.config = config;
23
+ this.orchestrator = orchestrator;
24
+ this.telemetry = telemetry;
25
+ this.cmd = cmd;
26
+ this.options = options ?? {};
27
+ }
28
+ setProgressCallback(cb) {
29
+ this.onProgress = cb;
30
+ }
31
+ setOutputCallback(cb) {
32
+ this.onOutput = cb;
33
+ }
34
+ async execute() {
35
+ const deployConfig = this.cmd.deployConfig;
36
+ const deploysDir = this.options.deploysDir ?? ORKIFY_DEPLOYS_DIR;
37
+ const releaseName = this.options.localTarball
38
+ ? `local-${this.cmd.version}`
39
+ : `${this.cmd.version}-${this.cmd.artifactId.slice(0, 8)}`;
40
+ const releasesDir = join(deploysDir, 'releases');
41
+ const releaseDir = join(releasesDir, releaseName);
42
+ const currentLink = join(deploysDir, 'current');
43
+ try {
44
+ mkdirSync(releasesDir, { recursive: true });
45
+ // 1. Download or copy local tarball
46
+ this.reportPhase('downloading');
47
+ const tarPath = join(releasesDir, `v${this.cmd.version}.tar.gz`);
48
+ if (this.options.localTarball) {
49
+ copyFileSync(this.options.localTarball, tarPath);
50
+ }
51
+ else {
52
+ await this.download(this.cmd.downloadUrl, tarPath);
53
+ await this.verifySha256(tarPath, this.cmd.sha256);
54
+ }
55
+ // 2. Extract
56
+ this.reportPhase('extracting');
57
+ mkdirSync(releaseDir, { recursive: true });
58
+ await this.extract(tarPath, releaseDir);
59
+ unlinkSync(tarPath);
60
+ // 2b. Write deploy metadata (used by restore to identify the current release)
61
+ writeFileSync(join(releaseDir, DEPLOY_META_FILE), JSON.stringify({
62
+ version: this.cmd.version,
63
+ artifactId: this.cmd.artifactId,
64
+ }), 'utf-8');
65
+ // 2c. Detect Next.js for version skew protection
66
+ if (detectFramework(releaseDir) === 'nextjs') {
67
+ this.nextDeploymentId = `v${this.cmd.version}-${this.cmd.artifactId.slice(0, 8)}`;
68
+ }
69
+ // 3. Fetch secrets
70
+ const secrets = this.options.secrets !== undefined
71
+ ? this.options.secrets
72
+ : await this.fetchSecrets(this.cmd.downloadToken);
73
+ // 4. Install + 5. Build
74
+ const buildEnv = { ...secrets, ...deployConfig.buildEnv };
75
+ // Pass NEXT_DEPLOYMENT_ID to build (unless user already set it)
76
+ if (this.nextDeploymentId && !buildEnv.NEXT_DEPLOYMENT_ID) {
77
+ buildEnv.NEXT_DEPLOYMENT_ID = this.nextDeploymentId;
78
+ }
79
+ if (!this.options.skipInstall) {
80
+ this.reportPhase('installing');
81
+ await this.runCommand(deployConfig.install, releaseDir, buildEnv);
82
+ }
83
+ if (!this.options.skipBuild && deployConfig.build) {
84
+ this.reportPhase('building');
85
+ await this.runCommand(deployConfig.build, releaseDir, buildEnv);
86
+ }
87
+ // 6. Swap symlink
88
+ let previousDir = null;
89
+ if (existsSync(currentLink)) {
90
+ try {
91
+ previousDir = readlinkSync(currentLink);
92
+ }
93
+ catch {
94
+ // Not a symlink
95
+ }
96
+ }
97
+ this.reportPhase('reloading');
98
+ this.swapSymlink(currentLink, releaseDir);
99
+ // 7. Reconcile processes
100
+ await this.reconcileProcesses(currentLink, secrets);
101
+ // 8. Monitor crash window
102
+ if (!this.options.skipMonitor) {
103
+ this.reportPhase('monitoring');
104
+ const crashWindow = deployConfig.crashWindow ?? DEPLOY_CRASH_WINDOW_DEFAULT;
105
+ const healthy = await this.monitorCrashWindow(crashWindow);
106
+ if (!healthy) {
107
+ if (previousDir && existsSync(previousDir)) {
108
+ this.swapSymlink(currentLink, previousDir);
109
+ await this.reconcileProcesses(currentLink, secrets);
110
+ this.reportPhase('rolled_back', 'Workers crashed within monitoring window');
111
+ return {
112
+ success: false,
113
+ error: 'Workers crashed within monitoring window, rolled back',
114
+ };
115
+ }
116
+ else {
117
+ const msg = 'Workers crashed and no previous version to rollback to';
118
+ this.reportPhase('failed', msg);
119
+ return { success: false, error: msg };
120
+ }
121
+ }
122
+ }
123
+ // 9. Success — health checks are now handled per-process during reconcile/reload
124
+ this.reportPhase('success');
125
+ // 10. Cleanup old releases (preserve the previous version for rollback)
126
+ this.cleanupOldReleases(releasesDir, 3, previousDir);
127
+ return { success: true };
128
+ }
129
+ catch (err) {
130
+ const message = err.message;
131
+ this.reportPhase('failed', message);
132
+ return { success: false, error: message };
133
+ }
134
+ }
135
+ reportPhase(phase, error) {
136
+ console.log(`[deploy] v${this.cmd.version}: ${phase}${error ? ` — ${error}` : ''}`);
137
+ this.onProgress?.(phase, error);
138
+ if (this.options.skipTelemetry)
139
+ return;
140
+ const status = {
141
+ deployId: this.cmd.deployId,
142
+ targetId: this.cmd.targetId,
143
+ phase,
144
+ buildLog: this.buildLog.slice(-5000),
145
+ error,
146
+ };
147
+ this.telemetry.setDeployStatus(status);
148
+ // Emit deploy lifecycle events for SSE streaming.
149
+ // Fields must be nested inside `details` so they survive Zod parsing
150
+ // on the ingest endpoint and end up in the ClickHouse details column.
151
+ if (phase === 'downloading') {
152
+ this.telemetry.emitEvent('process:deploy-started', 'deploy', {
153
+ details: { deployId: this.cmd.deployId, targetId: this.cmd.targetId },
154
+ });
155
+ }
156
+ else if (phase === 'success') {
157
+ this.telemetry.emitEvent('process:deploy-finished', 'deploy', {
158
+ details: { deployId: this.cmd.deployId, targetId: this.cmd.targetId },
159
+ });
160
+ }
161
+ else if (phase === 'failed' || phase === 'rolled_back') {
162
+ this.telemetry.emitEvent('process:deploy-failed', 'deploy', {
163
+ details: {
164
+ deployId: this.cmd.deployId,
165
+ targetId: this.cmd.targetId,
166
+ error: error ?? phase,
167
+ },
168
+ });
169
+ }
170
+ }
171
+ async download(url, destPath) {
172
+ const response = await fetch(url);
173
+ if (!response.ok || !response.body) {
174
+ throw new Error(`Download failed: ${response.status}`);
175
+ }
176
+ const fileStream = createWriteStream(destPath);
177
+ const nodeStream = Readable.fromWeb(response.body);
178
+ await pipeline(nodeStream, fileStream);
179
+ }
180
+ async verifySha256(filePath, expected) {
181
+ const hash = createHash('sha256');
182
+ const stream = createReadStream(filePath);
183
+ for await (const chunk of stream) {
184
+ hash.update(chunk);
185
+ }
186
+ const actual = hash.digest('hex');
187
+ if (actual !== expected) {
188
+ throw new Error(`SHA-256 mismatch: expected ${expected.slice(0, 12)}..., got ${actual.slice(0, 12)}...`);
189
+ }
190
+ }
191
+ extract(tarPath, destDir) {
192
+ execSync(`tar xzf "${tarPath}" -C "${destDir}"`, { stdio: 'pipe' });
193
+ }
194
+ async fetchSecrets(token) {
195
+ try {
196
+ const response = await fetch(`${this.config.apiHost}/api/v1/deploy/secrets`, {
197
+ method: 'POST',
198
+ headers: { 'Content-Type': 'application/json' },
199
+ body: JSON.stringify({ token }),
200
+ signal: AbortSignal.timeout(10000),
201
+ });
202
+ if (!response.ok) {
203
+ console.error(`Secrets fetch failed: ${response.status}`);
204
+ return {};
205
+ }
206
+ const body = (await response.json());
207
+ return body.secrets ?? {};
208
+ }
209
+ catch (err) {
210
+ console.error(`Secrets fetch error: ${err.message}`);
211
+ return {};
212
+ }
213
+ }
214
+ async runCommand(cmd, cwd, env) {
215
+ // Strip NODE_ENV during install/build so devDependencies (TypeScript,
216
+ // bundlers, etc.) are installed. Runtime processes get NODE_ENV from
217
+ // secrets/env as configured — this only affects the build phase.
218
+ const { NODE_ENV: _stripped, ...parentEnv } = process.env;
219
+ return new Promise((resolve, reject) => {
220
+ const isWin = process.platform === 'win32';
221
+ const child = spawn(isWin ? 'cmd.exe' : 'sh', isWin ? ['/c', cmd] : ['-c', cmd], {
222
+ cwd,
223
+ env: { ...parentEnv, ...env },
224
+ stdio: ['pipe', 'pipe', 'pipe'],
225
+ });
226
+ const capture = (data) => {
227
+ const text = data.toString();
228
+ this.buildLogLines.push(...text.split('\n'));
229
+ if (this.buildLogLines.length > 1000) {
230
+ this.buildLogLines.splice(0, this.buildLogLines.length - 1000);
231
+ }
232
+ this.buildLog = this.buildLogLines.join('\n');
233
+ if (this.onOutput) {
234
+ for (const line of text.split('\n')) {
235
+ if (line.trim())
236
+ this.onOutput(line);
237
+ }
238
+ }
239
+ };
240
+ child.stdout?.on('data', capture);
241
+ child.stderr?.on('data', capture);
242
+ child.on('exit', (code) => {
243
+ if (code === 0)
244
+ resolve();
245
+ else
246
+ reject(new Error(`Command "${cmd}" exited with code ${code}\n${this.buildLog}`));
247
+ });
248
+ child.on('error', reject);
249
+ });
250
+ }
251
+ swapSymlink(linkPath, targetPath) {
252
+ if (process.platform === 'win32') {
253
+ if (existsSync(linkPath))
254
+ rmSync(linkPath, { recursive: true });
255
+ execSync(`mklink /J "${linkPath}" "${targetPath}"`, { shell: 'cmd.exe' });
256
+ }
257
+ else {
258
+ const tmpLink = linkPath + '.tmp';
259
+ if (existsSync(tmpLink))
260
+ unlinkSync(tmpLink);
261
+ symlinkSync(targetPath, tmpLink);
262
+ renameSync(tmpLink, linkPath);
263
+ }
264
+ }
265
+ async monitorCrashWindow(windowSeconds) {
266
+ return new Promise((resolve) => {
267
+ let settled = false;
268
+ const onExit = (data) => {
269
+ if (!settled && data.code !== 0 && data.code !== null) {
270
+ settled = true;
271
+ clearTimeout(timer);
272
+ this.orchestrator.removeListener('worker:exit', onExit);
273
+ resolve(false);
274
+ }
275
+ };
276
+ this.orchestrator.on('worker:exit', onExit);
277
+ const timer = setTimeout(() => {
278
+ if (!settled) {
279
+ settled = true;
280
+ this.orchestrator.removeListener('worker:exit', onExit);
281
+ resolve(true);
282
+ }
283
+ }, windowSeconds * 1000);
284
+ timer.unref();
285
+ });
286
+ }
287
+ async reconcileProcesses(currentLink, secrets) {
288
+ const fileConfig = getOrkifyConfig(currentLink);
289
+ if (!fileConfig?.processes?.length) {
290
+ throw new Error('No processes defined in orkify.yml');
291
+ }
292
+ const configs = fileConfig.processes;
293
+ // Resolve script paths relative to currentLink and set cwd
294
+ const resolvedConfigs = configs.map((config) => ({
295
+ ...config,
296
+ script: join(currentLink, config.script),
297
+ cwd: currentLink,
298
+ }));
299
+ // Default NODE_ENV=production for runtime processes during deploy.
300
+ // Users can override this via secrets/env vars on the dashboard.
301
+ const runtimeEnv = { NODE_ENV: 'production', ...secrets };
302
+ // Pass NEXT_DEPLOYMENT_ID to runtime for version skew protection
303
+ if (this.nextDeploymentId && !runtimeEnv.NEXT_DEPLOYMENT_ID) {
304
+ runtimeEnv.NEXT_DEPLOYMENT_ID = this.nextDeploymentId;
305
+ }
306
+ const result = await this.orchestrator.reconcile(resolvedConfigs, runtimeEnv);
307
+ if (result.started.length > 0) {
308
+ console.log(`[deploy] Started: ${result.started.join(', ')}`);
309
+ }
310
+ if (result.reloaded.length > 0) {
311
+ console.log(`[deploy] Reloaded: ${result.reloaded.join(', ')}`);
312
+ }
313
+ if (result.deleted.length > 0) {
314
+ console.log(`[deploy] Deleted: ${result.deleted.join(', ')}`);
315
+ }
316
+ }
317
+ cleanupOldReleases(releasesDir, keep, preserveDir) {
318
+ try {
319
+ const entries = readdirSync(releasesDir)
320
+ .filter((e) => statSync(join(releasesDir, e)).isDirectory())
321
+ .sort((a, b) => {
322
+ // Sort by directory mtime descending (newest first)
323
+ const mtimeA = statSync(join(releasesDir, a)).mtimeMs;
324
+ const mtimeB = statSync(join(releasesDir, b)).mtimeMs;
325
+ return mtimeB - mtimeA;
326
+ });
327
+ for (const entry of entries.slice(keep)) {
328
+ const dir = join(releasesDir, entry);
329
+ // Never delete the previous release — it's the rollback target
330
+ if (preserveDir && dir === preserveDir)
331
+ continue;
332
+ rmSync(dir, { recursive: true, force: true });
333
+ }
334
+ }
335
+ catch {
336
+ // Ignore cleanup errors
337
+ }
338
+ }
339
+ }
340
+ //# sourceMappingURL=DeployExecutor.js.map
@@ -0,0 +1,20 @@
1
+ import type { SavedState } from '../types/index.js';
2
+ interface PackageManager {
3
+ name: string;
4
+ install: string;
5
+ }
6
+ export declare function detectPackageManager(projectDir: string): PackageManager;
7
+ export declare function detectBuildCommand(packageJson: Record<string, unknown>, pm: PackageManager): null | string;
8
+ export declare function detectEntryPoint(packageJson: Record<string, unknown>, projectDir: string): string;
9
+ export declare function readPackageJson(projectDir: string): Record<string, unknown>;
10
+ export declare function getOrkifyConfig(projectDir: string): null | SavedState;
11
+ export declare function saveOrkifyConfig(projectDir: string, config: SavedState): void;
12
+ export declare function interactiveConfig(projectDir: string): Promise<SavedState>;
13
+ export declare function collectGitMetadata(projectDir: string): {
14
+ gitSha?: string;
15
+ gitBranch?: string;
16
+ gitAuthor?: string;
17
+ gitMessage?: string;
18
+ };
19
+ export {};
20
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,161 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { createInterface } from 'node:readline';
5
+ import { parse, stringify } from 'yaml';
6
+ import { DEFAULT_LOG_MAX_AGE, DEFAULT_LOG_MAX_FILES, DEFAULT_LOG_MAX_SIZE, ORKIFY_CONFIG_FILE, } from '../constants.js';
7
+ export function detectPackageManager(projectDir) {
8
+ if (existsSync(join(projectDir, 'pnpm-lock.yaml')))
9
+ return { name: 'pnpm', install: 'pnpm install --frozen-lockfile' };
10
+ if (existsSync(join(projectDir, 'yarn.lock')))
11
+ return { name: 'yarn', install: 'yarn install --frozen-lockfile' };
12
+ if (existsSync(join(projectDir, 'bun.lock')))
13
+ return { name: 'bun', install: 'bun install --frozen-lockfile' };
14
+ return { name: 'npm', install: 'npm ci' };
15
+ }
16
+ export function detectBuildCommand(packageJson, pm) {
17
+ const scripts = packageJson.scripts;
18
+ if (scripts?.build)
19
+ return `${pm.name} run build`;
20
+ return null;
21
+ }
22
+ export function detectEntryPoint(packageJson, projectDir) {
23
+ if (packageJson.main && typeof packageJson.main === 'string')
24
+ return packageJson.main;
25
+ const candidates = [
26
+ 'server.mjs',
27
+ 'server.js',
28
+ 'app.mjs',
29
+ 'app.js',
30
+ 'index.mjs',
31
+ 'index.js',
32
+ 'src/server.mjs',
33
+ 'src/server.js',
34
+ 'src/index.mjs',
35
+ 'src/index.js',
36
+ 'dist/server.js',
37
+ 'dist/index.js',
38
+ 'build/index.js',
39
+ ];
40
+ for (const candidate of candidates) {
41
+ if (existsSync(join(projectDir, candidate)))
42
+ return candidate;
43
+ }
44
+ return 'server.js';
45
+ }
46
+ export function readPackageJson(projectDir) {
47
+ const pkgPath = join(projectDir, 'package.json');
48
+ if (!existsSync(pkgPath)) {
49
+ throw new Error(`No package.json found in ${projectDir}`);
50
+ }
51
+ return JSON.parse(readFileSync(pkgPath, 'utf-8'));
52
+ }
53
+ export function getOrkifyConfig(projectDir) {
54
+ const configPath = join(projectDir, ORKIFY_CONFIG_FILE);
55
+ if (!existsSync(configPath))
56
+ return null;
57
+ try {
58
+ const content = readFileSync(configPath, 'utf-8');
59
+ const raw = parse(content);
60
+ if (!raw)
61
+ return null;
62
+ return raw;
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ export function saveOrkifyConfig(projectDir, config) {
69
+ const configPath = join(projectDir, ORKIFY_CONFIG_FILE);
70
+ writeFileSync(configPath, stringify(config, { defaultStringType: 'QUOTE_DOUBLE' }), 'utf-8');
71
+ }
72
+ async function prompt(question, prefill) {
73
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
74
+ return new Promise((resolve) => {
75
+ rl.question(`${question}: `, (answer) => {
76
+ rl.close();
77
+ resolve(answer.trim());
78
+ });
79
+ rl.write(prefill);
80
+ });
81
+ }
82
+ export async function interactiveConfig(projectDir) {
83
+ const hasPkg = existsSync(join(projectDir, 'package.json'));
84
+ const pkg = hasPkg ? readPackageJson(projectDir) : {};
85
+ const pm = detectPackageManager(projectDir);
86
+ const buildCmd = detectBuildCommand(pkg, pm);
87
+ const entry = detectEntryPoint(pkg, projectDir);
88
+ console.log('\nConfiguring deployment for this project:\n');
89
+ const install = await prompt('Install command (empty to skip)', hasPkg ? pm.install : '');
90
+ const build = await prompt('Build command (empty to skip)', hasPkg ? buildCmd || `${pm.name} run build` : '');
91
+ const entryPoint = await prompt('Entry point', entry);
92
+ const workersStr = await prompt('Workers (0 = max CPU cores, 1 = fork mode)', '0');
93
+ const parsed = parseInt(workersStr, 10);
94
+ const workers = Number.isNaN(parsed) ? 0 : parsed;
95
+ const deploy = { install };
96
+ if (build) {
97
+ deploy.build = build;
98
+ }
99
+ const process = {
100
+ name: 'app',
101
+ script: entryPoint,
102
+ cwd: projectDir,
103
+ workerCount: workers,
104
+ execMode: workers === 0 || workers > 1 ? 'cluster' : 'fork',
105
+ watch: false,
106
+ env: {},
107
+ nodeArgs: [],
108
+ args: [],
109
+ killTimeout: 5000,
110
+ maxRestarts: 10,
111
+ minUptime: 1000,
112
+ restartDelay: 100,
113
+ sticky: false,
114
+ logMaxSize: DEFAULT_LOG_MAX_SIZE,
115
+ logMaxFiles: DEFAULT_LOG_MAX_FILES,
116
+ logMaxAge: DEFAULT_LOG_MAX_AGE,
117
+ };
118
+ return {
119
+ version: 1,
120
+ deploy,
121
+ processes: [process],
122
+ };
123
+ }
124
+ export function collectGitMetadata(projectDir) {
125
+ const meta = {};
126
+ try {
127
+ meta.gitSha = execSync('git rev-parse HEAD', { cwd: projectDir, encoding: 'utf-8' }).trim();
128
+ }
129
+ catch {
130
+ // Not a git repo
131
+ }
132
+ try {
133
+ meta.gitBranch = execSync('git branch --show-current', {
134
+ cwd: projectDir,
135
+ encoding: 'utf-8',
136
+ }).trim();
137
+ }
138
+ catch {
139
+ // git not available
140
+ }
141
+ try {
142
+ meta.gitAuthor = execSync('git log -1 --format=%an', {
143
+ cwd: projectDir,
144
+ encoding: 'utf-8',
145
+ }).trim();
146
+ }
147
+ catch {
148
+ // git not available
149
+ }
150
+ try {
151
+ meta.gitMessage = execSync('git log -1 --format=%s', {
152
+ cwd: projectDir,
153
+ encoding: 'utf-8',
154
+ }).trim();
155
+ }
156
+ catch {
157
+ // git not available
158
+ }
159
+ return meta;
160
+ }
161
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,2 @@
1
+ export declare function parseEnvFile(content: string): Record<string, string>;
2
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1,17 @@
1
+ export function parseEnvFile(content) {
2
+ const env = {};
3
+ for (const line of content.split('\n')) {
4
+ const trimmed = line.trim();
5
+ if (!trimmed || trimmed.startsWith('#'))
6
+ continue;
7
+ const eqIndex = trimmed.indexOf('=');
8
+ if (eqIndex === -1)
9
+ continue;
10
+ const key = trimmed.slice(0, eqIndex).trim();
11
+ const value = trimmed.slice(eqIndex + 1).trim();
12
+ if (key)
13
+ env[key] = value;
14
+ }
15
+ return env;
16
+ }
17
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1,32 @@
1
+ interface FileDep {
2
+ dir: string;
3
+ files: string[];
4
+ }
5
+ /**
6
+ * Scan package.json for `file:` dependencies. For each one, walk the
7
+ * referenced directory and collect its files. Returns a rewritten
8
+ * package.json string with paths pointing to `.file-deps/<name>/` and
9
+ * a map of the collected files — or null if there are no file deps.
10
+ */
11
+ export declare function bundleFileDeps(projectDir: string): null | {
12
+ rewrittenPkg: string;
13
+ rewrittenLock: null | string;
14
+ fileDeps: Map<string, FileDep>;
15
+ };
16
+ /**
17
+ * Create a tar.gz archive of the project directory.
18
+ *
19
+ * Walks the directory tree, respecting .gitignore files from the git root
20
+ * all the way down into the project, plus a built-in exclude list
21
+ * (node_modules, .git, .env, etc.).
22
+ *
23
+ * Any `file:` dependencies in package.json are bundled into the tarball
24
+ * under `.file-deps/<name>/` and the paths are rewritten so `npm ci`
25
+ * can resolve them on the deploy target.
26
+ */
27
+ export interface TarballOptions {
28
+ excludeSourceMaps?: boolean;
29
+ }
30
+ export declare function createTarball(projectDir: string, options?: TarballOptions): Promise<string>;
31
+ export {};
32
+ //# sourceMappingURL=tarball.d.ts.map