@akash-chowdhury-24/deployhub 2.0.5 → 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 +1 -1
- package/src/adapters/dotnet.adapter.js +12 -5
- package/src/adapters/go.adapter.js +12 -5
- package/src/adapters/java.adapter.js +12 -5
- package/src/adapters/node.adapter.js +10 -3
- package/src/adapters/php.adapter.js +12 -5
- package/src/adapters/python.adapter.js +7 -1
- package/src/adapters/rails.adapter.js +7 -2
- package/src/commands/init.js +25 -8
- package/src/core/stages.js +5 -1
- package/src/deployment/deployment-env.js +106 -14
- package/src/deployment/providers/docker.js +283 -36
- package/src/utils/docker-image.js +181 -0
- package/src/utils/dockerfile.js +98 -0
- package/src/utils/github-actions.js +54 -10
- package/src/utils/scaffold.js +49 -10
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import { execa } from 'execa';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createLogger } from '../logger/index.js';
|
|
5
|
+
import { resolveDockerImageRef } from '../utils/docker-image.js';
|
|
5
6
|
|
|
6
7
|
function create(config, cwd) {
|
|
7
8
|
const log = createLogger('dotnet');
|
|
@@ -28,11 +29,17 @@ function create(config, cwd) {
|
|
|
28
29
|
},
|
|
29
30
|
|
|
30
31
|
async docker() {
|
|
31
|
-
if (await fs.pathExists(path.join(cwd, 'Dockerfile'))) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
if (!(await fs.pathExists(path.join(cwd, 'Dockerfile')))) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const { fullImage, latestImage } = resolveDockerImageRef(config);
|
|
36
|
+
log.info(`Building Docker image (${fullImage})...`);
|
|
37
|
+
await execa('docker', ['build', '-t', fullImage, '.'], {
|
|
38
|
+
cwd,
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
});
|
|
41
|
+
if (fullImage !== latestImage) {
|
|
42
|
+
await execa('docker', ['tag', fullImage, latestImage], { stdio: 'pipe' });
|
|
36
43
|
}
|
|
37
44
|
},
|
|
38
45
|
};
|
|
@@ -2,6 +2,7 @@ import { execa } from 'execa';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createLogger } from '../logger/index.js';
|
|
5
|
+
import { resolveDockerImageRef } from '../utils/docker-image.js';
|
|
5
6
|
|
|
6
7
|
function create(config, cwd) {
|
|
7
8
|
const log = createLogger('go');
|
|
@@ -27,11 +28,17 @@ function create(config, cwd) {
|
|
|
27
28
|
},
|
|
28
29
|
|
|
29
30
|
async docker() {
|
|
30
|
-
if (await fs.pathExists(path.join(cwd, 'Dockerfile'))) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
if (!(await fs.pathExists(path.join(cwd, 'Dockerfile')))) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const { fullImage, latestImage } = resolveDockerImageRef(config);
|
|
35
|
+
log.info(`Building Docker image (${fullImage})...`);
|
|
36
|
+
await execa('docker', ['build', '-t', fullImage, '.'], {
|
|
37
|
+
cwd,
|
|
38
|
+
stdio: 'inherit',
|
|
39
|
+
});
|
|
40
|
+
if (fullImage !== latestImage) {
|
|
41
|
+
await execa('docker', ['tag', fullImage, latestImage], { stdio: 'pipe' });
|
|
35
42
|
}
|
|
36
43
|
},
|
|
37
44
|
};
|
|
@@ -2,6 +2,7 @@ import { execa } from 'execa';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createLogger } from '../logger/index.js';
|
|
5
|
+
import { resolveDockerImageRef } from '../utils/docker-image.js';
|
|
5
6
|
|
|
6
7
|
function create(config, cwd) {
|
|
7
8
|
const log = createLogger('java');
|
|
@@ -33,11 +34,17 @@ function create(config, cwd) {
|
|
|
33
34
|
},
|
|
34
35
|
|
|
35
36
|
async docker() {
|
|
36
|
-
if (await fs.pathExists(path.join(cwd, 'Dockerfile'))) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
if (!(await fs.pathExists(path.join(cwd, 'Dockerfile')))) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const { fullImage, latestImage } = resolveDockerImageRef(config);
|
|
41
|
+
log.info(`Building Docker image (${fullImage})...`);
|
|
42
|
+
await execa('docker', ['build', '-t', fullImage, '.'], {
|
|
43
|
+
cwd,
|
|
44
|
+
stdio: 'inherit',
|
|
45
|
+
});
|
|
46
|
+
if (fullImage !== latestImage) {
|
|
47
|
+
await execa('docker', ['tag', fullImage, latestImage], { stdio: 'pipe' });
|
|
41
48
|
}
|
|
42
49
|
},
|
|
43
50
|
};
|
|
@@ -2,6 +2,7 @@ import { execa } from 'execa';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createLogger } from '../logger/index.js';
|
|
5
|
+
import { resolveDockerImageRef } from '../utils/docker-image.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {Object} LanguageAdapter
|
|
@@ -64,12 +65,18 @@ function create(config, cwd) {
|
|
|
64
65
|
log.warn('No Dockerfile found, skipping docker build');
|
|
65
66
|
return;
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
await execa('docker', ['build', '-t',
|
|
68
|
+
const { fullImage, latestImage } = resolveDockerImageRef(config);
|
|
69
|
+
log.info(`Building Docker image (${fullImage})...`);
|
|
70
|
+
await execa('docker', ['build', '-t', fullImage, '.'], {
|
|
70
71
|
cwd,
|
|
71
72
|
stdio: 'inherit',
|
|
72
73
|
});
|
|
74
|
+
if (fullImage !== latestImage) {
|
|
75
|
+
await execa('docker', ['tag', fullImage, latestImage], {
|
|
76
|
+
cwd,
|
|
77
|
+
stdio: 'pipe',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
73
80
|
},
|
|
74
81
|
};
|
|
75
82
|
}
|
|
@@ -2,6 +2,7 @@ import { execa } from 'execa';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createLogger } from '../logger/index.js';
|
|
5
|
+
import { resolveDockerImageRef } from '../utils/docker-image.js';
|
|
5
6
|
|
|
6
7
|
function create(config, cwd) {
|
|
7
8
|
const log = createLogger('php');
|
|
@@ -30,11 +31,17 @@ function create(config, cwd) {
|
|
|
30
31
|
},
|
|
31
32
|
|
|
32
33
|
async docker() {
|
|
33
|
-
if (await fs.pathExists(path.join(cwd, 'Dockerfile'))) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
if (!(await fs.pathExists(path.join(cwd, 'Dockerfile')))) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const { fullImage, latestImage } = resolveDockerImageRef(config);
|
|
38
|
+
log.info(`Building Docker image (${fullImage})...`);
|
|
39
|
+
await execa('docker', ['build', '-t', fullImage, '.'], {
|
|
40
|
+
cwd,
|
|
41
|
+
stdio: 'inherit',
|
|
42
|
+
});
|
|
43
|
+
if (fullImage !== latestImage) {
|
|
44
|
+
await execa('docker', ['tag', fullImage, latestImage], { stdio: 'pipe' });
|
|
38
45
|
}
|
|
39
46
|
},
|
|
40
47
|
};
|
|
@@ -2,6 +2,7 @@ import { execa } from 'execa';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createLogger } from '../logger/index.js';
|
|
5
|
+
import { resolveDockerImageRef } from '../utils/docker-image.js';
|
|
5
6
|
|
|
6
7
|
function create(config, cwd) {
|
|
7
8
|
const log = createLogger('python');
|
|
@@ -43,10 +44,15 @@ function create(config, cwd) {
|
|
|
43
44
|
log.warn('No Dockerfile found, skipping');
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
+
const { fullImage, latestImage } = resolveDockerImageRef(config);
|
|
48
|
+
log.info(`Building Docker image (${fullImage})...`);
|
|
49
|
+
await execa('docker', ['build', '-t', fullImage, '.'], {
|
|
47
50
|
cwd,
|
|
48
51
|
stdio: 'inherit',
|
|
49
52
|
});
|
|
53
|
+
if (fullImage !== latestImage) {
|
|
54
|
+
await execa('docker', ['tag', fullImage, latestImage], { stdio: 'pipe' });
|
|
55
|
+
}
|
|
50
56
|
},
|
|
51
57
|
};
|
|
52
58
|
}
|
|
@@ -2,6 +2,7 @@ import { execa } from 'execa';
|
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { createLogger } from '../logger/index.js';
|
|
5
|
+
import { resolveDockerImageRef } from '../utils/docker-image.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @param {import('../core/config.js').DeployHubConfig} config
|
|
@@ -54,11 +55,15 @@ function create(config, cwd) {
|
|
|
54
55
|
log.warn('No Dockerfile found, skipping docker build');
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const { fullImage, latestImage } = resolveDockerImageRef(config);
|
|
59
|
+
log.info(`Building Docker image (${fullImage})...`);
|
|
60
|
+
await execa('docker', ['build', '-t', fullImage, '.'], {
|
|
59
61
|
cwd,
|
|
60
62
|
stdio: 'inherit',
|
|
61
63
|
});
|
|
64
|
+
if (fullImage !== latestImage) {
|
|
65
|
+
await execa('docker', ['tag', fullImage, latestImage], { stdio: 'pipe' });
|
|
66
|
+
}
|
|
62
67
|
},
|
|
63
68
|
};
|
|
64
69
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { getProjectVersion } from '../utils/version.js';
|
|
14
14
|
import {
|
|
15
15
|
writeWorkflowFile,
|
|
16
|
-
|
|
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 {
|
|
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 = [
|
|
@@ -199,6 +202,9 @@ async function generateProjectScaffold(config, environments, cwd) {
|
|
|
199
202
|
if (dockerResult.generated) {
|
|
200
203
|
console.log(chalk.gray(' • Dockerfile (auto-generated)'));
|
|
201
204
|
}
|
|
205
|
+
if (dockerResult.dockerignoreGenerated) {
|
|
206
|
+
console.log(chalk.gray(' • .dockerignore (auto-generated)'));
|
|
207
|
+
}
|
|
202
208
|
|
|
203
209
|
const k8sResult = await ensureKubernetesManifests(cwd, config, environments);
|
|
204
210
|
if (k8sResult.generated) {
|
|
@@ -495,23 +501,34 @@ export function registerInitCommand(program) {
|
|
|
495
501
|
);
|
|
496
502
|
await fs.writeFile(envExampleDest, envExampleContent);
|
|
497
503
|
|
|
498
|
-
const
|
|
504
|
+
const secretsChecklist = getGithubSecretsChecklist(
|
|
505
|
+
config.storage,
|
|
506
|
+
deploy,
|
|
507
|
+
environments,
|
|
508
|
+
config
|
|
509
|
+
);
|
|
499
510
|
|
|
500
511
|
console.log('');
|
|
501
512
|
if (primaryDeployType) {
|
|
502
513
|
console.log(chalk.green.bold(`✔ Config generated for ${primaryDeployType} deployment.`));
|
|
503
|
-
printDeploymentNextSteps(primaryDeployType,
|
|
514
|
+
printDeploymentNextSteps(primaryDeployType, secretsChecklist);
|
|
504
515
|
} else {
|
|
505
516
|
console.log(chalk.green.bold('✓ DeployHub initialized successfully!'));
|
|
506
517
|
console.log('');
|
|
507
518
|
console.log(chalk.bold('Next steps:'));
|
|
508
519
|
console.log(' 1. Copy .env.example to .env and fill in credentials');
|
|
509
|
-
if (
|
|
520
|
+
if (secretsChecklist.length > 0) {
|
|
510
521
|
console.log(' 2. Add these secrets to GitHub (Settings → Secrets):');
|
|
511
|
-
|
|
522
|
+
secretsChecklist.forEach((item) =>
|
|
523
|
+
console.log(` ${formatSecretChecklistLine(item)}`)
|
|
524
|
+
);
|
|
512
525
|
}
|
|
513
|
-
console.log(
|
|
514
|
-
|
|
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
|
+
);
|
|
515
532
|
}
|
|
516
533
|
|
|
517
534
|
console.log('');
|
package/src/core/stages.js
CHANGED
|
@@ -41,6 +41,8 @@ export function buildPipelineStages(config, cwd, state) {
|
|
|
41
41
|
ctx.config.port = detected.port;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
// Resolve version before docker so pipeline build and deploy share the same tag
|
|
45
|
+
ctx.config.version = await getProjectVersion(ctx.cwd);
|
|
44
46
|
const scaffold = await ensureDeployScaffold(
|
|
45
47
|
ctx.cwd,
|
|
46
48
|
ctx.config,
|
|
@@ -118,7 +120,9 @@ export function buildPipelineStages(config, cwd, state) {
|
|
|
118
120
|
name: 'artifact',
|
|
119
121
|
enabled: (ctx) => ctx.config.artifact !== false,
|
|
120
122
|
async run(ctx) {
|
|
121
|
-
ctx.config.version
|
|
123
|
+
if (!ctx.config.version) {
|
|
124
|
+
ctx.config.version = await getProjectVersion(ctx.cwd);
|
|
125
|
+
}
|
|
122
126
|
const result = await createArtifact(
|
|
123
127
|
ctx.config,
|
|
124
128
|
/** @type {string[]} */ (ctx.state.deployedTargets || []),
|
|
@@ -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
|
-
*
|
|
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[]}
|
|
@@ -389,14 +400,92 @@ export function getDeploymentSecretKeys(deployType, config = null) {
|
|
|
389
400
|
for (const d of defs) {
|
|
390
401
|
if (d.when === 'backend' && !isBackend) continue;
|
|
391
402
|
if (d.when === 'optional') continue;
|
|
392
|
-
|
|
393
|
-
|
|
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 });
|
|
468
|
+
}
|
|
394
469
|
continue;
|
|
395
470
|
}
|
|
396
|
-
|
|
471
|
+
|
|
472
|
+
byKey.set(key, { key, required, note });
|
|
397
473
|
}
|
|
398
474
|
|
|
399
|
-
return
|
|
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})`;
|
|
400
489
|
}
|
|
401
490
|
|
|
402
491
|
/**
|
|
@@ -442,6 +531,7 @@ export function generateDeploymentEnvSection(
|
|
|
442
531
|
return lines.join('\n').trimEnd();
|
|
443
532
|
}
|
|
444
533
|
|
|
534
|
+
|
|
445
535
|
/**
|
|
446
536
|
* @param {string} key
|
|
447
537
|
* @param {import('../core/config.js').DeployHubConfig} [config]
|
|
@@ -512,13 +602,15 @@ export const DEPLOYMENT_GUIDE = {
|
|
|
512
602
|
],
|
|
513
603
|
automates: [
|
|
514
604
|
'Generates config, workflow, and .env.example for registry and image settings.',
|
|
605
|
+
'Generates a starter Dockerfile and .dockerignore when missing.',
|
|
515
606
|
'Tests Docker daemon connectivity during init.',
|
|
516
|
-
'Builds
|
|
607
|
+
'Builds the image once during the pipeline docker stage, then reuses it on deploy.',
|
|
517
608
|
],
|
|
518
609
|
after: [
|
|
519
|
-
'Copy .env.example to .env and set DOCKER_IMAGE_NAME (
|
|
520
|
-
'
|
|
521
|
-
'
|
|
610
|
+
'Copy .env.example to .env and set DOCKER_IMAGE_NAME (required — e.g. myuser/myapp).',
|
|
611
|
+
'Optional in .env: DOCKER_IMAGE_TAG, DOCKER_REGISTRY_URL, DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH.',
|
|
612
|
+
'If using a private registry: also set DOCKER_REGISTRY_USERNAME and DOCKER_REGISTRY_TOKEN.',
|
|
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.',
|
|
522
614
|
'Run deployhub doctor to verify Docker is reachable.',
|
|
523
615
|
'git push origin main to trigger your first deployment.',
|
|
524
616
|
],
|
|
@@ -612,9 +704,9 @@ export const DEPLOYMENT_GUIDE = {
|
|
|
612
704
|
|
|
613
705
|
/**
|
|
614
706
|
* @param {string} deployType
|
|
615
|
-
* @param {
|
|
707
|
+
* @param {SecretChecklistItem[]} [checklist]
|
|
616
708
|
*/
|
|
617
|
-
export function printDeploymentNextSteps(deployType,
|
|
709
|
+
export function printDeploymentNextSteps(deployType, checklist = []) {
|
|
618
710
|
const guide = DEPLOYMENT_GUIDE[deployType];
|
|
619
711
|
if (!guide) return;
|
|
620
712
|
|
|
@@ -625,10 +717,10 @@ export function printDeploymentNextSteps(deployType, extraSecrets = []) {
|
|
|
625
717
|
console.log(` ${i + 1}. ${step}`);
|
|
626
718
|
});
|
|
627
719
|
|
|
628
|
-
if (
|
|
720
|
+
if (checklist.length > 0) {
|
|
629
721
|
console.log('\n GitHub Secrets to add (Settings → Secrets and variables → Actions):');
|
|
630
|
-
for (const
|
|
631
|
-
console.log(`
|
|
722
|
+
for (const item of checklist) {
|
|
723
|
+
console.log(` ${formatSecretChecklistLine(item)}`);
|
|
632
724
|
}
|
|
633
725
|
}
|
|
634
726
|
}
|