@geekmidas/cli 1.10.35 → 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 +20 -0
- package/dist/{config-B62g483e.mjs → config-Bbu1JKvf.mjs} +2 -2
- package/dist/{config-B62g483e.mjs.map → config-Bbu1JKvf.mjs.map} +1 -1
- package/dist/{config-Cuo8vFsp.cjs → config-DujV8DbR.cjs} +2 -2
- package/dist/{config-Cuo8vFsp.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/deploy/sniffer-loader.cjs +1 -1
- 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-Dt_dZ7K4.d.mts → index-BBvGMG_A.d.mts} +162 -68
- package/dist/index-BBvGMG_A.d.mts.map +1 -0
- package/dist/{index-C3t5VL4R.d.cts → index-Dz2a7xQU.d.cts} +162 -68
- package/dist/index-Dz2a7xQU.d.cts.map +1 -0
- package/dist/index.cjs +1366 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1366 -149
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-B_rJjImN.cjs → openapi-0JUzU5Tv.cjs} +72 -15
- package/dist/openapi-0JUzU5Tv.cjs.map +1 -0
- package/dist/{openapi-CflxypuN.mjs → openapi-C5AycKTR.mjs} +73 -16
- package/dist/openapi-C5AycKTR.mjs.map +1 -0
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +7 -0
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +7 -0
- package/dist/openapi.d.mts.map +1 -1
- 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 +4 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +2 -2
- package/dist/{workspace-CGYykWfn.cjs → workspace-CQTT7HJQ.cjs} +107 -27
- package/dist/workspace-CQTT7HJQ.cjs.map +1 -0
- package/dist/{workspace-Bi4X7Yzy.mjs → workspace-D-37Ylqo.mjs} +90 -28
- package/dist/workspace-D-37Ylqo.mjs.map +1 -0
- package/docs/dev-server-resilience-design.md +189 -0
- package/package.json +4 -4
- package/src/__tests__/openapi.spec.ts +99 -9
- 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 +29 -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/env-resolver.ts +5 -10
- package/src/deploy/index.ts +25 -8
- package/src/deploy/sniffer.ts +11 -5
- package/src/deploy/types.ts +1 -1
- package/src/dev/__tests__/index.spec.ts +11 -11
- package/src/dev/index.ts +43 -9
- 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/index.ts +6 -2
- package/src/init/__tests__/generators.spec.ts +147 -0
- package/src/init/__tests__/init.spec.ts +1 -1
- package/src/init/generators/mobile-expo.ts +576 -0
- package/src/init/generators/monorepo.ts +42 -11
- package/src/init/generators/web-tanstack.ts +348 -0
- package/src/init/index.ts +40 -3
- package/src/init/templates/index.ts +31 -0
- package/src/init/versions.ts +2 -2
- package/src/openapi.ts +110 -23
- 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 +96 -4
- package/src/workspace/__tests__/publicEnv.spec.ts +64 -0
- 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 +17 -3
- package/src/workspace/publicEnv.ts +57 -0
- package/src/workspace/schema.ts +126 -27
- package/src/workspace/types.ts +40 -64
- package/dist/index-C3t5VL4R.d.cts.map +0 -1
- package/dist/index-Dt_dZ7K4.d.mts.map +0 -1
- package/dist/openapi-B_rJjImN.cjs.map +0 -1
- package/dist/openapi-CflxypuN.mjs.map +0 -1
- package/dist/workspace-Bi4X7Yzy.mjs.map +0 -1
- package/dist/workspace-CGYykWfn.cjs.map +0 -1
|
@@ -430,7 +430,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
430
430
|
resolvedDeployTarget: 'dokploy',
|
|
431
431
|
},
|
|
432
432
|
web: {
|
|
433
|
-
type: '
|
|
433
|
+
type: 'web',
|
|
434
434
|
path: 'apps/web',
|
|
435
435
|
port: 3001,
|
|
436
436
|
dependencies: ['api'],
|
|
@@ -527,7 +527,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
527
527
|
resolvedDeployTarget: 'dokploy',
|
|
528
528
|
},
|
|
529
529
|
web: {
|
|
530
|
-
type: '
|
|
530
|
+
type: 'web',
|
|
531
531
|
path: 'apps/web',
|
|
532
532
|
port: 3002,
|
|
533
533
|
dependencies: ['api', 'auth'],
|
|
@@ -566,7 +566,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
566
566
|
resolvedDeployTarget: 'dokploy',
|
|
567
567
|
},
|
|
568
568
|
web: {
|
|
569
|
-
type: '
|
|
569
|
+
type: 'web',
|
|
570
570
|
path: 'apps/web',
|
|
571
571
|
port: 3002,
|
|
572
572
|
dependencies: ['api'],
|
|
@@ -614,7 +614,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
614
614
|
};
|
|
615
615
|
|
|
616
616
|
const app = {
|
|
617
|
-
type: '
|
|
617
|
+
type: 'web' as const,
|
|
618
618
|
path: 'apps/web',
|
|
619
619
|
port: 3000,
|
|
620
620
|
dependencies: ['api', 'auth'],
|
|
@@ -645,7 +645,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
645
645
|
};
|
|
646
646
|
|
|
647
647
|
const app = {
|
|
648
|
-
type: '
|
|
648
|
+
type: 'web' as const,
|
|
649
649
|
path: 'apps/web',
|
|
650
650
|
port: 3000,
|
|
651
651
|
dependencies: ['api', 'auth'], // wants both api and auth
|
|
@@ -719,7 +719,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
719
719
|
it('should inject DATABASE_URL for backend apps', () => {
|
|
720
720
|
const workspaceName = 'test-workspace';
|
|
721
721
|
const hasPostgres = true;
|
|
722
|
-
const appType: 'backend' | '
|
|
722
|
+
const appType: 'backend' | 'web' = 'backend';
|
|
723
723
|
|
|
724
724
|
const envVars: string[] = [];
|
|
725
725
|
|
|
@@ -735,7 +735,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
735
735
|
|
|
736
736
|
it('should not inject DATABASE_URL for frontend apps', () => {
|
|
737
737
|
const hasPostgres = true;
|
|
738
|
-
const appType = '
|
|
738
|
+
const appType = 'web' as 'backend' | 'web';
|
|
739
739
|
|
|
740
740
|
const envVars: string[] = [];
|
|
741
741
|
|
|
@@ -794,7 +794,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
794
794
|
it('should have correct structure for failed AppDeployResult', () => {
|
|
795
795
|
const failedResult = {
|
|
796
796
|
appName: 'web',
|
|
797
|
-
type: '
|
|
797
|
+
type: 'web' as const,
|
|
798
798
|
success: false,
|
|
799
799
|
error: 'Build failed',
|
|
800
800
|
};
|
|
@@ -809,7 +809,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
809
809
|
{ appName: 'api', type: 'backend' as const, success: true },
|
|
810
810
|
{
|
|
811
811
|
appName: 'web',
|
|
812
|
-
type: '
|
|
812
|
+
type: 'web' as const,
|
|
813
813
|
success: false,
|
|
814
814
|
error: 'Failed',
|
|
815
815
|
},
|
|
@@ -834,7 +834,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
834
834
|
};
|
|
835
835
|
|
|
836
836
|
const webApp = {
|
|
837
|
-
type: '
|
|
837
|
+
type: 'web' as const,
|
|
838
838
|
path: 'apps/web',
|
|
839
839
|
port: 3000,
|
|
840
840
|
dependencies: ['api', 'auth'],
|
|
@@ -873,7 +873,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
873
873
|
};
|
|
874
874
|
|
|
875
875
|
const webApp = {
|
|
876
|
-
type: '
|
|
876
|
+
type: 'web' as const,
|
|
877
877
|
path: 'apps/web',
|
|
878
878
|
port: 3001,
|
|
879
879
|
dependencies: ['api', 'payments'],
|
|
@@ -922,7 +922,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
922
922
|
};
|
|
923
923
|
|
|
924
924
|
const webApp = {
|
|
925
|
-
type: '
|
|
925
|
+
type: 'web' as const,
|
|
926
926
|
path: 'apps/web',
|
|
927
927
|
port: 3001,
|
|
928
928
|
dependencies: ['api', 'auth'],
|
|
@@ -997,7 +997,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
997
997
|
|
|
998
998
|
// Step 2: Deploy web (depends on api)
|
|
999
999
|
const webApp = {
|
|
1000
|
-
type: '
|
|
1000
|
+
type: 'web' as const,
|
|
1001
1001
|
path: 'apps/web',
|
|
1002
1002
|
port: 3001,
|
|
1003
1003
|
dependencies: ['api'],
|
|
@@ -1048,7 +1048,7 @@ describe('workspaceDeployCommand', () => {
|
|
|
1048
1048
|
};
|
|
1049
1049
|
|
|
1050
1050
|
const webApp = {
|
|
1051
|
-
type: '
|
|
1051
|
+
type: 'web' as const,
|
|
1052
1052
|
path: 'apps/web',
|
|
1053
1053
|
port: 3000,
|
|
1054
1054
|
dependencies: ['api', 'auth', 'notifications'],
|
|
@@ -32,7 +32,7 @@ describe('sniffAppEnvironment', () => {
|
|
|
32
32
|
|
|
33
33
|
describe('frontend apps', () => {
|
|
34
34
|
it('should return empty env vars for frontend apps with no dependencies', async () => {
|
|
35
|
-
const app = createApp({ type: '
|
|
35
|
+
const app = createApp({ type: 'web', dependencies: [] });
|
|
36
36
|
|
|
37
37
|
const result = await sniffAppEnvironment(app, 'web', workspacePath);
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ describe('sniffAppEnvironment', () => {
|
|
|
42
42
|
|
|
43
43
|
it('should return NEXT_PUBLIC_{DEP}_URL for frontend dependencies', async () => {
|
|
44
44
|
const app = createApp({
|
|
45
|
-
type: '
|
|
45
|
+
type: 'web',
|
|
46
46
|
dependencies: ['api', 'auth'],
|
|
47
47
|
});
|
|
48
48
|
|
|
@@ -56,7 +56,7 @@ describe('sniffAppEnvironment', () => {
|
|
|
56
56
|
|
|
57
57
|
it('should generate uppercase dep names in NEXT_PUBLIC_{DEP}_URL', async () => {
|
|
58
58
|
const app = createApp({
|
|
59
|
-
type: '
|
|
59
|
+
type: 'web',
|
|
60
60
|
dependencies: ['payments-service', 'notification_api'],
|
|
61
61
|
});
|
|
62
62
|
|
|
@@ -73,7 +73,7 @@ describe('sniffAppEnvironment', () => {
|
|
|
73
73
|
describe('config sniffing', () => {
|
|
74
74
|
it('should sniff env vars from config.client path', async () => {
|
|
75
75
|
const app = createApp({
|
|
76
|
-
type: '
|
|
76
|
+
type: 'web',
|
|
77
77
|
path: fixturesPath,
|
|
78
78
|
dependencies: [],
|
|
79
79
|
config: {
|
|
@@ -90,7 +90,7 @@ describe('sniffAppEnvironment', () => {
|
|
|
90
90
|
|
|
91
91
|
it('should sniff env vars from config.server path', async () => {
|
|
92
92
|
const app = createApp({
|
|
93
|
-
type: '
|
|
93
|
+
type: 'web',
|
|
94
94
|
path: fixturesPath,
|
|
95
95
|
dependencies: [],
|
|
96
96
|
config: {
|
|
@@ -107,7 +107,7 @@ describe('sniffAppEnvironment', () => {
|
|
|
107
107
|
|
|
108
108
|
it('should combine vars from both config.client and config.server', async () => {
|
|
109
109
|
const app = createApp({
|
|
110
|
-
type: '
|
|
110
|
+
type: 'web',
|
|
111
111
|
path: fixturesPath,
|
|
112
112
|
dependencies: ['api'],
|
|
113
113
|
config: {
|
|
@@ -129,7 +129,7 @@ describe('sniffAppEnvironment', () => {
|
|
|
129
129
|
|
|
130
130
|
it('should deduplicate vars from both config files', async () => {
|
|
131
131
|
const app = createApp({
|
|
132
|
-
type: '
|
|
132
|
+
type: 'web',
|
|
133
133
|
path: fixturesPath,
|
|
134
134
|
dependencies: [],
|
|
135
135
|
config: {
|
|
@@ -306,7 +306,7 @@ describe('sniffAppEnvironment', () => {
|
|
|
306
306
|
|
|
307
307
|
it('returns empty optionalEnvVars for frontend apps', async () => {
|
|
308
308
|
const app = createApp({
|
|
309
|
-
type: '
|
|
309
|
+
type: 'web',
|
|
310
310
|
dependencies: ['api'],
|
|
311
311
|
});
|
|
312
312
|
|
|
@@ -339,7 +339,7 @@ describe('sniffAllApps', () => {
|
|
|
339
339
|
// No entry, routes, or envParser - will return empty
|
|
340
340
|
},
|
|
341
341
|
web: {
|
|
342
|
-
type: '
|
|
342
|
+
type: 'web',
|
|
343
343
|
path: 'apps/web',
|
|
344
344
|
port: 3001,
|
|
345
345
|
dependencies: ['api', 'auth'],
|
package/src/deploy/domain.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getPublicEnvPrefix } from '../workspace/index.js';
|
|
1
2
|
import type {
|
|
2
3
|
DokployWorkspaceConfig,
|
|
3
4
|
NormalizedAppConfig,
|
|
@@ -71,7 +72,7 @@ export function isMainFrontendApp(
|
|
|
71
72
|
app: NormalizedAppConfig,
|
|
72
73
|
allApps: Record<string, NormalizedAppConfig>,
|
|
73
74
|
): boolean {
|
|
74
|
-
if (app.type !== '
|
|
75
|
+
if (app.type !== 'web') {
|
|
75
76
|
return false;
|
|
76
77
|
}
|
|
77
78
|
|
|
@@ -82,7 +83,7 @@ export function isMainFrontendApp(
|
|
|
82
83
|
|
|
83
84
|
// Otherwise, check if this is the first frontend
|
|
84
85
|
for (const [name, a] of Object.entries(allApps)) {
|
|
85
|
-
if (a.type === '
|
|
86
|
+
if (a.type === 'web') {
|
|
86
87
|
return name === appName;
|
|
87
88
|
}
|
|
88
89
|
}
|
|
@@ -91,9 +92,13 @@ export function isMainFrontendApp(
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/**
|
|
94
|
-
* Generate public URL build args for a
|
|
95
|
+
* Generate public URL build args for a web/mobile app based on its dependencies.
|
|
95
96
|
*
|
|
96
|
-
*
|
|
97
|
+
* The prefix is chosen by the app's framework (NEXT_PUBLIC_, VITE_,
|
|
98
|
+
* EXPO_PUBLIC_, or none for Remix). Apps without a public prefix get no
|
|
99
|
+
* build args — they should fetch URLs at runtime instead.
|
|
100
|
+
*
|
|
101
|
+
* @param app - The web/mobile app configuration
|
|
97
102
|
* @param deployedUrls - Map of app name to deployed public URL
|
|
98
103
|
* @returns Array of build args like 'NEXT_PUBLIC_API_URL=https://api.example.com'
|
|
99
104
|
*/
|
|
@@ -101,14 +106,14 @@ export function generatePublicUrlBuildArgs(
|
|
|
101
106
|
app: NormalizedAppConfig,
|
|
102
107
|
deployedUrls: Record<string, string>,
|
|
103
108
|
): string[] {
|
|
104
|
-
const
|
|
109
|
+
const prefix = getPublicEnvPrefix(app);
|
|
110
|
+
if (!prefix) return [];
|
|
105
111
|
|
|
112
|
+
const buildArgs: string[] = [];
|
|
106
113
|
for (const dep of app.dependencies) {
|
|
107
114
|
const publicUrl = deployedUrls[dep];
|
|
108
115
|
if (publicUrl) {
|
|
109
|
-
|
|
110
|
-
const envVarName = `NEXT_PUBLIC_${dep.toUpperCase()}_URL`;
|
|
111
|
-
buildArgs.push(`${envVarName}=${publicUrl}`);
|
|
116
|
+
buildArgs.push(`${prefix}${dep.toUpperCase()}_URL=${publicUrl}`);
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
119
|
|
|
@@ -118,9 +123,12 @@ export function generatePublicUrlBuildArgs(
|
|
|
118
123
|
/**
|
|
119
124
|
* Get public URL arg names from app dependencies.
|
|
120
125
|
*
|
|
121
|
-
* @param app - The
|
|
122
|
-
* @returns Array of arg names like 'NEXT_PUBLIC_API_URL'
|
|
126
|
+
* @param app - The web/mobile app configuration
|
|
127
|
+
* @returns Array of arg names like 'NEXT_PUBLIC_API_URL', or [] for frameworks
|
|
128
|
+
* without a public prefix (e.g. Remix).
|
|
123
129
|
*/
|
|
124
130
|
export function getPublicUrlArgNames(app: NormalizedAppConfig): string[] {
|
|
125
|
-
|
|
131
|
+
const prefix = getPublicEnvPrefix(app);
|
|
132
|
+
if (!prefix) return [];
|
|
133
|
+
return app.dependencies.map((dep) => `${prefix}${dep.toUpperCase()}_URL`);
|
|
126
134
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { randomBytes } from 'node:crypto';
|
|
10
10
|
import type { StageSecrets } from '../secrets/types';
|
|
11
|
+
import { stripPublicPrefix } from '../workspace/publicEnv';
|
|
11
12
|
import type { NormalizedAppConfig } from '../workspace/types';
|
|
12
13
|
import {
|
|
13
14
|
type AppDbCredentials,
|
|
@@ -208,17 +209,11 @@ export function resolveEnvVar(
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
// Check dependency URLs (e.g., AUTH_URL -> dependencyUrls.auth)
|
|
211
|
-
// Also supports
|
|
212
|
+
// Also supports any known public prefix (NEXT_PUBLIC_, VITE_, ...) so
|
|
213
|
+
// VITE_AUTH_URL and NEXT_PUBLIC_AUTH_URL both resolve to dependencyUrls.auth.
|
|
212
214
|
if (context.dependencyUrls && varName.endsWith('_URL')) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (varName.startsWith('NEXT_PUBLIC_')) {
|
|
216
|
-
// NEXT_PUBLIC_AUTH_URL -> auth
|
|
217
|
-
depName = varName.slice(12, -4).toLowerCase();
|
|
218
|
-
} else {
|
|
219
|
-
// AUTH_URL -> auth
|
|
220
|
-
depName = varName.slice(0, -4).toLowerCase();
|
|
221
|
-
}
|
|
215
|
+
const stripped = stripPublicPrefix(varName) ?? varName;
|
|
216
|
+
const depName = stripped.slice(0, -4).toLowerCase();
|
|
222
217
|
|
|
223
218
|
if (context.dependencyUrls[depName]) {
|
|
224
219
|
return context.dependencyUrls[depName];
|
package/src/deploy/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ import { readStageSecrets } from '../secrets/storage.js';
|
|
|
61
61
|
import {
|
|
62
62
|
getAppBuildOrder,
|
|
63
63
|
getDeployTargetError,
|
|
64
|
+
getPublicEnvPrefix,
|
|
64
65
|
isDeployTargetSupported,
|
|
65
66
|
} from '../workspace/index.js';
|
|
66
67
|
import type { NormalizedWorkspace } from '../workspace/types.js';
|
|
@@ -1201,11 +1202,22 @@ export async function workspaceDeployCommand(
|
|
|
1201
1202
|
// ==================================================================
|
|
1202
1203
|
// Separate apps by type for two-phase deployment
|
|
1203
1204
|
// ==================================================================
|
|
1205
|
+
// Mobile apps deploy via their own toolchain (e.g. EAS Build for Expo)
|
|
1206
|
+
// — skip them in the Dokploy deploy phases.
|
|
1207
|
+
const skippedMobileApps = appsToDeployNames.filter(
|
|
1208
|
+
(name) => workspace.apps[name]!.type === 'mobile',
|
|
1209
|
+
);
|
|
1210
|
+
if (skippedMobileApps.length > 0) {
|
|
1211
|
+
logger.log(
|
|
1212
|
+
`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(', ')}`,
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1204
1216
|
const backendApps = appsToDeployNames.filter(
|
|
1205
1217
|
(name) => workspace.apps[name]!.type === 'backend',
|
|
1206
1218
|
);
|
|
1207
1219
|
const frontendApps = appsToDeployNames.filter(
|
|
1208
|
-
(name) => workspace.apps[name]!.type === '
|
|
1220
|
+
(name) => workspace.apps[name]!.type === 'web',
|
|
1209
1221
|
);
|
|
1210
1222
|
|
|
1211
1223
|
// ==================================================================
|
|
@@ -1661,7 +1673,8 @@ export async function workspaceDeployCommand(
|
|
|
1661
1673
|
dependencyUrls,
|
|
1662
1674
|
};
|
|
1663
1675
|
|
|
1664
|
-
// Resolve all env vars BEFORE Docker build (
|
|
1676
|
+
// Resolve all env vars BEFORE Docker build (public-prefixed vars
|
|
1677
|
+
// must be present at bundler build time so they get inlined).
|
|
1665
1678
|
const sniffedVars = sniffedApps.get(appName)?.requiredEnvVars ?? [];
|
|
1666
1679
|
const { valid, missing, resolved } = validateEnvVars(
|
|
1667
1680
|
sniffedVars,
|
|
@@ -1678,14 +1691,18 @@ export async function workspaceDeployCommand(
|
|
|
1678
1691
|
);
|
|
1679
1692
|
}
|
|
1680
1693
|
|
|
1681
|
-
// Build args:
|
|
1694
|
+
// Build args: only the framework's public-prefixed vars get baked
|
|
1695
|
+
// into the bundle. Server-only vars stay as runtime env.
|
|
1696
|
+
const publicPrefix = getPublicEnvPrefix(app.framework);
|
|
1682
1697
|
const buildArgs: string[] = [];
|
|
1683
1698
|
const publicUrlArgNames: string[] = [];
|
|
1684
1699
|
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1700
|
+
if (publicPrefix) {
|
|
1701
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
1702
|
+
if (key.startsWith(publicPrefix)) {
|
|
1703
|
+
buildArgs.push(`${key}=${value}`);
|
|
1704
|
+
publicUrlArgNames.push(key);
|
|
1705
|
+
}
|
|
1689
1706
|
}
|
|
1690
1707
|
}
|
|
1691
1708
|
|
|
@@ -1693,7 +1710,7 @@ export async function workspaceDeployCommand(
|
|
|
1693
1710
|
logger.log(` Build args: ${publicUrlArgNames.join(', ')}`);
|
|
1694
1711
|
}
|
|
1695
1712
|
|
|
1696
|
-
// Build Docker image with
|
|
1713
|
+
// Build Docker image with public-prefixed vars as build args
|
|
1697
1714
|
const imageName = `${workspace.name}-${appName}`;
|
|
1698
1715
|
const imageRef = registry
|
|
1699
1716
|
? `${registry}/${imageName}:${imageTag}`
|
package/src/deploy/sniffer.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
6
|
import type { SniffResult } from '@geekmidas/envkit/sniffer';
|
|
7
7
|
import { normalizeRoutes } from '../workspace/client-generator.js';
|
|
8
|
+
import { getPublicEnvPrefix } from '../workspace/publicEnv.js';
|
|
8
9
|
import type { NormalizedAppConfig } from '../workspace/types.js';
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -116,11 +117,16 @@ export async function sniffAppEnvironment(
|
|
|
116
117
|
const { logWarnings = true, markOptional = false } = options;
|
|
117
118
|
|
|
118
119
|
// 1. Frontend apps - handle dependencies and config sniffing
|
|
119
|
-
if (app.type === '
|
|
120
|
-
// Auto-generate
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
120
|
+
if (app.type === 'web' || app.type === 'mobile') {
|
|
121
|
+
// Auto-generate {prefix}{DEP}_URL from dependencies, where prefix matches
|
|
122
|
+
// the framework's public-var convention (NEXT_PUBLIC_, VITE_, ...).
|
|
123
|
+
// For frameworks without a prefix (e.g. Remix), no dep var is required.
|
|
124
|
+
const publicPrefix = getPublicEnvPrefix(app.framework);
|
|
125
|
+
const depVars = publicPrefix
|
|
126
|
+
? (app.dependencies ?? []).map(
|
|
127
|
+
(dep) => `${publicPrefix}${dep.toUpperCase()}_URL`,
|
|
128
|
+
)
|
|
129
|
+
: [];
|
|
124
130
|
|
|
125
131
|
// If config specified, sniff by importing the file(s)
|
|
126
132
|
// The file calls .parse() at module load, which triggers sniffer to capture vars
|
package/src/deploy/types.ts
CHANGED
|
@@ -455,13 +455,13 @@ describe('Workspace Dev Server', () => {
|
|
|
455
455
|
dependencies: [],
|
|
456
456
|
},
|
|
457
457
|
web: {
|
|
458
|
-
type: '
|
|
458
|
+
type: 'web',
|
|
459
459
|
path: 'apps/web',
|
|
460
460
|
port: 3001,
|
|
461
461
|
dependencies: [],
|
|
462
462
|
},
|
|
463
463
|
admin: {
|
|
464
|
-
type: '
|
|
464
|
+
type: 'web',
|
|
465
465
|
path: 'apps/admin',
|
|
466
466
|
port: 3002,
|
|
467
467
|
dependencies: [],
|
|
@@ -481,7 +481,7 @@ describe('Workspace Dev Server', () => {
|
|
|
481
481
|
dependencies: [],
|
|
482
482
|
},
|
|
483
483
|
web: {
|
|
484
|
-
type: '
|
|
484
|
+
type: 'web',
|
|
485
485
|
path: 'apps/web',
|
|
486
486
|
port: 3000, // Same port as api!
|
|
487
487
|
dependencies: [],
|
|
@@ -512,13 +512,13 @@ describe('Workspace Dev Server', () => {
|
|
|
512
512
|
dependencies: [],
|
|
513
513
|
},
|
|
514
514
|
web: {
|
|
515
|
-
type: '
|
|
515
|
+
type: 'web',
|
|
516
516
|
path: 'apps/web',
|
|
517
517
|
port: 3001,
|
|
518
518
|
dependencies: [],
|
|
519
519
|
},
|
|
520
520
|
admin: {
|
|
521
|
-
type: '
|
|
521
|
+
type: 'web',
|
|
522
522
|
path: 'apps/admin',
|
|
523
523
|
port: 3001, // Conflicts with web
|
|
524
524
|
dependencies: [],
|
|
@@ -554,7 +554,7 @@ describe('Workspace Dev Server', () => {
|
|
|
554
554
|
dependencies: [],
|
|
555
555
|
},
|
|
556
556
|
web: {
|
|
557
|
-
type: '
|
|
557
|
+
type: 'web',
|
|
558
558
|
path: 'apps/web',
|
|
559
559
|
port: 3001,
|
|
560
560
|
dependencies: [],
|
|
@@ -580,7 +580,7 @@ describe('Workspace Dev Server', () => {
|
|
|
580
580
|
dependencies: [],
|
|
581
581
|
},
|
|
582
582
|
web: {
|
|
583
|
-
type: '
|
|
583
|
+
type: 'web',
|
|
584
584
|
path: 'apps/web',
|
|
585
585
|
port: 3002,
|
|
586
586
|
dependencies: ['api', 'auth'],
|
|
@@ -634,7 +634,7 @@ describe('Workspace Dev Server', () => {
|
|
|
634
634
|
dependencies: [],
|
|
635
635
|
},
|
|
636
636
|
web: {
|
|
637
|
-
type: '
|
|
637
|
+
type: 'web',
|
|
638
638
|
path: 'apps/web',
|
|
639
639
|
port: 3001,
|
|
640
640
|
dependencies: ['api'],
|
|
@@ -669,13 +669,13 @@ describe('Workspace Dev Server', () => {
|
|
|
669
669
|
dependencies: ['auth'], // Payments depends on auth
|
|
670
670
|
},
|
|
671
671
|
web: {
|
|
672
|
-
type: '
|
|
672
|
+
type: 'web',
|
|
673
673
|
path: 'apps/web',
|
|
674
674
|
port: 3003,
|
|
675
675
|
dependencies: ['api', 'auth'], // Web depends on api and auth
|
|
676
676
|
},
|
|
677
677
|
admin: {
|
|
678
|
-
type: '
|
|
678
|
+
type: 'web',
|
|
679
679
|
path: 'apps/admin',
|
|
680
680
|
port: 3004,
|
|
681
681
|
dependencies: ['api', 'payments'], // Admin depends on api and payments
|
|
@@ -1056,7 +1056,7 @@ describe('Workspace Dev Server', () => {
|
|
|
1056
1056
|
resolvedDeployTarget: 'dokploy',
|
|
1057
1057
|
},
|
|
1058
1058
|
web: {
|
|
1059
|
-
type: '
|
|
1059
|
+
type: 'web',
|
|
1060
1060
|
path: 'apps/web',
|
|
1061
1061
|
port: 3001,
|
|
1062
1062
|
dependencies: ['api'],
|
package/src/dev/index.ts
CHANGED
|
@@ -688,6 +688,23 @@ const FRONTEND_FRAMEWORKS: Record<FrontendFramework, FrontendFrameworkSpec> = {
|
|
|
688
688
|
dependency: 'vite',
|
|
689
689
|
installCommand: 'pnpm add -D vite',
|
|
690
690
|
},
|
|
691
|
+
'tanstack-start': {
|
|
692
|
+
displayName: 'TanStack Start',
|
|
693
|
+
configFiles: [
|
|
694
|
+
'vite.config.js',
|
|
695
|
+
'vite.config.ts',
|
|
696
|
+
'vite.config.mjs',
|
|
697
|
+
'vite.config.cjs',
|
|
698
|
+
],
|
|
699
|
+
dependency: '@tanstack/react-start',
|
|
700
|
+
installCommand: 'pnpm add @tanstack/react-start @tanstack/react-router',
|
|
701
|
+
},
|
|
702
|
+
expo: {
|
|
703
|
+
displayName: 'Expo',
|
|
704
|
+
configFiles: ['app.config.ts', 'app.config.js', 'app.json'],
|
|
705
|
+
dependency: 'expo',
|
|
706
|
+
installCommand: 'pnpm add expo',
|
|
707
|
+
},
|
|
691
708
|
};
|
|
692
709
|
|
|
693
710
|
/**
|
|
@@ -699,7 +716,18 @@ function detectFrontendFramework(
|
|
|
699
716
|
fullPath: string,
|
|
700
717
|
deps: Record<string, string>,
|
|
701
718
|
): FrontendFramework | undefined {
|
|
702
|
-
|
|
719
|
+
// tanstack-start before vite/remix so its more specific dep wins; vite last
|
|
720
|
+
// because plain vite is the fallback when neither nextjs/remix/tanstack match.
|
|
721
|
+
// Order matters: more specific deps win over general ones.
|
|
722
|
+
// expo first (own dep), then tanstack-start (uses vite), remix (uses vite),
|
|
723
|
+
// nextjs (own dep), and plain vite last as the fallback.
|
|
724
|
+
const order: FrontendFramework[] = [
|
|
725
|
+
'expo',
|
|
726
|
+
'tanstack-start',
|
|
727
|
+
'nextjs',
|
|
728
|
+
'remix',
|
|
729
|
+
'vite',
|
|
730
|
+
];
|
|
703
731
|
for (const name of order) {
|
|
704
732
|
if (deps[FRONTEND_FRAMEWORKS[name].dependency]) {
|
|
705
733
|
return name;
|
|
@@ -828,7 +856,7 @@ export async function validateFrontendApps(
|
|
|
828
856
|
const results: FrontendValidationResult[] = [];
|
|
829
857
|
|
|
830
858
|
for (const [appName, app] of Object.entries(workspace.apps)) {
|
|
831
|
-
if (app.type === '
|
|
859
|
+
if (app.type === 'web') {
|
|
832
860
|
const result = await validateFrontendApp(
|
|
833
861
|
appName,
|
|
834
862
|
app.path,
|
|
@@ -892,13 +920,19 @@ async function workspaceDevCommand(
|
|
|
892
920
|
([_, app]) => app.type === 'backend',
|
|
893
921
|
);
|
|
894
922
|
const frontendApps = Object.entries(workspace.apps).filter(
|
|
895
|
-
([_, app]) => app.type === '
|
|
923
|
+
([_, app]) => app.type === 'web',
|
|
924
|
+
);
|
|
925
|
+
const mobileApps = Object.entries(workspace.apps).filter(
|
|
926
|
+
([_, app]) => app.type === 'mobile',
|
|
896
927
|
);
|
|
897
928
|
|
|
898
929
|
logger.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
930
|
+
const counts = [
|
|
931
|
+
`${backendApps.length} backend`,
|
|
932
|
+
`${frontendApps.length} web`,
|
|
933
|
+
];
|
|
934
|
+
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
935
|
+
logger.log(` ${counts.join(', ')} app(s)`);
|
|
902
936
|
|
|
903
937
|
// Check for port conflicts
|
|
904
938
|
const conflicts = checkPortConflicts(workspace);
|
|
@@ -1002,9 +1036,9 @@ async function workspaceDevCommand(
|
|
|
1002
1036
|
app.dependencies.length > 0
|
|
1003
1037
|
? ` (depends on: ${app.dependencies.join(', ')})`
|
|
1004
1038
|
: '';
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
);
|
|
1039
|
+
const icon =
|
|
1040
|
+
app.type === 'backend' ? '🔧' : app.type === 'mobile' ? '📱' : '🌐';
|
|
1041
|
+
logger.log(` ${icon} ${appName} → http://localhost:${app.port}${deps}`);
|
|
1008
1042
|
}
|
|
1009
1043
|
|
|
1010
1044
|
// Find the config file path for GKM_CONFIG_PATH
|
|
@@ -647,7 +647,7 @@ describe('generateWorkspaceCompose', () => {
|
|
|
647
647
|
resolvedDeployTarget: 'dokploy',
|
|
648
648
|
},
|
|
649
649
|
web: {
|
|
650
|
-
type: '
|
|
650
|
+
type: 'web',
|
|
651
651
|
path: 'apps/web',
|
|
652
652
|
port: 3001,
|
|
653
653
|
dependencies: ['api'],
|
|
@@ -793,7 +793,7 @@ describe('generateWorkspaceCompose', () => {
|
|
|
793
793
|
resolvedDeployTarget: 'dokploy',
|
|
794
794
|
},
|
|
795
795
|
web: {
|
|
796
|
-
type: '
|
|
796
|
+
type: 'web',
|
|
797
797
|
path: 'apps/web',
|
|
798
798
|
port: 3001,
|
|
799
799
|
dependencies: ['api', 'auth'],
|
|
@@ -849,7 +849,7 @@ describe('generateWorkspaceCompose', () => {
|
|
|
849
849
|
const workspace = createWorkspace({
|
|
850
850
|
apps: {
|
|
851
851
|
web: {
|
|
852
|
-
type: '
|
|
852
|
+
type: 'web',
|
|
853
853
|
path: 'apps/web',
|
|
854
854
|
port: 3001,
|
|
855
855
|
dependencies: [],
|
|
@@ -1016,7 +1016,7 @@ describe('generateWorkspaceCompose', () => {
|
|
|
1016
1016
|
const workspace = createWorkspace({
|
|
1017
1017
|
apps: {
|
|
1018
1018
|
web: {
|
|
1019
|
-
type: '
|
|
1019
|
+
type: 'web',
|
|
1020
1020
|
path: 'apps/web',
|
|
1021
1021
|
port: 3001,
|
|
1022
1022
|
dependencies: [],
|
package/src/docker/compose.ts
CHANGED
|
@@ -444,8 +444,10 @@ export function generateWorkspaceCompose(
|
|
|
444
444
|
services:
|
|
445
445
|
`;
|
|
446
446
|
|
|
447
|
-
// Generate service for each app
|
|
447
|
+
// Generate service for each app (mobile apps deploy via their own
|
|
448
|
+
// toolchain — no compose service)
|
|
448
449
|
for (const [appName, app] of apps) {
|
|
450
|
+
if (app.type === 'mobile') continue;
|
|
449
451
|
yaml += generateAppService(appName, app, apps, {
|
|
450
452
|
registry,
|
|
451
453
|
projectName: workspace.name,
|
|
@@ -698,9 +700,9 @@ function generateAppService(
|
|
|
698
700
|
const imageRef = registry ? `\${REGISTRY:-${registry}}/` : '';
|
|
699
701
|
|
|
700
702
|
// Health check path - frontends use /, backends use /health
|
|
701
|
-
const healthCheckPath = app.type === '
|
|
703
|
+
const healthCheckPath = app.type === 'web' ? '/' : '/health';
|
|
702
704
|
const healthCheckCmd =
|
|
703
|
-
app.type === '
|
|
705
|
+
app.type === 'web'
|
|
704
706
|
? `["CMD", "wget", "-q", "--spider", "http://localhost:${app.port}/"]`
|
|
705
707
|
: `["CMD", "wget", "-q", "--spider", "http://localhost:${app.port}${healthCheckPath}"]`;
|
|
706
708
|
|