@akash-chowdhury-24/deployhub 2.0.6 → 2.0.7

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": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Zero-configuration deployment and artifact manager",
5
5
  "type": "module",
6
6
  "main": "./src/cli/index.js",
@@ -13,7 +13,7 @@ import {
13
13
  import { getProjectVersion } from '../utils/version.js';
14
14
  import {
15
15
  writeWorkflowFile,
16
- getRequiredSecrets,
16
+ getGithubSecretsChecklist,
17
17
  generateEnvExampleContent,
18
18
  addDeployhubToPackageJson,
19
19
  DEFAULT_NPM_CLI_SOURCE,
@@ -30,7 +30,10 @@ import {
30
30
  getDockerEnvSecrets,
31
31
  SSH_BASED,
32
32
  } from '../deployment/init-prompts.js';
33
- import { printDeploymentNextSteps } from '../deployment/deployment-env.js';
33
+ import {
34
+ printDeploymentNextSteps,
35
+ formatSecretChecklistLine,
36
+ } from '../deployment/deployment-env.js';
34
37
  import { confirmValueIfContainsSpaces } from '../deployment/init-helpers.js';
35
38
 
36
39
  const FRONTEND_CHOICES = [
@@ -498,23 +501,34 @@ export function registerInitCommand(program) {
498
501
  );
499
502
  await fs.writeFile(envExampleDest, envExampleContent);
500
503
 
501
- const secrets = getRequiredSecrets(config.storage, deploy, environments, config);
504
+ const secretsChecklist = getGithubSecretsChecklist(
505
+ config.storage,
506
+ deploy,
507
+ environments,
508
+ config
509
+ );
502
510
 
503
511
  console.log('');
504
512
  if (primaryDeployType) {
505
513
  console.log(chalk.green.bold(`✔ Config generated for ${primaryDeployType} deployment.`));
506
- printDeploymentNextSteps(primaryDeployType, secrets);
514
+ printDeploymentNextSteps(primaryDeployType, secretsChecklist);
507
515
  } else {
508
516
  console.log(chalk.green.bold('✓ DeployHub initialized successfully!'));
509
517
  console.log('');
510
518
  console.log(chalk.bold('Next steps:'));
511
519
  console.log(' 1. Copy .env.example to .env and fill in credentials');
512
- if (secrets.length > 0) {
520
+ if (secretsChecklist.length > 0) {
513
521
  console.log(' 2. Add these secrets to GitHub (Settings → Secrets):');
514
- secrets.forEach((s) => console.log(` • ${s}`));
522
+ secretsChecklist.forEach((item) =>
523
+ console.log(` ${formatSecretChecklistLine(item)}`)
524
+ );
515
525
  }
516
- console.log(` ${secrets.length > 0 ? '3' : '2'}. Run ${chalk.cyan('deployhub doctor')} to verify your setup`);
517
- console.log(` ${secrets.length > 0 ? '4' : '3'}. Push to main — GitHub Actions will run ${chalk.cyan('deployhub build')} automatically`);
526
+ console.log(
527
+ ` ${secretsChecklist.length > 0 ? '3' : '2'}. Run ${chalk.cyan('deployhub doctor')} to verify your setup`
528
+ );
529
+ console.log(
530
+ ` ${secretsChecklist.length > 0 ? '4' : '3'}. Push to main — GitHub Actions will run ${chalk.cyan('deployhub build')} automatically`
531
+ );
518
532
  }
519
533
 
520
534
  console.log('');
@@ -353,6 +353,8 @@ export const DEPLOYMENT_ENV_KEYS = Object.fromEntries(
353
353
  );
354
354
 
355
355
  /**
356
+ * Locally required env keys for doctor method-specific checks.
357
+ * Excludes optional and CI-only vars.
356
358
  * @param {string} deployType
357
359
  * @param {import('../core/config.js').DeployHubConfig} [config]
358
360
  * @returns {string[]}
@@ -373,7 +375,16 @@ export function getDeploymentEnvKeys(deployType, config = null) {
373
375
  }
374
376
 
375
377
  /**
376
- * Required secrets for GitHub Actions (includes CI-only vars like SSH_KEY).
378
+ * Map a def key to the GitHub Actions secret name (SSH_KEY_PATH SSH_KEY).
379
+ * @param {string} key
380
+ */
381
+ function toGithubSecretKey(key) {
382
+ return key === 'SSH_KEY_PATH' ? 'SSH_KEY' : key;
383
+ }
384
+
385
+ /**
386
+ * Genuinely required secrets for doctor "Secrets" check and required checklist items.
387
+ * Includes CI-only vars (e.g. SSH_KEY) but NOT optional vars.
377
388
  * @param {string} deployType
378
389
  * @param {import('../core/config.js').DeployHubConfig} [config]
379
390
  * @returns {string[]}
@@ -388,22 +399,93 @@ export function getDeploymentSecretKeys(deployType, config = null) {
388
399
 
389
400
  for (const d of defs) {
390
401
  if (d.when === 'backend' && !isBackend) continue;
391
- // Docker optional vars must still appear in CI workflow/secrets checklist —
392
- // empty secrets are fine when unused; missing DOCKER_IMAGE_NAME is not.
393
- if (d.when === 'optional') {
394
- if (deployType === 'docker' && d.key.startsWith('DOCKER_')) {
395
- keys.push(d.key);
402
+ if (d.when === 'optional') continue;
403
+ keys.push(toGithubSecretKey(d.key));
404
+ }
405
+
406
+ return [...new Set(keys)];
407
+ }
408
+
409
+ /**
410
+ * Broad CI-wiring list for the GitHub Actions workflow generator.
411
+ * Includes required, CI-only, and optional keys so `${{ secrets.X }}` is
412
+ * available when the user sets optional secrets — empty secrets are fine.
413
+ * @param {string} deployType
414
+ * @param {import('../core/config.js').DeployHubConfig} [config]
415
+ * @returns {string[]}
416
+ */
417
+ export function getDeploymentWorkflowSecretKeys(deployType, config = null) {
418
+ const defs = DEPLOYMENT_ENV_DEFS[deployType] || [];
419
+ const projectType = config?.projectType || 'frontend';
420
+ const isBackend = projectType === 'backend' || projectType === 'both';
421
+
422
+ /** @type {string[]} */
423
+ const keys = [];
424
+
425
+ for (const d of defs) {
426
+ if (d.when === 'backend' && !isBackend) continue;
427
+ keys.push(toGithubSecretKey(d.key));
428
+ }
429
+
430
+ return [...new Set(keys)];
431
+ }
432
+
433
+ /**
434
+ * @typedef {{ key: string, required: boolean, note?: string }} SecretChecklistItem
435
+ */
436
+
437
+ /**
438
+ * Labeled GitHub Secrets checklist entries for a single deploy method.
439
+ * @param {string} deployType
440
+ * @param {import('../core/config.js').DeployHubConfig} [config]
441
+ * @returns {SecretChecklistItem[]}
442
+ */
443
+ export function getDeploymentSecretChecklistItems(deployType, config = null) {
444
+ const defs = DEPLOYMENT_ENV_DEFS[deployType] || [];
445
+ const projectType = config?.projectType || 'frontend';
446
+ const isBackend = projectType === 'backend' || projectType === 'both';
447
+
448
+ /** @type {Map<string, SecretChecklistItem>} */
449
+ const byKey = new Map();
450
+
451
+ for (const d of defs) {
452
+ if (d.when === 'backend' && !isBackend) continue;
453
+
454
+ const key = toGithubSecretKey(d.key);
455
+ const required = d.when !== 'optional';
456
+ const note =
457
+ d.when === 'optional'
458
+ ? d.optionalReason
459
+ : d.when === 'ci'
460
+ ? d.optionalReason || 'required for GitHub Actions CI (paste private key contents)'
461
+ : undefined;
462
+
463
+ const existing = byKey.get(key);
464
+ if (existing) {
465
+ // Prefer required if any def for this key is required
466
+ if (required && !existing.required) {
467
+ byKey.set(key, { key, required: true, note });
396
468
  }
397
469
  continue;
398
470
  }
399
- if (d.key === 'SSH_KEY_PATH') {
400
- keys.push('SSH_KEY');
401
- continue;
402
- }
403
- keys.push(d.key);
471
+
472
+ byKey.set(key, { key, required, note });
404
473
  }
405
474
 
406
- return keys;
475
+ return Array.from(byKey.values());
476
+ }
477
+
478
+ /**
479
+ * Format a checklist item for console output.
480
+ * @param {SecretChecklistItem} item
481
+ */
482
+ export function formatSecretChecklistLine(item) {
483
+ if (item.required) {
484
+ const note = item.note ? ` — ${item.note}` : '';
485
+ return `• ${item.key} (required${note})`;
486
+ }
487
+ const note = item.note ? ` — ${item.note}` : '';
488
+ return `• ${item.key} (optional${note})`;
407
489
  }
408
490
 
409
491
  /**
@@ -449,6 +531,7 @@ export function generateDeploymentEnvSection(
449
531
  return lines.join('\n').trimEnd();
450
532
  }
451
533
 
534
+
452
535
  /**
453
536
  * @param {string} key
454
537
  * @param {import('../core/config.js').DeployHubConfig} [config]
@@ -527,8 +610,7 @@ export const DEPLOYMENT_GUIDE = {
527
610
  'Copy .env.example to .env and set DOCKER_IMAGE_NAME (required — e.g. myuser/myapp).',
528
611
  'Optional in .env: DOCKER_IMAGE_TAG, DOCKER_REGISTRY_URL, DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH.',
529
612
  'If using a private registry: also set DOCKER_REGISTRY_USERNAME and DOCKER_REGISTRY_TOKEN.',
530
- 'Add GitHub Secrets (Settings → Secrets and variables → Actions): DOCKER_IMAGE_NAME (required). Local .env is NOT used by GitHub Actions — doctor only checks your machine.',
531
- 'Also add as GitHub Secrets if set locally: DOCKER_IMAGE_TAG, DOCKER_REGISTRY_URL, DOCKER_REGISTRY_USERNAME, DOCKER_REGISTRY_TOKEN, DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH.',
613
+ 'Add the GitHub Secrets listed below (Settings → Secrets and variables → Actions). Local .env is NOT used by GitHub Actions — doctor only checks your machine.',
532
614
  'Run deployhub doctor to verify Docker is reachable.',
533
615
  'git push origin main to trigger your first deployment.',
534
616
  ],
@@ -622,9 +704,9 @@ export const DEPLOYMENT_GUIDE = {
622
704
 
623
705
  /**
624
706
  * @param {string} deployType
625
- * @param {string[]} [extraSecrets]
707
+ * @param {SecretChecklistItem[]} [checklist]
626
708
  */
627
- export function printDeploymentNextSteps(deployType, extraSecrets = []) {
709
+ export function printDeploymentNextSteps(deployType, checklist = []) {
628
710
  const guide = DEPLOYMENT_GUIDE[deployType];
629
711
  if (!guide) return;
630
712
 
@@ -635,10 +717,10 @@ export function printDeploymentNextSteps(deployType, extraSecrets = []) {
635
717
  console.log(` ${i + 1}. ${step}`);
636
718
  });
637
719
 
638
- if (extraSecrets.length > 0) {
720
+ if (checklist.length > 0) {
639
721
  console.log('\n GitHub Secrets to add (Settings → Secrets and variables → Actions):');
640
- for (const s of extraSecrets) {
641
- console.log(` ${s}`);
722
+ for (const item of checklist) {
723
+ console.log(` ${formatSecretChecklistLine(item)}`);
642
724
  }
643
725
  }
644
726
  }
@@ -3,7 +3,8 @@ import path from 'path';
3
3
  import { getWorkflowHeaderComment } from './author.js';
4
4
  import {
5
5
  generateDeploymentEnvSection,
6
- getDeploymentSecretKeys,
6
+ getDeploymentWorkflowSecretKeys,
7
+ getDeploymentSecretChecklistItems,
7
8
  DEPLOYMENT_ENV_KEYS,
8
9
  } from '../deployment/deployment-env.js';
9
10
 
@@ -287,7 +288,7 @@ export function generateWorkflowYaml(
287
288
  const env = environments[envName];
288
289
  if (!env) continue;
289
290
 
290
- const keys = getDeploymentSecretKeys(env.type, config);
291
+ const keys = getDeploymentWorkflowSecretKeys(env.type, config);
291
292
  for (const key of keys) {
292
293
  envVars.add(`${key}: \${{ secrets.${key} }}`);
293
294
  }
@@ -438,10 +439,12 @@ export async function guessCliGithubRepo(cwd = process.cwd()) {
438
439
  }
439
440
 
440
441
  /**
442
+ * Flat list of required secret key names (for callers that only need keys).
441
443
  * @param {string[]} storageProviders
442
444
  * @param {string[]} deployEnvironments
443
445
  * @param {Record<string, { type: string }>} environments
444
446
  * @param {import('../core/config.js').DeployHubConfig} [config]
447
+ * @param {string|null} [cliSource]
445
448
  * @returns {string[]}
446
449
  */
447
450
  export function getRequiredSecrets(
@@ -451,28 +454,69 @@ export function getRequiredSecrets(
451
454
  config = null,
452
455
  cliSource = null
453
456
  ) {
454
- /** @type {Set<string>} */
455
- const secrets = new Set();
457
+ return getGithubSecretsChecklist(
458
+ storageProviders,
459
+ deployEnvironments,
460
+ environments,
461
+ config,
462
+ cliSource
463
+ )
464
+ .filter((item) => item.required)
465
+ .map((item) => item.key);
466
+ }
467
+
468
+ /**
469
+ * Labeled GitHub Secrets checklist (required + optional) for post-init output.
470
+ * @param {string[]} storageProviders
471
+ * @param {string[]} deployEnvironments
472
+ * @param {Record<string, { type: string }>} environments
473
+ * @param {import('../core/config.js').DeployHubConfig} [config]
474
+ * @param {string|null} [cliSource]
475
+ * @returns {import('../deployment/deployment-env.js').SecretChecklistItem[]}
476
+ */
477
+ export function getGithubSecretsChecklist(
478
+ storageProviders,
479
+ deployEnvironments,
480
+ environments,
481
+ config = null,
482
+ cliSource = null
483
+ ) {
484
+ /** @type {Map<string, import('../deployment/deployment-env.js').SecretChecklistItem>} */
485
+ const byKey = new Map();
456
486
 
457
487
  const resolvedCliSource = cliSource || config?.cli?.source;
458
488
  if (isGithubCliSource(resolvedCliSource)) {
459
- secrets.add(GITHUB_CLI_TOKEN_SECRET);
489
+ byKey.set(GITHUB_CLI_TOKEN_SECRET, {
490
+ key: GITHUB_CLI_TOKEN_SECRET,
491
+ required: true,
492
+ note: 'required when installing DeployHub CLI from a private GitHub repo',
493
+ });
460
494
  }
461
495
 
462
496
  for (const provider of storageProviders) {
463
497
  const keys = PROVIDER_ENV_MAP[provider] || [];
464
- keys.forEach((k) => secrets.add(k));
498
+ for (const key of keys) {
499
+ byKey.set(key, { key, required: true });
500
+ }
465
501
  }
466
502
 
467
503
  for (const envName of deployEnvironments) {
468
504
  const env = environments[envName];
469
- if (!env) continue;
505
+ if (!env?.type) continue;
470
506
 
471
- const keys = getDeploymentSecretKeys(env.type, config);
472
- keys.forEach((k) => secrets.add(k));
507
+ for (const item of getDeploymentSecretChecklistItems(env.type, config)) {
508
+ const existing = byKey.get(item.key);
509
+ if (existing) {
510
+ if (item.required && !existing.required) {
511
+ byKey.set(item.key, item);
512
+ }
513
+ continue;
514
+ }
515
+ byKey.set(item.key, item);
516
+ }
473
517
  }
474
518
 
475
- return Array.from(secrets);
519
+ return Array.from(byKey.values());
476
520
  }
477
521
 
478
522
  /**