ci/release/README.md
@d2lang/d2 is the canonical JavaScript package. @terrastruct/d2 is published from the
same built files as a compatibility package during the namespace transition. The
npm-stage.yml workflow stages both package names for review with npm trusted publishing;
each staged package has its own stage ID and must be reviewed and approved separately. If
one stage succeeds and the other fails, use GitHub's Re-run failed jobs action on the
same workflow run. That preserves the original commit and reuses the same immutable
package artifact; do not use Re-run all jobs or start a fresh workflow dispatch to
recover only one identity.
Before staging an official version, bump d2js/js/package.json and package-lock.json to
that exact version in a signed commit. Nightly stages derive their prerelease version from
the checked-in version automatically. @d2lang/d2 must already exist and both packages
must separately trust the d2lang/d2 npm-stage.yml workflow for stage-only publishing.
Each trusted publisher is restricted to the npm-release GitHub environment, whose
deployment branch policy accepts only protected branches. The workflow itself also
refuses to pack from any ref other than refs/heads/master.
The canonical package was bootstrapped once from the existing @terrastruct/[email protected]
built artifacts because npm does not allow a brand-new package to use staged publishing.
Direct publishing is retained only for local bootstrap or recovery. It requires a
short-lived NPM_TOKEN that can publish every selected package and is not stored in GitHub.
The script pre-packs and preflights every selected package before publishing; a retry skips
an existing version only when its registry tarball integrity exactly matches the local
artifact.
After the workflow finishes, confirm both matrix stage jobs are green and inspect the two
pending records with npm stage list and npm stage view <stage-id>. Approve each only
after their package versions and artifacts from npm stage download <stage-id> match.
Use npm stage approve <stage-id> for each identity. If the release is abandoned, reject
every pending half with
npm stage reject <stage-id> before starting another run. A pending stage reserves its
package/version, so list pending stages before retrying any failed job.
The template for the install script in the root of the d2 repository.
Generates the install.sh script in the root of the repository by prepending the libraries it depends on from ../sub/lib.
The first release-script run must leave the GitHub release as a draft. Pushing its tag starts two workflows:
Release archives builds all six archives twice from the exact tag commit with the
pinned Go toolchain and compares their SHA-256 digests. It strips and verifies each
binary, normalizes every archive, enforces size budgets, runs each archive on a native
GitHub-hosted runner, and generates signed build-provenance and SBOM attestations. The
release script waits for that exact successful workflow run, downloads its immutable
archive artifact, verifies SHA256SUMS, and uploads those same bytes to the draft.Windows MSI waits for the six uploaded archives and pins the exact Windows amd64
archive, builds the installer on GitHub's windows-2022 runner, verifies its metadata
and installed contents, and uploads d2-<version>-windows-amd64.msi to that same draft.Do not publish the release until the workflow is green and the MSI is present. Review and
merge the release PR, then publish the draft on GitHub. The D2 wrapper rejects
release.sh --publish, and it refuses any release-builder rerun after the MSI is attached.
This prevents the shared helper from publishing while the asynchronous build is running or
moving the tag after an MSI was built from it. release.sh --rebuild is intentionally
unsupported for CI-built archives. Re-run only failed jobs in the existing workflow run;
a full rerun fails closed instead of replacing its immutable Actions artifacts.
It also rejects a legacy local MSI in the version's build directory so the shared asset
uploader cannot attach an unverified installer.
The MSI workflow can also be dispatched manually with release upload disabled. v0.7.1 is
the continuity fixture; because that release predates notices in the archives, only this
non-uploading fixture build packages the current notices file. Every production MSI
requires THIRD_PARTY_NOTICES.txt from its pinned release archive. Re-run only a failed
job: the workflow's Actions artifact is immutable within a run, and a full rerun fails
closed instead of replacing it.
Every release produced by this workflow includes SHA256SUMS. To verify archive
provenance and its checksum:
gh attestation verify d2-v0.0.0-linux-amd64.tar.gz --repo d2lang/d2
sha256sum --check --ignore-missing SHA256SUMS
The MSI remains unsigned. Authenticode signing is tracked separately in issue #1078.
The installer uses WiX 7. Before the workflow can install or run WiX 7, a repository owner
must review the WiX Open Source Maintenance Fee and EULA terms,
confirm that any required sponsorship is in place, and set the WIX7_EULA_ACCEPTED
repository variable to exactly true. That explicit owner-controlled gate enables the
workflow to pass -acceptEula wix7. Pull requests still validate the pinned release inputs
when the variable is absent, but skip the EULA-dependent MSI steps. Tag-triggered and
manually dispatched builds fail closed when the variable is absent.
Use --host-only to build only the release for the host's $OS-$ARCH pair. Local
development invocations still build locally. The production release path instead downloads
the exact archives produced by the tag-triggered Release archives workflow.
./docker/build.sh is retained as a load-only local development helper. It rejects
--push, --latest, and inherited RELEASE publishing. The release script no longer
calls it or publishes Docker tags from a legacy AWS SSH builder.
The manually dispatched Publish Docker release GitHub workflow is the production path
for Docker Hub. It replaces the Docker portion of the legacy AWS release path; the Windows
MSI is built separately by the Windows MSI workflow described above.
Run it from the protected master branch after the GitHub release is published. Enter the
exact v-prefixed release version and leave publish_latest off unless this stable release
should become the default Docker image. The workflow rejects publish_latest for a GitHub
prerelease or a semver prerelease.
The docker-release GitHub environment must provide a DOCKERHUB_USERNAME variable set
to d2lang and a DOCKERHUB_TOKEN secret containing a personal access token for that
Docker ID. The Docker ID must have write access to both the canonical d2lang/d2
repository and the maintained terrastruct/d2 compatibility mirror. The environment's
deployment branch policy must
allow only protected branches. Docker Hub tag immutability must remain enabled on both
repositories for version tags with
^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$; latest remains outside
that rule and mutable. This repository-side rule is the final overwrite guard.
Before publishing a production tag, the workflow:
master commit, and uses that release commit's Dockerfile;Only then does it create the requested version tag in the canonical repository and its
compatibility mirror. Existing version tags are immutable, so the workflow refuses to
overwrite one. If publish_latest was explicitly selected, it updates latest in both
repositories from the immutable version-manifest digest only after the version manifests
are published and verified. If verification or latest promotion fails after a version
tag was created, use GitHub's Re-run failed jobs action: the same run accepts an
existing version tag only when its descriptors exactly match that run's verified candidate,
and the separate latest job can retry without touching the version tags. A new dispatch
still refuses any existing version tag during preflight.
The manually dispatched Docker continuity test GitHub workflow proves that an existing,
published D2 release can be rebuilt for Docker Hub without the legacy AWS builders. It
downloads the release's exact Linux archives, builds on native GitHub-hosted amd64 and arm64
runners, verifies the images, and publishes only
d2lang/d2:continuity-test-<workflow-run-id>-<attempt> and the matching
terrastruct/d2 compatibility tag. It never updates a release version tag or latest.
Dispatch the workflow from the protected master branch. The version input must name a
published, non-draft GitHub release with both Linux archives; v0.7.1 is the default
continuity fixture. The workflow removes both test tags after verification. If Docker Hub
rejects either cleanup request, the cleanup job fails instead of silently leaving a test
tag behind. Because collaborators on personal Docker Hub repositories cannot delete tags,
the docker-release environment must also provide two owner-scoped secrets with Read,
Write, Delete permission: DOCKERHUB_D2LANG_DELETE_TOKEN from the d2lang Docker ID and
DOCKERHUB_TERRASTRUCT_DELETE_TOKEN from the terrastruct Docker ID. Cleanup is restricted
to the exact d2lang/d2 and terrastruct/d2 test tags for the current workflow run and
verifies that each tag is absent after deletion.
The v0.7.1 fixture embeds playwright-go v0.4702.0, whose original driver CDN no longer
serves the required ZIP files. For that fixture only, the workflow reconstructs the same
Playwright 1.47.2 driver layout from a checksum-pinned official playwright-core npm
tarball and the image's Node runtime. Browser payloads use Playwright's current direct CDN.
The published D2 archive remains unchanged. Other release versions use their tagged
Dockerfile without this compatibility step.
This remains a non-production regression test. The separate Publish Docker release
workflow is the production publisher.
Called by build.sh to create a release archive.
Do not invoke directly. If you want to produce a build for a single platform run build.sh as so:
# To only build the linux-amd64 release.
./build.sh --run=linux-amd64
# To only build the linux-amd64 release locally.
./build.sh --local --run=linux-amd64