@akash-chowdhury-24/deployhub 1.0.6 → 1.0.8

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 CHANGED
@@ -109,6 +109,558 @@ deployhub build
109
109
 
110
110
  Or push to `main` — GitHub Actions runs `deployhub build` automatically.
111
111
 
112
+ For a full walkthrough by project type, language, and deployment mode, see [Complete Tutorial](#complete-tutorial) below.
113
+
114
+ ## Complete Tutorial
115
+
116
+ This section walks through every supported setup: **frontend only**, **backend only**, **full stack**, **storage only** (build + upload artifacts, no deploy), and **storage + deployment** (build, upload, then deploy). Use it as a checklist from zero to a working pipeline.
117
+
118
+ ### What DeployHub does on every run
119
+
120
+ When you run `deployhub build` (locally or in GitHub Actions), DeployHub runs these stages in order:
121
+
122
+ | Stage | What happens |
123
+ |-------|----------------|
124
+ | **detect** | Auto-detect framework, language, build output |
125
+ | **install** | Install dependencies (`npm ci`, `pip install`, `mvn`, etc.) |
126
+ | **test** | Run tests (skippable via config) |
127
+ | **build** | Run your build command(s) |
128
+ | **docker** | Build Docker image if `Dockerfile` exists and enabled |
129
+ | **artifact** | Create versioned `artifact.zip` + metadata locally |
130
+ | **storage** | Upload artifact to all selected providers (parallel) |
131
+ | **deploy** | Deploy to targets — **only if you configured deployment during `init`** |
132
+ | **verify** | Hit your health-check URL — **only if configured** |
133
+ | **notify** | Slack / email / webhook — **only if enabled** |
134
+
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 or platform.
136
+
137
+ **Storage + deployment** means you answer **Yes**, pick targets, and add the matching secrets. Deploy always runs **after** storage upload succeeds.
138
+
139
+ ---
140
+
141
+ ### Prerequisites (all projects)
142
+
143
+ 1. **Git repository** with a remote (GitHub recommended for CI).
144
+ 2. **DeployHub installed** — see [Installation](#installation) above.
145
+ 3. **Run from your project root** (where `package.json`, `go.mod`, `pom.xml`, etc. lives).
146
+
147
+ | Language / stack | You need on the machine / in CI |
148
+ |------------------|----------------------------------|
149
+ | Node.js (React, Vue, Express, NestJS, …) | Node.js 18+, npm |
150
+ | Python (FastAPI, Django, Flask) | Python 3.11+, `requirements.txt` or `pyproject.toml` |
151
+ | PHP (Laravel, Symfony) | PHP, Composer, `composer.json` |
152
+ | Java (Spring Boot) | JDK 17+, Maven, `pom.xml` |
153
+ | Go | Go 1.22+, `go.mod` |
154
+ | .NET | .NET 8 SDK, `.csproj` |
155
+ | Ruby on Rails | Ruby 3.2+, Bundler, `Gemfile` |
156
+
157
+ ---
158
+
159
+ ### Step 1 — Initialize (every workflow)
160
+
161
+ ```bash
162
+ cd your-project
163
+ deployhub init
164
+ ```
165
+
166
+ The wizard asks the same core questions for every setup:
167
+
168
+ | Prompt | What to choose |
169
+ |--------|----------------|
170
+ | **Project name** | Defaults to folder name; used in artifact paths and deploy paths |
171
+ | **What are you deploying?** | `Frontend only` · `Backend only` · `Both (monorepo / fullstack)` |
172
+ | **Framework** | Auto-detected when possible; confirm or change |
173
+ | **Build command / output** | Pre-filled per framework (see [Framework defaults](#framework-defaults-by-language) below) |
174
+ | **Storage providers** | Pick one or more: Local, AWS S3, Google Drive, Azure, GCP, Dropbox |
175
+ | **Configure deployment?** | **No** = storage only · **Yes** = storage + deploy |
176
+ | **CLI source for GitHub Actions** | Default `npm:@akash-chowdhury-24/deployhub` is fine for most users |
177
+
178
+ **Generated files:**
179
+
180
+ - `deployhub.config.json` — project settings (no secrets)
181
+ - `.github/workflows/deployhub.yml` — CI pipeline
182
+ - `.env.example` — list of env vars you may need
183
+ - `nginx.conf` — auto-generated if frontend deploys to SSH
184
+ - `firebase.json` — auto-generated if you pick Firebase Hosting
185
+
186
+ ---
187
+
188
+ ### Step 2 — Credentials
189
+
190
+ ```bash
191
+ cp .env.example .env
192
+ # Edit .env locally
193
+
194
+ deployhub storage add aws # repeat per provider
195
+ deployhub storage add gdrive
196
+ ```
197
+
198
+ For **GitHub Actions**, add the same values as repository secrets (Settings → Secrets and variables → Actions). See [GitHub Secrets](#github-secrets).
199
+
200
+ ---
201
+
202
+ ### Step 3 — Verify
203
+
204
+ ```bash
205
+ deployhub doctor
206
+ ```
207
+
208
+ Fix any ✗ items before your first build.
209
+
210
+ ---
211
+
212
+ ### Step 4 — Build (and deploy if configured)
213
+
214
+ ```bash
215
+ deployhub build
216
+ ```
217
+
218
+ Or push to `main` / `master` — the generated workflow runs the same command.
219
+
220
+ **Useful follow-up commands:**
221
+
222
+ ```bash
223
+ deployhub artifact list # see uploaded versions
224
+ deployhub artifact restore v1.2.3 # download a past build
225
+ deployhub deploy # deploy latest artifact without rebuilding
226
+ deployhub rollback v1.2.2 # roll back on server
227
+ deployhub logs # last deployment logs
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Walkthrough: Storage only
233
+
234
+ Use this when you want **versioned build artifacts in the cloud** but deploy manually (or add deployment later).
235
+
236
+ ### During `deployhub init`
237
+
238
+ 1. Choose project type (frontend / backend / both).
239
+ 2. Select framework and confirm build settings.
240
+ 3. Check at least one **storage** provider (Local is checked by default).
241
+ 4. Answer **Configure deployment?** → **No**.
242
+
243
+ ### Resulting config
244
+
245
+ `deployhub.config.json` will have `"deploy": []` and `"pipeline": { "deploy": false }`. Every `deployhub build` still runs detect → install → test → build → artifact → **storage**.
246
+
247
+ ### Example: React app → AWS S3 only
248
+
249
+ ```bash
250
+ deployhub init
251
+ # What are you deploying? → Frontend only
252
+ # Framework → React
253
+ # Build command → npm run build
254
+ # Build output → dist
255
+ # Storage → ✓ AWS S3
256
+ # Configure deployment? → No
257
+ ```
258
+
259
+ ```bash
260
+ cp .env.example .env
261
+ # Set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_BUCKET, AWS_REGION
262
+
263
+ deployhub doctor
264
+ deployhub build
265
+ ```
266
+
267
+ Artifacts appear under `artifact/{projectName}/{date}/v{version}/` locally **and** in your S3 bucket.
268
+
269
+ ### Same steps for other languages
270
+
271
+ Storage-only init is **identical** for every language — only the framework/build prompts change. See [Framework defaults](#framework-defaults-by-language).
272
+
273
+ ---
274
+
275
+ ## Walkthrough: Storage + deployment
276
+
277
+ Use this when you want **build → upload artifact → deploy** in one command.
278
+
279
+ ### During `deployhub init`
280
+
281
+ 1. Complete project type + framework setup.
282
+ 2. Select storage provider(s).
283
+ 3. Answer **Configure deployment?** → **Yes**.
284
+ 4. Follow the deployment prompts (differs by project type — below).
285
+
286
+ Deployment **requires** at least one storage provider. DeployHub restores from the uploaded artifact on the target.
287
+
288
+ ---
289
+
290
+ ## Walkthrough: Frontend only
291
+
292
+ ### Init choices
293
+
294
+ | Prompt | Options |
295
+ |--------|---------|
296
+ | What are you deploying? | **Frontend only** |
297
+ | Framework | React, Vue, Angular, Next.js, Svelte, Astro, Vanilla JS, Other |
298
+ | Configure deployment? | No (storage only) or Yes |
299
+
300
+ If **Yes** to deployment:
301
+
302
+ | Prompt | Options |
303
+ |--------|---------|
304
+ | How do you want to deploy? | **Managed platform** or **Self-hosted server** |
305
+
306
+ #### Option A — Managed platform (Vercel, Netlify, …)
307
+
308
+ Best for static sites and Jamstack frontends. DeployHub builds locally/CI, uploads the artifact, then invokes the platform CLI.
309
+
310
+ | Platform | Best for | Secrets to add |
311
+ |----------|----------|----------------|
312
+ | Vercel | Next.js, React | `VERCEL_TOKEN`, `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID` |
313
+ | Netlify | React, Vue, Svelte | `NETLIFY_AUTH_TOKEN`, `NETLIFY_SITE_ID` |
314
+ | Cloudflare Pages | Astro, static React | `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`, `CF_PROJECT_NAME` |
315
+ | AWS Amplify | React, Next.js on AWS | `AWS_*`, `AMPLIFY_APP_ID` |
316
+ | Azure Static Web Apps | React, Angular | `AZURE_STATIC_WEB_APPS_TOKEN` |
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` |
319
+
320
+ **Example: Vue → Google Drive + Netlify**
321
+
322
+ ```bash
323
+ deployhub init
324
+ # Frontend only → Vue
325
+ # Storage: Local + Google Drive
326
+ # Configure deployment? Yes
327
+ # Managed platform → Netlify
328
+ # Site ID: (from Netlify dashboard)
329
+ ```
330
+
331
+ ```bash
332
+ cp .env.example .env
333
+ # GDRIVE_* and NETLIFY_AUTH_TOKEN, NETLIFY_SITE_ID
334
+
335
+ deployhub doctor
336
+ git push origin main
337
+ ```
338
+
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
+ **Example: Angular → Azure Blob + SSH**
352
+
353
+ ```bash
354
+ deployhub init
355
+ # Frontend only → Angular
356
+ # Build: ng build, output dist
357
+ # Storage: Azure Blob
358
+ # Configure deployment? Yes → Self-hosted → ssh
359
+ # Host, user, deploy path: /var/www/my-app
360
+ ```
361
+
362
+ Add `AZURE_*`, `SSH_HOST`, `SSH_USER`, `SSH_KEY` to `.env` and GitHub Secrets. Review the generated `nginx.conf` and install it on the server.
363
+
364
+ #### Frontend framework defaults
365
+
366
+ | Framework | Build command | Output dir | Notes |
367
+ |-----------|---------------|------------|-------|
368
+ | React | `npm run build` | `dist` or `build` | Create React App uses `build` |
369
+ | Vue | `npm run build` | `dist` | Vite default |
370
+ | Angular | `ng build` | `dist` | |
371
+ | Next.js | `npm run build` | `.next` | Platform deploy recommended |
372
+ | Svelte | `npm run build` | `public` | |
373
+ | Astro | `astro build` | `dist` | |
374
+ | Vanilla JS | *(none)* | `.` | Copies static files as-is |
375
+
376
+ ---
377
+
378
+ ## Walkthrough: Backend only
379
+
380
+ Backends always deploy to a **self-hosted target** (SSH, Docker, EC2, Azure VM, GCP VM, or Kubernetes). There is no “managed platform” path for backend-only projects.
381
+
382
+ ### Init choices
383
+
384
+ | Prompt | Typical value |
385
+ |--------|----------------|
386
+ | What are you deploying? | **Backend only** |
387
+ | Language / framework | See table below |
388
+ | Start command | e.g. `npm start`, `uvicorn main:app …` |
389
+ | Port | e.g. 3000, 8000, 8080 |
390
+ | Storage | At least one provider |
391
+ | Configure deployment? | Yes for storage + deploy |
392
+ | Deployment type | ssh (most common), docker, ec2, kubernetes, … |
393
+ | App name | PM2 process name on server |
394
+ | Health check URL | e.g. `https://api.example.com/health` |
395
+
396
+ ### Example: Express API → S3 + SSH
397
+
398
+ ```bash
399
+ deployhub init
400
+ # Backend only → Node.js Express
401
+ # Start: npm start, port 3000
402
+ # Storage: AWS S3
403
+ # Configure deployment? Yes → ssh
404
+ # Host: 203.0.113.10, user: deploy, path: /var/www/my-api
405
+ # App name: my-api
406
+ # Health URL: https://api.example.com/health
407
+ ```
408
+
409
+ On the server, ensure **Node.js**, **PM2**, and your app dependencies are available. DeployHub SSHs in, extracts the artifact, runs install if needed, and restarts PM2.
410
+
411
+ ### Example: FastAPI → Dropbox + SSH
412
+
413
+ ```bash
414
+ deployhub init
415
+ # Backend only → Python FastAPI
416
+ # Start: uvicorn main:app --host 0.0.0.0 --port 8000
417
+ # Storage: Dropbox
418
+ # Deploy: ssh
419
+ ```
420
+
421
+ Server needs **Python 3.11+**, `pip`, and ideally **gunicorn/uvicorn** for production.
422
+
423
+ ### Backend framework defaults
424
+
425
+ | Framework | Language | Build | Start | Port | Test |
426
+ |-----------|----------|-------|-------|------|------|
427
+ | Express | Node | — | `npm start` | 3000 | `npm test` |
428
+ | NestJS | Node | `nest build` | `node dist/main` | 3000 | `npm test` |
429
+ | Fastify / Koa | Node | — | `npm start` | 3000 | `npm test` |
430
+ | FastAPI | Python | — | `uvicorn main:app --host 0.0.0.0 --port 8000` | 8000 | `pytest` |
431
+ | Django | Python | — | `gunicorn config.wsgi:application --bind 0.0.0.0:8000` | 8000 | `python manage.py test` |
432
+ | Flask | Python | — | `gunicorn app:app --bind 0.0.0.0:5000` | 5000 | `pytest` |
433
+ | Laravel | PHP | — | `php artisan serve` | 80 | `php artisan test` |
434
+ | Symfony | PHP | — | `php-fpm` | 80 | `php bin/phpunit` |
435
+ | Spring Boot | Java | `mvn package` | `java -jar target/*.jar` | 8080 | `mvn test` |
436
+ | Go | Go | `go build -o bin/app .` | `./bin/app` | 8080 | `go test ./...` |
437
+ | .NET | C# | `dotnet publish -c Release -o publish` | `dotnet App.dll` | 5000 | `dotnet test` |
438
+ | Rails | Ruby | `bundle exec rake assets:precompile` | `bundle exec puma` | 3000 | `bundle exec rspec` |
439
+
440
+ **Node.js backends** without a build step still get packaged; set `buildCommand` to empty in config if you truly have no compile step.
441
+
442
+ **Java / Go / .NET** always run a compile step before artifact creation.
443
+
444
+ ---
445
+
446
+ ## Walkthrough: Full stack (frontend + backend)
447
+
448
+ Choose **Both (monorepo / fullstack)** when frontend and backend live in the **same repository root** (typical monorepo layout).
449
+
450
+ ### Init flow
451
+
452
+ 1. **Frontend** — framework, build command, output directory.
453
+ 2. **Backend** — framework, start command, port.
454
+ 3. **Storage** — one or more providers.
455
+ 4. **Configure deployment?** → Yes.
456
+ 5. **Frontend deploy method** — Managed platform **or** self-hosted SSH.
457
+ 6. **Backend deploy** — always self-hosted (SSH, Docker, EC2, …).
458
+
459
+ DeployHub runs **both** builds, packs them into one artifact, uploads once, then deploys frontend and backend to their respective targets.
460
+
461
+ ### Example: React + Express monorepo
462
+
463
+ ```bash
464
+ deployhub init
465
+ # Both (monorepo / fullstack)
466
+ # Frontend: React, npm run build, dist
467
+ # Backend: Express, npm start, port 3000
468
+ # Storage: AWS S3 + Local
469
+ # Configure deployment? Yes
470
+ # Frontend: Managed platform → Vercel
471
+ # Backend: ssh → api.example.com, path /var/www/my-app/api, PM2 name my-app-api
472
+ ```
473
+
474
+ **Secrets:** AWS + `VERCEL_*` + `SSH_*`
475
+
476
+ **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
+
478
+ ### Example: React + Express, both on one VPS
479
+
480
+ ```bash
481
+ deployhub init
482
+ # Both → React + Express
483
+ # Frontend deploy: Self-hosted server
484
+ # Frontend path: /var/www/my-app/public
485
+ # Backend: ssh, path /var/www/my-app/api
486
+ ```
487
+
488
+ DeployHub generates `nginx.conf` to serve static files and proxy API requests.
489
+
490
+ ### Example: Next.js API routes only
491
+
492
+ Use **Frontend only** with **Next.js** and deploy to **Vercel** or **Firebase App Hosting** — no separate backend entry needed.
493
+
494
+ ---
495
+
496
+ ## Framework defaults by language
497
+
498
+ ### JavaScript / TypeScript (Node)
499
+
500
+ All JS frontends share the same install/build flow: `npm ci` → `npm run build` → artifact from output directory.
501
+
502
+ | Role | Frameworks |
503
+ |------|------------|
504
+ | Frontend | React, Vue, Angular, Next.js, Svelte, Astro, Vanilla |
505
+ | Backend | Express, NestJS, Fastify, Koa |
506
+
507
+ **Init is the same** for each; only default build/output/start commands differ (tables above).
508
+
509
+ ### Python
510
+
511
+ - **Detect:** `requirements.txt` containing `fastapi`, `django`, or `flask`.
512
+ - **Install:** `pip install -r requirements.txt`
513
+ - **Test:** `pytest` (if `pytest.ini` exists) or Django test runner.
514
+ - **Build:** Usually skipped (`buildCommand: null`); artifact includes source + dependencies list.
515
+ - **Deploy (SSH):** Server must have Python + pip; start command runs uvicorn/gunicorn.
516
+
517
+ ### PHP
518
+
519
+ - **Detect:** `composer.json` with `laravel/framework` or `symfony/framework-bundle`.
520
+ - **Install:** Composer (on CI and server).
521
+ - **Deploy:** SSH with PHP-FPM or `php artisan` for Laravel.
522
+
523
+ ### Java
524
+
525
+ - **Detect:** `pom.xml` with Spring Boot.
526
+ - **Install/Build:** `mvn package` produces JAR in `target/`.
527
+ - **Deploy:** SSH runs `java -jar target/*.jar` (or your custom start command).
528
+
529
+ ### Go
530
+
531
+ - **Detect:** `go.mod` present.
532
+ - **Build:** `go build -o bin/app .`
533
+ - **Artifact:** `bin/` binary + any config files.
534
+ - **Deploy:** SSH copies binary and restarts process.
535
+
536
+ ### .NET
537
+
538
+ - **Detect:** `.csproj` in project root.
539
+ - **Build:** `dotnet publish -c Release -o publish`
540
+ - **Deploy:** SSH runs `dotnet YourApp.dll` from publish folder.
541
+
542
+ ### Ruby
543
+
544
+ - **Detect:** `Gemfile` with `rails`.
545
+ - **Build:** `bundle exec rake assets:precompile` (for production assets).
546
+ - **Deploy:** SSH with `bundle exec puma` or your configured start command.
547
+
548
+ ---
549
+
550
+ ## GitHub Actions setup
551
+
552
+ After `init`, commit these files:
553
+
554
+ ```bash
555
+ git add deployhub.config.json .github/workflows/deployhub.yml .env.example
556
+ git commit -m "Add DeployHub CI"
557
+ ```
558
+
559
+ 1. Open **Settings → Secrets and variables → Actions** in your GitHub repo.
560
+ 2. Add every secret listed at the end of `deployhub init` (storage + deployment).
561
+ 3. Push to `main` or `master` — the workflow triggers on push.
562
+
563
+ The workflow installs the correct language runtime (Node, Python, Java, Go, .NET, Ruby) based on your `deployhub.config.json`, installs DeployHub, runs `deployhub build`, and uses your secrets.
564
+
565
+ To run manually: **Actions → DeployHub → Run workflow**.
566
+
567
+ ---
568
+
569
+ ## Choosing storage providers
570
+
571
+ | Provider | Good for | Setup command |
572
+ |----------|----------|---------------|
573
+ | **Local** | Dev/testing, no cloud account | No credentials |
574
+ | **AWS S3** | Production, CI-friendly | `deployhub storage add aws` |
575
+ | **Google Drive** | Small teams, manual downloads | `deployhub storage add gdrive` |
576
+ | **Azure Blob** | Azure ecosystem | `deployhub storage add azure` |
577
+ | **GCP Storage** | GCP ecosystem | `deployhub storage add gcp` |
578
+ | **Dropbox** | Simple off-site backup | `deployhub storage add dropbox` |
579
+
580
+ You can enable **multiple providers** — DeployHub uploads to all of them in parallel on every build.
581
+
582
+ ---
583
+
584
+ ## Deployment target cheat sheet
585
+
586
+ | Project type | Frontend deploy options | Backend deploy options |
587
+ |--------------|-------------------------|------------------------|
588
+ | Frontend only | Platform **or** SSH/Docker/EC2/K8s | — |
589
+ | Backend only | — | SSH/Docker/EC2/Azure VM/GCP VM/K8s |
590
+ | Full stack | Platform **or** SSH (static + nginx) | SSH/Docker/EC2/K8s (always) |
591
+
592
+ | Mode | Storage | Deploy | When to use |
593
+ |------|---------|--------|-------------|
594
+ | **Storage only** | ✓ | ✗ | Backups, audit trail, manual releases |
595
+ | **Storage + deploy** | ✓ | ✓ | Full CI/CD |
596
+
597
+ ---
598
+
599
+ ## Minimal `deployhub.config.json` examples
600
+
601
+ ### Storage only — React
602
+
603
+ ```json
604
+ {
605
+ "project": "my-react-app",
606
+ "projectType": "frontend",
607
+ "framework": "react",
608
+ "buildCommand": "npm run build",
609
+ "buildOutput": "dist",
610
+ "storage": ["local", "aws"],
611
+ "deploy": [],
612
+ "pipeline": { "test": true, "deploy": false, "verify": false }
613
+ }
614
+ ```
615
+
616
+ ### Storage + deploy — FastAPI on SSH
617
+
618
+ ```json
619
+ {
620
+ "project": "my-api",
621
+ "projectType": "backend",
622
+ "framework": "fastapi",
623
+ "language": "python",
624
+ "startCommand": "uvicorn main:app --host 0.0.0.0 --port 8000",
625
+ "port": 8000,
626
+ "storage": ["aws"],
627
+ "deploy": ["production"],
628
+ "environments": {
629
+ "production": {
630
+ "deploymentType": "server",
631
+ "type": "ssh",
632
+ "host": "203.0.113.10",
633
+ "user": "deploy",
634
+ "deployPath": "/var/www/my-api",
635
+ "appName": "my-api",
636
+ "framework": "fastapi"
637
+ }
638
+ },
639
+ "pipeline": { "deploy": true, "verify": true },
640
+ "healthCheck": { "url": "https://api.example.com/health", "timeout": 30 }
641
+ }
642
+ ```
643
+
644
+ Prefer `deployhub init` over hand-writing config — it sets adapters, workflow, and secrets list correctly.
645
+
646
+ ---
647
+
648
+ ## Troubleshooting
649
+
650
+ | Problem | Fix |
651
+ |---------|-----|
652
+ | `Deploy requires storage upload` | Add at least one storage provider in config |
653
+ | AWS / GDrive check fails in `doctor` | Run `deployhub storage add <provider>` and match GitHub Secrets |
654
+ | SSH deploy fails | Verify `SSH_KEY` is the **private** key; user can write to deploy path |
655
+ | Wrong output uploaded | Fix `buildOutput` in config (`dist` vs `build` vs `.next`) |
656
+ | 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
+ | Monorepo subfolders | Edit `buildCommand` paths in `deployhub.config.json` after init |
659
+
660
+ Run `deployhub doctor` after any config change.
661
+
662
+ ---
663
+
112
664
  ## Commands
113
665
 
114
666
  | Command | Description |
@@ -130,7 +682,9 @@ Or push to `main` — GitHub Actions runs `deployhub build` automatically.
130
682
 
131
683
  ## GitHub Secrets
132
684
 
133
- Add these secrets in your repository (Settings → Secrets and variables → Actions). Only add secrets for providers you selected during `init`:
685
+ Add these secrets in your repository (Settings → Secrets and variables → Actions). Only add secrets for providers you selected during `init`. At the end of `deployhub init`, DeployHub prints the exact list for your project.
686
+
687
+ ### Storage
134
688
 
135
689
  | Secret | Provider |
136
690
  |--------|----------|
@@ -148,9 +702,30 @@ Add these secrets in your repository (Settings → Secrets and variables → Act
148
702
  | `GCP_KEY_FILE` | GCP Storage |
149
703
  | `GCP_BUCKET` | GCP Storage |
150
704
  | `DROPBOX_ACCESS_TOKEN` | Dropbox |
151
- | `SSH_HOST` | SSH deployment |
152
- | `SSH_USER` | SSH deployment |
153
- | `SSH_KEY` | SSH deployment |
705
+ | `FTP_HOST`, `FTP_USER`, `FTP_PASSWORD` | FTP storage |
706
+
707
+ ### Server deployment (SSH, EC2, VMs)
708
+
709
+ | Secret | Used for |
710
+ |--------|----------|
711
+ | `SSH_HOST` | Target server hostname |
712
+ | `SSH_USER` | SSH username |
713
+ | `SSH_KEY` | Private SSH key (PEM) |
714
+ | `SSH_DEPLOY_PATH` | Remote directory (optional if set in config) |
715
+ | `SSH_APP_NAME` | PM2 process name for backends |
716
+ | `SSH_PORT` | App port on server (optional) |
717
+
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 |
154
729
 
155
730
  ## `deployhub doctor` Output
156
731
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akash-chowdhury-24/deployhub",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Zero-configuration deployment and artifact manager",
5
5
  "type": "module",
6
6
  "main": "./src/cli/index.js",
@@ -14,6 +14,7 @@ import { getProjectVersion } from '../utils/version.js';
14
14
  import {
15
15
  writeWorkflowFile,
16
16
  getRequiredSecrets,
17
+ generateEnvExampleContent,
17
18
  guessCliGithubRepo,
18
19
  addDeployhubToPackageJson,
19
20
  } from '../utils/github-actions.js';
@@ -288,25 +289,6 @@ function buildServerEnvEntry(
288
289
  return envEntry;
289
290
  }
290
291
 
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
292
  /**
311
293
  * @param {Record<string, unknown>} config
312
294
  * @param {Record<string, Record<string, unknown>>} environments
@@ -780,7 +762,12 @@ export function registerInitCommand(program) {
780
762
  await generateProjectScaffold(config, environments, cwd);
781
763
 
782
764
  const envExampleDest = path.join(cwd, '.env.example');
783
- const envExampleContent = await getEnvExampleContent();
765
+ const envExampleContent = generateEnvExampleContent(
766
+ config.storage,
767
+ deploy,
768
+ environments,
769
+ config
770
+ );
784
771
  await fs.writeFile(envExampleDest, envExampleContent);
785
772
 
786
773
  const secrets = getRequiredSecrets(config.storage, deploy, environments, config);
@@ -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,6 +37,28 @@ 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}`;
37
64
 
@@ -400,4 +427,113 @@ export function getRequiredSecrets(
400
427
  return Array.from(secrets);
401
428
  }
402
429
 
430
+ /**
431
+ * @param {string} title
432
+ * @param {string[]} keys
433
+ * @param {Record<string, string>} [defaults]
434
+ * @param {Set<string>} seenKeys
435
+ * @returns {string}
436
+ */
437
+ function formatEnvSection(title, keys, defaults, seenKeys) {
438
+ const newKeys = keys.filter((key) => !seenKeys.has(key));
439
+ if (newKeys.length === 0) return '';
440
+
441
+ for (const key of newKeys) {
442
+ seenKeys.add(key);
443
+ }
444
+
445
+ const lines = newKeys.map((key) => {
446
+ const value = defaults?.[key] ?? ENV_VAR_DEFAULTS[key] ?? '';
447
+ return value ? `${key}=${value}` : `${key}=`;
448
+ });
449
+
450
+ return `# ${title}\n${lines.join('\n')}\n`;
451
+ }
452
+
453
+ /**
454
+ * @param {string[]} storageProviders
455
+ * @param {string[]} deployEnvironments
456
+ * @param {Record<string, Record<string, unknown>>} environments
457
+ * @param {import('../core/config.js').DeployHubConfig} [config]
458
+ * @returns {string}
459
+ */
460
+ export function generateEnvExampleContent(
461
+ storageProviders,
462
+ deployEnvironments,
463
+ environments,
464
+ config = null
465
+ ) {
466
+ /** @type {Set<string>} */
467
+ const seenKeys = new Set();
468
+ /** @type {string[]} */
469
+ const sections = [];
470
+
471
+ const addSection = (title, keys, defaults = {}) => {
472
+ const section = formatEnvSection(title, keys, defaults, seenKeys);
473
+ if (section) sections.push(section);
474
+ };
475
+
476
+ for (const provider of storageProviders) {
477
+ const keys = PROVIDER_ENV_MAP[provider] || [];
478
+ if (keys.length > 0) {
479
+ addSection(PROVIDER_LABELS[provider] || provider, keys);
480
+ }
481
+ }
482
+
483
+ for (const envName of deployEnvironments) {
484
+ const env = environments[envName];
485
+ if (!env) continue;
486
+
487
+ if (env.deploymentType === 'platform' || env.frontendDeploymentType === 'platform') {
488
+ const platform = env.platform;
489
+ if (platform) {
490
+ const examples = getPlatformEnvExample(platform);
491
+ addSection(
492
+ PLATFORM_LABELS[platform] || platform,
493
+ Object.keys(examples),
494
+ examples
495
+ );
496
+ }
497
+ continue;
498
+ }
499
+
500
+ const keys = PROVIDER_ENV_MAP[env.type] || [];
501
+ if (keys.length > 0) {
502
+ addSection(PROVIDER_LABELS[env.type] || env.type, keys);
503
+ }
504
+
505
+ if (env.type === 'ssh' && config) {
506
+ const projectType = config.projectType || 'frontend';
507
+ if (projectType === 'backend' || projectType === 'both') {
508
+ addSection('SSH Deployment (backend)', BACKEND_SSH_ENV_VARS);
509
+ }
510
+ }
511
+ }
512
+
513
+ if (config?.notifications) {
514
+ if (config.notifications.slack) {
515
+ addSection('Notifications', ['SLACK_WEBHOOK_URL']);
516
+ }
517
+ if (config.notifications.webhook) {
518
+ addSection('Notifications', ['WEBHOOK_URL']);
519
+ }
520
+ if (config.notifications.email) {
521
+ addSection('Email (SMTP)', [
522
+ 'SMTP_HOST',
523
+ 'SMTP_PORT',
524
+ 'SMTP_USER',
525
+ 'SMTP_PASS',
526
+ 'NOTIFICATION_EMAIL',
527
+ 'NOTIFY_EMAIL_TO',
528
+ ]);
529
+ }
530
+ }
531
+
532
+ if (sections.length === 0) {
533
+ return '# Add your environment variables here\n';
534
+ }
535
+
536
+ return `${sections.join('\n')}\n`;
537
+ }
538
+
403
539
  export { PROVIDER_ENV_MAP };