release.yml 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. name: Release
  2. on:
  3. push:
  4. tags:
  5. - "v*"
  6. permissions:
  7. contents: write
  8. jobs:
  9. test:
  10. runs-on: ubuntu-latest
  11. strategy:
  12. matrix:
  13. node-version: [20, 22, 24]
  14. steps:
  15. - uses: actions/checkout@v4
  16. - uses: pnpm/action-setup@v4
  17. with:
  18. version: 9
  19. - uses: actions/setup-node@v4
  20. with:
  21. node-version: ${{ matrix.node-version }}
  22. cache: pnpm
  23. - run: pnpm install --frozen-lockfile
  24. - run: pnpm build
  25. - run: pnpm --filter @actalk/inkos-core typecheck
  26. - run: pnpm --filter @actalk/inkos typecheck
  27. - run: pnpm test
  28. smoke-test:
  29. runs-on: ubuntu-latest
  30. needs: test
  31. steps:
  32. - uses: actions/checkout@v4
  33. - uses: pnpm/action-setup@v4
  34. with:
  35. version: 9
  36. - uses: actions/setup-node@v4
  37. with:
  38. node-version: 22
  39. cache: pnpm
  40. - run: pnpm install --frozen-lockfile
  41. - run: pnpm build
  42. - name: Smoke test — CLI help and version
  43. run: |
  44. node packages/cli/dist/index.js --help
  45. node packages/cli/dist/index.js --version
  46. - name: Smoke test — core import
  47. run: node -e "import('./packages/core/dist/index.js').then(m => { if (!m.PipelineRunner) throw new Error('missing PipelineRunner'); console.log('core import OK') })"
  48. - name: Smoke test — verify no workspace:* in tarballs
  49. run: |
  50. set -euo pipefail
  51. for pkg in packages/core packages/cli packages/studio; do
  52. echo "--- Checking $pkg ---"
  53. cd "$pkg"
  54. PACKDIR=$(mktemp -d)
  55. npm pack --pack-destination "$PACKDIR" 2>/dev/null
  56. TGZ=$(ls "$PACKDIR"/*.tgz)
  57. PACKED_PKG=$(tar -xOf "$TGZ" package/package.json)
  58. if echo "$PACKED_PKG" | grep -q '"workspace:'; then
  59. echo "FAIL: $pkg tarball still contains workspace: protocol"
  60. echo "$PACKED_PKG" | grep 'workspace:'
  61. exit 1
  62. fi
  63. echo "OK: $pkg tarball is clean"
  64. rm -rf "$PACKDIR"
  65. cd - >/dev/null
  66. done
  67. publish-canary:
  68. runs-on: ubuntu-latest
  69. needs: smoke-test
  70. outputs:
  71. release_version: ${{ steps.versions.outputs.release_version }}
  72. canary_version: ${{ steps.versions.outputs.canary_version }}
  73. steps:
  74. - uses: actions/checkout@v4
  75. - uses: pnpm/action-setup@v4
  76. with:
  77. version: 9
  78. - uses: actions/setup-node@v4
  79. with:
  80. node-version: 22
  81. cache: pnpm
  82. registry-url: https://registry.npmjs.org
  83. - run: pnpm install --frozen-lockfile
  84. - run: pnpm build
  85. - name: Derive release versions
  86. id: versions
  87. run: |
  88. RELEASE_VERSION="${GITHUB_REF_NAME#v}"
  89. CANARY_VERSION="${RELEASE_VERSION}-canary.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}"
  90. echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
  91. echo "canary_version=$CANARY_VERSION" >> "$GITHUB_OUTPUT"
  92. - name: Rewrite package versions for canary publish
  93. run: |
  94. node scripts/set-package-versions.mjs "${{ steps.versions.outputs.canary_version }}" --root .
  95. pnpm verify:publish-manifests
  96. - name: Publish core canary
  97. working-directory: packages/core
  98. run: pnpm publish --tag canary --access public --no-git-checks
  99. env:
  100. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
  101. - name: Publish Studio canary
  102. working-directory: packages/studio
  103. run: pnpm publish --tag canary --access public --no-git-checks
  104. env:
  105. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
  106. - name: Publish CLI canary
  107. working-directory: packages/cli
  108. run: pnpm publish --tag canary --access public --no-git-checks
  109. env:
  110. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
  111. verify-canary:
  112. runs-on: ubuntu-latest
  113. needs: publish-canary
  114. steps:
  115. - uses: actions/setup-node@v4
  116. with:
  117. node-version: 22
  118. registry-url: https://registry.npmjs.org
  119. - name: Verify canary dist-tag and installation
  120. run: |
  121. set -euo pipefail
  122. EXPECTED_CANARY="${{ needs.publish-canary.outputs.canary_version }}"
  123. ACTUAL_CANARY=""
  124. for _ in 1 2 3 4 5 6; do
  125. ACTUAL_CANARY=$(npm view @actalk/inkos@canary version 2>/dev/null || true)
  126. if [ "$ACTUAL_CANARY" = "$EXPECTED_CANARY" ]; then
  127. break
  128. fi
  129. sleep 10
  130. done
  131. if [ "$ACTUAL_CANARY" != "$EXPECTED_CANARY" ]; then
  132. echo "FATAL: canary dist-tag mismatch: expected $EXPECTED_CANARY got ${ACTUAL_CANARY:-<empty>}"
  133. exit 1
  134. fi
  135. TMPDIR=$(mktemp -d)
  136. cd "$TMPDIR"
  137. npm init -y
  138. npm install "@actalk/inkos@$EXPECTED_CANARY"
  139. if grep -q '"workspace:' node_modules/@actalk/inkos/package.json; then
  140. echo "FATAL: canary CLI package still contains workspace: protocol"
  141. cat node_modules/@actalk/inkos/package.json | grep workspace
  142. exit 1
  143. fi
  144. if grep -q '"workspace:' node_modules/@actalk/inkos-studio/package.json; then
  145. echo "FATAL: canary studio package still contains workspace: protocol"
  146. cat node_modules/@actalk/inkos-studio/package.json | grep workspace
  147. exit 1
  148. fi
  149. if grep -q '"workspace:' node_modules/@actalk/inkos-core/package.json; then
  150. echo "FATAL: canary core package still contains workspace: protocol"
  151. cat node_modules/@actalk/inkos-core/package.json | grep workspace
  152. exit 1
  153. fi
  154. npx inkos --version
  155. test -f node_modules/@actalk/inkos-studio/dist/api/index.js
  156. node -e "import('@actalk/inkos-core').then(m => { if (!m.PipelineRunner) throw new Error('installed core import missing PipelineRunner'); console.log('installed core import OK') })"
  157. test -f node_modules/@actalk/inkos-core/dist/notify/telegram.js || test -f node_modules/@actalk/inkos/node_modules/@actalk/inkos-core/dist/notify/telegram.js
  158. echo "Canary verification passed"
  159. rm -rf "$TMPDIR"
  160. publish-release:
  161. runs-on: ubuntu-latest
  162. needs: [publish-canary, verify-canary]
  163. steps:
  164. - uses: actions/checkout@v4
  165. - uses: pnpm/action-setup@v4
  166. with:
  167. version: 9
  168. - uses: actions/setup-node@v4
  169. with:
  170. node-version: 22
  171. cache: pnpm
  172. registry-url: https://registry.npmjs.org
  173. - run: pnpm install --frozen-lockfile
  174. - run: pnpm build
  175. - name: Rewrite package versions for final publish
  176. run: |
  177. node scripts/set-package-versions.mjs "${{ needs.publish-canary.outputs.release_version }}" --root .
  178. pnpm verify:publish-manifests
  179. - name: Publish core latest
  180. working-directory: packages/core
  181. run: pnpm publish --access public --no-git-checks
  182. env:
  183. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
  184. - name: Publish Studio latest
  185. working-directory: packages/studio
  186. run: pnpm publish --access public --no-git-checks
  187. env:
  188. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
  189. - name: Publish CLI latest
  190. working-directory: packages/cli
  191. run: pnpm publish --access public --no-git-checks
  192. env:
  193. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
  194. verify-release:
  195. runs-on: ubuntu-latest
  196. needs: [publish-canary, publish-release]
  197. steps:
  198. - uses: actions/setup-node@v4
  199. with:
  200. node-version: 22
  201. registry-url: https://registry.npmjs.org
  202. - name: Verify latest dist-tag and installation
  203. run: |
  204. set -euo pipefail
  205. EXPECTED_RELEASE="${{ needs.publish-canary.outputs.release_version }}"
  206. ACTUAL_LATEST=""
  207. for _ in 1 2 3 4 5 6; do
  208. ACTUAL_LATEST=$(npm view @actalk/inkos@latest version 2>/dev/null || true)
  209. if [ "$ACTUAL_LATEST" = "$EXPECTED_RELEASE" ]; then
  210. break
  211. fi
  212. sleep 10
  213. done
  214. if [ "$ACTUAL_LATEST" != "$EXPECTED_RELEASE" ]; then
  215. echo "FATAL: latest dist-tag mismatch: expected $EXPECTED_RELEASE got ${ACTUAL_LATEST:-<empty>}"
  216. exit 1
  217. fi
  218. TMPDIR=$(mktemp -d)
  219. cd "$TMPDIR"
  220. npm init -y
  221. npm install "@actalk/inkos@$EXPECTED_RELEASE"
  222. if grep -q '"workspace:' node_modules/@actalk/inkos/package.json; then
  223. echo "FATAL: released CLI package still contains workspace: protocol"
  224. cat node_modules/@actalk/inkos/package.json | grep workspace
  225. exit 1
  226. fi
  227. if grep -q '"workspace:' node_modules/@actalk/inkos-studio/package.json; then
  228. echo "FATAL: released studio package still contains workspace: protocol"
  229. cat node_modules/@actalk/inkos-studio/package.json | grep workspace
  230. exit 1
  231. fi
  232. if grep -q '"workspace:' node_modules/@actalk/inkos-core/package.json; then
  233. echo "FATAL: released core package still contains workspace: protocol"
  234. cat node_modules/@actalk/inkos-core/package.json | grep workspace
  235. exit 1
  236. fi
  237. npx inkos --version
  238. test -f node_modules/@actalk/inkos-studio/dist/api/index.js
  239. node -e "import('@actalk/inkos-core').then(m => { if (!m.PipelineRunner) throw new Error('installed core import missing PipelineRunner'); console.log('installed core import OK') })"
  240. test -f node_modules/@actalk/inkos-core/dist/notify/telegram.js || test -f node_modules/@actalk/inkos/node_modules/@actalk/inkos-core/dist/notify/telegram.js
  241. echo "Release verification passed"
  242. rm -rf "$TMPDIR"
  243. github-release:
  244. runs-on: ubuntu-latest
  245. needs: verify-release
  246. steps:
  247. - uses: actions/checkout@v4
  248. with:
  249. fetch-depth: 0
  250. - name: Generate release notes
  251. id: notes
  252. run: |
  253. PREV_TAG=$(git tag --sort=-creatordate | sed -n '2p' || echo "")
  254. if [ -n "$PREV_TAG" ]; then
  255. echo "notes<<EOF" >> "$GITHUB_OUTPUT"
  256. git log --pretty=format:"- %s (%h)" "$PREV_TAG"..HEAD >> "$GITHUB_OUTPUT"
  257. echo "" >> "$GITHUB_OUTPUT"
  258. echo "EOF" >> "$GITHUB_OUTPUT"
  259. else
  260. echo "notes=Initial release" >> "$GITHUB_OUTPUT"
  261. fi
  262. - uses: softprops/action-gh-release@v2
  263. with:
  264. body: ${{ steps.notes.outputs.notes }}
  265. generate_release_notes: true