@akash-chowdhury-24/deployhub 2.0.4 → 2.0.6

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.
@@ -0,0 +1,584 @@
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
+ const DOCKERIGNORE_HEADER =
9
+ '# Generated by DeployHub — safe to edit. Existing .dockerignore is never overwritten.\n';
10
+
11
+ /**
12
+ * @param {string} projectName
13
+ * @returns {string}
14
+ */
15
+ export function sanitizeDockerProjectName(projectName) {
16
+ return projectName.replace(/[^a-zA-Z0-9_-]/g, '-');
17
+ }
18
+
19
+ /**
20
+ * @param {import('../core/config.js').DeployHubConfig} config
21
+ * @returns {{ framework: string, buildCommand: string|null, buildOutput: string, startCommand: string|null, port: number, projectType: string }}
22
+ */
23
+ export function resolveDockerSettings(config) {
24
+ const projectType = config.projectType || 'frontend';
25
+
26
+ if (projectType === 'both' && config.backend) {
27
+ return {
28
+ framework: config.backend.framework || 'express',
29
+ buildCommand: config.backend.buildCommand ?? null,
30
+ buildOutput: config.backend.buildOutput || '.',
31
+ startCommand: config.backend.startCommand || 'npm start',
32
+ port: config.backend.port || 3000,
33
+ projectType,
34
+ };
35
+ }
36
+
37
+ return {
38
+ framework: config.framework || 'express',
39
+ buildCommand: config.buildCommand ?? null,
40
+ buildOutput: config.buildOutput || 'dist',
41
+ startCommand: config.startCommand || 'npm start',
42
+ port: config.port || 3000,
43
+ projectType,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Generate a .dockerignore matching the Dockerfile language/framework.
49
+ * @param {import('../core/config.js').DeployHubConfig} config
50
+ * @returns {string}
51
+ */
52
+ export function generateDockerignore(config) {
53
+ const settings = resolveDockerSettings(config);
54
+ const { framework, buildOutput, projectType } = settings;
55
+
56
+ /** @type {string[]} */
57
+ const lines = [DOCKERIGNORE_HEADER.trimEnd(), ''];
58
+
59
+ const common = [
60
+ '.git',
61
+ '.gitignore',
62
+ '.env',
63
+ '.env.*',
64
+ '!.env.example',
65
+ 'artifact',
66
+ '.deployhub',
67
+ 'coverage',
68
+ '*.md',
69
+ '!README.md',
70
+ '.github',
71
+ '.vscode',
72
+ '.idea',
73
+ 'deployhub.config.json',
74
+ ];
75
+ lines.push(...common, '');
76
+
77
+ const frontendStatic = ['react', 'vue', 'angular', 'svelte', 'astro', 'vanilla'];
78
+ const nodeFrameworks = [
79
+ ...frontendStatic,
80
+ 'nextjs',
81
+ 'nestjs',
82
+ 'express',
83
+ 'fastify',
84
+ 'koa',
85
+ ];
86
+ const pythonFrameworks = ['fastapi', 'django', 'flask'];
87
+ const phpFrameworks = ['laravel', 'symfony'];
88
+
89
+ if (nodeFrameworks.includes(framework) || !framework) {
90
+ lines.push('node_modules', 'npm-debug.log*', 'yarn-error.log*', '.npm', '');
91
+ // Source Dockerfile rebuilds inside the image — exclude local build output from context
92
+ if (
93
+ frontendStatic.includes(framework) ||
94
+ framework === 'nextjs' ||
95
+ framework === 'nestjs' ||
96
+ (projectType === 'frontend' && buildOutput && buildOutput !== '.')
97
+ ) {
98
+ const out = buildOutput || 'dist';
99
+ if (out !== '.') lines.push(out);
100
+ if (framework === 'nextjs') lines.push('.next');
101
+ if (out !== 'build') lines.push('build');
102
+ lines.push('');
103
+ }
104
+ }
105
+
106
+ if (pythonFrameworks.includes(framework)) {
107
+ lines.push(
108
+ '__pycache__',
109
+ '*.py[cod]',
110
+ '.venv',
111
+ 'venv',
112
+ '.pytest_cache',
113
+ '*.egg-info',
114
+ ''
115
+ );
116
+ }
117
+
118
+ if (phpFrameworks.includes(framework)) {
119
+ lines.push('vendor', '');
120
+ }
121
+
122
+ if (framework === 'spring') {
123
+ lines.push('target', '.gradle', 'build', '');
124
+ }
125
+
126
+ if (framework === 'go') {
127
+ lines.push('bin', '');
128
+ }
129
+
130
+ if (framework === 'dotnet') {
131
+ lines.push('bin', 'obj', '');
132
+ }
133
+
134
+ if (framework === 'rails') {
135
+ lines.push('tmp', 'log', 'vendor/bundle', '');
136
+ }
137
+
138
+ return `${lines.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd()}\n`;
139
+ }
140
+
141
+ /**
142
+ * @param {import('../core/config.js').DeployHubConfig} config
143
+ * @returns {string}
144
+ */
145
+ export function generateDockerfile(config) {
146
+ const settings = resolveDockerSettings(config);
147
+ const { framework } = settings;
148
+
149
+ const frontendStatic = ['react', 'vue', 'angular', 'svelte', 'astro', 'vanilla'];
150
+ if (frontendStatic.includes(framework)) {
151
+ return generateFrontendStaticDockerfile(settings);
152
+ }
153
+
154
+ switch (framework) {
155
+ case 'nextjs':
156
+ return generateNextjsDockerfile(settings);
157
+ case 'nestjs':
158
+ return generateNestjsDockerfile(settings);
159
+ case 'express':
160
+ case 'fastify':
161
+ case 'koa':
162
+ return generateNodeBackendDockerfile(settings);
163
+ case 'fastapi':
164
+ return generateFastapiDockerfile(settings);
165
+ case 'django':
166
+ return generateDjangoDockerfile(settings);
167
+ case 'flask':
168
+ return generateFlaskDockerfile(settings);
169
+ case 'laravel':
170
+ return generateLaravelDockerfile(settings);
171
+ case 'symfony':
172
+ return generateSymfonyDockerfile(settings);
173
+ case 'spring':
174
+ return generateSpringDockerfile(settings);
175
+ case 'go':
176
+ return generateGoDockerfile(settings);
177
+ case 'dotnet':
178
+ return generateDotnetDockerfile(settings);
179
+ case 'rails':
180
+ return generateRailsDockerfile(settings);
181
+ default:
182
+ return generateNodeBackendDockerfile(settings);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * @param {{ buildCommand: string|null, buildOutput: string, port: number }} settings
188
+ */
189
+ function generateFrontendStaticDockerfile(settings) {
190
+ const buildCmd = settings.buildCommand || 'npm run build';
191
+ const output = settings.buildOutput || 'dist';
192
+
193
+ return `${GENERATED_HEADER}
194
+ FROM node:20-alpine AS build
195
+ WORKDIR /app
196
+ COPY package*.json ./
197
+ RUN npm ci
198
+ COPY . .
199
+ RUN ${buildCmd}
200
+
201
+ FROM nginx:alpine
202
+ COPY --from=build /app/${output} /usr/share/nginx/html
203
+ EXPOSE 80
204
+ CMD ["nginx", "-g", "daemon off;"]
205
+ `;
206
+ }
207
+
208
+ /**
209
+ * @param {{ buildCommand: string|null, startCommand: string|null, port: number }} settings
210
+ */
211
+ function generateNextjsDockerfile(settings) {
212
+ const buildCmd = settings.buildCommand || 'npm run build';
213
+ const startCmd = settings.startCommand || 'npm start';
214
+ const port = settings.port || 3000;
215
+ const startParts = parseCommand(startCmd);
216
+
217
+ return `${GENERATED_HEADER}
218
+ FROM node:20-alpine AS deps
219
+ WORKDIR /app
220
+ COPY package*.json ./
221
+ RUN npm ci
222
+
223
+ FROM node:20-alpine AS build
224
+ WORKDIR /app
225
+ COPY --from=deps /app/node_modules ./node_modules
226
+ COPY . .
227
+ RUN ${buildCmd}
228
+
229
+ FROM node:20-alpine AS runner
230
+ WORKDIR /app
231
+ ENV NODE_ENV=production
232
+ COPY --from=build /app/package*.json ./
233
+ COPY --from=build /app/node_modules ./node_modules
234
+ COPY --from=build /app/.next ./.next
235
+ COPY --from=build /app/public ./public
236
+ EXPOSE ${port}
237
+ CMD ${JSON.stringify(startParts)}
238
+ `;
239
+ }
240
+
241
+ /**
242
+ * @param {{ buildCommand: string|null, buildOutput: string, startCommand: string|null, port: number }} settings
243
+ */
244
+ function generateNestjsDockerfile(settings) {
245
+ const buildCmd = settings.buildCommand || 'npm run build';
246
+ const startCmd = settings.startCommand || 'node dist/main';
247
+ const port = settings.port || 3000;
248
+ const startParts = parseCommand(startCmd);
249
+
250
+ return `${GENERATED_HEADER}
251
+ FROM node:20-alpine AS build
252
+ WORKDIR /app
253
+ COPY package*.json ./
254
+ RUN npm ci
255
+ COPY . .
256
+ RUN ${buildCmd}
257
+
258
+ FROM node:20-alpine
259
+ WORKDIR /app
260
+ ENV NODE_ENV=production
261
+ COPY package*.json ./
262
+ RUN npm ci --omit=dev
263
+ COPY --from=build /app/${settings.buildOutput || 'dist'} ./${settings.buildOutput || 'dist'}
264
+ EXPOSE ${port}
265
+ CMD ${JSON.stringify(startParts)}
266
+ `;
267
+ }
268
+
269
+ /**
270
+ * @param {{ buildCommand: string|null, buildOutput: string, startCommand: string|null, port: number }} settings
271
+ */
272
+ function generateNodeBackendDockerfile(settings) {
273
+ const port = settings.port || 3000;
274
+ const startCmd = settings.startCommand || 'npm start';
275
+ const startParts = parseCommand(startCmd);
276
+
277
+ if (settings.buildCommand) {
278
+ return `${GENERATED_HEADER}
279
+ FROM node:20-alpine AS build
280
+ WORKDIR /app
281
+ COPY package*.json ./
282
+ RUN npm ci
283
+ COPY . .
284
+ RUN ${settings.buildCommand}
285
+
286
+ FROM node:20-alpine
287
+ WORKDIR /app
288
+ ENV NODE_ENV=production
289
+ COPY package*.json ./
290
+ RUN npm ci --omit=dev
291
+ COPY --from=build /app/${settings.buildOutput || '.'} ./${settings.buildOutput || '.'}
292
+ EXPOSE ${port}
293
+ CMD ${JSON.stringify(startParts)}
294
+ `;
295
+ }
296
+
297
+ return `${GENERATED_HEADER}
298
+ FROM node:20-alpine AS deps
299
+ WORKDIR /app
300
+ COPY package*.json ./
301
+ RUN npm ci --omit=dev
302
+
303
+ FROM node:20-alpine
304
+ WORKDIR /app
305
+ ENV NODE_ENV=production
306
+ COPY --from=deps /app/node_modules ./node_modules
307
+ COPY . .
308
+ EXPOSE ${port}
309
+ CMD ${JSON.stringify(startParts)}
310
+ `;
311
+ }
312
+
313
+ /**
314
+ * @param {{ startCommand: string|null, port: number }} settings
315
+ */
316
+ function generateFastapiDockerfile(settings) {
317
+ const port = settings.port || 8000;
318
+ const startCmd = settings.startCommand || `uvicorn main:app --host 0.0.0.0 --port ${port}`;
319
+ const startParts = parseCommand(startCmd);
320
+
321
+ return `${GENERATED_HEADER}
322
+ FROM python:3.11-slim AS build
323
+ WORKDIR /app
324
+ COPY requirements.txt ./
325
+ RUN pip install --no-cache-dir -r requirements.txt
326
+
327
+ FROM python:3.11-slim
328
+ WORKDIR /app
329
+ COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
330
+ COPY --from=build /usr/local/bin /usr/local/bin
331
+ COPY . .
332
+ EXPOSE ${port}
333
+ CMD ${JSON.stringify(startParts)}
334
+ `;
335
+ }
336
+
337
+ /**
338
+ * @param {{ startCommand: string|null, port: number }} settings
339
+ */
340
+ function generateDjangoDockerfile(settings) {
341
+ const port = settings.port || 8000;
342
+ const startCmd =
343
+ settings.startCommand || `gunicorn config.wsgi:application --bind 0.0.0.0:${port}`;
344
+ const startParts = parseCommand(startCmd);
345
+
346
+ return `${GENERATED_HEADER}
347
+ FROM python:3.11-slim AS build
348
+ WORKDIR /app
349
+ COPY requirements.txt ./
350
+ RUN pip install --no-cache-dir -r requirements.txt
351
+
352
+ FROM python:3.11-slim
353
+ WORKDIR /app
354
+ COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
355
+ COPY --from=build /usr/local/bin /usr/local/bin
356
+ COPY . .
357
+ EXPOSE ${port}
358
+ CMD ${JSON.stringify(startParts)}
359
+ `;
360
+ }
361
+
362
+ /**
363
+ * @param {{ startCommand: string|null, port: number }} settings
364
+ */
365
+ function generateFlaskDockerfile(settings) {
366
+ const port = settings.port || 5000;
367
+ const startCmd = settings.startCommand || `gunicorn app:app --bind 0.0.0.0:${port}`;
368
+ const startParts = parseCommand(startCmd);
369
+
370
+ return `${GENERATED_HEADER}
371
+ FROM python:3.11-slim AS build
372
+ WORKDIR /app
373
+ COPY requirements.txt ./
374
+ RUN pip install --no-cache-dir -r requirements.txt
375
+
376
+ FROM python:3.11-slim
377
+ WORKDIR /app
378
+ COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
379
+ COPY --from=build /usr/local/bin /usr/local/bin
380
+ COPY . .
381
+ EXPOSE ${port}
382
+ CMD ${JSON.stringify(startParts)}
383
+ `;
384
+ }
385
+
386
+ /**
387
+ * @param {{ port: number }} settings
388
+ */
389
+ function generateLaravelDockerfile(settings) {
390
+ const port = settings.port || 80;
391
+
392
+ return `${GENERATED_HEADER}
393
+ FROM composer:2 AS vendor
394
+ WORKDIR /app
395
+ COPY composer.json composer.lock ./
396
+ RUN composer install --no-dev --optimize-autoloader --no-interaction
397
+
398
+ FROM php:8.2-fpm-alpine
399
+ WORKDIR /var/www/html
400
+ COPY --from=vendor /app/vendor ./vendor
401
+ COPY . .
402
+ RUN chown -R www-data:www-data storage bootstrap/cache || true
403
+ EXPOSE ${port}
404
+ CMD ["php-fpm"]
405
+ `;
406
+ }
407
+
408
+ /**
409
+ * @param {{ port: number }} settings
410
+ */
411
+ function generateSymfonyDockerfile(settings) {
412
+ const port = settings.port || 80;
413
+
414
+ return `${GENERATED_HEADER}
415
+ FROM composer:2 AS vendor
416
+ WORKDIR /app
417
+ COPY composer.json composer.lock ./
418
+ RUN composer install --no-dev --optimize-autoloader --no-interaction
419
+
420
+ FROM php:8.2-fpm-alpine
421
+ WORKDIR /var/www/html
422
+ COPY --from=vendor /app/vendor ./vendor
423
+ COPY . .
424
+ EXPOSE ${port}
425
+ CMD ["php-fpm"]
426
+ `;
427
+ }
428
+
429
+ /**
430
+ * @param {{ buildCommand: string|null, port: number }} settings
431
+ */
432
+ function generateSpringDockerfile(settings) {
433
+ const buildCmd = settings.buildCommand || 'mvn package -DskipTests';
434
+ const port = settings.port || 8080;
435
+
436
+ return `${GENERATED_HEADER}
437
+ FROM eclipse-temurin:17-jdk-alpine AS build
438
+ WORKDIR /app
439
+ COPY pom.xml ./
440
+ COPY src ./src
441
+ RUN apk add --no-cache maven && ${buildCmd}
442
+
443
+ FROM eclipse-temurin:17-jre-alpine
444
+ WORKDIR /app
445
+ COPY --from=build /app/target/*.jar app.jar
446
+ EXPOSE ${port}
447
+ CMD ["java", "-jar", "app.jar"]
448
+ `;
449
+ }
450
+
451
+ /**
452
+ * @param {{ buildCommand: string|null, startCommand: string|null, port: number }} settings
453
+ */
454
+ function generateGoDockerfile(settings) {
455
+ const buildCmd = settings.buildCommand || 'go build -o /app/bin/app .';
456
+ const port = settings.port || 8080;
457
+
458
+ return `${GENERATED_HEADER}
459
+ FROM golang:1.22-alpine AS build
460
+ WORKDIR /app
461
+ COPY go.mod go.sum ./
462
+ RUN go mod download
463
+ COPY . .
464
+ RUN ${buildCmd}
465
+
466
+ FROM alpine:3.19
467
+ WORKDIR /app
468
+ RUN apk add --no-cache ca-certificates
469
+ COPY --from=build /app/bin/app ./app
470
+ EXPOSE ${port}
471
+ CMD ["./app"]
472
+ `;
473
+ }
474
+
475
+ /**
476
+ * @param {{ buildCommand: string|null, buildOutput: string, startCommand: string|null, port: number }} settings
477
+ */
478
+ function generateDotnetDockerfile(settings) {
479
+ const buildCmd = settings.buildCommand || 'dotnet publish -c Release -o /app/publish';
480
+ const port = settings.port || 5000;
481
+ const output = settings.buildOutput || 'publish';
482
+
483
+ return `${GENERATED_HEADER}
484
+ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
485
+ WORKDIR /src
486
+ COPY *.csproj ./
487
+ RUN dotnet restore
488
+ COPY . .
489
+ RUN ${buildCmd}
490
+
491
+ FROM mcr.microsoft.com/dotnet/aspnet:8.0
492
+ WORKDIR /app
493
+ COPY --from=build /src/${output} .
494
+ EXPOSE ${port}
495
+ ENV ASPNETCORE_URLS=http://+:${port}
496
+ CMD ["dotnet", "App.dll"]
497
+ `;
498
+ }
499
+
500
+ /**
501
+ * @param {{ startCommand: string|null, port: number }} settings
502
+ */
503
+ function generateRailsDockerfile(settings) {
504
+ const port = settings.port || 3000;
505
+ const startCmd = settings.startCommand || 'bundle exec puma -C config/puma.rb';
506
+ const startParts = parseCommand(startCmd);
507
+
508
+ return `${GENERATED_HEADER}
509
+ FROM ruby:3.2-slim AS build
510
+ WORKDIR /app
511
+ RUN apt-get update -qq && apt-get install -y build-essential libpq-dev && rm -rf /var/lib/apt/lists/*
512
+ COPY Gemfile Gemfile.lock ./
513
+ RUN bundle install --without development test
514
+ COPY . .
515
+ RUN bundle exec rake assets:precompile || true
516
+
517
+ FROM ruby:3.2-slim
518
+ WORKDIR /app
519
+ RUN apt-get update -qq && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
520
+ COPY --from=build /usr/local/bundle /usr/local/bundle
521
+ COPY --from=build /app .
522
+ EXPOSE ${port}
523
+ CMD ${JSON.stringify(startParts)}
524
+ `;
525
+ }
526
+
527
+ /**
528
+ * @param {string} command
529
+ * @returns {string[]}
530
+ */
531
+ function parseCommand(command) {
532
+ const trimmed = command.trim();
533
+ if (trimmed.startsWith('[')) {
534
+ try {
535
+ return JSON.parse(trimmed);
536
+ } catch {
537
+ // fall through
538
+ }
539
+ }
540
+
541
+ const match = trimmed.match(/^(\S+)(?:\s+(.*))?$/);
542
+ if (!match) return ['sh', '-c', trimmed];
543
+ const [, bin, rest] = match;
544
+ if (!rest) return [bin];
545
+ return [bin, ...rest.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g).map((part) => part.replace(/^['"]|['"]$/g, ''))];
546
+ }
547
+
548
+ /**
549
+ * Human-readable framework label for user messages.
550
+ * @param {string} framework
551
+ */
552
+ export function getDockerfileFrameworkLabel(framework) {
553
+ const labels = {
554
+ react: 'React',
555
+ vue: 'Vue',
556
+ angular: 'Angular',
557
+ nextjs: 'Next.js',
558
+ svelte: 'Svelte',
559
+ astro: 'Astro',
560
+ vanilla: 'Vanilla JS',
561
+ express: 'Express',
562
+ nestjs: 'NestJS',
563
+ fastify: 'Fastify',
564
+ koa: 'Koa',
565
+ fastapi: 'FastAPI',
566
+ django: 'Django',
567
+ flask: 'Flask',
568
+ laravel: 'Laravel',
569
+ symfony: 'Symfony',
570
+ spring: 'Spring Boot',
571
+ go: 'Go',
572
+ dotnet: '.NET',
573
+ rails: 'Ruby on Rails',
574
+ };
575
+ return labels[framework] || framework;
576
+ }
577
+
578
+ export default {
579
+ generateDockerfile,
580
+ generateDockerignore,
581
+ resolveDockerSettings,
582
+ sanitizeDockerProjectName,
583
+ getDockerfileFrameworkLabel,
584
+ };
@@ -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