@akash-chowdhury-24/deployhub 1.0.16 → 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.
Files changed (32) hide show
  1. package/README.md +264 -59
  2. package/package.json +4 -3
  3. package/src/commands/doctor.js +168 -170
  4. package/src/commands/init.js +80 -371
  5. package/src/commands/rollback.js +1 -1
  6. package/src/core/config.js +17 -13
  7. package/src/core/stages.js +0 -11
  8. package/src/deployment/deployment-env.js +625 -0
  9. package/src/deployment/index.js +0 -72
  10. package/src/deployment/init-helpers.js +389 -0
  11. package/src/deployment/init-prompts.js +432 -0
  12. package/src/deployment/providers/azure-vm.js +85 -2
  13. package/src/deployment/providers/docker.js +101 -8
  14. package/src/deployment/providers/ec2.js +88 -2
  15. package/src/deployment/providers/gcp-vm.js +89 -2
  16. package/src/deployment/providers/kubernetes.js +104 -7
  17. package/src/deployment/providers/ssh.js +21 -4
  18. package/src/utils/github-actions.js +28 -131
  19. package/src/utils/rollback/engine.js +69 -0
  20. package/src/deployment/providers/platforms/_shared.js +0 -167
  21. package/src/deployment/providers/platforms/aws-amplify.js +0 -367
  22. package/src/deployment/providers/platforms/azure-static-web-apps.js +0 -68
  23. package/src/deployment/providers/platforms/cloudflare-pages.js +0 -103
  24. package/src/deployment/providers/platforms/firebase-app-hosting.js +0 -95
  25. package/src/deployment/providers/platforms/firebase-hosting.js +0 -99
  26. package/src/deployment/providers/platforms/index.js +0 -44
  27. package/src/deployment/providers/platforms/netlify.js +0 -102
  28. package/src/deployment/providers/platforms/vercel.js +0 -92
  29. package/src/rollback/engine.js +0 -102
  30. package/src/utils/firebase-config-generator.js +0 -35
  31. package/src/utils/init-platform.js +0 -236
  32. package/src/utils/platform-env.js +0 -132
@@ -1,95 +0,0 @@
1
- import { createLogger } from '../../../logger/index.js';
2
- import {
3
- runCli,
4
- saveDeploymentRecord,
5
- checkUrlHealth,
6
- } from './_shared.js';
7
-
8
- /**
9
- * @param {import('../../../core/config.js').DeployHubConfig} config
10
- * @param {string} envName
11
- * @param {Record<string, string>} [env]
12
- */
13
- export function createFirebaseAppHostingProvider(config, envName, env = process.env) {
14
- const environment = config.environments[envName];
15
- const log = createLogger('firebase-app-hosting');
16
- const token = env.FIREBASE_TOKEN;
17
- const projectId = environment?.projectId || env.FIREBASE_PROJECT_ID;
18
- const backendName =
19
- environment?.backendName || env.FIREBASE_APP_HOSTING_BACKEND || config.project;
20
-
21
- async function deploy(artifactDir) {
22
- if (!token || !projectId) {
23
- throw new Error('FIREBASE_TOKEN and FIREBASE_PROJECT_ID are required');
24
- }
25
-
26
- log.info(`Deploying to Firebase App Hosting (${projectId}/${backendName})...`);
27
-
28
- const createResult = await runCli(
29
- `firebase apphosting:backends:list --project=${projectId} --token=${token} --non-interactive`,
30
- process.cwd()
31
- );
32
-
33
- const backendExists =
34
- createResult.exitCode === 0 && createResult.stdout.includes(backendName);
35
-
36
- if (!backendExists) {
37
- log.info(`Creating App Hosting backend "${backendName}"...`);
38
- await runCli(
39
- `firebase apphosting:backends:create --project=${projectId} --token=${token} --backend=${backendName} --non-interactive`,
40
- process.cwd()
41
- );
42
- }
43
-
44
- const result = await runCli(
45
- `firebase deploy --only apphosting --token=${token} --project=${projectId} --non-interactive`,
46
- process.cwd()
47
- );
48
-
49
- if (result.exitCode !== 0) {
50
- throw new Error(`Firebase App Hosting deploy failed: ${result.stderr || result.stdout}`);
51
- }
52
-
53
- await saveDeploymentRecord(
54
- artifactDir,
55
- {
56
- platform: 'firebase-app-hosting',
57
- deploymentId: `apphosting-${Date.now()}`,
58
- projectId,
59
- backendName,
60
- deploymentUrl: config.healthCheck?.url || '',
61
- },
62
- envName
63
- );
64
-
65
- log.success('Deployed to Firebase App Hosting');
66
- }
67
-
68
- async function rollback(artifactDir) {
69
- log.info('Firebase App Hosting rollback — re-deploying previous artifact...');
70
- await deploy(artifactDir);
71
- }
72
-
73
- async function healthCheck() {
74
- const url = config.healthCheck?.url;
75
- if (!url) return true;
76
- return checkUrlHealth(url);
77
- }
78
-
79
- async function testConnection() {
80
- if (!token || !projectId) {
81
- throw new Error('FIREBASE_TOKEN and FIREBASE_PROJECT_ID are required');
82
- }
83
- const result = await runCli(
84
- `firebase projects:list --token=${token} --non-interactive`,
85
- process.cwd()
86
- );
87
- if (result.exitCode !== 0) {
88
- throw new Error('Invalid FIREBASE_TOKEN');
89
- }
90
- }
91
-
92
- return { deploy, rollback, healthCheck, testConnection };
93
- }
94
-
95
- export default { createFirebaseAppHostingProvider };
@@ -1,99 +0,0 @@
1
- import { createLogger } from '../../../logger/index.js';
2
- import { ensureFirebaseJson } from '../../../utils/firebase-config-generator.js';
3
- import {
4
- runCli,
5
- saveDeploymentRecord,
6
- readPreviousPlatformDeployment,
7
- checkUrlHealth,
8
- getBuildOutputDir,
9
- } from './_shared.js';
10
-
11
- /**
12
- * @param {import('../../../core/config.js').DeployHubConfig} config
13
- * @param {string} envName
14
- * @param {Record<string, string>} [env]
15
- */
16
- export function createFirebaseHostingProvider(config, envName, env = process.env) {
17
- const environment = config.environments[envName];
18
- const log = createLogger('firebase-hosting');
19
- const token = env.FIREBASE_TOKEN;
20
- const projectId = environment?.projectId || env.FIREBASE_PROJECT_ID;
21
-
22
- async function deploy(artifactDir) {
23
- if (!token || !projectId) {
24
- throw new Error('FIREBASE_TOKEN and FIREBASE_PROJECT_ID are required');
25
- }
26
-
27
- const cwd = process.cwd();
28
- const buildOutput = getBuildOutputDir(config);
29
- await ensureFirebaseJson(buildOutput, cwd);
30
-
31
- log.info(`Deploying to Firebase Hosting (${projectId})...`);
32
- const result = await runCli(
33
- `firebase deploy --only hosting --token=${token} --project=${projectId} --non-interactive`,
34
- cwd
35
- );
36
-
37
- if (result.exitCode !== 0) {
38
- throw new Error(`Firebase deploy failed: ${result.stderr || result.stdout}`);
39
- }
40
-
41
- const urlMatch = (result.stdout || '').match(/Hosting URL:\s*(https:\/\/\S+)/i);
42
- const deploymentUrl = urlMatch?.[1] || config.healthCheck?.url || '';
43
-
44
- await saveDeploymentRecord(
45
- artifactDir,
46
- {
47
- platform: 'firebase-hosting',
48
- deploymentUrl,
49
- deploymentId: deploymentUrl,
50
- projectId,
51
- },
52
- envName
53
- );
54
-
55
- log.success(`Deployed to Firebase Hosting: ${deploymentUrl || projectId}`);
56
- }
57
-
58
- async function rollback(artifactDir) {
59
- if (!token || !projectId) {
60
- throw new Error('FIREBASE_TOKEN and FIREBASE_PROJECT_ID are required');
61
- }
62
-
63
- log.info('Rolling back Firebase Hosting...');
64
- const result = await runCli(
65
- `firebase hosting:rollback --token=${token} --project=${projectId} --non-interactive`,
66
- process.cwd()
67
- );
68
- if (result.exitCode !== 0) {
69
- throw new Error(`Firebase rollback failed: ${result.stderr || result.stdout}`);
70
- }
71
- log.success('Firebase Hosting rollback complete');
72
- }
73
-
74
- async function healthCheck() {
75
- const url = config.healthCheck?.url;
76
- if (!url) return true;
77
- return checkUrlHealth(url);
78
- }
79
-
80
- async function testConnection() {
81
- if (!token || !projectId) {
82
- throw new Error('FIREBASE_TOKEN and FIREBASE_PROJECT_ID are required');
83
- }
84
- const result = await runCli(
85
- `firebase projects:list --token=${token} --non-interactive`,
86
- process.cwd()
87
- );
88
- if (result.exitCode !== 0) {
89
- throw new Error('Invalid FIREBASE_TOKEN');
90
- }
91
- if (!result.stdout.includes(projectId)) {
92
- throw new Error(`Firebase project "${projectId}" not accessible`);
93
- }
94
- }
95
-
96
- return { deploy, rollback, healthCheck, testConnection };
97
- }
98
-
99
- export default { createFirebaseHostingProvider };
@@ -1,44 +0,0 @@
1
- import { createVercelProvider } from './vercel.js';
2
- import { createNetlifyProvider } from './netlify.js';
3
- import { createCloudflarePagesProvider } from './cloudflare-pages.js';
4
- import { createAwsAmplifyProvider } from './aws-amplify.js';
5
- import { createAzureStaticWebAppsProvider } from './azure-static-web-apps.js';
6
- import { createFirebaseHostingProvider } from './firebase-hosting.js';
7
- import { createFirebaseAppHostingProvider } from './firebase-app-hosting.js';
8
-
9
- /** @type {Record<string, Function>} */
10
- const PLATFORM_FACTORIES = {
11
- vercel: createVercelProvider,
12
- netlify: createNetlifyProvider,
13
- 'cloudflare-pages': createCloudflarePagesProvider,
14
- 'aws-amplify': createAwsAmplifyProvider,
15
- 'azure-static-web-apps': createAzureStaticWebAppsProvider,
16
- 'firebase-hosting': createFirebaseHostingProvider,
17
- 'firebase-app-hosting': createFirebaseAppHostingProvider,
18
- };
19
-
20
- /**
21
- * @param {string} platform
22
- * @param {import('../../../core/config.js').DeployHubConfig} config
23
- * @param {string} envName
24
- * @param {Record<string, string>} [env]
25
- */
26
- export function createPlatformProvider(platform, config, envName, env = process.env) {
27
- const factory = PLATFORM_FACTORIES[platform];
28
- if (!factory) {
29
- throw new Error(`Unknown platform: ${platform}`);
30
- }
31
- return factory(config, envName, env);
32
- }
33
-
34
- export {
35
- createVercelProvider,
36
- createNetlifyProvider,
37
- createCloudflarePagesProvider,
38
- createAwsAmplifyProvider,
39
- createAzureStaticWebAppsProvider,
40
- createFirebaseHostingProvider,
41
- createFirebaseAppHostingProvider,
42
- };
43
-
44
- export default { createPlatformProvider, PLATFORM_FACTORIES };
@@ -1,102 +0,0 @@
1
- import axios from 'axios';
2
- import { createLogger } from '../../../logger/index.js';
3
- import {
4
- runCli,
5
- saveDeploymentRecord,
6
- readPreviousPlatformDeployment,
7
- checkUrlHealth,
8
- resolveBuildOutputPath,
9
- } from './_shared.js';
10
-
11
- /**
12
- * @param {import('../../../core/config.js').DeployHubConfig} config
13
- * @param {string} envName
14
- * @param {Record<string, string>} [env]
15
- */
16
- export function createNetlifyProvider(config, envName, env = process.env) {
17
- const environment = config.environments[envName];
18
- const log = createLogger('netlify');
19
- const authToken = env.NETLIFY_AUTH_TOKEN;
20
- const siteId = environment?.siteId || env.NETLIFY_SITE_ID;
21
-
22
- async function deploy(artifactDir) {
23
- if (!authToken || !siteId) {
24
- throw new Error('NETLIFY_AUTH_TOKEN and NETLIFY_SITE_ID are required');
25
- }
26
-
27
- const buildDir = resolveBuildOutputPath(config);
28
- log.info(`Deploying to Netlify from ${buildDir}...`);
29
-
30
- const result = await runCli(
31
- `netlify deploy --prod --dir="${buildDir}" --auth=${authToken} --site=${siteId}`,
32
- process.cwd()
33
- );
34
-
35
- if (result.exitCode !== 0) {
36
- throw new Error(`Netlify deploy failed: ${result.stderr || result.stdout}`);
37
- }
38
-
39
- const deployIdMatch = (result.stdout || '').match(/Deploy ID:\s*(\S+)/i);
40
- const urlMatch = (result.stdout || '').match(/Website URL:\s*(https:\/\/\S+)/i);
41
- const deployId = deployIdMatch?.[1] || '';
42
- const deploymentUrl = urlMatch?.[1] || config.healthCheck?.url || '';
43
-
44
- await saveDeploymentRecord(
45
- artifactDir,
46
- {
47
- platform: 'netlify',
48
- deploymentUrl,
49
- deploymentId: deployId,
50
- siteId,
51
- },
52
- envName
53
- );
54
-
55
- log.success(`Deployed to Netlify: ${deploymentUrl || siteId}`);
56
- }
57
-
58
- async function rollback(artifactDir) {
59
- if (!authToken || !siteId) {
60
- throw new Error('NETLIFY_AUTH_TOKEN and NETLIFY_SITE_ID are required');
61
- }
62
-
63
- const previous = await readPreviousPlatformDeployment(artifactDir);
64
- const deployId = previous?.deploymentId;
65
- if (!deployId) {
66
- throw new Error('No previous Netlify deployment ID found for rollback');
67
- }
68
-
69
- log.info(`Restoring Netlify deploy ${deployId}...`);
70
- await axios.post(
71
- `https://api.netlify.com/api/v1/sites/${siteId}/deploys/${deployId}/restore`,
72
- {},
73
- { headers: { Authorization: `Bearer ${authToken}` } }
74
- );
75
- log.success('Netlify rollback complete');
76
- }
77
-
78
- async function healthCheck() {
79
- const url = config.healthCheck?.url;
80
- if (!url) return true;
81
- return checkUrlHealth(url);
82
- }
83
-
84
- async function testConnection() {
85
- if (!authToken) throw new Error('NETLIFY_AUTH_TOKEN is required');
86
- const response = await axios.get('https://api.netlify.com/api/v1/user', {
87
- headers: { Authorization: `Bearer ${authToken}` },
88
- });
89
- if (!response.data?.email) {
90
- throw new Error('Invalid NETLIFY_AUTH_TOKEN');
91
- }
92
- if (siteId) {
93
- await axios.get(`https://api.netlify.com/api/v1/sites/${siteId}`, {
94
- headers: { Authorization: `Bearer ${authToken}` },
95
- });
96
- }
97
- }
98
-
99
- return { deploy, rollback, healthCheck, testConnection };
100
- }
101
-
102
- export default { createNetlifyProvider };
@@ -1,92 +0,0 @@
1
- import { createLogger } from '../../../logger/index.js';
2
- import {
3
- runCli,
4
- saveDeploymentRecord,
5
- readPreviousPlatformDeployment,
6
- checkUrlHealth,
7
- } from './_shared.js';
8
-
9
- /**
10
- * @param {import('../../../core/config.js').DeployHubConfig} config
11
- * @param {string} envName
12
- * @param {Record<string, string>} [env]
13
- */
14
- export function createVercelProvider(config, envName, env = process.env) {
15
- const log = createLogger('vercel');
16
- const token = env.VERCEL_TOKEN;
17
- const orgId = env.VERCEL_ORG_ID;
18
- const projectId = env.VERCEL_PROJECT_ID;
19
-
20
- async function deploy(artifactDir) {
21
- if (!token) throw new Error('VERCEL_TOKEN is required');
22
-
23
- log.info('Deploying to Vercel...');
24
- const cwd = process.cwd();
25
- const deployEnv = {
26
- VERCEL_ORG_ID: orgId || '',
27
- VERCEL_PROJECT_ID: projectId || '',
28
- };
29
-
30
- const result = await runCli(
31
- `vercel deploy --prod --token=${token} --yes`,
32
- cwd,
33
- deployEnv
34
- );
35
-
36
- if (result.exitCode !== 0) {
37
- throw new Error(`Vercel deploy failed: ${result.stderr || result.stdout}`);
38
- }
39
-
40
- const output = result.stdout || '';
41
- const urlMatch = output.match(/https:\/\/[^\s]+\.vercel\.app/);
42
- const deploymentUrl = urlMatch ? urlMatch[0] : config.healthCheck?.url || '';
43
-
44
- await saveDeploymentRecord(
45
- artifactDir,
46
- {
47
- platform: 'vercel',
48
- deploymentUrl,
49
- deploymentId: deploymentUrl,
50
- },
51
- envName
52
- );
53
-
54
- log.success(`Deployed to Vercel: ${deploymentUrl || 'production'}`);
55
- }
56
-
57
- async function rollback(artifactDir) {
58
- if (!token) throw new Error('VERCEL_TOKEN is required');
59
-
60
- const previous = await readPreviousPlatformDeployment(artifactDir);
61
- const deploymentUrl = previous?.deploymentUrl || previous?.deploymentId;
62
-
63
- log.info('Rolling back on Vercel...');
64
- const cmd = deploymentUrl
65
- ? `vercel rollback ${deploymentUrl} --token=${token} --yes`
66
- : `vercel rollback --token=${token} --yes`;
67
-
68
- const result = await runCli(cmd, process.cwd());
69
- if (result.exitCode !== 0) {
70
- throw new Error(`Vercel rollback failed: ${result.stderr || result.stdout}`);
71
- }
72
- log.success('Vercel rollback complete');
73
- }
74
-
75
- async function healthCheck() {
76
- const url = config.healthCheck?.url;
77
- if (!url) return true;
78
- return checkUrlHealth(url);
79
- }
80
-
81
- async function testConnection() {
82
- if (!token) throw new Error('VERCEL_TOKEN is required');
83
- const result = await runCli(`vercel whoami --token=${token}`, process.cwd());
84
- if (result.exitCode !== 0) {
85
- throw new Error('Invalid VERCEL_TOKEN');
86
- }
87
- }
88
-
89
- return { deploy, rollback, healthCheck, testConnection };
90
- }
91
-
92
- export default { createVercelProvider };
@@ -1,102 +0,0 @@
1
- import { downloadFromFirst } from '../storage/index.js';
2
- import { createPlatformProvider } from '../deployment/providers/platforms/index.js';
3
- import { extractArtifact } from '../artifact/engine.js';
4
- import { createLogger } from '../logger/index.js';
5
- import fs from 'fs-extra';
6
- import path from 'path';
7
-
8
- /**
9
- * @param {import('../core/config.js').DeployHubConfig} config
10
- * @param {string} artifactDir
11
- * @param {string} envName
12
- */
13
- async function rollbackPlatformTarget(config, artifactDir, envName) {
14
- const envConfig = config.environments[envName];
15
- if (!envConfig) {
16
- throw new Error(`Environment "${envName}" not found in config`);
17
- }
18
-
19
- const deploymentPath = path.join(artifactDir, 'deployment.json');
20
- if (await fs.pathExists(deploymentPath)) {
21
- const data = await fs.readJson(deploymentPath);
22
- const deployments = data.deployments || data.platformDeployments || [];
23
- const record = deployments.find((d) => d.environmentName === envName) || data.lastDeployment;
24
- if (record?.platform) {
25
- const log = createLogger('rollback');
26
- log.info(
27
- `Rolling back ${envName} on ${record.platform} (deploy ${record.deployId || record.deploymentId || 'previous'})...`
28
- );
29
- }
30
- }
31
-
32
- if (envConfig.deploymentType === 'platform' || envConfig.frontendDeploymentType === 'platform') {
33
- const platform = envConfig.platform;
34
- if (!platform) {
35
- throw new Error(`No platform configured for environment "${envName}"`);
36
- }
37
- const provider = createPlatformProvider(platform, config, envName);
38
- await provider.rollback(artifactDir);
39
- return;
40
- }
41
-
42
- if (
43
- envConfig.frontendDeploymentType === 'platform' &&
44
- (envConfig.backendDeploymentType === 'server' || envConfig.type)
45
- ) {
46
- const platformProvider = createPlatformProvider(envConfig.platform, config, envName);
47
- await platformProvider.rollback(artifactDir);
48
- const { getDeploymentProvider } = await import('../deployment/index.js');
49
- const serverProvider = getDeploymentProvider(envConfig.type, config, envName);
50
- await serverProvider.rollback(artifactDir);
51
- return;
52
- }
53
-
54
- const { getDeploymentProvider } = await import('../deployment/index.js');
55
- const provider = getDeploymentProvider(envConfig.type, config, envName);
56
- await provider.rollback(artifactDir);
57
- }
58
-
59
- /**
60
- * @param {import('../core/config.js').DeployHubConfig} config
61
- * @param {string} version
62
- * @param {string} [cwd]
63
- */
64
- export async function rollbackToVersion(config, version, cwd = process.cwd()) {
65
- const log = createLogger('rollback');
66
- const remoteKey = `${config.project}/v${version}/artifact.zip`;
67
- const restoreDir = path.join(cwd, '.deployhub-restore', `v${version}`);
68
- const artifactDir = path.join(restoreDir, 'artifact');
69
-
70
- log.info(`Downloading artifact v${version}...`);
71
- await fs.emptyDir(restoreDir);
72
- await fs.ensureDir(artifactDir);
73
-
74
- const zipPath = path.join(artifactDir, 'artifact.zip');
75
- await downloadFromFirst(config.storage, remoteKey, zipPath);
76
-
77
- log.info('Extracting artifact for rollback...');
78
- const extractedDir = path.join(artifactDir, '_extracted');
79
- await fs.emptyDir(extractedDir);
80
- await extractArtifact(artifactDir, extractedDir);
81
-
82
- const extractedDeployment = path.join(extractedDir, 'deployment.json');
83
- if (await fs.pathExists(extractedDeployment)) {
84
- await fs.copy(extractedDeployment, path.join(artifactDir, 'deployment.json'));
85
- }
86
-
87
- const targets = config.deploy || [];
88
- if (targets.length === 0) {
89
- log.warn('No deployment targets configured');
90
- return artifactDir;
91
- }
92
-
93
- log.info('Executing platform-specific rollback from deployment.json...');
94
- for (const envName of targets) {
95
- await rollbackPlatformTarget(config, artifactDir, envName);
96
- }
97
-
98
- log.success(`Rollback to v${version} complete`);
99
- return artifactDir;
100
- }
101
-
102
- export default { rollbackToVersion };
@@ -1,35 +0,0 @@
1
- /**
2
- * Generate firebase.json for Firebase Hosting if missing.
3
- *
4
- * @param {string} buildOutput
5
- * @returns {object}
6
- */
7
- export function generateFirebaseHostingConfig(buildOutput = 'dist') {
8
- return {
9
- hosting: {
10
- public: buildOutput,
11
- ignore: ['firebase.json', '**/.*', '**/node_modules/**'],
12
- rewrites: [{ source: '**', destination: '/index.html' }],
13
- },
14
- };
15
- }
16
-
17
- /**
18
- * @param {string} buildOutput
19
- * @param {string} [cwd]
20
- */
21
- export async function ensureFirebaseJson(buildOutput = 'dist', cwd = process.cwd()) {
22
- const fs = (await import('fs-extra')).default;
23
- const path = (await import('path')).default;
24
- const firebasePath = path.join(cwd, 'firebase.json');
25
-
26
- if (await fs.pathExists(firebasePath)) {
27
- return firebasePath;
28
- }
29
-
30
- const config = generateFirebaseHostingConfig(buildOutput);
31
- await fs.writeJson(firebasePath, config, { spaces: 2 });
32
- return firebasePath;
33
- }
34
-
35
- export default { generateFirebaseHostingConfig, ensureFirebaseJson };