Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: Release

on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to package, for example v0.4.0'
required: true
type: string

permissions:
contents: write

jobs:
package:
name: Package ${{ matrix.os }} / ${{ matrix.arch }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux
arch: x64
- os: macos-latest
platform: macos
arch: arm64
- os: windows-latest
platform: windows
arch: x64
runs-on: ${{ matrix.os }}
env:
WASM_PACK_VERSION: 0.12.1
PACKAGE_NAME: source_map_parser_node
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Set up Rust
shell: bash
run: |
rustup toolchain install stable --profile minimal
rustup default stable
rustup target add wasm32-unknown-unknown
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install wasm-pack
shell: bash
run: |
if ! command -v wasm-pack >/dev/null 2>&1; then
cargo install wasm-pack --version "$WASM_PACK_VERSION" --locked
fi
- name: Install Node dependencies
working-directory: crates/node_sdk
run: pnpm install --no-frozen-lockfile
- name: Run Rust tests
run: cargo test --workspace --exclude source_map_parser_node --all-features
- name: Run WASM tests
run: wasm-pack test --node crates/node_sdk
- name: Build Node/WASM package
working-directory: crates/node_sdk
run: pnpm run build
- name: Run Node SDK tests
working-directory: crates/node_sdk
run: pnpm test
- name: Stage release package
shell: bash
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}"
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then TAG="${{ github.event.inputs.tag }}"; fi
STAGE="release/${PACKAGE_NAME}-${{ matrix.platform }}-${{ matrix.arch }}"
mkdir -p "$STAGE/bin" "$STAGE/dist" "$STAGE/skills"
cp -R crates/node_sdk/dist/. "$STAGE/dist/"
cp -R crates/node_sdk/bin/. "$STAGE/bin/"
cp -R crates/node_sdk/skills/. "$STAGE/skills/"
cp crates/node_sdk/README.md "$STAGE/README.md"
cp LICENSE "$STAGE/LICENSE"
cp crates/node_sdk/package.json "$STAGE/package.json"
printf '%s\n' "$TAG" > "$STAGE/VERSION"
ARCHIVE="release/${PACKAGE_NAME}-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz"
tar -czf "$ARCHIVE" -C release "${PACKAGE_NAME}-${{ matrix.platform }}-${{ matrix.arch }}"
if command -v sha256sum >/dev/null 2>&1; then
(cd release && sha256sum "$(basename "$ARCHIVE")" > "$(basename "$ARCHIVE").sha256")
else
shasum -a 256 "$ARCHIVE" | sed "s#release/##" > "$ARCHIVE.sha256"
fi
- name: Upload package artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.PACKAGE_NAME }}-${{ matrix.platform }}-${{ matrix.arch }}
path: |
release/${{ env.PACKAGE_NAME }}-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz
release/${{ env.PACKAGE_NAME }}-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz.sha256

publish-github-release:
needs: package
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: release-artifacts
merge-multiple: true
- name: Publish GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
files: |
release-artifacts/*.tar.gz
release-artifacts/*.sha256
generate_release_notes: true
34 changes: 34 additions & 0 deletions crates/node_sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,37 @@ pnpm test # 运行 vitest 测试
## 许可证

MIT License

## CLI 安装与更新

从 npm 全局安装后会提供 `source-map-parser` 命令,并内置跨平台更新命令:

```bash
npm install -g source_map_parser_node
source-map-parser update
# 安装指定版本
source-map-parser update --version 0.4.0
```

如果你不想依赖 npm,也可以在 Linux/macOS 或带 bash 的 Windows 环境中使用随 Node 包一起分发的 GitHub Release 安装脚本。脚本会自动识别当前系统与 CPU 架构,从 tag 对应的 Release 资产中下载匹配的包:

```bash
# 本地依赖安装后使用
bash ./node_modules/source_map_parser_node/bin/install.sh

# 全局 npm 安装后使用
bash "$(npm root -g)/source_map_parser_node/bin/install.sh" --version v0.4.0
```

CLI 也可以通过 GitHub Release 方式自更新:

```bash
source-map-parser update --from github
source-map-parser update --from github --version v0.4.0
```

> Windows 用户推荐使用 npm 更新;GitHub 安装脚本需要 Git Bash、MSYS2 或 WSL 等 bash 环境。

## Tag Release 流程

推送 `v*.*.*` tag 后,GitHub Actions 会在 Linux、macOS、Windows 三类环境中构建并测试 Node/WASM 包,然后把按平台命名的 `source_map_parser_node-<platform>-<arch>.tar.gz` 上传到对应 GitHub Release。Release 包和 npm 包都会携带同一份 `bin/install.sh`,用户可以通过该脚本或 `source-map-parser update --from github` 下载使用。
101 changes: 101 additions & 0 deletions crates/node_sdk/bin/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
set -euo pipefail

REPO="MasonChow/source-map-parser"
PACKAGE_NAME="source_map_parser_node"
VERSION="latest"
INSTALL_DIR="${SOURCE_MAP_PARSER_INSTALL_DIR:-$HOME/.source-map-parser}"
BIN_DIR="${SOURCE_MAP_PARSER_BIN_DIR:-$HOME/.local/bin}"

usage() {
cat <<USAGE
Install source_map_parser_node from GitHub release artifacts.

Usage:
bash ./node_modules/source_map_parser_node/bin/install.sh
bash ./node_modules/source_map_parser_node/bin/install.sh --version v0.4.0 --install-dir ~/.source-map-parser --bin-dir ~/.local/bin

Options:
--version <tag|latest> Release tag to install. Defaults to latest.
--install-dir <dir> Package installation directory. Defaults to ~/.source-map-parser.
--bin-dir <dir> Directory for the source-map-parser shim. Defaults to ~/.local/bin.
-h, --help Show this help.
USAGE
}

while [[ $# -gt 0 ]]; do
case "$1" in
--version) VERSION="${2:?missing value for --version}"; shift 2 ;;
--install-dir) INSTALL_DIR="${2:?missing value for --install-dir}"; shift 2 ;;
--bin-dir) BIN_DIR="${2:?missing value for --bin-dir}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&2; usage; exit 1 ;;
esac
done

need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }; }
need curl
need tar
need node

case "$(uname -s)" in
Linux*) OS="linux" ;;
Darwin*) OS="macos" ;;
MINGW*|MSYS*|CYGWIN*) OS="windows" ;;
*) echo "Unsupported OS: $(uname -s)" >&2; exit 1 ;;
esac
case "$(uname -m)" in
x86_64|amd64) ARCH="x64" ;;
arm64|aarch64) ARCH="arm64" ;;
*) ARCH="$(uname -m)" ;;
esac

if [[ "$VERSION" == "latest" ]]; then
API_URL="https://api.github.com/repos/${REPO}/releases/latest"
else
API_URL="https://api.github.com/repos/${REPO}/releases/tags/${VERSION}"
fi

ASSET="${PACKAGE_NAME}-${OS}-${ARCH}.tar.gz"
DOWNLOAD_URL="$(curl -fsSL "$API_URL" | ASSET="$ASSET" node -e '
let data = "";
process.stdin.on("data", (chunk) => { data += chunk; });
process.stdin.on("end", () => {
const release = JSON.parse(data);
const asset = (release.assets || []).find((item) => item.name === process.env.ASSET);
if (!asset) process.exit(2);
console.log(asset.browser_download_url);
});
')" || {
echo "Could not find release asset ${ASSET}." >&2
exit 1
}
CHECKSUM_URL="${DOWNLOAD_URL}.sha256"

TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
curl -fL "$DOWNLOAD_URL" -o "$TMP_DIR/$ASSET"
if curl -fsL "$CHECKSUM_URL" -o "$TMP_DIR/$ASSET.sha256"; then
if command -v sha256sum >/dev/null 2>&1; then
(cd "$TMP_DIR" && sha256sum -c "$ASSET.sha256")
else
(cd "$TMP_DIR" && shasum -a 256 -c "$ASSET.sha256")
fi
else
echo "Checksum file not found; continuing without checksum verification." >&2
fi
mkdir -p "$INSTALL_DIR" "$BIN_DIR"
tar -xzf "$TMP_DIR/$ASSET" -C "$INSTALL_DIR" --strip-components=1
cat > "$BIN_DIR/source-map-parser" <<SHIM
#!/usr/bin/env bash
exec node "$INSTALL_DIR/bin/source-map-parser.mjs" "\$@"
SHIM
chmod +x "$BIN_DIR/source-map-parser"
if [[ "$OS" == "windows" ]]; then
cat > "$BIN_DIR/source-map-parser.cmd" <<SHIM
@echo off
node "$INSTALL_DIR/bin/source-map-parser.mjs" %*
SHIM
fi
echo "Installed source-map-parser to $BIN_DIR/source-map-parser"
echo "If needed, add $BIN_DIR to PATH."
85 changes: 85 additions & 0 deletions crates/node_sdk/bin/source-map-parser.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import process from 'node:process';

const PACKAGE_NAME = 'source_map_parser_node';
const INSTALL_SCRIPT_PATH = fileURLToPath(new URL('./install.sh', import.meta.url));

function printHelp() {
console.log(`source-map-parser CLI

Usage:
source-map-parser update [--version <version>] [--from npm|github]
source-map-parser --help

Commands:
update Update the global source_map_parser_node CLI/package.

Options:
--version <version> Install a specific npm version or GitHub tag. Defaults to latest.
--from <npm|github> Update through npm or the GitHub install script. Defaults to npm.
`);
}

function readOptionValue(argv, index, optionName) {
const value = argv[index + 1];
if (!value || value.startsWith('--')) {
throw new Error(`Missing value for ${optionName}`);
}
return value;
}

function parseArgs(argv) {
const args = { command: argv[2], version: 'latest', from: 'npm', help: false };
for (let i = 3; i < argv.length; i += 1) {
const value = argv[i];
if (value === '--version') {
args.version = readOptionValue(argv, i, '--version');
i += 1;
} else if (value === '--from') {
args.from = readOptionValue(argv, i, '--from');
i += 1;
} else if (value === '--help' || value === '-h') {
args.help = true;
} else {
throw new Error(`Unknown argument: ${value}`);
}
}
return args;
}

function run(command, args, options = {}) {
const result = spawnSync(command, args, { stdio: 'inherit', ...options });
if (result.error) throw result.error;
if (result.status !== 0) process.exit(result.status ?? 1);
}

function updateFromNpm(version) {
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const spec = version === 'latest' ? PACKAGE_NAME : `${PACKAGE_NAME}@${version}`;
run(npmCommand, ['install', '--global', spec]);
}

function updateFromGitHub(version) {
if (process.platform === 'win32') {
console.error('GitHub install script update requires bash. Use: source-map-parser update --from npm');
process.exit(1);
}
run('bash', [INSTALL_SCRIPT_PATH, '--version', version]);
}

try {
const args = parseArgs(process.argv);
if (!args.command || args.help || args.command === '--help' || args.command === '-h') {
printHelp();
process.exit(0);
}
if (args.command !== 'update') throw new Error(`Unknown command: ${args.command}`);
if (!['npm', 'github'].includes(args.from)) throw new Error('--from must be npm or github');
if (args.from === 'github') updateFromGitHub(args.version);
else updateFromNpm(args.version);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
6 changes: 5 additions & 1 deletion crates/node_sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"type": "module",
"files": [
"dist/",
"bin/",
"skills/",
"README.md",
"LICENSE"
Expand Down Expand Up @@ -36,5 +37,8 @@
"vite-plugin-top-level-await": "^1.6.0",
"vite-plugin-wasm": "^3.5.0",
"vitest": "^2.0.5"
},
"bin": {
"source-map-parser": "./bin/source-map-parser.mjs"
}
}
}
Loading