@akash-chowdhury-24/deployhub 1.0.16 → 2.0.0
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/README.md +50 -55
- package/package.json +1 -1
- package/src/commands/doctor.js +1 -141
- package/src/commands/init.js +25 -156
- package/src/core/config.js +2 -13
- package/src/core/stages.js +0 -11
- package/src/deployment/index.js +0 -72
- package/src/rollback/engine.js +6 -39
- package/src/utils/github-actions.js +6 -96
- package/src/deployment/providers/platforms/_shared.js +0 -167
- package/src/deployment/providers/platforms/aws-amplify.js +0 -367
- package/src/deployment/providers/platforms/azure-static-web-apps.js +0 -68
- package/src/deployment/providers/platforms/cloudflare-pages.js +0 -103
- package/src/deployment/providers/platforms/firebase-app-hosting.js +0 -95
- package/src/deployment/providers/platforms/firebase-hosting.js +0 -99
- package/src/deployment/providers/platforms/index.js +0 -44
- package/src/deployment/providers/platforms/netlify.js +0 -102
- package/src/deployment/providers/platforms/vercel.js +0 -92
- package/src/utils/firebase-config-generator.js +0 -35
- package/src/utils/init-platform.js +0 -236
- package/src/utils/platform-env.js +0 -132
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Zero-configuration deployment and artifact manager for Node.js projects. When you push to GitHub, DeployHub automatically detects your project type, builds it, creates a versioned artifact, uploads to cloud storage, and optionally deploys to your server.
|
|
4
4
|
|
|
5
|
+
**Supported deployment targets:** SSH, Docker, EC2, Azure VM, GCP VM, and Kubernetes — self-hosted and cloud VM only.
|
|
6
|
+
|
|
7
|
+
DeployHub no longer integrates with managed platforms like Vercel or Netlify — those tools already offer superior native git-push deployment. DeployHub instead focuses on artifact-first backups and self-hosted/server deployment, where no equivalent native solution exists.
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
DeployHub can be installed via **npm** (requires Node.js 18+) or as a **standalone binary** (no Node.js required).
|
|
@@ -80,7 +84,7 @@ This interactive wizard will:
|
|
|
80
84
|
- Detect your framework (React, Vue, Next.js, Node, Python, etc.)
|
|
81
85
|
- Configure build commands and output directory
|
|
82
86
|
- Set up storage providers (AWS, Google Drive, Azure, GCP, Dropbox, Local)
|
|
83
|
-
- Optionally configure deployment targets (SSH, Docker, EC2,
|
|
87
|
+
- Optionally configure deployment targets (SSH, Docker, EC2, Azure VM, GCP VM, Kubernetes)
|
|
84
88
|
- Generate `deployhub.config.json`
|
|
85
89
|
- Generate `.github/workflows/deployhub.yml`
|
|
86
90
|
- Generate `.env.example`
|
|
@@ -132,7 +136,7 @@ When you run `deployhub build` (locally or in GitHub Actions), DeployHub runs th
|
|
|
132
136
|
| **verify** | Hit your health-check URL — **only if configured** |
|
|
133
137
|
| **notify** | Slack / email / webhook — **only if enabled** |
|
|
134
138
|
|
|
135
|
-
**Storage only** means you answer **No** to *Configure deployment?* during `init`. You still get builds and cloud backups; nothing is pushed to a server
|
|
139
|
+
**Storage only** means you answer **No** to *Configure deployment?* during `init`. You still get builds and cloud backups; nothing is pushed to a server.
|
|
136
140
|
|
|
137
141
|
**Storage + deployment** means you answer **Yes**, pick targets, and add the matching secrets. Deploy always runs **after** storage upload succeeds.
|
|
138
142
|
|
|
@@ -181,7 +185,6 @@ The wizard asks the same core questions for every setup:
|
|
|
181
185
|
- `.github/workflows/deployhub.yml` — CI pipeline
|
|
182
186
|
- `.env.example` — list of env vars you may need
|
|
183
187
|
- `nginx.conf` — auto-generated if frontend deploys to SSH
|
|
184
|
-
- `firebase.json` — auto-generated if you pick Firebase Hosting
|
|
185
188
|
|
|
186
189
|
---
|
|
187
190
|
|
|
@@ -301,53 +304,39 @@ If **Yes** to deployment:
|
|
|
301
304
|
|
|
302
305
|
| Prompt | Options |
|
|
303
306
|
|--------|---------|
|
|
304
|
-
|
|
|
307
|
+
| Deployment type | **ssh**, docker, ec2, azure-vm, gcp-vm, kubernetes |
|
|
308
|
+
| Host, user, deploy path | SSH credentials and remote directory |
|
|
305
309
|
|
|
306
|
-
####
|
|
310
|
+
#### Self-hosted server (SSH, Docker, EC2, …)
|
|
307
311
|
|
|
308
|
-
Best
|
|
312
|
+
Best when you serve static files from your own VPS or cloud VM. DeployHub uploads the built `dist/` (or your output dir) over SSH and can generate `nginx.conf`.
|
|
309
313
|
|
|
310
|
-
|
|
|
311
|
-
|
|
312
|
-
|
|
|
313
|
-
|
|
|
314
|
-
|
|
|
315
|
-
|
|
|
316
|
-
|
|
|
317
|
-
| Firebase Hosting | SPAs | `FIREBASE_TOKEN`, `FIREBASE_PROJECT_ID` |
|
|
318
|
-
| Firebase App Hosting | Next.js / Angular SSR | `FIREBASE_TOKEN`, `FIREBASE_PROJECT_ID`, `FIREBASE_APP_HOSTING_BACKEND` |
|
|
314
|
+
| Deploy type | You provide |
|
|
315
|
+
|-------------|-------------|
|
|
316
|
+
| **ssh** | `SSH_HOST`, `SSH_USER`, `SSH_KEY`, deploy path |
|
|
317
|
+
| **docker** | Docker host access / image registry per your setup |
|
|
318
|
+
| **ec2** | SSH credentials to EC2 instance |
|
|
319
|
+
| **azure-vm** / **gcp-vm** | SSH to VM |
|
|
320
|
+
| **kubernetes** | Cluster credentials (via env / kubeconfig) |
|
|
319
321
|
|
|
320
|
-
**Example: Vue → Google Drive +
|
|
322
|
+
**Example: Vue → Google Drive + SSH**
|
|
321
323
|
|
|
322
324
|
```bash
|
|
323
325
|
deployhub init
|
|
324
326
|
# Frontend only → Vue
|
|
325
327
|
# Storage: Local + Google Drive
|
|
326
|
-
# Configure deployment? Yes
|
|
327
|
-
#
|
|
328
|
-
# Site ID: (from Netlify dashboard)
|
|
328
|
+
# Configure deployment? Yes → ssh
|
|
329
|
+
# Host, user, deploy path: /var/www/my-app
|
|
329
330
|
```
|
|
330
331
|
|
|
331
332
|
```bash
|
|
332
333
|
cp .env.example .env
|
|
333
|
-
# GDRIVE_* and
|
|
334
|
+
# GDRIVE_* and SSH_HOST, SSH_USER, SSH_KEY
|
|
334
335
|
|
|
335
336
|
deployhub doctor
|
|
336
337
|
git push origin main
|
|
337
338
|
```
|
|
338
339
|
|
|
339
|
-
#### Option B — Self-hosted server (SSH, Docker, EC2, …)
|
|
340
|
-
|
|
341
|
-
Best when you serve static files from your own VPS. DeployHub uploads the built `dist/` (or your output dir) over SSH and can generate `nginx.conf`.
|
|
342
|
-
|
|
343
|
-
| Deploy type | You provide |
|
|
344
|
-
|-------------|-------------|
|
|
345
|
-
| **ssh** | `SSH_HOST`, `SSH_USER`, `SSH_KEY`, deploy path |
|
|
346
|
-
| **docker** | Docker host access / image registry per your setup |
|
|
347
|
-
| **ec2** | SSH credentials to EC2 instance |
|
|
348
|
-
| **azure-vm** / **gcp-vm** | SSH to VM |
|
|
349
|
-
| **kubernetes** | Cluster credentials (via env / kubeconfig) |
|
|
350
|
-
|
|
351
340
|
**Example: Angular → Azure Blob + SSH**
|
|
352
341
|
|
|
353
342
|
```bash
|
|
@@ -368,7 +357,7 @@ Add `AZURE_*`, `SSH_HOST`, `SSH_USER`, `SSH_KEY` to `.env` and GitHub Secrets. R
|
|
|
368
357
|
| React | `npm run build` | `dist` or `build` | Create React App uses `build` |
|
|
369
358
|
| Vue | `npm run build` | `dist` | Vite default |
|
|
370
359
|
| Angular | `ng build` | `dist` | |
|
|
371
|
-
| Next.js | `npm run build` | `.next` |
|
|
360
|
+
| Next.js | `npm run build` | `.next` | Use Vercel/Netlify native deploy for managed hosting |
|
|
372
361
|
| Svelte | `npm run build` | `public` | |
|
|
373
362
|
| Astro | `astro build` | `dist` | |
|
|
374
363
|
| Vanilla JS | *(none)* | `.` | Copies static files as-is |
|
|
@@ -377,7 +366,7 @@ Add `AZURE_*`, `SSH_HOST`, `SSH_USER`, `SSH_KEY` to `.env` and GitHub Secrets. R
|
|
|
377
366
|
|
|
378
367
|
## Walkthrough: Backend only
|
|
379
368
|
|
|
380
|
-
Backends always deploy to a **self-hosted target** (SSH, Docker, EC2, Azure VM, GCP VM, or Kubernetes).
|
|
369
|
+
Backends always deploy to a **self-hosted target** (SSH, Docker, EC2, Azure VM, GCP VM, or Kubernetes).
|
|
381
370
|
|
|
382
371
|
### Init choices
|
|
383
372
|
|
|
@@ -453,10 +442,10 @@ Choose **Both (monorepo / fullstack)** when frontend and backend live in the **s
|
|
|
453
442
|
2. **Backend** — framework, start command, port.
|
|
454
443
|
3. **Storage** — one or more providers.
|
|
455
444
|
4. **Configure deployment?** → Yes.
|
|
456
|
-
5. **Frontend deploy
|
|
457
|
-
6. **Backend deploy** —
|
|
445
|
+
5. **Frontend deploy path** — self-hosted SSH (static files + nginx).
|
|
446
|
+
6. **Backend deploy** — SSH, Docker, EC2, etc.
|
|
458
447
|
|
|
459
|
-
DeployHub runs **both** builds, packs them into one artifact, uploads once, then deploys frontend and backend to their respective targets.
|
|
448
|
+
DeployHub runs **both** builds, packs them into one artifact, uploads once, then deploys frontend and backend to their respective server targets.
|
|
460
449
|
|
|
461
450
|
### Example: React + Express monorepo
|
|
462
451
|
|
|
@@ -467,11 +456,11 @@ deployhub init
|
|
|
467
456
|
# Backend: Express, npm start, port 3000
|
|
468
457
|
# Storage: AWS S3 + Local
|
|
469
458
|
# Configure deployment? Yes
|
|
470
|
-
# Frontend:
|
|
459
|
+
# Frontend: ssh → /var/www/my-app/public
|
|
471
460
|
# Backend: ssh → api.example.com, path /var/www/my-app/api, PM2 name my-app-api
|
|
472
461
|
```
|
|
473
462
|
|
|
474
|
-
**Secrets:** AWS + `
|
|
463
|
+
**Secrets:** AWS + `SSH_*`
|
|
475
464
|
|
|
476
465
|
**Layout tip:** Keep `package.json` scripts for both apps at the repo root, or ensure build commands point to the correct paths (edit `deployhub.config.json` after init if your monorepo uses subfolders).
|
|
477
466
|
|
|
@@ -489,7 +478,7 @@ DeployHub generates `nginx.conf` to serve static files and proxy API requests.
|
|
|
489
478
|
|
|
490
479
|
### Example: Next.js API routes only
|
|
491
480
|
|
|
492
|
-
Use **Frontend only** with **Next.js** and deploy
|
|
481
|
+
Use **Frontend only** with **Next.js** and deploy via **SSH** or your platform's native git-push workflow — no separate backend entry needed.
|
|
493
482
|
|
|
494
483
|
---
|
|
495
484
|
|
|
@@ -585,9 +574,9 @@ You can enable **multiple providers** — DeployHub uploads to all of them in pa
|
|
|
585
574
|
|
|
586
575
|
| Project type | Frontend deploy options | Backend deploy options |
|
|
587
576
|
|--------------|-------------------------|------------------------|
|
|
588
|
-
| Frontend only |
|
|
577
|
+
| Frontend only | SSH/Docker/EC2/Azure VM/GCP VM/K8s | — |
|
|
589
578
|
| Backend only | — | SSH/Docker/EC2/Azure VM/GCP VM/K8s |
|
|
590
|
-
| Full stack |
|
|
579
|
+
| Full stack | SSH (static + nginx) | SSH/Docker/EC2/K8s (always) |
|
|
591
580
|
|
|
592
581
|
| Mode | Storage | Deploy | When to use |
|
|
593
582
|
|------|---------|--------|-------------|
|
|
@@ -654,7 +643,6 @@ Prefer `deployhub init` over hand-writing config — it sets adapters, workflow,
|
|
|
654
643
|
| SSH deploy fails | Verify `SSH_KEY` is the **private** key; user can write to deploy path |
|
|
655
644
|
| Wrong output uploaded | Fix `buildOutput` in config (`dist` vs `build` vs `.next`) |
|
|
656
645
|
| Tests fail in CI | Set `"pipeline": { "test": false }` temporarily, or fix tests |
|
|
657
|
-
| Platform deploy missing CLI | Install platform CLI in CI (workflow does this for Vercel, Netlify, etc.) |
|
|
658
646
|
| Monorepo subfolders | Edit `buildCommand` paths in `deployhub.config.json` after init |
|
|
659
647
|
|
|
660
648
|
Run `deployhub doctor` after any config change.
|
|
@@ -715,18 +703,6 @@ Add these secrets in your repository (Settings → Secrets and variables → Act
|
|
|
715
703
|
| `SSH_APP_NAME` | PM2 process name for backends |
|
|
716
704
|
| `SSH_PORT` | App port on server (optional) |
|
|
717
705
|
|
|
718
|
-
### Managed platforms (frontend)
|
|
719
|
-
|
|
720
|
-
| Secret | Platform |
|
|
721
|
-
|--------|----------|
|
|
722
|
-
| `VERCEL_TOKEN`, `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID` | Vercel |
|
|
723
|
-
| `NETLIFY_AUTH_TOKEN`, `NETLIFY_SITE_ID` | Netlify |
|
|
724
|
-
| `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`, `CF_PROJECT_NAME` | Cloudflare Pages |
|
|
725
|
-
| `AMPLIFY_APP_ID` (+ `AWS_*` if not already set) | AWS Amplify |
|
|
726
|
-
| `AZURE_STATIC_WEB_APPS_TOKEN` | Azure Static Web Apps |
|
|
727
|
-
| `FIREBASE_TOKEN`, `FIREBASE_PROJECT_ID` | Firebase Hosting |
|
|
728
|
-
| `FIREBASE_APP_HOSTING_BACKEND` | Firebase App Hosting |
|
|
729
|
-
|
|
730
706
|
## `deployhub doctor` Output
|
|
731
707
|
|
|
732
708
|
The doctor command runs independent checks and always completes without crashing:
|
|
@@ -773,6 +749,25 @@ artifact/
|
|
|
773
749
|
README.md
|
|
774
750
|
```
|
|
775
751
|
|
|
752
|
+
`deployment.json` records server deployment metadata per environment:
|
|
753
|
+
|
|
754
|
+
```json
|
|
755
|
+
{
|
|
756
|
+
"targets": ["production"],
|
|
757
|
+
"deployedAt": "2026-07-01T12:00:00.000Z",
|
|
758
|
+
"deployments": [
|
|
759
|
+
{
|
|
760
|
+
"environmentName": "production",
|
|
761
|
+
"serverAddress": "203.0.113.10",
|
|
762
|
+
"processId": "my-api",
|
|
763
|
+
"timestamp": "2026-07-01T12:00:00.000Z"
|
|
764
|
+
}
|
|
765
|
+
]
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
Rollback redeploys a previous artifact to the configured server targets using this metadata.
|
|
770
|
+
|
|
776
771
|
## Configuration
|
|
777
772
|
|
|
778
773
|
`deployhub.config.json` is generated by `init`. Credentials are **never** stored in this file — only in `.env` or GitHub Secrets.
|
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -7,9 +7,6 @@ import { loadConfig, loadEnv } from '../core/config.js';
|
|
|
7
7
|
import { testProvider } from '../storage/index.js';
|
|
8
8
|
import { getDeploymentProvider } from '../deployment/index.js';
|
|
9
9
|
import { PROVIDER_ENV_MAP } from '../utils/github-actions.js';
|
|
10
|
-
import { PLATFORM_ENV_MAP, PLATFORM_CLI_MAP } from '../utils/platform-env.js';
|
|
11
|
-
import { createPlatformProvider } from '../deployment/providers/platforms/index.js';
|
|
12
|
-
import { isCliInstalled } from '../deployment/providers/platforms/_shared.js';
|
|
13
10
|
import { printDoctorFooter } from '../utils/author.js';
|
|
14
11
|
import { createLocalProvider } from '../storage/providers/local.js';
|
|
15
12
|
|
|
@@ -163,132 +160,6 @@ async function runBackendProcessChecks(config, envName) {
|
|
|
163
160
|
return checks;
|
|
164
161
|
}
|
|
165
162
|
|
|
166
|
-
/**
|
|
167
|
-
* @param {import('../core/config.js').DeployHubConfig} config
|
|
168
|
-
* @param {string} envName
|
|
169
|
-
* @param {string} [cwd]
|
|
170
|
-
* @returns {Promise<CheckResult[]>}
|
|
171
|
-
*/
|
|
172
|
-
async function runPlatformChecks(config, envName, cwd = process.cwd()) {
|
|
173
|
-
const env = config.environments[envName];
|
|
174
|
-
const platform = env?.platform;
|
|
175
|
-
if (!platform) return [];
|
|
176
|
-
|
|
177
|
-
const cli = PLATFORM_CLI_MAP[platform];
|
|
178
|
-
const envKeys = PLATFORM_ENV_MAP[platform] || [];
|
|
179
|
-
/** @type {CheckResult[]} */
|
|
180
|
-
const checks = [];
|
|
181
|
-
|
|
182
|
-
if (cli?.binary) {
|
|
183
|
-
const cliLabel = cli.binary.charAt(0).toUpperCase() + cli.binary.slice(1);
|
|
184
|
-
checks.push(
|
|
185
|
-
await runCheck(`${cliLabel} CLI`, async () => {
|
|
186
|
-
const installed = await isCliInstalled(cli.binary);
|
|
187
|
-
if (installed) {
|
|
188
|
-
return { name: `${cliLabel} CLI`, pass: true, message: `${cli.binary} CLI installed` };
|
|
189
|
-
}
|
|
190
|
-
return {
|
|
191
|
-
name: `${cliLabel} CLI`,
|
|
192
|
-
pass: false,
|
|
193
|
-
message: `not found — run: ${cli.globalInstall || `npm install -g ${cli.install}`}`,
|
|
194
|
-
};
|
|
195
|
-
})
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
for (const key of envKeys) {
|
|
200
|
-
const isToken = key.includes('TOKEN') || key.includes('KEY');
|
|
201
|
-
checks.push(
|
|
202
|
-
await runCheck(key, async () => {
|
|
203
|
-
if (!process.env[key]) {
|
|
204
|
-
return { name: key, pass: false, message: 'not set in .env' };
|
|
205
|
-
}
|
|
206
|
-
if (isToken && key === envKeys[0]) {
|
|
207
|
-
try {
|
|
208
|
-
const provider = createPlatformProvider(platform, config, envName);
|
|
209
|
-
if (provider.testConnection) {
|
|
210
|
-
await provider.testConnection();
|
|
211
|
-
return { name: key, pass: true, message: 'token valid' };
|
|
212
|
-
}
|
|
213
|
-
} catch (err) {
|
|
214
|
-
return {
|
|
215
|
-
name: key,
|
|
216
|
-
pass: false,
|
|
217
|
-
message: err instanceof Error ? err.message : 'token invalid',
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return { name: key, pass: true, message: 'present' };
|
|
222
|
-
})
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (platform === 'vercel') {
|
|
227
|
-
checks.push(
|
|
228
|
-
await runCheck('Vercel project link', async () => {
|
|
229
|
-
const vercelJson = path.join(cwd, '.vercel', 'project.json');
|
|
230
|
-
if (await fs.pathExists(vercelJson)) {
|
|
231
|
-
return { name: 'Vercel project link', pass: true, message: '.vercel/project.json found' };
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
name: 'Vercel project link',
|
|
235
|
-
pass: false,
|
|
236
|
-
message: 'not found — run: vercel link',
|
|
237
|
-
};
|
|
238
|
-
})
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (platform === 'firebase-hosting') {
|
|
243
|
-
checks.push(
|
|
244
|
-
await runCheck('firebase.json', async () => {
|
|
245
|
-
if (await fs.pathExists(path.join(cwd, 'firebase.json'))) {
|
|
246
|
-
return { name: 'firebase.json', pass: true, message: 'found' };
|
|
247
|
-
}
|
|
248
|
-
return {
|
|
249
|
-
name: 'firebase.json',
|
|
250
|
-
pass: false,
|
|
251
|
-
message: 'missing — run deployhub init or create manually',
|
|
252
|
-
};
|
|
253
|
-
})
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
const provider = createPlatformProvider(platform, config, envName);
|
|
259
|
-
if (provider.testConnection && envKeys.every((k) => process.env[k])) {
|
|
260
|
-
checks.push(
|
|
261
|
-
await runCheck(`${platform} connection`, async () => {
|
|
262
|
-
await provider.testConnection();
|
|
263
|
-
if (platform === 'netlify') {
|
|
264
|
-
return { name: 'NETLIFY_SITE_ID', pass: true, message: 'site found' };
|
|
265
|
-
}
|
|
266
|
-
if (platform === 'cloudflare-pages') {
|
|
267
|
-
return { name: 'CF project', pass: true, message: 'project exists' };
|
|
268
|
-
}
|
|
269
|
-
if (platform === 'aws-amplify') {
|
|
270
|
-
return { name: 'AMPLIFY_APP_ID', pass: true, message: 'app found in AWS' };
|
|
271
|
-
}
|
|
272
|
-
if (platform.startsWith('firebase')) {
|
|
273
|
-
return { name: 'Firebase project', pass: true, message: 'project ID valid' };
|
|
274
|
-
}
|
|
275
|
-
return { name: `${platform} connection`, pass: true, message: 'connected' };
|
|
276
|
-
})
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
} catch (err) {
|
|
280
|
-
if (envKeys.every((k) => process.env[k])) {
|
|
281
|
-
checks.push({
|
|
282
|
-
name: `${platform} connection`,
|
|
283
|
-
pass: false,
|
|
284
|
-
message: err instanceof Error ? err.message : String(err),
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return checks;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
163
|
/**
|
|
293
164
|
* @param {import('commander').Command} program
|
|
294
165
|
*/
|
|
@@ -467,11 +338,6 @@ export function registerDoctorCommand(program) {
|
|
|
467
338
|
const env = config.environments[envName];
|
|
468
339
|
if (!env) continue;
|
|
469
340
|
|
|
470
|
-
if (env.deploymentType === 'platform' || env.frontendDeploymentType === 'platform') {
|
|
471
|
-
const platformChecks = await runPlatformChecks(config, envName, cwd);
|
|
472
|
-
results.push(...platformChecks);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
341
|
if (env.type && ['ssh', 'ec2', 'azure-vm', 'gcp-vm'].includes(env.type)) {
|
|
476
342
|
results.push(
|
|
477
343
|
await runCheck('SSH target', async () => {
|
|
@@ -541,13 +407,7 @@ export function registerDoctorCommand(program) {
|
|
|
541
407
|
const env = config.environments[envName];
|
|
542
408
|
if (!env) continue;
|
|
543
409
|
|
|
544
|
-
if (env.
|
|
545
|
-
const platform = env.platform;
|
|
546
|
-
if (platform) {
|
|
547
|
-
const keys = PLATFORM_ENV_MAP[platform] || [];
|
|
548
|
-
required.push(...keys);
|
|
549
|
-
}
|
|
550
|
-
} else if (env.type) {
|
|
410
|
+
if (env.type) {
|
|
551
411
|
const keys = PROVIDER_ENV_MAP[env.type] || [];
|
|
552
412
|
required.push(...keys);
|
|
553
413
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -18,15 +18,7 @@ import {
|
|
|
18
18
|
addDeployhubToPackageJson,
|
|
19
19
|
DEFAULT_NPM_CLI_SOURCE,
|
|
20
20
|
} from '../utils/github-actions.js';
|
|
21
|
-
import {
|
|
22
|
-
promptPlatformQuestions,
|
|
23
|
-
appendPlatformEnvExample,
|
|
24
|
-
showPlatformComparison,
|
|
25
|
-
suggestHealthUrl,
|
|
26
|
-
PLATFORM_CHOICES,
|
|
27
|
-
} from '../utils/init-platform.js';
|
|
28
21
|
import { printAuthorFooter } from '../utils/author.js';
|
|
29
|
-
import { ensureFirebaseJson } from '../utils/firebase-config-generator.js';
|
|
30
22
|
import { generateNginxConfig } from '../utils/nginx.js';
|
|
31
23
|
|
|
32
24
|
const FRONTEND_CHOICES = [
|
|
@@ -297,24 +289,10 @@ function buildServerEnvEntry(
|
|
|
297
289
|
async function generateProjectScaffold(config, environments, cwd) {
|
|
298
290
|
const envList = Object.values(environments);
|
|
299
291
|
|
|
300
|
-
const
|
|
301
|
-
(env) =>
|
|
302
|
-
env.platform === 'firebase-hosting' || env.platform === 'firebase-app-hosting'
|
|
292
|
+
const usesFrontendSsh = envList.some(
|
|
293
|
+
(env) => env.type === 'ssh' || env.deploymentType === 'server'
|
|
303
294
|
);
|
|
304
295
|
|
|
305
|
-
if (usesFirebase && !(await fs.pathExists(path.join(cwd, 'firebase.json')))) {
|
|
306
|
-
const buildOutput =
|
|
307
|
-
config.frontend?.buildOutput || config.buildOutput || 'dist';
|
|
308
|
-
await ensureFirebaseJson(buildOutput, cwd);
|
|
309
|
-
console.log(chalk.gray(' • firebase.json (auto-generated)'));
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const usesFrontendSsh = envList.some((env) => {
|
|
313
|
-
if (env.frontendDeploymentType === 'platform') return false;
|
|
314
|
-
if (env.deploymentType === 'platform' && !env.type) return false;
|
|
315
|
-
return env.type === 'ssh' || env.deploymentType === 'server';
|
|
316
|
-
});
|
|
317
|
-
|
|
318
296
|
const isFrontendProject =
|
|
319
297
|
config.projectType === 'frontend' || config.projectType === 'both';
|
|
320
298
|
|
|
@@ -455,9 +433,6 @@ export function registerInitCommand(program) {
|
|
|
455
433
|
let healthUrl = '';
|
|
456
434
|
|
|
457
435
|
if (answers.configureDeploy) {
|
|
458
|
-
const buildOutput =
|
|
459
|
-
frontendConfig?.buildOutput || singleConfig?.buildOutput || 'dist';
|
|
460
|
-
|
|
461
436
|
if (projectType === 'backend') {
|
|
462
437
|
const deployAnswers = await promptServerDeployment(
|
|
463
438
|
projectName,
|
|
@@ -479,103 +454,24 @@ export function registerInitCommand(program) {
|
|
|
479
454
|
healthUrl = `http://localhost:${singleConfig.port}/health`;
|
|
480
455
|
}
|
|
481
456
|
} else if (projectType === 'frontend') {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
]);
|
|
499
|
-
|
|
500
|
-
const { envName } = await inquirer.prompt([
|
|
501
|
-
{
|
|
502
|
-
type: 'input',
|
|
503
|
-
name: 'envName',
|
|
504
|
-
message: 'Environment name:',
|
|
505
|
-
default: 'production',
|
|
506
|
-
},
|
|
507
|
-
]);
|
|
508
|
-
deploy.push(envName);
|
|
509
|
-
|
|
510
|
-
if (deployMethod === 'platform') {
|
|
511
|
-
showPlatformComparison();
|
|
512
|
-
const { platform } = await inquirer.prompt([
|
|
513
|
-
{
|
|
514
|
-
type: 'list',
|
|
515
|
-
name: 'platform',
|
|
516
|
-
message: 'Select platform:',
|
|
517
|
-
choices: PLATFORM_CHOICES,
|
|
518
|
-
},
|
|
519
|
-
]);
|
|
520
|
-
|
|
521
|
-
const platformConfig = await promptPlatformQuestions(
|
|
522
|
-
platform,
|
|
523
|
-
projectName,
|
|
524
|
-
buildOutput,
|
|
525
|
-
cwd
|
|
526
|
-
);
|
|
527
|
-
await appendPlatformEnvExample(platform, cwd);
|
|
528
|
-
|
|
529
|
-
const { healthUrlInput } = await inquirer.prompt([
|
|
530
|
-
{
|
|
531
|
-
type: 'input',
|
|
532
|
-
name: 'healthUrlInput',
|
|
533
|
-
message: 'Health check URL (optional):',
|
|
534
|
-
default: suggestHealthUrl(platform, projectName),
|
|
535
|
-
},
|
|
536
|
-
]);
|
|
537
|
-
|
|
538
|
-
environments[envName] = {
|
|
539
|
-
deploymentType: 'platform',
|
|
540
|
-
...platformConfig,
|
|
541
|
-
};
|
|
542
|
-
healthUrl = healthUrlInput || suggestHealthUrl(platform, projectName);
|
|
543
|
-
} else {
|
|
544
|
-
const deployAnswers = await promptServerDeployment(
|
|
545
|
-
projectName,
|
|
546
|
-
projectType,
|
|
547
|
-
backendConfig,
|
|
548
|
-
singleConfig
|
|
549
|
-
);
|
|
550
|
-
deployAnswers.envName = envName;
|
|
551
|
-
environments[envName] = buildServerEnvEntry(
|
|
552
|
-
deployAnswers,
|
|
553
|
-
projectType,
|
|
554
|
-
projectName,
|
|
555
|
-
backendConfig,
|
|
556
|
-
singleConfig
|
|
557
|
-
);
|
|
558
|
-
if (deployAnswers.healthUrl) healthUrl = deployAnswers.healthUrl;
|
|
457
|
+
const deployAnswers = await promptServerDeployment(
|
|
458
|
+
projectName,
|
|
459
|
+
projectType,
|
|
460
|
+
backendConfig,
|
|
461
|
+
singleConfig
|
|
462
|
+
);
|
|
463
|
+
deploy.push(deployAnswers.envName);
|
|
464
|
+
environments[deployAnswers.envName] = buildServerEnvEntry(
|
|
465
|
+
deployAnswers,
|
|
466
|
+
projectType,
|
|
467
|
+
projectName,
|
|
468
|
+
backendConfig,
|
|
469
|
+
singleConfig
|
|
470
|
+
);
|
|
471
|
+
if (deployAnswers.healthUrl) {
|
|
472
|
+
healthUrl = deployAnswers.healthUrl;
|
|
559
473
|
}
|
|
560
474
|
} else {
|
|
561
|
-
const { frontendDeployMethod } = await inquirer.prompt([
|
|
562
|
-
{
|
|
563
|
-
type: 'list',
|
|
564
|
-
name: 'frontendDeployMethod',
|
|
565
|
-
message: 'How do you want to deploy the frontend?',
|
|
566
|
-
choices: [
|
|
567
|
-
{
|
|
568
|
-
name: 'Managed platform (Vercel, Netlify, Cloudflare Pages, etc.)',
|
|
569
|
-
value: 'platform',
|
|
570
|
-
},
|
|
571
|
-
{
|
|
572
|
-
name: 'Self-hosted server (SSH, Docker, EC2, etc.)',
|
|
573
|
-
value: 'server',
|
|
574
|
-
},
|
|
575
|
-
],
|
|
576
|
-
},
|
|
577
|
-
]);
|
|
578
|
-
|
|
579
475
|
const { envName } = await inquirer.prompt([
|
|
580
476
|
{
|
|
581
477
|
type: 'input',
|
|
@@ -587,36 +483,11 @@ export function registerInitCommand(program) {
|
|
|
587
483
|
deploy.push(envName);
|
|
588
484
|
|
|
589
485
|
/** @type {Record<string, unknown>} */
|
|
590
|
-
const envEntry = {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
{
|
|
596
|
-
type: 'list',
|
|
597
|
-
name: 'platform',
|
|
598
|
-
message: 'Select frontend platform:',
|
|
599
|
-
choices: PLATFORM_CHOICES,
|
|
600
|
-
},
|
|
601
|
-
]);
|
|
602
|
-
|
|
603
|
-
const platformConfig = await promptPlatformQuestions(
|
|
604
|
-
platform,
|
|
605
|
-
projectName,
|
|
606
|
-
buildOutput,
|
|
607
|
-
cwd
|
|
608
|
-
);
|
|
609
|
-
await appendPlatformEnvExample(platform, cwd);
|
|
610
|
-
|
|
611
|
-
Object.assign(envEntry, {
|
|
612
|
-
frontendDeploymentType: 'platform',
|
|
613
|
-
deploymentType: 'platform',
|
|
614
|
-
...platformConfig,
|
|
615
|
-
});
|
|
616
|
-
healthUrl = suggestHealthUrl(platform, projectName);
|
|
617
|
-
} else {
|
|
618
|
-
envEntry.frontendDeploymentType = 'server';
|
|
619
|
-
}
|
|
486
|
+
const envEntry = {
|
|
487
|
+
backendDeploymentType: 'server',
|
|
488
|
+
frontendDeploymentType: 'server',
|
|
489
|
+
deploymentType: 'server',
|
|
490
|
+
};
|
|
620
491
|
|
|
621
492
|
console.log(chalk.gray('\nBackend will be deployed to a self-hosted server.'));
|
|
622
493
|
const backendDeployAnswers = await inquirer.prompt([
|
|
@@ -664,8 +535,7 @@ export function registerInitCommand(program) {
|
|
|
664
535
|
path: backendDeployAnswers.backendDeployPath || `/var/www/${projectName}/api`,
|
|
665
536
|
});
|
|
666
537
|
|
|
667
|
-
|
|
668
|
-
const { frontendDeployPath } = await inquirer.prompt([
|
|
538
|
+
const { frontendDeployPath } = await inquirer.prompt([
|
|
669
539
|
{
|
|
670
540
|
type: 'input',
|
|
671
541
|
name: 'frontendDeployPath',
|
|
@@ -673,8 +543,7 @@ export function registerInitCommand(program) {
|
|
|
673
543
|
default: `/var/www/${projectName}/public`,
|
|
674
544
|
},
|
|
675
545
|
]);
|
|
676
|
-
|
|
677
|
-
}
|
|
546
|
+
envEntry.frontendDeployPath = frontendDeployPath;
|
|
678
547
|
|
|
679
548
|
const { healthUrlInput } = await inquirer.prompt([
|
|
680
549
|
{
|
package/src/core/config.js
CHANGED
|
@@ -14,19 +14,8 @@ const SideConfigSchema = z.object({
|
|
|
14
14
|
|
|
15
15
|
const EnvironmentSchema = z.object({
|
|
16
16
|
type: z.string().optional(),
|
|
17
|
-
deploymentType: z.enum(['
|
|
18
|
-
|
|
19
|
-
projectName: z.string().optional(),
|
|
20
|
-
siteId: z.string().optional(),
|
|
21
|
-
accountId: z.string().optional(),
|
|
22
|
-
appId: z.string().optional(),
|
|
23
|
-
region: z.string().optional(),
|
|
24
|
-
branch: z.string().optional(),
|
|
25
|
-
githubConnected: z.boolean().optional(),
|
|
26
|
-
resourceName: z.string().optional(),
|
|
27
|
-
projectId: z.string().optional(),
|
|
28
|
-
backendName: z.string().optional(),
|
|
29
|
-
frontendDeploymentType: z.enum(['platform', 'server']).optional(),
|
|
17
|
+
deploymentType: z.enum(['server']).optional(),
|
|
18
|
+
frontendDeploymentType: z.enum(['server']).optional(),
|
|
30
19
|
backendDeploymentType: z.enum(['server']).optional(),
|
|
31
20
|
host: z.string().optional(),
|
|
32
21
|
user: z.string().optional(),
|
package/src/core/stages.js
CHANGED
|
@@ -5,8 +5,6 @@ import { uploadToAll } from '../storage/index.js';
|
|
|
5
5
|
import { deployToAll } from '../deployment/index.js';
|
|
6
6
|
import { sendNotifications } from '../notifications/index.js';
|
|
7
7
|
import axios from 'axios';
|
|
8
|
-
import fs from 'fs-extra';
|
|
9
|
-
import path from 'path';
|
|
10
8
|
import { getProjectVersion } from '../utils/version.js';
|
|
11
9
|
|
|
12
10
|
/**
|
|
@@ -149,15 +147,6 @@ export function buildPipelineStages(config, cwd, state) {
|
|
|
149
147
|
const deployed = await deployToAll(ctx.config, artifactDir);
|
|
150
148
|
ctx.state.deployedTargets = deployed;
|
|
151
149
|
|
|
152
|
-
const deploymentPath = path.join(artifactDir, 'deployment.json');
|
|
153
|
-
if (await fs.pathExists(deploymentPath)) {
|
|
154
|
-
const data = await fs.readJson(deploymentPath);
|
|
155
|
-
const last = data.lastDeployment;
|
|
156
|
-
if (last?.deployUrl || last?.deploymentUrl) {
|
|
157
|
-
ctx.state.lastDeployUrl = last.deployUrl || last.deploymentUrl;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
150
|
await repackArtifactZip(artifactDir);
|
|
162
151
|
const zipPath = /** @type {string} */ (ctx.state.zipPath);
|
|
163
152
|
if (zipPath) {
|