@akash-chowdhury-24/deployhub 2.0.3 → 2.0.5
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 +50 -4
- package/package.json +1 -1
- package/src/artifact/engine.js +13 -0
- package/src/commands/doctor.js +154 -0
- package/src/commands/init.js +28 -2
- package/src/core/stages.js +13 -0
- package/src/deployment/deployment-env.js +9 -0
- package/src/deployment/providers/kubernetes.js +6 -4
- package/src/deployment/providers/ssh.js +75 -10
- package/src/utils/dockerfile.js +486 -0
- package/src/utils/github-actions.js +56 -1
- package/src/utils/kubernetes-manifests.js +168 -0
- package/src/utils/nginx.js +60 -3
- package/src/utils/scaffold.js +192 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dockerfile generation based on detected framework / project settings.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const GENERATED_HEADER =
|
|
6
|
+
'# Generated by DeployHub — review exposed port and start command before deploying.\n';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} projectName
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
export function sanitizeDockerProjectName(projectName) {
|
|
13
|
+
return projectName.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('../core/config.js').DeployHubConfig} config
|
|
18
|
+
* @returns {{ framework: string, buildCommand: string|null, buildOutput: string, startCommand: string|null, port: number, projectType: string }}
|
|
19
|
+
*/
|
|
20
|
+
export function resolveDockerSettings(config) {
|
|
21
|
+
const projectType = config.projectType || 'frontend';
|
|
22
|
+
|
|
23
|
+
if (projectType === 'both' && config.backend) {
|
|
24
|
+
return {
|
|
25
|
+
framework: config.backend.framework || 'express',
|
|
26
|
+
buildCommand: config.backend.buildCommand ?? null,
|
|
27
|
+
buildOutput: config.backend.buildOutput || '.',
|
|
28
|
+
startCommand: config.backend.startCommand || 'npm start',
|
|
29
|
+
port: config.backend.port || 3000,
|
|
30
|
+
projectType,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
framework: config.framework || 'express',
|
|
36
|
+
buildCommand: config.buildCommand ?? null,
|
|
37
|
+
buildOutput: config.buildOutput || 'dist',
|
|
38
|
+
startCommand: config.startCommand || 'npm start',
|
|
39
|
+
port: config.port || 3000,
|
|
40
|
+
projectType,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {import('../core/config.js').DeployHubConfig} config
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
export function generateDockerfile(config) {
|
|
49
|
+
const settings = resolveDockerSettings(config);
|
|
50
|
+
const { framework } = settings;
|
|
51
|
+
|
|
52
|
+
const frontendStatic = ['react', 'vue', 'angular', 'svelte', 'astro', 'vanilla'];
|
|
53
|
+
if (frontendStatic.includes(framework)) {
|
|
54
|
+
return generateFrontendStaticDockerfile(settings);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
switch (framework) {
|
|
58
|
+
case 'nextjs':
|
|
59
|
+
return generateNextjsDockerfile(settings);
|
|
60
|
+
case 'nestjs':
|
|
61
|
+
return generateNestjsDockerfile(settings);
|
|
62
|
+
case 'express':
|
|
63
|
+
case 'fastify':
|
|
64
|
+
case 'koa':
|
|
65
|
+
return generateNodeBackendDockerfile(settings);
|
|
66
|
+
case 'fastapi':
|
|
67
|
+
return generateFastapiDockerfile(settings);
|
|
68
|
+
case 'django':
|
|
69
|
+
return generateDjangoDockerfile(settings);
|
|
70
|
+
case 'flask':
|
|
71
|
+
return generateFlaskDockerfile(settings);
|
|
72
|
+
case 'laravel':
|
|
73
|
+
return generateLaravelDockerfile(settings);
|
|
74
|
+
case 'symfony':
|
|
75
|
+
return generateSymfonyDockerfile(settings);
|
|
76
|
+
case 'spring':
|
|
77
|
+
return generateSpringDockerfile(settings);
|
|
78
|
+
case 'go':
|
|
79
|
+
return generateGoDockerfile(settings);
|
|
80
|
+
case 'dotnet':
|
|
81
|
+
return generateDotnetDockerfile(settings);
|
|
82
|
+
case 'rails':
|
|
83
|
+
return generateRailsDockerfile(settings);
|
|
84
|
+
default:
|
|
85
|
+
return generateNodeBackendDockerfile(settings);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {{ buildCommand: string|null, buildOutput: string, port: number }} settings
|
|
91
|
+
*/
|
|
92
|
+
function generateFrontendStaticDockerfile(settings) {
|
|
93
|
+
const buildCmd = settings.buildCommand || 'npm run build';
|
|
94
|
+
const output = settings.buildOutput || 'dist';
|
|
95
|
+
|
|
96
|
+
return `${GENERATED_HEADER}
|
|
97
|
+
FROM node:20-alpine AS build
|
|
98
|
+
WORKDIR /app
|
|
99
|
+
COPY package*.json ./
|
|
100
|
+
RUN npm ci
|
|
101
|
+
COPY . .
|
|
102
|
+
RUN ${buildCmd}
|
|
103
|
+
|
|
104
|
+
FROM nginx:alpine
|
|
105
|
+
COPY --from=build /app/${output} /usr/share/nginx/html
|
|
106
|
+
EXPOSE 80
|
|
107
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {{ buildCommand: string|null, startCommand: string|null, port: number }} settings
|
|
113
|
+
*/
|
|
114
|
+
function generateNextjsDockerfile(settings) {
|
|
115
|
+
const buildCmd = settings.buildCommand || 'npm run build';
|
|
116
|
+
const startCmd = settings.startCommand || 'npm start';
|
|
117
|
+
const port = settings.port || 3000;
|
|
118
|
+
const startParts = parseCommand(startCmd);
|
|
119
|
+
|
|
120
|
+
return `${GENERATED_HEADER}
|
|
121
|
+
FROM node:20-alpine AS deps
|
|
122
|
+
WORKDIR /app
|
|
123
|
+
COPY package*.json ./
|
|
124
|
+
RUN npm ci
|
|
125
|
+
|
|
126
|
+
FROM node:20-alpine AS build
|
|
127
|
+
WORKDIR /app
|
|
128
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
129
|
+
COPY . .
|
|
130
|
+
RUN ${buildCmd}
|
|
131
|
+
|
|
132
|
+
FROM node:20-alpine AS runner
|
|
133
|
+
WORKDIR /app
|
|
134
|
+
ENV NODE_ENV=production
|
|
135
|
+
COPY --from=build /app/package*.json ./
|
|
136
|
+
COPY --from=build /app/node_modules ./node_modules
|
|
137
|
+
COPY --from=build /app/.next ./.next
|
|
138
|
+
COPY --from=build /app/public ./public
|
|
139
|
+
EXPOSE ${port}
|
|
140
|
+
CMD ${JSON.stringify(startParts)}
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {{ buildCommand: string|null, buildOutput: string, startCommand: string|null, port: number }} settings
|
|
146
|
+
*/
|
|
147
|
+
function generateNestjsDockerfile(settings) {
|
|
148
|
+
const buildCmd = settings.buildCommand || 'npm run build';
|
|
149
|
+
const startCmd = settings.startCommand || 'node dist/main';
|
|
150
|
+
const port = settings.port || 3000;
|
|
151
|
+
const startParts = parseCommand(startCmd);
|
|
152
|
+
|
|
153
|
+
return `${GENERATED_HEADER}
|
|
154
|
+
FROM node:20-alpine AS build
|
|
155
|
+
WORKDIR /app
|
|
156
|
+
COPY package*.json ./
|
|
157
|
+
RUN npm ci
|
|
158
|
+
COPY . .
|
|
159
|
+
RUN ${buildCmd}
|
|
160
|
+
|
|
161
|
+
FROM node:20-alpine
|
|
162
|
+
WORKDIR /app
|
|
163
|
+
ENV NODE_ENV=production
|
|
164
|
+
COPY package*.json ./
|
|
165
|
+
RUN npm ci --omit=dev
|
|
166
|
+
COPY --from=build /app/${settings.buildOutput || 'dist'} ./${settings.buildOutput || 'dist'}
|
|
167
|
+
EXPOSE ${port}
|
|
168
|
+
CMD ${JSON.stringify(startParts)}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @param {{ buildCommand: string|null, buildOutput: string, startCommand: string|null, port: number }} settings
|
|
174
|
+
*/
|
|
175
|
+
function generateNodeBackendDockerfile(settings) {
|
|
176
|
+
const port = settings.port || 3000;
|
|
177
|
+
const startCmd = settings.startCommand || 'npm start';
|
|
178
|
+
const startParts = parseCommand(startCmd);
|
|
179
|
+
|
|
180
|
+
if (settings.buildCommand) {
|
|
181
|
+
return `${GENERATED_HEADER}
|
|
182
|
+
FROM node:20-alpine AS build
|
|
183
|
+
WORKDIR /app
|
|
184
|
+
COPY package*.json ./
|
|
185
|
+
RUN npm ci
|
|
186
|
+
COPY . .
|
|
187
|
+
RUN ${settings.buildCommand}
|
|
188
|
+
|
|
189
|
+
FROM node:20-alpine
|
|
190
|
+
WORKDIR /app
|
|
191
|
+
ENV NODE_ENV=production
|
|
192
|
+
COPY package*.json ./
|
|
193
|
+
RUN npm ci --omit=dev
|
|
194
|
+
COPY --from=build /app/${settings.buildOutput || '.'} ./${settings.buildOutput || '.'}
|
|
195
|
+
EXPOSE ${port}
|
|
196
|
+
CMD ${JSON.stringify(startParts)}
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return `${GENERATED_HEADER}
|
|
201
|
+
FROM node:20-alpine AS deps
|
|
202
|
+
WORKDIR /app
|
|
203
|
+
COPY package*.json ./
|
|
204
|
+
RUN npm ci --omit=dev
|
|
205
|
+
|
|
206
|
+
FROM node:20-alpine
|
|
207
|
+
WORKDIR /app
|
|
208
|
+
ENV NODE_ENV=production
|
|
209
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
210
|
+
COPY . .
|
|
211
|
+
EXPOSE ${port}
|
|
212
|
+
CMD ${JSON.stringify(startParts)}
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @param {{ startCommand: string|null, port: number }} settings
|
|
218
|
+
*/
|
|
219
|
+
function generateFastapiDockerfile(settings) {
|
|
220
|
+
const port = settings.port || 8000;
|
|
221
|
+
const startCmd = settings.startCommand || `uvicorn main:app --host 0.0.0.0 --port ${port}`;
|
|
222
|
+
const startParts = parseCommand(startCmd);
|
|
223
|
+
|
|
224
|
+
return `${GENERATED_HEADER}
|
|
225
|
+
FROM python:3.11-slim AS build
|
|
226
|
+
WORKDIR /app
|
|
227
|
+
COPY requirements.txt ./
|
|
228
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
229
|
+
|
|
230
|
+
FROM python:3.11-slim
|
|
231
|
+
WORKDIR /app
|
|
232
|
+
COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
|
|
233
|
+
COPY --from=build /usr/local/bin /usr/local/bin
|
|
234
|
+
COPY . .
|
|
235
|
+
EXPOSE ${port}
|
|
236
|
+
CMD ${JSON.stringify(startParts)}
|
|
237
|
+
`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {{ startCommand: string|null, port: number }} settings
|
|
242
|
+
*/
|
|
243
|
+
function generateDjangoDockerfile(settings) {
|
|
244
|
+
const port = settings.port || 8000;
|
|
245
|
+
const startCmd =
|
|
246
|
+
settings.startCommand || `gunicorn config.wsgi:application --bind 0.0.0.0:${port}`;
|
|
247
|
+
const startParts = parseCommand(startCmd);
|
|
248
|
+
|
|
249
|
+
return `${GENERATED_HEADER}
|
|
250
|
+
FROM python:3.11-slim AS build
|
|
251
|
+
WORKDIR /app
|
|
252
|
+
COPY requirements.txt ./
|
|
253
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
254
|
+
|
|
255
|
+
FROM python:3.11-slim
|
|
256
|
+
WORKDIR /app
|
|
257
|
+
COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
|
|
258
|
+
COPY --from=build /usr/local/bin /usr/local/bin
|
|
259
|
+
COPY . .
|
|
260
|
+
EXPOSE ${port}
|
|
261
|
+
CMD ${JSON.stringify(startParts)}
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @param {{ startCommand: string|null, port: number }} settings
|
|
267
|
+
*/
|
|
268
|
+
function generateFlaskDockerfile(settings) {
|
|
269
|
+
const port = settings.port || 5000;
|
|
270
|
+
const startCmd = settings.startCommand || `gunicorn app:app --bind 0.0.0.0:${port}`;
|
|
271
|
+
const startParts = parseCommand(startCmd);
|
|
272
|
+
|
|
273
|
+
return `${GENERATED_HEADER}
|
|
274
|
+
FROM python:3.11-slim AS build
|
|
275
|
+
WORKDIR /app
|
|
276
|
+
COPY requirements.txt ./
|
|
277
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
278
|
+
|
|
279
|
+
FROM python:3.11-slim
|
|
280
|
+
WORKDIR /app
|
|
281
|
+
COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
|
|
282
|
+
COPY --from=build /usr/local/bin /usr/local/bin
|
|
283
|
+
COPY . .
|
|
284
|
+
EXPOSE ${port}
|
|
285
|
+
CMD ${JSON.stringify(startParts)}
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @param {{ port: number }} settings
|
|
291
|
+
*/
|
|
292
|
+
function generateLaravelDockerfile(settings) {
|
|
293
|
+
const port = settings.port || 80;
|
|
294
|
+
|
|
295
|
+
return `${GENERATED_HEADER}
|
|
296
|
+
FROM composer:2 AS vendor
|
|
297
|
+
WORKDIR /app
|
|
298
|
+
COPY composer.json composer.lock ./
|
|
299
|
+
RUN composer install --no-dev --optimize-autoloader --no-interaction
|
|
300
|
+
|
|
301
|
+
FROM php:8.2-fpm-alpine
|
|
302
|
+
WORKDIR /var/www/html
|
|
303
|
+
COPY --from=vendor /app/vendor ./vendor
|
|
304
|
+
COPY . .
|
|
305
|
+
RUN chown -R www-data:www-data storage bootstrap/cache || true
|
|
306
|
+
EXPOSE ${port}
|
|
307
|
+
CMD ["php-fpm"]
|
|
308
|
+
`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {{ port: number }} settings
|
|
313
|
+
*/
|
|
314
|
+
function generateSymfonyDockerfile(settings) {
|
|
315
|
+
const port = settings.port || 80;
|
|
316
|
+
|
|
317
|
+
return `${GENERATED_HEADER}
|
|
318
|
+
FROM composer:2 AS vendor
|
|
319
|
+
WORKDIR /app
|
|
320
|
+
COPY composer.json composer.lock ./
|
|
321
|
+
RUN composer install --no-dev --optimize-autoloader --no-interaction
|
|
322
|
+
|
|
323
|
+
FROM php:8.2-fpm-alpine
|
|
324
|
+
WORKDIR /var/www/html
|
|
325
|
+
COPY --from=vendor /app/vendor ./vendor
|
|
326
|
+
COPY . .
|
|
327
|
+
EXPOSE ${port}
|
|
328
|
+
CMD ["php-fpm"]
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @param {{ buildCommand: string|null, port: number }} settings
|
|
334
|
+
*/
|
|
335
|
+
function generateSpringDockerfile(settings) {
|
|
336
|
+
const buildCmd = settings.buildCommand || 'mvn package -DskipTests';
|
|
337
|
+
const port = settings.port || 8080;
|
|
338
|
+
|
|
339
|
+
return `${GENERATED_HEADER}
|
|
340
|
+
FROM eclipse-temurin:17-jdk-alpine AS build
|
|
341
|
+
WORKDIR /app
|
|
342
|
+
COPY pom.xml ./
|
|
343
|
+
COPY src ./src
|
|
344
|
+
RUN apk add --no-cache maven && ${buildCmd}
|
|
345
|
+
|
|
346
|
+
FROM eclipse-temurin:17-jre-alpine
|
|
347
|
+
WORKDIR /app
|
|
348
|
+
COPY --from=build /app/target/*.jar app.jar
|
|
349
|
+
EXPOSE ${port}
|
|
350
|
+
CMD ["java", "-jar", "app.jar"]
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @param {{ buildCommand: string|null, startCommand: string|null, port: number }} settings
|
|
356
|
+
*/
|
|
357
|
+
function generateGoDockerfile(settings) {
|
|
358
|
+
const buildCmd = settings.buildCommand || 'go build -o /app/bin/app .';
|
|
359
|
+
const port = settings.port || 8080;
|
|
360
|
+
|
|
361
|
+
return `${GENERATED_HEADER}
|
|
362
|
+
FROM golang:1.22-alpine AS build
|
|
363
|
+
WORKDIR /app
|
|
364
|
+
COPY go.mod go.sum ./
|
|
365
|
+
RUN go mod download
|
|
366
|
+
COPY . .
|
|
367
|
+
RUN ${buildCmd}
|
|
368
|
+
|
|
369
|
+
FROM alpine:3.19
|
|
370
|
+
WORKDIR /app
|
|
371
|
+
RUN apk add --no-cache ca-certificates
|
|
372
|
+
COPY --from=build /app/bin/app ./app
|
|
373
|
+
EXPOSE ${port}
|
|
374
|
+
CMD ["./app"]
|
|
375
|
+
`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* @param {{ buildCommand: string|null, buildOutput: string, startCommand: string|null, port: number }} settings
|
|
380
|
+
*/
|
|
381
|
+
function generateDotnetDockerfile(settings) {
|
|
382
|
+
const buildCmd = settings.buildCommand || 'dotnet publish -c Release -o /app/publish';
|
|
383
|
+
const port = settings.port || 5000;
|
|
384
|
+
const output = settings.buildOutput || 'publish';
|
|
385
|
+
|
|
386
|
+
return `${GENERATED_HEADER}
|
|
387
|
+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
|
388
|
+
WORKDIR /src
|
|
389
|
+
COPY *.csproj ./
|
|
390
|
+
RUN dotnet restore
|
|
391
|
+
COPY . .
|
|
392
|
+
RUN ${buildCmd}
|
|
393
|
+
|
|
394
|
+
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
|
395
|
+
WORKDIR /app
|
|
396
|
+
COPY --from=build /src/${output} .
|
|
397
|
+
EXPOSE ${port}
|
|
398
|
+
ENV ASPNETCORE_URLS=http://+:${port}
|
|
399
|
+
CMD ["dotnet", "App.dll"]
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* @param {{ startCommand: string|null, port: number }} settings
|
|
405
|
+
*/
|
|
406
|
+
function generateRailsDockerfile(settings) {
|
|
407
|
+
const port = settings.port || 3000;
|
|
408
|
+
const startCmd = settings.startCommand || 'bundle exec puma -C config/puma.rb';
|
|
409
|
+
const startParts = parseCommand(startCmd);
|
|
410
|
+
|
|
411
|
+
return `${GENERATED_HEADER}
|
|
412
|
+
FROM ruby:3.2-slim AS build
|
|
413
|
+
WORKDIR /app
|
|
414
|
+
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev && rm -rf /var/lib/apt/lists/*
|
|
415
|
+
COPY Gemfile Gemfile.lock ./
|
|
416
|
+
RUN bundle install --without development test
|
|
417
|
+
COPY . .
|
|
418
|
+
RUN bundle exec rake assets:precompile || true
|
|
419
|
+
|
|
420
|
+
FROM ruby:3.2-slim
|
|
421
|
+
WORKDIR /app
|
|
422
|
+
RUN apt-get update -qq && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
|
|
423
|
+
COPY --from=build /usr/local/bundle /usr/local/bundle
|
|
424
|
+
COPY --from=build /app .
|
|
425
|
+
EXPOSE ${port}
|
|
426
|
+
CMD ${JSON.stringify(startParts)}
|
|
427
|
+
`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @param {string} command
|
|
432
|
+
* @returns {string[]}
|
|
433
|
+
*/
|
|
434
|
+
function parseCommand(command) {
|
|
435
|
+
const trimmed = command.trim();
|
|
436
|
+
if (trimmed.startsWith('[')) {
|
|
437
|
+
try {
|
|
438
|
+
return JSON.parse(trimmed);
|
|
439
|
+
} catch {
|
|
440
|
+
// fall through
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const match = trimmed.match(/^(\S+)(?:\s+(.*))?$/);
|
|
445
|
+
if (!match) return ['sh', '-c', trimmed];
|
|
446
|
+
const [, bin, rest] = match;
|
|
447
|
+
if (!rest) return [bin];
|
|
448
|
+
return [bin, ...rest.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g).map((part) => part.replace(/^['"]|['"]$/g, ''))];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Human-readable framework label for user messages.
|
|
453
|
+
* @param {string} framework
|
|
454
|
+
*/
|
|
455
|
+
export function getDockerfileFrameworkLabel(framework) {
|
|
456
|
+
const labels = {
|
|
457
|
+
react: 'React',
|
|
458
|
+
vue: 'Vue',
|
|
459
|
+
angular: 'Angular',
|
|
460
|
+
nextjs: 'Next.js',
|
|
461
|
+
svelte: 'Svelte',
|
|
462
|
+
astro: 'Astro',
|
|
463
|
+
vanilla: 'Vanilla JS',
|
|
464
|
+
express: 'Express',
|
|
465
|
+
nestjs: 'NestJS',
|
|
466
|
+
fastify: 'Fastify',
|
|
467
|
+
koa: 'Koa',
|
|
468
|
+
fastapi: 'FastAPI',
|
|
469
|
+
django: 'Django',
|
|
470
|
+
flask: 'Flask',
|
|
471
|
+
laravel: 'Laravel',
|
|
472
|
+
symfony: 'Symfony',
|
|
473
|
+
spring: 'Spring Boot',
|
|
474
|
+
go: 'Go',
|
|
475
|
+
dotnet: '.NET',
|
|
476
|
+
rails: 'Ruby on Rails',
|
|
477
|
+
};
|
|
478
|
+
return labels[framework] || framework;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export default {
|
|
482
|
+
generateDockerfile,
|
|
483
|
+
resolveDockerSettings,
|
|
484
|
+
sanitizeDockerProjectName,
|
|
485
|
+
getDockerfileFrameworkLabel,
|
|
486
|
+
};
|
|
@@ -208,6 +208,56 @@ function getBackendSetupSteps(config) {
|
|
|
208
208
|
return steps;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
const KUBECTL_VERSION = 'v1.30.4';
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {string[]} deployEnvironments
|
|
215
|
+
* @param {Record<string, { type: string }>} environments
|
|
216
|
+
* @returns {boolean}
|
|
217
|
+
*/
|
|
218
|
+
function hasKubernetesDeploy(deployEnvironments, environments) {
|
|
219
|
+
return deployEnvironments.some((envName) => environments[envName]?.type === 'kubernetes');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @returns {string}
|
|
224
|
+
*/
|
|
225
|
+
function getKubernetesSetupSteps() {
|
|
226
|
+
return ` - name: Setup kubectl
|
|
227
|
+
uses: azure/setup-kubectl@v4
|
|
228
|
+
with:
|
|
229
|
+
version: '${KUBECTL_VERSION}'
|
|
230
|
+
|
|
231
|
+
- name: Configure kubeconfig
|
|
232
|
+
env:
|
|
233
|
+
KUBECONFIG_SECRET: \${{ secrets.KUBECONFIG }}
|
|
234
|
+
run: |
|
|
235
|
+
mkdir -p "$GITHUB_WORKSPACE/.kube"
|
|
236
|
+
if echo "$KUBECONFIG_SECRET" | base64 -d > "$GITHUB_WORKSPACE/.kube/config" 2>/dev/null; then
|
|
237
|
+
:
|
|
238
|
+
else
|
|
239
|
+
printf '%s' "$KUBECONFIG_SECRET" > "$GITHUB_WORKSPACE/.kube/config"
|
|
240
|
+
fi
|
|
241
|
+
chmod 600 "$GITHUB_WORKSPACE/.kube/config"`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* @param {string[]} deployEnvironments
|
|
246
|
+
* @param {Record<string, { type: string }>} environments
|
|
247
|
+
* @param {Set<string>} envVars
|
|
248
|
+
*/
|
|
249
|
+
function applyKubernetesWorkflowEnv(deployEnvironments, environments, envVars) {
|
|
250
|
+
if (!hasKubernetesDeploy(deployEnvironments, environments)) return;
|
|
251
|
+
|
|
252
|
+
for (const entry of envVars) {
|
|
253
|
+
if (entry.startsWith('KUBECONFIG:')) {
|
|
254
|
+
envVars.delete(entry);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
envVars.add('KUBECONFIG: ${{ github.workspace }}/.kube/config');
|
|
259
|
+
}
|
|
260
|
+
|
|
211
261
|
/**
|
|
212
262
|
* @param {string[]} storageProviders
|
|
213
263
|
* @param {string[]} deployEnvironments
|
|
@@ -243,6 +293,8 @@ export function generateWorkflowYaml(
|
|
|
243
293
|
}
|
|
244
294
|
}
|
|
245
295
|
|
|
296
|
+
applyKubernetesWorkflowEnv(deployEnvironments, environments, envVars);
|
|
297
|
+
|
|
246
298
|
const envBlock = Array.from(envVars)
|
|
247
299
|
.map((line) => ` ${line}`)
|
|
248
300
|
.join('\n');
|
|
@@ -253,6 +305,9 @@ export function generateWorkflowYaml(
|
|
|
253
305
|
const githubGitConfigStep = isGithubCliSource(cliSource)
|
|
254
306
|
? `${getGithubGitConfigStep()}\n`
|
|
255
307
|
: '';
|
|
308
|
+
const kubernetesSteps = hasKubernetesDeploy(deployEnvironments, environments)
|
|
309
|
+
? `${getKubernetesSetupSteps()}\n`
|
|
310
|
+
: '';
|
|
256
311
|
|
|
257
312
|
const projectType = config?.projectType || 'frontend';
|
|
258
313
|
const installDepsCommand =
|
|
@@ -272,7 +327,7 @@ jobs:
|
|
|
272
327
|
${uniqueBackendSteps ? `${uniqueBackendSteps}\n` : ''} - uses: actions/setup-node@v4
|
|
273
328
|
with:
|
|
274
329
|
node-version: '20'
|
|
275
|
-
${githubGitConfigStep} - name: Install project dependencies
|
|
330
|
+
${kubernetesSteps}${githubGitConfigStep} - name: Install project dependencies
|
|
276
331
|
run: ${installDepsCommand}
|
|
277
332
|
- name: Install DeployHub CLI
|
|
278
333
|
run: npm install ${installSpec} --no-save
|