@geekmidas/cli 0.12.0 → 0.14.0

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 (82) hide show
  1. package/dist/bundler-BjholBlA.cjs +131 -0
  2. package/dist/bundler-BjholBlA.cjs.map +1 -0
  3. package/dist/bundler-DWctKN1z.mjs +130 -0
  4. package/dist/bundler-DWctKN1z.mjs.map +1 -0
  5. package/dist/config.d.cts +1 -1
  6. package/dist/config.d.mts +1 -1
  7. package/dist/dokploy-api-B7KxOQr3.cjs +3 -0
  8. package/dist/dokploy-api-C7F9VykY.cjs +317 -0
  9. package/dist/dokploy-api-C7F9VykY.cjs.map +1 -0
  10. package/dist/dokploy-api-CaETb2L6.mjs +305 -0
  11. package/dist/dokploy-api-CaETb2L6.mjs.map +1 -0
  12. package/dist/dokploy-api-DHvfmWbi.mjs +3 -0
  13. package/dist/{encryption-Dyf_r1h-.cjs → encryption-D7Efcdi9.cjs} +1 -1
  14. package/dist/{encryption-Dyf_r1h-.cjs.map → encryption-D7Efcdi9.cjs.map} +1 -1
  15. package/dist/{encryption-C8H-38Yy.mjs → encryption-h4Nb6W-M.mjs} +1 -1
  16. package/dist/{encryption-C8H-38Yy.mjs.map → encryption-h4Nb6W-M.mjs.map} +1 -1
  17. package/dist/index.cjs +1520 -1136
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.mjs +1520 -1136
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/{openapi-Bt_1FDpT.cjs → openapi-C89hhkZC.cjs} +3 -3
  22. package/dist/{openapi-Bt_1FDpT.cjs.map → openapi-C89hhkZC.cjs.map} +1 -1
  23. package/dist/{openapi-BfFlOBCG.mjs → openapi-CZVcfxk-.mjs} +3 -3
  24. package/dist/{openapi-BfFlOBCG.mjs.map → openapi-CZVcfxk-.mjs.map} +1 -1
  25. package/dist/{openapi-react-query-B6XTeGqS.mjs → openapi-react-query-CM2_qlW9.mjs} +1 -1
  26. package/dist/{openapi-react-query-B6XTeGqS.mjs.map → openapi-react-query-CM2_qlW9.mjs.map} +1 -1
  27. package/dist/{openapi-react-query-B-sNWHFU.cjs → openapi-react-query-iKjfLzff.cjs} +1 -1
  28. package/dist/{openapi-react-query-B-sNWHFU.cjs.map → openapi-react-query-iKjfLzff.cjs.map} +1 -1
  29. package/dist/openapi-react-query.cjs +1 -1
  30. package/dist/openapi-react-query.mjs +1 -1
  31. package/dist/openapi.cjs +1 -1
  32. package/dist/openapi.d.cts +1 -1
  33. package/dist/openapi.d.mts +1 -1
  34. package/dist/openapi.mjs +1 -1
  35. package/dist/{storage-C9PU_30f.mjs → storage-BaOP55oq.mjs} +48 -2
  36. package/dist/storage-BaOP55oq.mjs.map +1 -0
  37. package/dist/{storage-BXoJvmv2.cjs → storage-Bn3K9Ccu.cjs} +59 -1
  38. package/dist/storage-Bn3K9Ccu.cjs.map +1 -0
  39. package/dist/storage-UfyTn7Zm.cjs +7 -0
  40. package/dist/storage-nkGIjeXt.mjs +3 -0
  41. package/dist/{types-BR0M2v_c.d.mts → types-BgaMXsUa.d.cts} +3 -1
  42. package/dist/{types-BR0M2v_c.d.mts.map → types-BgaMXsUa.d.cts.map} +1 -1
  43. package/dist/{types-BhkZc-vm.d.cts → types-iFk5ms7y.d.mts} +3 -1
  44. package/dist/{types-BhkZc-vm.d.cts.map → types-iFk5ms7y.d.mts.map} +1 -1
  45. package/package.json +4 -4
  46. package/src/auth/__tests__/credentials.spec.ts +127 -0
  47. package/src/auth/__tests__/index.spec.ts +69 -0
  48. package/src/auth/credentials.ts +33 -0
  49. package/src/auth/index.ts +57 -50
  50. package/src/build/__tests__/bundler.spec.ts +444 -0
  51. package/src/build/__tests__/endpoint-analyzer.spec.ts +623 -0
  52. package/src/build/__tests__/handler-templates.spec.ts +272 -0
  53. package/src/build/bundler.ts +126 -8
  54. package/src/build/index.ts +31 -0
  55. package/src/build/types.ts +6 -0
  56. package/src/deploy/__tests__/dokploy-api.spec.ts +698 -0
  57. package/src/deploy/__tests__/dokploy.spec.ts +196 -6
  58. package/src/deploy/__tests__/index.spec.ts +339 -0
  59. package/src/deploy/__tests__/init.spec.ts +147 -16
  60. package/src/deploy/docker.ts +32 -3
  61. package/src/deploy/dokploy-api.ts +581 -0
  62. package/src/deploy/dokploy.ts +66 -93
  63. package/src/deploy/index.ts +587 -32
  64. package/src/deploy/init.ts +192 -249
  65. package/src/deploy/types.ts +19 -1
  66. package/src/dev/__tests__/index.spec.ts +95 -0
  67. package/src/docker/__tests__/templates.spec.ts +144 -0
  68. package/src/docker/index.ts +96 -6
  69. package/src/docker/templates.ts +114 -27
  70. package/src/generators/EndpointGenerator.ts +2 -2
  71. package/src/index.ts +34 -13
  72. package/src/secrets/__tests__/storage.spec.ts +208 -0
  73. package/src/secrets/storage.ts +73 -0
  74. package/src/types.ts +2 -0
  75. package/dist/bundler-DRXCw_YR.mjs +0 -70
  76. package/dist/bundler-DRXCw_YR.mjs.map +0 -1
  77. package/dist/bundler-WsEvH_b2.cjs +0 -71
  78. package/dist/bundler-WsEvH_b2.cjs.map +0 -1
  79. package/dist/storage-BUYQJgz7.cjs +0 -4
  80. package/dist/storage-BXoJvmv2.cjs.map +0 -1
  81. package/dist/storage-C9PU_30f.mjs.map +0 -1
  82. package/dist/storage-DLJAYxzJ.mjs +0 -3
@@ -1,12 +1,17 @@
1
1
  import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
2
3
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
4
  import type { GkmConfig } from '../../types';
4
5
  import {
5
6
  detectPackageManager,
7
+ findLockfilePath,
6
8
  generateDockerEntrypoint,
7
9
  generateDockerignore,
8
10
  generateMultiStageDockerfile,
9
11
  generateSlimDockerfile,
12
+ getLockfileName,
13
+ hasTurboConfig,
14
+ isMonorepo,
10
15
  resolveDockerConfig,
11
16
  } from '../templates';
12
17
 
@@ -277,4 +282,143 @@ describe('docker templates', () => {
277
282
  expect(result.port).toBe(3000); // default
278
283
  });
279
284
  });
285
+
286
+ describe('findLockfilePath', () => {
287
+ it('should find pnpm-lock.yaml in current directory', () => {
288
+ mockExistsSync.mockImplementation((path) => {
289
+ return path === join('/test/project', 'pnpm-lock.yaml');
290
+ });
291
+
292
+ expect(findLockfilePath('/test/project')).toBe(
293
+ join('/test/project', 'pnpm-lock.yaml'),
294
+ );
295
+ });
296
+
297
+ it('should find lockfile in parent directory (monorepo)', () => {
298
+ mockExistsSync.mockImplementation((path) => {
299
+ // Lockfile only exists at monorepo root
300
+ return path === join('/test', 'pnpm-lock.yaml');
301
+ });
302
+
303
+ expect(findLockfilePath('/test/project/apps/api')).toBe(
304
+ join('/test', 'pnpm-lock.yaml'),
305
+ );
306
+ });
307
+
308
+ it('should find yarn.lock when present', () => {
309
+ mockExistsSync.mockImplementation((path) => {
310
+ return path === join('/test/project', 'yarn.lock');
311
+ });
312
+
313
+ expect(findLockfilePath('/test/project')).toBe(
314
+ join('/test/project', 'yarn.lock'),
315
+ );
316
+ });
317
+
318
+ it('should find package-lock.json when present', () => {
319
+ mockExistsSync.mockImplementation((path) => {
320
+ return path === join('/test/project', 'package-lock.json');
321
+ });
322
+
323
+ expect(findLockfilePath('/test/project')).toBe(
324
+ join('/test/project', 'package-lock.json'),
325
+ );
326
+ });
327
+
328
+ it('should find bun.lockb when present', () => {
329
+ mockExistsSync.mockImplementation((path) => {
330
+ return path === join('/test/project', 'bun.lockb');
331
+ });
332
+
333
+ expect(findLockfilePath('/test/project')).toBe(
334
+ join('/test/project', 'bun.lockb'),
335
+ );
336
+ });
337
+
338
+ it('should return null when no lockfile found', () => {
339
+ mockExistsSync.mockReturnValue(false);
340
+
341
+ expect(findLockfilePath('/test/project')).toBeNull();
342
+ });
343
+
344
+ it('should prioritize lockfiles in order: pnpm, bun, yarn, npm', () => {
345
+ // If multiple lockfiles exist, pnpm should be found first
346
+ mockExistsSync.mockImplementation((path) => {
347
+ const pathStr = String(path);
348
+ return (
349
+ pathStr.endsWith('pnpm-lock.yaml') ||
350
+ pathStr.endsWith('package-lock.json')
351
+ );
352
+ });
353
+
354
+ const result = findLockfilePath('/test/project');
355
+ expect(result).toContain('pnpm-lock.yaml');
356
+ });
357
+ });
358
+
359
+ describe('getLockfileName', () => {
360
+ it('should return pnpm-lock.yaml for pnpm', () => {
361
+ expect(getLockfileName('pnpm')).toBe('pnpm-lock.yaml');
362
+ });
363
+
364
+ it('should return package-lock.json for npm', () => {
365
+ expect(getLockfileName('npm')).toBe('package-lock.json');
366
+ });
367
+
368
+ it('should return yarn.lock for yarn', () => {
369
+ expect(getLockfileName('yarn')).toBe('yarn.lock');
370
+ });
371
+
372
+ it('should return bun.lockb for bun', () => {
373
+ expect(getLockfileName('bun')).toBe('bun.lockb');
374
+ });
375
+ });
376
+
377
+ describe('isMonorepo', () => {
378
+ it('should return false when lockfile is in current directory', () => {
379
+ mockExistsSync.mockImplementation((path) => {
380
+ return path === join('/test/project', 'pnpm-lock.yaml');
381
+ });
382
+
383
+ expect(isMonorepo('/test/project')).toBe(false);
384
+ });
385
+
386
+ it('should return true when lockfile is in parent directory', () => {
387
+ mockExistsSync.mockImplementation((path) => {
388
+ return path === join('/test', 'pnpm-lock.yaml');
389
+ });
390
+
391
+ expect(isMonorepo('/test/project/apps/api')).toBe(true);
392
+ });
393
+
394
+ it('should return false when no lockfile found', () => {
395
+ mockExistsSync.mockReturnValue(false);
396
+
397
+ expect(isMonorepo('/test/project')).toBe(false);
398
+ });
399
+ });
400
+
401
+ describe('hasTurboConfig', () => {
402
+ it('should return true when turbo.json exists in current directory', () => {
403
+ mockExistsSync.mockImplementation((path) => {
404
+ return path === join('/test/project', 'turbo.json');
405
+ });
406
+
407
+ expect(hasTurboConfig('/test/project')).toBe(true);
408
+ });
409
+
410
+ it('should return true when turbo.json exists in parent directory', () => {
411
+ mockExistsSync.mockImplementation((path) => {
412
+ return path === join('/test', 'turbo.json');
413
+ });
414
+
415
+ expect(hasTurboConfig('/test/project/apps/api')).toBe(true);
416
+ });
417
+
418
+ it('should return false when turbo.json not found', () => {
419
+ mockExistsSync.mockReturnValue(false);
420
+
421
+ expect(hasTurboConfig('/test/project')).toBe(false);
422
+ });
423
+ });
280
424
  });
@@ -1,18 +1,28 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
2
+ import { copyFileSync, existsSync, unlinkSync } from 'node:fs';
3
3
  import { mkdir, writeFile } from 'node:fs/promises';
4
- import { join } from 'node:path';
4
+ import { basename, join } from 'node:path';
5
5
  import { loadConfig } from '../config';
6
6
  import { generateDockerCompose, generateMinimalDockerCompose } from './compose';
7
7
  import {
8
8
  detectPackageManager,
9
+ findLockfilePath,
9
10
  generateDockerEntrypoint,
10
11
  generateDockerignore,
11
12
  generateMultiStageDockerfile,
12
13
  generateSlimDockerfile,
14
+ hasTurboConfig,
15
+ isMonorepo,
13
16
  resolveDockerConfig,
14
17
  } from './templates';
15
18
 
19
+ export {
20
+ detectPackageManager,
21
+ findLockfilePath,
22
+ hasTurboConfig,
23
+ isMonorepo,
24
+ } from './templates';
25
+
16
26
  const logger = console;
17
27
 
18
28
  export interface DockerOptions {
@@ -82,6 +92,42 @@ export async function dockerCommand(
82
92
 
83
93
  // Detect package manager from lockfiles
84
94
  const packageManager = detectPackageManager();
95
+ const inMonorepo = isMonorepo();
96
+ const hasTurbo = hasTurboConfig();
97
+
98
+ // Auto-enable turbo for monorepos with turbo.json
99
+ let useTurbo = options.turbo ?? false;
100
+ if (inMonorepo && !useSlim) {
101
+ if (hasTurbo) {
102
+ useTurbo = true;
103
+ logger.log(' Detected monorepo with turbo.json - using turbo prune');
104
+ } else {
105
+ throw new Error(
106
+ 'Monorepo detected but turbo.json not found.\n\n' +
107
+ 'Docker builds in monorepos require Turborepo for proper dependency isolation.\n\n' +
108
+ 'To fix this:\n' +
109
+ ' 1. Install turbo: pnpm add -Dw turbo\n' +
110
+ ' 2. Create turbo.json in your monorepo root\n' +
111
+ ' 3. Run this command again\n\n' +
112
+ 'See: https://turbo.build/repo/docs/guides/tools/docker',
113
+ );
114
+ }
115
+ }
116
+
117
+ // Get the actual package name from package.json for turbo prune
118
+ let turboPackage = options.turboPackage ?? dockerConfig.imageName;
119
+ if (useTurbo && !options.turboPackage) {
120
+ try {
121
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
122
+ const pkg = require(`${process.cwd()}/package.json`);
123
+ if (pkg.name) {
124
+ turboPackage = pkg.name;
125
+ logger.log(` Turbo package: ${turboPackage}`);
126
+ }
127
+ } catch {
128
+ // Fall back to imageName
129
+ }
130
+ }
85
131
 
86
132
  const templateOptions = {
87
133
  imageName: dockerConfig.imageName,
@@ -89,8 +135,8 @@ export async function dockerCommand(
89
135
  port: dockerConfig.port,
90
136
  healthCheckPath,
91
137
  prebuilt: useSlim,
92
- turbo: options.turbo,
93
- turboPackage: options.turboPackage ?? dockerConfig.imageName,
138
+ turbo: useTurbo,
139
+ turboPackage,
94
140
  packageManager,
95
141
  };
96
142
 
@@ -99,7 +145,7 @@ export async function dockerCommand(
99
145
  ? generateSlimDockerfile(templateOptions)
100
146
  : generateMultiStageDockerfile(templateOptions);
101
147
 
102
- const dockerMode = useSlim ? 'slim' : options.turbo ? 'turbo' : 'multi-stage';
148
+ const dockerMode = useSlim ? 'slim' : useTurbo ? 'turbo' : 'multi-stage';
103
149
 
104
150
  const dockerfilePath = join(dockerDir, 'Dockerfile');
105
151
  await writeFile(dockerfilePath, dockerfile);
@@ -161,6 +207,42 @@ export async function dockerCommand(
161
207
  return result;
162
208
  }
163
209
 
210
+ /**
211
+ * Ensure lockfile exists in the build context
212
+ * For monorepos, copies from workspace root if needed
213
+ * Returns cleanup function if file was copied
214
+ */
215
+ function ensureLockfile(cwd: string): (() => void) | null {
216
+ const lockfilePath = findLockfilePath(cwd);
217
+
218
+ if (!lockfilePath) {
219
+ logger.warn(
220
+ '\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.',
221
+ );
222
+ return null;
223
+ }
224
+
225
+ const lockfileName = basename(lockfilePath);
226
+ const localLockfile = join(cwd, lockfileName);
227
+
228
+ // If lockfile exists locally (same directory), nothing to do
229
+ if (lockfilePath === localLockfile) {
230
+ return null;
231
+ }
232
+
233
+ logger.log(` Copying ${lockfileName} from monorepo root...`);
234
+ copyFileSync(lockfilePath, localLockfile);
235
+
236
+ // Return cleanup function
237
+ return () => {
238
+ try {
239
+ unlinkSync(localLockfile);
240
+ } catch {
241
+ // Ignore cleanup errors
242
+ }
243
+ };
244
+ }
245
+
164
246
  /**
165
247
  * Build Docker image
166
248
  * Uses BuildKit for cache mount support
@@ -178,12 +260,17 @@ async function buildDockerImage(
178
260
 
179
261
  logger.log(`\n🐳 Building Docker image: ${fullImageName}`);
180
262
 
263
+ const cwd = process.cwd();
264
+
265
+ // Ensure lockfile exists (copy from monorepo root if needed)
266
+ const cleanup = ensureLockfile(cwd);
267
+
181
268
  try {
182
269
  // Use BuildKit for cache mount support (required for --mount=type=cache)
183
270
  execSync(
184
271
  `DOCKER_BUILDKIT=1 docker build -f .gkm/docker/Dockerfile -t ${fullImageName} .`,
185
272
  {
186
- cwd: process.cwd(),
273
+ cwd,
187
274
  stdio: 'inherit',
188
275
  env: { ...process.env, DOCKER_BUILDKIT: '1' },
189
276
  },
@@ -193,6 +280,9 @@ async function buildDockerImage(
193
280
  throw new Error(
194
281
  `Failed to build Docker image: ${error instanceof Error ? error.message : 'Unknown error'}`,
195
282
  );
283
+ } finally {
284
+ // Clean up copied lockfile
285
+ cleanup?.();
196
286
  }
197
287
  }
198
288
 
@@ -22,6 +22,13 @@ export interface MultiStageDockerfileOptions extends DockerTemplateOptions {
22
22
  turboPackage?: string;
23
23
  }
24
24
 
25
+ const LOCKFILES: [string, PackageManager][] = [
26
+ ['pnpm-lock.yaml', 'pnpm'],
27
+ ['bun.lockb', 'bun'],
28
+ ['yarn.lock', 'yarn'],
29
+ ['package-lock.json', 'npm'],
30
+ ];
31
+
25
32
  /**
26
33
  * Detect package manager from lockfiles
27
34
  * Walks up the directory tree to find lockfile (for monorepos)
@@ -29,19 +36,12 @@ export interface MultiStageDockerfileOptions extends DockerTemplateOptions {
29
36
  export function detectPackageManager(
30
37
  cwd: string = process.cwd(),
31
38
  ): PackageManager {
32
- const lockfiles: [string, PackageManager][] = [
33
- ['pnpm-lock.yaml', 'pnpm'],
34
- ['bun.lockb', 'bun'],
35
- ['yarn.lock', 'yarn'],
36
- ['package-lock.json', 'npm'],
37
- ];
38
-
39
39
  let dir = cwd;
40
40
  const root = parse(dir).root;
41
41
 
42
42
  // Walk up the directory tree
43
43
  while (dir !== root) {
44
- for (const [lockfile, pm] of lockfiles) {
44
+ for (const [lockfile, pm] of LOCKFILES) {
45
45
  if (existsSync(join(dir, lockfile))) {
46
46
  return pm;
47
47
  }
@@ -50,7 +50,7 @@ export function detectPackageManager(
50
50
  }
51
51
 
52
52
  // Check root directory
53
- for (const [lockfile, pm] of lockfiles) {
53
+ for (const [lockfile, pm] of LOCKFILES) {
54
54
  if (existsSync(join(root, lockfile))) {
55
55
  return pm;
56
56
  }
@@ -59,6 +59,94 @@ export function detectPackageManager(
59
59
  return 'pnpm'; // default
60
60
  }
61
61
 
62
+ /**
63
+ * Find the lockfile path by walking up the directory tree
64
+ * Returns the full path to the lockfile, or null if not found
65
+ */
66
+ export function findLockfilePath(cwd: string = process.cwd()): string | null {
67
+ let dir = cwd;
68
+ const root = parse(dir).root;
69
+
70
+ // Walk up the directory tree
71
+ while (dir !== root) {
72
+ for (const [lockfile] of LOCKFILES) {
73
+ const lockfilePath = join(dir, lockfile);
74
+ if (existsSync(lockfilePath)) {
75
+ return lockfilePath;
76
+ }
77
+ }
78
+ dir = dirname(dir);
79
+ }
80
+
81
+ // Check root directory
82
+ for (const [lockfile] of LOCKFILES) {
83
+ const lockfilePath = join(root, lockfile);
84
+ if (existsSync(lockfilePath)) {
85
+ return lockfilePath;
86
+ }
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ /**
93
+ * Get the lockfile name for a package manager
94
+ */
95
+ export function getLockfileName(pm: PackageManager): string {
96
+ const lockfileMap: Record<PackageManager, string> = {
97
+ pnpm: 'pnpm-lock.yaml',
98
+ npm: 'package-lock.json',
99
+ yarn: 'yarn.lock',
100
+ bun: 'bun.lockb',
101
+ };
102
+ return lockfileMap[pm];
103
+ }
104
+
105
+ /**
106
+ * Check if we're in a monorepo (lockfile is in a parent directory)
107
+ */
108
+ export function isMonorepo(cwd: string = process.cwd()): boolean {
109
+ const lockfilePath = findLockfilePath(cwd);
110
+ if (!lockfilePath) {
111
+ return false;
112
+ }
113
+
114
+ // Check if lockfile is in a parent directory (not in cwd)
115
+ const lockfileDir = dirname(lockfilePath);
116
+ return lockfileDir !== cwd;
117
+ }
118
+
119
+ /**
120
+ * Check if turbo.json exists (walks up directory tree)
121
+ */
122
+ export function hasTurboConfig(cwd: string = process.cwd()): boolean {
123
+ let dir = cwd;
124
+ const root = parse(dir).root;
125
+
126
+ while (dir !== root) {
127
+ if (existsSync(join(dir, 'turbo.json'))) {
128
+ return true;
129
+ }
130
+ dir = dirname(dir);
131
+ }
132
+
133
+ return existsSync(join(root, 'turbo.json'));
134
+ }
135
+
136
+ /**
137
+ * Get install command for turbo builds (without frozen lockfile)
138
+ * Turbo prune creates a subset that may not perfectly match the lockfile
139
+ */
140
+ function getTurboInstallCmd(pm: PackageManager): string {
141
+ const commands: Record<PackageManager, string> = {
142
+ pnpm: 'pnpm install',
143
+ npm: 'npm install',
144
+ yarn: 'yarn install',
145
+ bun: 'bun install',
146
+ };
147
+ return commands[pm];
148
+ }
149
+
62
150
  /**
63
151
  * Get package manager specific commands and paths
64
152
  */
@@ -72,6 +160,7 @@ function getPmConfig(pm: PackageManager) {
72
160
  cacheTarget: '/root/.local/share/pnpm/store',
73
161
  cacheId: 'pnpm',
74
162
  run: 'pnpm',
163
+ dlx: 'pnpm dlx',
75
164
  addGlobal: 'pnpm add -g',
76
165
  },
77
166
  npm: {
@@ -82,6 +171,7 @@ function getPmConfig(pm: PackageManager) {
82
171
  cacheTarget: '/root/.npm',
83
172
  cacheId: 'npm',
84
173
  run: 'npm run',
174
+ dlx: 'npx',
85
175
  addGlobal: 'npm install -g',
86
176
  },
87
177
  yarn: {
@@ -92,6 +182,7 @@ function getPmConfig(pm: PackageManager) {
92
182
  cacheTarget: '/root/.yarn/cache',
93
183
  cacheId: 'yarn',
94
184
  run: 'yarn',
185
+ dlx: 'yarn dlx',
95
186
  addGlobal: 'yarn global add',
96
187
  },
97
188
  bun: {
@@ -102,6 +193,7 @@ function getPmConfig(pm: PackageManager) {
102
193
  cacheTarget: '/root/.bun/install/cache',
103
194
  cacheId: 'bun',
104
195
  run: 'bun run',
196
+ dlx: 'bunx',
105
197
  addGlobal: 'bun add -g',
106
198
  },
107
199
  };
@@ -178,8 +270,8 @@ WORKDIR /app
178
270
  # Copy source (deps already installed)
179
271
  COPY . .
180
272
 
181
- # Build production server
182
- RUN ${pm.run} gkm build --provider server --production
273
+ # Build production server using CLI from npm
274
+ RUN ${pm.dlx} @geekmidas/cli build --provider server --production
183
275
 
184
276
  # Stage 3: Production
185
277
  FROM ${baseImage} AS runner
@@ -225,19 +317,13 @@ function generateTurboDockerfile(options: MultiStageDockerfileOptions): string {
225
317
 
226
318
  const pm = getPmConfig(packageManager);
227
319
  const installPm = pm.install ? `RUN ${pm.install}` : '';
228
- const hasFetch = packageManager === 'pnpm';
229
320
 
230
- // pnpm has fetch which allows better caching
231
- const depsInstall = hasFetch
232
- ? `# Fetch and install from cache
233
- RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
234
- ${pm.fetch}
321
+ // For turbo builds, we can't use --frozen-lockfile because turbo prune
322
+ // creates a subset that may not perfectly match. Use relaxed install.
323
+ const turboInstallCmd = getTurboInstallCmd(packageManager);
235
324
 
236
- RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
237
- ${pm.installCmd}`
238
- : `# Install dependencies with cache
239
- RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
240
- ${pm.installCmd}`;
325
+ // Use pnpm dlx for pnpm (avoids global bin dir issues in Docker)
326
+ const turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';
241
327
 
242
328
  return `# syntax=docker/dockerfile:1
243
329
  # Stage 1: Prune monorepo
@@ -246,12 +332,11 @@ FROM ${baseImage} AS pruner
246
332
  WORKDIR /app
247
333
 
248
334
  ${installPm}
249
- RUN ${pm.addGlobal} turbo
250
335
 
251
336
  COPY . .
252
337
 
253
338
  # Prune to only include necessary packages
254
- RUN turbo prune ${turboPackage} --docker
339
+ RUN ${turboCmd} prune ${turboPackage} --docker
255
340
 
256
341
  # Stage 2: Install dependencies
257
342
  FROM ${baseImage} AS deps
@@ -264,7 +349,9 @@ ${installPm}
264
349
  COPY --from=pruner /app/out/${pm.lockfile} ./
265
350
  COPY --from=pruner /app/out/json/ ./
266
351
 
267
- ${depsInstall}
352
+ # Install dependencies (no frozen-lockfile since turbo prune creates a subset)
353
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
354
+ ${turboInstallCmd}
268
355
 
269
356
  # Stage 3: Build
270
357
  FROM deps AS builder
@@ -274,8 +361,8 @@ WORKDIR /app
274
361
  # Copy pruned source
275
362
  COPY --from=pruner /app/out/full/ ./
276
363
 
277
- # Build production server
278
- RUN ${pm.run} gkm build --provider server --production
364
+ # Build production server using CLI from npm
365
+ RUN ${pm.dlx} @geekmidas/cli build --provider server --production
279
366
 
280
367
  # Stage 4: Production
281
368
  FROM ${baseImage} AS runner
@@ -925,11 +925,11 @@ import { createApp } from './app.js';
925
925
 
926
926
  const port = Number(process.env.PORT) || 3000;
927
927
 
928
- const { app, start } = await createApp();
928
+ const { start } = await createApp();
929
929
 
930
930
  await start({
931
931
  port,
932
- serve: (app, port) => serve({ fetch: app.fetch, port }),
932
+ serve: (app, port) => { serve({ fetch: app.fetch, port }); },
933
933
  });
934
934
  `;
935
935