blob: b9f934d32673ad8f8b6b6f2997a2f7bd2fbb5075 [file]
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
const { packageWindowsX64, runCommand } = await import(
new URL('package-windows-x64.mjs', import.meta.url)
);
const {
assertWindowsProductVersion,
powerShellLiteral,
readPeMachine,
verifyPackagedWindowsApp,
verifyWindowsX64Release,
} = await import(new URL('verify-windows-x64.mjs', import.meta.url));
const desktopManifest = JSON.parse(
await readFile(new URL('../apps/desktop/package.json', import.meta.url), 'utf8'),
);
function peImage(machine) {
const image = Buffer.alloc(0x100);
image.writeUInt8(0x4d, 0);
image.writeUInt8(0x5a, 1);
image.writeUInt32LE(0x80, 0x3c);
image.write('PE\0\0', 0x80, 'latin1');
image.writeUInt16LE(machine, 0x84);
return image;
}
function packagedAppOptions(overrides = {}) {
return {
// rcedit writes the four-part Windows form of the manifest version, so this
// is what a correct packaged executable actually reports.
run: async (command) => {
if (command === 'powershell')
return { stdout: `${desktopManifest.version}.0\r\n`, stderr: '' };
return { stdout: '', stderr: '' };
},
requirePath: async () => {},
forbidPath: async () => {},
readMachine: async () => 0x8664,
smokeRenderer: async () => {},
...overrides,
};
}
test('npm is spawned through the shell on Windows, the only way npm.cmd resolves', async () => {
// libuv's Windows process launcher tries only .com and .exe and ignores
// PATHEXT, so a bare `npm` is ENOENT there — the packaging script would die
// on its first command. This cannot be caught by running the suite on macOS,
// so the spawn strategy itself is what gets pinned.
const calls = [];
const spawnProcess = (command, args, options) => {
calls.push({ command, options });
const child = new EventEmitter();
setImmediate(() => child.emit('exit', 0, null));
return child;
};
await runCommand('npm', ['run', 'build'], { spawnProcess, platform: 'win32' });
await runCommand('npm', ['run', 'build'], { spawnProcess, platform: 'darwin' });
assert.equal(calls[0].options.shell, true);
assert.equal(calls[1].options.shell, false);
});
test('Windows packaging fails closed on hosts that cannot produce the release', async () => {
await assert.rejects(packageWindowsX64({ platform: 'darwin', arch: 'x64' }), /Windows x64 host/);
await assert.rejects(packageWindowsX64({ platform: 'win32', arch: 'arm64' }), /Windows x64 host/);
});
test('Windows packaging regenerates bundled Git evidence before electron-builder consumes it', async () => {
const commands = [];
await packageWindowsX64({
platform: 'win32',
arch: 'x64',
run: async (command, args) => commands.push([command, ...args].join(' ')),
remove: async () => {},
assertFile: async () => {},
});
assert.deepEqual(commands, [
'npm run clean',
'npm run build',
'npm run prepare:bundled-git',
'npm run check:release',
'npm --workspace @maka/desktop run package:windows-x64',
]);
});
test('Windows verification fails closed on the host, the input, and the architecture', async () => {
await assert.rejects(
verifyWindowsX64Release('C:/Maka.exe', { platform: 'darwin' }),
/requires Windows/,
);
await assert.rejects(verifyWindowsX64Release('', { platform: 'win32' }), /Usage:/);
await assert.rejects(
verifyWindowsX64Release('C:/Maka.zip', { platform: 'win32' }),
/NSIS installer/,
);
// The app directory the ZIP was archived from has to be there; a release
// directory without it is a half-built one, not something to verify.
await assert.rejects(
verifyWindowsX64Release('C:/does-not-exist/Maka.exe', { platform: 'win32' }),
/ENOENT/,
);
await assert.rejects(
verifyPackagedWindowsApp('C:/app', packagedAppOptions({ readMachine: async () => 0x014c })),
/must be x64/,
);
});
test('the Windows product version resource is read in the four-part form rcedit writes', () => {
// The release version is the first three parts; the fourth is a build number.
// Comparing the raw string against the manifest version would reject every
// real executable, which is exactly what a mock-shaped oracle hides.
assertWindowsProductVersion('0.1.5.0', '0.1.5');
assertWindowsProductVersion('0.1.5.42\r\n', '0.1.5');
assertWindowsProductVersion('0.2.0.0', '0.2.0-beta.1');
for (const wrong of ['0.1.4.0', '0.1.5', '0.1.5.0.1', '0.1.5.x', '']) {
assert.throws(() => assertWindowsProductVersion(wrong, '0.1.5'), /Expected app version/);
}
});
test('a packaged Windows app whose version does not match the manifest is rejected', async () => {
await assert.rejects(
verifyPackagedWindowsApp(
'C:/app',
packagedAppOptions({
run: async (command) => {
if (command === 'powershell') return { stdout: '0.0.0.0\r\n', stderr: '' };
return { stdout: '', stderr: '' };
},
}),
),
/Expected app version/,
);
});
test('the packaged Windows app is checked for every unsigned helper that could still be in a tree', async () => {
// Same reason as the macOS check: `apps/desktop/resources/bin` is gitignored,
// so a helper a developer prepared once is still on their machine and would
// be packaged without anything noticing.
const forbidden = [];
await assert.rejects(
verifyPackagedWindowsApp(
'C:/app',
packagedAppOptions({
forbidPath: async (path) => {
forbidden.push(path);
},
// Everything before the architecture check has to pass, so the failure
// that ends this run is the one after the forbids.
readMachine: async () => 0x014c,
}),
),
/must be x64/,
);
// The exact set, not "some path ends with each name": a helper is forbidden in
// both the directories it could be staged from, and matching either one would
// let the other be dropped without a test noticing.
assert.deepEqual(forbidden.map((path) => path.slice('C:/app/resources/'.length)).sort(), [
'bin/cua-driver',
'bin/maka-cu',
'licenses/officecli',
'tools/cua-driver',
'tools/maka-cu',
'tools/officecli',
]);
});
test('paths reach PowerShell as literals, not as strings it expands', () => {
// A double-quoted PowerShell string expands $, and the path being verified is
// an argument this script does not choose.
assert.equal(powerShellLiteral('D:/a/Maka.exe'), "'D:/a/Maka.exe'");
assert.equal(powerShellLiteral("C:/it's/Maka.exe"), "'C:/it''s/Maka.exe'");
assert.equal(powerShellLiteral('C:/$env:TEMP/Maka.exe'), "'C:/$env:TEMP/Maka.exe'");
});
test('the PE machine is read from the executable header', async () => {
const directory = await mkdtemp(join(tmpdir(), 'maka-pe-'));
try {
const amd64 = join(directory, 'amd64.exe');
const i386 = join(directory, 'i386.exe');
const notPe = join(directory, 'not-pe.exe');
await writeFile(amd64, peImage(0x8664));
await writeFile(i386, peImage(0x014c));
await writeFile(notPe, Buffer.alloc(0x100));
assert.equal(await readPeMachine(amd64), 0x8664);
assert.equal(await readPeMachine(i386), 0x014c);
await assert.rejects(readPeMachine(notPe), /not a PE image/);
} finally {
await rm(directory, { recursive: true, force: true });
}
});