@akash-chowdhury-24/deployhub 1.0.7 → 1.0.9

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.7",
3
+ "version": "1.0.9",
4
4
  "description": "Zero-configuration deployment and artifact manager",
5
5
  "type": "module",
6
6
  "main": "./src/cli/index.js",
@@ -14,8 +14,11 @@ import { getProjectVersion } from '../utils/version.js';
14
14
  import {
15
15
  writeWorkflowFile,
16
16
  getRequiredSecrets,
17
+ generateEnvExampleContent,
17
18
  guessCliGithubRepo,
18
19
  addDeployhubToPackageJson,
20
+ isGithubCliSource,
21
+ normalizeGithubCliSource,
19
22
  } from '../utils/github-actions.js';
20
23
  import {
21
24
  promptPlatformQuestions,
@@ -288,25 +291,6 @@ function buildServerEnvEntry(
288
291
  return envEntry;
289
292
  }
290
293
 
291
- /**
292
- * @returns {Promise<string>}
293
- */
294
- async function getEnvExampleContent() {
295
- const possiblePaths = [
296
- path.join(path.dirname(process.execPath), '.env.example'),
297
- path.join(process.cwd(), '.env.example'),
298
- new URL('../../.env.example', import.meta.url).pathname,
299
- ];
300
- for (const p of possiblePaths) {
301
- try {
302
- return await fs.readFile(p, 'utf-8');
303
- } catch {
304
- // try next path
305
- }
306
- }
307
- return '# Add your environment variables here\n';
308
- }
309
-
310
294
  /**
311
295
  * @param {Record<string, unknown>} config
312
296
  * @param {Record<string, Record<string, unknown>>} environments
@@ -470,6 +454,7 @@ export function registerInitCommand(program) {
470
454
  message:
471
455
  'DeployHub CLI source for GitHub Actions (github:user/repo or npm:@akash-chowdhury-24/deployhub):',
472
456
  default: defaultCliRepo,
457
+ filter: (value) => normalizeGithubCliSource(value),
473
458
  },
474
459
  ]);
475
460
 
@@ -780,7 +765,12 @@ export function registerInitCommand(program) {
780
765
  await generateProjectScaffold(config, environments, cwd);
781
766
 
782
767
  const envExampleDest = path.join(cwd, '.env.example');
783
- const envExampleContent = await getEnvExampleContent();
768
+ const envExampleContent = generateEnvExampleContent(
769
+ config.storage,
770
+ deploy,
771
+ environments,
772
+ config
773
+ );
784
774
  await fs.writeFile(envExampleDest, envExampleContent);
785
775
 
786
776
  const secrets = getRequiredSecrets(config.storage, deploy, environments, config);
@@ -794,7 +784,17 @@ export function registerInitCommand(program) {
794
784
  console.log(' • .env.example');
795
785
  console.log('');
796
786
  console.log(chalk.bold('Next steps:'));
797
- console.log(' 1. Push the DeployHub CLI repo to GitHub (if using github: source)');
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
798
  console.log(' 2. Copy .env.example to .env and fill in credentials');
799
799
  if (secrets.length > 0) {
800
800
  console.log(' 3. Add these secrets to GitHub (Settings → Secrets):');
@@ -1,6 +1,11 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
- import { PLATFORM_ENV_MAP, PLATFORM_CLI_MAP } from './platform-env.js';
3
+ import {
4
+ PLATFORM_ENV_MAP,
5
+ PLATFORM_CLI_MAP,
6
+ PLATFORM_CHOICES,
7
+ getPlatformEnvExample,
8
+ } from './platform-env.js';
4
9
  import { getWorkflowHeaderComment } from './author.js';
5
10
 
6
11
  /** @typedef {'aws'|'azure'|'gcp'|'gdrive'|'dropbox'|'local'|'ftp'|'ssh'} ProviderEnvKey */
@@ -32,8 +37,68 @@ const BACKEND_SSH_ENV_VARS = [
32
37
  'SSH_PORT',
33
38
  ];
34
39
 
40
+ const PROVIDER_LABELS = {
41
+ aws: 'AWS S3',
42
+ azure: 'Azure Blob',
43
+ gcp: 'GCP',
44
+ gdrive: 'Google Drive',
45
+ dropbox: 'Dropbox',
46
+ ftp: 'FTP',
47
+ ssh: 'SSH Deployment',
48
+ };
49
+
50
+ const PLATFORM_LABELS = Object.fromEntries(
51
+ PLATFORM_CHOICES.map(({ name, value }) => [value, name])
52
+ );
53
+
54
+ const ENV_VAR_DEFAULTS = {
55
+ AWS_REGION: 'us-east-1',
56
+ FTP_PORT: '21',
57
+ FTP_PATH: '/uploads',
58
+ SSH_DEPLOY_PATH: '/var/www/app',
59
+ SMTP_PORT: '587',
60
+ };
61
+
35
62
  const NPM_PACKAGE = '@akash-chowdhury-24/deployhub';
36
63
  const DEFAULT_NPM_CLI_SOURCE = `npm:${NPM_PACKAGE}`;
64
+ export const GITHUB_CLI_TOKEN_SECRET = 'DEPLOYHUB_GITHUB_TOKEN';
65
+
66
+ /**
67
+ * @param {string} [cliSource]
68
+ * @returns {boolean}
69
+ */
70
+ export function isGithubCliSource(cliSource) {
71
+ const normalized = normalizeCliSource(cliSource);
72
+ return normalized.startsWith('github:');
73
+ }
74
+
75
+ /**
76
+ * Normalizes common GitHub remote formats to github:user/repo.
77
+ * @param {string} cliSource
78
+ * @returns {string}
79
+ */
80
+ export function normalizeGithubCliSource(cliSource) {
81
+ if (!cliSource) return cliSource;
82
+
83
+ const trimmed = cliSource.trim();
84
+ const httpsMatch = trimmed.match(
85
+ /^https?:\/\/github\.com\/([^/]+)\/([^/#?]+?)(?:\.git)?(?:[/?#].*)?$/
86
+ );
87
+ if (httpsMatch) {
88
+ return `github:${httpsMatch[1]}/${httpsMatch[2]}`;
89
+ }
90
+
91
+ const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/#?]+?)(?:\.git)?$/);
92
+ if (sshMatch) {
93
+ return `github:${sshMatch[1]}/${sshMatch[2]}`;
94
+ }
95
+
96
+ if (trimmed.startsWith('github:')) {
97
+ return trimmed.replace(/^github:/, 'github:').replace(/\.git$/, '');
98
+ }
99
+
100
+ return trimmed;
101
+ }
37
102
 
38
103
  /** @param {string} [cliSource] */
39
104
  function normalizeCliSource(cliSource) {
@@ -41,7 +106,7 @@ function normalizeCliSource(cliSource) {
41
106
  if (/^npm:(deployhub-cli|deploy-hub-cli|deployhub)$/.test(cliSource)) {
42
107
  return DEFAULT_NPM_CLI_SOURCE;
43
108
  }
44
- return cliSource;
109
+ return normalizeGithubCliSource(cliSource);
45
110
  }
46
111
 
47
112
  /**
@@ -62,6 +127,26 @@ export function getCliInstallSpec(cliSource) {
62
127
  return normalized;
63
128
  }
64
129
 
130
+ /**
131
+ * @returns {string}
132
+ */
133
+ function getGithubGitConfigStep() {
134
+ return ` - name: Configure GitHub access for DeployHub CLI
135
+ env:
136
+ ${GITHUB_CLI_TOKEN_SECRET}: \${{ secrets.${GITHUB_CLI_TOKEN_SECRET} }}
137
+ GITHUB_TOKEN: \${{ github.token }}
138
+ run: |
139
+ TOKEN="\${${GITHUB_CLI_TOKEN_SECRET}:-$GITHUB_TOKEN}"
140
+ if [ -n "$TOKEN" ]; then
141
+ git config --global url."https://oauth2:\${TOKEN}@github.com/".insteadOf "https://github.com/"
142
+ git config --global url."https://oauth2:\${TOKEN}@github.com/".insteadOf "git@github.com:"
143
+ git config --global url."https://oauth2:\${TOKEN}@github.com/".insteadOf "ssh://git@github.com/"
144
+ else
145
+ git config --global url."https://github.com/".insteadOf "ssh://git@github.com/"
146
+ git config --global url."https://github.com/".insteadOf "git@github.com:"
147
+ fi`;
148
+ }
149
+
65
150
  /**
66
151
  * @param {import('../core/config.js').DeployHubConfig} [config]
67
152
  * @returns {string[]}
@@ -219,6 +304,9 @@ export function generateWorkflowYaml(
219
304
  const installSpec = getCliInstallSpec(cliSource);
220
305
  const backendSteps = getBackendSetupSteps(config);
221
306
  const uniqueBackendSteps = [...new Set(backendSteps)].join('\n');
307
+ const githubGitConfigStep = isGithubCliSource(cliSource)
308
+ ? `${getGithubGitConfigStep()}\n`
309
+ : '';
222
310
 
223
311
  const envList = deployEnvironments
224
312
  .map((name) => environments[name])
@@ -243,7 +331,7 @@ jobs:
243
331
  ${uniqueBackendSteps ? `${uniqueBackendSteps}\n` : ''} - uses: actions/setup-node@v4
244
332
  with:
245
333
  node-version: '20'
246
- - name: Install project dependencies
334
+ ${githubGitConfigStep} - name: Install project dependencies
247
335
  run: ${installDepsCommand}
248
336
  ${platformSteps ? `${platformSteps}\n` : ''} - name: Install DeployHub CLI
249
337
  run: npm install ${installSpec} --no-save
@@ -364,11 +452,17 @@ export function getRequiredSecrets(
364
452
  storageProviders,
365
453
  deployEnvironments,
366
454
  environments,
367
- config = null
455
+ config = null,
456
+ cliSource = null
368
457
  ) {
369
458
  /** @type {Set<string>} */
370
459
  const secrets = new Set();
371
460
 
461
+ const resolvedCliSource = cliSource || config?.cli?.source;
462
+ if (isGithubCliSource(resolvedCliSource)) {
463
+ secrets.add(GITHUB_CLI_TOKEN_SECRET);
464
+ }
465
+
372
466
  for (const provider of storageProviders) {
373
467
  const keys = PROVIDER_ENV_MAP[provider] || [];
374
468
  keys.forEach((k) => secrets.add(k));
@@ -400,4 +494,113 @@ export function getRequiredSecrets(
400
494
  return Array.from(secrets);
401
495
  }
402
496
 
497
+ /**
498
+ * @param {string} title
499
+ * @param {string[]} keys
500
+ * @param {Record<string, string>} [defaults]
501
+ * @param {Set<string>} seenKeys
502
+ * @returns {string}
503
+ */
504
+ function formatEnvSection(title, keys, defaults, seenKeys) {
505
+ const newKeys = keys.filter((key) => !seenKeys.has(key));
506
+ if (newKeys.length === 0) return '';
507
+
508
+ for (const key of newKeys) {
509
+ seenKeys.add(key);
510
+ }
511
+
512
+ const lines = newKeys.map((key) => {
513
+ const value = defaults?.[key] ?? ENV_VAR_DEFAULTS[key] ?? '';
514
+ return value ? `${key}=${value}` : `${key}=`;
515
+ });
516
+
517
+ return `# ${title}\n${lines.join('\n')}\n`;
518
+ }
519
+
520
+ /**
521
+ * @param {string[]} storageProviders
522
+ * @param {string[]} deployEnvironments
523
+ * @param {Record<string, Record<string, unknown>>} environments
524
+ * @param {import('../core/config.js').DeployHubConfig} [config]
525
+ * @returns {string}
526
+ */
527
+ export function generateEnvExampleContent(
528
+ storageProviders,
529
+ deployEnvironments,
530
+ environments,
531
+ config = null
532
+ ) {
533
+ /** @type {Set<string>} */
534
+ const seenKeys = new Set();
535
+ /** @type {string[]} */
536
+ const sections = [];
537
+
538
+ const addSection = (title, keys, defaults = {}) => {
539
+ const section = formatEnvSection(title, keys, defaults, seenKeys);
540
+ if (section) sections.push(section);
541
+ };
542
+
543
+ for (const provider of storageProviders) {
544
+ const keys = PROVIDER_ENV_MAP[provider] || [];
545
+ if (keys.length > 0) {
546
+ addSection(PROVIDER_LABELS[provider] || provider, keys);
547
+ }
548
+ }
549
+
550
+ for (const envName of deployEnvironments) {
551
+ const env = environments[envName];
552
+ if (!env) continue;
553
+
554
+ if (env.deploymentType === 'platform' || env.frontendDeploymentType === 'platform') {
555
+ const platform = env.platform;
556
+ if (platform) {
557
+ const examples = getPlatformEnvExample(platform);
558
+ addSection(
559
+ PLATFORM_LABELS[platform] || platform,
560
+ Object.keys(examples),
561
+ examples
562
+ );
563
+ }
564
+ continue;
565
+ }
566
+
567
+ const keys = PROVIDER_ENV_MAP[env.type] || [];
568
+ if (keys.length > 0) {
569
+ addSection(PROVIDER_LABELS[env.type] || env.type, keys);
570
+ }
571
+
572
+ if (env.type === 'ssh' && config) {
573
+ const projectType = config.projectType || 'frontend';
574
+ if (projectType === 'backend' || projectType === 'both') {
575
+ addSection('SSH Deployment (backend)', BACKEND_SSH_ENV_VARS);
576
+ }
577
+ }
578
+ }
579
+
580
+ if (config?.notifications) {
581
+ if (config.notifications.slack) {
582
+ addSection('Notifications', ['SLACK_WEBHOOK_URL']);
583
+ }
584
+ if (config.notifications.webhook) {
585
+ addSection('Notifications', ['WEBHOOK_URL']);
586
+ }
587
+ if (config.notifications.email) {
588
+ addSection('Email (SMTP)', [
589
+ 'SMTP_HOST',
590
+ 'SMTP_PORT',
591
+ 'SMTP_USER',
592
+ 'SMTP_PASS',
593
+ 'NOTIFICATION_EMAIL',
594
+ 'NOTIFY_EMAIL_TO',
595
+ ]);
596
+ }
597
+ }
598
+
599
+ if (sections.length === 0) {
600
+ return '# Add your environment variables here\n';
601
+ }
602
+
603
+ return `${sections.join('\n')}\n`;
604
+ }
605
+
403
606
  export { PROVIDER_ENV_MAP };