@geekmidas/cli 1.10.36 → 1.10.37
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/CHANGELOG.md +10 -0
- package/dist/{config-U-gojtxn.mjs → config-Bbu1JKvf.mjs} +2 -2
- package/dist/{config-U-gojtxn.mjs.map → config-Bbu1JKvf.mjs.map} +1 -1
- package/dist/{config-QnuOcYXp.cjs → config-DujV8DbR.cjs} +2 -2
- package/dist/{config-QnuOcYXp.cjs.map → config-DujV8DbR.cjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +2 -2
- package/dist/{fullstack-secrets-Bchl2MDd.cjs → fullstack-secrets-BYzi6Hwl.cjs} +3 -2
- package/dist/{fullstack-secrets-Bchl2MDd.cjs.map → fullstack-secrets-BYzi6Hwl.cjs.map} +1 -1
- package/dist/{fullstack-secrets-P84v8zWn.mjs → fullstack-secrets-l8s3II_H.mjs} +3 -2
- package/dist/{fullstack-secrets-P84v8zWn.mjs.map → fullstack-secrets-l8s3II_H.mjs.map} +1 -1
- package/dist/{index-D7iT4dnv.d.mts → index-BBvGMG_A.d.mts} +124 -67
- package/dist/index-BBvGMG_A.d.mts.map +1 -0
- package/dist/{index-DRQq26DF.d.cts → index-Dz2a7xQU.d.cts} +124 -67
- package/dist/index-Dz2a7xQU.d.cts.map +1 -0
- package/dist/index.cjs +244 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +244 -91
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-Bb4UEyZN.cjs → openapi-0JUzU5Tv.cjs} +9 -2
- package/dist/{openapi-Bb4UEyZN.cjs.map → openapi-0JUzU5Tv.cjs.map} +1 -1
- package/dist/{openapi-BA0e3I_s.mjs → openapi-C5AycKTR.mjs} +10 -3
- package/dist/{openapi-BA0e3I_s.mjs.map → openapi-C5AycKTR.mjs.map} +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.mjs +3 -3
- package/dist/{reconcile-DAAKa0YR.mjs → reconcile-CYGmqXvq.mjs} +2 -2
- package/dist/{reconcile-DAAKa0YR.mjs.map → reconcile-CYGmqXvq.mjs.map} +1 -1
- package/dist/{reconcile-CZtzdrnc.cjs → reconcile-D93YG7G5.cjs} +2 -2
- package/dist/{reconcile-CZtzdrnc.cjs.map → reconcile-D93YG7G5.cjs.map} +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +1 -1
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-BobDOIG9.cjs → workspace-CQTT7HJQ.cjs} +34 -25
- package/dist/workspace-CQTT7HJQ.cjs.map +1 -0
- package/dist/{workspace-Cgmvgwh8.mjs → workspace-D-37Ylqo.mjs} +34 -25
- package/dist/workspace-D-37Ylqo.mjs.map +1 -0
- package/docs/dev-server-resilience-design.md +189 -0
- package/package.json +5 -5
- package/src/build/__tests__/workspace-build.spec.ts +7 -7
- package/src/build/index.ts +25 -9
- package/src/credentials/__tests__/helpers.ts +1 -1
- package/src/credentials/__tests__/workspaceCredentials.spec.ts +1 -1
- package/src/deploy/__tests__/domain.spec.ts +12 -12
- package/src/deploy/__tests__/env-resolver.spec.ts +1 -1
- package/src/deploy/__tests__/index.spec.ts +14 -14
- package/src/deploy/__tests__/sniffer.spec.ts +9 -9
- package/src/deploy/domain.ts +19 -11
- package/src/deploy/index.ts +12 -1
- package/src/deploy/sniffer.ts +1 -1
- package/src/deploy/types.ts +1 -1
- package/src/dev/__tests__/index.spec.ts +11 -11
- package/src/dev/index.ts +14 -8
- package/src/docker/__tests__/compose.spec.ts +4 -4
- package/src/docker/compose.ts +5 -3
- package/src/docker/index.ts +34 -6
- package/src/docker/templates.ts +138 -0
- package/src/generators/OpenApiTsGenerator.ts +22 -0
- package/src/generators/__tests__/OpenApiTsGenerator.registry.spec.ts +77 -0
- package/src/init/__tests__/init.spec.ts +1 -1
- package/src/init/generators/monorepo.ts +3 -3
- package/src/secrets/__tests__/reconcile.spec.ts +1 -1
- package/src/setup/__tests__/reconcile-secrets.spec.ts +2 -2
- package/src/setup/fullstack-secrets.ts +7 -1
- package/src/setup/index.ts +2 -1
- package/src/test/__tests__/__fixtures__/workspace.ts +1 -1
- package/src/test/__tests__/web.spec.ts +2 -2
- package/src/workspace/__tests__/client-generator.spec.ts +6 -6
- package/src/workspace/__tests__/index.spec.ts +4 -4
- package/src/workspace/__tests__/schema.spec.ts +27 -30
- package/src/workspace/__tests__/type-inference.spec.ts +5 -5
- package/src/workspace/client-generator.ts +10 -7
- package/src/workspace/index.ts +1 -1
- package/src/workspace/publicEnv.ts +2 -4
- package/src/workspace/schema.ts +126 -27
- package/src/workspace/types.ts +40 -69
- package/dist/index-D7iT4dnv.d.mts.map +0 -1
- package/dist/index-DRQq26DF.d.cts.map +0 -1
- package/dist/workspace-BobDOIG9.cjs.map +0 -1
- package/dist/workspace-Cgmvgwh8.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
|
-
const require_workspace = require('./workspace-
|
|
4
|
-
const require_config = require('./config-
|
|
3
|
+
const require_workspace = require('./workspace-CQTT7HJQ.cjs');
|
|
4
|
+
const require_config = require('./config-DujV8DbR.cjs');
|
|
5
5
|
const require_credentials = require('./credentials-C8DWtnMY.cjs');
|
|
6
|
-
const require_fullstack_secrets = require('./fullstack-secrets-
|
|
6
|
+
const require_fullstack_secrets = require('./fullstack-secrets-BYzi6Hwl.cjs');
|
|
7
7
|
const require_storage = require('./storage-B1wvztiJ.cjs');
|
|
8
|
-
const require_openapi = require('./openapi-
|
|
8
|
+
const require_openapi = require('./openapi-0JUzU5Tv.cjs');
|
|
9
9
|
const require_dokploy_api = require('./dokploy-api-Dvyq-LsM.cjs');
|
|
10
10
|
const require_encryption = require('./encryption-DgKS-Dv9.cjs');
|
|
11
11
|
const require_CachedStateProvider = require('./CachedStateProvider-CxsXOKkg.cjs');
|
|
@@ -35,7 +35,7 @@ const prompts = require_chunk.__toESM(require("prompts"));
|
|
|
35
35
|
|
|
36
36
|
//#region package.json
|
|
37
37
|
var name = "@geekmidas/cli";
|
|
38
|
-
var version = "1.10.
|
|
38
|
+
var version = "1.10.36";
|
|
39
39
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
40
40
|
var private$1 = false;
|
|
41
41
|
var type = "module";
|
|
@@ -1595,7 +1595,7 @@ async function validateFrontendApp(appName, appPath, workspaceRoot, framework) {
|
|
|
1595
1595
|
*/
|
|
1596
1596
|
async function validateFrontendApps(workspace) {
|
|
1597
1597
|
const results = [];
|
|
1598
|
-
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "
|
|
1598
|
+
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "web") {
|
|
1599
1599
|
const result = await validateFrontendApp(appName, app.path, workspace.root, app.framework);
|
|
1600
1600
|
results.push(result);
|
|
1601
1601
|
}
|
|
@@ -1631,9 +1631,12 @@ async function loadDevSecrets(workspace) {
|
|
|
1631
1631
|
async function workspaceDevCommand(workspace, options) {
|
|
1632
1632
|
const appCount = Object.keys(workspace.apps).length;
|
|
1633
1633
|
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
1634
|
-
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "
|
|
1634
|
+
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
1635
|
+
const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
|
|
1635
1636
|
logger$11.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
1636
|
-
|
|
1637
|
+
const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
|
|
1638
|
+
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
1639
|
+
logger$11.log(` ${counts.join(", ")} app(s)`);
|
|
1637
1640
|
const conflicts = checkPortConflicts(workspace);
|
|
1638
1641
|
if (conflicts.length > 0) {
|
|
1639
1642
|
for (const conflict of conflicts) logger$11.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
@@ -1682,7 +1685,8 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1682
1685
|
const app = workspace.apps[appName];
|
|
1683
1686
|
if (!app) continue;
|
|
1684
1687
|
const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
|
|
1685
|
-
|
|
1688
|
+
const icon = app.type === "backend" ? "🔧" : app.type === "mobile" ? "📱" : "🌐";
|
|
1689
|
+
logger$11.log(` ${icon} ${appName} → http://localhost:${app.port}${deps}`);
|
|
1686
1690
|
}
|
|
1687
1691
|
const configFiles = [
|
|
1688
1692
|
"gkm.config.ts",
|
|
@@ -2399,7 +2403,7 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2399
2403
|
const results = [];
|
|
2400
2404
|
const apps = Object.entries(workspace.apps);
|
|
2401
2405
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2402
|
-
const frontendApps = apps.filter(([, app]) => app.type === "
|
|
2406
|
+
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
2403
2407
|
logger$9.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
2404
2408
|
logger$9.log(` Backend apps: ${backendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2405
2409
|
logger$9.log(` Frontend apps: ${frontendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
@@ -2442,7 +2446,7 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2442
2446
|
await require_openapi.openapiCommand({ cwd: workspace.root });
|
|
2443
2447
|
logger$9.log(`\n📋 Build Summary:`);
|
|
2444
2448
|
for (const result of results) {
|
|
2445
|
-
const icon = result.type === "backend" ? "⚙️" : "🌐";
|
|
2449
|
+
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
2446
2450
|
logger$9.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2447
2451
|
}
|
|
2448
2452
|
} catch (error) {
|
|
@@ -2463,8 +2467,14 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2463
2467
|
*/
|
|
2464
2468
|
function getAppOutputPath(workspace, _appName, app) {
|
|
2465
2469
|
const appPath = (0, node_path.join)(workspace.root, app.path);
|
|
2466
|
-
if (app.type === "
|
|
2467
|
-
|
|
2470
|
+
if (app.type === "mobile") return "";
|
|
2471
|
+
if (app.type === "web") switch (app.framework) {
|
|
2472
|
+
case "vite":
|
|
2473
|
+
case "tanstack-start": return (0, node_path.join)(appPath, "dist");
|
|
2474
|
+
case "remix": return (0, node_path.join)(appPath, "build");
|
|
2475
|
+
default: return (0, node_path.join)(appPath, ".next");
|
|
2476
|
+
}
|
|
2477
|
+
return (0, node_path.join)(appPath, ".gkm");
|
|
2468
2478
|
}
|
|
2469
2479
|
/**
|
|
2470
2480
|
* Format routes for logging, handling PartitionedRoutes.
|
|
@@ -3026,6 +3036,66 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3026
3036
|
return results;
|
|
3027
3037
|
}
|
|
3028
3038
|
|
|
3039
|
+
//#endregion
|
|
3040
|
+
//#region src/deploy/domain.ts
|
|
3041
|
+
/**
|
|
3042
|
+
* Resolve the hostname for an app based on stage configuration.
|
|
3043
|
+
*
|
|
3044
|
+
* Domain resolution priority:
|
|
3045
|
+
* 1. Explicit app.domain override (string or stage-specific)
|
|
3046
|
+
* 2. Default pattern based on app type:
|
|
3047
|
+
* - Main frontend app gets base domain (e.g., 'myapp.com')
|
|
3048
|
+
* - Other apps get prefixed domain (e.g., 'api.myapp.com')
|
|
3049
|
+
*
|
|
3050
|
+
* @param appName - The name of the app
|
|
3051
|
+
* @param app - The normalized app configuration
|
|
3052
|
+
* @param stage - The deployment stage (e.g., 'production', 'development')
|
|
3053
|
+
* @param dokployConfig - Dokploy workspace configuration with domain mappings
|
|
3054
|
+
* @param isMainFrontend - Whether this is the main frontend app
|
|
3055
|
+
* @returns The resolved hostname for the app
|
|
3056
|
+
* @throws Error if no domain configuration is found for the stage
|
|
3057
|
+
*/
|
|
3058
|
+
function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
|
|
3059
|
+
if (app.domain) {
|
|
3060
|
+
if (typeof app.domain === "string") return app.domain;
|
|
3061
|
+
if (app.domain[stage]) return app.domain[stage];
|
|
3062
|
+
}
|
|
3063
|
+
const baseDomain = dokployConfig?.domains?.[stage];
|
|
3064
|
+
if (!baseDomain) throw new Error(`No domain configured for stage "${stage}". Add deploy.dokploy.domains.${stage} to gkm.config.ts`);
|
|
3065
|
+
if (isMainFrontend) return baseDomain;
|
|
3066
|
+
return `${appName}.${baseDomain}`;
|
|
3067
|
+
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Determine if an app is the "main" frontend (gets base domain).
|
|
3070
|
+
*
|
|
3071
|
+
* An app is considered the main frontend if:
|
|
3072
|
+
* 1. It's named 'web' and is a frontend type
|
|
3073
|
+
* 2. It's the first frontend app in the apps list
|
|
3074
|
+
*
|
|
3075
|
+
* @param appName - The name of the app to check
|
|
3076
|
+
* @param app - The app configuration
|
|
3077
|
+
* @param allApps - All apps in the workspace
|
|
3078
|
+
* @returns True if this is the main frontend app
|
|
3079
|
+
*/
|
|
3080
|
+
function isMainFrontendApp(appName, app, allApps) {
|
|
3081
|
+
if (app.type !== "web") return false;
|
|
3082
|
+
if (appName === "web") return true;
|
|
3083
|
+
for (const [name$1, a] of Object.entries(allApps)) if (a.type === "web") return name$1 === appName;
|
|
3084
|
+
return false;
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Get public URL arg names from app dependencies.
|
|
3088
|
+
*
|
|
3089
|
+
* @param app - The web/mobile app configuration
|
|
3090
|
+
* @returns Array of arg names like 'NEXT_PUBLIC_API_URL', or [] for frameworks
|
|
3091
|
+
* without a public prefix (e.g. Remix).
|
|
3092
|
+
*/
|
|
3093
|
+
function getPublicUrlArgNames(app) {
|
|
3094
|
+
const prefix = require_workspace.getPublicEnvPrefix(app);
|
|
3095
|
+
if (!prefix) return [];
|
|
3096
|
+
return app.dependencies.map((dep) => `${prefix}${dep.toUpperCase()}_URL`);
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3029
3099
|
//#endregion
|
|
3030
3100
|
//#region src/docker/compose.ts
|
|
3031
3101
|
/** Default Docker images for services */
|
|
@@ -3337,15 +3407,18 @@ function generateWorkspaceCompose(workspace, options = {}) {
|
|
|
3337
3407
|
|
|
3338
3408
|
services:
|
|
3339
3409
|
`;
|
|
3340
|
-
for (const [appName, app] of apps)
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3410
|
+
for (const [appName, app] of apps) {
|
|
3411
|
+
if (app.type === "mobile") continue;
|
|
3412
|
+
yaml$1 += generateAppService(appName, app, apps, {
|
|
3413
|
+
registry,
|
|
3414
|
+
projectName: workspace.name,
|
|
3415
|
+
hasPostgres,
|
|
3416
|
+
hasRedis,
|
|
3417
|
+
hasMinio,
|
|
3418
|
+
hasMail,
|
|
3419
|
+
eventsBackend
|
|
3420
|
+
});
|
|
3421
|
+
}
|
|
3349
3422
|
if (hasPostgres) yaml$1 += `
|
|
3350
3423
|
postgres:
|
|
3351
3424
|
image: ${postgresImage}
|
|
@@ -3514,8 +3587,8 @@ function getInfraServiceImage(serviceName, config) {
|
|
|
3514
3587
|
function generateAppService(appName, app, allApps, options) {
|
|
3515
3588
|
const { registry, projectName, hasPostgres, hasRedis, hasMinio, hasMail, eventsBackend } = options;
|
|
3516
3589
|
const imageRef = registry ? `\${REGISTRY:-${registry}}/` : "";
|
|
3517
|
-
const healthCheckPath = app.type === "
|
|
3518
|
-
const healthCheckCmd = app.type === "
|
|
3590
|
+
const healthCheckPath = app.type === "web" ? "/" : "/health";
|
|
3591
|
+
const healthCheckCmd = app.type === "web" ? `["CMD", "wget", "-q", "--spider", "http://localhost:${app.port}/"]` : `["CMD", "wget", "-q", "--spider", "http://localhost:${app.port}${healthCheckPath}"]`;
|
|
3519
3592
|
let yaml$1 = `
|
|
3520
3593
|
${appName}:
|
|
3521
3594
|
build:
|
|
@@ -4318,6 +4391,115 @@ ENTRYPOINT ["/sbin/tini", "--"]
|
|
|
4318
4391
|
CMD ["node", "index.mjs"]
|
|
4319
4392
|
`;
|
|
4320
4393
|
}
|
|
4394
|
+
/**
|
|
4395
|
+
* Generate a Dockerfile for Node-based SSR web frameworks (TanStack Start,
|
|
4396
|
+
* Remix). Builds with turbo, then runs the framework's production start
|
|
4397
|
+
* script via `npm start`.
|
|
4398
|
+
*
|
|
4399
|
+
* Public URL build args are declared so framework bundlers (Vite, Vinxi)
|
|
4400
|
+
* can inline them at build time. Per-framework prefix is the caller's job.
|
|
4401
|
+
* @internal Exported for testing
|
|
4402
|
+
*/
|
|
4403
|
+
function generateNodeWebDockerfile(options) {
|
|
4404
|
+
const { baseImage, port, appPath, turboPackage, packageManager, publicUrlArgs = [] } = options;
|
|
4405
|
+
const pm = getPmConfig(packageManager);
|
|
4406
|
+
const installPm = pm.install ? `RUN ${pm.install}` : "";
|
|
4407
|
+
const turboInstallCmd = getTurboInstallCmd(packageManager);
|
|
4408
|
+
const turboCmd = packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo";
|
|
4409
|
+
const startCmd = packageManager === "pnpm" ? "pnpm start" : `${pm.lockfile.startsWith("package-lock") ? "npm" : packageManager} start`;
|
|
4410
|
+
const argDecls = publicUrlArgs.map((a) => `ARG ${a}=""`).join("\n");
|
|
4411
|
+
const envDecls = publicUrlArgs.map((a) => `ENV ${a}=$${a}`).join("\n");
|
|
4412
|
+
return `# syntax=docker/dockerfile:1
|
|
4413
|
+
# Node SSR web Dockerfile (TanStack Start / Remix) with turbo prune
|
|
4414
|
+
|
|
4415
|
+
FROM ${baseImage} AS pruner
|
|
4416
|
+
WORKDIR /app
|
|
4417
|
+
${installPm}
|
|
4418
|
+
COPY . .
|
|
4419
|
+
RUN ${turboCmd} prune ${turboPackage} --docker
|
|
4420
|
+
|
|
4421
|
+
FROM ${baseImage} AS deps
|
|
4422
|
+
WORKDIR /app
|
|
4423
|
+
${installPm}
|
|
4424
|
+
COPY --from=pruner /app/out/${pm.lockfile} ./
|
|
4425
|
+
COPY --from=pruner /app/out/json/ ./
|
|
4426
|
+
RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
|
|
4427
|
+
${turboInstallCmd}
|
|
4428
|
+
|
|
4429
|
+
FROM deps AS builder
|
|
4430
|
+
WORKDIR /app
|
|
4431
|
+
${argDecls}
|
|
4432
|
+
${envDecls}
|
|
4433
|
+
COPY --from=pruner /app/out/full/ ./
|
|
4434
|
+
COPY --from=pruner /app/tsconfig.* ./
|
|
4435
|
+
RUN ${turboCmd} run build --filter=${turboPackage}
|
|
4436
|
+
|
|
4437
|
+
FROM ${baseImage} AS runner
|
|
4438
|
+
WORKDIR /app
|
|
4439
|
+
RUN apk add --no-cache tini
|
|
4440
|
+
RUN addgroup --system --gid 1001 nodejs && \\
|
|
4441
|
+
adduser --system --uid 1001 app
|
|
4442
|
+
|
|
4443
|
+
# Copy the whole built workspace — frameworks differ on output paths
|
|
4444
|
+
COPY --from=builder --chown=app:nodejs /app/ ./
|
|
4445
|
+
|
|
4446
|
+
ENV NODE_ENV=production
|
|
4447
|
+
ENV PORT=${port}
|
|
4448
|
+
ENV HOSTNAME="0.0.0.0"
|
|
4449
|
+
|
|
4450
|
+
USER app
|
|
4451
|
+
EXPOSE ${port}
|
|
4452
|
+
|
|
4453
|
+
ENTRYPOINT ["/sbin/tini", "--"]
|
|
4454
|
+
CMD ["sh", "-c", "cd ${appPath} && ${startCmd}"]
|
|
4455
|
+
`;
|
|
4456
|
+
}
|
|
4457
|
+
/**
|
|
4458
|
+
* Generate a Dockerfile for Vite SPA apps. Builds the static bundle with
|
|
4459
|
+
* turbo, then serves it via nginx.
|
|
4460
|
+
* @internal Exported for testing
|
|
4461
|
+
*/
|
|
4462
|
+
function generateViteStaticDockerfile(options) {
|
|
4463
|
+
const { baseImage, port, appPath, turboPackage, packageManager, publicUrlArgs = [] } = options;
|
|
4464
|
+
const pm = getPmConfig(packageManager);
|
|
4465
|
+
const installPm = pm.install ? `RUN ${pm.install}` : "";
|
|
4466
|
+
const turboInstallCmd = getTurboInstallCmd(packageManager);
|
|
4467
|
+
const turboCmd = packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo";
|
|
4468
|
+
const argDecls = publicUrlArgs.map((a) => `ARG ${a}=""`).join("\n");
|
|
4469
|
+
const envDecls = publicUrlArgs.map((a) => `ENV ${a}=$${a}`).join("\n");
|
|
4470
|
+
return `# syntax=docker/dockerfile:1
|
|
4471
|
+
# Vite SPA Dockerfile — builds static assets, serves via nginx
|
|
4472
|
+
|
|
4473
|
+
FROM ${baseImage} AS pruner
|
|
4474
|
+
WORKDIR /app
|
|
4475
|
+
${installPm}
|
|
4476
|
+
COPY . .
|
|
4477
|
+
RUN ${turboCmd} prune ${turboPackage} --docker
|
|
4478
|
+
|
|
4479
|
+
FROM ${baseImage} AS deps
|
|
4480
|
+
WORKDIR /app
|
|
4481
|
+
${installPm}
|
|
4482
|
+
COPY --from=pruner /app/out/${pm.lockfile} ./
|
|
4483
|
+
COPY --from=pruner /app/out/json/ ./
|
|
4484
|
+
RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
|
|
4485
|
+
${turboInstallCmd}
|
|
4486
|
+
|
|
4487
|
+
FROM deps AS builder
|
|
4488
|
+
WORKDIR /app
|
|
4489
|
+
${argDecls}
|
|
4490
|
+
${envDecls}
|
|
4491
|
+
COPY --from=pruner /app/out/full/ ./
|
|
4492
|
+
COPY --from=pruner /app/tsconfig.* ./
|
|
4493
|
+
RUN ${turboCmd} run build --filter=${turboPackage}
|
|
4494
|
+
|
|
4495
|
+
FROM nginx:alpine AS runner
|
|
4496
|
+
COPY --from=builder /app/${appPath}/dist /usr/share/nginx/html
|
|
4497
|
+
RUN printf 'server {\\n listen ${port};\\n root /usr/share/nginx/html;\\n index index.html;\\n location / { try_files $uri $uri/ /index.html; }\\n}\\n' \\
|
|
4498
|
+
> /etc/nginx/conf.d/default.conf
|
|
4499
|
+
EXPOSE ${port}
|
|
4500
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
4501
|
+
`;
|
|
4502
|
+
}
|
|
4321
4503
|
|
|
4322
4504
|
//#endregion
|
|
4323
4505
|
//#region src/docker/index.ts
|
|
@@ -4503,6 +4685,10 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4503
4685
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
4504
4686
|
logger$7.log(` Package manager: ${packageManager}`);
|
|
4505
4687
|
for (const [appName, app] of apps) {
|
|
4688
|
+
if (app.type === "mobile") {
|
|
4689
|
+
logger$7.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
4690
|
+
continue;
|
|
4691
|
+
}
|
|
4506
4692
|
const appPath = app.path;
|
|
4507
4693
|
const fullAppPath = (0, node_path.join)(workspace.root, appPath);
|
|
4508
4694
|
const turboPackage = getAppPackageName(fullAppPath) ?? appName;
|
|
@@ -4511,15 +4697,28 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4511
4697
|
const buildType = hasEntry ? "entry" : app.type;
|
|
4512
4698
|
logger$7.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
4513
4699
|
let dockerfile;
|
|
4514
|
-
if (app.type === "
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4700
|
+
if (app.type === "web") {
|
|
4701
|
+
const publicUrlArgs = getPublicUrlArgNames(app);
|
|
4702
|
+
const webOpts = {
|
|
4703
|
+
imageName,
|
|
4704
|
+
baseImage: "node:22-alpine",
|
|
4705
|
+
port: app.port,
|
|
4706
|
+
appPath,
|
|
4707
|
+
turboPackage,
|
|
4708
|
+
packageManager,
|
|
4709
|
+
publicUrlArgs
|
|
4710
|
+
};
|
|
4711
|
+
switch (app.framework) {
|
|
4712
|
+
case "vite":
|
|
4713
|
+
dockerfile = generateViteStaticDockerfile(webOpts);
|
|
4714
|
+
break;
|
|
4715
|
+
case "tanstack-start":
|
|
4716
|
+
case "remix":
|
|
4717
|
+
dockerfile = generateNodeWebDockerfile(webOpts);
|
|
4718
|
+
break;
|
|
4719
|
+
default: dockerfile = generateNextjsDockerfile(webOpts);
|
|
4720
|
+
}
|
|
4721
|
+
} else if (app.entry) dockerfile = generateEntryDockerfile({
|
|
4523
4722
|
imageName,
|
|
4524
4723
|
baseImage: "node:22-alpine",
|
|
4525
4724
|
port: app.port,
|
|
@@ -4559,7 +4758,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4559
4758
|
logger$7.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
|
|
4560
4759
|
logger$7.log("\n📋 Build commands:");
|
|
4561
4760
|
for (const result of results) {
|
|
4562
|
-
const icon = result.type === "backend" ? "⚙️" : "🌐";
|
|
4761
|
+
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
4563
4762
|
logger$7.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
4564
4763
|
}
|
|
4565
4764
|
logger$7.log("\n📋 Run all services:");
|
|
@@ -4796,54 +4995,6 @@ async function deployDokploy(options) {
|
|
|
4796
4995
|
};
|
|
4797
4996
|
}
|
|
4798
4997
|
|
|
4799
|
-
//#endregion
|
|
4800
|
-
//#region src/deploy/domain.ts
|
|
4801
|
-
/**
|
|
4802
|
-
* Resolve the hostname for an app based on stage configuration.
|
|
4803
|
-
*
|
|
4804
|
-
* Domain resolution priority:
|
|
4805
|
-
* 1. Explicit app.domain override (string or stage-specific)
|
|
4806
|
-
* 2. Default pattern based on app type:
|
|
4807
|
-
* - Main frontend app gets base domain (e.g., 'myapp.com')
|
|
4808
|
-
* - Other apps get prefixed domain (e.g., 'api.myapp.com')
|
|
4809
|
-
*
|
|
4810
|
-
* @param appName - The name of the app
|
|
4811
|
-
* @param app - The normalized app configuration
|
|
4812
|
-
* @param stage - The deployment stage (e.g., 'production', 'development')
|
|
4813
|
-
* @param dokployConfig - Dokploy workspace configuration with domain mappings
|
|
4814
|
-
* @param isMainFrontend - Whether this is the main frontend app
|
|
4815
|
-
* @returns The resolved hostname for the app
|
|
4816
|
-
* @throws Error if no domain configuration is found for the stage
|
|
4817
|
-
*/
|
|
4818
|
-
function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
|
|
4819
|
-
if (app.domain) {
|
|
4820
|
-
if (typeof app.domain === "string") return app.domain;
|
|
4821
|
-
if (app.domain[stage]) return app.domain[stage];
|
|
4822
|
-
}
|
|
4823
|
-
const baseDomain = dokployConfig?.domains?.[stage];
|
|
4824
|
-
if (!baseDomain) throw new Error(`No domain configured for stage "${stage}". Add deploy.dokploy.domains.${stage} to gkm.config.ts`);
|
|
4825
|
-
if (isMainFrontend) return baseDomain;
|
|
4826
|
-
return `${appName}.${baseDomain}`;
|
|
4827
|
-
}
|
|
4828
|
-
/**
|
|
4829
|
-
* Determine if an app is the "main" frontend (gets base domain).
|
|
4830
|
-
*
|
|
4831
|
-
* An app is considered the main frontend if:
|
|
4832
|
-
* 1. It's named 'web' and is a frontend type
|
|
4833
|
-
* 2. It's the first frontend app in the apps list
|
|
4834
|
-
*
|
|
4835
|
-
* @param appName - The name of the app to check
|
|
4836
|
-
* @param app - The app configuration
|
|
4837
|
-
* @param allApps - All apps in the workspace
|
|
4838
|
-
* @returns True if this is the main frontend app
|
|
4839
|
-
*/
|
|
4840
|
-
function isMainFrontendApp(appName, app, allApps) {
|
|
4841
|
-
if (app.type !== "frontend") return false;
|
|
4842
|
-
if (appName === "web") return true;
|
|
4843
|
-
for (const [name$1, a] of Object.entries(allApps)) if (a.type === "frontend") return name$1 === appName;
|
|
4844
|
-
return false;
|
|
4845
|
-
}
|
|
4846
|
-
|
|
4847
4998
|
//#endregion
|
|
4848
4999
|
//#region src/deploy/env-resolver.ts
|
|
4849
5000
|
/**
|
|
@@ -5317,7 +5468,7 @@ function resolveSnifferFile(baseName) {
|
|
|
5317
5468
|
*/
|
|
5318
5469
|
async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
|
|
5319
5470
|
const { logWarnings = true, markOptional = false } = options;
|
|
5320
|
-
if (app.type === "
|
|
5471
|
+
if (app.type === "web" || app.type === "mobile") {
|
|
5321
5472
|
const publicPrefix = require_workspace.getPublicEnvPrefix(app.framework);
|
|
5322
5473
|
const depVars = publicPrefix ? (app.dependencies ?? []).map((dep) => `${publicPrefix}${dep.toUpperCase()}_URL`) : [];
|
|
5323
5474
|
if (app.config) {
|
|
@@ -6244,8 +6395,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6244
6395
|
}
|
|
6245
6396
|
}
|
|
6246
6397
|
}
|
|
6398
|
+
const skippedMobileApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "mobile");
|
|
6399
|
+
if (skippedMobileApps.length > 0) logger$3.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
6247
6400
|
const backendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "backend");
|
|
6248
|
-
const frontendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "
|
|
6401
|
+
const frontendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "web");
|
|
6249
6402
|
const perAppDbCredentials = /* @__PURE__ */ new Map();
|
|
6250
6403
|
if (provisionedPostgres && backendApps.length > 0) {
|
|
6251
6404
|
const appsNeedingDb = backendApps.filter((appName) => {
|
|
@@ -8741,7 +8894,7 @@ export default defineConfig({
|
|
|
8741
8894
|
function generateFrontendAppEntry(options) {
|
|
8742
8895
|
switch (options.frontendFramework) {
|
|
8743
8896
|
case "tanstack-start": return `web: {
|
|
8744
|
-
type: '
|
|
8897
|
+
type: 'web',
|
|
8745
8898
|
framework: 'tanstack-start',
|
|
8746
8899
|
path: 'apps/web',
|
|
8747
8900
|
port: 3001,
|
|
@@ -8752,14 +8905,14 @@ function generateFrontendAppEntry(options) {
|
|
|
8752
8905
|
},
|
|
8753
8906
|
},`;
|
|
8754
8907
|
case "expo": return `app: {
|
|
8755
|
-
type: '
|
|
8908
|
+
type: 'mobile',
|
|
8756
8909
|
framework: 'expo',
|
|
8757
8910
|
path: 'apps/app',
|
|
8758
8911
|
port: 8081,
|
|
8759
8912
|
dependencies: ['api', 'auth'],
|
|
8760
8913
|
},`;
|
|
8761
8914
|
default: return `web: {
|
|
8762
|
-
type: '
|
|
8915
|
+
type: 'web',
|
|
8763
8916
|
framework: 'nextjs',
|
|
8764
8917
|
path: 'apps/web',
|
|
8765
8918
|
port: 3001,
|
|
@@ -13139,7 +13292,7 @@ function printSummary(workspace, stage) {
|
|
|
13139
13292
|
logger$1.log("\n✅ Development environment ready!\n");
|
|
13140
13293
|
logger$1.log("📋 Apps:");
|
|
13141
13294
|
for (const [name$1, app] of Object.entries(workspace.apps)) {
|
|
13142
|
-
const icon = app.type === "
|
|
13295
|
+
const icon = app.type === "web" ? "🌐" : app.type === "mobile" ? "📱" : "🔧";
|
|
13143
13296
|
logger$1.log(` ${icon} ${name$1} → http://localhost:${app.port}`);
|
|
13144
13297
|
}
|
|
13145
13298
|
logger$1.log("\n🚀 Next steps:");
|
|
@@ -13613,7 +13766,7 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
13613
13766
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13614
13767
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
13615
13768
|
const { pushSecrets: pushSecrets$1 } = await Promise.resolve().then(() => require("./sync-D1Pa30oV.cjs"));
|
|
13616
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
13769
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-D93YG7G5.cjs"));
|
|
13617
13770
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
|
|
13618
13771
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13619
13772
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
@@ -13639,7 +13792,7 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
13639
13792
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
13640
13793
|
const { pullSecrets: pullSecrets$1 } = await Promise.resolve().then(() => require("./sync-D1Pa30oV.cjs"));
|
|
13641
13794
|
const { writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
|
|
13642
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
13795
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-D93YG7G5.cjs"));
|
|
13643
13796
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13644
13797
|
let secrets = await pullSecrets$1(options.stage, workspace);
|
|
13645
13798
|
if (!secrets) {
|
|
@@ -13664,7 +13817,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
13664
13817
|
const globalOptions = program.opts();
|
|
13665
13818
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13666
13819
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
13667
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
13820
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-D93YG7G5.cjs"));
|
|
13668
13821
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
|
|
13669
13822
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13670
13823
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|