From shipwright-deploy
Deploys to Jelastic (Infomaniak) with smoke test verification, rollback support, and optional Supabase migrations. Useful for automated deployment and rollback.
How this skill is triggered — by the user, by Claude, or both
Slash command
/shipwright-deploy:deployThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Deploys to Jelastic (Infomaniak) with smoke tests and rollback.
Deploys to Jelastic (Infomaniak) with smoke tests and rollback.
Governing rules: Read and follow shared/constitution.md (ALWAYS / ASK FIRST / NEVER boundaries).
================================================================================
SHIPWRIGHT-DEPLOY: Deployment
================================================================================
Deploys to Jelastic Cloud (Infomaniak, Switzerland).
Usage: /shipwright-deploy (DEV, automatic)
or: /shipwright-deploy --prod (PROD, requires confirmation)
or: /shipwright-deploy --rollback (restore last PROD snapshot)
or: Invoked by /shipwright-run (orchestrator)
Flow:
1. Validate credentials
2. Run Supabase migrations (if applicable)
3. Deploy via Jelastic API
4. Smoke test
5. Rollback on failure
Environments:
DEV: dev-{project}.jpc.infomaniak.com
PROD: {project}.jpc.infomaniak.com
================================================================================
uv run "{plugin_root}/scripts/checks/validate-deploy.py"
Checks for:
JELASTIC_TOKEN environment variableSUPABASE_ACCESS_TOKEN (for migrations)Resolve it with {shared_root}/scripts/tools/get_phase_context.py --phase-task-id "{phaseTaskId}" --phase deploy (omit --phase-task-id entirely if the orchestrator did not hand you one — that selects standalone) and store the returned mode as invocation_mode. The dispatch token is the authority — never re-derive the mode from run-config state (authority: shared/scripts/lib/phase_invocation_mode.py).
pipeline — dispatched: enforce gates and deploy. Do NOT call orchestrator.py update-step (single-session-apply owns completion; it is inert in a driven run anyway).standalone — no token: skip pipeline-state updates; skip the test gate but warn "No pipeline test results found. Deploying without test verification."; still produce all artifacts. If requires_out_of_sequence_warning is true, warn + ASK before continuing (gate deploy.out-of-sequence-continue).error (exit 2) — dispatched with an unresolvable token: STOP, return ok: false; never continue as standalone.Note: a PROD deploy is ASK-FIRST regardless of invocation_mode (constitution).
Under single-session pipeline mode (run_config.mode == "single_session"), interactive gates follow a per-gate policy — resolve via ${SHIPWRIGHT_PLUGIN_ROOT}/../../shared/scripts/tools/resolve_gate_policy.py --phase deploy --list. PROD / destructive-migration / rollback gates stay hard-stop (explicit human confirmation, always, regardless of autonomy). Full contract: shared/prompts/single-session-gate-discipline.md.
Check that required deploy environment variables from the stack profile are available.
uv run "{shared_root}/scripts/validate_env.py" \
--project-root "{project_root}" \
--phase deploy
Where {shared_root} = {plugin_root}/../../shared (relative to plugin root).
Parse the JSON output:
skipped == true: No profile or no vars defined — continue.
success == true: All required deploy vars present — continue.
success == false: Missing required vars — use AskUserQuestion:
Missing environment variables for deployment
The following required variables are not set:
VAR_NAME— descriptionPlease set the missing environment variables, then confirm to continue.
Options: "I've set the variables — continue" / "Skip validation and proceed anyway"
If user updates: re-run validation to confirm. If user skips: proceed with a warning.
optional_missing: Log a warning but do not block.
Before deploying, verify all tests passed:
shipwright_test_results.jsonunit.status == "passed" AND (e2e.status == "passed" OR e2e.status == "skipped")================================================================================
SHIPWRIGHT-DEPLOY: Test Gate Failed
================================================================================
Cannot deploy — tests have not passed.
Unit: {status} | E2E: {status}
Run /shipwright-test first, or confirm to proceed at your own risk.
================================================================================
Ask user for confirmation before proceeding. Do NOT deploy silently with failing tests.
| Flag | Target | Behavior |
|---|---|---|
| (none) | DEV | Automatic, no confirmation |
--prod | PROD | Requires explicit user confirmation |
--rollback | PROD | Restore last clone, requires confirmation |
If the orchestrator handed you a phaseTaskId — i.e. /shipwright-run dispatched
you as a phase-runner subagent — you are part of an active pipeline. Run this as your
very first action:
uv run "${SHIPWRIGHT_PLUGIN_ROOT}/../../shared/scripts/tools/get_phase_context.py" \
--phase-task-id <phaseTaskId-from-context>
The tool prints structured JSON with runId, phase, splitId, prerequisites,
runConditions, and a skill_artifacts_to_read list. Read those artifacts
before proceeding so this phase session has full context for what came before.
Deploy is the pipeline-terminal phase — when this session's Stop hook fires
complete-phase-task, the run will flip to status="complete".
If NO phaseTaskId was handed to you, this is a standalone invocation —
continue with Step 1 below as normal.
One resolver, one verdict. This is the same tool your "Detect Invocation Mode" step
already ran, so reuse that payload rather than re-deriving anything: its mode IS your
invocation_mode. Pass --phase <your phase> so a token belonging to another phase is
rejected, and if mode is "error" (exit 2) STOP — a dispatched phase must never
fall back to standalone.
Only runs if migration files exist in the profile's migrations.dir.
Read migrations config from the stack profile.
supabase/config.toml exists — if not: run npx supabase init.supabase/ directory exists) — if not: run npx supabase link --project-ref <ref> (requires SUPABASE_ACCESS_TOKEN)SUPABASE_ACCESS_TOKEN is set — if not: prompt user to create one at https://supabase.com/dashboard/account/tokens and add to .env.localIf any prerequisite fails: stop and inform user with specific remediation steps.
DEV migrations are applied during Build/Iterate. Verify all are current:
{migrations.list_cmd}
If pending migrations exist:
supports_idempotent_apply is true: warn user, offer to apply them now{migrations.dry_run_cmd}
Present dry-run output to user (note: dry-run output format varies by stack — present raw output for human review). Require explicit confirmation before:
{migrations.apply_cmd}
Destructive changes (detected by shipwright-build hooks): always warn and require confirmation regardless of target.
After apply_cmd succeeds, run the migration verifier against the migrations that were just applied. The verifier parses -- VERIFY: comments from each migration and runs them via psql. A failed verification triggers the same rollback path as a smoke-test failure (see Step 5 → "Smoke Test Failed → Rollback").
uv run "{plugin_root}/scripts/lib/migration_verifier.py" \
--migration {applied_migration_path_1} \
[--migration {applied_migration_path_N}] \
--db-url "{prod_db_url_or_pooled_url}" \
--output .shipwright/deploy/migration-verify.json
Read the JSON output. Branch on all_passed:
true — proceed to "Post-Migration Manual Steps".false — present the failing report (per-file, per-VERIFY-statement) to the user via AskUserQuestion. Two options:
.shipwright/agent_docs/decision_log.md capturing: the migration file(s), the failing VERIFY statement(s), the user's stated reason for override, and the timestamp. This is mandatory — a one-click override on a failed PROD VERIFY without an audit trail is exactly the kind of "did anyone notice that?" event compliance reports must surface afterward. Use write_decision_log.py (see Step 9) with title "Override: failed migration verification".Backwards-compat: migrations without any -- VERIFY: comment are reported as skipped=True, all_passed=True and do not cause a rollback. New migrations should always include at least one -- VERIFY: block — see shared/templates/rules/migrations.md.template for the convention and examples.
Check migrations.post_apply_manual_steps from the stack profile. For each entry where trigger_tag matches a migration just applied, inform user via AskUserQuestion with the action and note. Wait for confirmation before proceeding.
Goal: Create a rollback point before deploying to PROD.
uv run "{plugin_root}/scripts/lib/jelastic_client.py" clone-env \
--env-name "{prod_env}" \
--clone-name "{prod_env}-backup"
This creates a full clone of the PROD environment. If deployment fails, we can restore from this clone.
User confirmation:
AskUserQuestion:
question: "Deploy to PRODUCTION ({prod_env}.jpc.infomaniak.com)?"
context: "Backup clone will be created first."
options:
- "Deploy to PROD"
- "Cancel"
uv run "{plugin_root}/scripts/lib/jelastic_client.py" deploy \
--env-name "{env_name}" \
--branch "{branch}"
This calls the Jelastic VCS Update API to pull the latest code from git.
If environment doesn't exist yet: create it first via create-env.
uv run "{shared_root}/scripts/smoke_test.py" \
--url "https://{env_name}.jpc.infomaniak.com" \
--timeout 30 \
--health-path "/api/health"
Wait up to 60 seconds for the deployment to become ready (poll every 5s).
================================================================================
SHIPWRIGHT-DEPLOY: SUCCESS
================================================================================
Target: {DEV | PROD}
URL: https://{env_name}.jpc.infomaniak.com
Status: {status_code} ({response_time}ms)
Migrations: {applied | skipped | N/A}
================================================================================
Record deploy event (captures deployed URL for downstream consumers):
uv run "{shared_root}/scripts/tools/record_event.py" \
--project-root "$(pwd)" \
--type phase_completed \
--phase deploy \
--detail "https://{env_name}.jpc.infomaniak.com"
Phase complete — update pipeline state:
Deploy runs the Minimum Phase Completion Canon at C1/C2/C3 only. C4 is skipped
(decided in plan) and C5 is skipped — deployment is operational history
(events.jsonl + phase_history), not a product change; a per-deploy CHANGELOG
[Unreleased] bullet would duplicate the changelog plugin's release block.
: "${SHIPWRIGHT_RUN_ID:=deploy-$(date +%Y%m%d-%H%M%S)-{env_name}}"
export SHIPWRIGHT_RUN_ID
# C1 — already emitted as the phase_completed event above.
# C2 — delivery dashboard
uv run "{shared_root}/scripts/tools/update_build_dashboard.py" \
--project-root "$(pwd)" --phase deploy --detail "Deployed to {url}" \
--session-id "{SHIPWRIGHT_SESSION_ID}"
# C3 (NEW 12.4) — canon-marker handoff
uv run "{shared_root}/scripts/tools/generate_session_handoff.py" \
--project-root "$(pwd)" --canon-marker --phase deploy \
--reason "deploy to {env_name}: {status}"
# C4 — SKIPPED by policy (execution, not decision).
# C5 — SKIPPED by policy (operational history, not product change;
# release narrative belongs to the changelog plugin).
# phase_history (NEW 12.4)
uv run "{shared_root}/scripts/tools/append_phase_history.py" \
--project-root "$(pwd)" --phase deploy --run-id "$SHIPWRIGHT_RUN_ID" \
--entry-json '{"target":"{env_name}","url":"{url}","version":"v{version}","outcome":"success"}'
# Mark deploy phase complete (triggers compliance update automatically).
# _validate_deploy() (new in 12.4) runs the test-gate pre-condition
# plus the deploy_checks verifier (C1/C2/C3 + phase_history).
uv run "{plugin_root}/../../plugins/shipwright-run/scripts/lib/orchestrator.py" \
update-step --project-root "$(pwd)" --step deploy --status complete
Reflection — Capture Deploy Learnings:
If deployment had issues or required adjustments:
If learnings exist:
.shipwright/agent_docs/conventions.md under ## Learnings
Format: - ({YYYY-MM-DD}) deploy — {summary}DEV: Git-based rollback
uv run "{plugin_root}/scripts/lib/rollback.py" \
--env-name "{env_name}" \
--strategy git \
--target-ref "{last_known_good_tag}"
PROD: Restore from clone
uv run "{plugin_root}/scripts/lib/rollback.py" \
--env-name "{env_name}" \
--strategy clone \
--clone-name "{prod_env}-backup"
Log rollback in .shipwright/agent_docs/decision_log.md.
================================================================================
SHIPWRIGHT-DEPLOY: FAILED → ROLLED BACK
================================================================================
Target: {DEV | PROD}
Error: {smoke test error}
Rollback: {git revert to {tag} | restored from clone}
Action: Fix the issue and re-deploy
================================================================================
--rollback)When invoked with --rollback:
Shipwright treats rollback as a property of every deploy target, not a feature of one. Three patterns apply universally; their mechanics are target-specific. The Jelastic flow above is one reference implementation — the same discipline applies to any target Shipwright would call shipped.
Every deploy has a documented path back to the previous working state. The
mechanics are target-specific — git-tag revert (DEV-typical), environment-
clone restore (Jelastic PROD), atomic deploy-ID promote (Vercel), image-tag
rollback (Docker Compose, Kubernetes) — but the property is universal: a
deploy is not complete until its rollback is operable. Application-tier
and data-tier rollback are separate concerns; the schema's
rollback.data_rollback_strategy field captures how each target handles
DB-schema-vs-app-code drift.
Every deploy and every rollback leaves an auditable record before the next
change touches the same target. Pipeline-side: phase_completed events in
shipwright_events.jsonl, an entry in phase_history, and — for rollbacks
— an ADR in decision_log.md with the failure cause. Target-side: deploy
IDs, clone names, image-tag history, or whatever the platform exposes via
vercel inspect / getenvinfo / registry API. The why-it-happened
outlives the on-call shift.
Both rollback paths — automatic (smoke-test-fail) and manual (operator-initiated) — must be runnable from the documentation alone. Manual rollback requires explicit confirmation; automatic rollback logs its trigger and announces itself in the deploy output. A silent rollback is the failure mode worse than the failure that caused it.
A target proves it satisfies the discipline by filling in a Deploy Profile
at shared/profiles/deploy/<target_id>.json, validated against
shared/profiles/deploy-profile.schema.json. Three reference profiles
ship today: Jelastic (full implementation, confidence: verified),
Vercel (declarative stub, confidence: documented), and
Compose-VPS (declarative stub, confidence: documented). The two
stubs exist to keep the schema honest — they describe how targets with
fundamentally different rollback mechanics (atomic vs. snapshot vs. clone)
fill the same shape. To add a real implementation: write the client, fill
the profile, run validate_deploy_profile.py --strict. See
references/rollback-discipline.md
for the pattern-by-pattern mapping.
npx claudepluginhub svenroth-ai/shipwright --plugin shipwright-deployGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.