@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.
- package/README.md +215 -5
- package/package.json +4 -3
- package/src/commands/doctor.js +168 -30
- package/src/commands/init.js +62 -222
- package/src/commands/rollback.js +1 -1
- package/src/core/config.js +15 -0
- package/src/deployment/deployment-env.js +625 -0
- package/src/deployment/init-helpers.js +389 -0
- package/src/deployment/init-prompts.js +432 -0
- package/src/deployment/providers/azure-vm.js +85 -2
- package/src/deployment/providers/docker.js +101 -8
- package/src/deployment/providers/ec2.js +88 -2
- package/src/deployment/providers/gcp-vm.js +89 -2
- package/src/deployment/providers/kubernetes.js +104 -7
- package/src/deployment/providers/ssh.js +21 -4
- package/src/utils/github-actions.js +24 -37
- package/src/{rollback → utils/rollback}/engine.js +6 -6
package/src/commands/init.js
CHANGED
|
@@ -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) =>
|
|
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
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/commands/rollback.js
CHANGED
package/src/core/config.js
CHANGED
|
@@ -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(),
|