diff --git a/.gitea/workflows/package.yml b/.gitea/workflows/package.yml index 5a7ca57..f8320e6 100644 --- a/.gitea/workflows/package.yml +++ b/.gitea/workflows/package.yml @@ -2,9 +2,8 @@ name: Package component on: push: - branches: - - main - workflow_dispatch: + tags: + - 'v*' jobs: package: @@ -17,10 +16,11 @@ jobs: set -euo pipefail server_url="${{ gitea.server_url }}" repository_url="${server_url%/}/${{ gitea.repository }}.git" + tag_name="${{ gitea.ref_name }}" git init . git remote add origin "$repository_url" - git fetch --depth 1 origin "${{ gitea.sha }}" - git checkout --detach FETCH_HEAD + git fetch --depth 1 origin "refs/tags/$tag_name:refs/tags/$tag_name" + git checkout --detach "$tag_name^{commit}" - name: Set up pnpm run: | @@ -30,47 +30,71 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Set package version + - name: Validate release version shell: bash run: | set -euo pipefail + tag_name="${{ gitea.ref_name }}" + tag_pattern='^v([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?)$' + if [[ ! "$tag_name" =~ $tag_pattern ]]; then + echo "Release tags must use vX.Y.Z or vX.Y.Z-prerelease format: $tag_name" >&2 + exit 1 + fi + + package_version="${BASH_REMATCH[1]}" source_version="$(node -p "require('./package.json').version")" - latest_version="$( - find /downloads -maxdepth 1 -type f -name 'waveform-analysis-*-*.tgz' -printf '%f\n' \ - | sed -nE 's/^waveform-analysis-([0-9]+\.[0-9]+\.[0-9]+)-[0-9a-f]+\.tgz$/\1/p' \ - | sort -V \ - | tail -n 1 - )" - next_version="$(node - "$source_version" "$latest_version" <<'NODE' - const source = process.argv[2] - const latest = process.argv[3] - const parse = (version) => { - const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version) - if (!match) throw new Error(`Unsupported package version: ${version}`) - return match.slice(1).map(Number) - } - const compare = (left, right) => { - for (let index = 0; index < 3; index += 1) { - if (left[index] !== right[index]) return left[index] - right[index] - } - return 0 - } - const sourceParts = parse(source) - if (!latest) { - process.stdout.write(source) - } else { - const latestParts = parse(latest) - if (compare(sourceParts, latestParts) > 0) { - process.stdout.write(source) - } else { - latestParts[2] += 1 - process.stdout.write(latestParts.join('.')) - } - } - NODE - )" - pnpm pkg set version="$next_version" - printf 'Packaging waveform-analysis@%s\n' "$next_version" + if [[ "$source_version" != "$package_version" ]]; then + echo "package.json version $source_version does not match tag $tag_name" >&2 + exit 1 + fi + + if [[ "$package_version" == *-* ]]; then + npm_dist_tag='next' + is_prerelease='true' + else + npm_dist_tag='latest' + is_prerelease='false' + fi + + cat > release.env <&2 + exit 1 + fi + + package_metadata="$(curl --fail --show-error --silent "$registry_url$package_name")" + if node -e 'const chunks = []; process.stdin.on("data", chunk => chunks.push(chunk)); process.stdin.on("end", () => { const metadata = JSON.parse(Buffer.concat(chunks).toString()); process.exit(metadata.versions?.[process.argv[1]] ? 0 : 1) })' "$PACKAGE_VERSION" <<<"$package_metadata"; then + echo "npm package $package_name@$PACKAGE_VERSION already exists" >&2 + exit 1 + fi + + - name: Validate publish credentials + shell: bash + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + GITEA_RELEASE_TOKEN: ${{ secrets.GITEA_RELEASE_TOKEN }} + run: | + set -euo pipefail + : "${NODE_AUTH_TOKEN:?NPM_PUBLISH_TOKEN is required}" + : "${GITEA_RELEASE_TOKEN:?GITEA_RELEASE_TOKEN is required}" - name: Type check run: pnpm typecheck @@ -85,14 +109,25 @@ jobs: run: pnpm build - name: Pack component - run: pnpm pack --pack-destination artifacts + shell: bash + run: | + set -euo pipefail + source release.env + pnpm pack --pack-destination artifacts + package_file="$(find artifacts -maxdepth 1 -type f -name '*.tgz' -print -quit)" + test -n "$package_file" + mkdir -p release-assets + published_name="waveform-analysis-${PACKAGE_VERSION}.tgz" + install -m 644 "$package_file" "release-assets/$published_name" + sha256sum "release-assets/$published_name" > "release-assets/$published_name.sha256" - name: Verify component package shell: bash run: | set -euo pipefail - package_file="$(find artifacts -maxdepth 1 -type f -name '*.tgz' -print -quit)" - test -n "$package_file" + source release.env + package_file="release-assets/waveform-analysis-${PACKAGE_VERSION}.tgz" + test -f "$package_file" package_contents="$(tar -tzf "$package_file")" for required_file in \ package/dist/index.js \ @@ -110,15 +145,15 @@ jobs: shell: bash run: | set -euo pipefail - package_file="$(find artifacts -maxdepth 1 -type f -name '*.tgz' -print -quit)" - package_version="$(node -p "require('./package.json').version")" - short_sha="$(printf '%.12s' '${{ gitea.sha }}')" - published_name="waveform-analysis-${package_version}-${short_sha}.tgz" - install -m 644 "$package_file" "/downloads/$published_name" + source release.env + published_name="waveform-analysis-${PACKAGE_VERSION}.tgz" + install -m 644 "release-assets/$published_name" "/downloads/$published_name" cd /downloads sha256sum "$published_name" > "$published_name.sha256" - ln -sfn "$published_name" waveform-analysis-latest.tgz - ln -sfn "$published_name.sha256" waveform-analysis-latest.tgz.sha256 + if [[ "$IS_PRERELEASE" == 'false' ]]; then + ln -sfn "$published_name" waveform-analysis-latest.tgz + ln -sfn "$published_name.sha256" waveform-analysis-latest.tgz.sha256 + fi - name: Publish to Gitea npm registry shell: bash @@ -126,8 +161,51 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} run: | set -euo pipefail + source release.env + : "${NODE_AUTH_TOKEN:?NPM_PUBLISH_TOKEN is required}" registry_url='https://lqycustomsite.online/api/packages/admin/npm/' - package_file="$(find artifacts -maxdepth 1 -type f -name '*.tgz' -print -quit)" - test -n "$package_file" + package_file="release-assets/waveform-analysis-${PACKAGE_VERSION}.tgz" npm config set -- '//lqycustomsite.online/api/packages/admin/npm/:_authToken' "$NODE_AUTH_TOKEN" - npm publish "./$package_file" --registry="$registry_url" + npm publish "$package_file" --registry="$registry_url" --tag "$NPM_DIST_TAG" + + - name: Create Gitea release + shell: bash + env: + GITEA_RELEASE_TOKEN: ${{ secrets.GITEA_RELEASE_TOKEN }} + run: | + set -euo pipefail + source release.env + : "${GITEA_RELEASE_TOKEN:?GITEA_RELEASE_TOKEN is required}" + server_url="${{ gitea.server_url }}" + api_url="$server_url/api/v1/repos/${{ gitea.repository }}/releases" + target_commitish="$(git rev-parse HEAD)" + + node - "$TAG_NAME" "$PACKAGE_VERSION" "$target_commitish" "$IS_PRERELEASE" > release.json <<'NODE' + const [tagName, packageVersion, targetCommitish, prerelease] = process.argv.slice(2) + process.stdout.write(JSON.stringify({ + tag_name: tagName, + target_commitish: targetCommitish, + name: `waveform-analysis ${tagName}`, + body: `Release ${tagName}\n\n- npm: waveform-analysis@${packageVersion}`, + draft: false, + prerelease: prerelease === 'true', + })) + NODE + + release_response="$(curl --fail --show-error --silent \ + --request POST \ + --header "Authorization: token $GITEA_RELEASE_TOKEN" \ + --header 'Content-Type: application/json' \ + --data @release.json \ + "$api_url")" + release_id="$(node -e 'const chunks = []; process.stdin.on("data", chunk => chunks.push(chunk)); process.stdin.on("end", () => { const release = JSON.parse(Buffer.concat(chunks).toString()); if (!release.id) process.exit(1); process.stdout.write(String(release.id)) })' <<<"$release_response")" + + for asset in release-assets/*; do + asset_name="$(basename "$asset")" + curl --fail --show-error --silent \ + --request POST \ + --header "Authorization: token $GITEA_RELEASE_TOKEN" \ + --header 'Content-Type: application/octet-stream' \ + --data-binary "@$asset" \ + "$api_url/$release_id/assets?name=$asset_name" > /dev/null + done diff --git a/README.md b/README.md index 4dd51b9..eecdd99 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,22 @@ import 'waveform-analysis/style.css' Vue、D3、Ant Design Vue 和 vue3-colorpicker 是 peer dependencies,需要由使用方安装。 `WaveformChart` 支持采样值与采样率,也支持显式的 `{ x, y }[]` 点数组。 +## 发布 + +发布由推送版本 tag 触发。先将 `package.json` 的 `version` 更新为目标版本并提交,再创建同版本 tag: + +```bash +git tag -a v0.1.7 -m "Release v0.1.7" +git push origin main --follow-tags +``` + +支持稳定版 `vX.Y.Z` 与预发布版 `vX.Y.Z-rc.1`。tag 去掉 `v` 后必须与 `package.json` 的 +`version` 完全一致。稳定版发布为 npm `latest` 并更新服务器下载目录的 latest 链接;预发布版发布为 +npm `next`,不会覆盖稳定版 latest。流水线会创建 Gitea Release,并上传 `.tgz` 与 SHA-256 校验文件。 + +仓库 Actions 需要配置 `NPM_PUBLISH_TOKEN`(npm 包发布权限)和 `GITEA_RELEASE_TOKEN`(仓库 Release +写入权限)两个 Secret。 + ## 波形图 组件内置缩放、悬浮取点和 tooltip。调用方只需要提供波形数据: diff --git a/package.json b/package.json index 7d207a2..fcccd67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "waveform-analysis", - "version": "0.1.0", + "version": "0.1.7", "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/types/index.d.ts",