From 6911ef015c43814d79ce505e39597fec2e82728a Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Sun, 20 Sep 2026 14:42:53 +0000
Subject: [PATCH 1/2] =?UTF-8?q?feat(v1.16.0):=20=E6=8C=89=E4=BE=9D?=
=?UTF-8?q?=E8=B5=96=E9=A1=BA=E5=BA=8F=E5=B0=86=20workspace=20crate=20?=
=?UTF-8?q?=E5=8F=91=E5=B8=83=E5=88=B0=20crates.io?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
为 amber_transpile / amber_sandbox 补齐 path+version 与 crates.io 元数据,
收紧并修正 amberjs 的 include 白名单,并在 Release Assets 中按依赖顺序
幂等发布(已存在版本跳过)。
Co-authored-by: Henry Zhang
---
.github/workflows/release-assets.yml | 47 ++++-
Cargo.toml | 52 ++----
README.md | 7 +
apps/website/src/docs/installation.md | 8 +
apps/website/src/docs/installation.zh.md | 8 +
crates/amber_sandbox/Cargo.toml | 7 +
crates/amber_sandbox/README.md | 9 +
crates/amber_transpile/Cargo.toml | 7 +
crates/amber_transpile/README.md | 9 +
docs/CONTRIBUTING.md | 2 +
docs/GITHUB_ACTIONS_RUNTIME_INFRASTRUCTURE.md | 4 +-
tests/release_workflow_tests.rs | 164 ++++++++++++++++++
12 files changed, 278 insertions(+), 46 deletions(-)
create mode 100644 crates/amber_sandbox/README.md
create mode 100644 crates/amber_transpile/README.md
diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml
index 51fb5423..1a9e93ce 100644
--- a/.github/workflows/release-assets.yml
+++ b/.github/workflows/release-assets.yml
@@ -144,7 +144,7 @@ jobs:
name: GitHub Release
if: ${{ always() && !cancelled() && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') }}
runs-on: ubuntu-latest
- timeout-minutes: 15
+ timeout-minutes: 60
needs: build-assets
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
@@ -267,6 +267,49 @@ jobs:
if: env.CARGO_REGISTRY_TOKEN != ''
uses: dtolnay/rust-toolchain@1.97.1
+ - name: Cache cargo registry
+ if: env.CARGO_REGISTRY_TOKEN != ''
+ uses: Swatinem/rust-cache@v2
+ with:
+ shared-key: crates-io-publish
+ cache-targets: false
+ cache-on-failure: true
+
+ # Members first (path+version deps), then root. Re-runs on workflow_dispatch
+ # treat "already uploaded" as success. A publish failure after GitHub
+ # Release is intentional so the missing crates.io crate is visible.
- name: Publish to crates.io
if: env.CARGO_REGISTRY_TOKEN != ''
- run: cargo publish --locked --token "$CARGO_REGISTRY_TOKEN"
+ run: |
+ set -euo pipefail
+ publish_one() {
+ local pkg="$1"
+ local attempt
+ for attempt in 1 2 3 4 5; do
+ local log
+ log="$(mktemp)"
+ set +e
+ cargo publish -p "$pkg" --locked --token "$CARGO_REGISTRY_TOKEN" 2>&1 | tee "$log"
+ local status="${PIPESTATUS[0]}"
+ set -e
+ if [ "$status" -eq 0 ]; then
+ return 0
+ fi
+ if grep -qiE 'already exists|already uploaded' "$log"; then
+ echo "skip $pkg (already on crates.io)"
+ return 0
+ fi
+ if grep -qiE 'no matching package named|not found in the registry|try again later|timed out|timeout|503|502|429' "$log"; then
+ echo "retry $pkg after index/network delay (attempt ${attempt})"
+ sleep $((attempt * 15))
+ continue
+ fi
+ echo "::error::cargo publish -p $pkg failed"
+ return 1
+ done
+ echo "::error::cargo publish -p $pkg failed after retries"
+ return 1
+ }
+ publish_one amber_transpile
+ publish_one amber_sandbox
+ publish_one amberjs
diff --git a/Cargo.toml b/Cargo.toml
index 22d916bf..03987d77 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,51 +13,19 @@ homepage = "https://amberjs.com"
documentation = "https://docs.rs/amberjs"
keywords = ["javascript", "typescript", "runtime", "v8"]
categories = ["command-line-utilities", "development-tools"]
+# Whitelist: enough to build the default `amber` binary after `cargo publish`,
+# without shipping target/, node_modules/, apps/, or the 46MB benchmarks/ tree.
+# `/src/**` is required — lib.rs compiles many modules the old file-by-file
+# list omitted, and include_str! also needs src/web_api/*.js plus types/.
include = [
"/Cargo.toml",
"/Cargo.lock",
"/README.md",
"/LICENSE",
"/build.rs",
- "/src/lib.rs",
- "/src/napi/*.rs",
- "/src/main.rs",
- "/src/event_loop.rs",
- "/src/runtime_minimal.rs",
- "/src/package_manager.rs",
- "/src/watcher.rs",
- "/src/watcher_websocket.rs",
- "/src/ecosystem_lite.rs",
- "/src/performance_analyzer.rs",
- "/src/performance_comparison.rs",
- "/src/performance_regression.rs",
- "/src/performance_reporter.rs",
- "/src/ai/*.rs",
- "/src/ai_inference/*.rs",
- "/src/aiops/**/*.rs",
- "/src/benchmarks/*.rs",
- "/src/cloud_native/**/*.rs",
- "/src/error/*.rs",
- "/src/fallback/*.rs",
- "/src/memory/*.rs",
- "/src/monitor/**/*.rs",
- "/src/multilang/*.rs",
- "/src/nodejs_core/*.rs",
- "/src/observability/**/*.rs",
- "/src/platform/*.rs",
- "/src/security/*.rs",
- "/src/testing/**/*.rs",
- "/src/typescript/*.rs",
- "/src/v8_snapshot/*.rs",
- "/src/web_api/*.rs",
- "/types/*.d.ts",
- "/tests/*.rs",
- "/tests/integration/*.rs",
- "/examples/basics/*.js",
- "/examples/basics/*.ts",
- "/examples/testing/*.js",
- "/docs/CLI_USAGE_GUIDE.md",
- "/docs/QUICK_START.md",
+ "/src/**",
+ "/types/**",
+ "/benches/runtime_startup.rs",
]
[workspace]
@@ -91,9 +59,9 @@ wasmtime-wasi = "38.0"
# WebAssembly bindings
wasm-bindgen = "0.2"
-# Workspace member crates
-amber_transpile = { path = "crates/amber_transpile" }
-amber_sandbox = { path = "crates/amber_sandbox" }
+# Workspace member crates (path for local builds; version for crates.io)
+amber_transpile = { path = "crates/amber_transpile", version = "1.16.0" }
+amber_sandbox = { path = "crates/amber_sandbox", version = "1.16.0" }
wasm-bindgen-futures = "0.4"
# WebAssembly binary format
diff --git a/README.md b/README.md
index fc3ba9c3..ca297be0 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@
+
@@ -74,6 +75,12 @@ Homebrew (formula in this repo; SHA256 is rewritten on each GitHub Release):
brew install zh30/tap/amber
```
+crates.io (each GitHub `v*` tag publishes via Release Assets + `CARGO_REGISTRY_TOKEN`; install the `amber` binary):
+
+```sh
+cargo install amberjs
+```
+
```sh
amber --version
amber eval "1 + 1"
diff --git a/apps/website/src/docs/installation.md b/apps/website/src/docs/installation.md
index 04c1c554..b0992301 100644
--- a/apps/website/src/docs/installation.md
+++ b/apps/website/src/docs/installation.md
@@ -28,6 +28,14 @@ Homebrew (formula in the Amber repo; tap hashes are filled after each GitHub Rel
brew install zh30/tap/amber
```
+From crates.io (GitHub `v*` tags publish `amber_transpile`, `amber_sandbox`, then `amberjs` via Release Assets + `CARGO_REGISTRY_TOKEN`):
+
+```bash
+cargo install amberjs
+```
+
+This installs the `amber` binary. Needs Rust **1.97.1** and a C++ toolchain for V8.
+
### What the Install Script Does
1. **Detects Environment**: Automatically identifies your OS (macOS / Linux) and CPU architecture (Apple Silicon `arm64` or Intel `x86_64`).
diff --git a/apps/website/src/docs/installation.zh.md b/apps/website/src/docs/installation.zh.md
index e60bc175..681faca6 100644
--- a/apps/website/src/docs/installation.zh.md
+++ b/apps/website/src/docs/installation.zh.md
@@ -28,6 +28,14 @@ Homebrew(配方在 Amber 仓库内;每次 GitHub Release 后填写 sha256)
brew install zh30/tap/amber
```
+crates.io(GitHub `v*` 标签通过 Release Assets + `CARGO_REGISTRY_TOKEN` 按 `amber_transpile` → `amber_sandbox` → `amberjs` 发布):
+
+```bash
+cargo install amberjs
+```
+
+会安装 `amber` 二进制。需要 Rust **1.97.1** 以及编译 V8 的 C++ 工具链。
+
### 安装脚本执行过程说明
1. **自动识别硬件与系统**:自动检测你的系统(macOS / Linux)与 CPU 架构(Apple Silicon `arm64`、Intel `x86_64`);
diff --git a/crates/amber_sandbox/Cargo.toml b/crates/amber_sandbox/Cargo.toml
index 0f2a819b..c195af97 100644
--- a/crates/amber_sandbox/Cargo.toml
+++ b/crates/amber_sandbox/Cargo.toml
@@ -2,8 +2,15 @@
name = "amber_sandbox"
version = "1.16.0"
edition = "2021"
+authors = ["Henry Zhang"]
description = "Capability sandbox and in-memory virtual filesystem for Amber runtime"
license = "MIT"
+readme = "README.md"
+repository = "https://github.com/zh30/amberjs"
+homepage = "https://amberjs.com"
+documentation = "https://docs.rs/amber_sandbox"
+keywords = ["sandbox", "vfs", "permissions"]
+categories = ["development-tools"]
[dependencies]
once_cell = "1.0"
diff --git a/crates/amber_sandbox/README.md b/crates/amber_sandbox/README.md
new file mode 100644
index 00000000..8d56bc85
--- /dev/null
+++ b/crates/amber_sandbox/README.md
@@ -0,0 +1,9 @@
+# amber_sandbox
+
+Capability sandbox and in-memory virtual filesystem used by the [Amber](https://github.com/zh30/amberjs) runtime (`--sandbox`, permission policy, deterministic VFS).
+
+Published as a workspace crate so `amberjs` can be released to crates.io. The CLI users want is:
+
+```sh
+cargo install amberjs
+```
diff --git a/crates/amber_transpile/Cargo.toml b/crates/amber_transpile/Cargo.toml
index 08d25d13..29e28a4e 100644
--- a/crates/amber_transpile/Cargo.toml
+++ b/crates/amber_transpile/Cargo.toml
@@ -2,8 +2,15 @@
name = "amber_transpile"
version = "1.16.0"
edition = "2021"
+authors = ["Henry Zhang"]
description = "Fast TypeScript transpile and bundling engine for Amber runtime"
license = "MIT"
+readme = "README.md"
+repository = "https://github.com/zh30/amberjs"
+homepage = "https://amberjs.com"
+documentation = "https://docs.rs/amber_transpile"
+keywords = ["javascript", "typescript", "transpile"]
+categories = ["development-tools"]
[dependencies]
oxc = { version = "0.147", default-features = false, features = [
diff --git a/crates/amber_transpile/README.md b/crates/amber_transpile/README.md
new file mode 100644
index 00000000..72b416f6
--- /dev/null
+++ b/crates/amber_transpile/README.md
@@ -0,0 +1,9 @@
+# amber_transpile
+
+TypeScript/TSX transpile engine for the [Amber](https://github.com/zh30/amberjs) runtime. It uses [oxc](https://oxc.rs/) and is transpile-only (no `tsc` typecheck).
+
+Published as a workspace crate so `amberjs` can be released to crates.io. The CLI users want is:
+
+```sh
+cargo install amberjs
+```
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index eddfdb37..6c4578b6 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -596,6 +596,8 @@ pub fn execute_string(&self, code: &str) -> Result {
- [ ] 更新 CHANGELOG.md
- [ ] 创建 GitHub Release
+3. **crates.io**: 打 `v*` 标签(或对已有标签跑 Release Assets 的 `workflow_dispatch`)。GitHub Release / Homebrew / cosign 完成后,同一 job 用仓库 secret `CARGO_REGISTRY_TOKEN` 按 `amber_transpile` → `amber_sandbox` → `amberjs` 发布。token 为空则跳过并打 warning。用户安装:`cargo install amberjs`(二进制名 `amber`)。
+
## 📞 获取帮助
如果您需要帮助,可以通过以下方式联系我们:
diff --git a/docs/GITHUB_ACTIONS_RUNTIME_INFRASTRUCTURE.md b/docs/GITHUB_ACTIONS_RUNTIME_INFRASTRUCTURE.md
index aa46edde..c8579f59 100644
--- a/docs/GITHUB_ACTIONS_RUNTIME_INFRASTRUCTURE.md
+++ b/docs/GITHUB_ACTIONS_RUNTIME_INFRASTRUCTURE.md
@@ -9,7 +9,7 @@ PR gates, multi-OS GitHub Releases (including Windows zip), GHCR publish, SBOM/c
| Workflow file | Trigger | What it actually does |
|---|---|---|
| `.github/workflows/ci.yml` | `push` `main`, PR → `main`, `workflow_dispatch`, `v*` | rustc **1.97.1**, fmt, clippy `-D warnings`, `cargo test`, feature matrix `benchmarks` + `observability` (fail-closed), WinterTC named step, Node conformance, `amber test examples/testing`, `cargo package --list`, ubuntu + macOS release smoke, Windows smoke, **cargo-audit fail-closed**, cargo-deny advisories/licenses |
-| `.github/workflows/release-assets.yml` | `v*` tags, `workflow_dispatch` | linux gnu x64/arm64, macOS arm64/x64, Windows x64 zip (**no** `continue-on-error` on Windows); requires Unix archives **and** Windows zip with `amber.exe`; checksums, CycloneDX SBOM, cosign; Homebrew formula SHA rewrite |
+| `.github/workflows/release-assets.yml` | `v*` tags, `workflow_dispatch` | linux gnu x64/arm64, macOS arm64/x64, Windows x64 zip (**no** `continue-on-error` on Windows); requires Unix archives **and** Windows zip with `amber.exe`; checksums, CycloneDX SBOM, cosign; Homebrew formula SHA rewrite; crates.io publish of `amber_transpile` → `amber_sandbox` → `amberjs` when `CARGO_REGISTRY_TOKEN` is set |
| `.github/workflows/docker.yml` | `main`, `v*`, `workflow_dispatch` | Build/push `ghcr.io/zh30/amberjs` **linux/amd64 only** (no fake `linux/arm64` tag) |
| `.github/workflows/performance-tests.yml` | Monday 02:00 UTC cron, `workflow_dispatch` | Release lib benchmark tests. Not a PR gate |
@@ -18,7 +18,7 @@ PR gates, multi-OS GitHub Releases (including Windows zip), GHCR publish, SBOM/c
- GHCR is **amd64-only**.
- `feature = "ai"` is empty and is not in the CI matrix; default `amber:ai` compiles without it.
- `enterprise` / `cloudnative` / `multilang` / `tch` are not CI-gated.
-- crates.io publish is skipped without `CARGO_REGISTRY_TOKEN`.
+- crates.io publish is skipped without `CARGO_REGISTRY_TOKEN` (warning annotation). With the token, Release Assets publishes workspace crates in dependency order; already-uploaded versions are skipped.
- Live tag, brew checksum against a published Release, and `microsoft/winget-pkgs` submission are out of band.
See [IMPLEMENTATION_PLAN_v1.9.1.md](./IMPLEMENTATION_PLAN_v1.9.1.md) and [INFRASTRUCTURE_CAPABILITY_BACKLOG.md](./INFRASTRUCTURE_CAPABILITY_BACKLOG.md).
diff --git a/tests/release_workflow_tests.rs b/tests/release_workflow_tests.rs
index e0dfa9ad..4d64dc86 100644
--- a/tests/release_workflow_tests.rs
+++ b/tests/release_workflow_tests.rs
@@ -83,6 +83,170 @@ fn tag_v_star_publishes_non_draft_release_with_five_amber_archives() {
);
}
+fn first_package_version(toml: &str) -> &str {
+ let package = toml
+ .split("[package]")
+ .nth(1)
+ .expect("[package] table")
+ .split('[')
+ .next()
+ .expect("package fields");
+ package
+ .lines()
+ .find_map(|line| {
+ let line = line.trim();
+ line.strip_prefix("version = \"")
+ .and_then(|rest| rest.strip_suffix('"'))
+ })
+ .expect("package version")
+}
+
+#[test]
+fn workspace_path_deps_have_crates_io_versions_matching_members() {
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ let root = fs::read_to_string(manifest.join("Cargo.toml")).unwrap();
+ let transpile = fs::read_to_string(manifest.join("crates/amber_transpile/Cargo.toml")).unwrap();
+ let sandbox = fs::read_to_string(manifest.join("crates/amber_sandbox/Cargo.toml")).unwrap();
+ let zed = fs::read_to_string(manifest.join("extensions/zed/Cargo.toml")).unwrap();
+
+ let root_ver = first_package_version(&root);
+ let transpile_ver = first_package_version(&transpile);
+ let sandbox_ver = first_package_version(&sandbox);
+ assert_eq!(
+ root_ver, transpile_ver,
+ "amber_transpile version must match root"
+ );
+ assert_eq!(
+ root_ver, sandbox_ver,
+ "amber_sandbox version must match root"
+ );
+ assert!(
+ root.contains(&format!(
+ "amber_transpile = {{ path = \"crates/amber_transpile\", version = \"{root_ver}\" }}"
+ )),
+ "root must depend on amber_transpile with path + version for cargo publish: {root}"
+ );
+ assert!(
+ root.contains(&format!(
+ "amber_sandbox = {{ path = \"crates/amber_sandbox\", version = \"{root_ver}\" }}"
+ )),
+ "root must depend on amber_sandbox with path + version for cargo publish: {root}"
+ );
+
+ for (name, toml) in [("amber_transpile", &transpile), ("amber_sandbox", &sandbox)] {
+ assert!(
+ toml.contains("license = \"MIT\""),
+ "{name} must declare MIT license"
+ );
+ assert!(
+ toml.contains("repository = \"https://github.com/zh30/amberjs\""),
+ "{name} must declare the GitHub repository for crates.io"
+ );
+ assert!(
+ toml.contains("description = \""),
+ "{name} must have a crates.io description"
+ );
+ assert!(
+ !toml.contains("publish = false"),
+ "{name} must be publishable"
+ );
+ }
+ assert!(
+ zed.contains("publish = false"),
+ "Zed extension must not be published to crates.io"
+ );
+}
+
+#[test]
+fn cargo_package_include_covers_src_and_types_not_huge_trees() {
+ let cargo =
+ fs::read_to_string(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml")).unwrap();
+ let include = cargo
+ .split("include = [")
+ .nth(1)
+ .and_then(|rest| rest.split(']').next())
+ .expect("include list");
+ assert!(
+ include.contains("/src/**"),
+ "include must ship the whole src tree so the published crate builds: {include}"
+ );
+ assert!(
+ include.contains("/types/**") || include.contains("/types/*.d.ts"),
+ "include must ship types/ for include_str!(amberjs.d.ts): {include}"
+ );
+ assert!(
+ include.contains("/build.rs") && include.contains("/Cargo.lock"),
+ "published package must include build.rs and Cargo.lock: {include}"
+ );
+ for banned in [
+ "node_modules",
+ "benchmarks/",
+ "/apps/",
+ "/target/",
+ "/docs/STAGE_",
+ ] {
+ assert!(
+ !include.contains(banned),
+ "include must not whitelist {banned}: {include}"
+ );
+ }
+}
+
+#[test]
+fn crates_io_publish_is_ordered_and_idempotent_after_github_release() {
+ let yaml = release_assets_yaml();
+ let release_at = yaml
+ .find("uses: softprops/action-gh-release")
+ .expect("GitHub Release step");
+ let publish_at = yaml
+ .find("Publish to crates.io")
+ .expect("crates.io publish step");
+ assert!(
+ publish_at > release_at,
+ "crates.io publish must run after action-gh-release"
+ );
+ let publish = &yaml[publish_at..];
+ let transpile = publish
+ .find("amber_transpile")
+ .expect("publish amber_transpile");
+ let sandbox = publish
+ .find("amber_sandbox")
+ .expect("publish amber_sandbox");
+ let root = publish
+ .find("publish_one amberjs")
+ .or_else(|| publish.find("-p amberjs"))
+ .expect("publish amberjs");
+ assert!(
+ transpile < sandbox && sandbox < root,
+ "publish order must be amber_transpile, amber_sandbox, amberjs"
+ );
+ assert!(
+ publish.contains("already exists") && publish.contains("already uploaded"),
+ "re-running a tag must skip versions already on crates.io"
+ );
+ assert!(
+ publish.contains("--locked") && publish.contains("--token"),
+ "cargo publish must use --locked and CARGO_REGISTRY_TOKEN"
+ );
+ assert!(
+ publish.contains("CARGO_REGISTRY_TOKEN"),
+ "publish must use the existing CARGO_REGISTRY_TOKEN secret"
+ );
+ assert!(
+ !publish.contains("zed-amberjs"),
+ "Zed extension crate must not be published"
+ );
+ let publish_step = yaml.split("Publish to crates.io").nth(1).unwrap_or("");
+ let publish_step = publish_step
+ .split("\n - name:")
+ .next()
+ .unwrap_or(publish_step);
+ assert!(
+ !publish_step.contains("continue-on-error"),
+ "crates.io publish must fail the job when the registry is down"
+ );
+}
+
#[test]
fn homebrew_formula_push_must_not_block_github_release() {
let yaml = release_assets_yaml();
From 3fcf8190b9acae7c8a8b5a9a7ee7ec61f7b299fb Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Sun, 20 Sep 2026 14:46:49 +0000
Subject: [PATCH 2/2] =?UTF-8?q?docs(v1.16.0):=20=E8=AF=B4=E6=98=8E=20amber?=
=?UTF-8?q?js=20dry-run=20=E4=BE=9D=E8=B5=96=20crates.io=20=E6=88=90?=
=?UTF-8?q?=E5=91=98=20crate?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
本地 cargo publish --dry-run -p amberjs 在 amber_transpile / amber_sandbox
尚未上架时无法解析 version 依赖;Release Assets 会先发布这两个成员。
Co-authored-by: Henry Zhang
---
Cargo.toml | 4 +++-
docs/CONTRIBUTING.md | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index 03987d77..00a967ab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -59,7 +59,9 @@ wasmtime-wasi = "38.0"
# WebAssembly bindings
wasm-bindgen = "0.2"
-# Workspace member crates (path for local builds; version for crates.io)
+# Workspace member crates (path for local builds; version for crates.io).
+# `cargo publish -p amberjs --dry-run` cannot resolve these until the same
+# versions exist on crates.io; Release Assets publishes the members first.
amber_transpile = { path = "crates/amber_transpile", version = "1.16.0" }
amber_sandbox = { path = "crates/amber_sandbox", version = "1.16.0" }
wasm-bindgen-futures = "0.4"
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 6c4578b6..7d083216 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -596,7 +596,7 @@ pub fn execute_string(&self, code: &str) -> Result {
- [ ] 更新 CHANGELOG.md
- [ ] 创建 GitHub Release
-3. **crates.io**: 打 `v*` 标签(或对已有标签跑 Release Assets 的 `workflow_dispatch`)。GitHub Release / Homebrew / cosign 完成后,同一 job 用仓库 secret `CARGO_REGISTRY_TOKEN` 按 `amber_transpile` → `amber_sandbox` → `amberjs` 发布。token 为空则跳过并打 warning。用户安装:`cargo install amberjs`(二进制名 `amber`)。
+3. **crates.io**: 打 `v*` 标签(或对已有标签跑 Release Assets 的 `workflow_dispatch`)。GitHub Release / Homebrew / cosign 完成后,同一 job 用仓库 secret `CARGO_REGISTRY_TOKEN` 按 `amber_transpile` → `amber_sandbox` → `amberjs` 发布。token 为空则跳过并打 warning。用户安装:`cargo install amberjs`(二进制名 `amber`)。本地可 `cargo publish -p amber_transpile --dry-run` / `amber_sandbox`;根包 `amberjs` 的 dry-run 要等这两个 crate 的同一版本已在 crates.io 上(cargo 按 version 解析,工作流会先发布成员)。
## 📞 获取帮助