diff --git a/.github/workflows/alpha-bootstrap.yml b/.github/workflows/alpha-bootstrap.yml new file mode 100644 index 0000000..cbb7975 --- /dev/null +++ b/.github/workflows/alpha-bootstrap.yml @@ -0,0 +1,50 @@ +name: Alpha Bootstrap + +on: + create: + +permissions: + contents: write + pull-requests: write + +jobs: + bootstrap: + runs-on: ubuntu-latest + steps: + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + - name: Filter only alpha/*.*.* branch creation + id: guard + run: | + RAW_REF="${GITHUB_REF_NAME}" # created branch name + if [[ "$RAW_REF" != alpha/*.*.* ]]; then + echo "Not an alpha/*.*.* branch: $RAW_REF. Exiting."; echo "continue=false" >> $GITHUB_OUTPUT; exit 0; fi + echo "continue=true" >> $GITHUB_OUTPUT + - name: Stop if not alpha pattern + if: steps.guard.outputs.continue != 'true' + run: echo "Skipped" + - name: Derive release branch name + if: steps.guard.outputs.continue == 'true' + id: names + run: | + RAW_REF="${GITHUB_REF_NAME}" # alpha/x.x.x + BASE=${RAW_REF#alpha/} + echo "release_branch=release/$BASE" >> $GITHUB_OUTPUT + - name: Create release branch + if: steps.guard.outputs.continue == 'true' + run: | + git config user.name 'github-actions' + git config user.email 'github-actions@users.noreply.github.com' + git checkout -b "${{ steps.names.outputs.release_branch }}" + git push origin "${{ steps.names.outputs.release_branch }}" + - name: Comment info + if: steps.guard.outputs.continue == 'true' + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: 1 + body: | + Created release branch `${{ steps.names.outputs.release_branch }}` for alpha `${{ github.ref_name }}`. + (Adjust this workflow to post elsewhere if desired.) diff --git a/.github/workflows/alpha-merge-beta.yml b/.github/workflows/alpha-merge-beta.yml new file mode 100644 index 0000000..4ba6edd --- /dev/null +++ b/.github/workflows/alpha-merge-beta.yml @@ -0,0 +1,86 @@ +name: Alpha Merge -> Beta Publish + +on: + pull_request: + types: [closed] + branches: + - 'alpha/**' + +permissions: + contents: write + packages: write + +jobs: + beta-publish: + if: github.event.pull_request.merged == true && startsWith(github.base_ref, 'alpha/') + runs-on: ubuntu-latest + env: + WASM_PACK_VERSION: 0.12.1 + steps: + - name: Checkout full history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Derive base version + id: base + run: | + # base branch name alpha/x.y.z + BASE_BRANCH="${GITHUB_BASE_REF}" # alpha/1.2.3 + RAW=${BASE_BRANCH#alpha/} + echo "raw=$RAW" >> $GITHUB_OUTPUT + - name: Find latest beta tag + id: find + run: | + RAW=${{ steps.base.outputs.raw }} + # list existing beta tags vRAW-beta.N + LAST=$(git tag --list "v${RAW}-beta.*" | sed -E 's/.*-beta\.([0-9]+)/\1/' | sort -n | tail -1) + if [ -z "$LAST" ]; then NEXT=0; else NEXT=$((LAST+1)); fi + echo "next=$NEXT" >> $GITHUB_OUTPUT + echo "Will create beta index $NEXT for $RAW" + - name: Bump Cargo.toml versions (beta) + run: | + RAW=${{ steps.base.outputs.raw }} + BETA_IDX=${{ steps.find.outputs.next }} + VERSION="${RAW}-beta.${BETA_IDX}" + for FILE in crates/source_map_parser/Cargo.toml crates/node_sdk/Cargo.toml; do + awk -v ver="$VERSION" 'BEGIN{done=0} /^version *=/ && done==0 {print "version = \"" ver "\""; done=1; next} {print}' "$FILE" > /tmp/_tmp && mv /tmp/_tmp "$FILE" + done + # sync dependency inside node_sdk + sed -i.bak -E "s/source_map_parser = \{ version = \"[^\"]+\"/source_map_parser = { version = \"$VERSION\"/" crates/node_sdk/Cargo.toml || true + rm -f crates/node_sdk/Cargo.toml.bak + echo "Updated versions to $VERSION" + - name: Commit version bump to alpha & cherry-pick to release + run: | + git config user.name 'github-actions' + git config user.email 'github-actions@users.noreply.github.com' + RAW=${{ steps.base.outputs.raw }} + BETA_IDX=${{ steps.find.outputs.next }} + VERSION="${RAW}-beta.${BETA_IDX}" + git add crates/*/Cargo.toml + git commit -m "chore(release): v$VERSION" || echo "No changes" + # push to alpha + git push origin "${GITHUB_BASE_REF}" || true + # update release branch + REL_BRANCH="release/${RAW}" + git fetch origin "$REL_BRANCH":"$REL_BRANCH" || true + git checkout "$REL_BRANCH" + git cherry-pick "${GITHUB_BASE_REF}" || echo "Cherry-pick skipped" + git push origin "$REL_BRANCH" || true + # back to alpha + git checkout "${GITHUB_BASE_REF}" + - name: Generate CHANGELOG fragment + run: | + chmod +x scripts/generate-changelog.sh || true + RAW=${{ steps.base.outputs.raw }} + BETA_IDX=${{ steps.find.outputs.next }} + VERSION="${RAW}-beta.${BETA_IDX}" + ./scripts/generate-changelog.sh "$VERSION" "${{ github.server_url }}/${{ github.repository }}" || echo "No changelog script" + - name: Create beta tag & push + run: | + RAW=${{ steps.base.outputs.raw }} + BETA_IDX=${{ steps.find.outputs.next }} + VERSION="${RAW}-beta.${BETA_IDX}" + git tag "v$VERSION" + git push origin "v$VERSION" + - name: Trigger release workflow (uses tag push) + run: echo "Tag pushed triggers existing Release workflow" diff --git a/.github/workflows/finalize-release.yml b/.github/workflows/finalize-release.yml new file mode 100644 index 0000000..fea935d --- /dev/null +++ b/.github/workflows/finalize-release.yml @@ -0,0 +1,80 @@ +name: Finalize Release (Stable) + +on: + workflow_dispatch: + inputs: + target: + description: 'Release base version x.y.z (without v prefix)' + required: true + dry_run: + description: 'Dry run (no tag push / PR)' + required: false + default: 'false' + +permissions: + contents: write + pull-requests: write + +jobs: + finalize: + runs-on: ubuntu-latest + env: + WASM_PACK_VERSION: 0.12.1 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Validate release branch context + run: | + TARGET='${{ inputs.target }}' + if [[ ! $TARGET =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Target must be x.y.z"; exit 1; fi + CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + EXPECT="release/${TARGET}" + if [ "$CURRENT_BRANCH" != "$EXPECT" ]; then + echo "Must run on $EXPECT branch (current $CURRENT_BRANCH)"; exit 1; fi + - name: Ensure Cargo.toml uses stable version (no pre) + run: | + TARGET='${{ inputs.target }}' + for FILE in crates/source_map_parser/Cargo.toml crates/node_sdk/Cargo.toml; do + CUR=$(grep '^version' "$FILE" | head -1 | cut -d '"' -f2) + if echo "$CUR" | grep -qE 'alpha|beta|rc'; then + echo "Updating $FILE -> $TARGET" + awk -v ver="$TARGET" 'BEGIN{done=0} /^version *=/ && done==0 {print "version = \"" ver "\""; done=1; next} {print}' "$FILE" > /tmp/_tmp && mv /tmp/_tmp "$FILE" + fi + done + # sync dependency version + sed -i.bak -E "s/source_map_parser = \{ version = \"[^\"]+\"/source_map_parser = { version = \"$TARGET\"/" crates/node_sdk/Cargo.toml || true + rm -f crates/node_sdk/Cargo.toml.bak + git diff --name-only || true + - name: Commit stable bump + run: | + TARGET='${{ inputs.target }}' + if [ -n "$(git status --porcelain)" ]; then + git config user.name 'github-actions' + git config user.email 'github-actions@users.noreply.github.com' + git add crates/*/Cargo.toml + git commit -m "chore(release): v$TARGET stable" + git push origin HEAD + else + echo "No version bump needed" + fi + - name: Generate CHANGELOG + run: | + chmod +x scripts/generate-changelog.sh || true + ./scripts/generate-changelog.sh '${{ inputs.target }}' "${{ github.server_url }}/${{ github.repository }}" || echo 'No changelog script' + - name: Create tag + if: inputs.dry_run != 'true' + run: | + TARGET='${{ inputs.target }}' + git tag "v$TARGET" + git push origin "v$TARGET" + - name: Open PR back to main + if: inputs.dry_run != 'true' + uses: peter-evans/create-pull-request@v6 + with: + branch: chore/merge-release-v${{ inputs.target }} + title: 'chore: merge release v${{ inputs.target }} into main' + body: 'Stable release v${{ inputs.target }} merge back to main' + base: main + labels: release diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..218d739 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,145 @@ +name: Prepare Release + +on: + workflow_dispatch: + inputs: + bump_type: + description: 'Version bump type (major|minor|patch|premajor|preminor|prepatch|prerelease)' + required: true + default: patch + preid: + description: 'Pre-release identifier (beta|rc|alpha)' + required: false + default: beta + dry_run: + description: 'Dry run (true = no branch/PR)' + required: false + default: 'false' + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + next_version: ${{ steps.calc.outputs.next_version }} + is_prerelease: ${{ steps.calc.outputs.is_prerelease }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq + + - name: Calculate next version + id: calc + env: + BUMP: ${{ github.event.inputs.bump_type }} + PREID: ${{ github.event.inputs.preid }} + run: | + set -e + CORE_FILE=crates/source_map_parser/Cargo.toml + NODE_FILE=crates/node_sdk/Cargo.toml + CURRENT=$(grep '^version' $CORE_FILE | head -1 | cut -d '"' -f2) + echo "Current version: $CURRENT" + + base_version() { + echo "$1" | sed -E 's/(-|\+).*//' + } + + inc_semver() { + local ver=$1 part=$2 + IFS='.' read -r MA MI PA <<<"$ver" + case $part in + major) MA=$((MA+1)); MI=0; PA=0;; + minor) MI=$((MI+1)); PA=0;; + patch) PA=$((PA+1));; + *) echo "Unknown bump" >&2; exit 1;; + esac + echo "${MA}.${MI}.${PA}" + } + + is_prerelease=false + case $BUMP in + premajor|preminor|prepatch|prerelease) + is_prerelease=true + ;; + esac + + BASE=$(base_version "$CURRENT") + + if [[ $BUMP == premajor || $BUMP == preminor || $BUMP == prepatch ]]; then + case $BUMP in + premajor) BASE=$(inc_semver "$BASE" major);; + preminor) BASE=$(inc_semver "$BASE" minor);; + prepatch) BASE=$(inc_semver "$BASE" patch);; + esac + NEXT_BASE=$BASE + SEQ=0 + elif [[ $BUMP == prerelease ]]; then + NEXT_BASE=$BASE + # find last prerelease with same base + LAST=$(git tag --list "v${BASE}-${PREID}.*" | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+-'"$PREID"'\.([0-9]+)$/\1/' | sort -n | tail -1) + if [[ -z $LAST ]]; then SEQ=0; else SEQ=$((LAST+1)); fi + else + # normal bump + NEXT_BASE=$(inc_semver "$BASE" $BUMP) + fi + + if [[ $is_prerelease == true ]]; then + NEXT_VERSION="${NEXT_BASE}-${PREID}.${SEQ:-0}" + else + NEXT_VERSION="$NEXT_BASE" + fi + + echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT + echo "is_prerelease=$is_prerelease" >> $GITHUB_OUTPUT + echo "Next: $NEXT_VERSION" + + - name: Update versions in Cargo.toml + run: | + NV=${{ steps.calc.outputs.next_version }} + for f in crates/source_map_parser/Cargo.toml crates/node_sdk/Cargo.toml; do + awk -v ver="$NV" 'BEGIN{done=0} /^version *=/ && done==0 {print "version = \"" ver "\""; done=1; next} {print}' "$f" > /tmp/_tmp && mv /tmp/_tmp "$f" + done + # sync dependency in node_sdk + sed -i "s/version = \"[0-9A-Za-z.-]*\" }/version = \"$NV\" }/" crates/node_sdk/Cargo.toml + echo "Updated versions to $NV" + grep '^version' crates/source_map_parser/Cargo.toml | head -1 + grep '^version' crates/node_sdk/Cargo.toml | head -1 + + - name: Generate changelog (preview) + run: | + chmod +x scripts/generate-changelog.sh || true + ./scripts/generate-changelog.sh ${{ steps.calc.outputs.next_version }} "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" || echo "Changelog script skipped" + head -100 CHANGELOG.md || true + + - name: Dry run stop + if: github.event.inputs.dry_run == 'true' + run: | + echo "Dry run requested. No branch or PR created."; exit 0 + + - name: Create release branch & commit + run: | + NV=${{ steps.calc.outputs.next_version }} + BR=chore/release-v$NV + git config user.name 'github-actions' + git config user.email 'github-actions@users.noreply.github.com' + git checkout -b "$BR" + git add crates/*/Cargo.toml CHANGELOG.md || true + git commit -m "chore(release): v$NV" + git push origin "$BR" + + - name: Open PR + uses: peter-evans/create-pull-request@v6 + with: + branch: chore/release-v${{ steps.calc.outputs.next_version }} + title: 'chore(release): v${{ steps.calc.outputs.next_version }}' + body: | + Prepare release v${{ steps.calc.outputs.next_version }} + - bump type: ${{ github.event.inputs.bump_type }} + - prerelease: ${{ steps.calc.outputs.is_prerelease }} (preid=${{ github.event.inputs.preid }}) + labels: release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63f926e..d6b9222 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,7 @@ on: permissions: contents: write packages: write + pull-requests: write env: WASM_PACK_VERSION: 0.12.1 @@ -17,6 +18,7 @@ jobs: runs-on: ubuntu-latest outputs: version: ${{ steps.extract.outputs.version }} + is_beta: ${{ steps.extract.outputs.is_beta }} steps: - uses: actions/checkout@v4 with: @@ -26,18 +28,27 @@ jobs: run: | TAG="${GITHUB_REF_NAME}" VERSION="${TAG#v}" + if echo "$VERSION" | grep -qiE 'beta|rc|alpha'; then + echo "is_beta=true" >> $GITHUB_OUTPUT + else + echo "is_beta=false" >> $GITHUB_OUTPUT + fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Tag version: $VERSION" - - name: Check crate versions match tag + - name: Check node crate version matches tag run: | TAG_VERSION=${{ steps.extract.outputs.version }} - CORE_VERSION=$(grep '^version' crates/source_map_parser/Cargo.toml | head -1 | cut -d '"' -f2) - NODE_VERSION=$(grep '^version' crates/node_sdk/Cargo.toml | head -1 | cut -d '"' -f2) - echo "core: $CORE_VERSION node: $NODE_VERSION tag: $TAG_VERSION" - if [ "$CORE_VERSION" != "$TAG_VERSION" ] || [ "$NODE_VERSION" != "$TAG_VERSION" ]; then - echo "Version mismatch. Please bump crate versions to $TAG_VERSION before tagging." >&2 - exit 1 + FILE=crates/node_sdk/Cargo.toml + CURRENT=$(grep '^version' "$FILE" | head -1 | cut -d '"' -f2) + echo "current: $CURRENT tag: $TAG_VERSION" + if [ "$CURRENT" != "$TAG_VERSION" ]; then + echo "Adjusting version in $FILE -> $TAG_VERSION" + # replace first occurrence of version line under [package] + awk -v ver="$TAG_VERSION" 'BEGIN{done=0} /^version *=/ && done==0 {print "version = \"" ver "\""; done=1; next} {print}' "$FILE" > /tmp/_tmp && mv /tmp/_tmp "$FILE" fi + grep '^version' "$FILE" | head -1 + - name: (Optional) Show diff after version sync + run: git diff --name-only && git --no-pager diff crates/node_sdk/Cargo.toml || true - name: Set up Rust uses: actions-rs/toolchain@v1 with: @@ -79,32 +90,11 @@ jobs: name: changelog path: CHANGELOG.md - publish-crates: - needs: verify-and-test - runs-on: ubuntu-latest - environment: release - steps: - - uses: actions/checkout@v4 - - name: Set up Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - name: Cargo login - run: cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }} - - name: Publish core crate - run: | - cargo publish -p source_map_parser --no-verify || echo "core crate already published" - - name: Publish node wasm crate - run: | - cargo publish -p source_map_parser_node --no-verify || echo "node crate already published" - publish-npm: needs: [verify-and-test] runs-on: ubuntu-latest env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # ensure repository secret NPM_TOKEN is set steps: - uses: actions/checkout@v4 - name: Set up Rust @@ -127,16 +117,32 @@ jobs: run: | wasm-pack build crates/node_sdk --target nodejs --out-dir pkg --release ls -l pkg + - name: Copy README into package + run: | + if [ -f crates/node_sdk/README.md ]; then + cp crates/node_sdk/README.md pkg/README.md + echo "Copied README.md into pkg/" + else + echo "crates/node_sdk/README.md not found" >&2 + fi - name: Set npm auth run: | echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > ~/.npmrc - - name: Publish to npm + - name: Publish to npm (stable / beta) run: | cd pkg - npm publish --access public || echo "npm package already published" + VERSION=${{ needs.verify-and-test.outputs.version }} + IS_BETA=${{ needs.verify-and-test.outputs.is_beta }} + if [ "$IS_BETA" = "true" ]; then + echo "Publishing prerelease (beta) with dist-tag beta" + npm publish --tag beta --access public || echo "npm beta publish skipped" + else + echo "Publishing stable release" + npm publish --access public || echo "npm publish skipped" + fi github-release: - needs: [publish-crates, publish-npm] + needs: [publish-npm] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -158,55 +164,6 @@ jobs: tag_name: ${{ github.ref_name }} name: source-map-parser ${{ github.ref_name }} body_path: notes.txt + prerelease: ${{ needs.verify-and-test.outputs.is_beta == 'true' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-gitlab: - needs: [verify-and-test] - runs-on: ubuntu-latest - env: - GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} - GITLAB_PROJECT_ID: ${{ secrets.GITLAB_PROJECT_ID }} - GITLAB_PROJECT_PATH: ${{ secrets.GITLAB_PROJECT_PATH }} - VERSION: ${{ needs.verify-and-test.outputs.version }} - steps: - - uses: actions/checkout@v4 - - name: Prepare artifacts - run: | - cargo package -p source_map_parser --allow-dirty - cargo package -p source_map_parser_node --allow-dirty - wasm-pack build crates/node_sdk --target nodejs --out-dir pkg --release - tar czf source_map_parser-${VERSION}.crate.tar.gz target/package/source_map_parser-*.crate - tar czf source_map_parser_node-${VERSION}.crate.tar.gz target/package/source_map_parser_node-*.crate - tar czf source_map_parser_node-wasm-${VERSION}.tgz -C pkg . - - name: Upload to GitLab generic packages - if: env.GITLAB_TOKEN != '' - run: | - for f in source_map_parser-${VERSION}.crate.tar.gz source_map_parser_node-${VERSION}.crate.tar.gz source_map_parser_node-wasm-${VERSION}.tgz; do - echo "Uploading $f" - curl --fail -H "PRIVATE-TOKEN: $GITLAB_TOKEN" --upload-file "$f" \ - "https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/packages/generic/source-map-parser/${VERSION}/$f" - done - - name: Publish to GitLab npm registry - if: env.GITLAB_TOKEN != '' - run: | - if [ -z "$GITLAB_PROJECT_PATH" ]; then - echo "GITLAB_PROJECT_PATH secret not set, skip GitLab npm publish"; - exit 0; - fi - # Configure .npmrc for GitLab registry (avoid heredoc for YAML lint friendliness) - echo "@${GITLAB_PROJECT_PATH#*/}:registry=https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/packages/npm/" > .npmrc - echo "//gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/packages/npm/:_authToken=${GITLAB_TOKEN}" >> .npmrc - echo "always-auth=true" >> .npmrc - # Adjust package name for GitLab scope if needed - PACKAGE_JSON=pkg/package.json - if jq -e '.name' "$PACKAGE_JSON" >/dev/null 2>&1; then - ORIGINAL_NAME=$(jq -r '.name' $PACKAGE_JSON) - if [[ ! $ORIGINAL_NAME == @*/* ]]; then - # prepend scope from project path last segment - SCOPE="@${GITLAB_PROJECT_PATH##*/}" - TMP=$(mktemp) - jq --arg scope "$SCOPE" --arg name "$ORIGINAL_NAME" '.name = ($scope + "/" + $name)' $PACKAGE_JSON > $TMP && mv $TMP $PACKAGE_JSON - fi - fi - (cd pkg && npm publish --registry https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/packages/npm/ || echo "GitLab npm publish skipped/failed") diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 500a87d..8ee6852 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,161 +1,7 @@ -# Contributors Guide +# Contributors Guide (Merged) -> 面向希望参与 `source-map-parser` 开发与发布的贡献者。本指南涵盖:开发环境、代码规范、测试、版本与发布流程、CI/CD、Changelog 生成、跨平台与多 Registry 发布(crates.io / npm / GitLab)。 +完整贡献与开发指南已合并进 `DEVELOPMENT.md` 中的 “贡献” 章节。 -## 快速开始 +请前往查看:`DEVELOPMENT.md#贡献` -1. 克隆仓库并进入目录: - ```bash - git clone git@github.com:MasonChow/source-map-parser.git - cd source-map-parser - ``` -2. 安装 Rust stable(推荐使用 rustup,需 wasm 目标): - ```bash - rustup target add wasm32-unknown-unknown - ``` -3. 运行全部测试(本地): - ```bash - cargo test --workspace --exclude source_map_parser_node --all-features - wasm-pack test --node crates/node_sdk - ``` -4. (可选)启用缓存 / 构建加速:配置 `sccache` 并导出 `RUSTC_WRAPPER=sccache`。 - -## 代码结构 - -- `crates/source_map_parser`:核心逻辑 (stack 解析 / token 映射 / context snippet / error stack 映射)。 -- `crates/node_sdk`:WASM 导出 (Node 目标),通过 `wasm-bindgen` 提供 JS 可调用接口。 -- `scripts/`:自动化脚本(例如 `generate-changelog.sh`)。 -- `.github/workflows/`:CI / Release Pipelines。 - -## 开发约定 - -- 采用 Rust 2021 edition;保持 `clippy` clean(后续可加入强制检查)。 -- 测试命名:`*_test.rs` 或内联 `mod tests { ... }`;避免跨模块耦合。 -- 新功能需至少包含: - - 单元测试覆盖核心逻辑 - - 如影响 WASM API,补充 `node_sdk` 侧 wasm-bindgen 测试 -- 提交信息推荐遵循 Conventional Commits: - - `feat(scope): 描述` - - `fix(scope): 描述` - - `refactor: ...` / `perf: ...` / `docs: ...` / `test: ...` / `chore: ...` - - 破坏性变更:`feat!: ...` 或正文含 `BREAKING CHANGE:` - -## Changelog 生成逻辑 - -脚本:`scripts/generate-changelog.sh [repo_url]` - -- 自动检测最近 tag 与当前 HEAD 的提交区间 -- 解析 Conventional Commit type(scope)!: 描述 -- 输出分类 (Features / Fixes / Performance / Refactors / Docs / Tests / Build / CI / Style / Chore / Other / Breaking Changes) -- 自动生成 compare 链接(GitHub/GitLab) -- 提交哈希转为 commit 链接 - -### Mermaid:Changelog 生成流程 - -```mermaid -digraph G { - rankdir=LR; - A[读取最新 tag] --> B[git log 范围] - B --> C[逐行解析 Conventional Commit] - C --> D{匹配 type(scope)!} - D --> E[分类聚合] - D --> F[检测 BREAKING] - E --> G[生成 Compare 链接] - F --> H[Breaking Section] - G --> I[写入新版本 Heading] - H --> I - I --> J[合并旧 CHANGELOG] -} -``` - -## 版本与 Tag 策略 - -- 手动 bump 两个 crate (`crates/source_map_parser` 与 `crates/node_sdk`) 版本号保持一致。 -- `node_sdk` 中对核心 crate 需显式 `version = "x.y.z"`,以便 crates.io 发布。 -- 创建 tag:`vX.Y.Z`;CI 中将校验 tag 与 crate versions 一致。 -- 建议遵循 SemVer: - - MINOR:新增功能向后兼容 - - PATCH:修复缺陷 - - MAJOR 或 feat!: 破坏性变更 - -## 发布流水线概览 - -触发:推送 `v*` tag。 -包含 Job:版本校验测试 → 发布 crates.io → 发布 npm (wasm-pack) → 发布 GitLab Generic Packages & GitLab npm → GitHub Release。 - -### Mermaid:Release Pipeline - -```mermaid -flowchart TB - start([Push tag vX.Y.Z]) --> verify[verify-and-test]\n校验版本+测试+生成 CHANGELOG - verify --> crates[publish-crates]\n cargo publish - verify --> npmPub[publish-npm]\n wasm-pack + npm publish - verify --> gitlab[publish-gitlab]\n generic + npm registry - crates --> release[github-release]\n读取 changelog - npmPub --> release - gitlab --> release - release --> done([Release 完成]) -``` - -### GitLab 包与 npm Registry 发布 - -Job: `publish-gitlab` - -- 生成 artifacts:两个 crate 的 `.crate` 打包 + wasm 打包 `tgz` -- 上传到 Generic Packages:`/packages/generic/source-map-parser//...` -- 若配置 GitLab npm:生成 `.npmrc` 并按需注入 scope 后 `npm publish` - -### Mermaid:GitLab npm 发布 - -```mermaid -sequenceDiagram - participant J as Job - participant S as Secrets - participant R as GitLab Registry - J->>S: 读取 GITLAB_TOKEN / PROJECT_ID / PROJECT_PATH - J->>J: wasm-pack build pkg - J->>J: 生成 .npmrc 指向 project npm registry - J->>J: scope 处理 (若包名无 @scope/ 前缀) - J->>R: npm publish - R-->>J: 201 Created / Already exists -``` - -## 必要 Secrets (GitHub Actions) - -| 名称 | 用途 | -| -------------------- | ---------------------------------- | -| CARGO_REGISTRY_TOKEN | 发布到 crates.io | -| NPM_TOKEN | 发布到 npm registry (官方) | -| GITLAB_TOKEN | 上传 Generic Packages / GitLab npm | -| GITLAB_PROJECT_ID | GitLab 项目 numeric id | -| GITLAB_PROJECT_PATH | GitLab 项目完整 path (用于 scope) | - -## 本地发布前检查清单 - -- [ ] 所有测试通过 (`cargo test`, `wasm-pack test --node`) -- [ ] Changelog 已根据提交适当书写(可运行脚本预览) -- [ ] crate 版本同步且未与已发布版本冲突 -- [ ] 提交消息符合规范(尤其是 Breaking Changes) -- [ ] README 与文档更新(如 API 变更) - -## 常见问题 (FAQ) - -1. Q: 发布时报 path dependency 错误? - A: 确认 `node_sdk` 中 `source_map_parser` 依赖包含 `version = "x.y.z"`。 -2. Q: npm 包名需要自定义? - A: 修改 wasm 构建产物前生成的 `pkg/package.json` 或在 GitLab job 中跳过重写逻辑。 -3. Q: 没有触发 Release? - A: 确认推送的是轻量 tag `vX.Y.Z` 且在默认远程 (origin) 上。 -4. Q: GitLab npm 发布失败? - A: 检查 `GITLAB_PROJECT_PATH` 与 Token 权限 (write_package_registry)。 - -## 后续增强建议 - -- 引入 `cargo-deny` / `clippy` as CI gates -- 自动版本号 bump + 变更文件回写 (Release PR 模式) -- 多平台编译验证 (aarch64, windows) + sccache -- CHANGELOG 添加 commit diff 链接到每条目 (目前仅哈希链接) - ---- - -欢迎通过 Issue / PR 提交改进建议。🎉 +(本文件保留做历史兼容,可在后续版本删除。) diff --git a/README.md b/README.md index 845deed..89d5b2d 100644 --- a/README.md +++ b/README.md @@ -47,29 +47,7 @@ fn main() { - SourceMapParserClient::map_stack_trace: 多行堆栈批量映射 - SourceMapParserClient::map_error_stack: 带首行错误消息的整块错误堆栈映射,可选上下文 -## 迁移指引 (从旧版 js-stack-parser) - -| 旧接口 | 新方式 | -| ----------------------------------------------------------- | ----------------------------------------- | -| generate_source_map_token(source_map_content, line, column) | SourceMapParserClient::new + lookup_token | -| 逐行 regex 手工解析 | parse_stack_trace / parse_stack_line | -| 自行解析 sourcesContent | SourceMapParserClient::unpack_all_sources | - -旧接口仍保留 (如 `token_generator::generate_source_map_token`) 以便平滑迁移。 - -## 设计原则 - -1. 纯计算,无网络 / 磁盘 I/O -2. 失败可恢复:无法解析的堆栈行直接跳过 -3. 面向多端封装:Rust Facade 保持稳定 API -4. 性能优先:最小复制,延迟解析 - -## 后续计划 - -- SourceMap 缓存层 (LRU) -- 上下文代码可配置提取 API -- Node / WASM 新 Facade 封装 -- 性能基准 & 监测脚本 +> 开发 / 构建 / 测试 / Roadmap 请见: [DEVELOPMENT.md](./DEVELOPMENT.md) ## 通用上下文查询示例 @@ -86,118 +64,23 @@ fn main() { } ``` -## WASM (Node) 导出快速使用 - -生成后的 Node 包 (`crates/node_sdk/pkg`) 暴露以下函数: - -| 函数 | 说明 | -| ------------------------------------------------------------ | -------------------------------------- | -| `lookup_token(sm, line, column)` | 获取原始行列 Token | -| `lookup_token_with_context(sm, line, column, context_lines)` | 获取带上下文 Token | -| `lookup_context(sm, line, column, context_lines)` | 仅获取上下文片段 (ContextSnippet) | -| `map_stack_line(sm, stack_line)` | 单行堆栈 -> Token | -| `map_stack_line_with_context(sm, stack_line, context_lines)` | 单行堆栈 -> 带上下文 Token | -| `map_stack_trace(sm, stack_trace)` | 多行堆栈批量映射 (不含首行错误消息) | -| `map_error_stack(sm, error_stack_raw, context_lines?)` | 整段错误堆栈 (含首行) 映射,可选上下文 | - -Node 端示例: - -```bash -node - <<'EOF' -const m = require('./crates/node_sdk/pkg'); -const sm = JSON.stringify({version:3,sources:['a.js'],sourcesContent:['fn()\n'],names:[],mappings:'AAAA'}); -console.log(JSON.parse(m.lookup_token(sm,1,0))); -EOF -``` - -## 开发 - -```bash -cargo test -``` - -### WASM 测试 (Node) - -使用 `wasm-pack test --node` 运行 `crates/node_sdk` 下的绑定测试: - -```bash -wasm-pack test --node crates/node_sdk -``` - -(不覆盖浏览器环境;如未来需要再扩展) - -CI 已提供 GitHub Actions 工作流 `.github/workflows/ci.yml`,覆盖: +## Node / WASM 使用 -1. Rust 原生单元/集成测试 -2. Node 环境下 WASM 测试 (wasm-pack test --node) +Node (WASM 绑定) 的全部 API、构建、测试、发布指引已迁移至 `crates/node_sdk/README.md`: -调试提示: +- 参见: [Node / WASM 使用说明](./crates/node_sdk/README.md) -- 确认已安装目标:`rustup target add wasm32-unknown-unknown` -- 避免直接 `cargo test --target wasm32-unknown-unknown`(缺少 runner 会尝试执行 .wasm 导致 126 错误) -- 添加新测试需使用 `#[wasm_bindgen_test]` 标注函数 +根 README 仅保留 Rust 与整体介绍,避免重复维护。 更多多端使用方式: -- [web/deno 使用方式](./crates/web_pkg/README.md) +- [web / deno 使用方式](./crates/web_pkg/README.md) (占位,如存在) - [rust 使用方式](./crates/source_map_parser/README.md) -## 构建部署 - -本地或 CI 均可构建(标准 Rust + wasm-pack 工具链)。 - -- 安装 Rust 工具链:推荐使用 [rustup](https://rustup.rs/)。 -- 安装 wasm-pack:参考官方安装指引 [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/)。 - -### 构建 - -```bash -bash build.sh -``` - -### WASM 构建 (Node 目标) - -当前仓库的 WASM 导出 crate 位于 `crates/node_sdk`,提供面向 Node.js (CommonJS) 的 API。根目录是一个 Cargo workspace(无 `[package]`),因此直接在根执行 `wasm-pack build` 会出现: - -``` -TOML parse error at line 1, column 1 -missing field `package` -``` - -请进入具体 crate 或使用提供的脚本: - -```bash -# 方式 1: 进入 crate 手动构建 -cd crates/node_sdk -wasm-pack build --target nodejs --release - -# 方式 2: 在仓库根使用脚本 -bash scripts/build-wasm-node.sh -``` - -构建输出目录:`crates/node_sdk/pkg`,包含 `package.json`, `.wasm`, 以及绑定 JS 文件,可直接 `require()` 使用。 - -快速 Node 端调用示例(假设在仓库根运行): - -```bash -node - <<'EOF' -const m = require('./crates/node_sdk/pkg'); -const sm = JSON.stringify({version:3,sources:['a.js'],sourcesContent:['fn()\n'],names:[],mappings:'AAAA'}); -console.log(JSON.parse(m.lookup_token(sm,1,0))); -EOF -``` - -(不提供浏览器 / ESM 目标,本分支仅关注 Node 使用) +--- -## TODO / Roadmap (扩展) +### 更多文档 -- [ ] Node 原生 N-API 封装 (基于 napi-rs),提供更低调用开销 & Zero-Copy Buffer 传递 - - [ ] 新建 crate: `crates/node_napi` (napi-rs + feature gate) - - [ ] 暴露与 WASM 一致的高层 API (`lookup_token`, `map_error_stack` 等) - - [ ] Benchmark: N-API vs WASM (cold/warm 多次调用) - - [ ] 添加 TypeScript 声明 / 自动生成 d.ts -- [ ] SourceMap 缓存 LRU (可选容量 & 命中统计) -- [ ] CLI: `sourcemap-lookup` 支持批量堆栈文件解析 -- [ ] Web 目标 (独立 `web_sdk` crate) 支持 ESM + Tree-shaking -- [ ] 性能基准脚本 (criterion / Node benchmark) 与 README 指标展示 -- [ ] 发布流程脚本(版本号同步、npm publish、Git tag) +- 开发/构建/Roadmap: [DEVELOPMENT.md](./DEVELOPMENT.md) +- Node / WASM 使用: [crates/node_sdk/README.md](./crates/node_sdk/README.md) +- Rust crate 细节: [crates/source_map_parser/README.md](./crates/source_map_parser/README.md) diff --git a/crates/node_sdk/Cargo.toml b/crates/node_sdk/Cargo.toml index a617290..5cfc047 100644 --- a/crates/node_sdk/Cargo.toml +++ b/crates/node_sdk/Cargo.toml @@ -14,7 +14,7 @@ serde_json = "1.0" wasm-bindgen = "0.2.89" serde-wasm-bindgen = "0.6.5" js-sys = "0.3" -source_map_parser = { path = "../source_map_parser", version = "0.1.0" } +source_map_parser = { path = "../source_map_parser" } [lib] crate-type = ["cdylib", "rlib"] diff --git a/crates/node_sdk/README.md b/crates/node_sdk/README.md new file mode 100644 index 0000000..b5cb5ea --- /dev/null +++ b/crates/node_sdk/README.md @@ -0,0 +1,46 @@ +# node_sdk (WASM / Node 使用指南) + +基于 `wasm-bindgen` 导出的 WASM + JS 绑定,提供在 Node.js 环境下对 `source_map_parser` 的高层 API 访问。 + +核心能力: + +- 映射编译后行列到原始源码 (`lookup_token` / `lookup_token_with_context`) +- 多格式堆栈解析 + 批量映射 (`map_stack_line*`, `map_stack_trace`, `map_error_stack`) +- 解包 SourceMap 中全部内嵌源码 (`lookup_context`) + +## 快速开始 + +```bash +npm install source-map-parser-wasm # 如已发布,可直接安装;未发布请使用本地路径 +# 或者使用本仓库本地构建产物 +``` + +本仓库构建后产物位于 `pkg/` 目录,可直接 `require()`: + +```js +const m = require('./pkg'); +const sm = JSON.stringify({ + version: 3, + sources: ['a.js'], + sourcesContent: ['fn()\n'], + names: [], + mappings: 'AAAA', +}); +console.log(JSON.parse(m.lookup_token(sm, 1, 0))); +``` + +## 导出 API + +| 函数 | 说明 | +| ------------------------------------------------------------ | -------------------------------------- | +| `lookup_token(sm, line, column)` | 获取原始行列 Token | +| `lookup_token_with_context(sm, line, column, context_lines)` | 获取带上下文 Token | +| `lookup_context(sm, line, column, context_lines)` | 仅获取上下文片段 (ContextSnippet) | +| `map_stack_line(sm, stack_line)` | 单行堆栈 -> Token | +| `map_stack_line_with_context(sm, stack_line, context_lines)` | 单行堆栈 -> 带上下文 Token | +| `map_stack_trace(sm, stack_trace)` | 多行堆栈批量映射 (不含首行错误消息) | +| `map_error_stack(sm, error_stack_raw, context_lines?)` | 整段错误堆栈 (含首行) 映射,可选上下文 | + +--- + +更多开发 / 构建 / 测试 / 发布 / Roadmap 内容: 参见仓库根 `DEVELOPMENT.md`。 diff --git a/crates/source_map_parser/README.md b/crates/source_map_parser/README.md index caa6e6a..d6379d2 100644 --- a/crates/source_map_parser/README.md +++ b/crates/source_map_parser/README.md @@ -1,4 +1,4 @@ -# source_map_parser (原 js_stack_parser 核心能力迁移) +# source_map_parser 通用 Source Map 解析与堆栈源码还原核心库。提供多引擎(V8 / Firefox / Safari)堆栈解析、行列映射、源码上下文提取、整段错误堆栈批量还原等能力。 @@ -49,22 +49,6 @@ fn main() { } ``` -## 升级说明 - -旧 crate 名称 `js_stack_parser` 已被替换;核心 API 在新 crate 下保持一致,原始低层函数仍可在迁移期保留(如需)但建议使用 `SourceMapParserClient` 高层封装。 - -## 设计原则 - -- 纯计算:不做文件/网络 I/O -- 明确分层:解析 / 定位 / 上下文 分离 -- 稳定 API:Facade 封装便于多端 (WASM / Node) 绑定 - -## 计划 - -- LRU SourceMap 缓存 -- bench 性能基准 -- 失败行诊断信息增强 - --- -欢迎 issue / PR 进一步完善。 +更多:开发与 Roadmap 查看仓库根 `DEVELOPMENT.md`。