Skip to content

Commit b88172e

Browse files
committed
test: add Nub loader recipe
1 parent 5377bac commit b88172e

6 files changed

Lines changed: 183 additions & 0 deletions

File tree

examples/using-pm2-and-transpilers/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,32 @@ $ pm2 install typescript
1313
$ pm2 start http.ts
1414
```
1515

16+
### Node with `@nubjs/loader`
17+
18+
Install the loader in the application. Keep PM2 on its normal Node interpreter. The loader needs Node.js 18.19 or later. PM2's daemon and cluster workers use that Node executable.
19+
20+
```sh
21+
npm install --save-dev @nubjs/loader@0.8.3
22+
```
23+
24+
Configure `node_args` as an array so PM2 preloads the loader before its fork and cluster containers start:
25+
26+
```js
27+
module.exports = {
28+
apps: [{
29+
name: 'api',
30+
script: './src/server.ts',
31+
interpreter: 'node',
32+
node_args: ['--import', '@nubjs/loader'],
33+
instances: 2,
34+
exec_mode: 'cluster',
35+
wait_ready: true,
36+
}],
37+
};
38+
```
39+
40+
Start the ecosystem file with `pm2 start ecosystem.config.cjs`. PM2 keeps its Node process containers, IPC readiness, reload behavior, and cluster support. The loader does not provision or select a Node version. Retain the application's TypeScript type-check in CI. Do not set `interpreter` to a launcher executable. PM2 treats it as an arbitrary interpreter instead of using its Node containers.
41+
1642
## Livescript
1743

1844

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"debug": "4.4.3"
149149
},
150150
"devDependencies": {
151+
"@nubjs/loader": "0.8.3",
151152
"express": "^4.21.0",
152153
"mocha": "^11.7.0",
153154
"should": "^13.2.3"

test/e2e.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ runTest ./test/e2e/cli/start-app.sh
1717
runTest ./test/e2e/cli/operate-regex.sh
1818
#runTest ./test/e2e/cli/bun.sh
1919
runTest ./test/e2e/cli/typescript.sh
20+
runTest ./test/e2e/cli/nub-loader.sh
2021
runTest ./test/e2e/cli/app-configuration.sh
2122
runTest ./test/e2e/cli/binary.sh
2223
runTest ./test/e2e/cli/startOrX.sh

test/e2e/cli/nub-loader.sh

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env bash
2+
3+
SRC=$(cd $(dirname "$0"); pwd)
4+
source "${SRC}/../include.sh"
5+
6+
fixture="$file_path/nub-loader"
7+
log_file="$fixture/loader.log"
8+
9+
cleanup() {
10+
$pm2 delete all >/dev/null 2>&1 || true
11+
$pm2 kill >/dev/null 2>&1 || true
12+
}
13+
trap cleanup EXIT
14+
15+
pids_for() {
16+
$pm2 jlist | $node -e '
17+
let input = "";
18+
process.stdin.on("data", (chunk) => input += chunk);
19+
process.stdin.on("end", () => {
20+
console.log(JSON.parse(input)
21+
.filter((app) => app.name === "nub-loader-cluster")
22+
.map((app) => app.pid)
23+
.sort((left, right) => left - right)
24+
.join(" "));
25+
});
26+
'
27+
}
28+
29+
cd "$fixture"
30+
> "$log_file"
31+
32+
$pm2 start ecosystem.config.cjs
33+
should 'should start the TypeScript fork and cluster apps' 'online' 3
34+
35+
expected_node=$($node -p 'process.versions.node')
36+
grep -F '"mode":"pm2"' "$log_file"
37+
spec 'Should transform non-erasable TypeScript syntax'
38+
grep -F "\"cwd\":\"$fixture\"" "$log_file"
39+
spec 'Should resolve the loader from the application cwd'
40+
grep -F "\"node\":\"$expected_node\"" "$log_file"
41+
spec 'Should run workers on the daemon Node version'
42+
grep -F 'service.ts' "$log_file"
43+
spec 'Should preserve TypeScript source maps'
44+
45+
old_cluster_pids=$(pids_for)
46+
[ "$(echo "$old_cluster_pids" | wc -w)" -eq 2 ]
47+
spec 'Should record both cluster worker PIDs before reload'
48+
49+
PM2_BIN="$pm2" $node <<'NODE'
50+
const { spawn } = require('child_process');
51+
const http = require('http');
52+
53+
let failures = 0;
54+
let pending = 0;
55+
56+
function request() {
57+
pending += 1;
58+
const request = http.get('http://127.0.0.1:45678', (response) => {
59+
if (response.statusCode !== 200)
60+
failures += 1;
61+
response.resume();
62+
response.once('end', () => pending -= 1);
63+
});
64+
request.setTimeout(1000, () => request.destroy());
65+
request.once('error', () => {
66+
failures += 1;
67+
pending -= 1;
68+
});
69+
}
70+
71+
request();
72+
const reload = spawn(process.env.PM2_BIN, ['reload', 'nub-loader-cluster'], { stdio: 'inherit' });
73+
const probes = setInterval(request, 10);
74+
75+
reload.once('close', (code) => {
76+
clearInterval(probes);
77+
setTimeout(() => {
78+
if (code !== 0 || failures > 0 || pending !== 0)
79+
process.exit(1);
80+
}, 100);
81+
});
82+
NODE
83+
spec 'Should serve requests without interruption during cluster reload'
84+
85+
should 'should keep both TypeScript cluster workers online after reload' 'online' 3
86+
87+
new_cluster_pids=$(pids_for)
88+
[ "$old_cluster_pids" != "$new_cluster_pids" ]
89+
spec 'Should replace cluster workers after readiness reload'
90+
grep -F '"event":"shutdown"' "$log_file"
91+
spec 'Should deliver graceful shutdown to replaced workers'
92+
93+
$pm2 delete all
94+
sleep 1
95+
for pid in $old_cluster_pids $new_cluster_pids; do
96+
! kill -0 "$pid" 2>/dev/null
97+
done
98+
spec 'Should not leave replaced cluster workers running'
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const path = require('path');
2+
3+
const app = {
4+
script: './service.ts',
5+
cwd: __dirname,
6+
interpreter: 'node',
7+
node_args: ['--import', '@nubjs/loader'],
8+
wait_ready: true,
9+
listen_timeout: 5000,
10+
kill_timeout: 1000,
11+
env: {
12+
PORT: 45678,
13+
},
14+
merge_logs: true,
15+
log_file: path.join(__dirname, 'loader.log'),
16+
};
17+
18+
module.exports = {
19+
apps: [
20+
{
21+
...app,
22+
name: 'nub-loader-fork',
23+
},
24+
{
25+
...app,
26+
name: 'nub-loader-cluster',
27+
instances: 2,
28+
exec_mode: 'cluster',
29+
},
30+
],
31+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { createServer } = require('node:http');
2+
3+
enum ContainerMode {
4+
Pm2 = 'pm2',
5+
}
6+
7+
const sourceMapFrame = new Error('source map check').stack?.split('\n')[1]?.trim();
8+
9+
console.log(JSON.stringify({
10+
event: 'ready',
11+
mode: ContainerMode.Pm2,
12+
cwd: process.cwd(),
13+
node: process.versions.node,
14+
sourceMapFrame,
15+
}));
16+
17+
const server = createServer((_request, response) => {
18+
response.end('ok');
19+
});
20+
21+
process.on('SIGINT', () => {
22+
console.log(JSON.stringify({ event: 'shutdown', pid: process.pid }));
23+
server.close(() => process.exit(0));
24+
});
25+
26+
server.listen(Number(process.env.PORT), () => process.send?.('ready'));

0 commit comments

Comments
 (0)