c8ctl-plugin-nano 1.12.0 → 1.13.0
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/c8ctl-plugin.js +103 -17
- package/package.json +8 -8
package/c8ctl-plugin.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* c8ctl nano restart [<nodes>] [--purge] ...
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
31
|
+
import { spawn, spawnSync, execFileSync, execSync } from 'node:child_process';
|
|
32
32
|
import {
|
|
33
33
|
existsSync,
|
|
34
34
|
mkdirSync,
|
|
@@ -2877,21 +2877,90 @@ function compareSemver(a, b) {
|
|
|
2877
2877
|
return 0;
|
|
2878
2878
|
}
|
|
2879
2879
|
|
|
2880
|
+
/**
|
|
2881
|
+
* Resolve how npm must be spawned on the given platform. Spawning `npm`
|
|
2882
|
+
* directly is not portable: on Windows npm is a `npm.cmd` shim, so bare
|
|
2883
|
+
* `"npm"` fails with ENOENT and `"npm.cmd"` fails with EINVAL under the
|
|
2884
|
+
* CVE-2024-27980 hardening. On Windows the shim is therefore run through
|
|
2885
|
+
* cmd.exe (`shell: true`) with every argument double-quoted, and the two
|
|
2886
|
+
* constructs that survive double quotes — an embedded `"` and a `%VAR%`
|
|
2887
|
+
* reference — are rejected rather than escaped.
|
|
2888
|
+
*
|
|
2889
|
+
* This mirrors the host CLI's own `buildNpmInvocation`; it is the local
|
|
2890
|
+
* fallback for `runNpm` when the host runner (`c8ctl.npm`) is unavailable.
|
|
2891
|
+
* `platform` is a parameter so the Windows branch is unit-testable on POSIX.
|
|
2892
|
+
*/
|
|
2893
|
+
function buildNpmInvocation(args, platform = process.platform) {
|
|
2894
|
+
if (platform !== 'win32') {
|
|
2895
|
+
return { command: 'npm', args: [...args], shell: false };
|
|
2896
|
+
}
|
|
2897
|
+
for (const arg of args) {
|
|
2898
|
+
if (/["\r\n\0]/.test(arg)) {
|
|
2899
|
+
throw new Error(
|
|
2900
|
+
`Refusing to run npm: argument contains a quote or line break that cannot be passed safely to cmd.exe: ${JSON.stringify(arg)}`,
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
if (/%[A-Z_][^%]*?%/i.test(arg)) {
|
|
2904
|
+
throw new Error(
|
|
2905
|
+
`Refusing to run npm: argument contains a cmd.exe environment variable reference: ${JSON.stringify(arg)}`,
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
return {
|
|
2910
|
+
command: 'npm.cmd',
|
|
2911
|
+
args: args.map((arg) => `"${arg.replace(/(\\+)$/, '$1$1')}"`),
|
|
2912
|
+
shell: true,
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
/** Local, platform-aware npm runner used when the host `c8ctl.npm` is absent. */
|
|
2917
|
+
function runNpmLocal(args, { stdout = false, stdio } = {}) {
|
|
2918
|
+
const { command, args: resolved, shell } = buildNpmInvocation(args);
|
|
2919
|
+
if (shell) {
|
|
2920
|
+
const cmdLine = [command, ...resolved].join(' ');
|
|
2921
|
+
if (stdout) {
|
|
2922
|
+
return { stdout: execSync(cmdLine, { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }) };
|
|
2923
|
+
}
|
|
2924
|
+
execSync(cmdLine, { stdio });
|
|
2925
|
+
return undefined;
|
|
2926
|
+
}
|
|
2927
|
+
if (stdout) {
|
|
2928
|
+
return {
|
|
2929
|
+
stdout: execFileSync(command, resolved, { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', shell: false }),
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
execFileSync(command, resolved, { stdio, shell: false });
|
|
2933
|
+
return undefined;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
/**
|
|
2937
|
+
* Run npm portably. Prefers the host CLI's cross-platform runner
|
|
2938
|
+
* (`globalThis.c8ctl.npm`, added in c8ctl's plugin runtime); falls back to the
|
|
2939
|
+
* local platform-aware invocation for older hosts and for the detached update
|
|
2940
|
+
* refresh, which runs without the host runtime. Throws on a nonzero exit.
|
|
2941
|
+
*/
|
|
2942
|
+
function runNpm(args, { stdout = false, stdio } = {}) {
|
|
2943
|
+
const host = globalThis.c8ctl;
|
|
2944
|
+
if (host && typeof host.npm === 'function') {
|
|
2945
|
+
return stdout ? host.npm({ args, stdout: true }) : host.npm({ args, stdio });
|
|
2946
|
+
}
|
|
2947
|
+
return runNpmLocal(args, { stdout, stdio });
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2880
2950
|
/** Latest published version of `name` per the npm registry (throws on failure). */
|
|
2881
2951
|
function npmLatestVersion(name) {
|
|
2882
|
-
const
|
|
2883
|
-
|
|
2884
|
-
if (res.status !== 0) {
|
|
2885
|
-
throw new Error((res.stderr || '').trim() || `npm view exited ${res.status}`);
|
|
2886
|
-
}
|
|
2887
|
-
return res.stdout.trim();
|
|
2952
|
+
const { stdout } = runNpm(['view', name, 'version'], { stdout: true });
|
|
2953
|
+
return stdout.trim();
|
|
2888
2954
|
}
|
|
2889
2955
|
|
|
2890
2956
|
/** True when this plugin lives under npm's global node_modules (so `-g` updates it). */
|
|
2891
2957
|
function isGlobalInstall() {
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2958
|
+
let root;
|
|
2959
|
+
try {
|
|
2960
|
+
root = runNpm(['root', '-g'], { stdout: true }).stdout.trim();
|
|
2961
|
+
} catch {
|
|
2962
|
+
return false;
|
|
2963
|
+
}
|
|
2895
2964
|
return Boolean(root) && pluginDir.startsWith(root);
|
|
2896
2965
|
}
|
|
2897
2966
|
|
|
@@ -3009,9 +3078,9 @@ function updatePlugin(req) {
|
|
|
3009
3078
|
const where = info.mode === 'managed' ? 'the c8ctl plugin store' : "npm's global prefix";
|
|
3010
3079
|
console.log(`Pulling ${name}@${latest} into ${where}...`);
|
|
3011
3080
|
console.log('');
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3081
|
+
try {
|
|
3082
|
+
runNpm(installArgs, { stdio: 'inherit' });
|
|
3083
|
+
} catch (err) {
|
|
3015
3084
|
let hint;
|
|
3016
3085
|
if (info.mode === 'managed') {
|
|
3017
3086
|
hint = `You can also run:\n${manual}`;
|
|
@@ -3020,8 +3089,9 @@ function updatePlugin(req) {
|
|
|
3020
3089
|
} else {
|
|
3021
3090
|
hint = `You may need elevated permissions: sudo ${manual.trim()}`;
|
|
3022
3091
|
}
|
|
3092
|
+
const code = typeof err?.status === 'number' ? ` (exit ${err.status})` : '';
|
|
3023
3093
|
throw new Error(
|
|
3024
|
-
`npm ${installArgs.join(' ')} failed
|
|
3094
|
+
`npm ${installArgs.join(' ')} failed${code}. ${hint}`,
|
|
3025
3095
|
);
|
|
3026
3096
|
}
|
|
3027
3097
|
console.log('');
|
|
@@ -3081,13 +3151,28 @@ function updateNotifierDisabled() {
|
|
|
3081
3151
|
* fresh result is used on the *next* invocation.
|
|
3082
3152
|
*/
|
|
3083
3153
|
function spawnUpdateRefresh(name, cacheFile) {
|
|
3154
|
+
// The refresh runs in a detached bare-node child that has no host runtime, so
|
|
3155
|
+
// it cannot use c8ctl.npm. Resolve the portable npm invocation here (single
|
|
3156
|
+
// source of truth) and bake the decided command into a generic runner in the
|
|
3157
|
+
// child — the child makes no platform decision of its own.
|
|
3158
|
+
let inv;
|
|
3159
|
+
try {
|
|
3160
|
+
inv = buildNpmInvocation(['view', name, 'version']);
|
|
3161
|
+
} catch {
|
|
3162
|
+
return; /* unsafe argument for cmd.exe; skip this cycle */
|
|
3163
|
+
}
|
|
3084
3164
|
const script =
|
|
3085
|
-
'const{
|
|
3165
|
+
'const{execFileSync,execSync}=require("child_process");' +
|
|
3086
3166
|
'const{readFileSync,writeFileSync}=require("fs");' +
|
|
3167
|
+
`const cmd=${JSON.stringify(inv.command)},args=${JSON.stringify(inv.args)},shell=${JSON.stringify(inv.shell)};` +
|
|
3087
3168
|
`let prev={};try{prev=JSON.parse(readFileSync(${JSON.stringify(cacheFile)},"utf8"))}catch{}` +
|
|
3088
3169
|
'const out=Object.assign({},prev,{lastCheck:Date.now()});' +
|
|
3089
|
-
|
|
3090
|
-
'
|
|
3170
|
+
'try{' +
|
|
3171
|
+
'const o=shell' +
|
|
3172
|
+
'?execSync([cmd,...args].join(" "),{stdio:["ignore","pipe","pipe"],encoding:"utf8"})' +
|
|
3173
|
+
':execFileSync(cmd,args,{stdio:["ignore","pipe","pipe"],encoding:"utf8",shell:false});' +
|
|
3174
|
+
'out.latest=String(o||"").trim()' +
|
|
3175
|
+
'}catch{}' +
|
|
3091
3176
|
`try{writeFileSync(${JSON.stringify(cacheFile)},JSON.stringify(out))}catch{}`;
|
|
3092
3177
|
try {
|
|
3093
3178
|
const child = spawn(process.execPath, ['-e', script], { detached: true, stdio: 'ignore' });
|
|
@@ -4062,6 +4147,7 @@ function parseProcessosRequest(args, flags) {
|
|
|
4062
4147
|
// Internal helpers exported for tests/tooling only. c8ctl consumes just
|
|
4063
4148
|
// `metadata` and `commands`; these named exports are inert to it.
|
|
4064
4149
|
export { resolveBinary, findBinary, launcherEnvMarkers };
|
|
4150
|
+
export { buildNpmInvocation };
|
|
4065
4151
|
export {
|
|
4066
4152
|
normalizeTaskEnvelope,
|
|
4067
4153
|
collectEnvelopeFrom,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "c8ctl plugin to start, inspect, and stop a local Nano BPM (nanobpmn) cluster",
|
|
6
6
|
"main": "c8ctl-plugin.js",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"semantic-release": "^25.0.3"
|
|
50
50
|
},
|
|
51
51
|
"optionalDependencies": {
|
|
52
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.
|
|
53
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.
|
|
54
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.
|
|
55
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.
|
|
56
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.
|
|
57
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.
|
|
58
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.
|
|
52
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.13.0",
|
|
53
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.13.0",
|
|
54
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.13.0",
|
|
55
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.13.0",
|
|
56
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.13.0",
|
|
57
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.13.0",
|
|
58
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.13.0"
|
|
59
59
|
}
|
|
60
60
|
}
|