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: 9 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,18 @@ jobs:
architecture: ${{ matrix.node_arch }}

- name: Install dependencies
# Node 26's ThinLTO config makes node-gyp emit the lld-only
# /opt:lldltojobs that MSVC link.exe rejects (LNK1117); the install
# script source-builds the addon on Windows. Force LTO off.
env:
npm_config_enable_thin_lto: 'false'
npm_config_enable_lto: 'false'
run: npm ci

- name: Prebuild package
env:
npm_config_enable_thin_lto: 'false'
npm_config_enable_lto: 'false'
run: npm run ${{ matrix.command }} -- ${{ matrix.args }}

- uses: actions/upload-artifact@v4
Expand Down
14 changes: 11 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [20, 22, 23, 24]
# windows-latest (Server 2025 / VS 18) breaks the node-gyp VS detection
# bundled with older Node; pin to windows-2022 for legacy coverage.
os: [ubuntu-latest, macos-latest, windows-2022]
node-version: [20, 22, 23, 24, 25, 26]
steps:
- uses: actions/checkout@v4

Expand All @@ -28,6 +30,12 @@ jobs:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
# On Windows + Node 26 the install script source-builds the native
# addon; Node 26's ThinLTO config makes node-gyp emit the lld-only
# /opt:lldltojobs, which MSVC link.exe rejects (LNK1117). Force LTO off.
env:
npm_config_enable_thin_lto: 'false'
npm_config_enable_lto: 'false'
run: npm ci

- name: Ensure systemd directory exists (Linux)
Expand All @@ -46,7 +54,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [20, 22, 23, 24]
node-version: [20, 22, 23, 24, 25, 26]
steps:
- uses: actions/checkout@v4

Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"access": "public"
},
"dependencies": {
"nan": "^2.24.0",
"nan": "^2.28.0",
"node-gyp-build": "^4.8.4",
"plist": "^3.1.0"
},
Expand Down Expand Up @@ -98,15 +98,15 @@
"prepublishOnly": "npm run build",
"test": "node --test",
"semantic-release": "semantic-release",
"build:gyp": "prebuildify --napi false --strip --target 20.20.0 --target 21.7.3 --target 22.22.0 --target 23.11.1 --target 24.13.0 --target 25.3.0",
"build:gyp-cross": "prebuildify-cross --napi false --strip --target 20.20.0 --target 21.7.3 --target 22.22.0 --target 23.11.1 --target 24.13.0 --target 25.3.0",
"build:gyp": "prebuildify --napi false --strip --target 20.20.0 --target 21.7.3 --target 22.22.0 --target 23.11.1 --target 24.13.0 --target 25.3.0 --target 26.8.1",
"build:gyp-cross": "prebuildify-cross --napi false --strip --target 20.20.0 --target 21.7.3 --target 22.22.0 --target 23.11.1 --target 24.13.0 --target 25.3.0 --target 26.8.1",
"native_install": "node-gyp-build",
"install": "node scripts/install.mjs",
"prepare": "is-ci || husky install"
},
"overrides": {
"prebuildify": {
"node-abi": "^4.24.0"
"node-abi": "^4.35.0"
}
}
}
10 changes: 9 additions & 1 deletion scripts/install.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));

if (platform() === 'win32') {
// The native addon is only needed on Windows, so the build normally runs only
// there. OS_SERVICE_FORCE_NATIVE_INSTALL forces it anywhere (manual rebuilds,
// CI verifying the build/exit-code path on a non-Windows runner).
if (platform() === 'win32' || process.env.OS_SERVICE_FORCE_NATIVE_INSTALL) {
const npmProcess = spawn('npm', ['run', 'native_install'], {
input: 'Windows detected. Installing native module.',
stdio: 'inherit',
Expand All @@ -19,14 +22,19 @@ if (platform() === 'win32') {
npmProcess.on('exit', (code, signal) => {
if (code !== null) {
console.log(`Child process exited with code ${code}`);
// Propagate a failing build; a silent exit 0 ships a broken addon that
// only fails later at require() time.
process.exit(code);
} else if (signal !== null) {
console.log(`Child process was terminated by signal ${signal}`);
process.exit(1);
}
});

// Handle child process errors
npmProcess.on('error', (error) => {
console.error('Error occurred:', error);
process.exit(1);
});

// Forward parent process signals to the child process
Expand Down
54 changes: 54 additions & 0 deletions test/e2e/install-exit-code.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, it } from 'node:test';
import { strictEqual } from 'node:assert';
import { spawn } from 'node:child_process';
import { mkdtemp, mkdir, writeFile, copyFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const installScript = join(__dirname, '..', '..', 'scripts', 'install.mjs');

// Run install.mjs in an isolated project whose `native_install` exits with the
// given code, forcing the (normally Windows-only) build path via
// OS_SERVICE_FORCE_NATIVE_INSTALL so the exit-propagation is exercised on any OS.
function runInstall(nativeInstallExitCode) {
return new Promise((resolvePromise, reject) => {
(async () => {
const dir = await mkdtemp(join(tmpdir(), 'os-service-install-'));
try {
await mkdir(join(dir, 'scripts'), { recursive: true });
await copyFile(installScript, join(dir, 'scripts', 'install.mjs'));
await writeFile(
join(dir, 'package.json'),
JSON.stringify({
name: 'install-exit-code-fixture',
scripts: { native_install: `node -e "process.exit(${nativeInstallExitCode})"` }
})
);
const child = spawn(process.execPath, ['scripts/install.mjs'], {
cwd: dir,
env: { ...process.env, OS_SERVICE_FORCE_NATIVE_INSTALL: '1' },
stdio: 'ignore'
});
child.on('error', reject);
child.on('exit', (code) => { rm(dir, { recursive: true, force: true }).finally(() => resolvePromise(code)); });
} catch (err) {
await rm(dir, { recursive: true, force: true }).catch(() => {});
reject(err);
}
})();
});
}

describe('install.mjs native build exit code', () => {
it('should exit non-zero when the native build fails', async () => {
const code = await runInstall(1);
strictEqual(code, 1);
});

it('should exit zero when the native build succeeds', async () => {
const code = await runInstall(0);
strictEqual(code, 0);
});
});
Loading