Deploy CRA safely to local localhost:3000 and Windows production (192.168.50.55) with commit-hash pinning, NSSM restart, and verification; includes known SSH/quoting/PATH pitfalls and fixed command patterns.
Install
npx skillscat add renomo-lab/cra-local-w2016-server Install via the SkillsCat registry.
SKILL.md
CRA Deployment Skill (Windows)
Use this skill whenever the user asks to commit/push/deploy to:
- local dev host
http://localhost:3000 - remote production host
Administrator@192.168.50.55
Locked Environment (current)
- Local workspace repo:
C:\Users\Administrator\Desktop\Dev_App\CRA_Local_W2026\CRA_Local_W2016_Server - Production app path:
C:\CRA_Local_Main\app - Production service:
CRA_Local_App(NSSM-managed) - Production tooling:
- Git:
C:\CRA_Local_Main\tools\git\cmd\git.exe - Node/NPM:
C:\CRA_Local_Main\tools\node\npm.cmd
- Git:
Non-negotiable Rules
- Always deploy the exact local commit hash to production (no drift).
- Always rebuild before restart so
dist/build-info.jsonmatches deployed hash. - Always verify both:
- repo hash
- runtime HTTP health (
200)
- Local test runtime authority is
:3000. - For backend file/attachment changes, always run post-deploy attachment smoke tests (ASCII + non-ASCII filenames).
Local Deployment Workflow (:3000)
- Verify clean scope:
git status --short
- Commit and push:
git add ...git commit -m "..."git push origin main
- Build locally:
npm.cmd run build
- Restart local API runtime:
- stop listener on port 3000
- start
node server/index.jsfrom workspace root
- Verify:
Invoke-WebRequest http://localhost:3000/returns200dist/build-info.jsonhash equalsgit rev-parse HEAD
Production Deployment Workflow (SSH)
- Record target hash from local:
git rev-parse HEAD
- Fetch and pin production repo to target hash:
git.exe -C C:/CRA_Local_Main/app fetch --prune origingit.exe -C C:/CRA_Local_Main/app checkout maingit.exe -C C:/CRA_Local_Main/app reset --hard <target-hash>
- Install dependencies (use fallback ladder below if needed):
- Preferred:
C:\CRA_Local_Main\tools\node\npm.cmd --prefix C:/CRA_Local_Main/app ci --include=dev
- If npm lifecycle scripts fail with
'node' is not recognized:C:\CRA_Local_Main\tools\node\node.exe C:\CRA_Local_Main\tools\node\node_modules\npm\bin\npm-cli.js --prefix C:/CRA_Local_Main/app ci --include=dev --ignore-scriptsC:\CRA_Local_Main\tools\node\node.exe C:\CRA_Local_Main\app\node_modules\esbuild\install.js
- Preferred:
- Run migrations (known-good, independent from npm PATH issues):
cd /d C:\CRA_Local_Main\app && C:\CRA_Local_Main\tools\node\node.exe server\migrate.js
- Build (known-good, independent from npm PATH issues):
cd /d C:\CRA_Local_Main\app && C:\CRA_Local_Main\tools\node\node.exe node_modules\vite\bin\vite.js build && C:\CRA_Local_Main\tools\node\node.exe scripts\write-build-info.mjs
- Restart service:
C:\CRA_Local_Main\tools\nssm.exe restart CRA_Local_App
- Verify production:
git.exe -C C:/CRA_Local_Main/app rev-parse --short HEADtype C:\CRA_Local_Main\app\dist\build-info.jsonsc query CRA_Local_AppisRUNNINGInvoke-WebRequest http://localhost:3000/returns200(executed on remote host)
Post-Deploy Attachment Smoke Tests (Mandatory For File Handling Changes)
Run these immediately after production restart when changes touch attachment serving, preview, or download headers.
- Login and keep a session cookie:
POST /api/auth/login
- Pick one known attachment with ASCII filename and one with non-ASCII filename.
- Verify both endpoints return
200:GET /api/attachments/:id
- Confirm response headers are valid and no 500 occurs.
- Check server error log for header exceptions:
TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["Content-Disposition"]
- UI check:
- open attachment preview modal for both files and ensure image/PDF renders.
Fallback Ladder (Mandatory When Remote Shell Is Fragile)
Use this order and stop at first successful path:
npm.cmd --prefix ... ci --include=dev- If lifecycle scripts cannot find
node:- run npm via explicit
node.exe+npm-cli.jswith--ignore-scripts - then execute required postinstall manually (
esbuild/install.js)
- run npm via explicit
- Never skip migrate/build after fallback install.
- Always verify
dist/build-info.jsonhash before restart confirmation.
SSH Command Patterns That Avoid Past Failures
Use these patterns to avoid Windows quoting/parser issues:
- Prefer direct executable calls over PowerShell scriptblocks when possible:
- Good:
ssh host "C:\...\git.exe -C C:/... rev-parse --short HEAD"
- Good:
- Avoid
&&in PowerShell contexts. - In
cmd /cremote strings, escape command separators with^&when required. - Use forward slashes in
-Cgit paths (C:/CRA_Local_Main/app) to avoid backslash parsing surprises. - Do not assume
gitornodeare in PATH on remote host.- Use absolute tool paths.
- If
npm cifails with'node' is not recognizedinside install scripts, do not rely only on PATH injection.- Switch to explicit
node.exe npm-cli.js --ignore-scripts+ manualesbuildinstall.
- Switch to explicit
- Prefer direct remote
cmdexecution for build/migrate:ssh host "cd /d C:\CRA_Local_Main\app && C:\...\node.exe server\migrate.js"ssh host "cd /d C:\CRA_Local_Main\app && C:\...\node.exe node_modules\vite\bin\vite.js build && C:\...\node.exe scripts\write-build-info.mjs"
Quick Triage Map
- Error:
git is not recognized- Use
C:\CRA_Local_Main\tools\git\cmd\git.exe
- Use
- Error:
node is not recognizedduringnpm ci- Use fallback ladder step 2 (
node.exe npm-cli.js --ignore-scripts+ manualesbuildinstall)
- Use fallback ladder step 2 (
- Error:
Missing required env vars: PGHOST...during migration- Run migration from app folder with
cd /d C:\CRA_Local_Main\app && ...node.exe server\migrate.js(loads local.env)
- Run migration from app folder with
- Error: PowerShell parse error for
&or&&- Switch to separate commands, or use
cmd /cwith escaped^&
- Switch to separate commands, or use
- Error:
TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["Content-Disposition"]- Root cause is filename/header encoding (often non-ASCII or very long names).
- Use RFC5987-safe header construction (
filename*) with ASCII fallback. - Add safe fallback behavior so response still serves bytes even if filename encoding fails.
- Service restarted but UI unchanged
- Re-check
dist/build-info.jsonhash andrev-parse HEAD
- Re-check
Production Log Commands (Known-Good)
- Tail server stderr (PowerShell over SSH):
ssh Administrator@192.168.50.55 'powershell -NoProfile -Command "Get-Content -Path ''C:\CRA_Local_Main\logs\app.stderr.log'' -Tail 200"'
- Tail server stdout:
ssh Administrator@192.168.50.55 'powershell -NoProfile -Command "Get-Content -Path ''C:\CRA_Local_Main\logs\app.stdout.log'' -Tail 200"'
Final Delivery Checklist (must report)
- Committed hash
- Pushed branch and remote
- Local
:3000health status - Production pinned hash
- Production service status
- Production
:3000health status - (When relevant) attachment smoke-test result for ASCII + non-ASCII filenames