@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.
- package/dist/bundler-BjholBlA.cjs +131 -0
- package/dist/bundler-BjholBlA.cjs.map +1 -0
- package/dist/bundler-DWctKN1z.mjs +130 -0
- package/dist/bundler-DWctKN1z.mjs.map +1 -0
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/dokploy-api-B7KxOQr3.cjs +3 -0
- package/dist/dokploy-api-C7F9VykY.cjs +317 -0
- package/dist/dokploy-api-C7F9VykY.cjs.map +1 -0
- package/dist/dokploy-api-CaETb2L6.mjs +305 -0
- package/dist/dokploy-api-CaETb2L6.mjs.map +1 -0
- package/dist/dokploy-api-DHvfmWbi.mjs +3 -0
- package/dist/{encryption-Dyf_r1h-.cjs → encryption-D7Efcdi9.cjs} +1 -1
- package/dist/{encryption-Dyf_r1h-.cjs.map → encryption-D7Efcdi9.cjs.map} +1 -1
- package/dist/{encryption-C8H-38Yy.mjs → encryption-h4Nb6W-M.mjs} +1 -1
- package/dist/{encryption-C8H-38Yy.mjs.map → encryption-h4Nb6W-M.mjs.map} +1 -1
- package/dist/index.cjs +1520 -1136
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1520 -1136
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-Bt_1FDpT.cjs → openapi-C89hhkZC.cjs} +3 -3
- package/dist/{openapi-Bt_1FDpT.cjs.map → openapi-C89hhkZC.cjs.map} +1 -1
- package/dist/{openapi-BfFlOBCG.mjs → openapi-CZVcfxk-.mjs} +3 -3
- package/dist/{openapi-BfFlOBCG.mjs.map → openapi-CZVcfxk-.mjs.map} +1 -1
- package/dist/{openapi-react-query-B6XTeGqS.mjs → openapi-react-query-CM2_qlW9.mjs} +1 -1
- package/dist/{openapi-react-query-B6XTeGqS.mjs.map → openapi-react-query-CM2_qlW9.mjs.map} +1 -1
- package/dist/{openapi-react-query-B-sNWHFU.cjs → openapi-react-query-iKjfLzff.cjs} +1 -1
- package/dist/{openapi-react-query-B-sNWHFU.cjs.map → openapi-react-query-iKjfLzff.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{storage-C9PU_30f.mjs → storage-BaOP55oq.mjs} +48 -2
- package/dist/storage-BaOP55oq.mjs.map +1 -0
- package/dist/{storage-BXoJvmv2.cjs → storage-Bn3K9Ccu.cjs} +59 -1
- package/dist/storage-Bn3K9Ccu.cjs.map +1 -0
- package/dist/storage-UfyTn7Zm.cjs +7 -0
- package/dist/storage-nkGIjeXt.mjs +3 -0
- package/dist/{types-BR0M2v_c.d.mts → types-BgaMXsUa.d.cts} +3 -1
- package/dist/{types-BR0M2v_c.d.mts.map → types-BgaMXsUa.d.cts.map} +1 -1
- package/dist/{types-BhkZc-vm.d.cts → types-iFk5ms7y.d.mts} +3 -1
- package/dist/{types-BhkZc-vm.d.cts.map → types-iFk5ms7y.d.mts.map} +1 -1
- package/package.json +4 -4
- package/src/auth/__tests__/credentials.spec.ts +127 -0
- package/src/auth/__tests__/index.spec.ts +69 -0
- package/src/auth/credentials.ts +33 -0
- package/src/auth/index.ts +57 -50
- package/src/build/__tests__/bundler.spec.ts +444 -0
- package/src/build/__tests__/endpoint-analyzer.spec.ts +623 -0
- package/src/build/__tests__/handler-templates.spec.ts +272 -0
- package/src/build/bundler.ts +126 -8
- package/src/build/index.ts +31 -0
- package/src/build/types.ts +6 -0
- package/src/deploy/__tests__/dokploy-api.spec.ts +698 -0
- package/src/deploy/__tests__/dokploy.spec.ts +196 -6
- package/src/deploy/__tests__/index.spec.ts +339 -0
- package/src/deploy/__tests__/init.spec.ts +147 -16
- package/src/deploy/docker.ts +32 -3
- package/src/deploy/dokploy-api.ts +581 -0
- package/src/deploy/dokploy.ts +66 -93
- package/src/deploy/index.ts +587 -32
- package/src/deploy/init.ts +192 -249
- package/src/deploy/types.ts +19 -1
- package/src/dev/__tests__/index.spec.ts +95 -0
- package/src/docker/__tests__/templates.spec.ts +144 -0
- package/src/docker/index.ts +96 -6
- package/src/docker/templates.ts +114 -27
- package/src/generators/EndpointGenerator.ts +2 -2
- package/src/index.ts +34 -13
- package/src/secrets/__tests__/storage.spec.ts +208 -0
- package/src/secrets/storage.ts +73 -0
- package/src/types.ts +2 -0
- package/dist/bundler-DRXCw_YR.mjs +0 -70
- package/dist/bundler-DRXCw_YR.mjs.map +0 -1
- package/dist/bundler-WsEvH_b2.cjs +0 -71
- package/dist/bundler-WsEvH_b2.cjs.map +0 -1
- package/dist/storage-BUYQJgz7.cjs +0 -4
- package/dist/storage-BXoJvmv2.cjs.map +0 -1
- package/dist/storage-C9PU_30f.mjs.map +0 -1
- 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
|
});
|
package/src/docker/index.ts
CHANGED
|
@@ -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:
|
|
93
|
-
turboPackage
|
|
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' :
|
|
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
|
|
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
|
|
package/src/docker/templates.ts
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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
|
|