Skip to content

Commit bddea42

Browse files
authored
feat: add versioned CLI releases and npm installation archives (#248)
* feat: publish npm installation archives from version tags * feat: commit source versions before tagging CLI releases * test: serialize BYOK proxy checks on Node 24.2
1 parent a55d629 commit bddea42

17 files changed

Lines changed: 857 additions & 38 deletions

‎.agents/skills/testing-workflow/SKILL.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ suites are outside this distribution's verification.
3333
| Permission facade on macOS/Linux | `pnpm test:policy` |
3434
| Sandbox on macOS | `pnpm test:sandbox` |
3535
| Source-sync, workflow and release tools | `pnpm test:release-tools` |
36+
| npm release archive installation | `MCODE_RELEASE_TAG=vX.Y.Z MCODE_RELEASE_ARCHIVE=/path/to/package.tar.gz pnpm verify --profile package` |
3637
| Types and standalone build boundary | `pnpm typecheck`, `pnpm build`, `pnpm check:standalone` |
3738
| Published files and generated paths | `pnpm check:source`, `pnpm check:tsconfig` |
3839

@@ -61,6 +62,13 @@ Skills under `.agents/skills`, unknown paths and inventory changes require the
6162
full profile. The `archive` profile is for source-archive validation, not a way to
6263
bypass the clean-commit export requirement.
6364

65+
The `package` profile authenticates an npm release archive, installs it into a
66+
temporary npm prefix, and exercises its launcher, native dependencies and offline
67+
smoke/BYOK suites. It requires `MCODE_RELEASE_TAG` and `MCODE_RELEASE_ARCHIVE` and
68+
does not replace source validation. The release command commits matching root/TUI source versions before tagging.
69+
The release workflow rejects version mismatches and runs the full profile before
70+
package installation checks.
71+
6472
## Manual evidence and reporting
6573

6674
For CLI behavior, exercise the built `dist/cli.js` and inspect stdout, stderr and

‎.github/workflows/cli-release.yml‎

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
name: CLI release
2+
on:
3+
push:
4+
tags: ['v*']
5+
pull_request:
6+
paths:
7+
- '.github/workflows/cli-release.yml'
8+
- 'scripts/build.mjs'
9+
- 'scripts/*cli-release.mjs'
10+
- 'scripts/release-cli.mjs'
11+
- 'scripts/lib/cli-release.mjs'
12+
- 'scripts/verify.mjs'
13+
- 'test/smoke.test.mjs'
14+
- 'test/byok.test.mjs'
15+
- 'pnpm-lock.yaml'
16+
- 'package.json'
17+
- 'packages/tui/package.json'
18+
workflow_dispatch:
19+
inputs:
20+
tag:
21+
description: 'Optional tag matching the source version (dry run; does not publish)'
22+
required: false
23+
type: string
24+
permissions:
25+
contents: read
26+
concurrency:
27+
group: cli-release-${{ inputs.tag || github.ref }}
28+
cancel-in-progress: false
29+
jobs:
30+
build:
31+
runs-on: ubuntu-latest
32+
timeout-minutes: 25
33+
outputs:
34+
version: ${{ steps.release.outputs.version }}
35+
matrix: ${{ steps.release.outputs.matrix }}
36+
tag: ${{ steps.release.outputs.tag }}
37+
steps:
38+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
39+
with:
40+
fetch-depth: 0
41+
persist-credentials: false
42+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
43+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
44+
with:
45+
node-version: '24'
46+
cache: pnpm
47+
- name: Validate release version and revision
48+
id: release
49+
shell: bash
50+
run: |
51+
node --input-type=module -e 'import { appendFileSync } from "node:fs"; import { cliBuildVersion, cliReleaseTargets } from "./scripts/lib/cli-release.mjs"; const tag = process.env.REQUESTED_TAG || `v${cliBuildVersion(process.cwd())}`; const version = cliBuildVersion(process.cwd(), tag); appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\ntag=${tag}\nmatrix=${JSON.stringify({include: cliReleaseTargets})}\n`); appendFileSync(process.env.GITHUB_ENV, `MCODE_RELEASE_TAG=${tag}\n`);'
52+
if [ "$EVENT_NAME" = push ]; then
53+
test "$(git rev-parse "refs/tags/$REQUESTED_TAG^{commit}")" = "$(git rev-parse HEAD)"
54+
fi
55+
env:
56+
EVENT_NAME: ${{ github.event_name }}
57+
REQUESTED_TAG: ${{ inputs.tag || (github.event_name == 'push' && github.ref_name) || '' }}
58+
- run: pnpm install --frozen-lockfile
59+
- uses: ./.github/actions/setup-gitleaks
60+
- name: Scan source history
61+
run: gitleaks git --redact --config .gitleaks.toml --log-opts=--all
62+
- run: pnpm verify
63+
env:
64+
MCODE_VERIFY_REPORT_DIR: ${{ runner.temp }}/build-report
65+
- name: Scan distribution
66+
run: gitleaks dir dist --redact --config .gitleaks.toml
67+
- name: Package the tagged CLI
68+
shell: bash
69+
run: node scripts/package-cli-release.mjs "$MCODE_RELEASE_TAG" "$RUNNER_TEMP/package"
70+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
71+
with:
72+
name: cli-package
73+
path: ${{ runner.temp }}/package/
74+
retention-days: 14
75+
if-no-files-found: error
76+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
77+
if: ${{ always() }}
78+
with:
79+
name: cli-build-report
80+
path: ${{ runner.temp }}/build-report/
81+
retention-days: 14
82+
install:
83+
needs: build
84+
env:
85+
MCODE_RELEASE_TAG: ${{ needs.build.outputs.tag }}
86+
runs-on: ${{ matrix.os }}
87+
timeout-minutes: 20
88+
strategy:
89+
fail-fast: false
90+
matrix: ${{ fromJSON(needs.build.outputs.matrix) }}
91+
steps:
92+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
93+
with:
94+
persist-credentials: false
95+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
96+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
97+
with:
98+
node-version: ${{ matrix.node }}
99+
cache: pnpm
100+
- run: pnpm install --frozen-lockfile
101+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
102+
with:
103+
name: cli-package
104+
path: ${{ runner.temp }}/package
105+
- run: pnpm verify --profile package
106+
env:
107+
MCODE_RELEASE_ARCHIVE: ${{ runner.temp }}/package/minimax-code-${{ needs.build.outputs.version }}.tar.gz
108+
MCODE_VERIFY_REPORT_DIR: ${{ runner.temp }}/install-report
109+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
110+
if: ${{ always() }}
111+
with:
112+
name: cli-install-${{ matrix.os }}-${{ matrix.node }}
113+
path: ${{ runner.temp }}/install-report/
114+
retention-days: 14
115+
publish:
116+
needs: [build, install]
117+
if: github.event_name == 'push'
118+
permissions:
119+
contents: write
120+
runs-on: ubuntu-latest
121+
timeout-minutes: 5
122+
env:
123+
MCODE_RELEASE_TAG: ${{ needs.build.outputs.tag }}
124+
steps:
125+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
126+
with:
127+
persist-credentials: false
128+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
129+
with:
130+
node-version: '24'
131+
package-manager-cache: false
132+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
133+
with:
134+
name: cli-package
135+
path: ${{ runner.temp }}/package
136+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
137+
with:
138+
pattern: cli-install-*
139+
path: ${{ runner.temp }}/reports
140+
- name: Publish only the authenticated, validated archive
141+
run: node scripts/publish-cli-release.mjs
142+
env:
143+
GH_TOKEN: ${{ github.token }}
144+
GH_REPO: ${{ github.repository }}
145+
MCODE_RELEASE_DIRECTORY: ${{ runner.temp }}/package
146+
MCODE_RELEASE_REPORTS: ${{ runner.temp }}/reports

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Existing README files, `CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md`, Ma
4545

4646
`Node compatibility` runs weekly and on demand against macOS and Linux with Node 22.19.0, 24.2.0, 25 and 26. Windows is also temporarily excluded from this matrix. It does not run automatically on PRs. Dispatch it on the selected branch for changes to supported Node versions, native dependencies or compatibility-sensitive verification tooling, and before a source release. This covers the minimum versions of the two supported ranges and the additional supported majors. Deferring those versions from ordinary PR checks can delay regression discovery; a known failure in a supported version still needs resolution before release. Dependabot proposes weekly Actions and npm updates, grouping Actions and development-tool minor/patch updates. External Actions use reviewed full commit SHAs, while local actions and reusable workflows come from the same checked-out revision.
4747

48-
Source candidates are requested independently through the `Source candidate` workflow; ordinary PRs and main pushes do not produce them. Its Linux/macOS archive validation is described in [Releasing](docs/releasing.md). Product npm and installer publication remains a separate release process.
48+
Source candidates are requested independently through the `Source candidate` workflow; ordinary PRs and main pushes do not produce them. Its Linux/macOS archive validation is described in [Releasing](docs/releasing.md). `CLI release` builds npm-installable tar.gz packages from version tags and validates the same archive before attaching it to a GitHub Release. Its `package` verification profile tests installation of an existing archive; it does not replace full source verification. npm registry and official installer publication remain separate release processes.
4949

5050
Source export reports separate archive creation, extraction, inventory validation, hashing, and cleanup timings in `export.json`. Windows uses native `tar` after complete archive preflight; other systems use the Node extractor. The preflight rejects traversal, links, duplicate entries and Git metadata before writing files. A machine without native `tar` falls back to Node. Set `MCODE_SOURCE_EXTRACTOR=node` or `native` when comparing extractors locally.
5151

‎docs/installation.md‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,35 @@
1-
# Install from source
1+
# Installation packages and source builds
2+
3+
## Install a GitHub release archive
4+
5+
When a CLI release is available on [GitHub Releases](https://github.com/MiniMax-AI/minimax-code/releases),
6+
download `minimax-code-X.Y.Z.tar.gz` and the matching `.sha256` file. This is an npm
7+
installation package containing the built CLI; no source build or pnpm is needed.
8+
Install Node.js 22.19+ (22.x), 24.2+ (24.x), 25 or 26 first. npm still needs network
9+
access to public npm for runtime dependencies. Native dependencies can require
10+
Python and C/C++ build tools when no matching prebuilt binary is available.
11+
12+
For example, for an available `v0.4.13` release:
13+
14+
```bash
15+
# Linux; on macOS use: shasum -a 256 -c minimax-code-0.4.13.tar.gz.sha256
16+
sha256sum -c minimax-code-0.4.13.tar.gz.sha256
17+
npm install --global ./minimax-code-0.4.13.tar.gz --registry=https://registry.npmjs.org/ --include=optional --ignore-scripts=false --allow-scripts=better-sqlite3
18+
mcode --version
19+
```
20+
21+
Keep optional dependencies enabled and allow the native SQLite installation
22+
script. The tag determines the installed version. GitHub archive installation is
23+
validated on Linux and macOS; Windows package acceptance is currently not run.
24+
25+
This archive uses the same `@minimax-ai/code` package name, `mcode` command and
26+
default user data directory as the official npm CLI. Installing it globally into
27+
the same npm prefix replaces that npm installation. Update to another GitHub
28+
version by explicitly installing its archive; the built-in updater follows the
29+
official npm registry channel and does not select GitHub release assets. To remove
30+
the package, use `npm uninstall --global @minimax-ai/code`. User data remains in place.
31+
32+
## Install from source
233

334
The official CLI is available as [`@minimax-ai/code`](https://www.npmjs.com/package/@minimax-ai/code). Public npm `latest` was 0.4.12 on 2026-09-18. Follow the [official quick start](https://agent.minimax.io/docs/cli/quick-start) or the [README installation steps](../README.md#quick-start) for the macOS / Linux / WSL installer, Windows PowerShell installer, or npm installation.
435

‎docs/releasing.md‎

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,74 @@
1-
# Releasing a source preview
1+
# Releasing MiniMax Code
2+
3+
## Tag-triggered CLI installation packages
4+
5+
Run the release command from a clean checkout of the latest reviewed `origin/main`.
6+
Git and an authenticated `gh-axi` or `gh` are required:
7+
8+
```bash
9+
pnpm release:cli --version 0.4.13 --dry-run
10+
pnpm release:cli --version 0.4.13
11+
```
12+
13+
The dry run checks the starting revision, versions and remote refs without changing
14+
files, creating commits or pushing. The release command then:
15+
16+
1. Creates `release/v0.4.13` from the reviewed starting commit.
17+
2. Bumps `package.json` and `packages/tui/package.json` together and commits them.
18+
3. Creates the annotated `v0.4.13` tag on that version commit.
19+
4. Atomically pushes the release branch and tag, without pushing `main`.
20+
5. Opens a version PR back to `main`; merge it through the normal review process.
21+
22+
CI requires the tag, both committed source versions and `mcode --version` to agree.
23+
It does not override the source version during a build. An existing tag or release
24+
branch, a non-increasing version, uncommitted files, or a starting commit other
25+
than the latest `origin/main` stops the command before version changes.
26+
27+
The tag starts the release workflow independently of the version PR. If a network
28+
or PR-creation failure occurs, inspect the local and remote branch/tag before
29+
retrying: the version commit and tag are retained for recovery. If both refs were
30+
pushed but opening the PR failed, open that version PR manually. Never delete and
31+
recreate an already distributed tag. Merge the version PR before starting the
32+
next release so `main` carries the released version.
33+
34+
The workflow runs the full verification profile and secret scans, builds one
35+
`minimax-code-X.Y.Z.tar.gz` npm installation package, and authenticates and installs
36+
that same archive on Linux and macOS with Node 22.19.0, 24.2.0, 25 and 26. Each
37+
installation checks the generated `mcode` launcher, native SQLite, ripgrep, and
38+
the offline smoke/BYOK suites. Windows validation remains paused.
39+
40+
Only after every installation succeeds does CI create a GitHub Release with the
41+
archive and its `.sha256` checksum. Tags such as `v0.4.13-rc.1` create prereleases.
42+
Release creation starts as a draft; assets are uploaded before it becomes public.
43+
Existing releases are never overwritten. If publication fails after draft
44+
creation, inspect the draft and workflow artifacts before deciding whether to
45+
finish publication manually or delete only the incomplete draft and rerun.
46+
Never move an already distributed tag to different code.
47+
48+
To exercise this workflow without publication, dispatch `CLI release` on a
49+
selected branch. An optional tag input must match the committed source version. A manual dispatch only builds and
50+
validates Actions artifacts, even when the requested tag already exists.
51+
PRs that change release tooling also run the build/install matrix using the
52+
committed source version, without creating a tag or publishing a release.
53+
54+
To reproduce the packaging and installation checks locally, use a clean reviewed
55+
commit and keep output outside the repository:
56+
57+
```bash
58+
export MCODE_RELEASE_TAG="v$(node -p 'require("./package.json").version')"
59+
pnpm verify
60+
node scripts/package-cli-release.mjs "$MCODE_RELEASE_TAG" /tmp/mcode-release
61+
MCODE_RELEASE_ARCHIVE="/tmp/mcode-release/minimax-code-${MCODE_RELEASE_TAG#v}.tar.gz" pnpm verify --profile package
62+
```
63+
64+
The `package` profile validates installation of an existing archive; it does not
65+
replace full source verification. The archive includes the compiled CLI, runtime
66+
assets, licenses and a `release.json` source receipt. npm installs external runtime
67+
dependencies, including native dependencies, for the user's platform. It does not
68+
include Node.js and is not an offline bundle. See [installation](installation.md#install-a-github-release-archive).
69+
This workflow does not publish to the npm registry or change the official installer.
70+
71+
## Source previews
272

373
The current source target is MiniMax Code 0.4.12. Workspace and local-build manifests remain `private: true` to prevent accidental npm publication. A source release, npm package, and installer are separate artifacts with separate verification.
474

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"scripts": {
1212
"build": "node scripts/build.mjs",
13+
"release:cli": "node scripts/release-cli.mjs",
1314
"start": "node dist/cli.js",
1415
"mcode": "node dist/cli.js",
1516
"verify": "node scripts/verify.mjs",

‎release/public-source.json‎

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/build.mjs‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
writeFileSync,
1010
} from "node:fs";
1111
import { createRequire } from "node:module";
12+
import { execFileSync } from 'node:child_process';
1213
import { fileURLToPath } from "node:url";
1314
import path from "node:path";
1415
import { copyLocalRuntimeAssets } from "./lib/local-runtime-assets.mjs";
@@ -17,6 +18,7 @@ import { shouldCopyTuiRuntimeResource } from "./lib/tui-package-privacy.mjs";
1718
import { TUI_DISABLED_BUILTIN_SKILL_NAMES } from "./lib/builtin-skills.mjs";
1819
import { copyMcodeToolsArtifact } from './lib/mcode-tools-artifact.mjs';
1920
import { readExtraction } from "./lib/release-metadata.mjs";
21+
import { cliBuildVersion, cliExternalModules } from './lib/cli-release.mjs';
2022

2123
const root = fileURLToPath(new URL("../", import.meta.url));
2224
const metadata = readExtraction(root);
@@ -72,7 +74,7 @@ const sourcePlugin = {
7274
});
7375
},
7476
};
75-
const version = packages.get("@minimax/code").manifest.version;
77+
const version = cliBuildVersion(root);
7678
const result = await build({
7779
absWorkingDir: root,
7880
entryPoints: {
@@ -81,12 +83,7 @@ const result = await build({
8183
'mcode-tools': 'packages/tui/src/cli/mcode-tools-entry.ts',
8284
'matrix-mcp-stdio': 'packages/agent-tools/src/desktop/matrix-mcp-stdio.ts',
8385
},
84-
external: [
85-
"better-sqlite3",
86-
"@mariozechner/clipboard",
87-
"@vscode/ripgrep",
88-
"@larksuiteoapi/node-sdk",
89-
],
86+
external: cliExternalModules,
9087
outdir,
9188
bundle: true,
9289
splitting: true,
@@ -143,7 +140,12 @@ console.log(
143140
writeFileSync(
144141
path.join(outdir, "package.json"),
145142
JSON.stringify(
146-
{ name: "@minimax-ai/code", version, type: "module", private: true },
143+
{
144+
name: "@minimax-ai/code", version, type: "module", private: true,
145+
...(process.env.MCODE_RELEASE_TAG ? {
146+
gitHead: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(),
147+
} : {}),
148+
},
147149
null,
148150
2,
149151
) + "\n",

0 commit comments

Comments
 (0)