@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akash-chowdhury-24/deployhub",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Zero-configuration deployment and artifact manager",
5
5
  "type": "module",
6
6
  "main": "./src/cli/index.js",
@@ -15,10 +15,8 @@ import {
15
15
  writeWorkflowFile,
16
16
  getRequiredSecrets,
17
17
  generateEnvExampleContent,
18
- guessCliGithubRepo,
19
18
  addDeployhubToPackageJson,
20
- isGithubCliSource,
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 defaultCliRepo = await guessCliGithubRepo(cwd);
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: answers.cliSource,
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(answers.cliSource, cwd);
745
+ await addDeployhubToPackageJson(cliSource, cwd);
756
746
  await writeWorkflowFile(
757
747
  config.storage,
758
748
  deploy,
759
749
  environments,
760
750
  cwd,
761
- answers.cliSource,
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
- if (isGithubCliSource(answers.cliSource)) {
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(' 3. Add these secrets to GitHub (Settings → Secrets):');
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 ? '4' : '3'}. Run ${chalk.cyan('deployhub doctor')} to verify your setup`);
804
- console.log(` ${secrets.length > 0 ? '5' : '4'}. Push to main — GitHub Actions will run ${chalk.cyan('deployhub build')} automatically`);
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
  });
@@ -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
- async function deploy(artifactDir) {
25
- if (!appId) throw new Error('AMPLIFY_APP_ID is required');
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
- log.info('Triggering Amplify release job (GitHub connected)...');
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 main --job-type RELEASE`,
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('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');
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 main --output json`,
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', zipPath, zipUploadUrl], {
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 main --job-id ${jobId}`,
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
- 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
- };
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 main --job-id ${jobId} --job-type RETRY`,
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 main --job-type RELEASE`,
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
- const result = await runCli(
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 (result.exitCode !== 0) {
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
  }