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
9 changes: 6 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
name: release

# Source of truth is the verified release tag itself. Publishing a GitHub Release
# does not trigger npm publication again; Release Drafter only prepares notes.
# Source of truth is the verified package release tag itself. Moving major tags
# such as v1 are consumer pointers only and must not trigger npm publication.
# Publishing a GitHub Release does not trigger npm publication again; Release
# Drafter only prepares notes.
on:
push:
tags:
- "v*"
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-*"
workflow_dispatch:
inputs:
release_tag:
Expand Down
4 changes: 2 additions & 2 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ Default inputs:
- `command`: `audit`, `doctor`, `fix`
- `path`: project path to inspect, default `.`
- `registry-url`: optional npm registry override for pre-release smoke or private registry validation
- `release-tag`: replace this with a published release tag, for example `v0.1.0`
- `release-tag`: replace this with a published immutable release tag, for example `v1.0.0`. After the stable major tag passes smoke, `v1` is also valid.

Maintainers should use the [release operator runbook](https://github.com/JeremyDev87/maximus/blob/master/docs/release-operator-runbook.md) for alpha or stable releases and same-tag reruns. Release Drafter only refreshes draft notes on `master`; actual publication stays in the tag-driven release workflow.
Maintainers should use the [release operator runbook](https://github.com/JeremyDev87/maximus/blob/master/docs/release-operator-runbook.md) for alpha or stable releases and same-tag reruns. Release Drafter only refreshes draft notes on `master`; actual publication and major tag promotion stay gated by the tag-driven release workflow and action smoke results.
<!-- release-docs:end -->

## Local Development
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ Maximus audit
- `command`: `audit`, `doctor`, `fix`
- `path`: 검사할 프로젝트 경로, 기본값 `.`
- `registry-url`: pre-release smoke나 사설 registry 검증이 필요할 때만 쓰는 optional npm registry override
- `release-tag`: publish된 릴리즈 태그를 넣으세요. 예: `v0.1.0`
- `release-tag`: publish된 immutable 릴리즈 태그를 넣으세요. 예: `v1.0.0`. 안정 major tag가 smoke를 통과한 뒤에는 `v1`도 사용할 수 있습니다.

유지보수자가 실제 alpha/stable 릴리즈를 준비하거나 같은 태그를 안전하게 재실행할 때는 [release operator runbook](https://github.com/JeremyDev87/maximus/blob/master/docs/release-operator-runbook.md)을 기준으로 진행합니다. Release Drafter는 `master`에서 draft notes만 갱신하며, 실제 publish는 tag-driven release workflow만 담당합니다.
유지보수자가 실제 alpha/stable 릴리즈를 준비하거나 같은 태그를 안전하게 재실행할 때는 [release operator runbook](https://github.com/JeremyDev87/maximus/blob/master/docs/release-operator-runbook.md)을 기준으로 진행합니다. Release Drafter는 `master`에서 draft notes만 갱신하며, 실제 publish와 major tag promotion은 tag-driven release workflow와 action smoke 결과를 기준으로 진행합니다.
<!-- release-docs:end -->

## 로컬 개발
Expand Down
3 changes: 2 additions & 1 deletion bin/maximus.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const cliArgs = process.argv.slice(2);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const frozenJsReferenceNote =
"Rust가 Maximus의 canonical runtime입니다. 포함된 JS reference는 frozen 상태이며 legacy 호환 명령을 위한 임시 compatibility bridge로만 유지됩니다.";
const EXECUTABLE_PROBE_TIMEOUT_MS = 5000;

try {
const runtime = await resolveRuntime(cliArgs);
Expand Down Expand Up @@ -404,7 +405,7 @@ async function probeExecutable(binaryPath) {
const timeout = setTimeout(() => {
child.kill("SIGKILL");
settle(false);
}, 1000);
}, EXECUTABLE_PROBE_TIMEOUT_MS);

child.on("error", () => {
clearTimeout(timeout);
Expand Down
25 changes: 20 additions & 5 deletions docs/github-action-marketplace.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,18 @@

## 사용 경로

안정 태그를 발행한 뒤에는 다음처럼 wrapper action 경로로 사용할 수 있습니다.
안정 태그를 발행한 뒤에는 root action 또는 Marketplace wrapper action 경로로 사용할 수 있습니다.

Root action:

```yaml
- uses: JeremyDev87/maximus@v1
with:
command: audit
path: .
```

Marketplace wrapper action:

```yaml
- uses: JeremyDev87/maximus/.github/actions/marketplace-wrapper@v1
Expand All @@ -27,9 +38,12 @@

## 버전 태그 전략

- stable consumer 예시는 major tag `v1`를 우선으로 안내합니다.
- 재현 가능한 pinning이 필요하면 `v1.2.3`처럼 immutable release tag를 사용합니다.
- `v1` 같은 moving major tag는 stable release가 준비된 뒤에만 최신 stable release로 이동합니다.
- stable consumer 예시는 moving major tag `v1`를 우선으로 안내합니다.
- 재현 가능한 pinning이 필요하면 `v1.0.0`처럼 immutable release tag를 사용합니다.
- `v1`은 `v1.0.0` 같은 immutable stable tag publish가 끝난 뒤에만 같은 commit으로 이동합니다.
- `v1`은 prerelease tag로 이동하지 않습니다.
- `v1` 이동은 npm publication trigger가 아니며, `release.yml`은 `v1.0.0` 같은 package release tag만 받습니다.
- `v1` 이동 후에는 `action-smoke.yml`을 `--ref v1`로 실행해서 root action과 marketplace wrapper action을 둘 다 검증합니다.

## 구현 원칙

Expand All @@ -40,5 +54,6 @@
## 유지보수 체크리스트

- root `action.yml` 입력이 바뀌면 wrapper action 입력도 같은 turn에 동기화합니다.
- release smoke는 여전히 root action contract와 published tag 기준으로 검증합니다.
- release smoke는 root action contract, marketplace wrapper contract, published tag 기준으로 검증합니다.
- `v1` tag 이동은 immutable stable tag publish와 smoke가 끝난 뒤 별도 확인을 받고 수행합니다.
- README 예시 추가가 필요하면 `README.md` / `README.en.md`를 소유한 별도 lane에서 처리합니다.
18 changes: 11 additions & 7 deletions docs/npm-wrapper-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
## 목적

- 루트 `maximus` npm package를 thin launcher로 유지하면서 실제 실행은 Rust binary로 위임한다.
- 플랫폼별 binary package를 optional dependency로 두고, hard cutover 전까지는 packed install과 repo 개발 환경 모두에서 reference runtime fallback을 허용한다.
- 플랫폼별 binary package를 optional dependency로 두고, hard cutover 전까지는 packed install과 repo 개발 환경 모두에서 제한적 reference runtime fallback을 허용한다.
- placeholder platform package가 잘못 publish되더라도 hard cutover 전에는 wrapper가 이를 무시하고 JS reference runtime으로 fallback한다.
- v1.0.0의 정식 native runtime 표면은 macOS와 Linux glibc 4개 package로 고정한다.

## 런타임 선택 순서

Expand All @@ -17,21 +18,24 @@
3. `target/debug/maximus`가 없고 `target/release/maximus`가 있으면 그 binary를 사용한다.
4. repository local binary가 없고 설치된 platform package가 있으면 그 안의 `bin/maximus` Rust binary를 실행한다.
- 단, placeholder marker(`MAXIMUS_RUST_BINARY_PLACEHOLDER`)가 있으면 실행하지 않고 다음 후보로 넘어간다.
5. hard cutover 전까지 설치된 root package 안의 `src/cli.js` reference runtime으로 fallback한다.
5. hard cutover 전까지 설치된 root package 안의 `src/cli.js` reference runtime으로 제한적으로 fallback한다.
6. 위 경로가 모두 없을 때만 wrapper가 실패한다.

## unsupported 정책

- 지원 플랫폼:
- v1.0.0 native runtime 지원 플랫폼:
- `darwin-arm64`
- `darwin-x64`
- `linux-arm64-gnu`
- `linux-x64-gnu`
- hard cutover 전 미지원 플랫폼:
- v1.0.0 prebuilt native runtime 미지원 플랫폼:
- Windows
- Linux musl
- 기타 미지원 CPU 조합
- 미지원 플랫폼에서도 `src/cli.js` reference runtime이 남아 있는 동안은 JS fallback으로 계속 동작한다.
- 미지원 플랫폼에서도 `src/cli.js` reference runtime이 남아 있는 동안은 limited compatibility fallback으로만 동작한다.
- JS fallback 허용 범위는 config file이 없고 Rust-only flag가 없는 legacy-compatible `audit`, `doctor`, `fix --dry-run` 흐름이다.
- `maximus.config.json`, `.maximusrc.json`, `--only`, `--skip`, `--fail-on`, `--diff`, `--fix-id`, `--fix-prefix`, `--format`, `--output`, `fix` without `--dry-run`에는 native Rust runtime이 필요하다.
- Windows와 Linux musl은 v1.0.0에서 정식 native 지원 플랫폼으로 표시하지 않는다. JS fallback 제거는 별도 hard cutover 작업으로 다룬다.
- reference runtime이 제거된 뒤에는 wrapper가 명확한 unsupported 오류 메시지를 출력해야 한다.

## package layout
Expand All @@ -51,9 +55,9 @@
## local smoke

1. 루트 package tarball 생성:
- `npm pack --json > /tmp/maximus-npm-pack.json`
- `env npm_config_cache=/tmp/maximus-release-pack/.npm-cache npm pack --json --pack-destination /tmp/maximus-release-pack > /tmp/maximus-release-pack/pack.json`
2. packed wrapper smoke:
- `node ./scripts/run-packed-wrapper-smoke.mjs /tmp/maximus-npm-pack.json ./test/fixtures/clean-project`
- `node ./scripts/run-packed-wrapper-smoke.mjs /tmp/maximus-release-pack/pack.json ./test/fixtures/clean-project`

helper script는 다음을 보장한다.

Expand Down
80 changes: 57 additions & 23 deletions docs/release-operator-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@

This runbook is for maintainers preparing and promoting Maximus releases.

It documents the preflight checks, the alpha-to-stable promotion path, and the rerun rules that match the checked-in GitHub workflows. It does not publish anything by itself. The tag-driven workflow in `.github/workflows/release.yml` remains the only release path.
It documents the preflight checks, the alpha-to-stable promotion path, the v1 major tag policy, and the rerun rules that match the checked-in GitHub workflows. It does not publish anything by itself. The tag-driven workflow in `.github/workflows/release.yml` remains the only release path.

## Release Model

- The release source of truth is the verified Git tag.
- `package.json` version and the tag must match exactly. For example, `0.2.0-alpha.1` must be released from `v0.2.0-alpha.1`.
- The release source of truth is the verified package release tag.
- `package.json` version and the tag must match exactly. For example, `1.0.0-alpha.1` must be released from `v1.0.0-alpha.1`.
- `.github/workflows/release.yml` listens only to package release tags such as `v1.0.0` and `v1.0.0-alpha.1`; moving major tags such as `v1` must not trigger npm publication.
- Prerelease versions publish with the npm dist-tag `next`.
- Stable versions publish with the npm dist-tag `latest`.
- `.github/workflows/release-drafter.yml` only maintains draft notes on `master`. It does not publish npm packages or run release smoke jobs.
- v1.0.0 ships the root package `@jeremyfellaz/maximus` plus four native runtime packages:
- `@jeremyfellaz/maximus-darwin-arm64`
- `@jeremyfellaz/maximus-darwin-x64`
- `@jeremyfellaz/maximus-linux-arm64-gnu`
- `@jeremyfellaz/maximus-linux-x64-gnu`
- Windows and Linux musl do not have prebuilt native runtime packages in v1.0.0. They remain limited compatibility targets only while the frozen JS fallback is still present.
- The moving major action tag `v1` is updated only after the immutable stable tag, such as `v1.0.0`, has completed npm publication plus root action and marketplace wrapper smoke. Never move `v1` for prerelease tags.
- `.github/workflows/release-drafter.yml` only maintains draft notes on `master`. It does not publish npm packages, move `v1`, or run release smoke jobs.
- `workflow_dispatch` reruns are only valid for an existing tag ref. Do not run the release workflow from `master` or any other branch.

## Preflight Before Creating A New Tag
Expand All @@ -20,15 +28,17 @@ Run this checklist on a clean `master` checkout before creating a new release ta
1. Pull the target commit from `master`.
2. Confirm the release notes draft looks correct on GitHub. Treat Release Drafter output as notes only.
3. Confirm the package namespace state with npm.
4. Run the local final gate.
4. Run the local final gate from the exact release candidate SHA.

Suggested commands:

```bash
git switch master
git pull --ff-only

export RELEASE_VERSION=0.2.0-alpha.1
export RELEASE_VERSION=1.0.0
export NPM_CONFIG_CACHE=/tmp/maximus-npm-cache
export PACK_ROOT=/tmp/maximus-release-pack

cargo test --workspace
npm test
Expand All @@ -37,27 +47,28 @@ node --test test/github-action-wiring.test.js
node --test test/release-workflow-context.test.js
node --test test/wrapper-runtime.test.js test/packed-wrapper-fallback.test.js

npm_config_cache=/tmp/maximus-npm-cache npm view "@jeremyfellaz/maximus@$RELEASE_VERSION" version
env npm_config_cache="$NPM_CONFIG_CACHE" npm view "@jeremyfellaz/maximus@$RELEASE_VERSION" version
for package in \
@jeremyfellaz/maximus-darwin-arm64 \
@jeremyfellaz/maximus-darwin-x64 \
@jeremyfellaz/maximus-linux-arm64-gnu \
@jeremyfellaz/maximus-linux-x64-gnu
do
npm_config_cache=/tmp/maximus-npm-cache npm view "${package}@${RELEASE_VERSION}" version
env npm_config_cache="$NPM_CONFIG_CACHE" npm view "${package}@${RELEASE_VERSION}" version
done

rm -rf /tmp/maximus-release-pack
mkdir -p /tmp/maximus-release-pack
/bin/zsh -lc 'npm_config_cache=/tmp/maximus-release-pack/.npm-cache npm pack --json --pack-destination /tmp/maximus-release-pack > /tmp/maximus-release-pack/pack.json'
node ./scripts/run-packed-wrapper-smoke.mjs /tmp/maximus-release-pack/pack.json ./test/fixtures/clean-project
rm -rf "$PACK_ROOT"
mkdir -p "$PACK_ROOT"
env npm_config_cache="$PACK_ROOT/.npm-cache" npm pack --json --pack-destination "$PACK_ROOT" > "$PACK_ROOT/pack.json"
node ./scripts/run-packed-wrapper-smoke.mjs "$PACK_ROOT/pack.json" ./test/fixtures/clean-project
```

How to read the npm checks:

- Before the first public release, `npm view "<pkg>@$RELEASE_VERSION" version` returning `E404` is acceptable.
- After a release already exists, that exact version should resolve.
- If npm returns an auth or permission failure instead of `E404`, stop and confirm the publishing account has access to the `@jeremyfellaz` scope before tagging.
- Keep `npm_config_cache` pointed at a disposable path for all local npm preflight and `npm pack` commands. Some maintainer machines may have a default npm cache that is not writable by the current user.

## Preflight Before A Same-Tag Rerun

Expand All @@ -68,8 +79,10 @@ The local verification target must match the tag commit, not the current tip of
Suggested commands:

```bash
export RELEASE_TAG=v0.2.0
export RELEASE_VERSION=0.2.0
export RELEASE_TAG=v1.0.0
export RELEASE_VERSION=1.0.0
export NPM_CONFIG_CACHE=/tmp/maximus-npm-cache
export PACK_ROOT=/tmp/maximus-release-pack

git fetch --tags origin
git switch --detach "$RELEASE_TAG"
Expand All @@ -81,15 +94,20 @@ node --test test/github-action-wiring.test.js
node --test test/release-workflow-context.test.js
node --test test/wrapper-runtime.test.js test/packed-wrapper-fallback.test.js

npm_config_cache=/tmp/maximus-npm-cache npm view "@jeremyfellaz/maximus@$RELEASE_VERSION" version
env npm_config_cache="$NPM_CONFIG_CACHE" npm view "@jeremyfellaz/maximus@$RELEASE_VERSION" version
for package in \
@jeremyfellaz/maximus-darwin-arm64 \
@jeremyfellaz/maximus-darwin-x64 \
@jeremyfellaz/maximus-linux-arm64-gnu \
@jeremyfellaz/maximus-linux-x64-gnu
do
npm_config_cache=/tmp/maximus-npm-cache npm view "${package}@${RELEASE_VERSION}" version
env npm_config_cache="$NPM_CONFIG_CACHE" npm view "${package}@${RELEASE_VERSION}" version
done

rm -rf "$PACK_ROOT"
mkdir -p "$PACK_ROOT"
env npm_config_cache="$PACK_ROOT/.npm-cache" npm pack --json --pack-destination "$PACK_ROOT" > "$PACK_ROOT/pack.json"
node ./scripts/run-packed-wrapper-smoke.mjs "$PACK_ROOT/pack.json" ./test/fixtures/clean-project
```

Rules:
Expand All @@ -113,15 +131,16 @@ Example:
```bash
git switch master
git pull --ff-only
git tag -a v0.2.0-alpha.1 -m "release: v0.2.0-alpha.1"
git push origin v0.2.0-alpha.1
git tag -a v1.0.0-alpha.1 -m "release: v1.0.0-alpha.1"
git push origin v1.0.0-alpha.1
```

Expected behavior:

- The release workflow publishes with dist-tag `next`.
- Platform packages publish before the root wrapper.
- Published-wrapper smoke and GitHub Action smoke both run against the same tagged snapshot.
- The moving major tag `v1` is not moved for prerelease candidates.

## Stable Promotion Flow

Expand All @@ -130,23 +149,37 @@ Use this path after a prerelease has been validated and you are ready to promote
1. Open a version-only PR that removes the prerelease suffix across the package manifests.
2. Merge that PR to `master`.
3. Re-run the new-tag preflight checklist on the new stable commit.
4. Create and push the stable tag that matches the stable package version.
4. Create and push the immutable stable tag that matches the stable package version.
5. Watch the release workflow and verify the stable version resolves on npm.
6. After the immutable tag publish, published-wrapper smoke, and action smoke are all green, move the `v1` major tag to that same commit.
7. Re-run `action-smoke.yml` through `v1` to prove both the root action and marketplace wrapper work from the moving major tag.

Example:

```bash
git switch master
git pull --ff-only
git tag -a v0.2.0 -m "release: v0.2.0"
git push origin v0.2.0
git tag -a v1.0.0 -m "release: v1.0.0"
git push origin v1.0.0
```

Major tag promotion after the release workflow is green:

```bash
export RELEASE_TAG=v1.0.0
export RELEASE_SHA="$(git rev-list -n 1 "$RELEASE_TAG")"
git tag -f v1 "$RELEASE_TAG"
git push origin refs/tags/v1 --force-with-lease
gh workflow run action-smoke.yml --ref v1 -f release_tag=v1 -f release_sha="$RELEASE_SHA"
```

Expected behavior:

- The release workflow publishes with dist-tag `latest`.
- The release tag and `package.json` version match exactly.
- Release Drafter continues to prepare the next notes draft on `master`, but it does not publish or promote anything by itself.
- Moving `v1` does not trigger the tag-driven release workflow.
- Root action smoke and marketplace wrapper smoke both pass before and after `v1` moves.
- Release Drafter continues to prepare the next notes draft on `master`, but it does not publish, promote, or move tags by itself.

## Safe Reruns

Expand All @@ -157,7 +190,7 @@ Use `workflow_dispatch` only with the same tag as both the selected ref and the
Example:

```bash
gh workflow run release.yml --ref v0.2.0 -f release_tag=v0.2.0
gh workflow run release.yml --ref v1.0.0 -f release_tag=v1.0.0
```

Rules:
Expand Down Expand Up @@ -200,3 +233,4 @@ Rules:

- Keep release-related docs aligned: `README.md`, `README.en.md`, `CONTRIBUTING.md`, this runbook, and the release workflows should describe the same release model.
- If a change touches release wiring, package naming, or packed-install behavior, re-run the full preflight checklist before asking a maintainer to tag a release.
- Do not run tag creation, tag movement, release workflow dispatch, or npm publication without an explicit maintainer confirmation for that operation.
3 changes: 2 additions & 1 deletion scripts/assert-installed-native-runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { access, open } from "node:fs/promises";
import { fileURLToPath } from "node:url";

const PLACEHOLDER_MARKER = "MAXIMUS_RUST_BINARY_PLACEHOLDER";
const EXECUTABLE_PROBE_TIMEOUT_MS = 5000;

export async function assertInstalledNativeRuntime(installRoot) {
const runtime = await inspectInstalledNativeRuntime(installRoot);
Expand Down Expand Up @@ -187,7 +188,7 @@ async function probeExecutable(binaryPath) {
const timeout = setTimeout(() => {
child.kill("SIGKILL");
settle(false);
}, 1000);
}, EXECUTABLE_PROBE_TIMEOUT_MS);

child.on("error", () => {
clearTimeout(timeout);
Expand Down
Loading