@akash-chowdhury-24/deployhub 1.0.12 → 1.0.14

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akash-chowdhury-24/deployhub",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Zero-configuration deployment and artifact manager",
5
5
  "type": "module",
6
6
  "main": "./src/cli/index.js",
@@ -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,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs-extra';
3
- import { execa } from 'execa';
3
+ import archiver from 'archiver';
4
+ import axios from 'axios';
4
5
  import { createLogger } from '../../../logger/index.js';
5
6
  import {
6
7
  runCli,
@@ -9,6 +10,37 @@ 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
+
32
+ /**
33
+ * @param {string} zipPath
34
+ * @param {string} uploadUrl
35
+ */
36
+ async function uploadZipToAmplify(zipPath, uploadUrl) {
37
+ await axios.put(uploadUrl, fs.createReadStream(zipPath), {
38
+ headers: { 'Content-Type': 'application/zip' },
39
+ maxBodyLength: Infinity,
40
+ maxContentLength: Infinity,
41
+ });
42
+ }
43
+
12
44
  /**
13
45
  * @param {import('../../../core/config.js').DeployHubConfig} config
14
46
  * @param {string} envName
@@ -19,21 +51,68 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
19
51
  const log = createLogger('aws-amplify');
20
52
  const appId = environment?.appId || env.AMPLIFY_APP_ID;
21
53
  const region = environment?.region || env.AWS_REGION || 'us-east-1';
54
+ const branch = environment?.branch || env.AMPLIFY_BRANCH || 'main';
22
55
  const githubConnected = environment?.githubConnected ?? false;
23
56
 
24
- async function deploy(artifactDir) {
25
- if (!appId) throw new Error('AMPLIFY_APP_ID is required');
26
-
27
- const awsEnv = {
57
+ function getAwsEnv() {
58
+ return {
28
59
  AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID || '',
29
60
  AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY || '',
30
61
  AWS_DEFAULT_REGION: region,
31
62
  };
63
+ }
64
+
65
+ /**
66
+ * @param {string} stage
67
+ */
68
+ function resolveAmplifyStage(stage) {
69
+ const normalized = stage.toLowerCase();
70
+ if (normalized === 'production') return 'PRODUCTION';
71
+ if (normalized === 'staging') return 'BETA';
72
+ return 'DEVELOPMENT';
73
+ }
74
+
75
+ async function branchExists(awsEnv) {
76
+ const result = await runCli(
77
+ `aws amplify get-branch --app-id ${appId} --branch-name ${branch}`,
78
+ process.cwd(),
79
+ awsEnv
80
+ );
81
+ return result.exitCode === 0;
82
+ }
83
+
84
+ async function ensureBranchExists(awsEnv) {
85
+ if (await branchExists(awsEnv)) return;
86
+
87
+ if (githubConnected) {
88
+ throw new Error(
89
+ `Amplify branch "${branch}" not found. Connect your GitHub repo in the Amplify console or choose a branch that already exists.`
90
+ );
91
+ }
92
+
93
+ const stage = resolveAmplifyStage(envName);
94
+ log.info(`Creating Amplify branch "${branch}" for manual zip deploys...`);
95
+ const createResult = await runCli(
96
+ `aws amplify create-branch --app-id ${appId} --branch-name ${branch} --stage ${stage} --no-enable-auto-build --description "Created by DeployHub for manual deployments"`,
97
+ process.cwd(),
98
+ awsEnv
99
+ );
100
+ if (createResult.exitCode !== 0) {
101
+ throw new Error(`Amplify create-branch failed: ${createResult.stderr || createResult.stdout}`);
102
+ }
103
+ log.success(`Amplify branch "${branch}" ready`);
104
+ }
105
+
106
+ async function deploy(artifactDir) {
107
+ if (!appId) throw new Error('AMPLIFY_APP_ID is required');
108
+
109
+ const awsEnv = getAwsEnv();
32
110
 
33
111
  if (githubConnected) {
34
- log.info('Triggering Amplify release job (GitHub connected)...');
112
+ await ensureBranchExists(awsEnv);
113
+ log.info(`Triggering Amplify release job on branch ${branch} (GitHub connected)...`);
35
114
  const result = await runCli(
36
- `aws amplify start-job --app-id ${appId} --branch-name main --job-type RELEASE`,
115
+ `aws amplify start-job --app-id ${appId} --branch-name ${branch} --job-type RELEASE`,
37
116
  process.cwd(),
38
117
  awsEnv
39
118
  );
@@ -50,6 +129,7 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
50
129
  platform: 'aws-amplify',
51
130
  deploymentId: jobId,
52
131
  appId,
132
+ branch,
53
133
  method: 'github',
54
134
  },
55
135
  envName
@@ -58,14 +138,22 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
58
138
  return;
59
139
  }
60
140
 
61
- log.info('Uploading artifact zip to Amplify...');
62
- const zipPath = path.join(artifactDir, 'artifact.zip');
63
- if (!(await fs.pathExists(zipPath))) {
64
- throw new Error('artifact.zip not found for Amplify upload');
141
+ log.info(`Uploading build output to Amplify branch ${branch}...`);
142
+ await ensureBranchExists(awsEnv);
143
+
144
+ const buildOutput = config.frontend?.buildOutput || config.buildOutput || 'dist';
145
+ const buildDir = path.join(process.cwd(), buildOutput);
146
+ if (!(await fs.pathExists(buildDir))) {
147
+ throw new Error(
148
+ `Build output not found at ${buildOutput}. Amplify needs the built static files, not artifact.zip with a nested folder.`
149
+ );
65
150
  }
66
151
 
152
+ const amplifyZipPath = path.join(artifactDir, 'amplify-deploy.zip');
153
+ await createRootZip(buildDir, amplifyZipPath);
154
+
67
155
  const createResult = await runCli(
68
- `aws amplify create-deployment --app-id ${appId} --branch-name main --output json`,
156
+ `aws amplify create-deployment --app-id ${appId} --branch-name ${branch} --output json`,
69
157
  process.cwd(),
70
158
  awsEnv
71
159
  );
@@ -77,14 +165,11 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
77
165
  const jobId = deployment.jobId;
78
166
  const zipUploadUrl = deployment.zipUploadUrl;
79
167
 
80
- await execa('curl', ['-T', zipPath, zipUploadUrl], {
81
- env: { ...process.env, ...awsEnv },
82
- stdio: 'inherit',
83
- shell: true,
84
- });
168
+ log.info('Uploading zip to Amplify...');
169
+ await uploadZipToAmplify(amplifyZipPath, zipUploadUrl);
85
170
 
86
171
  const startResult = await runCli(
87
- `aws amplify start-deployment --app-id ${appId} --branch-name main --job-id ${jobId}`,
172
+ `aws amplify start-deployment --app-id ${appId} --branch-name ${branch} --job-id ${jobId}`,
88
173
  process.cwd(),
89
174
  awsEnv
90
175
  );
@@ -92,12 +177,15 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
92
177
  throw new Error(`Amplify start-deployment failed: ${startResult.stderr}`);
93
178
  }
94
179
 
180
+ await fs.remove(amplifyZipPath);
181
+
95
182
  await saveDeploymentRecord(
96
183
  artifactDir,
97
184
  {
98
185
  platform: 'aws-amplify',
99
186
  deploymentId: jobId,
100
187
  appId,
188
+ branch,
101
189
  method: 'zip',
102
190
  },
103
191
  envName
@@ -114,21 +202,18 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
114
202
  throw new Error('No previous Amplify job ID found for rollback');
115
203
  }
116
204
 
117
- const awsEnv = {
118
- AWS_ACCESS_KEY_ID: env.AWS_ACCESS_KEY_ID || '',
119
- AWS_SECRET_ACCESS_KEY: env.AWS_SECRET_ACCESS_KEY || '',
120
- AWS_DEFAULT_REGION: region,
121
- };
205
+ const awsEnv = getAwsEnv();
206
+ const rollbackBranch = previous?.branch || branch;
122
207
 
123
- log.info(`Re-triggering Amplify job ${jobId}...`);
208
+ log.info(`Re-triggering Amplify job ${jobId} on branch ${rollbackBranch}...`);
124
209
  const result = await runCli(
125
- `aws amplify start-job --app-id ${appId} --branch-name main --job-id ${jobId} --job-type RETRY`,
210
+ `aws amplify start-job --app-id ${appId} --branch-name ${rollbackBranch} --job-id ${jobId} --job-type RETRY`,
126
211
  process.cwd(),
127
212
  awsEnv
128
213
  );
129
214
  if (result.exitCode !== 0) {
130
215
  await runCli(
131
- `aws amplify start-job --app-id ${appId} --branch-name main --job-type RELEASE`,
216
+ `aws amplify start-job --app-id ${appId} --branch-name ${rollbackBranch} --job-type RELEASE`,
132
217
  process.cwd(),
133
218
  awsEnv
134
219
  );
@@ -144,18 +229,28 @@ export function createAwsAmplifyProvider(config, envName, env = process.env) {
144
229
 
145
230
  async function testConnection() {
146
231
  if (!appId) throw new Error('AMPLIFY_APP_ID is required');
147
- const result = await runCli(
232
+
233
+ const awsEnv = getAwsEnv();
234
+ const appResult = await runCli(
148
235
  `aws amplify get-app --app-id ${appId}`,
149
236
  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
- }
237
+ awsEnv
155
238
  );
156
- if (result.exitCode !== 0) {
239
+ if (appResult.exitCode !== 0) {
157
240
  throw new Error('Could not find Amplify app — check AMPLIFY_APP_ID and AWS credentials');
158
241
  }
242
+
243
+ if (await branchExists(awsEnv)) return;
244
+
245
+ if (githubConnected) {
246
+ throw new Error(
247
+ `Amplify branch "${branch}" not found. Connect your GitHub repo in the Amplify console or choose a branch that already exists.`
248
+ );
249
+ }
250
+
251
+ log.info(
252
+ `Amplify branch "${branch}" will be created automatically on the first deploy`
253
+ );
159
254
  }
160
255
 
161
256
  return { deploy, rollback, healthCheck, testConnection };
@@ -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
  }