@lionad/safe-npx 0.4.0 → 0.5.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/README.md +13 -3
- package/dist/index.js +35 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
**This project is archived as of April 2026.**
|
|
11
11
|
|
|
12
|
-
npm v11.10.0+ now provides native protection via `min-release-age` configuration.
|
|
12
|
+
npm v11.10.0+ now provides native protection via `min-release-age` configuration.
|
|
13
13
|
|
|
14
14
|
**项目已于 2026 年 4 月归档。**
|
|
15
15
|
|
|
16
|
-
npm v11.10.0+ 已原生支持 `min-release-age` 配置,提供相同的供应链攻击防护。
|
|
16
|
+
npm v11.10.0+ 已原生支持 `min-release-age` 配置,提供相同的供应链攻击防护。
|
|
17
17
|
|
|
18
18
|
### Migration / 迁移指南
|
|
19
19
|
|
|
@@ -86,7 +86,8 @@ snpx 使用**包名前/后**来区分不同层级的参数:
|
|
|
86
86
|
| 类别 | 支持的参数 | 说明 |
|
|
87
87
|
|------|-----------|------|
|
|
88
88
|
| **snpx 专属** | `--help`, `--version`, `--show-version`<br>`--time`, `--fallback-strategy`<br>`--self-update`, `--unsafe-self-update` | snpx 的配置选项 |
|
|
89
|
-
| **npx 白名单** | `-y`, `--yes`, `--no`<br>`-p <pkg>`, `--package=<pkg>`<br>`-c <cmd>`, `--call=<cmd>`<br>`--offline`, `--prefer-offline`<br>`-w <name>`, `--workspace=<name>`<br>`--
|
|
89
|
+
| **npx 白名单** | `-y`, `--yes`, `--no`<br>`-p <pkg>`, `--package=<pkg>`<br>`-c <cmd>`, `--call=<cmd>`<br>`--offline`, `--prefer-offline`<br>`-w <name>`, `--workspace=<name>`<br>`--quiet`, `--registry=<url>` | 传递给 npx 的前置参数 |
|
|
90
|
+
| **snpx 日志** | `--verbose` | 显示 snpx 内部日志(默认静默) |
|
|
90
91
|
|
|
91
92
|
**After package / 包名之后**(全部透传给被执行的工具):
|
|
92
93
|
|
|
@@ -156,8 +157,17 @@ snpx --help
|
|
|
156
157
|
|
|
157
158
|
# Use -- to prevent tool flags from being parsed as snpx flags / 使用 -- 防止工具参数被误解析为 snpx 标志
|
|
158
159
|
snpx -- cowsay --help
|
|
160
|
+
|
|
161
|
+
# Show snpx internal logs / 显示 snpx 内部日志
|
|
162
|
+
snpx --verbose cowsay@latest
|
|
159
163
|
```
|
|
160
164
|
|
|
165
|
+
### Default Silent Mode / 默认静默模式
|
|
166
|
+
|
|
167
|
+
**By default, snpx runs silently.** It only prints `--help`, `--version`, and `--show-version` to stdout, plus errors to stderr. No `[snpx] Resolving...` logs are emitted unless you pass `--verbose`. This ensures stdout remains clean for downstream consumers like MCP servers.
|
|
168
|
+
|
|
169
|
+
**默认情况下,snpx 以静默模式运行。** 只有 `--help`、`--version`、`--show-version` 会输出到 stdout,错误输出到 stderr。除非传入 `--verbose`,否则不会打印 `[snpx] Resolving...` 等内部日志。这保证了对 MCP server 等下游依赖的 stdout 不产生污染。
|
|
170
|
+
|
|
161
171
|
## Environment Variables / 环境变量
|
|
162
172
|
|
|
163
173
|
- `SNPX_TIME` — Default for `--time` / `--time` 的默认值
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
|
+
import { constants, homedir } from "os";
|
|
3
4
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
4
5
|
import { dirname, join } from "path";
|
|
5
6
|
import { fileURLToPath } from "url";
|
|
6
|
-
import { homedir } from "os";
|
|
7
7
|
//#region \0rolldown/runtime.js
|
|
8
8
|
var __create = Object.create;
|
|
9
9
|
var __defProp = Object.defineProperty;
|
|
@@ -38,20 +38,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
38
38
|
}) : target, mod));
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region src/constants.ts
|
|
41
|
-
var constants_exports = /* @__PURE__ */ __exportAll({
|
|
42
|
-
CACHE_DIR: () => CACHE_DIR,
|
|
43
|
-
DEFAULT_FALLBACK_STRATEGY: () => DEFAULT_FALLBACK_STRATEGY,
|
|
44
|
-
DEFAULT_TIME_HOURS: () => 24,
|
|
45
|
-
HELP_TEXT: () => HELP_TEXT,
|
|
46
|
-
MS_PER_HOUR: () => MS_PER_HOUR,
|
|
47
|
-
NPX_PREFIX_FLAGS: () => NPX_PREFIX_FLAGS,
|
|
48
|
-
NPX_PREFIX_FLAGS_WITH_VALUE: () => NPX_PREFIX_FLAGS_WITH_VALUE,
|
|
49
|
-
PKG_NAME: () => PKG_NAME,
|
|
50
|
-
REGISTRY: () => REGISTRY,
|
|
51
|
-
VERSION: () => VERSION,
|
|
52
|
-
colors: () => colors,
|
|
53
|
-
isTty: () => isTty
|
|
54
|
-
});
|
|
55
41
|
var __dirname, VERSION, CACHE_DIR, REGISTRY, PKG_NAME, DEFAULT_TIME_HOURS, DEFAULT_FALLBACK_STRATEGY, MS_PER_HOUR, isTty, colors, HELP_TEXT, NPX_PREFIX_FLAGS, NPX_PREFIX_FLAGS_WITH_VALUE;
|
|
56
42
|
var init_constants = __esmMin((() => {
|
|
57
43
|
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -93,6 +79,8 @@ ${colors.bold("Options:")}
|
|
|
93
79
|
${colors.dim("major")} = most recently published version of previous major line
|
|
94
80
|
${colors.yellow("--show-version")} Print resolved version and exit (no execution)
|
|
95
81
|
${colors.yellow("--version")} Print snpx version and exit
|
|
82
|
+
${colors.yellow("--silent")} Suppress snpx info logs (default)
|
|
83
|
+
${colors.yellow("--verbose")} Show snpx info logs
|
|
96
84
|
${colors.yellow("--self-update")} Check for snpx updates (safe mode, default 24h)
|
|
97
85
|
${colors.yellow("--unsafe-self-update")} Allow immediate snpx updates without safety window
|
|
98
86
|
|
|
@@ -121,7 +109,6 @@ ${colors.bold("Examples:")}
|
|
|
121
109
|
"--workspaces",
|
|
122
110
|
"--ws",
|
|
123
111
|
"--include-workspace-root",
|
|
124
|
-
"--silent",
|
|
125
112
|
"--quiet",
|
|
126
113
|
"-q"
|
|
127
114
|
]);
|
|
@@ -139,6 +126,15 @@ ${colors.bold("Examples:")}
|
|
|
139
126
|
//#region src/cli.ts
|
|
140
127
|
init_constants();
|
|
141
128
|
/**
|
|
129
|
+
* Create a logger that suppresses info-level output when silent is true.
|
|
130
|
+
* Info messages are written to stderr to avoid polluting stdout.
|
|
131
|
+
*/
|
|
132
|
+
function createLogger(silent) {
|
|
133
|
+
return { info: (msg) => {
|
|
134
|
+
if (!silent) console.error(msg);
|
|
135
|
+
} };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
142
138
|
* Extract package name from spec.
|
|
143
139
|
* @example 'cowsay@1.5.0' → 'cowsay'
|
|
144
140
|
* @example '@vue/cli@latest' → '@vue/cli'
|
|
@@ -170,6 +166,8 @@ function parseArgs(argv) {
|
|
|
170
166
|
showVersion: false,
|
|
171
167
|
selfUpdate: false,
|
|
172
168
|
unsafeSelfUpdate: false,
|
|
169
|
+
silent: true,
|
|
170
|
+
verbose: false,
|
|
173
171
|
time: null,
|
|
174
172
|
fallbackStrategy: null
|
|
175
173
|
};
|
|
@@ -199,7 +197,11 @@ function parseArgs(argv) {
|
|
|
199
197
|
else if (arg === "--version") snpxFlags.version = true;
|
|
200
198
|
else if (arg === "--show-version") snpxFlags.showVersion = true;
|
|
201
199
|
else if (arg === "--self-update") snpxFlags.selfUpdate = true;
|
|
202
|
-
else if (arg === "--
|
|
200
|
+
else if (arg === "--silent") snpxFlags.silent = true;
|
|
201
|
+
else if (arg === "--verbose") {
|
|
202
|
+
snpxFlags.silent = false;
|
|
203
|
+
snpxFlags.verbose = true;
|
|
204
|
+
} else if (arg === "--unsafe-self-update") snpxFlags.unsafeSelfUpdate = true;
|
|
203
205
|
else if (arg === "--time") {
|
|
204
206
|
if (i + 1 >= args.length) throw new Error("Missing value for --time");
|
|
205
207
|
snpxFlags.time = args[++i];
|
|
@@ -249,7 +251,8 @@ function buildOptions(snpxFlags) {
|
|
|
249
251
|
return {
|
|
250
252
|
timeHours,
|
|
251
253
|
timeMs: parsedHours * MS_PER_HOUR,
|
|
252
|
-
strategy: (snpxFlags.fallbackStrategy ?? envStrategy ?? "patch,minor,major").split(",").map((s) => s.trim()).filter(Boolean)
|
|
254
|
+
strategy: (snpxFlags.fallbackStrategy ?? envStrategy ?? "patch,minor,major").split(",").map((s) => s.trim()).filter(Boolean),
|
|
255
|
+
silent: snpxFlags.verbose ? false : snpxFlags.silent ?? true
|
|
253
256
|
};
|
|
254
257
|
}
|
|
255
258
|
//#endregion
|
|
@@ -1804,8 +1807,9 @@ init_registry();
|
|
|
1804
1807
|
function runNpx(args) {
|
|
1805
1808
|
return new Promise((resolve, reject) => {
|
|
1806
1809
|
const child = spawn("npx", args, { stdio: "inherit" });
|
|
1807
|
-
child.on("close", (code) => {
|
|
1808
|
-
process.exitCode =
|
|
1810
|
+
child.on("close", (code, signal) => {
|
|
1811
|
+
if (signal) process.exitCode = 128 + (constants.signals[signal] || 0);
|
|
1812
|
+
else process.exitCode = code ?? 0;
|
|
1809
1813
|
resolve();
|
|
1810
1814
|
});
|
|
1811
1815
|
child.on("error", reject);
|
|
@@ -1824,12 +1828,13 @@ async function main() {
|
|
|
1824
1828
|
console.log(VERSION);
|
|
1825
1829
|
return;
|
|
1826
1830
|
}
|
|
1827
|
-
const { timeHours, timeMs, strategy } = buildOptions(snpxFlags);
|
|
1831
|
+
const { timeHours, timeMs, strategy, silent } = buildOptions(snpxFlags);
|
|
1832
|
+
const logger = createLogger(silent);
|
|
1828
1833
|
const selfUpdate = snpxFlags.selfUpdate;
|
|
1829
1834
|
const unsafeSelfUpdate = snpxFlags.unsafeSelfUpdate;
|
|
1830
1835
|
if (selfUpdate || unsafeSelfUpdate) {
|
|
1831
1836
|
const unsafe = unsafeSelfUpdate;
|
|
1832
|
-
|
|
1837
|
+
logger.info(`[snpx] Checking for updates${unsafe ? " (unsafe mode)" : ""}...`);
|
|
1833
1838
|
try {
|
|
1834
1839
|
const { hasUpdate, currentVersion, latestVersion } = await checkSelfUpdate();
|
|
1835
1840
|
if (!latestVersion) {
|
|
@@ -1837,21 +1842,20 @@ async function main() {
|
|
|
1837
1842
|
process.exit(1);
|
|
1838
1843
|
}
|
|
1839
1844
|
if (hasUpdate) {
|
|
1840
|
-
|
|
1841
|
-
|
|
1845
|
+
logger.info(`[snpx] Update available: ${currentVersion} → ${latestVersion}`);
|
|
1846
|
+
logger.info("[snpx] Run: npm update -g @lionad/safe-npx");
|
|
1842
1847
|
if (!unsafe) {
|
|
1843
1848
|
const { fetchPackageMetadata } = await Promise.resolve().then(() => (init_registry(), registry_exports));
|
|
1844
|
-
const { REGISTRY } = await Promise.resolve().then(() => (init_constants(), constants_exports));
|
|
1845
1849
|
const latestTime = ((await fetchPackageMetadata("@lionad/safe-npx")).time || {})[latestVersion];
|
|
1846
1850
|
if (latestTime) {
|
|
1847
1851
|
const age = Date.now() - new Date(latestTime).getTime();
|
|
1848
1852
|
if (age < timeMs) {
|
|
1849
|
-
|
|
1850
|
-
|
|
1853
|
+
logger.info(`[snpx] Warning: Latest version is only ${Math.floor(age / MS_PER_HOUR)}h old. Waiting for ${timeHours}h safety window.`);
|
|
1854
|
+
logger.info("[snpx] Use --unsafe-self-update to bypass (not recommended)");
|
|
1851
1855
|
}
|
|
1852
1856
|
}
|
|
1853
1857
|
}
|
|
1854
|
-
} else
|
|
1858
|
+
} else logger.info(`[snpx] Already up to date (${currentVersion})`);
|
|
1855
1859
|
} catch (err) {
|
|
1856
1860
|
console.error(`[snpx] Error checking for updates: ${err.message}`);
|
|
1857
1861
|
process.exit(1);
|
|
@@ -1872,19 +1876,19 @@ async function main() {
|
|
|
1872
1876
|
}
|
|
1873
1877
|
let version = getCachedVersion(pkgName, timeMs);
|
|
1874
1878
|
if (!version) {
|
|
1875
|
-
|
|
1879
|
+
logger.info(`[snpx] Resolving safe version for ${pkgName}...`);
|
|
1876
1880
|
try {
|
|
1877
1881
|
version = await resolveSafeVersion(pkgName, {
|
|
1878
1882
|
timeMs,
|
|
1879
1883
|
strategy
|
|
1880
1884
|
});
|
|
1881
1885
|
setCachedVersion(pkgName, version);
|
|
1882
|
-
|
|
1886
|
+
logger.info(`[snpx] Using ${pkgName}@${version} (strategy: ${strategy.join(",")}, window: ${timeHours}h)`);
|
|
1883
1887
|
} catch (err) {
|
|
1884
1888
|
console.error(`[snpx] Error: ${err.message}`);
|
|
1885
1889
|
process.exit(1);
|
|
1886
1890
|
}
|
|
1887
|
-
} else
|
|
1891
|
+
} else logger.info(`[snpx] Using cached ${pkgName}@${version}`);
|
|
1888
1892
|
if (snpxFlags.showVersion) {
|
|
1889
1893
|
console.log(version);
|
|
1890
1894
|
return;
|