Skip to content

Commit e7d809d

Browse files
authored
fix(windows): clarify source checkout requirements (#303)
* fix(windows): clarify source checkout requirements Skip only WSL conversion fixtures on native Windows and add an explicit local-NTFS preflight to the source-build instructions. Cover localized fsutil output and fail-closed diagnostics without changing production path validation. * ci: add focused Windows source contract Run a Windows Node 24 contract on non-documentation pull requests while keeping the full capability suite on Linux and macOS. Share the gate through the verifier and Vitest suite manifest, keep the compatibility matrix macOS/Linux, and document the Windows-only boundary.
1 parent 1c64165 commit e7d809d

14 files changed

Lines changed: 248 additions & 26 deletions

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,16 @@ jobs:
5555
strategy:
5656
fail-fast: false
5757
matrix:
58-
# Windows validation is temporarily paused until its checks are reliable.
59-
os: [ubuntu-latest, macos-latest]
58+
# Windows runs a focused contract; full capability coverage remains on Linux/macOS.
59+
os: [ubuntu-latest, macos-latest, windows-latest]
6060
node: ["24"]
6161
include:
6262
- os: ubuntu-latest
6363
node: "24"
6464
profile: full
65+
- os: windows-latest
66+
node: "24"
67+
profile: windows
6568
runs-on: ${{ matrix.os }}
6669
steps:
6770
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

‎CONTRIBUTING.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ pnpm install --frozen-lockfile
3737
pnpm verify
3838
```
3939

40-
`pnpm verify` runs the complete gate list in the same order as GitHub CI. Normal PR and main-branch checks use Node.js 24 on Linux and macOS. Windows CI is temporarily paused while its checks are made reliable. The Linux job runs this full profile; the macOS job uses `pnpm verify --profile platform`, which omits only the duplicate TypeScript compiler check. Both jobs still export source, build, and test their own platform artifacts. Gates that depend on platform behaviour are selected by platform rather than skipped silently; run `pnpm verify --list` or `pnpm verify --profile platform --list` to inspect either plan. Individual gates remain available as their own scripts, such as `pnpm typecheck` or `pnpm test:byok`, while you iterate.
40+
`pnpm verify` runs the complete gate list in the same order as GitHub CI. Normal PR and main-branch checks use Node.js 24 on Linux and macOS. The Linux job runs the full profile; the macOS job uses `pnpm verify --profile platform`, which omits only the duplicate TypeScript compiler check. Windows runs the focused `pnpm verify --profile windows` contract on PRs; the profile is Windows-only and fails closed elsewhere. It checks source inventory, release tooling, build boundaries, artifacts, and Windows-specific tests without running the full capability suite. Gates that depend on platform behaviour are selected by platform rather than skipped silently; run `pnpm verify --list`, `pnpm verify --profile platform --list`, or `pnpm verify --profile windows --list` to inspect each plan. Individual gates remain available as their own scripts, such as `pnpm typecheck` or `pnpm test:byok`, while you iterate.
4141

4242
CI writes per-gate timing and exit metadata to the Job Summary and a seven-day `verification-<os>-node-<version>-<attempt>` artifact. For a local report, set `MCODE_VERIFY_REPORT_DIR` to a directory outside the repository. Reports distinguish `PASS`, `FAIL`, intentional `SKIP`, and `NOT_RUN` after a failure. JSON is checkpointed before and after each gate; a cancelled run may leave `RUNNING`, which is not a pass. If installation fails before verification starts, no verification report is available. Reports do not collect command output, environment variables, or runtime data; read the corresponding gate's job log for failure details, including the existing bounded BYOK timeout diagnostics. CI jobs have a 15-minute verification limit and a 10-minute release-audit limit.
4343

4444
Existing README files, `CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md`, Markdown under `docs/`, and media directly under `docs/assets/` use the `docs` profile when they are the only changed paths. That profile checks the source inventory and generated paths, exports the committed source, and tests release tooling. History and source-snapshot secret scans still run; platform builds and distribution scans are skipped. Mixed changes, unknown paths, missing comparisons, and any `release/` inventory change get full CI. Documentation-only changes skip the platform matrix entirely. The `verification` aggregate check always runs and rejects failed, cancelled, or unexpectedly skipped jobs. Use it together with `source-history-artifact` as required checks when configuring branch protection; this repository's automation does not change administrative settings.
4545

46-
`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.
46+
`Node compatibility` runs weekly and on demand against macOS and Linux with Node 22.19.0, 24.2.0, 25 and 26. Windows remains excluded from this matrix; the PR Windows contract is a focused Node 24 check, not a substitute for the full compatibility 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

4848
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

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,11 @@ The [small, reproducible project](examples/clamp) is the same task used in the d
221221

222222
## Build from source
223223

224-
To develop MCode or run this source checkout, you need Git, **Node.js 22.19+ (22.x), 24.2+ (24.x), 25, or 26**, and **pnpm 9.12.0**.
225-
224+
To develop MCode or run this source checkout, you need Git, **Node.js 22.19+ (22.x), 24.2+ (24.x), 25, or 26**, and **pnpm 9.12.0**. On Windows, keep the checkout on a local NTFS volume and outside cloud-synced folders; the preflight command below checks the volume before pnpm creates workspace links.
226225
```bash
227226
git clone https://github.com/MiniMax-AI/minimax-code.git
228227
cd minimax-code
228+
node scripts/check-windows-source-location.mjs
229229
pnpm install --frozen-lockfile
230230
pnpm build
231231
pnpm mcode

‎README_ZH.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,11 @@ profile 使用 `~/.minimax-<profile>`;`MINIMAX_DATA_DIR` 或 `MAVIS_DATA_DIR`
221221

222222
## 从源码构建
223223

224-
开发 MCode 或运行本仓库源码需要 Git、Node.js **22.19+(22 系列)、24.2+(24 系列)、25 或 26**,以及 **pnpm 9.12.0**。
225-
224+
开发 MCode 或运行本仓库源码需要 Git、Node.js **22.19+(22 系列)、24.2+(24 系列)、25 或 26**,以及 **pnpm 9.12.0**。在 Windows 上,请将源码放在本地 NTFS 卷上,并避开云同步目录;下面的预检命令会在 pnpm 创建 workspace link 前检查卷类型。
226225
```bash
227226
git clone https://github.com/MiniMax-AI/minimax-code.git
228227
cd minimax-code
228+
node scripts/check-windows-source-location.mjs
229229
pnpm install --frozen-lockfile
230230
pnpm build
231231
pnpm mcode

‎docs/installation.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ This guide builds the 0.4.12 source preview. Workspace/local build manifests rem
4141

4242
For a source build, you need Git, Node.js 22.19+ (22.x), 24.2+ (24.x), 25, or 26, and pnpm 9.12.0. Regular CI uses Node.js 24 across Linux and macOS. The weekly and manual compatibility matrix covers Node.js 22.19.0, 24.2.0, 25, and 26 on both platforms. Windows CI and source-candidate validation are temporarily paused while their checks are made reliable. Initial installation and build require access to public npm.
4343

44+
On Windows, check out this repository on a local NTFS volume before running `pnpm install`. The repository uses pnpm workspace links for vendored packages, and those links require NTFS junctions. FAT32/exFAT volumes, network shares, and other non-local Windows volumes cannot create the required junctions. The preflight command below verifies the volume and stops with a clear message before pnpm creates workspace links; run it immediately before `pnpm install`. A local NTFS volume can still contain a cloud-synced folder, which the preflight cannot identify reliably; keep the checkout outside OneDrive, Google Drive, Dropbox, and similar synced folders.
45+
4446
Node 24.0 and 24.1 are unsupported: their bundled libuv can return inconsistent Windows file identity metadata, causing safe configuration reads to fail. [Node 24.2.0](https://nodejs.org/en/blog/release/v24.2.0) includes libuv 1.51.0 with the [upstream fix](https://github.com/libuv/libuv/commit/82cdfb75f). Use a current patch release of a supported Node line.
4547

4648
```bash
4749
git clone https://github.com/MiniMax-AI/minimax-code.git
4850
cd minimax-code
4951
corepack enable
5052
corepack prepare pnpm@9.12.0 --activate
53+
node scripts/check-windows-source-location.mjs
5154
pnpm install --frozen-lockfile
5255
pnpm build
5356
pnpm mcode --help

‎docs/releasing.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ Before npm or installer distribution, separately validate the published package
8787

8888
The `Source candidate` workflow exports the selected commit without Git history, verifies its receipt, and scans both repository history and the extracted source. Linux and macOS runners authenticate the same archive, install from public npm into fresh stores, and run the archive verification profile.
8989

90-
Windows validation is temporarily paused across source verification, Node compatibility, and source candidates. Candidate reports cover only Linux and macOS; a successful candidate does not establish Windows acceptance. Restore the Windows workflow matrices and the required report set in `scripts/source-candidate.mjs` together when Windows checks are reliable again.
90+
Windows full validation is temporarily paused for Node compatibility and source candidates. Ordinary pull requests run a focused Windows source-verification contract, but candidate reports still cover only Linux and macOS; a successful candidate does not establish Windows acceptance. Restore the Windows compatibility/source-candidate matrices and the required report set in `scripts/source-candidate.mjs` together when those checks are reliable again.
9191

9292
After both platform jobs pass, the workflow creates a `source-candidate-<full-SHA>` artifact containing:
9393

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"test:status-contract": "node scripts/run-vitest-suite.mjs status-contract",
2626
"check:source": "node scripts/source-inventory.mjs",
2727
"test:capabilities": "node scripts/run-vitest-suite.mjs capability",
28+
"test:windows": "node scripts/run-vitest-suite.mjs windows",
2829
"test:artifact": "node --test test/public-artifact.test.mjs",
2930
"test:release-tools": "node --test test/source-sync.test.mjs"
3031
},

‎packages/tui/test/unit/wsl-attachment-paths.test.ts‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ vi.mock("node:child_process", async (importOriginal) => ({
2929
let workspaceDir: string;
3030
let imagePath: string;
3131
const windowsPath = String.raw`D:\Users\demo\Documents\Screen shots\截图.png`;
32+
const skipWslConversion = process.platform === "win32";
3233

3334
beforeEach(async () => {
3435
host.platform.mockReturnValue("linux");
@@ -48,8 +49,11 @@ afterEach(async () => {
4849
await rm(workspaceDir, { recursive: true, force: true });
4950
});
5051

52+
// The mocked wslpath output is the host temp path. Native Windows correctly
53+
// rejects that as a Linux absolute path, so only WSL conversion cases skip there;
54+
// native path handling remains covered.
5155
describe("WSL attachment paths", () => {
52-
it.each([
56+
it.skipIf(process.platform === "win32").each([
5357
windowsPath,
5458
`"${windowsPath}"`,
5559
`'${windowsPath}'`,
@@ -91,7 +95,7 @@ describe("WSL attachment paths", () => {
9195
},
9296
);
9397

94-
it("resolves headless --file through the same conversion before realpath", async () => {
98+
it.skipIf(skipWslConversion)("resolves headless --file through the same conversion before realpath", async () => {
9599
const invocation = await resolveTuiExecInvocation(
96100
"describe",
97101
{ cwd: workspaceDir, file: [windowsPath] },
@@ -109,7 +113,7 @@ describe("WSL attachment paths", () => {
109113
expect(host.executeFile).toHaveBeenCalledOnce();
110114
});
111115

112-
it.each(["WSL_DISTRO_NAME", "WSL_INTEROP", "WSLENV"])(
116+
it.skipIf(skipWslConversion).each(["WSL_DISTRO_NAME", "WSL_INTEROP", "WSLENV"])(
113117
"detects WSL via %s even without a Microsoft kernel name",
114118
async (name) => {
115119
host.release.mockReturnValue("custom-kernel");
@@ -156,7 +160,7 @@ describe("WSL attachment paths", () => {
156160
expect(host.executeFile).not.toHaveBeenCalled();
157161
});
158162

159-
it.each([
163+
it.skipIf(skipWslConversion).each([
160164
String.raw`d:\Screen shots\$(touch marker);'截图'.png`,
161165
String.raw`\\server\share\截图.png`,
162166
])("passes Windows paths as a literal argument: %s", async (reference) => {
@@ -221,7 +225,7 @@ describe("WSL attachment paths", () => {
221225
},
222226
);
223227

224-
it("still rejects missing files and directories after conversion", async () => {
228+
it.skipIf(skipWslConversion)("still rejects missing files and directories after conversion", async () => {
225229
host.executeFile.mockResolvedValue({
226230
stdout: `${join(workspaceDir, "missing.png")}\n`,
227231
});

‎release/public-source.json‎

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { execFileSync } from "node:child_process";
2+
import { fileURLToPath } from "node:url";
3+
import path from "node:path";
4+
5+
const LOCAL_NTFS_REQUIREMENT =
6+
"Windows source checkouts must be on a local NTFS volume. pnpm workspace links require NTFS junctions; FAT32/exFAT volumes and network shares are not supported. Keep cloud-synced folders outside the checkout.";
7+
8+
function fail(reason) {
9+
return { ok: false, reason: `${LOCAL_NTFS_REQUIREMENT} ${reason}` };
10+
}
11+
12+
/**
13+
* Validate the Windows volume before pnpm attempts to create workspace links.
14+
*
15+
* The function is exported so the platform-specific policy can be tested without
16+
* requiring a Windows host. Non-Windows platforms are intentionally a no-op.
17+
*/
18+
export function checkWindowsSourceLocation({
19+
platform = process.platform,
20+
cwd = process.cwd(),
21+
execFile = execFileSync,
22+
allowNonFixed = process.env.GITHUB_ACTIONS === "true",
23+
} = {}) {
24+
if (platform !== "win32") return { ok: true, skipped: true };
25+
26+
const pathApi = platform === "win32" ? path.win32 : path;
27+
const root = pathApi.parse(pathApi.resolve(cwd)).root;
28+
if (!/^[a-z]:\\$/iu.test(root) || root.startsWith("\\\\")) {
29+
return fail("The checkout root is not a local drive-letter path.");
30+
}
31+
// `fsutil` accepts a drive letter more reliably than a root path with a
32+
// trailing backslash across Windows runner images.
33+
const volume = root.slice(0, 2);
34+
35+
let driveType;
36+
let volumeInfo;
37+
try {
38+
driveType = execFile("fsutil", ["fsinfo", "drivetype", volume], {
39+
encoding: "utf8",
40+
stdio: ["ignore", "pipe", "pipe"],
41+
});
42+
volumeInfo = execFile("fsutil", ["fsinfo", "volumeinfo", volume], {
43+
encoding: "utf8",
44+
stdio: ["ignore", "pipe", "pipe"],
45+
});
46+
} catch (error) {
47+
const detail = error instanceof Error ? ` (${error.message})` : "";
48+
return fail(`Windows could not verify the checkout volume${detail}.`);
49+
}
50+
51+
if (!allowNonFixed && !/:\s*DRIVE_FIXED(?:\r?\n|$)/iu.test(driveType)) {
52+
return fail("The checkout volume is not a local fixed drive.");
53+
}
54+
if (!/:\s*NTFS(?:\r?\n|$)/iu.test(volumeInfo)) {
55+
return fail("The checkout volume is not formatted as NTFS.");
56+
}
57+
58+
return { ok: true, skipped: false };
59+
}
60+
61+
export function runWindowsSourceLocationCheck({
62+
platform = process.platform,
63+
cwd = process.cwd(),
64+
execFile = execFileSync,
65+
allowNonFixed = process.env.GITHUB_ACTIONS === "true",
66+
report = (message) => console.error(`[source-check] ${message}`),
67+
} = {}) {
68+
const result = checkWindowsSourceLocation({ platform, cwd, execFile, allowNonFixed });
69+
if (!result.ok) report(result.reason);
70+
return result;
71+
}
72+
73+
const scriptPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
74+
if (scriptPath === fileURLToPath(import.meta.url)) {
75+
const result = runWindowsSourceLocationCheck();
76+
if (!result.ok) process.exitCode = 1;
77+
}

0 commit comments

Comments
 (0)