@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
|
@@ -1,7 +1,93 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
1
2
|
import { createSshProvider } from './ssh.js';
|
|
3
|
+
import { createLogger } from '../../logger/index.js';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../../core/config.js').DeployHubConfig} config
|
|
7
|
+
* @param {string} envName
|
|
8
|
+
* @param {Record<string, string>} [env]
|
|
9
|
+
*/
|
|
10
|
+
export function createEc2Provider(config, envName, env = process.env) {
|
|
11
|
+
const log = createLogger('ec2');
|
|
12
|
+
const instanceId = env.EC2_INSTANCE_ID;
|
|
13
|
+
const region = env.AWS_REGION || 'us-east-1';
|
|
14
|
+
|
|
15
|
+
async function resolveHost() {
|
|
16
|
+
const environment = config.environments[envName];
|
|
17
|
+
if (environment?.host || env.SSH_HOST) {
|
|
18
|
+
return environment?.host || env.SSH_HOST;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!instanceId) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'EC2 host unknown. Set SSH_HOST to your instance public IP, or set EC2_INSTANCE_ID with AWS credentials for auto lookup.'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
log.info(`Looking up public IP for instance ${instanceId}...`);
|
|
28
|
+
|
|
29
|
+
/** @type {Record<string, string>} */
|
|
30
|
+
const awsEnv = { ...process.env };
|
|
31
|
+
if (env.AWS_ACCESS_KEY_ID) awsEnv.AWS_ACCESS_KEY_ID = env.AWS_ACCESS_KEY_ID;
|
|
32
|
+
if (env.AWS_SECRET_ACCESS_KEY) awsEnv.AWS_SECRET_ACCESS_KEY = env.AWS_SECRET_ACCESS_KEY;
|
|
33
|
+
awsEnv.AWS_DEFAULT_REGION = region;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const { stdout } = await execa(
|
|
37
|
+
'aws',
|
|
38
|
+
[
|
|
39
|
+
'ec2',
|
|
40
|
+
'describe-instances',
|
|
41
|
+
'--instance-ids',
|
|
42
|
+
instanceId,
|
|
43
|
+
'--query',
|
|
44
|
+
'Reservations[0].Instances[0].PublicIpAddress',
|
|
45
|
+
'--output',
|
|
46
|
+
'text',
|
|
47
|
+
'--region',
|
|
48
|
+
region,
|
|
49
|
+
],
|
|
50
|
+
{ stdio: 'pipe', env: awsEnv }
|
|
51
|
+
);
|
|
52
|
+
const publicIp = stdout.trim();
|
|
53
|
+
if (!publicIp || publicIp === 'None') {
|
|
54
|
+
throw new Error('No public IP returned');
|
|
55
|
+
}
|
|
56
|
+
log.info(`Resolved EC2 host: ${publicIp}`);
|
|
57
|
+
return publicIp;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Could not resolve public IP for ${instanceId} — ${msg}. Set SSH_HOST manually or install/configure AWS CLI with ec2:DescribeInstances access.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const sshProvider = createSshProvider(config, envName, env);
|
|
67
|
+
|
|
68
|
+
async function connect() {
|
|
69
|
+
const host = await resolveHost();
|
|
70
|
+
const environment = config.environments[envName];
|
|
71
|
+
if (environment && !environment.host) {
|
|
72
|
+
environment.host = host;
|
|
73
|
+
}
|
|
74
|
+
if (!env.SSH_HOST) {
|
|
75
|
+
env.SSH_HOST = host;
|
|
76
|
+
}
|
|
77
|
+
return sshProvider.connect();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...sshProvider,
|
|
82
|
+
connect,
|
|
83
|
+
deploy: sshProvider.deploy.bind(sshProvider),
|
|
84
|
+
rollback: sshProvider.rollback.bind(sshProvider),
|
|
85
|
+
healthCheck: sshProvider.healthCheck.bind(sshProvider),
|
|
86
|
+
testConnection: async () => {
|
|
87
|
+
const ssh = await connect();
|
|
88
|
+
ssh.dispose();
|
|
89
|
+
},
|
|
90
|
+
};
|
|
5
91
|
}
|
|
6
92
|
|
|
7
93
|
export default { createEc2Provider };
|
|
@@ -1,7 +1,94 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
1
2
|
import { createSshProvider } from './ssh.js';
|
|
3
|
+
import { createLogger } from '../../logger/index.js';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../../core/config.js').DeployHubConfig} config
|
|
7
|
+
* @param {string} envName
|
|
8
|
+
* @param {Record<string, string>} [env]
|
|
9
|
+
*/
|
|
10
|
+
export function createGcpVmProvider(config, envName, env = process.env) {
|
|
11
|
+
const log = createLogger('gcp-vm');
|
|
12
|
+
const projectId = env.GCP_PROJECT_ID;
|
|
13
|
+
const zone = env.GCP_ZONE;
|
|
14
|
+
const instanceName = env.GCP_INSTANCE_NAME;
|
|
15
|
+
|
|
16
|
+
async function resolveHost() {
|
|
17
|
+
const environment = config.environments[envName];
|
|
18
|
+
if (environment?.host || env.SSH_HOST) {
|
|
19
|
+
return environment?.host || env.SSH_HOST;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!projectId || !zone || !instanceName) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'GCP VM host unknown. Set SSH_HOST to your instance external IP, or set GCP_PROJECT_ID, GCP_ZONE, and GCP_INSTANCE_NAME for auto lookup.'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
log.info(`Looking up external IP for GCP instance ${instanceName}...`);
|
|
29
|
+
|
|
30
|
+
/** @type {Record<string, string>} */
|
|
31
|
+
const gcpEnv = { ...process.env };
|
|
32
|
+
if (env.GCP_KEY_FILE) {
|
|
33
|
+
gcpEnv.GOOGLE_APPLICATION_CREDENTIALS = env.GCP_KEY_FILE;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const { stdout } = await execa(
|
|
38
|
+
'gcloud',
|
|
39
|
+
[
|
|
40
|
+
'compute',
|
|
41
|
+
'instances',
|
|
42
|
+
'describe',
|
|
43
|
+
instanceName,
|
|
44
|
+
'--zone',
|
|
45
|
+
zone,
|
|
46
|
+
'--project',
|
|
47
|
+
projectId,
|
|
48
|
+
'--format',
|
|
49
|
+
'get(networkInterfaces[0].accessConfigs[0].natIP)',
|
|
50
|
+
],
|
|
51
|
+
{ stdio: 'pipe', env: gcpEnv }
|
|
52
|
+
);
|
|
53
|
+
const publicIp = stdout.trim();
|
|
54
|
+
if (!publicIp) {
|
|
55
|
+
throw new Error('No external IP returned');
|
|
56
|
+
}
|
|
57
|
+
log.info(`Resolved GCP VM host: ${publicIp}`);
|
|
58
|
+
return publicIp;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Could not resolve external IP for ${instanceName} — ${msg}. Set SSH_HOST manually or run gcloud auth login and verify project/zone/instance name.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const sshProvider = createSshProvider(config, envName, env);
|
|
68
|
+
|
|
69
|
+
async function connect() {
|
|
70
|
+
const host = await resolveHost();
|
|
71
|
+
const environment = config.environments[envName];
|
|
72
|
+
if (environment && !environment.host) {
|
|
73
|
+
environment.host = host;
|
|
74
|
+
}
|
|
75
|
+
if (!env.SSH_HOST) {
|
|
76
|
+
env.SSH_HOST = host;
|
|
77
|
+
}
|
|
78
|
+
return sshProvider.connect();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...sshProvider,
|
|
83
|
+
connect,
|
|
84
|
+
deploy: sshProvider.deploy.bind(sshProvider),
|
|
85
|
+
rollback: sshProvider.rollback.bind(sshProvider),
|
|
86
|
+
healthCheck: sshProvider.healthCheck.bind(sshProvider),
|
|
87
|
+
testConnection: async () => {
|
|
88
|
+
const ssh = await connect();
|
|
89
|
+
ssh.dispose();
|
|
90
|
+
},
|
|
91
|
+
};
|
|
5
92
|
}
|
|
6
93
|
|
|
7
94
|
export default { createGcpVmProvider };
|
|
@@ -1,27 +1,124 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
2
5
|
import { createLogger } from '../../logger/index.js';
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @param {import('../../core/config.js').DeployHubConfig} config
|
|
9
|
+
* @param {string} envName
|
|
10
|
+
* @param {Record<string, string>} [env]
|
|
11
|
+
*/
|
|
12
|
+
export function createKubernetesProvider(config, envName, env = process.env) {
|
|
5
13
|
const log = createLogger('kubernetes');
|
|
6
14
|
|
|
15
|
+
const kubeconfig = env.KUBECONFIG || path.join(os.homedir(), '.kube', 'config');
|
|
16
|
+
const context = env.KUBE_CONTEXT || '';
|
|
17
|
+
const namespace = env.KUBE_NAMESPACE || config.project || 'default';
|
|
18
|
+
|
|
19
|
+
function getKubectlEnv() {
|
|
20
|
+
const expanded = kubeconfig.replace(/^~/, os.homedir());
|
|
21
|
+
return { ...process.env, KUBECONFIG: path.resolve(expanded) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string[]} baseArgs
|
|
26
|
+
*/
|
|
27
|
+
function kubectlArgs(baseArgs) {
|
|
28
|
+
/** @type {string[]} */
|
|
29
|
+
const args = [...baseArgs];
|
|
30
|
+
if (context) args.push('--context', context);
|
|
31
|
+
if (namespace) args.push('--namespace', namespace);
|
|
32
|
+
return args;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} artifactDir
|
|
37
|
+
*/
|
|
7
38
|
async function deploy(artifactDir) {
|
|
8
|
-
log.info(
|
|
9
|
-
|
|
10
|
-
|
|
39
|
+
log.info(`Deploying to Kubernetes (namespace: ${namespace}${context ? `, context: ${context}` : ''})...`);
|
|
40
|
+
|
|
41
|
+
const manifestDir = artifactDir;
|
|
42
|
+
const hasManifests =
|
|
43
|
+
(await fs.pathExists(path.join(manifestDir, 'k8s'))) ||
|
|
44
|
+
(await fs.readdir(manifestDir)).some((f) => /\.ya?ml$/.test(f));
|
|
45
|
+
|
|
46
|
+
if (!hasManifests) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'No Kubernetes manifests found in artifact. Add .yaml files or a k8s/ directory to your project.'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const applyTarget = (await fs.pathExists(path.join(manifestDir, 'k8s')))
|
|
53
|
+
? path.join(manifestDir, 'k8s')
|
|
54
|
+
: manifestDir;
|
|
55
|
+
|
|
56
|
+
await execa('kubectl', kubectlArgs(['apply', '-f', applyTarget]), {
|
|
11
57
|
stdio: 'inherit',
|
|
58
|
+
env: getKubectlEnv(),
|
|
12
59
|
});
|
|
60
|
+
|
|
61
|
+
const imageName = env.DOCKER_IMAGE_NAME;
|
|
62
|
+
const imageTag = env.DOCKER_IMAGE_TAG || config.version || 'latest';
|
|
63
|
+
if (imageName && config.project) {
|
|
64
|
+
await execa(
|
|
65
|
+
'kubectl',
|
|
66
|
+
kubectlArgs([
|
|
67
|
+
'set',
|
|
68
|
+
'image',
|
|
69
|
+
`deployment/${config.project}`,
|
|
70
|
+
`${config.project}=${imageName}:${imageTag}`,
|
|
71
|
+
]),
|
|
72
|
+
{ stdio: 'pipe', env: getKubectlEnv() }
|
|
73
|
+
).catch(() => {
|
|
74
|
+
log.warn('kubectl set image skipped (deployment name may differ from project name)');
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log.success('Kubernetes deployment complete');
|
|
13
79
|
}
|
|
14
80
|
|
|
15
81
|
async function rollback(artifactDir) {
|
|
16
|
-
|
|
82
|
+
log.info('Rolling back Kubernetes deployment...');
|
|
83
|
+
await execa('kubectl', kubectlArgs(['rollout', 'undo', `deployment/${config.project}`]), {
|
|
84
|
+
stdio: 'inherit',
|
|
85
|
+
env: getKubectlEnv(),
|
|
86
|
+
}).catch(async () => {
|
|
87
|
+
log.warn('kubectl rollout undo failed — redeploying previous artifact instead');
|
|
88
|
+
await deploy(artifactDir);
|
|
89
|
+
});
|
|
17
90
|
}
|
|
18
91
|
|
|
19
92
|
async function healthCheck() {
|
|
20
|
-
|
|
93
|
+
const url = config.healthCheck?.url;
|
|
94
|
+
if (url) {
|
|
95
|
+
try {
|
|
96
|
+
const { stdout } = await execa('curl', ['-sf', '-o', '/dev/null', '-w', '%{http_code}', url], {
|
|
97
|
+
stdio: 'pipe',
|
|
98
|
+
});
|
|
99
|
+
return stdout.trim().startsWith('2');
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await execa(
|
|
107
|
+
'kubectl',
|
|
108
|
+
kubectlArgs(['rollout', 'status', `deployment/${config.project}`, '--timeout=30s']),
|
|
109
|
+
{ stdio: 'pipe', env: getKubectlEnv() }
|
|
110
|
+
);
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
21
115
|
}
|
|
22
116
|
|
|
23
117
|
async function testConnection() {
|
|
24
|
-
await execa('kubectl', ['cluster-info'], {
|
|
118
|
+
await execa('kubectl', kubectlArgs(['cluster-info']), {
|
|
119
|
+
stdio: 'pipe',
|
|
120
|
+
env: getKubectlEnv(),
|
|
121
|
+
});
|
|
25
122
|
}
|
|
26
123
|
|
|
27
124
|
return { deploy, rollback, healthCheck, testConnection };
|
|
@@ -39,27 +39,44 @@ export function createSshProvider(config, envName, env = process.env) {
|
|
|
39
39
|
const port = environment.port || config.port || Number(env.SSH_PORT) || 3000;
|
|
40
40
|
const sshKey = env.SSH_KEY;
|
|
41
41
|
const keyPath = environment.keyPath || env.SSH_KEY_PATH;
|
|
42
|
+
const sshPort = Number(env.SSH_SSH_PORT) || environment.sshPort || 22;
|
|
42
43
|
|
|
43
44
|
const log = createLogger('ssh');
|
|
44
45
|
|
|
45
46
|
async function connect() {
|
|
46
47
|
if (!host || !user) {
|
|
47
|
-
throw new Error(
|
|
48
|
+
throw new Error(
|
|
49
|
+
'SSH host and user are required. Set SSH_HOST and SSH_USER in .env (see .env.example comments).'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!sshKey && !keyPath) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
'SSH authentication required. Set SSH_KEY_PATH (local) or SSH_KEY (CI secret) in .env — see .env.example.'
|
|
56
|
+
);
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
const ssh = new NodeSSH();
|
|
51
60
|
/** @type {import('node-ssh').SSHConnectOptions} */
|
|
52
|
-
const connectOpts = { host, username: user };
|
|
61
|
+
const connectOpts = { host, username: user, port: sshPort };
|
|
53
62
|
|
|
54
63
|
if (sshKey) {
|
|
55
64
|
const tmpKeyPath = path.join(os.tmpdir(), 'deployhub-ssh-key');
|
|
56
65
|
await fs.writeFile(tmpKeyPath, sshKey, { mode: 0o600 });
|
|
57
66
|
connectOpts.privateKeyPath = tmpKeyPath;
|
|
58
67
|
} else if (keyPath) {
|
|
59
|
-
|
|
68
|
+
const expanded = keyPath.replace(/^~/, os.homedir());
|
|
69
|
+
connectOpts.privateKeyPath = path.resolve(expanded);
|
|
60
70
|
}
|
|
61
71
|
|
|
62
|
-
|
|
72
|
+
try {
|
|
73
|
+
await ssh.connect(connectOpts);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
76
|
+
throw new Error(
|
|
77
|
+
`SSH connection failed to ${user}@${host}:${sshPort} — ${msg}. Check SSH_HOST, SSH_USER, SSH_KEY_PATH, and that port ${sshPort} is open in your firewall/security group.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
63
80
|
return ssh;
|
|
64
81
|
}
|
|
65
82
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { getWorkflowHeaderComment } from './author.js';
|
|
4
|
+
import {
|
|
5
|
+
generateDeploymentEnvSection,
|
|
6
|
+
getDeploymentSecretKeys,
|
|
7
|
+
DEPLOYMENT_ENV_KEYS,
|
|
8
|
+
} from '../deployment/deployment-env.js';
|
|
4
9
|
|
|
5
10
|
/** @typedef {'aws'|'azure'|'gcp'|'gdrive'|'dropbox'|'local'|'ftp'|'ssh'} ProviderEnvKey */
|
|
6
11
|
|
|
@@ -22,23 +27,27 @@ const PROVIDER_ENV_MAP = {
|
|
|
22
27
|
dropbox: ['DROPBOX_ACCESS_TOKEN'],
|
|
23
28
|
local: [],
|
|
24
29
|
ftp: ['FTP_HOST', 'FTP_USER', 'FTP_PASSWORD'],
|
|
25
|
-
ssh:
|
|
30
|
+
ssh: DEPLOYMENT_ENV_KEYS.ssh,
|
|
31
|
+
docker: DEPLOYMENT_ENV_KEYS.docker,
|
|
32
|
+
ec2: DEPLOYMENT_ENV_KEYS.ec2,
|
|
33
|
+
'azure-vm': DEPLOYMENT_ENV_KEYS['azure-vm'],
|
|
34
|
+
'gcp-vm': DEPLOYMENT_ENV_KEYS['gcp-vm'],
|
|
35
|
+
kubernetes: DEPLOYMENT_ENV_KEYS.kubernetes,
|
|
26
36
|
};
|
|
27
37
|
|
|
28
|
-
const BACKEND_SSH_ENV_VARS = [
|
|
29
|
-
'SSH_DEPLOY_PATH',
|
|
30
|
-
'SSH_APP_NAME',
|
|
31
|
-
'SSH_PORT',
|
|
32
|
-
];
|
|
33
|
-
|
|
34
38
|
const PROVIDER_LABELS = {
|
|
35
39
|
aws: 'AWS S3',
|
|
36
40
|
azure: 'Azure Blob',
|
|
37
|
-
gcp: 'GCP',
|
|
41
|
+
gcp: 'GCP Storage',
|
|
38
42
|
gdrive: 'Google Drive',
|
|
39
43
|
dropbox: 'Dropbox',
|
|
40
44
|
ftp: 'FTP',
|
|
41
45
|
ssh: 'SSH Deployment',
|
|
46
|
+
docker: 'Docker Deployment',
|
|
47
|
+
ec2: 'AWS EC2 Deployment',
|
|
48
|
+
'azure-vm': 'Azure VM Deployment',
|
|
49
|
+
'gcp-vm': 'GCP VM Deployment',
|
|
50
|
+
kubernetes: 'Kubernetes Deployment',
|
|
42
51
|
};
|
|
43
52
|
|
|
44
53
|
const ENV_VAR_DEFAULTS = {
|
|
@@ -46,6 +55,7 @@ const ENV_VAR_DEFAULTS = {
|
|
|
46
55
|
FTP_PORT: '21',
|
|
47
56
|
FTP_PATH: '/uploads',
|
|
48
57
|
SSH_DEPLOY_PATH: '/var/www/app',
|
|
58
|
+
SSH_SSH_PORT: '22',
|
|
49
59
|
SMTP_PORT: '587',
|
|
50
60
|
};
|
|
51
61
|
|
|
@@ -227,19 +237,10 @@ export function generateWorkflowYaml(
|
|
|
227
237
|
const env = environments[envName];
|
|
228
238
|
if (!env) continue;
|
|
229
239
|
|
|
230
|
-
const keys =
|
|
240
|
+
const keys = getDeploymentSecretKeys(env.type, config);
|
|
231
241
|
for (const key of keys) {
|
|
232
242
|
envVars.add(`${key}: \${{ secrets.${key} }}`);
|
|
233
243
|
}
|
|
234
|
-
|
|
235
|
-
if (env.type === 'ssh' && config) {
|
|
236
|
-
const projectType = config.projectType || 'frontend';
|
|
237
|
-
if (projectType === 'backend' || projectType === 'both') {
|
|
238
|
-
for (const key of BACKEND_SSH_ENV_VARS) {
|
|
239
|
-
envVars.add(`${key}: \${{ secrets.${key} }}`);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
const envBlock = Array.from(envVars)
|
|
@@ -412,15 +413,8 @@ export function getRequiredSecrets(
|
|
|
412
413
|
const env = environments[envName];
|
|
413
414
|
if (!env) continue;
|
|
414
415
|
|
|
415
|
-
const keys =
|
|
416
|
+
const keys = getDeploymentSecretKeys(env.type, config);
|
|
416
417
|
keys.forEach((k) => secrets.add(k));
|
|
417
|
-
|
|
418
|
-
if (env.type === 'ssh' && config) {
|
|
419
|
-
const projectType = config.projectType || 'frontend';
|
|
420
|
-
if (projectType === 'backend' || projectType === 'both') {
|
|
421
|
-
BACKEND_SSH_ENV_VARS.forEach((k) => secrets.add(k));
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
418
|
}
|
|
425
419
|
|
|
426
420
|
return Array.from(secrets);
|
|
@@ -481,18 +475,11 @@ export function generateEnvExampleContent(
|
|
|
481
475
|
|
|
482
476
|
for (const envName of deployEnvironments) {
|
|
483
477
|
const env = environments[envName];
|
|
484
|
-
if (!env) continue;
|
|
485
|
-
|
|
486
|
-
const keys = PROVIDER_ENV_MAP[env.type] || [];
|
|
487
|
-
if (keys.length > 0) {
|
|
488
|
-
addSection(PROVIDER_LABELS[env.type] || env.type, keys);
|
|
489
|
-
}
|
|
478
|
+
if (!env?.type) continue;
|
|
490
479
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
addSection('SSH Deployment (backend)', BACKEND_SSH_ENV_VARS);
|
|
495
|
-
}
|
|
480
|
+
const deploySection = generateDeploymentEnvSection(env.type, config, environments);
|
|
481
|
+
if (deploySection) {
|
|
482
|
+
sections.push(`${deploySection}\n`);
|
|
496
483
|
}
|
|
497
484
|
}
|
|
498
485
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { downloadFromFirst } from '
|
|
2
|
-
import { getDeploymentProvider } from '
|
|
3
|
-
import { extractArtifact } from '
|
|
4
|
-
import { createLogger } from '
|
|
1
|
+
import { downloadFromFirst } from '../../storage/index.js';
|
|
2
|
+
import { getDeploymentProvider } from '../../deployment/index.js';
|
|
3
|
+
import { extractArtifact } from '../../artifact/engine.js';
|
|
4
|
+
import { createLogger } from '../../logger/index.js';
|
|
5
5
|
import fs from 'fs-extra';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* @param {import('
|
|
9
|
+
* @param {import('../../core/config.js').DeployHubConfig} config
|
|
10
10
|
* @param {string} artifactDir
|
|
11
11
|
* @param {string} envName
|
|
12
12
|
*/
|
|
@@ -24,7 +24,7 @@ async function rollbackTarget(config, artifactDir, envName) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* @param {import('
|
|
27
|
+
* @param {import('../../core/config.js').DeployHubConfig} config
|
|
28
28
|
* @param {string} version
|
|
29
29
|
* @param {string} [cwd]
|
|
30
30
|
*/
|