@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.
Files changed (3) hide show
  1. package/README.md +13 -3
  2. package/dist/index.js +35 -31
  3. 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. snpx has served its purpose and is no longer needed.
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` 配置,提供相同的供应链攻击防护。snpx 已完成历史使命,不再需要。
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>`--silent`, `--quiet`, `--registry=<url>` | 传递给 npx 的前置参数 |
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 === "--unsafe-self-update") snpxFlags.unsafeSelfUpdate = true;
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 = code ?? 0;
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
- console.error(`[snpx] Checking for updates${unsafe ? " (unsafe mode)" : ""}...`);
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
- console.error(`[snpx] Update available: ${currentVersion} → ${latestVersion}`);
1841
- console.error("[snpx] Run: npm update -g @lionad/safe-npx");
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
- console.error(`[snpx] Warning: Latest version is only ${Math.floor(age / MS_PER_HOUR)}h old. Waiting for ${timeHours}h safety window.`);
1850
- console.error("[snpx] Use --unsafe-self-update to bypass (not recommended)");
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 console.error(`[snpx] Already up to date (${currentVersion})`);
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
- console.error(`[snpx] Resolving safe version for ${pkgName}...`);
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
- console.error(`[snpx] Using ${pkgName}@${version} (strategy: ${strategy.join(",")}, window: ${timeHours}h)`);
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 console.error(`[snpx] Using cached ${pkgName}@${version}`);
1891
+ } else logger.info(`[snpx] Using cached ${pkgName}@${version}`);
1888
1892
  if (snpxFlags.showVersion) {
1889
1893
  console.log(version);
1890
1894
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lionad/safe-npx",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Safe npx wrapper - lock to latest-1 version with 24h cache",
5
5
  "type": "module",
6
6
  "bin": {