Skip to content

Commit 68284bb

Browse files
authored
fix: query structured Windows source volume properties (#383)
1 parent c593d3d commit 68284bb

4 files changed

Lines changed: 112 additions & 60 deletions

File tree

‎docs/installation.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ 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.
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`. It queries structured volume properties through Windows PowerShell and CIM, without requiring an elevated shell or parsing localized `fsutil` output. If PowerShell or CIM is unavailable or blocked, verification fails with the query error. 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.
4545

4646
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.
4747

‎scripts/check-windows-source-location.mjs‎

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,47 @@ export function checkWindowsSourceLocation({
2828
if (!/^[a-z]:\\$/iu.test(root) || root.startsWith("\\\\")) {
2929
return fail("The checkout root is not a local drive-letter path.");
3030
}
31-
// `fsutil` accepts a drive letter more reliably than a root path with a
32-
// trailing backslash across Windows runner images.
3331
const volume = root.slice(0, 2);
3432

35-
let driveType;
36-
let volumeInfo;
33+
let disk;
3734
try {
38-
driveType = execFile("fsutil", ["fsinfo", "drivetype", volume], {
39-
encoding: "utf8",
40-
stdio: ["ignore", "pipe", "pipe"],
41-
});
42-
volumeInfo = execFile("fsutil", ["fsinfo", "volumeinfo", volume], {
35+
// CIM exposes numeric drive types and filesystem identifiers independent of
36+
// display language. fsutil prints localized text (e.g. "E: - Fixed Drive")
37+
// and its volumeinfo command can require elevation. Query CIM directly so
38+
// ordinary contributor shells use the same structured policy as CI.
39+
// Only the validated drive letter above is interpolated, never the cwd.
40+
const output = execFile("powershell.exe", [
41+
"-NoLogo", "-NoProfile", "-NonInteractive", "-Command",
42+
"$ErrorActionPreference = 'Stop'; " +
43+
`Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='${volume}'" | ` +
44+
"Select-Object DeviceID, DriveType, FileSystem | ConvertTo-Json -Compress",
45+
], {
4346
encoding: "utf8",
4447
stdio: ["ignore", "pipe", "pipe"],
48+
timeout: 15_000,
49+
windowsHide: true,
4550
});
51+
disk = JSON.parse(output.trim());
52+
if (
53+
!disk || Array.isArray(disk) ||
54+
typeof disk.DeviceID !== "string" ||
55+
disk.DeviceID.toUpperCase() !== volume.toUpperCase() ||
56+
!Number.isInteger(disk.DriveType) ||
57+
disk.DriveType < 0 || disk.DriveType > 6 ||
58+
typeof disk.FileSystem !== "string"
59+
) {
60+
throw new Error("CIM returned incomplete or unexpected volume information");
61+
}
4662
} catch (error) {
4763
const detail = error instanceof Error ? ` (${error.message})` : "";
4864
return fail(`Windows could not verify the checkout volume${detail}.`);
4965
}
5066

51-
if (!allowNonFixed && !/:\s*DRIVE_FIXED(?:\r?\n|$)/iu.test(driveType)) {
67+
// Win32_LogicalDisk.DriveType: 3 = local disk, 4 = network drive.
68+
if (!allowNonFixed && disk.DriveType !== 3) {
5269
return fail("The checkout volume is not a local fixed drive.");
5370
}
54-
if (!/:\s*NTFS(?:\r?\n|$)/iu.test(volumeInfo)) {
71+
if (disk.FileSystem.toUpperCase() !== "NTFS") {
5572
return fail("The checkout volume is not formatted as NTFS.");
5673
}
5774

‎test/source-sync.test.mjs‎

Lines changed: 82 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -22,65 +22,100 @@ import { compareRuns, exitCodeForStatus, renderReport, spread, validateRun, vali
2222
import { copyMcodeToolsArtifact, downloadMcodeToolsArtifact, MCODE_TOOLS_ARTIFACT } from '../scripts/lib/mcode-tools-artifact.mjs';
2323
import { checkWindowsSourceLocation, runWindowsSourceLocationCheck } from '../scripts/check-windows-source-location.mjs';
2424

25-
test('Windows source preflight accepts localized fsutil labels', () => {
26-
const result = checkWindowsSourceLocation({
27-
platform: 'win32',
28-
cwd: 'C:\\repo',
29-
execFile: (_command, args) => args[1] === 'drivetype'
30-
? 'Laufwerkstyp: DRIVE_FIXED\n'
31-
: 'Dateisystemname: NTFS\n',
32-
});
33-
assert.deepEqual(result, { ok: true, skipped: false });
25+
test('Windows source preflight accepts local NTFS without parsing fsutil display text', () => {
26+
for (const fsutilOutput of ['E: - Fixed Drive\r\n', 'E: - Festplatte\r\n', new Error('Error 5: Access is denied')]) {
27+
const calls = [];
28+
const result = checkWindowsSourceLocation({
29+
platform: 'win32', cwd: 'E:\\src\\minimax-code', allowNonFixed: false,
30+
execFile: (command, args, options) => {
31+
calls.push(command);
32+
if (command === 'fsutil') {
33+
if (fsutilOutput instanceof Error) throw fsutilOutput;
34+
return args[1] === 'drivetype' ? fsutilOutput : 'File System Name : NTFS\r\n';
35+
}
36+
assert.equal(command, 'powershell.exe');
37+
assert.deepEqual(args.slice(0, 4), ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command']);
38+
assert.match(args[4], /\$ErrorActionPreference\s*=\s*'Stop'/);
39+
assert.match(args[4], /Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='E:'"/);
40+
assert.match(args[4], /ConvertTo-Json -Compress/);
41+
assert.equal(options.encoding, 'utf8');
42+
assert.ok(options.timeout > 0);
43+
return '\r\n{"DeviceID":"E:","DriveType":3,"FileSystem":"NTFS"}\r\n';
44+
},
45+
});
46+
assert.deepEqual(result, { ok: true, skipped: false });
47+
assert.deepEqual(calls, ['powershell.exe']);
48+
}
3449
});
3550

36-
test('Windows source preflight requires a local NTFS checkout', () => {
37-
const calls = [];
38-
const execFile = (command, args) => {
39-
calls.push([command, args]);
40-
if (args[1] === 'drivetype') return 'Drive type is : DRIVE_FIXED\n';
41-
return 'File System Name : NTFS\n';
42-
};
43-
assert.deepEqual(
44-
checkWindowsSourceLocation({ platform: 'win32', cwd: 'C:\\repo', execFile }),
45-
{ ok: true, skipped: false },
46-
);
47-
assert.deepEqual(calls.map(([command, args]) => [command, args[1], args[2]]), [
48-
['fsutil', 'drivetype', 'C:'],
49-
['fsutil', 'volumeinfo', 'C:'],
50-
]);
51+
const checkWindowsVolume = (disk, allowNonFixed = false) => checkWindowsSourceLocation({
52+
platform: 'win32', cwd: 'C:\\repo', allowNonFixed,
53+
execFile: () => JSON.stringify({ DeviceID: 'C:', DriveType: 3, FileSystem: 'NTFS', ...disk }),
5154
});
5255

53-
test('Windows source preflight rejects unsupported volumes clearly', () => {
54-
const run = (driveType, volumeInfo, cwd = 'C:\\repo', allowNonFixed = false) => checkWindowsSourceLocation({
55-
platform: 'win32', cwd, allowNonFixed,
56-
execFile: (_command, args) => args[1] === 'drivetype' ? driveType : volumeInfo,
57-
});
58-
assert.match(run('Drive type is : DRIVE_REMOTE\n', 'File System Name : NTFS\n').reason, /not a local fixed drive/);
59-
assert.deepEqual(
60-
run('Drive type is : DRIVE_REMOTE\n', 'File System Name : NTFS\n', 'C:\\repo', true),
61-
{ ok: true, skipped: false },
62-
);
63-
assert.match(run('Drive type is : DRIVE_FIXED\n', 'File System Name : NTFS\n', '\\\\server\\share\\repo').reason, /not a local drive-letter path/);
56+
test('Windows source preflight rejects unsupported drive types and file systems', () => {
57+
for (const DriveType of [0, 1, 2, 4, 5, 6]) {
58+
assert.match(checkWindowsVolume({ DriveType }).reason, /not a local fixed drive/);
59+
}
60+
for (const FileSystem of ['FAT32', 'exFAT', 'ReFS', '']) {
61+
for (const allowNonFixed of [false, true]) {
62+
assert.match(checkWindowsVolume({ FileSystem }, allowNonFixed).reason, /not formatted as NTFS/);
63+
}
64+
}
65+
// Preserve the existing explicit runner override, but never use it in the
66+
// positive fixed-drive regression or native-host acceptance case.
67+
assert.deepEqual(checkWindowsVolume({ DriveType: 4 }, true), { ok: true, skipped: false });
68+
});
69+
70+
test('Windows source preflight rejects UNC paths before querying Windows', () => {
71+
for (const allowNonFixed of [false, true]) {
72+
const result = checkWindowsSourceLocation({
73+
platform: 'win32', cwd: '\\\\server\\share\\repo', allowNonFixed,
74+
execFile: () => assert.fail('must not query a UNC path'),
75+
});
76+
assert.match(result.reason, /not a local drive-letter path/);
77+
}
78+
});
79+
80+
test('Windows source preflight fails closed on missing, malformed or mismatched CIM data', () => {
81+
for (const output of [
82+
'', 'not JSON', 'null', '[]', '{}',
83+
'{"DeviceID":"D:","DriveType":3,"FileSystem":"NTFS"}',
84+
'{"DeviceID":"C:","DriveType":"3","FileSystem":"NTFS"}',
85+
'{"DeviceID":"C:","DriveType":3,"FileSystem":null}',
86+
'{"DeviceID":"C:","DriveType":null,"FileSystem":"NTFS"}',
87+
'[{"DeviceID":"C:","DriveType":3,"FileSystem":"NTFS"}]',
88+
]) {
89+
for (const allowNonFixed of [false, true]) {
90+
const result = checkWindowsSourceLocation({
91+
platform: 'win32', cwd: 'C:\\repo', allowNonFixed, execFile: () => output,
92+
});
93+
assert.equal(result.ok, false, output);
94+
assert.match(result.reason, /could not verify the checkout volume/);
95+
}
96+
}
97+
assert.deepEqual(checkWindowsVolume({ DeviceID: 'c:', FileSystem: 'ntfs' }), { ok: true, skipped: false });
6498
});
6599

66100
test('Windows source preflight is a no-op on non-Windows platforms', () => {
67101
assert.deepEqual(
68-
checkWindowsSourceLocation({ platform: 'linux', execFile: () => assert.fail('must not run fsutil') }),
102+
checkWindowsSourceLocation({ platform: 'linux', execFile: () => assert.fail('must not query Windows') }),
69103
{ ok: true, skipped: true },
70104
);
71105
});
72106

73-
test('Windows source preflight propagates a failed check to the CLI', () => {
74-
const messages = [];
75-
const result = runWindowsSourceLocationCheck({
76-
platform: 'win32',
77-
cwd: 'C:\\repo',
78-
execFile: () => { throw new Error('fsutil unavailable'); },
79-
report: (message) => messages.push(message),
80-
});
81-
assert.equal(result.ok, false);
82-
assert.equal(messages.length, 1);
83-
assert.match(messages[0], /fsutil unavailable/);
107+
test('Windows source preflight reports command, permission and timeout failures to the CLI', () => {
108+
for (const error of [new Error('spawn powershell.exe ENOENT'), new Error('Access is denied'), new Error('ETIMEDOUT')]) {
109+
const messages = [];
110+
const result = runWindowsSourceLocationCheck({
111+
platform: 'win32', cwd: 'C:\\repo', allowNonFixed: false,
112+
execFile: () => { throw error; },
113+
report: (message) => messages.push(message),
114+
});
115+
assert.equal(result.ok, false);
116+
assert.equal(messages.length, 1);
117+
assert.ok(messages[0].includes(error.message));
118+
}
84119
});
85120

86121
test('artifact download recovers from TLS reset and interrupted response bodies', async () => {

‎test/windows-contract.test.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ afterEach(() => {
2020

2121
describe.skipIf(process.platform !== "win32")("Windows source contract", () => {
2222
it("accepts the Windows checkout on a local NTFS volume", () => {
23-
assert.deepEqual(checkWindowsSourceLocation(), {
23+
assert.deepEqual(checkWindowsSourceLocation({ allowNonFixed: false }), {
2424
ok: true,
2525
skipped: false,
2626
});

0 commit comments

Comments
 (0)