D2 项目发布工程化指南:从 npm 双包发布、Release 工作流到 Docker Hub 生产发布的完整实践 D2 项目发布工程化指南从 npm 双包发布、Release 工作流到 Docker Hub 生产发布的完整实践【免费下载链接】d2D2 is a modern diagram scripting language that turns text to diagrams.项目地址: https://gitcode.com/GitHub_Trending/d2/d2导读本文以 D2 仓库ci/release/README.md为骨架系统讲解这个文本即图表项目D2 is a modern diagram scripting language that turns text to diagrams的完整发布体系涵盖d2lang/d2与terrastruct/d2两个 npm 包的 staged 发布流程、release.sh驱动的语义化版本发布准备、GitHub Actions 主导的Release archives依赖图归档构建、SHA-256 校验、SBOM 生成、MSI 安装包、Draft 上传、build.sh/_build.sh的本地归档构建以及Publish Docker release工作流到 Docker Hub 的生产发布。读完本文你将掌握 D2 从版本号分配、分支与 Tag 准备到制品构建、校验、发布的全链路操作并能基于仓库内的脚本与工具源码理解每一步的底层约束与安全设计。npm 发布d2lang/d2与terrastruct/d2的双身份 staged 发布D2 的 JavaScript 包有两条身份d2lang/d2是标准canonical包terrastruct/d2则在命名空间迁移期间作为兼容包从同一份构建产物发布。两个包由npm-stage.yml工作流同时暂存stage供 npm trusted publishing 审查每个暂存包拥有独立的 stage ID必须分别审查、分别批准。发布前的版本与信任前提发布正式版本前需要先将d2js/js/package.json与d2js/js/package-lock.json提升bump到完全一致的版本号且该提交必须是签名提交。每晚nightly的暂存版本号会自动从仓库内已检入的版本号推导出 prerelease 版本无需手工指定。d2lang/d2必须已经存在于 npm 上两个包必须各自信任d2lang/d2仓库的npm-stage.yml工作流且仅允许 stage-only 发布。每个受信任的发布者都被限制在npm-releaseGitHub 环境中该环境的部署分支策略只接受受保护分支工作流本身也拒绝从除refs/heads/master之外的任何 ref 打包。标准包之所以需要已经存在是因为 npm 不允许全新包直接使用 staged 发布——d2lang/d2是从既有terrastruct/d20.1.33的构建产物一次性引导bootstrap出来的。失败恢复的黄金法则Re-run failed jobs当两个 stage 中一个成功、一个失败时正确的恢复动作是在同一次 workflow run上使用 GitHub 的Re-run failed jobs。这样会保留原始提交并复用同一份不可变的包制品。禁止使用Re-run all jobs或重新发起一次全新的 workflow dispatch 来只恢复其中一个身份——因为暂存的包/版本会被 pending stage 保留占用新 run 会与之冲突。直接发布Direct publishing的保留用途直接发布仅用于本地引导或灾难恢复。它要求一个短期的NPM_TOKEN能发布所有被选包、且不存储在 GitHub 中。脚本在发布前会先预打包并对每个被选包做 preflight 检查重试时只有当 registry 中已有版本的 tarball 完整性校验值与本地制品逐字节一致时才会跳过该版本。审查与批准清单工作流结束后需要确认两个矩阵 stage 任务均为绿色然后用 npm 官方 CLI 检查两个 pending 记录npm stage list npm stage view stage-id npm stage download stage-id # 下载制品并核对包版本与制品 npm stage approve stage-id # 每个身份单独批准若发布被放弃在发起下一次 run 之前必须对每个 pending 半成品执行npm stage reject stage-id。由于 pending stage 会保留其包/版本重试失败任务前务必先npm stage list查看暂存状态。安装脚本install.sh 与 gen_install.sh仓库根目录的 install.sh 是由模板生成的一体化安装脚本其模板位于 ci/release/_install.sh生成器为 ci/release/gen_install.sh。生成机制gen_install.sh将_install.sh以及它依赖的库来自ci/sub/lib按顺序拼接成一个单一脚本./ci/sub/lib/rand.sh./ci/sub/lib/temp.sh./ci/sub/lib/log.sh./ci/sub/lib/flag.sh./ci/sub/lib/release.sh./ci/release/checksum.sh./ci/release/_install.sh实现安装逻辑生成过程中会用sed删除各库文件中的source依赖行^\. /并对_install.sh截取cd -- $(dirname...到cd -之间的主体最终脚本头部带有DO NOT EDIT / bundled together的警告注释。生成后的install.sh会被设为只读chmod -w防止误改。支持的安装方法安装脚本目前支持 standalone从 GitHub 下载独立 release 归档与 homebrewmacOS/Linux 包管理器两种方法detect模式会自动探测macOS 上有 brew 则用 homebrew否则回退 standaloneLinux/Windows 直接走 standalone。核心用法curl -fsSL https://d2lang.com/install.sh | sh -s -- # 交互式安装detect 模式 sh install.sh --dry-run # 只展示将要执行的操作 sh install.sh --version v0.9.0-rc.1 --methodstandalone # 安装指定版本 sh install.sh --prefix /usr/local --tala latest # 指定前缀并附带安装 TALA sh install.sh --uninstall # 卸载需与安装时相同的 method/prefix sh install.sh -x # 以 set -x 追踪执行关键参数说明来自 ci/release/_install.sh 的help输出--prefix pathstandalone 安装到的 unix 目录层级默认/usr/local若当前用户不可写则回退~/.local。安装后需将$PREFIX/bin加入$PATH、$PREFIX/share/man加入$MANPATH。--tala [latest]附带安装 Terrastruct 的闭源布局引擎 TALA二进制会安装到$PREFIX/bin/d2plugin-tala。--force即使版本相同也强制重装先卸载再安装会删除$PREFIX/lib/d2/d2-VERSION但保留~/.cache/d2/release中的归档缓存。下载的归档统一缓存到~/.cache/d2/release可用$XDG_CACHE_HOME改变路径见cache_dir()的实现无 HOME 时回退/tmp/d2-cache/release解压到$PREFIX/lib/d2/d2-VERSION。install.sh还内置了 checksum 校验逻辑从 GitHub API 读取 release 的 SHA-256 digest解析name/digest字段兼容 digest 缺失的旧版本下载后逐字节比对失败则丢弃缓存并报错校验逻辑的实现与测试分别见 ci/release/checksum.sh 与 ci/release/checksum_test.go。release.sh发布准备的顶层入口ci/release/release.sh 是生成新 release 的顶层脚本本质是对 Python 脚本 ci/release/prepare.py 的薄封装exec python3 ./ci/release/prepare.py $。运行./release.sh --help可查看用法。运行前提干净的 checkoutgit status --porcelain必须为空Python 3、Git、已认证的 GitHub CLIgh需要 GitHub push 权限用于检查既有 draft release。版本号规则版本号必须是 v 前缀的语义化版本v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z]([.-][0-9A-Za-z])*)?预发布版本必须有语义化后缀例如--versionv0.9.0-rc.1--prerelease搭配看起来像稳定版的版本会被拒绝一个版本号只分配一次。脚本永不amend release 提交、force-push 分支或替换 Tag包括 draft release 的 Tag。幂等与防冲突设计源码级从 ci/release/prepare.py 可以看到一系列守护性检查检查本地与远端 release 分支、Tag含 peeled commitrefs/tags/v^{}是否一致发现冲突即报错并建议换新版本号远端已有匹配 Tag 时prepare 变成 no-op提示跟随既有 Actions run已发布published的 release以及已附带.msi资产的 draft都会拒绝再次准备本地残留的失败推送 Tag仅当其指向未变更的 prepared commit 时才可以被推送且不重建 Tag 对象未打 Tag 的部分准备会复用既有 changelog 提交并正常推送分支不产生多余的空提交若远端准备提交在本地不可用需先git fetch origin再重试已废弃的--publish、--rebuild、--skip-build参数会被识别并给出迁移提示直接发布必须改为手动在 GitHub 上发布完整 draft。准备流程脚本会创建版本分支如v0.9.0将 ci/release/changelogs/next.md 复制为ci/release/changelogs/version.md并把 template.mdFeatures/Improvements/Bugfixes 三段式模板含 d2.js 独立 changelog 指引写回next.md随后提交 changelog、推送分支、创建带 Human/AI 模板描述的 PR保留既有 PR 描述不变、打注解 Tag 并推送。Release immutability 与制品不可变性建议在仓库 release 设置中开启Release immutability以在 GitHub 侧强制锁定。Draft 资产在发布前可编辑但上传器仍只接受与既有资产完全一致的字节发布动作会冻结 release 资产与关联 Tag因此必须在发布前挂载并核验全部资产。release notes/标题与 prerelease/latest 状态仍可编辑该设置只对未来的发布生效不回锁已发布的 release。Release archives 依赖图一次 Tag 推送触发的完整流水线推送 Tag 会触发Release archives工作流形成一条依赖图README 中编号 1–4 描述CI 测试 归档构建既有 CI 工作流测试精确的 Tag 提交并行地archive 任务用固定的 Go 工具链把六个平台归档各构建两次比对 SHA-256 摘要校验 stripped 二进制与归一化归档强制执行体积预算并生成 SBOM。原生冒烟测试每个归档在其支持的平台上原生运行检查版本输出、SVG 与 PNG 渲染。Windows MSI可复用的Windows MSI工作流消费本次 run 的 windows/amd64 归档校验 checksum 后在windows-2022上构建并验证安装包它始终从新归档打包 notices旧的已发布 release 不再作为 PR 测试夹具。Attestation 与 Draft 上传测试、原生冒烟、MSI 验证全部通过后工作流对归档 provenance、SBOM、checksum manifest 做 attestation一个写权限独立的 job 创建或续传 draft上传全部归档、SHA256SUMS、MSI 与d2.spdx.json。上传器在上传前会重新检查 Tag 提交与 draft 身份/状态并核验所有最终资产的 digest既有资产只有字节完全一致才被接受任何内容都不会被覆盖。流水线没有工作站下载/上传的交接环节也没有独立的 MSI poller。恢复策略与保留窗口不修改代码或制品的失败恢复对那次 run 使用Re-run failed jobs代码或构建变更必须换新版本号即使旧版本仍是 draftActions artifacts 不可变整体重跑会失败而不是替换draft 上传中断时失败 job 会通过接受一致的既有资产、只附加缺失资产来续传Actions artifacts 保留 30 天恢复需在该窗口内进行。制品验证命令每个 release 都包含SHA256SUMS可用 GitHub CLI 与 sha256sum 验证 provenance 与 checksumgh attestation verify d2-v0.0.0-linux-amd64.tar.gz --repo d2lang/d2 sha256sum --check --ignore-missing SHA256SUMSWiX 7 与 MSI 门控MSI 安装包使用 WiX 7目前仍未签名Authenticode 签名单独跟踪。在 workflow 安装或运行 WiX 7 之前仓库 owner 必须审阅 WiX Open Source Maintenance Fee 与 EULA 条款、确认必要的赞助到位并把仓库变量WIX7_EULA_ACCEPTED设置为精确的true。这个显式 owner 控制的闸门让 workflow 得以传-acceptEula wix7。变量缺失时PR 仍会构建并冒烟测试 release 归档但跳过 MSI 打包Tag 触发的构建直接关闭失败不上传任何 draft。PR 与 release 的职责边界Release 相关的 PR 会跑相同的 archive 与原生冒烟 job在 WiX 闸门开启时还包括 MSI 构建但 PR永不做 attestation 或写 release 资产。CI 由 Tag 工作流直接调用因此不依赖另一个 token 生成的事件。build.sh 与 _build.sh本地归档构建ci/release/build.sh 负责为每个平台构建 release 归档到./build/VERSION/*.tar.gz实际为ci/release/build/VERSION/d2-VERSION-OS-ARCH.tar.gz。版本默认通过git describe探测可用--version vX.X.X覆盖。常用参数./build.sh --help # 查看全部参数 ./build.sh --runlinux-amd64 # 只构建 linux-amd64 归档 ./build.sh --local --runlinux-amd64 # 本地构建单个平台 ./build.sh --host-only # 只构建宿主机 $OS-$ARCH 对 ./build.sh --rebuild # 强制重建已存在的归档 ./build.sh --install / --uninstall # 构建 host-only 归档并安装/卸载 ./build.sh --lockfile-force # 强制接管远端构建器锁文件注意本地开发调用仍然在本地构建但生产构建与制品上传归属 Tag 触发的Release archives工作流RELEASE1的本地构建会被明确拒绝build.sh会提示使用./ci/release/release.sh走 Actions。_build.sh 的单平台构建细节ci/release/_build.sh 由build.sh调用不应直接执行。它把发布模板ci/release/template下的 LICENSE、Makefile、man 手册、scripts、由README.md.sh渲染出的 README组装进构建目录然后强制使用go.mod中固定的 Go 工具链版本PINNED_GO_VERSIONrelease 构建时实际 Go 版本不符直接失败要求干净的 tracked 工作区VCS_MODIFIEDfalse与RELEASE_TOOL环境变量指向宿主 release-tool 二进制以CGO_ENABLED0、GOTOOLCHAINlocal、GOWORKoff、-trimpath、-buildvcstrue构建-ldflags注入lib/version.Versionamd64 固定GOAMD64v1arm64 固定GOARM64v8.0用 release-tool 依次执行verify-binary含体积上限默认 40,000,000 字节、archive按 SOURCE_DATE_EPOCH 归一化时间戳、verify-archive体积上限默认 18,000,000 字节。release-tool归档、校验与 SBOM 的底层实现ci/release/releasetool/main.go 是上述能力的 Go 实现提供六个子命令archive、checksums、sbom、verify-checksums、verify-binary、verify-archive。几个值得注意的设计归档归一化gzip 头时间戳与每个 tar 条目都统一为SOURCE_DATE_EPOCHUID/GID 归零、清空属主目录0755、普通文件0644原可执行位则0755、符号链接0777拒绝设备文件/管道/socket 等非常规类型并校验符号链接不得逃逸归档根目录verify-binary通过debug/buildinfo读取二进制内嵌的 Go 版本、主模块路径与构建设置-buildmodeexe、-compilergc、-trimpathtrue、CGO_ENABLED0、GOOS/GOARCH、vcs/vcs.revision/vcs.modified等并分别用debug/elf、debug/macho、debug/pe校验三种平台的二进制已被剥离无符号表/DWARF但保留 Go 栈与 buildinfoverify-archive校验条目严格排序、路径安全拒绝绝对路径、..逃逸、反斜杠/NUL、时间戳/属主归一化、必含文件LICENSE.txt、THIRD_PARTY_NOTICES.txt、Makefile、README.md、man/d2.1、scripts 下的 install/lib/uninstall 以及bin/d2或bin/d2.exesbom基于二进制内嵌的debug.BuildInfo生成 SPDX-2.3 JSONd2.spdx.json主包限定为github.com/d2lang/d2依赖以pkg:golang/...purl 形式列出并为每个依赖记录 Go module checksum 与 replace 信息。Docker本地构建助手与生产发布分离docker/build.sh仅限本地的 load-only 助手ci/release/docker/build.sh 被保留为仅限本地开发的 load-only 助手它拒绝--push、--latest以及继承的RELEASE发布方式这些参数会直接报错退出。它从ci/release/build/latest读取版本、把对应 linux 归档与 entrypoint.shfixuiddumb-init执行 d2拷入 docker 目录然后docker buildx build --load \ -t $D2_DOCKER_IMAGE:$VERSION \ -f ./ci/release/docker/Dockerfile ./ci/release/build/$VERSION/dockerrelease 脚本已不再调用它也不再从遗留 AWS SSH builder 发布 Docker Tag。生产 Docker 发布Publish Docker release 工作流Docker Hub 的生产路径是手动触发的Publish Docker releaseGitHub 工作流它替代了遗留 AWS release 路径中的 Docker 部分Windows MSI 由前述Windows MSI工作流单独构建。触发前提GitHub release 发布后从受保护的master分支运行输入精确的 v 前缀 release 版本除非该稳定版要成为默认镜像否则不要开启publish_latest。工作流会拒绝为 GitHub prerelease 或语义化 prerelease 开启publish_latest。环境要求docker-releaseGitHub 环境必须提供变量DOCKERHUB_USERNAMEd2lang与 secretDOCKERHUB_TOKEN该 Docker ID 的 personal access tokenDocker ID 需对标准仓库d2lang/d2和维护中的兼容镜像terrastruct/d2都有写权限环境部署分支策略只允许受保护分支。两个仓库都要保持版本 Tag 的 Docker Hub tag immutability规则^v[0-9]\.[0-9]\.[0-9](-[0-9A-Za-z]([.-][0-9A-Za-z])*)?$latest在该规则之外、保持可变这个仓库侧规则是最终覆盖守卫。发布前校验六步验证受保护的 master 触发、已发布的非 draft 语义化 GitHub release、精确的 linux amd64/arm64 资产 ID 及其 GitHub SHA-256 摘要显式把 Git Tag peel 到 commit要求该 commit 是触发mastercommit 的祖先并原样使用该 release commit 的 Dockerfile在 GitHub 托管的 amd64/arm64 原生 runner 上构建仅推送带 provenance 的 untagged digest对两个 digest 运行原生 version/SVG/PNG 冒烟测试dry-run 并验证精确的双平台 manifest含 provenance attestation立即重新获取 release 并重新 peel Tag要求 release ID、发布/预发布状态、Tag commit、资产 ID 与资产 digest 全部不变。只有全部通过才会在标准仓库与兼容镜像仓库创建请求的版本 Tag。既有版本 Tag 不可变工作流拒绝覆盖。若显式选择了publish_latest则仅在两个版本 manifest 发布并验证后才用不可变的版本 manifest digest 更新两仓库的latest。若版本 Tag 已创建但验证或latest提升失败用Re-run failed jobs同一次 run 仅在既有版本 Tag 的描述符与本次 run 验证过的候选完全一致时接受它独立的latestjob 可重试而不触碰版本 Tag新 dispatch 在 preflight 阶段仍拒绝任何既有版本 Tag。发布清单速查npm 包在签名提交中同步 bumpd2js/js/package.json与package-lock.json触发npm-stage.ymlnpm stage list→ 核对 → 逐个npm stage approve。版本准备干净 checkout 上运行./release.sh --versionvX.Y.Zprerelease 用--prerelease跟随 Actions run 完成测试、归档、冒烟、MSI、attestation 与 draft 上传。收尾审查并合并 release PR在 GitHub 上手动发布完整 draft用gh attestation verify与sha256sum --check核验制品。DockerGitHub release 发布后从受保护 master 手动 dispatchPublish Docker release输入 v 前缀版本按需决定publish_latest。结语D2 的发布体系把本地准备与云端构建发布做了清晰切割release.sh/prepare.py只负责不可变的元数据与 Git refs所有制品由 Tag 触发的Release archives工作流在 CI 中构建、双重校验并上传Docker 生产发布则独立走受保护环境的工作流配合 Docker Hub 侧不可变 Tag 规则形成最后一道防线。理解这条链路后你既可以安全地参与 D2 的版本发布也可以把同样的版本不可变 CI 所有权 幂等恢复模式复用到其他 Go 项目的发布工程中。【免费下载链接】d2D2 is a modern diagram scripting language that turns text to diagrams.项目地址: https://gitcode.com/GitHub_Trending/d2/d2创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考