@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.
- package/README.md +264 -59
- package/package.json +4 -3
- package/src/commands/doctor.js +168 -170
- package/src/commands/init.js +80 -371
- package/src/commands/rollback.js +1 -1
- package/src/core/config.js +17 -13
- package/src/core/stages.js +0 -11
- package/src/deployment/deployment-env.js +625 -0
- package/src/deployment/index.js +0 -72
- 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 +28 -131
- package/src/utils/rollback/engine.js +69 -0
- package/src/deployment/providers/platforms/_shared.js +0 -167
- package/src/deployment/providers/platforms/aws-amplify.js +0 -367
- package/src/deployment/providers/platforms/azure-static-web-apps.js +0 -68
- package/src/deployment/providers/platforms/cloudflare-pages.js +0 -103
- package/src/deployment/providers/platforms/firebase-app-hosting.js +0 -95
- package/src/deployment/providers/platforms/firebase-hosting.js +0 -99
- package/src/deployment/providers/platforms/index.js +0 -44
- package/src/deployment/providers/platforms/netlify.js +0 -102
- package/src/deployment/providers/platforms/vercel.js +0 -92
- package/src/rollback/engine.js +0 -102
- package/src/utils/firebase-config-generator.js +0 -35
- package/src/utils/init-platform.js +0 -236
- 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 };
|
package/src/rollback/engine.js
DELETED
|
@@ -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 };
|