@geekmidas/cli 0.10.0 → 0.12.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/README.md +525 -0
- package/dist/bundler-DRXCw_YR.mjs +70 -0
- package/dist/bundler-DRXCw_YR.mjs.map +1 -0
- package/dist/bundler-WsEvH_b2.cjs +71 -0
- package/dist/bundler-WsEvH_b2.cjs.map +1 -0
- package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
- package/dist/config-AmInkU7k.cjs.map +1 -0
- package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
- package/dist/config-DYULeEv8.mjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +1 -1
- package/dist/encryption-C8H-38Yy.mjs +42 -0
- package/dist/encryption-C8H-38Yy.mjs.map +1 -0
- package/dist/encryption-Dyf_r1h-.cjs +44 -0
- package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
- package/dist/index.cjs +2116 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2134 -192
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
- package/dist/openapi-BfFlOBCG.mjs.map +1 -0
- package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
- package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
- package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
- package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
- package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
- package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.d.cts.map +1 -1
- package/dist/openapi-react-query.d.mts.map +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/storage-BUYQJgz7.cjs +4 -0
- package/dist/storage-BXoJvmv2.cjs +149 -0
- package/dist/storage-BXoJvmv2.cjs.map +1 -0
- package/dist/storage-C9PU_30f.mjs +101 -0
- package/dist/storage-C9PU_30f.mjs.map +1 -0
- package/dist/storage-DLJAYxzJ.mjs +3 -0
- package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
- package/dist/types-BR0M2v_c.d.mts.map +1 -0
- package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
- package/dist/types-BhkZc-vm.d.cts.map +1 -0
- package/examples/cron-example.ts +27 -27
- package/examples/env.ts +27 -27
- package/examples/function-example.ts +31 -31
- package/examples/gkm.config.json +20 -20
- package/examples/gkm.config.ts +8 -8
- package/examples/gkm.minimal.config.json +5 -5
- package/examples/gkm.production.config.json +25 -25
- package/examples/logger.ts +2 -2
- package/package.json +6 -6
- package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
- package/src/__tests__/config.spec.ts +55 -55
- package/src/__tests__/loadEnvFiles.spec.ts +93 -93
- package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
- package/src/__tests__/openapi-react-query.spec.ts +497 -497
- package/src/__tests__/openapi.spec.ts +428 -428
- package/src/__tests__/test-helpers.ts +76 -76
- package/src/auth/__tests__/credentials.spec.ts +204 -0
- package/src/auth/__tests__/index.spec.ts +168 -0
- package/src/auth/credentials.ts +187 -0
- package/src/auth/index.ts +226 -0
- package/src/build/__tests__/index-new.spec.ts +474 -474
- package/src/build/__tests__/manifests.spec.ts +333 -333
- package/src/build/bundler.ts +141 -0
- package/src/build/endpoint-analyzer.ts +236 -0
- package/src/build/handler-templates.ts +1253 -0
- package/src/build/index.ts +250 -179
- package/src/build/manifests.ts +52 -52
- package/src/build/providerResolver.ts +145 -145
- package/src/build/types.ts +64 -43
- package/src/config.ts +39 -39
- package/src/deploy/__tests__/docker.spec.ts +111 -0
- package/src/deploy/__tests__/dokploy.spec.ts +245 -0
- package/src/deploy/__tests__/init.spec.ts +662 -0
- package/src/deploy/docker.ts +128 -0
- package/src/deploy/dokploy.ts +204 -0
- package/src/deploy/index.ts +136 -0
- package/src/deploy/init.ts +484 -0
- package/src/deploy/types.ts +48 -0
- package/src/dev/__tests__/index.spec.ts +266 -266
- package/src/dev/index.ts +647 -601
- package/src/docker/__tests__/compose.spec.ts +531 -0
- package/src/docker/__tests__/templates.spec.ts +280 -0
- package/src/docker/compose.ts +273 -0
- package/src/docker/index.ts +230 -0
- package/src/docker/templates.ts +446 -0
- package/src/generators/CronGenerator.ts +72 -72
- package/src/generators/EndpointGenerator.ts +699 -398
- package/src/generators/FunctionGenerator.ts +84 -84
- package/src/generators/Generator.ts +72 -72
- package/src/generators/OpenApiTsGenerator.ts +577 -577
- package/src/generators/SubscriberGenerator.ts +124 -124
- package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
- package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
- package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
- package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
- package/src/generators/index.ts +4 -4
- package/src/index.ts +623 -201
- package/src/init/__tests__/generators.spec.ts +334 -334
- package/src/init/__tests__/init.spec.ts +332 -332
- package/src/init/__tests__/utils.spec.ts +89 -89
- package/src/init/generators/config.ts +175 -175
- package/src/init/generators/docker.ts +41 -41
- package/src/init/generators/env.ts +72 -72
- package/src/init/generators/index.ts +1 -1
- package/src/init/generators/models.ts +64 -64
- package/src/init/generators/monorepo.ts +161 -161
- package/src/init/generators/package.ts +71 -71
- package/src/init/generators/source.ts +6 -6
- package/src/init/index.ts +203 -208
- package/src/init/templates/api.ts +115 -115
- package/src/init/templates/index.ts +75 -75
- package/src/init/templates/minimal.ts +98 -98
- package/src/init/templates/serverless.ts +89 -89
- package/src/init/templates/worker.ts +98 -98
- package/src/init/utils.ts +54 -56
- package/src/openapi-react-query.ts +194 -194
- package/src/openapi.ts +63 -63
- package/src/secrets/__tests__/encryption.spec.ts +226 -0
- package/src/secrets/__tests__/generator.spec.ts +319 -0
- package/src/secrets/__tests__/index.spec.ts +91 -0
- package/src/secrets/__tests__/storage.spec.ts +403 -0
- package/src/secrets/encryption.ts +91 -0
- package/src/secrets/generator.ts +164 -0
- package/src/secrets/index.ts +383 -0
- package/src/secrets/storage.ts +134 -0
- package/src/secrets/types.ts +53 -0
- package/src/types.ts +295 -176
- package/tsdown.config.ts +11 -8
- package/dist/config-BrkUalUh.mjs.map +0 -1
- package/dist/config-C9aXOHBe.cjs.map +0 -1
- package/dist/openapi-BeHLKcwP.cjs.map +0 -1
- package/dist/openapi-CZLI4QTr.mjs.map +0 -1
- package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
- package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
- package/dist/types-DXgiA1sF.d.mts.map +0 -1
- package/dist/types-b-vwGpqc.d.cts.map +0 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import type { GkmConfig } from '../types';
|
|
3
|
+
import type { DeployResult, DockerDeployConfig } from './types';
|
|
4
|
+
|
|
5
|
+
const logger = console;
|
|
6
|
+
|
|
7
|
+
export interface DockerDeployOptions {
|
|
8
|
+
/** Deployment stage */
|
|
9
|
+
stage: string;
|
|
10
|
+
/** Image tag */
|
|
11
|
+
tag: string;
|
|
12
|
+
/** Skip pushing to registry */
|
|
13
|
+
skipPush?: boolean;
|
|
14
|
+
/** Master key from build */
|
|
15
|
+
masterKey?: string;
|
|
16
|
+
/** Docker config from gkm.config */
|
|
17
|
+
config: DockerDeployConfig;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the full image reference
|
|
22
|
+
*/
|
|
23
|
+
export function getImageRef(
|
|
24
|
+
registry: string | undefined,
|
|
25
|
+
imageName: string,
|
|
26
|
+
tag: string,
|
|
27
|
+
): string {
|
|
28
|
+
if (registry) {
|
|
29
|
+
return `${registry}/${imageName}:${tag}`;
|
|
30
|
+
}
|
|
31
|
+
return `${imageName}:${tag}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build Docker image
|
|
36
|
+
*/
|
|
37
|
+
async function buildImage(imageRef: string): Promise<void> {
|
|
38
|
+
logger.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
execSync(
|
|
42
|
+
`DOCKER_BUILDKIT=1 docker build -f .gkm/docker/Dockerfile -t ${imageRef} .`,
|
|
43
|
+
{
|
|
44
|
+
cwd: process.cwd(),
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
env: { ...process.env, DOCKER_BUILDKIT: '1' },
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
logger.log(`✅ Image built: ${imageRef}`);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Failed to build Docker image: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Push Docker image to registry
|
|
59
|
+
*/
|
|
60
|
+
async function pushImage(imageRef: string): Promise<void> {
|
|
61
|
+
logger.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
execSync(`docker push ${imageRef}`, {
|
|
65
|
+
cwd: process.cwd(),
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
});
|
|
68
|
+
logger.log(`✅ Image pushed: ${imageRef}`);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Failed to push Docker image: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deploy using Docker (build and optionally push image)
|
|
78
|
+
*/
|
|
79
|
+
export async function deployDocker(
|
|
80
|
+
options: DockerDeployOptions,
|
|
81
|
+
): Promise<DeployResult> {
|
|
82
|
+
const { stage, tag, skipPush, masterKey, config } = options;
|
|
83
|
+
|
|
84
|
+
const imageName = config.imageName ?? 'app';
|
|
85
|
+
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
86
|
+
|
|
87
|
+
// Build image
|
|
88
|
+
await buildImage(imageRef);
|
|
89
|
+
|
|
90
|
+
// Push to registry if not skipped
|
|
91
|
+
if (!skipPush) {
|
|
92
|
+
if (!config.registry) {
|
|
93
|
+
logger.warn(
|
|
94
|
+
'\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts',
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
await pushImage(imageRef);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Output deployment info
|
|
102
|
+
logger.log('\n✅ Docker deployment ready!');
|
|
103
|
+
logger.log(`\n📋 Deployment details:`);
|
|
104
|
+
logger.log(` Image: ${imageRef}`);
|
|
105
|
+
logger.log(` Stage: ${stage}`);
|
|
106
|
+
|
|
107
|
+
if (masterKey) {
|
|
108
|
+
logger.log(`\n🔐 Deploy with this environment variable:`);
|
|
109
|
+
logger.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
110
|
+
logger.log('\n Example docker run:');
|
|
111
|
+
logger.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
imageRef,
|
|
116
|
+
masterKey,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolve Docker deploy config from gkm config
|
|
122
|
+
*/
|
|
123
|
+
export function resolveDockerConfig(config: GkmConfig): DockerDeployConfig {
|
|
124
|
+
return {
|
|
125
|
+
registry: config.docker?.registry,
|
|
126
|
+
imageName: config.docker?.imageName,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { getDokployToken } from '../auth';
|
|
2
|
+
import type { DeployResult, DokployDeployConfig } from './types';
|
|
3
|
+
|
|
4
|
+
const logger = console;
|
|
5
|
+
|
|
6
|
+
export interface DokployDeployOptions {
|
|
7
|
+
/** Deployment stage */
|
|
8
|
+
stage: string;
|
|
9
|
+
/** Image tag */
|
|
10
|
+
tag: string;
|
|
11
|
+
/** Image reference */
|
|
12
|
+
imageRef: string;
|
|
13
|
+
/** Master key from build */
|
|
14
|
+
masterKey?: string;
|
|
15
|
+
/** Dokploy config from gkm.config */
|
|
16
|
+
config: DokployDeployConfig;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DokployErrorResponse {
|
|
20
|
+
message: string;
|
|
21
|
+
code?: string;
|
|
22
|
+
issues?: Array<{ message: string }>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the Dokploy API token from stored credentials or environment
|
|
27
|
+
*/
|
|
28
|
+
async function getApiToken(): Promise<string> {
|
|
29
|
+
const token = await getDokployToken();
|
|
30
|
+
if (!token) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
'Dokploy credentials not found.\n' +
|
|
33
|
+
'Run "gkm login --service dokploy" to authenticate, or set DOKPLOY_API_TOKEN.',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return token;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Make a request to the Dokploy API
|
|
41
|
+
*/
|
|
42
|
+
async function dokployRequest<T>(
|
|
43
|
+
endpoint: string,
|
|
44
|
+
baseUrl: string,
|
|
45
|
+
token: string,
|
|
46
|
+
body: Record<string, unknown>,
|
|
47
|
+
): Promise<T> {
|
|
48
|
+
const url = `${baseUrl}/api/${endpoint}`;
|
|
49
|
+
|
|
50
|
+
const response = await fetch(url, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: {
|
|
53
|
+
'Content-Type': 'application/json',
|
|
54
|
+
Authorization: `Bearer ${token}`,
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify(body),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
let errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const errorBody = (await response.json()) as DokployErrorResponse;
|
|
64
|
+
if (errorBody.message) {
|
|
65
|
+
errorMessage = `Dokploy API error: ${errorBody.message}`;
|
|
66
|
+
}
|
|
67
|
+
if (errorBody.issues?.length) {
|
|
68
|
+
errorMessage += `\n Issues: ${errorBody.issues.map((i) => i.message).join(', ')}`;
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// Ignore JSON parse errors
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw new Error(errorMessage);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return response.json() as Promise<T>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Update application environment variables
|
|
82
|
+
*/
|
|
83
|
+
async function updateEnvironment(
|
|
84
|
+
baseUrl: string,
|
|
85
|
+
token: string,
|
|
86
|
+
applicationId: string,
|
|
87
|
+
envVars: Record<string, string>,
|
|
88
|
+
): Promise<void> {
|
|
89
|
+
logger.log(' Updating environment variables...');
|
|
90
|
+
|
|
91
|
+
// Convert env vars to the format Dokploy expects (KEY=VALUE per line)
|
|
92
|
+
const envString = Object.entries(envVars)
|
|
93
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
94
|
+
.join('\n');
|
|
95
|
+
|
|
96
|
+
await dokployRequest('application.update', baseUrl, token, {
|
|
97
|
+
applicationId,
|
|
98
|
+
env: envString,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
logger.log(' ✓ Environment variables updated');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Trigger application deployment
|
|
106
|
+
*/
|
|
107
|
+
async function triggerDeploy(
|
|
108
|
+
baseUrl: string,
|
|
109
|
+
token: string,
|
|
110
|
+
applicationId: string,
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
logger.log(' Triggering deployment...');
|
|
113
|
+
|
|
114
|
+
await dokployRequest('application.deploy', baseUrl, token, {
|
|
115
|
+
applicationId,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
logger.log(' ✓ Deployment triggered');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Deploy to Dokploy
|
|
123
|
+
*/
|
|
124
|
+
export async function deployDokploy(
|
|
125
|
+
options: DokployDeployOptions,
|
|
126
|
+
): Promise<DeployResult> {
|
|
127
|
+
const { stage, imageRef, masterKey, config } = options;
|
|
128
|
+
|
|
129
|
+
logger.log(`\n🎯 Deploying to Dokploy...`);
|
|
130
|
+
logger.log(` Endpoint: ${config.endpoint}`);
|
|
131
|
+
logger.log(` Application: ${config.applicationId}`);
|
|
132
|
+
|
|
133
|
+
const token = await getApiToken();
|
|
134
|
+
|
|
135
|
+
// Prepare environment variables
|
|
136
|
+
const envVars: Record<string, string> = {};
|
|
137
|
+
|
|
138
|
+
if (masterKey) {
|
|
139
|
+
envVars.GKM_MASTER_KEY = masterKey;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Update environment if we have variables to set
|
|
143
|
+
if (Object.keys(envVars).length > 0) {
|
|
144
|
+
await updateEnvironment(
|
|
145
|
+
config.endpoint,
|
|
146
|
+
token,
|
|
147
|
+
config.applicationId,
|
|
148
|
+
envVars,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Trigger deployment
|
|
153
|
+
await triggerDeploy(config.endpoint, token, config.applicationId);
|
|
154
|
+
|
|
155
|
+
logger.log('\n✅ Dokploy deployment initiated!');
|
|
156
|
+
logger.log(`\n📋 Deployment details:`);
|
|
157
|
+
logger.log(` Image: ${imageRef}`);
|
|
158
|
+
logger.log(` Stage: ${stage}`);
|
|
159
|
+
logger.log(` Application ID: ${config.applicationId}`);
|
|
160
|
+
|
|
161
|
+
if (masterKey) {
|
|
162
|
+
logger.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Construct the probable deployment URL
|
|
166
|
+
const deploymentUrl = `${config.endpoint}/project/${config.projectId}`;
|
|
167
|
+
logger.log(`\n🔗 View deployment: ${deploymentUrl}`);
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
imageRef,
|
|
171
|
+
masterKey,
|
|
172
|
+
url: deploymentUrl,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Validate Dokploy configuration
|
|
178
|
+
*/
|
|
179
|
+
export function validateDokployConfig(
|
|
180
|
+
config: Partial<DokployDeployConfig> | undefined,
|
|
181
|
+
): config is DokployDeployConfig {
|
|
182
|
+
if (!config) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const required = ['endpoint', 'projectId', 'applicationId'] as const;
|
|
187
|
+
const missing = required.filter((key) => !config[key]);
|
|
188
|
+
|
|
189
|
+
if (missing.length > 0) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Missing Dokploy configuration: ${missing.join(', ')}\n` +
|
|
192
|
+
'Configure in gkm.config.ts:\n' +
|
|
193
|
+
' providers: {\n' +
|
|
194
|
+
' dokploy: {\n' +
|
|
195
|
+
" endpoint: 'https://dokploy.example.com',\n" +
|
|
196
|
+
" projectId: 'proj_xxx',\n" +
|
|
197
|
+
" applicationId: 'app_xxx',\n" +
|
|
198
|
+
' },\n' +
|
|
199
|
+
' }',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { buildCommand } from '../build/index';
|
|
2
|
+
import { loadConfig } from '../config';
|
|
3
|
+
import { deployDocker, resolveDockerConfig } from './docker';
|
|
4
|
+
import { deployDokploy, validateDokployConfig } from './dokploy';
|
|
5
|
+
import type { DeployOptions, DeployProvider, DeployResult } from './types';
|
|
6
|
+
|
|
7
|
+
const logger = console;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate image tag from stage and timestamp
|
|
11
|
+
*/
|
|
12
|
+
function generateTag(stage: string): string {
|
|
13
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
14
|
+
return `${stage}-${timestamp}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Main deploy command
|
|
19
|
+
*/
|
|
20
|
+
export async function deployCommand(
|
|
21
|
+
options: DeployOptions,
|
|
22
|
+
): Promise<DeployResult> {
|
|
23
|
+
const { provider, stage, tag, skipPush, skipBuild } = options;
|
|
24
|
+
|
|
25
|
+
logger.log(`\n🚀 Deploying to ${provider}...`);
|
|
26
|
+
logger.log(` Stage: ${stage}`);
|
|
27
|
+
|
|
28
|
+
// Load config
|
|
29
|
+
const config = await loadConfig();
|
|
30
|
+
|
|
31
|
+
// Generate tag if not provided
|
|
32
|
+
const imageTag = tag ?? generateTag(stage);
|
|
33
|
+
logger.log(` Tag: ${imageTag}`);
|
|
34
|
+
|
|
35
|
+
// Build for production with secrets injection (unless skipped)
|
|
36
|
+
let masterKey: string | undefined;
|
|
37
|
+
if (!skipBuild) {
|
|
38
|
+
logger.log(`\n📦 Building for production...`);
|
|
39
|
+
const buildResult = await buildCommand({
|
|
40
|
+
provider: 'server',
|
|
41
|
+
production: true,
|
|
42
|
+
stage,
|
|
43
|
+
});
|
|
44
|
+
masterKey = buildResult.masterKey;
|
|
45
|
+
} else {
|
|
46
|
+
logger.log(`\n⏭️ Skipping build (--skip-build)`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Resolve docker config for image reference
|
|
50
|
+
const dockerConfig = resolveDockerConfig(config);
|
|
51
|
+
const imageName = dockerConfig.imageName ?? 'app';
|
|
52
|
+
const registry = dockerConfig.registry;
|
|
53
|
+
const imageRef = registry
|
|
54
|
+
? `${registry}/${imageName}:${imageTag}`
|
|
55
|
+
: `${imageName}:${imageTag}`;
|
|
56
|
+
|
|
57
|
+
// Deploy based on provider
|
|
58
|
+
let result: DeployResult;
|
|
59
|
+
|
|
60
|
+
switch (provider) {
|
|
61
|
+
case 'docker': {
|
|
62
|
+
result = await deployDocker({
|
|
63
|
+
stage,
|
|
64
|
+
tag: imageTag,
|
|
65
|
+
skipPush,
|
|
66
|
+
masterKey,
|
|
67
|
+
config: dockerConfig,
|
|
68
|
+
});
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case 'dokploy': {
|
|
73
|
+
// Validate Dokploy config
|
|
74
|
+
const dokployConfigRaw = config.providers?.dokploy;
|
|
75
|
+
if (typeof dokployConfigRaw === 'boolean' || !dokployConfigRaw) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
'Dokploy provider requires configuration.\n' +
|
|
78
|
+
'Configure in gkm.config.ts:\n' +
|
|
79
|
+
' providers: {\n' +
|
|
80
|
+
' dokploy: {\n' +
|
|
81
|
+
" endpoint: 'https://dokploy.example.com',\n" +
|
|
82
|
+
" projectId: 'proj_xxx',\n" +
|
|
83
|
+
" applicationId: 'app_xxx',\n" +
|
|
84
|
+
' },\n' +
|
|
85
|
+
' }',
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Validate required fields (throws if missing)
|
|
90
|
+
validateDokployConfig(dokployConfigRaw);
|
|
91
|
+
const dokployConfig = dokployConfigRaw;
|
|
92
|
+
|
|
93
|
+
// First build and push the Docker image
|
|
94
|
+
await deployDocker({
|
|
95
|
+
stage,
|
|
96
|
+
tag: imageTag,
|
|
97
|
+
skipPush: false, // Dokploy needs the image in registry
|
|
98
|
+
masterKey,
|
|
99
|
+
config: {
|
|
100
|
+
registry: dokployConfig.registry ?? dockerConfig.registry,
|
|
101
|
+
imageName: dockerConfig.imageName,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Then trigger Dokploy deployment
|
|
106
|
+
result = await deployDokploy({
|
|
107
|
+
stage,
|
|
108
|
+
tag: imageTag,
|
|
109
|
+
imageRef,
|
|
110
|
+
masterKey,
|
|
111
|
+
config: dokployConfig,
|
|
112
|
+
});
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case 'aws-lambda': {
|
|
117
|
+
logger.log('\n⚠️ AWS Lambda deployment is not yet implemented.');
|
|
118
|
+
logger.log(' Use SST or AWS CDK for Lambda deployments.');
|
|
119
|
+
result = { imageRef, masterKey };
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
default: {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Unknown deploy provider: ${provider}\n` +
|
|
126
|
+
'Supported providers: docker, dokploy, aws-lambda',
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
logger.log('\n✅ Deployment complete!');
|
|
132
|
+
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type { DeployOptions, DeployProvider, DeployResult };
|