@akash-chowdhury-24/deployhub 2.0.0 → 2.0.2

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.
@@ -2,7 +2,7 @@ import inquirer from 'inquirer';
2
2
  import chalk from 'chalk';
3
3
  import fs from 'fs-extra';
4
4
  import path from 'path';
5
- import { saveConfig } from '../core/config.js';
5
+ import { saveConfig, appendEnv } from '../core/config.js';
6
6
  import { detectFrontend, detectBackend } from '../detectors/index.js';
7
7
  import {
8
8
  getFrontendInfo,
@@ -20,6 +20,13 @@ import {
20
20
  } from '../utils/github-actions.js';
21
21
  import { printAuthorFooter } from '../utils/author.js';
22
22
  import { generateNginxConfig } from '../utils/nginx.js';
23
+ import {
24
+ promptServerDeployment,
25
+ buildServerEnvEntry,
26
+ getDockerEnvSecrets,
27
+ SSH_BASED,
28
+ } from '../deployment/init-prompts.js';
29
+ import { printDeploymentNextSteps } from '../deployment/deployment-env.js';
23
30
 
24
31
  const FRONTEND_CHOICES = [
25
32
  { name: 'React', value: 'react' },
@@ -151,136 +158,6 @@ async function promptBuildSettings(defaults, side) {
151
158
  return inquirer.prompt(questions);
152
159
  }
153
160
 
154
- const SERVER_DEPLOY_TYPES = [
155
- 'ssh',
156
- 'docker',
157
- 'ec2',
158
- 'azure-vm',
159
- 'gcp-vm',
160
- 'kubernetes',
161
- ];
162
-
163
- /**
164
- * @param {string} projectName
165
- * @param {'frontend'|'backend'|'both'} projectType
166
- * @param {Record<string, unknown>|null} backendConfig
167
- * @param {Record<string, unknown>|null} singleConfig
168
- */
169
- async function promptServerDeployment(projectName, projectType, backendConfig, singleConfig) {
170
- return inquirer.prompt([
171
- {
172
- type: 'list',
173
- name: 'deployType',
174
- message: 'Deployment type:',
175
- choices: SERVER_DEPLOY_TYPES,
176
- },
177
- {
178
- type: 'input',
179
- name: 'envName',
180
- message: 'Environment name:',
181
- default: 'production',
182
- },
183
- {
184
- type: 'input',
185
- name: 'host',
186
- message: 'Host (for SSH-based targets):',
187
- when: (a) => ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(a.deployType),
188
- },
189
- {
190
- type: 'input',
191
- name: 'user',
192
- message: 'SSH user:',
193
- when: (a) => ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(a.deployType),
194
- },
195
- {
196
- type: 'input',
197
- name: 'deployPath',
198
- message: 'Deploy path:',
199
- default: `/var/www/${projectName}`,
200
- when: (a) =>
201
- ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(a.deployType) &&
202
- projectType !== 'both',
203
- },
204
- {
205
- type: 'input',
206
- name: 'frontendDeployPath',
207
- message: 'Frontend deploy path:',
208
- default: `/var/www/${projectName}/public`,
209
- when: (a) =>
210
- ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(a.deployType) &&
211
- projectType === 'both',
212
- },
213
- {
214
- type: 'input',
215
- name: 'backendDeployPath',
216
- message: 'Backend deploy path:',
217
- default: `/var/www/${projectName}/api`,
218
- when: (a) =>
219
- ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(a.deployType) &&
220
- projectType === 'both',
221
- },
222
- {
223
- type: 'input',
224
- name: 'appName',
225
- message: 'App name (for PM2):',
226
- default: projectType === 'both' ? `${projectName}-api` : projectName,
227
- when: (a) =>
228
- ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(a.deployType) &&
229
- (projectType === 'backend' || projectType === 'both'),
230
- },
231
- {
232
- type: 'input',
233
- name: 'healthUrl',
234
- message: 'Health check URL (optional):',
235
- },
236
- ]);
237
- }
238
-
239
- /**
240
- * @param {Awaited<ReturnType<typeof promptServerDeployment>>} deployAnswers
241
- * @param {'frontend'|'backend'|'both'} projectType
242
- * @param {string} projectName
243
- * @param {Record<string, unknown>|null} backendConfig
244
- * @param {Record<string, unknown>|null} singleConfig
245
- */
246
- function buildServerEnvEntry(
247
- deployAnswers,
248
- projectType,
249
- projectName,
250
- backendConfig,
251
- singleConfig
252
- ) {
253
- /** @type {Record<string, unknown>} */
254
- const envEntry = {
255
- deploymentType: 'server',
256
- type: deployAnswers.deployType,
257
- host: deployAnswers.host || '',
258
- user: deployAnswers.user || '',
259
- };
260
-
261
- if (projectType === 'both') {
262
- envEntry.frontendDeployPath =
263
- deployAnswers.frontendDeployPath || `/var/www/${projectName}/public`;
264
- envEntry.backendDeployPath =
265
- deployAnswers.backendDeployPath || `/var/www/${projectName}/api`;
266
- envEntry.appName = deployAnswers.appName || `${projectName}-api`;
267
- envEntry.framework = backendConfig?.framework || 'express';
268
- envEntry.path = envEntry.backendDeployPath;
269
- envEntry.backendDeploymentType = 'server';
270
- } else if (projectType === 'backend') {
271
- envEntry.deployPath = deployAnswers.deployPath || `/var/www/${projectName}`;
272
- envEntry.path = envEntry.deployPath;
273
- envEntry.appName = deployAnswers.appName || projectName;
274
- envEntry.framework = singleConfig?.framework || 'express';
275
- envEntry.port = singleConfig?.port || 3000;
276
- } else {
277
- envEntry.deployPath = deployAnswers.deployPath || `/var/www/${projectName}`;
278
- envEntry.path = envEntry.deployPath;
279
- }
280
-
281
- return envEntry;
282
- }
283
-
284
161
  /**
285
162
  * @param {Record<string, unknown>} config
286
163
  * @param {Record<string, Record<string, unknown>>} environments
@@ -290,7 +167,8 @@ async function generateProjectScaffold(config, environments, cwd) {
290
167
  const envList = Object.values(environments);
291
168
 
292
169
  const usesFrontendSsh = envList.some(
293
- (env) => env.type === 'ssh' || env.deploymentType === 'server'
170
+ (env) =>
171
+ SSH_BASED.includes(env.type) || env.deploymentType === 'server'
294
172
  );
295
173
 
296
174
  const isFrontendProject =
@@ -431,15 +309,19 @@ export function registerInitCommand(program) {
431
309
  /** @type {string[]} */
432
310
  const deploy = [];
433
311
  let healthUrl = '';
312
+ /** @type {string|undefined} */
313
+ let primaryDeployType;
314
+ /** @type {Record<string, string>|null} */
315
+ let dockerEnvToAppend = null;
434
316
 
435
317
  if (answers.configureDeploy) {
436
318
  if (projectType === 'backend') {
437
319
  const deployAnswers = await promptServerDeployment(
438
320
  projectName,
439
321
  projectType,
440
- backendConfig,
441
- singleConfig
322
+ backendConfig
442
323
  );
324
+ primaryDeployType = deployAnswers.deployType;
443
325
  deploy.push(deployAnswers.envName);
444
326
  environments[deployAnswers.envName] = buildServerEnvEntry(
445
327
  deployAnswers,
@@ -453,13 +335,14 @@ export function registerInitCommand(program) {
453
335
  } else if (singleConfig?.port) {
454
336
  healthUrl = `http://localhost:${singleConfig.port}/health`;
455
337
  }
338
+ dockerEnvToAppend = getDockerEnvSecrets(deployAnswers);
456
339
  } else if (projectType === 'frontend') {
457
340
  const deployAnswers = await promptServerDeployment(
458
341
  projectName,
459
342
  projectType,
460
- backendConfig,
461
- singleConfig
343
+ backendConfig
462
344
  );
345
+ primaryDeployType = deployAnswers.deployType;
463
346
  deploy.push(deployAnswers.envName);
464
347
  environments[deployAnswers.envName] = buildServerEnvEntry(
465
348
  deployAnswers,
@@ -471,16 +354,15 @@ export function registerInitCommand(program) {
471
354
  if (deployAnswers.healthUrl) {
472
355
  healthUrl = deployAnswers.healthUrl;
473
356
  }
357
+ dockerEnvToAppend = getDockerEnvSecrets(deployAnswers);
474
358
  } else {
475
- const { envName } = await inquirer.prompt([
476
- {
477
- type: 'input',
478
- name: 'envName',
479
- message: 'Environment name:',
480
- default: 'production',
481
- },
482
- ]);
483
- deploy.push(envName);
359
+ const backendDeployAnswers = await promptServerDeployment(
360
+ projectName,
361
+ 'both',
362
+ backendConfig
363
+ );
364
+ primaryDeployType = backendDeployAnswers.deployType;
365
+ deploy.push(backendDeployAnswers.envName);
484
366
 
485
367
  /** @type {Record<string, unknown>} */
486
368
  const envEntry = {
@@ -489,76 +371,25 @@ export function registerInitCommand(program) {
489
371
  deploymentType: 'server',
490
372
  };
491
373
 
492
- console.log(chalk.gray('\nBackend will be deployed to a self-hosted server.'));
493
- const backendDeployAnswers = await inquirer.prompt([
494
- {
495
- type: 'list',
496
- name: 'deployType',
497
- message: 'Backend deployment type:',
498
- choices: SERVER_DEPLOY_TYPES,
499
- default: 'ssh',
500
- },
501
- {
502
- type: 'input',
503
- name: 'host',
504
- message: 'Host (for SSH-based targets):',
505
- when: (a) => ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(a.deployType),
506
- },
507
- {
508
- type: 'input',
509
- name: 'user',
510
- message: 'SSH user:',
511
- when: (a) => ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(a.deployType),
512
- },
513
- {
514
- type: 'input',
515
- name: 'backendDeployPath',
516
- message: 'Backend deploy path:',
517
- default: `/var/www/${projectName}/api`,
518
- },
519
- {
520
- type: 'input',
521
- name: 'appName',
522
- message: 'App name (for PM2):',
523
- default: `${projectName}-api`,
524
- },
525
- ]);
526
-
527
- Object.assign(envEntry, {
528
- type: backendDeployAnswers.deployType,
529
- host: backendDeployAnswers.host || '',
530
- user: backendDeployAnswers.user || '',
531
- backendDeployPath:
532
- backendDeployAnswers.backendDeployPath || `/var/www/${projectName}/api`,
533
- appName: backendDeployAnswers.appName || `${projectName}-api`,
534
- framework: backendConfig?.framework || 'express',
535
- path: backendDeployAnswers.backendDeployPath || `/var/www/${projectName}/api`,
536
- });
537
-
538
- const { frontendDeployPath } = await inquirer.prompt([
539
- {
540
- type: 'input',
541
- name: 'frontendDeployPath',
542
- message: 'Frontend deploy path:',
543
- default: `/var/www/${projectName}/public`,
544
- },
545
- ]);
546
- envEntry.frontendDeployPath = frontendDeployPath;
547
-
548
- const { healthUrlInput } = await inquirer.prompt([
549
- {
550
- type: 'input',
551
- name: 'healthUrlInput',
552
- message: 'Health check URL (optional):',
553
- default: healthUrl || '',
554
- },
555
- ]);
556
- if (healthUrlInput) healthUrl = healthUrlInput;
557
- else if (!healthUrl && backendConfig?.port) {
374
+ Object.assign(
375
+ envEntry,
376
+ buildServerEnvEntry(
377
+ backendDeployAnswers,
378
+ 'both',
379
+ projectName,
380
+ backendConfig,
381
+ singleConfig
382
+ )
383
+ );
384
+
385
+ if (backendDeployAnswers.healthUrl) {
386
+ healthUrl = backendDeployAnswers.healthUrl;
387
+ } else if (backendConfig?.port) {
558
388
  healthUrl = `http://localhost:${backendConfig.port}/health`;
559
389
  }
560
390
 
561
- environments[envName] = envEntry;
391
+ environments[backendDeployAnswers.envName] = envEntry;
392
+ dockerEnvToAppend = getDockerEnvSecrets(backendDeployAnswers);
562
393
  }
563
394
  }
564
395
 
@@ -611,6 +442,9 @@ export function registerInitCommand(program) {
611
442
  }
612
443
 
613
444
  await saveConfig(config, cwd);
445
+ if (dockerEnvToAppend) {
446
+ await appendEnv(dockerEnvToAppend, cwd);
447
+ }
614
448
  await addDeployhubToPackageJson(cliSource, cwd);
615
449
  await writeWorkflowFile(
616
450
  config.storage,
@@ -635,22 +469,28 @@ export function registerInitCommand(program) {
635
469
  const secrets = getRequiredSecrets(config.storage, deploy, environments, config);
636
470
 
637
471
  console.log('');
638
- console.log(chalk.green.bold('✓ DeployHub initialized successfully!'));
472
+ if (primaryDeployType) {
473
+ console.log(chalk.green.bold(`✔ Config generated for ${primaryDeployType} deployment.`));
474
+ printDeploymentNextSteps(primaryDeployType, secrets);
475
+ } else {
476
+ console.log(chalk.green.bold('✓ DeployHub initialized successfully!'));
477
+ console.log('');
478
+ console.log(chalk.bold('Next steps:'));
479
+ console.log(' 1. Copy .env.example to .env and fill in credentials');
480
+ if (secrets.length > 0) {
481
+ console.log(' 2. Add these secrets to GitHub (Settings → Secrets):');
482
+ secrets.forEach((s) => console.log(` • ${s}`));
483
+ }
484
+ console.log(` ${secrets.length > 0 ? '3' : '2'}. Run ${chalk.cyan('deployhub doctor')} to verify your setup`);
485
+ console.log(` ${secrets.length > 0 ? '4' : '3'}. Push to main — GitHub Actions will run ${chalk.cyan('deployhub build')} automatically`);
486
+ }
487
+
639
488
  console.log('');
640
489
  console.log(chalk.bold('Generated files:'));
641
490
  console.log(' • deployhub.config.json');
642
491
  console.log(' • .github/workflows/deployhub.yml');
643
492
  console.log(' • .env.example');
644
493
  console.log('');
645
- console.log(chalk.bold('Next steps:'));
646
- console.log(' 1. Copy .env.example to .env and fill in credentials');
647
- if (secrets.length > 0) {
648
- console.log(' 2. Add these secrets to GitHub (Settings → Secrets):');
649
- secrets.forEach((s) => console.log(` • ${s}`));
650
- }
651
- console.log(` ${secrets.length > 0 ? '3' : '2'}. Run ${chalk.cyan('deployhub doctor')} to verify your setup`);
652
- console.log(` ${secrets.length > 0 ? '4' : '3'}. Push to main — GitHub Actions will run ${chalk.cyan('deployhub build')} automatically`);
653
- console.log('');
654
494
  printAuthorFooter();
655
495
  });
656
496
  }
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { loadConfig, loadEnv } from '../core/config.js';
3
- import { rollbackToVersion } from '../rollback/engine.js';
3
+ import { rollbackToVersion } from '../utils/rollback/engine.js';
4
4
  import axios from 'axios';
5
5
 
6
6
  /**
@@ -22,6 +22,21 @@ const EnvironmentSchema = z.object({
22
22
  path: z.string().optional(),
23
23
  deployPath: z.string().optional(),
24
24
  keyPath: z.string().optional(),
25
+ sshPort: z.number().optional(),
26
+ ec2InstanceId: z.string().optional(),
27
+ awsRegion: z.string().optional(),
28
+ azureSubscriptionId: z.string().optional(),
29
+ azureResourceGroup: z.string().optional(),
30
+ azureVmName: z.string().optional(),
31
+ gcpProjectId: z.string().optional(),
32
+ gcpZone: z.string().optional(),
33
+ gcpInstanceName: z.string().optional(),
34
+ kubeconfig: z.string().optional(),
35
+ kubeContext: z.string().optional(),
36
+ kubeNamespace: z.string().optional(),
37
+ dockerImageName: z.string().optional(),
38
+ dockerRegistryUrl: z.string().optional(),
39
+ dockerHost: z.string().optional(),
25
40
  appName: z.string().optional(),
26
41
  framework: z.string().optional(),
27
42
  port: z.number().optional(),