@akash-chowdhury-24/deployhub 1.0.11 → 1.0.13
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/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -15,10 +15,8 @@ import {
|
|
|
15
15
|
writeWorkflowFile,
|
|
16
16
|
getRequiredSecrets,
|
|
17
17
|
generateEnvExampleContent,
|
|
18
|
-
guessCliGithubRepo,
|
|
19
18
|
addDeployhubToPackageJson,
|
|
20
|
-
|
|
21
|
-
normalizeGithubCliSource,
|
|
19
|
+
DEFAULT_NPM_CLI_SOURCE,
|
|
22
20
|
} from '../utils/github-actions.js';
|
|
23
21
|
import {
|
|
24
22
|
promptPlatformQuestions,
|
|
@@ -365,7 +363,7 @@ export function registerInitCommand(program) {
|
|
|
365
363
|
const detectedFrontend = detectFrontend(cwd);
|
|
366
364
|
const detectedBackend = detectBackend(cwd);
|
|
367
365
|
const defaultName = path.basename(cwd) || 'my-app';
|
|
368
|
-
const
|
|
366
|
+
const cliSource = DEFAULT_NPM_CLI_SOURCE;
|
|
369
367
|
|
|
370
368
|
const { projectName } = await inquirer.prompt([
|
|
371
369
|
{
|
|
@@ -448,14 +446,6 @@ export function registerInitCommand(program) {
|
|
|
448
446
|
message: 'Configure deployment?',
|
|
449
447
|
default: false,
|
|
450
448
|
},
|
|
451
|
-
{
|
|
452
|
-
type: 'input',
|
|
453
|
-
name: 'cliSource',
|
|
454
|
-
message:
|
|
455
|
-
'DeployHub CLI source for GitHub Actions (github:user/repo or npm:@akash-chowdhury-24/deployhub):',
|
|
456
|
-
default: defaultCliRepo,
|
|
457
|
-
filter: (value) => normalizeGithubCliSource(value),
|
|
458
|
-
},
|
|
459
449
|
]);
|
|
460
450
|
|
|
461
451
|
/** @type {Record<string, Record<string, unknown>>} */
|
|
@@ -734,7 +724,7 @@ export function registerInitCommand(program) {
|
|
|
734
724
|
},
|
|
735
725
|
artifactRetention: 10,
|
|
736
726
|
cli: {
|
|
737
|
-
source:
|
|
727
|
+
source: cliSource,
|
|
738
728
|
},
|
|
739
729
|
};
|
|
740
730
|
|
|
@@ -752,13 +742,13 @@ export function registerInitCommand(program) {
|
|
|
752
742
|
}
|
|
753
743
|
|
|
754
744
|
await saveConfig(config, cwd);
|
|
755
|
-
await addDeployhubToPackageJson(
|
|
745
|
+
await addDeployhubToPackageJson(cliSource, cwd);
|
|
756
746
|
await writeWorkflowFile(
|
|
757
747
|
config.storage,
|
|
758
748
|
deploy,
|
|
759
749
|
environments,
|
|
760
750
|
cwd,
|
|
761
|
-
|
|
751
|
+
cliSource,
|
|
762
752
|
config
|
|
763
753
|
);
|
|
764
754
|
|
|
@@ -784,24 +774,13 @@ export function registerInitCommand(program) {
|
|
|
784
774
|
console.log(' • .env.example');
|
|
785
775
|
console.log('');
|
|
786
776
|
console.log(chalk.bold('Next steps:'));
|
|
787
|
-
|
|
788
|
-
console.log(
|
|
789
|
-
' 1. For a private DeployHub CLI repo, create a GitHub PAT with repo read access'
|
|
790
|
-
);
|
|
791
|
-
console.log(
|
|
792
|
-
` and add it as ${chalk.cyan('DEPLOYHUB_GITHUB_TOKEN')} in your demo project secrets`
|
|
793
|
-
);
|
|
794
|
-
console.log(' (public CLI repos can skip this — HTTPS is used automatically)');
|
|
795
|
-
} else {
|
|
796
|
-
console.log(' 1. Push the DeployHub CLI repo to GitHub (if using github: source)');
|
|
797
|
-
}
|
|
798
|
-
console.log(' 2. Copy .env.example to .env and fill in credentials');
|
|
777
|
+
console.log(' 1. Copy .env.example to .env and fill in credentials');
|
|
799
778
|
if (secrets.length > 0) {
|
|
800
|
-
console.log('
|
|
779
|
+
console.log(' 2. Add these secrets to GitHub (Settings → Secrets):');
|
|
801
780
|
secrets.forEach((s) => console.log(` • ${s}`));
|
|
802
781
|
}
|
|
803
|
-
console.log(` ${secrets.length > 0 ? '
|
|
804
|
-
console.log(` ${secrets.length > 0 ? '
|
|
782
|
+
console.log(` ${secrets.length > 0 ? '3' : '2'}. Run ${chalk.cyan('deployhub doctor')} to verify your setup`);
|
|
783
|
+
console.log(` ${secrets.length > 0 ? '4' : '3'}. Push to main — GitHub Actions will run ${chalk.cyan('deployhub build')} automatically`);
|
|
805
784
|
console.log('');
|
|
806
785
|
printAuthorFooter();
|
|
807
786
|
});
|
package/src/core/config.js
CHANGED
|
@@ -21,6 +21,7 @@ const EnvironmentSchema = z.object({
|
|
|
21
21
|
accountId: z.string().optional(),
|
|
22
22
|
appId: z.string().optional(),
|
|
23
23
|
region: z.string().optional(),
|
|
24
|
+
branch: z.string().optional(),
|
|
24
25
|
githubConnected: z.boolean().optional(),
|
|
25
26
|
resourceName: z.string().optional(),
|
|
26
27
|
projectId: z.string().optional(),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
|
+
import archiver from 'archiver';
|
|
3
4
|
import { execa } from 'execa';
|
|
4
5
|
import { createLogger } from '../../../logger/index.js';
|
|
5
6
|
import {
|
|
@@ -9,6 +10,25 @@ import {
|
|
|
9
10
|
checkUrlHealth,
|
|
10
11
|
} from './_shared.js';
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} sourceDir
|
|
15
|
+
* @param {string} zipPath
|
|
16
|
+
*/
|
|
17
|
+
async function createRootZip(sourceDir, zipPath) {
|
|
18
|
+
await fs.ensureDir(path.dirname(zipPath));
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const output = fs.createWriteStream(zipPath);
|
|
21
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
22
|
+
|
|
23
|
+
output.on('close', resolve);
|
|
24
|
+
archive.on('error', reject);
|
|
25
|
+
|
|
26
|
+
archive.pipe(output);
|
|
27
|
+
archive.directory(sourceDir, false);
|
|
28
|
+
archive.finalize();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
/**
|
|
13
33
|
* @param {import('../../../core/config.js').DeployHubConfig} config
|
|
14
34
|
* @param {string} envName
|
|
@@ -19,21 +39,68 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
|
|
|
19
39
|
const log = createLogger('aws-amplify');
|
|
20
40
|
const appId = environment?.appId || env.AMPLIFY_APP_ID;
|
|
21
41
|
const region = environment?.region || env.AWS_REGION || 'us-east-1';
|
|
42
|
+
const branch = environment?.branch || env.AMPLIFY_BRANCH || 'main';
|
|
22
43
|
const githubConnected = environment?.githubConnected ?? false;
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const awsEnv = {
|
|
45
|
+
function getAwsEnv() {
|
|
46
|
+
return {
|
|
28
47
|
AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID || '',
|
|
29
48
|
AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY || '',
|
|
30
49
|
AWS_DEFAULT_REGION: region,
|
|
31
50
|
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} stage
|
|
55
|
+
*/
|
|
56
|
+
function resolveAmplifyStage(stage) {
|
|
57
|
+
const normalized = stage.toLowerCase();
|
|
58
|
+
if (normalized === 'production') return 'PRODUCTION';
|
|
59
|
+
if (normalized === 'staging') return 'BETA';
|
|
60
|
+
return 'DEVELOPMENT';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function branchExists(awsEnv) {
|
|
64
|
+
const result = await runCli(
|
|
65
|
+
`aws amplify get-branch --app-id ${appId} --branch-name ${branch}`,
|
|
66
|
+
process.cwd(),
|
|
67
|
+
awsEnv
|
|
68
|
+
);
|
|
69
|
+
return result.exitCode === 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function ensureBranchExists(awsEnv) {
|
|
73
|
+
if (await branchExists(awsEnv)) return;
|
|
74
|
+
|
|
75
|
+
if (githubConnected) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Amplify branch "${branch}" not found. Connect your GitHub repo in the Amplify console or choose a branch that already exists.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const stage = resolveAmplifyStage(envName);
|
|
82
|
+
log.info(`Creating Amplify branch "${branch}" for manual zip deploys...`);
|
|
83
|
+
const createResult = await runCli(
|
|
84
|
+
`aws amplify create-branch --app-id ${appId} --branch-name ${branch} --stage ${stage} --no-enable-auto-build --description "Created by DeployHub for manual deployments"`,
|
|
85
|
+
process.cwd(),
|
|
86
|
+
awsEnv
|
|
87
|
+
);
|
|
88
|
+
if (createResult.exitCode !== 0) {
|
|
89
|
+
throw new Error(`Amplify create-branch failed: ${createResult.stderr || createResult.stdout}`);
|
|
90
|
+
}
|
|
91
|
+
log.success(`Amplify branch "${branch}" ready`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function deploy(artifactDir) {
|
|
95
|
+
if (!appId) throw new Error('AMPLIFY_APP_ID is required');
|
|
96
|
+
|
|
97
|
+
const awsEnv = getAwsEnv();
|
|
32
98
|
|
|
33
99
|
if (githubConnected) {
|
|
34
|
-
|
|
100
|
+
await ensureBranchExists(awsEnv);
|
|
101
|
+
log.info(`Triggering Amplify release job on branch ${branch} (GitHub connected)...`);
|
|
35
102
|
const result = await runCli(
|
|
36
|
-
`aws amplify start-job --app-id ${appId} --branch-name
|
|
103
|
+
`aws amplify start-job --app-id ${appId} --branch-name ${branch} --job-type RELEASE`,
|
|
37
104
|
process.cwd(),
|
|
38
105
|
awsEnv
|
|
39
106
|
);
|
|
@@ -50,6 +117,7 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
|
|
|
50
117
|
platform: 'aws-amplify',
|
|
51
118
|
deploymentId: jobId,
|
|
52
119
|
appId,
|
|
120
|
+
branch,
|
|
53
121
|
method: 'github',
|
|
54
122
|
},
|
|
55
123
|
envName
|
|
@@ -58,14 +126,22 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
|
|
|
58
126
|
return;
|
|
59
127
|
}
|
|
60
128
|
|
|
61
|
-
log.info(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
129
|
+
log.info(`Uploading build output to Amplify branch ${branch}...`);
|
|
130
|
+
await ensureBranchExists(awsEnv);
|
|
131
|
+
|
|
132
|
+
const buildOutput = config.frontend?.buildOutput || config.buildOutput || 'dist';
|
|
133
|
+
const buildDir = path.join(process.cwd(), buildOutput);
|
|
134
|
+
if (!(await fs.pathExists(buildDir))) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Build output not found at ${buildOutput}. Amplify needs the built static files, not artifact.zip with a nested folder.`
|
|
137
|
+
);
|
|
65
138
|
}
|
|
66
139
|
|
|
140
|
+
const amplifyZipPath = path.join(artifactDir, 'amplify-deploy.zip');
|
|
141
|
+
await createRootZip(buildDir, amplifyZipPath);
|
|
142
|
+
|
|
67
143
|
const createResult = await runCli(
|
|
68
|
-
`aws amplify create-deployment --app-id ${appId} --branch-name
|
|
144
|
+
`aws amplify create-deployment --app-id ${appId} --branch-name ${branch} --output json`,
|
|
69
145
|
process.cwd(),
|
|
70
146
|
awsEnv
|
|
71
147
|
);
|
|
@@ -77,14 +153,14 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
|
|
|
77
153
|
const jobId = deployment.jobId;
|
|
78
154
|
const zipUploadUrl = deployment.zipUploadUrl;
|
|
79
155
|
|
|
80
|
-
await execa('curl', ['-T',
|
|
156
|
+
await execa('curl', ['-T', amplifyZipPath, zipUploadUrl], {
|
|
81
157
|
env: { ...process.env, ...awsEnv },
|
|
82
158
|
stdio: 'inherit',
|
|
83
159
|
shell: true,
|
|
84
160
|
});
|
|
85
161
|
|
|
86
162
|
const startResult = await runCli(
|
|
87
|
-
`aws amplify start-deployment --app-id ${appId} --branch-name
|
|
163
|
+
`aws amplify start-deployment --app-id ${appId} --branch-name ${branch} --job-id ${jobId}`,
|
|
88
164
|
process.cwd(),
|
|
89
165
|
awsEnv
|
|
90
166
|
);
|
|
@@ -92,12 +168,15 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
|
|
|
92
168
|
throw new Error(`Amplify start-deployment failed: ${startResult.stderr}`);
|
|
93
169
|
}
|
|
94
170
|
|
|
171
|
+
await fs.remove(amplifyZipPath);
|
|
172
|
+
|
|
95
173
|
await saveDeploymentRecord(
|
|
96
174
|
artifactDir,
|
|
97
175
|
{
|
|
98
176
|
platform: 'aws-amplify',
|
|
99
177
|
deploymentId: jobId,
|
|
100
178
|
appId,
|
|
179
|
+
branch,
|
|
101
180
|
method: 'zip',
|
|
102
181
|
},
|
|
103
182
|
envName
|
|
@@ -114,21 +193,18 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
|
|
|
114
193
|
throw new Error('No previous Amplify job ID found for rollback');
|
|
115
194
|
}
|
|
116
195
|
|
|
117
|
-
const awsEnv =
|
|
118
|
-
|
|
119
|
-
AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY || '',
|
|
120
|
-
AWS_DEFAULT_REGION: region,
|
|
121
|
-
};
|
|
196
|
+
const awsEnv = getAwsEnv();
|
|
197
|
+
const rollbackBranch = previous?.branch || branch;
|
|
122
198
|
|
|
123
|
-
log.info(`Re-triggering Amplify job ${jobId}...`);
|
|
199
|
+
log.info(`Re-triggering Amplify job ${jobId} on branch ${rollbackBranch}...`);
|
|
124
200
|
const result = await runCli(
|
|
125
|
-
`aws amplify start-job --app-id ${appId} --branch-name
|
|
201
|
+
`aws amplify start-job --app-id ${appId} --branch-name ${rollbackBranch} --job-id ${jobId} --job-type RETRY`,
|
|
126
202
|
process.cwd(),
|
|
127
203
|
awsEnv
|
|
128
204
|
);
|
|
129
205
|
if (result.exitCode !== 0) {
|
|
130
206
|
await runCli(
|
|
131
|
-
`aws amplify start-job --app-id ${appId} --branch-name
|
|
207
|
+
`aws amplify start-job --app-id ${appId} --branch-name ${rollbackBranch} --job-type RELEASE`,
|
|
132
208
|
process.cwd(),
|
|
133
209
|
awsEnv
|
|
134
210
|
);
|
|
@@ -144,18 +220,28 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
|
|
|
144
220
|
|
|
145
221
|
async function testConnection() {
|
|
146
222
|
if (!appId) throw new Error('AMPLIFY_APP_ID is required');
|
|
147
|
-
|
|
223
|
+
|
|
224
|
+
const awsEnv = getAwsEnv();
|
|
225
|
+
const appResult = await runCli(
|
|
148
226
|
`aws amplify get-app --app-id ${appId}`,
|
|
149
227
|
process.cwd(),
|
|
150
|
-
|
|
151
|
-
AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID || '',
|
|
152
|
-
AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY || '',
|
|
153
|
-
AWS_DEFAULT_REGION: region,
|
|
154
|
-
}
|
|
228
|
+
awsEnv
|
|
155
229
|
);
|
|
156
|
-
if (
|
|
230
|
+
if (appResult.exitCode !== 0) {
|
|
157
231
|
throw new Error('Could not find Amplify app — check AMPLIFY_APP_ID and AWS credentials');
|
|
158
232
|
}
|
|
233
|
+
|
|
234
|
+
if (await branchExists(awsEnv)) return;
|
|
235
|
+
|
|
236
|
+
if (githubConnected) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`Amplify branch "${branch}" not found. Connect your GitHub repo in the Amplify console or choose a branch that already exists.`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
log.info(
|
|
243
|
+
`Amplify branch "${branch}" will be created automatically on the first deploy`
|
|
244
|
+
);
|
|
159
245
|
}
|
|
160
246
|
|
|
161
247
|
return { deploy, rollback, healthCheck, testConnection };
|
|
@@ -60,7 +60,7 @@ const ENV_VAR_DEFAULTS = {
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
const NPM_PACKAGE = '@akash-chowdhury-24/deployhub';
|
|
63
|
-
const DEFAULT_NPM_CLI_SOURCE = `npm:${NPM_PACKAGE}`;
|
|
63
|
+
export const DEFAULT_NPM_CLI_SOURCE = `npm:${NPM_PACKAGE}`;
|
|
64
64
|
export const GITHUB_CLI_TOKEN_SECRET = 'DEPLOYHUB_GITHUB_TOKEN';
|
|
65
65
|
|
|
66
66
|
/**
|
|
@@ -104,6 +104,12 @@ export async function promptPlatformQuestions(
|
|
|
104
104
|
message: 'AWS region:',
|
|
105
105
|
default: 'us-east-1',
|
|
106
106
|
},
|
|
107
|
+
{
|
|
108
|
+
type: 'input',
|
|
109
|
+
name: 'branch',
|
|
110
|
+
message: 'Amplify branch name (auto-created for manual zip deploys):',
|
|
111
|
+
default: 'main',
|
|
112
|
+
},
|
|
107
113
|
{
|
|
108
114
|
type: 'confirm',
|
|
109
115
|
name: 'githubConnected',
|
|
@@ -113,6 +119,7 @@ export async function promptPlatformQuestions(
|
|
|
113
119
|
]);
|
|
114
120
|
config.appId = answers.appId;
|
|
115
121
|
config.region = answers.region;
|
|
122
|
+
config.branch = answers.branch;
|
|
116
123
|
config.githubConnected = answers.githubConnected;
|
|
117
124
|
break;
|
|
118
125
|
}
|