@geekmidas/cli 0.38.0 → 0.40.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 (80) hide show
  1. package/dist/{bundler-DQIuE3Kn.mjs → bundler-Db83tLti.mjs} +2 -2
  2. package/dist/{bundler-DQIuE3Kn.mjs.map → bundler-Db83tLti.mjs.map} +1 -1
  3. package/dist/{bundler-CyHg1v_T.cjs → bundler-DsXfFSCU.cjs} +2 -2
  4. package/dist/{bundler-CyHg1v_T.cjs.map → bundler-DsXfFSCU.cjs.map} +1 -1
  5. package/dist/{config-BC5n1a2D.mjs → config-C0b0jdmU.mjs} +2 -2
  6. package/dist/{config-BC5n1a2D.mjs.map → config-C0b0jdmU.mjs.map} +1 -1
  7. package/dist/{config-BAE9LFC1.cjs → config-xVZsRjN7.cjs} +2 -2
  8. package/dist/{config-BAE9LFC1.cjs.map → config-xVZsRjN7.cjs.map} +1 -1
  9. package/dist/config.cjs +2 -2
  10. package/dist/config.d.cts +1 -1
  11. package/dist/config.d.mts +2 -2
  12. package/dist/config.mjs +2 -2
  13. package/dist/dokploy-api-Bdmk5ImW.cjs +3 -0
  14. package/dist/{dokploy-api-C5czOZoc.cjs → dokploy-api-BdxOMH_V.cjs} +43 -1
  15. package/dist/{dokploy-api-C5czOZoc.cjs.map → dokploy-api-BdxOMH_V.cjs.map} +1 -1
  16. package/dist/{dokploy-api-B9qR2Yn1.mjs → dokploy-api-DWsqNjwP.mjs} +43 -1
  17. package/dist/{dokploy-api-B9qR2Yn1.mjs.map → dokploy-api-DWsqNjwP.mjs.map} +1 -1
  18. package/dist/dokploy-api-tZSZaHd9.mjs +3 -0
  19. package/dist/{encryption-JtMsiGNp.mjs → encryption-BC4MAODn.mjs} +1 -1
  20. package/dist/{encryption-JtMsiGNp.mjs.map → encryption-BC4MAODn.mjs.map} +1 -1
  21. package/dist/encryption-Biq0EZ4m.cjs +4 -0
  22. package/dist/encryption-CQXBZGkt.mjs +3 -0
  23. package/dist/{encryption-BAz0xQ1Q.cjs → encryption-DaCB_NmS.cjs} +13 -3
  24. package/dist/{encryption-BAz0xQ1Q.cjs.map → encryption-DaCB_NmS.cjs.map} +1 -1
  25. package/dist/{index-C7TkoYmt.d.mts → index-CXa3odEw.d.mts} +68 -7
  26. package/dist/index-CXa3odEw.d.mts.map +1 -0
  27. package/dist/{index-CpchsC9w.d.cts → index-E8Nu2Rxl.d.cts} +67 -6
  28. package/dist/index-E8Nu2Rxl.d.cts.map +1 -0
  29. package/dist/index.cjs +787 -145
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.mjs +767 -125
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/{openapi-CjYeF-Tg.mjs → openapi-D3pA6FfZ.mjs} +2 -2
  34. package/dist/{openapi-CjYeF-Tg.mjs.map → openapi-D3pA6FfZ.mjs.map} +1 -1
  35. package/dist/{openapi-a-e3Y8WA.cjs → openapi-DhcCtKzM.cjs} +2 -2
  36. package/dist/{openapi-a-e3Y8WA.cjs.map → openapi-DhcCtKzM.cjs.map} +1 -1
  37. package/dist/{openapi-react-query-DvNpdDpM.cjs → openapi-react-query-C_MxpBgF.cjs} +1 -1
  38. package/dist/{openapi-react-query-DvNpdDpM.cjs.map → openapi-react-query-C_MxpBgF.cjs.map} +1 -1
  39. package/dist/{openapi-react-query-5rSortLH.mjs → openapi-react-query-ZoP9DPbY.mjs} +1 -1
  40. package/dist/{openapi-react-query-5rSortLH.mjs.map → openapi-react-query-ZoP9DPbY.mjs.map} +1 -1
  41. package/dist/openapi-react-query.cjs +1 -1
  42. package/dist/openapi-react-query.mjs +1 -1
  43. package/dist/openapi.cjs +3 -3
  44. package/dist/openapi.d.mts +1 -1
  45. package/dist/openapi.mjs +3 -3
  46. package/dist/{types-K2uQJ-FO.d.mts → types-BtGL-8QS.d.mts} +1 -1
  47. package/dist/{types-K2uQJ-FO.d.mts.map → types-BtGL-8QS.d.mts.map} +1 -1
  48. package/dist/workspace/index.cjs +1 -1
  49. package/dist/workspace/index.d.cts +2 -2
  50. package/dist/workspace/index.d.mts +3 -3
  51. package/dist/workspace/index.mjs +1 -1
  52. package/dist/{workspace-My0A4IRO.cjs → workspace-BDAhr6Kb.cjs} +33 -4
  53. package/dist/{workspace-My0A4IRO.cjs.map → workspace-BDAhr6Kb.cjs.map} +1 -1
  54. package/dist/{workspace-DFJ3sWfY.mjs → workspace-D_6ZCaR_.mjs} +33 -4
  55. package/dist/{workspace-DFJ3sWfY.mjs.map → workspace-D_6ZCaR_.mjs.map} +1 -1
  56. package/package.json +5 -5
  57. package/src/build/index.ts +23 -6
  58. package/src/deploy/__tests__/domain.spec.ts +231 -0
  59. package/src/deploy/__tests__/secrets.spec.ts +300 -0
  60. package/src/deploy/__tests__/sniffer.spec.ts +221 -0
  61. package/src/deploy/docker.ts +58 -29
  62. package/src/deploy/dokploy-api.ts +99 -0
  63. package/src/deploy/domain.ts +125 -0
  64. package/src/deploy/index.ts +364 -145
  65. package/src/deploy/secrets.ts +182 -0
  66. package/src/deploy/sniffer.ts +180 -0
  67. package/src/dev/index.ts +155 -9
  68. package/src/docker/index.ts +17 -2
  69. package/src/docker/templates.ts +171 -1
  70. package/src/index.ts +18 -1
  71. package/src/init/generators/auth.ts +2 -0
  72. package/src/init/versions.ts +2 -2
  73. package/src/workspace/index.ts +2 -0
  74. package/src/workspace/schema.ts +32 -6
  75. package/src/workspace/types.ts +64 -2
  76. package/tsconfig.tsbuildinfo +1 -1
  77. package/dist/dokploy-api-B0w17y4_.mjs +0 -3
  78. package/dist/dokploy-api-BnGeUqN4.cjs +0 -3
  79. package/dist/index-C7TkoYmt.d.mts.map +0 -1
  80. package/dist/index-CpchsC9w.d.cts.map +0 -1
@@ -25,6 +25,12 @@ export interface FrontendDockerfileOptions {
25
25
  turboPackage: string;
26
26
  /** Detected package manager */
27
27
  packageManager: PackageManager;
28
+ /**
29
+ * Public URL build args to include in the Dockerfile.
30
+ * These will be declared as ARG and converted to ENV for Next.js build.
31
+ * Example: ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_AUTH_URL']
32
+ */
33
+ publicUrlArgs?: string[];
28
34
  }
29
35
 
30
36
  export interface MultiStageDockerfileOptions extends DockerTemplateOptions {
@@ -568,7 +574,14 @@ export function resolveDockerConfig(
568
574
  export function generateNextjsDockerfile(
569
575
  options: FrontendDockerfileOptions,
570
576
  ): string {
571
- const { baseImage, port, appPath, turboPackage, packageManager } = options;
577
+ const {
578
+ baseImage,
579
+ port,
580
+ appPath,
581
+ turboPackage,
582
+ packageManager,
583
+ publicUrlArgs = ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_AUTH_URL'],
584
+ } = options;
572
585
 
573
586
  const pm = getPmConfig(packageManager);
574
587
  const installPm = pm.install ? `RUN ${pm.install}` : '';
@@ -580,6 +593,14 @@ export function generateNextjsDockerfile(
580
593
  // Use pnpm dlx for pnpm (avoids global bin dir issues in Docker)
581
594
  const turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';
582
595
 
596
+ // Generate ARG and ENV declarations for public URLs
597
+ const publicUrlArgDeclarations = publicUrlArgs
598
+ .map((arg) => `ARG ${arg}=""`)
599
+ .join('\n');
600
+ const publicUrlEnvDeclarations = publicUrlArgs
601
+ .map((arg) => `ENV ${arg}=$${arg}`)
602
+ .join('\n');
603
+
583
604
  return `# syntax=docker/dockerfile:1
584
605
  # Next.js standalone Dockerfile with turbo prune optimization
585
606
 
@@ -615,6 +636,13 @@ FROM deps AS builder
615
636
 
616
637
  WORKDIR /app
617
638
 
639
+ # Build-time args for public API URLs (populated by gkm deploy)
640
+ # These get baked into the Next.js build as public environment variables
641
+ ${publicUrlArgDeclarations}
642
+
643
+ # Convert ARGs to ENVs for Next.js build
644
+ ${publicUrlEnvDeclarations}
645
+
618
646
  # Copy pruned source
619
647
  COPY --from=pruner /app/out/full/ ./
620
648
 
@@ -717,9 +745,20 @@ FROM deps AS builder
717
745
 
718
746
  WORKDIR /app
719
747
 
748
+ # Build-time args for encrypted secrets
749
+ ARG GKM_ENCRYPTED_CREDENTIALS=""
750
+ ARG GKM_CREDENTIALS_IV=""
751
+
720
752
  # Copy pruned source
721
753
  COPY --from=pruner /app/out/full/ ./
722
754
 
755
+ # Write encrypted credentials for gkm build to embed
756
+ RUN if [ -n "$GKM_ENCRYPTED_CREDENTIALS" ]; then \
757
+ mkdir -p ${appPath}/.gkm && \
758
+ echo "$GKM_ENCRYPTED_CREDENTIALS" > ${appPath}/.gkm/credentials.enc && \
759
+ echo "$GKM_CREDENTIALS_IV" > ${appPath}/.gkm/credentials.iv; \
760
+ fi
761
+
723
762
  # Build production server using gkm
724
763
  RUN cd ${appPath} && ./node_modules/.bin/gkm build --provider server --production
725
764
 
@@ -750,3 +789,134 @@ ENTRYPOINT ["/sbin/tini", "--"]
750
789
  CMD ["node", "server.mjs"]
751
790
  `;
752
791
  }
792
+
793
+ /**
794
+ * Options for entry-based Dockerfile generation.
795
+ */
796
+ export interface EntryDockerfileOptions {
797
+ imageName: string;
798
+ baseImage: string;
799
+ port: number;
800
+ /** App path relative to workspace root */
801
+ appPath: string;
802
+ /** Entry file path relative to app path (e.g., './src/index.ts') */
803
+ entry: string;
804
+ /** Package name for turbo prune */
805
+ turboPackage: string;
806
+ /** Detected package manager */
807
+ packageManager: PackageManager;
808
+ /** Health check path (default: '/health') */
809
+ healthCheckPath?: string;
810
+ }
811
+
812
+ /**
813
+ * Generate a Dockerfile for apps with a custom entry point.
814
+ * Uses tsdown to bundle the entry point into dist/index.mjs.
815
+ * This is used for apps that don't use gkm routes (e.g., Better Auth servers).
816
+ * @internal Exported for testing
817
+ */
818
+ export function generateEntryDockerfile(options: EntryDockerfileOptions): string {
819
+ const {
820
+ baseImage,
821
+ port,
822
+ appPath,
823
+ entry,
824
+ turboPackage,
825
+ packageManager,
826
+ healthCheckPath = '/health',
827
+ } = options;
828
+
829
+ const pm = getPmConfig(packageManager);
830
+ const installPm = pm.install ? `RUN ${pm.install}` : '';
831
+ const turboInstallCmd = getTurboInstallCmd(packageManager);
832
+ const turboCmd = packageManager === 'pnpm' ? 'pnpm dlx turbo' : 'npx turbo';
833
+
834
+ return `# syntax=docker/dockerfile:1
835
+ # Entry-based Dockerfile with turbo prune + tsdown bundling
836
+
837
+ # Stage 1: Prune monorepo
838
+ FROM ${baseImage} AS pruner
839
+
840
+ WORKDIR /app
841
+
842
+ ${installPm}
843
+
844
+ COPY . .
845
+
846
+ # Prune to only include necessary packages
847
+ RUN ${turboCmd} prune ${turboPackage} --docker
848
+
849
+ # Stage 2: Install dependencies
850
+ FROM ${baseImage} AS deps
851
+
852
+ WORKDIR /app
853
+
854
+ ${installPm}
855
+
856
+ # Copy pruned lockfile and package.jsons
857
+ COPY --from=pruner /app/out/${pm.lockfile} ./
858
+ COPY --from=pruner /app/out/json/ ./
859
+
860
+ # Install dependencies
861
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
862
+ ${turboInstallCmd}
863
+
864
+ # Stage 3: Build with tsdown
865
+ FROM deps AS builder
866
+
867
+ WORKDIR /app
868
+
869
+ # Build-time args for encrypted secrets
870
+ ARG GKM_ENCRYPTED_CREDENTIALS=""
871
+ ARG GKM_CREDENTIALS_IV=""
872
+
873
+ # Copy pruned source
874
+ COPY --from=pruner /app/out/full/ ./
875
+
876
+ # Write encrypted credentials for tsdown to embed via define
877
+ RUN if [ -n "$GKM_ENCRYPTED_CREDENTIALS" ]; then \
878
+ mkdir -p ${appPath}/.gkm && \
879
+ echo "$GKM_ENCRYPTED_CREDENTIALS" > ${appPath}/.gkm/credentials.enc && \
880
+ echo "$GKM_CREDENTIALS_IV" > ${appPath}/.gkm/credentials.iv; \
881
+ fi
882
+
883
+ # Bundle entry point with tsdown (outputs to dist/index.mjs)
884
+ # Use define to embed credentials if present
885
+ RUN cd ${appPath} && \
886
+ if [ -f .gkm/credentials.enc ]; then \
887
+ CREDS=$(cat .gkm/credentials.enc) && \
888
+ IV=$(cat .gkm/credentials.iv) && \
889
+ npx tsdown ${entry} --outDir dist --format esm \
890
+ --define __GKM_ENCRYPTED_CREDENTIALS__="'\\"$CREDS\\"'" \
891
+ --define __GKM_CREDENTIALS_IV__="'\\"$IV\\"'"; \
892
+ else \
893
+ npx tsdown ${entry} --outDir dist --format esm; \
894
+ fi
895
+
896
+ # Stage 4: Production
897
+ FROM ${baseImage} AS runner
898
+
899
+ WORKDIR /app
900
+
901
+ RUN apk add --no-cache tini
902
+
903
+ RUN addgroup --system --gid 1001 nodejs && \\
904
+ adduser --system --uid 1001 app
905
+
906
+ # Copy bundled output only (no node_modules needed - fully bundled)
907
+ COPY --from=builder --chown=app:nodejs /app/${appPath}/dist/index.mjs ./
908
+
909
+ ENV NODE_ENV=production
910
+ ENV PORT=${port}
911
+
912
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
913
+ CMD wget -q --spider http://localhost:${port}${healthCheckPath} || exit 1
914
+
915
+ USER app
916
+
917
+ EXPOSE ${port}
918
+
919
+ ENTRYPOINT ["/sbin/tini", "--"]
920
+ CMD ["node", "index.mjs"]
921
+ `;
922
+ }
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@ import { loginCommand, logoutCommand, whoamiCommand } from './auth';
6
6
  import { buildCommand } from './build/index';
7
7
  import { type DeployProvider, deployCommand } from './deploy/index';
8
8
  import { deployInitCommand, deployListCommand } from './deploy/init';
9
- import { devCommand } from './dev/index';
9
+ import { devCommand, execCommand } from './dev/index';
10
10
  import { type DockerOptions, dockerCommand } from './docker/index';
11
11
  import { type InitOptions, initCommand } from './init/index';
12
12
  import { openapiCommand } from './openapi';
@@ -173,6 +173,23 @@ program
173
173
  },
174
174
  );
175
175
 
176
+ program
177
+ .command('exec')
178
+ .description('Run a command with secrets injected into Credentials')
179
+ .argument('<command...>', 'Command to run (use -- before command)')
180
+ .action(async (commandArgs: string[]) => {
181
+ try {
182
+ const globalOptions = program.opts();
183
+ if (globalOptions.cwd) {
184
+ process.chdir(globalOptions.cwd);
185
+ }
186
+ await execCommand(commandArgs);
187
+ } catch (error) {
188
+ console.error(error instanceof Error ? error.message : 'Command failed');
189
+ process.exit(1);
190
+ }
191
+ });
192
+
176
193
  program
177
194
  .command('test')
178
195
  .description('Run tests with secrets loaded from environment')
@@ -26,6 +26,8 @@ export function generateAuthAppFiles(
26
26
  build: 'tsc',
27
27
  start: 'node dist/index.js',
28
28
  typecheck: 'tsc --noEmit',
29
+ 'db:migrate': 'gkm exec -- npx @better-auth/cli migrate',
30
+ 'db:generate': 'gkm exec -- npx @better-auth/cli generate',
29
31
  },
30
32
  dependencies: {
31
33
  [modelsPackage]: 'workspace:*',
@@ -32,10 +32,10 @@ export const GEEKMIDAS_VERSIONS = {
32
32
  '@geekmidas/cli': CLI_VERSION,
33
33
  '@geekmidas/client': '~0.5.0',
34
34
  '@geekmidas/cloud': '~0.2.0',
35
- '@geekmidas/constructs': '~0.6.0',
35
+ '@geekmidas/constructs': '~0.7.0',
36
36
  '@geekmidas/db': '~0.3.0',
37
37
  '@geekmidas/emailkit': '~0.2.0',
38
- '@geekmidas/envkit': '~0.5.0',
38
+ '@geekmidas/envkit': '~0.6.0',
39
39
  '@geekmidas/errors': '~0.1.0',
40
40
  '@geekmidas/events': '~0.2.0',
41
41
  '@geekmidas/logger': '~0.4.0',
@@ -34,11 +34,13 @@ export type {
34
34
  AppConfigInput,
35
35
  AppInput,
36
36
  AppsRecord,
37
+ BackendFramework,
37
38
  ClientConfig,
38
39
  ConstrainedApps,
39
40
  DeployConfig,
40
41
  DeployTarget,
41
42
  DokployWorkspaceConfig,
43
+ FrontendFramework,
42
44
  InferAppNames,
43
45
  InferredWorkspaceConfig,
44
46
  LoadedConfig,
@@ -57,6 +57,26 @@ const ClientConfigSchema = z.object({
57
57
  */
58
58
  const AuthProviderSchema = z.enum(['better-auth']);
59
59
 
60
+ /**
61
+ * Backend framework schema for non-gkm apps.
62
+ */
63
+ const BackendFrameworkSchema = z.enum([
64
+ 'hono',
65
+ 'better-auth',
66
+ 'express',
67
+ 'fastify',
68
+ ]);
69
+
70
+ /**
71
+ * Frontend framework schema.
72
+ */
73
+ const FrontendFrameworkSchema = z.enum(['nextjs', 'remix', 'vite']);
74
+
75
+ /**
76
+ * Combined framework schema (backend or frontend).
77
+ */
78
+ const FrameworkSchema = z.union([BackendFrameworkSchema, FrontendFrameworkSchema]);
79
+
60
80
  /**
61
81
  * Deploy target schema.
62
82
  * Currently only 'dokploy' is supported.
@@ -205,8 +225,11 @@ const AppConfigSchema = z
205
225
  runtime: z.enum(['node', 'bun']).optional(),
206
226
  env: z.union([z.string(), z.array(z.string())]).optional(),
207
227
 
208
- // Frontend-specific
209
- framework: z.enum(['nextjs']).optional(),
228
+ // Entry point for non-gkm apps (used by dev and docker build)
229
+ entry: z.string().optional(),
230
+
231
+ // Framework (backend or frontend)
232
+ framework: FrameworkSchema.optional(),
210
233
  client: ClientConfigSchema.optional(),
211
234
 
212
235
  // Auth-specific
@@ -215,14 +238,17 @@ const AppConfigSchema = z
215
238
  // Note: routes is optional for backend apps - some backends like auth servers don't use routes
216
239
  .refine(
217
240
  (data) => {
218
- // Frontend apps must have framework
219
- if (data.type === 'frontend' && !data.framework) {
220
- return false;
241
+ // Frontend apps must have a frontend framework
242
+ if (data.type === 'frontend') {
243
+ const frontendFrameworks = ['nextjs', 'remix', 'vite'];
244
+ if (!data.framework || !frontendFrameworks.includes(data.framework)) {
245
+ return false;
246
+ }
221
247
  }
222
248
  return true;
223
249
  },
224
250
  {
225
- message: 'Frontend apps must have framework defined',
251
+ message: 'Frontend apps must have a valid frontend framework (nextjs, remix, vite)',
226
252
  path: ['framework'],
227
253
  },
228
254
  )
@@ -16,6 +16,16 @@ import type {
16
16
  */
17
17
  export type DeployTarget = 'dokploy' | 'vercel' | 'cloudflare';
18
18
 
19
+ /**
20
+ * Backend framework types for apps that don't use gkm routes.
21
+ */
22
+ export type BackendFramework = 'hono' | 'better-auth' | 'express' | 'fastify';
23
+
24
+ /**
25
+ * Frontend framework types.
26
+ */
27
+ export type FrontendFramework = 'nextjs' | 'remix' | 'vite';
28
+
19
29
  /**
20
30
  * Service image configuration for custom Docker images.
21
31
  */
@@ -51,6 +61,20 @@ export interface ServicesConfig {
51
61
  mail?: boolean | MailServiceConfig;
52
62
  }
53
63
 
64
+ /**
65
+ * Stage-based domain configuration.
66
+ * Maps deployment stages to base domains.
67
+ * @example { development: 'dev.myapp.com', production: 'myapp.com' }
68
+ */
69
+ export type DokployDomainsConfig = Record<string, string>;
70
+
71
+ /**
72
+ * Per-app domain override configuration.
73
+ * Can be a single domain string or stage-specific domains.
74
+ * @example 'api.custom.com' or { production: 'api.custom.com', staging: 'api.staging.com' }
75
+ */
76
+ export type AppDomainConfig = string | Record<string, string>;
77
+
54
78
  /**
55
79
  * Dokploy workspace deployment configuration.
56
80
  */
@@ -63,6 +87,13 @@ export interface DokployWorkspaceConfig {
63
87
  registry?: string;
64
88
  /** Registry ID in Dokploy */
65
89
  registryId?: string;
90
+ /**
91
+ * Stage-based domain configuration.
92
+ * The main frontend app gets the base domain.
93
+ * Other apps get {appName}.{baseDomain} by default.
94
+ * @example { development: 'dev.myapp.com', production: 'myapp.com' }
95
+ */
96
+ domains?: DokployDomainsConfig;
66
97
  }
67
98
 
68
99
  /**
@@ -163,11 +194,34 @@ interface AppConfigBase {
163
194
  /** Environment file(s) to load */
164
195
  env?: string | string[];
165
196
 
197
+ // Entry point for non-gkm apps
198
+ /**
199
+ * Entry file path for apps that don't use gkm routes.
200
+ * Used by both `gkm dev` (runs with tsx) and Docker builds (bundles with tsdown).
201
+ * @example './src/index.ts'
202
+ */
203
+ entry?: string;
204
+
166
205
  // Frontend-specific
167
- /** Frontend framework (currently only 'nextjs') */
168
- framework?: 'nextjs';
206
+ /** Framework for the app (frontend or backend without gkm routes) */
207
+ framework?: BackendFramework | FrontendFramework;
169
208
  /** Client generation configuration */
170
209
  client?: ClientConfig;
210
+
211
+ // Deployment
212
+ /**
213
+ * Override domain for this app (per-stage or single value).
214
+ * @example 'api.custom.com' or { production: 'api.custom.com', staging: 'api.staging.com' }
215
+ */
216
+ domain?: AppDomainConfig;
217
+
218
+ /**
219
+ * Required environment variables for entry-based apps.
220
+ * Use this instead of envParser for apps that don't use gkm routes.
221
+ * The deploy command uses this to filter which secrets to embed.
222
+ * @example ['DATABASE_URL', 'BETTER_AUTH_SECRET']
223
+ */
224
+ requiredEnv?: string[];
171
225
  }
172
226
 
173
227
  /**
@@ -294,6 +348,14 @@ export interface NormalizedAppConfig extends Omit<AppConfigBase, 'type'> {
294
348
  dependencies: string[];
295
349
  /** Resolved deploy target (app.deploy > deploy.default > 'dokploy') */
296
350
  resolvedDeployTarget: DeployTarget;
351
+ /** Entry file path for non-gkm apps */
352
+ entry?: string;
353
+ /** Framework for the app */
354
+ framework?: BackendFramework | FrontendFramework;
355
+ /** Override domain for this app */
356
+ domain?: AppDomainConfig;
357
+ /** Required environment variables for entry-based apps */
358
+ requiredEnv?: string[];
297
359
  }
298
360
 
299
361
  /**