Imported from TryGhost/Deploy (
AGENTS.md). Install upstream withnpx skills add TryGhost/Deploy. Copyright stays with the author.
AGENTS.md
@tryghost/deploy is a Shipit plugin that
deploys internal Ghost projects to servers over SSH. TypeScript compiled to
CommonJS, pnpm, Vitest. See README.md for what it does and how consumers use it.
Commands
pnpm install # uses the pinned pnpm (packageManager field) via Corepack
pnpm build # tsc → dist/ (the published, compiled output)
pnpm typecheck # tsc --noEmit
pnpm lint # oxlint + oxfmt --check
pnpm lint:fix # oxlint --fix + oxfmt
pnpm test # lint + typecheck + unit
pnpm test:unit # Vitest unit suite, with coverage (gated >=80%)
pnpm test:e2e # full deploy against throwaway Docker containers
Boundaries — do not break these
lib/deploy.tsis a runtime contract, not just this repo's tooling. It shells the dependency install command (yarn/npm/pnpm install) on the remote servers it deploys to, chosen fromshipit.config. Jenkins-deployed services depend on this exact behaviour. Don't "modernise" or change it; the unit tests (yarn install --production,npm install) and the e2einstalls lodashtests exist to pin it. This is separate from how this repo builds itself (which is pnpm).- Source is TypeScript in
index.ts/lib/*.ts; the published package is the compileddist/(main/typespoint there,filesships onlydist).dist/is gitignored and built bypnpm build— in CI, before publish (prepublishOnly), and inside the e2e runner. Don't edit or commitdist; change the.tssource and rebuild. - Node 20.20.0 is a deliberate required floor —
engines: ">=20.20.0",.nvmrc, CI matrix 20.20.0/22/24. The Jenkins jobs run this plugin under a per-project Node version (jenkins-jobs/jobs/apps/projects.yaml): Daisy.js, Zuul, Stats-Service and the dispatchers on 20.20.0, Scheduler/UpdateCheck on 24 — but Ghost-Rollout on 18.20.2 and elastic-alerting/AppMonitor on 14.18.1 (legacy Ubuntu 18.04 / glibc 2.27 infra that can't run Node 18+). We require 20.20.0 anyway: those sub-20 jobs will fail to install a new version until the infra team upgrades them (in progress) — a known, accepted tradeoff, not a bug. Do not lowerenginesto "support" Node 14/18. pnpmis pinned to the 10.x line (packageManager) on purpose — pnpm 11 requires Node >= 22.13 and would break the 20.20.0 CI leg. Don't bump it to 11 until the floor moves off Node 20.- GitHub Actions must be pinned to full commit SHAs (org policy rejects
floating tags — every job fails in seconds otherwise). Keep the
# vX.Y.Zcomment next to each SHA.
Gotchas
- e2e needs Docker running. It builds two containers: a
runner(installs this repo's deps with pnpm, runspnpm build, then Vitest againstdist/) and atarget(the deploy destination — it has npm, yarn, and pnpm available so all three runtime install paths can be exercised). - The required status check is
Required checks pass— an aggregator job thatneeds: [lint, test, e2e, build]. Add new required jobs to itsneeds(and to the result-count guard in the job), not to branch protection. - pnpm consumers must not share
node_modules. pnpm can't install into a symlinkednode_modules(ENOTDIR); see the README's pnpm/sharedLinksnote.
Publishing
Releasing is one command: pnpm ship <patch|minor|major> (or an exact
version, or no argument for the interactive picker). ship is
@tryghost/pro-ship; the
preship hook runs pnpm test first and aborts before any git mutation if
tests fail (no stranded version commit/tag). On success pro-ship bumps
package.json, commits and tags vX.Y.Z, and git push --follow-tagses to
main — allowed by the Ghost Foundation ruleset bypass (always). ship does
not publish locally (no --publish flag) — see below.
Publishing runs in CI, not on your machine. .github/workflows/publish.yml
triggers on any push to main that changes package.json and publishes to npm
via OIDC trusted publishing — no npm token is stored. The pushed release
commit is what fires it; the workflow skips (no-op) if that version is already
on the registry, so non-release edits to package.json are safe. It runs
pnpm publish, which builds dist/ via prepublishOnly and ships only dist,
with provenance attestation (public repo). A manual workflow_dispatch run is
available and defaults to --dry-run.