@selvajs/cli 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/create.js +13 -2
- package/src/commands/pm2.js +63 -6
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -269,8 +269,19 @@ function buildPackageJson(name /*, values */) {
|
|
|
269
269
|
'@selvajs/selva': 'latest',
|
|
270
270
|
// pm2 lives in the deployment's own node_modules so `selva start` can
|
|
271
271
|
// resolve it via node_modules/.bin/pm2 without a global install. The
|
|
272
|
-
// pm2.js wrapper
|
|
273
|
-
pm2
|
|
272
|
+
// pm2.js wrapper requires this local binary (see pm2Bin()) — we never
|
|
273
|
+
// fall back to a global pm2 because two pm2 binaries managing the
|
|
274
|
+
// same daemon produces persistent CLI-vs-daemon version-skew
|
|
275
|
+
// warnings and stops/restarts that mysteriously hang.
|
|
276
|
+
//
|
|
277
|
+
// PINNED EXACT (no caret) on purpose. PM2's daemon and CLI must be
|
|
278
|
+
// the same version, and the daemon is sticky — once a daemon is
|
|
279
|
+
// running, only `pm2 update` swaps it. A caret range means two
|
|
280
|
+
// deployments scaffolded weeks apart can install different 5.x
|
|
281
|
+
// versions, and any host that briefly ran a different pm2 (e.g. a
|
|
282
|
+
// stray `npm i -g pm2`) is left with a daemon that won't match
|
|
283
|
+
// either. Bump this deliberately, in lockstep with documentation.
|
|
284
|
+
pm2: '5.4.3'
|
|
274
285
|
};
|
|
275
286
|
|
|
276
287
|
// Use `selva` (resolved via node_modules/.bin) in the npm scripts. Running
|
package/src/commands/pm2.js
CHANGED
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
// • `pm2 restart` without --update-env silently keeps the old env, even
|
|
5
5
|
// after the operator edited .env. We always pass --update-env.
|
|
6
6
|
//
|
|
7
|
-
// •
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// • PM2's daemon and CLI must be the same version. The daemon is sticky:
|
|
8
|
+
// once forked it runs that version forever, regardless of what the on-
|
|
9
|
+
// disk binary later becomes. We resolve pm2 to ONE binary (the project-
|
|
10
|
+
// local one) so every command — interactive, scripted, admin endpoint —
|
|
11
|
+
// hits the same code path. If a different pm2 ever started the daemon
|
|
12
|
+
// (e.g. a stray `npm i -g pm2`), `ensurePm2InSync` detects the skew
|
|
13
|
+
// and runs `pm2 update` to respawn the daemon under our binary.
|
|
10
14
|
|
|
11
15
|
import { existsSync, readFileSync } from 'node:fs';
|
|
12
16
|
import { join } from 'node:path';
|
|
@@ -17,10 +21,53 @@ import { requireDeploymentDir, resolveDeploymentDir } from '../paths.js';
|
|
|
17
21
|
|
|
18
22
|
const APP_NAME = 'selva-compute';
|
|
19
23
|
|
|
24
|
+
// Resolve pm2 to the deployment's own copy. NO global fallback — having two
|
|
25
|
+
// pm2 binaries on the same host and letting them both manage the daemon
|
|
26
|
+
// produces the exact version-skew bug this wrapper exists to prevent.
|
|
20
27
|
function pm2Bin(dir) {
|
|
21
28
|
const local = join(dir, 'node_modules', '.bin', process.platform === 'win32' ? 'pm2.cmd' : 'pm2');
|
|
22
|
-
if (existsSync(local))
|
|
23
|
-
|
|
29
|
+
if (!existsSync(local)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`pm2 not found at ${local}. The deployment owns its own pm2 — run ` +
|
|
32
|
+
`\`npm install\` in ${dir} to install it. (We deliberately don't ` +
|
|
33
|
+
`fall back to a global pm2; two pm2s managing the same daemon causes ` +
|
|
34
|
+
`persistent skew warnings and hung restarts.)`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return local;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check whether the in-memory PM2 daemon was forked by a different pm2 than
|
|
41
|
+
// the one we're about to invoke. PM2 prints "In-memory PM2 is out-of-date" on
|
|
42
|
+
// every command in that state and process operations may stall. `pm2 update`
|
|
43
|
+
// is the only fix: dump → kill daemon → respawn under the current binary →
|
|
44
|
+
// resurrect dump. We run it here before any state-changing command so the
|
|
45
|
+
// caller never gets a half-applied stop/restart against a stale daemon.
|
|
46
|
+
function ensurePm2InSync(dir) {
|
|
47
|
+
const bin = pm2Bin(dir);
|
|
48
|
+
const probe = spawnSync(bin, ['ping'], {
|
|
49
|
+
cwd: dir,
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
shell: process.platform === 'win32'
|
|
52
|
+
});
|
|
53
|
+
const output = (probe.stdout ?? '') + (probe.stderr ?? '');
|
|
54
|
+
if (!/out-of-date/i.test(output)) return;
|
|
55
|
+
|
|
56
|
+
p.log.warn(
|
|
57
|
+
'PM2 in-memory daemon is a different version than the deployment-local pm2 — ' +
|
|
58
|
+
'running `pm2 update` to resync (this briefly restarts managed processes).'
|
|
59
|
+
);
|
|
60
|
+
const result = spawnSync(bin, ['update'], {
|
|
61
|
+
cwd: dir,
|
|
62
|
+
stdio: 'inherit',
|
|
63
|
+
shell: process.platform === 'win32'
|
|
64
|
+
});
|
|
65
|
+
if ((result.status ?? 1) !== 0) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
'`pm2 update` failed — daemon and CLI remain out of sync. ' +
|
|
68
|
+
'Investigate manually: `pm2 ping`, `pm2 -v`, `which -a pm2`.'
|
|
69
|
+
);
|
|
70
|
+
}
|
|
24
71
|
}
|
|
25
72
|
|
|
26
73
|
function runPm2(dir, args, { inherit = true } = {}) {
|
|
@@ -33,7 +80,7 @@ function runPm2(dir, args, { inherit = true } = {}) {
|
|
|
33
80
|
if (result.error) {
|
|
34
81
|
throw new Error(
|
|
35
82
|
`Failed to invoke pm2 (${bin}): ${result.error.message}. ` +
|
|
36
|
-
`Install pm2 with \`npm install
|
|
83
|
+
`Install pm2 with \`npm install\` in this directory.`
|
|
37
84
|
);
|
|
38
85
|
}
|
|
39
86
|
return result.status ?? 0;
|
|
@@ -42,6 +89,7 @@ function runPm2(dir, args, { inherit = true } = {}) {
|
|
|
42
89
|
export async function runStart() {
|
|
43
90
|
const dir = resolveDeploymentDir();
|
|
44
91
|
requireDeploymentDir(dir);
|
|
92
|
+
ensurePm2InSync(dir);
|
|
45
93
|
const exit = runPm2(dir, ['start', 'ecosystem.config.cjs']);
|
|
46
94
|
process.exit(exit);
|
|
47
95
|
}
|
|
@@ -49,6 +97,7 @@ export async function runStart() {
|
|
|
49
97
|
export async function runStop() {
|
|
50
98
|
const dir = resolveDeploymentDir();
|
|
51
99
|
requireDeploymentDir(dir);
|
|
100
|
+
ensurePm2InSync(dir);
|
|
52
101
|
const exit = runPm2(dir, ['stop', APP_NAME]);
|
|
53
102
|
process.exit(exit);
|
|
54
103
|
}
|
|
@@ -56,6 +105,7 @@ export async function runStop() {
|
|
|
56
105
|
export async function runRestart() {
|
|
57
106
|
const dir = resolveDeploymentDir();
|
|
58
107
|
requireDeploymentDir(dir);
|
|
108
|
+
ensurePm2InSync(dir);
|
|
59
109
|
// --update-env is the whole point of this wrapper — without it, edits to
|
|
60
110
|
// .env have no effect on the running process.
|
|
61
111
|
const exit = runPm2(dir, ['restart', APP_NAME, '--update-env']);
|
|
@@ -92,6 +142,13 @@ export async function runUpdate() {
|
|
|
92
142
|
return;
|
|
93
143
|
}
|
|
94
144
|
|
|
145
|
+
// Resync the daemon BEFORE we touch the running app — if the daemon is a
|
|
146
|
+
// different version than the local CLI, `pm2 stop` may report success
|
|
147
|
+
// while leaving the process group in a half-state, and the subsequent
|
|
148
|
+
// `pm2 start` then hangs. Running `pm2 update` here puts everything on
|
|
149
|
+
// the same version (and survives the dump+resurrect cycle).
|
|
150
|
+
ensurePm2InSync(dir);
|
|
151
|
+
|
|
95
152
|
// Stop the running process BEFORE npm rewrites node_modules/@selvajs/selva/build/.
|
|
96
153
|
// SvelteKit's node adapter lazy-imports chunks from build/server/chunks/ on every
|
|
97
154
|
// request; if we let npm replace them while the old process is still serving
|