@lionad/safe-npx 0.2.0 → 0.2.2
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/snpx.js +75 -49
package/package.json
CHANGED
package/snpx.js
CHANGED
|
@@ -15,35 +15,45 @@ const DEFAULT_TIME_HOURS = 24;
|
|
|
15
15
|
const DEFAULT_FALLBACK_STRATEGY = 'patch,minor,major';
|
|
16
16
|
const MS_PER_HOUR = 60 * 60 * 1000;
|
|
17
17
|
|
|
18
|
+
const _tty = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
19
|
+
const _c = {
|
|
20
|
+
bold: (s) => _tty ? `\x1b[1m${s}\x1b[22m` : s,
|
|
21
|
+
dim: (s) => _tty ? `\x1b[2m${s}\x1b[22m` : s,
|
|
22
|
+
green: (s) => _tty ? `\x1b[32m${s}\x1b[39m` : s,
|
|
23
|
+
cyan: (s) => _tty ? `\x1b[36m${s}\x1b[39m` : s,
|
|
24
|
+
yellow: (s) => _tty ? `\x1b[33m${s}\x1b[39m` : s,
|
|
25
|
+
};
|
|
26
|
+
|
|
18
27
|
const HELP_TEXT = `
|
|
19
|
-
safe-npx (snpx)
|
|
28
|
+
${_c.bold(_c.cyan('safe-npx (snpx)'))} — Safe npx wrapper with configurable fallback strategy
|
|
20
29
|
|
|
21
|
-
Usage:
|
|
22
|
-
snpx [options] <package>@latest [args...]
|
|
23
|
-
snpx [options] <package> [args...]
|
|
24
|
-
snpx [options] <command>
|
|
30
|
+
${_c.bold('Usage:')}
|
|
31
|
+
${_c.green('snpx')} [options] <package>@latest [args...]
|
|
32
|
+
${_c.green('snpx')} [options] <package> [args...]
|
|
33
|
+
${_c.green('snpx')} [options] <command>
|
|
25
34
|
|
|
26
|
-
Options:
|
|
27
|
-
-h, --help Show this help message
|
|
28
|
-
--time <hours> Safety window in hours (default: 24)
|
|
29
|
-
--fallback-strategy <str> Comma-separated fallback order.
|
|
35
|
+
${_c.bold('Options:')}
|
|
36
|
+
${_c.yellow('-h, --help')} Show this help message
|
|
37
|
+
${_c.yellow('--time')} ${_c.dim('<hours>')} Safety window in hours (default: 24)
|
|
38
|
+
${_c.yellow('--fallback-strategy')} ${_c.dim('<str>')} Comma-separated fallback order.
|
|
30
39
|
Default: patch,minor,major
|
|
31
40
|
Left-to-right: first matching safe version wins.
|
|
32
|
-
patch = version immediately before latest
|
|
33
|
-
minor = most recently published version of previous minor line
|
|
34
|
-
major = most recently published version of previous major line
|
|
35
|
-
--show-version Print resolved version and exit (no execution)
|
|
36
|
-
--self-update Check for snpx updates (safe mode, default 24h)
|
|
37
|
-
--unsafe-self-update Allow immediate snpx updates without safety window
|
|
38
|
-
|
|
39
|
-
Environment Variables:
|
|
40
|
-
SNPX_TIME Default for --time
|
|
41
|
-
SNPX_FALLBACK_STRATEGY Default for --fallback-strategy
|
|
42
|
-
|
|
43
|
-
Examples:
|
|
44
|
-
snpx -y cowsay@latest "Hello World"
|
|
45
|
-
snpx --time 48 --fallback-strategy patch,minor cowsay@latest
|
|
46
|
-
snpx --show-version cowsay@latest
|
|
41
|
+
${_c.dim('patch')} = version immediately before latest
|
|
42
|
+
${_c.dim('minor')} = most recently published version of previous minor line
|
|
43
|
+
${_c.dim('major')} = most recently published version of previous major line
|
|
44
|
+
${_c.yellow('--show-version')} Print resolved version and exit (no execution)
|
|
45
|
+
${_c.yellow('--self-update')} Check for snpx updates (safe mode, default 24h)
|
|
46
|
+
${_c.yellow('--unsafe-self-update')} Allow immediate snpx updates without safety window
|
|
47
|
+
|
|
48
|
+
${_c.bold('Environment Variables:')}
|
|
49
|
+
${_c.green('SNPX_TIME')} Default for --time
|
|
50
|
+
${_c.green('SNPX_FALLBACK_STRATEGY')} Default for --fallback-strategy
|
|
51
|
+
|
|
52
|
+
${_c.bold('Examples:')}
|
|
53
|
+
${_c.green('snpx')} -y cowsay@latest "Hello World"
|
|
54
|
+
${_c.green('snpx')} --time 48 --fallback-strategy patch,minor cowsay@latest
|
|
55
|
+
${_c.green('snpx')} --show-version cowsay@latest
|
|
56
|
+
${_c.green('snpx')} cowsay@latest --version ${_c.dim('# passes --version to cowsay')}
|
|
47
57
|
`.trim();
|
|
48
58
|
|
|
49
59
|
/**
|
|
@@ -64,9 +74,14 @@ export function parseSemver(version) {
|
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
/**
|
|
67
|
-
* Parse CLI arguments
|
|
68
|
-
*
|
|
69
|
-
*
|
|
77
|
+
* Parse CLI arguments using two-phase parsing:
|
|
78
|
+
*
|
|
79
|
+
* Phase 1 (before package): Only snpx flags accepted.
|
|
80
|
+
* Unknown --flags → error. Single-dash flags (-y) → npx passthrough.
|
|
81
|
+
* Phase 2 (after package): Everything is passthrough to the executed tool.
|
|
82
|
+
*
|
|
83
|
+
* snpx [snpx-flags] <package> [tool-args...]
|
|
84
|
+
* snpx [snpx-flags] (--help, --self-update, etc.)
|
|
70
85
|
*/
|
|
71
86
|
export function parseArgs(argv) {
|
|
72
87
|
const args = argv.slice(2);
|
|
@@ -78,14 +93,21 @@ export function parseArgs(argv) {
|
|
|
78
93
|
time: null,
|
|
79
94
|
fallbackStrategy: null,
|
|
80
95
|
};
|
|
81
|
-
const npxArgs = [];
|
|
82
96
|
let pkgSpec = null;
|
|
83
97
|
let pkgName = null;
|
|
84
|
-
|
|
98
|
+
const restArgs = [];
|
|
99
|
+
let foundPackage = false;
|
|
85
100
|
|
|
86
101
|
for (let i = 0; i < args.length; i++) {
|
|
87
102
|
const arg = args[i];
|
|
88
103
|
|
|
104
|
+
// Phase 2: after package name, everything is passthrough
|
|
105
|
+
if (foundPackage) {
|
|
106
|
+
restArgs.push(arg);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Phase 1: before package, only known snpx flags accepted
|
|
89
111
|
if (arg === '-h' || arg === '--help') {
|
|
90
112
|
snpxFlags.help = true;
|
|
91
113
|
} else if (arg === '--show-version') {
|
|
@@ -104,30 +126,34 @@ export function parseArgs(argv) {
|
|
|
104
126
|
snpxFlags.fallbackStrategy = args[++i];
|
|
105
127
|
} else if (arg.startsWith('--fallback-strategy=')) {
|
|
106
128
|
snpxFlags.fallbackStrategy = arg.slice('--fallback-strategy='.length);
|
|
129
|
+
} else if (arg.startsWith('--')) {
|
|
130
|
+
throw new Error(`Unknown flag: ${arg}. Run 'snpx --help' for available options.`);
|
|
131
|
+
} else if (arg.startsWith('-')) {
|
|
132
|
+
// Single-dash npx flags (e.g. -y, -p) are always passthrough
|
|
133
|
+
restArgs.push(arg);
|
|
107
134
|
} else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
// Positional: check if it's a package name
|
|
136
|
+
const latestMatch = arg.match(/^(@[^/]+\/[^@]+|[^@]+)@latest$/);
|
|
137
|
+
if (latestMatch) {
|
|
138
|
+
pkgSpec = arg;
|
|
139
|
+
pkgName = latestMatch[1];
|
|
140
|
+
foundPackage = true;
|
|
141
|
+
} else {
|
|
142
|
+
const bareMatch = arg.match(/^(@[^/]+\/[^@]+|[^@]+)$/);
|
|
143
|
+
if (bareMatch) {
|
|
144
|
+
pkgSpec = arg;
|
|
145
|
+
pkgName = bareMatch[1];
|
|
146
|
+
foundPackage = true;
|
|
112
147
|
} else {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
pkgName = latestMatch[1];
|
|
118
|
-
} else {
|
|
119
|
-
const bareMatch = arg.match(/^(@[^/]+\/[^@]+|[^@]+)$/);
|
|
120
|
-
if (bareMatch) {
|
|
121
|
-
pkgSpec = arg;
|
|
122
|
-
pkgName = bareMatch[1];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
148
|
+
// Not a recognized package spec (e.g. pkg@1.0.0).
|
|
149
|
+
// Stop phase 1; treat this and everything after as npx passthrough.
|
|
150
|
+
foundPackage = true;
|
|
151
|
+
restArgs.push(arg);
|
|
125
152
|
}
|
|
126
153
|
}
|
|
127
154
|
}
|
|
128
155
|
}
|
|
129
156
|
|
|
130
|
-
const restArgs = npxArgs.filter(a => a !== pkgSpec);
|
|
131
157
|
return { snpxFlags, pkgSpec, pkgName, restArgs, isLatest: !!(pkgSpec && pkgSpec.includes('@latest')) };
|
|
132
158
|
}
|
|
133
159
|
|
|
@@ -263,14 +289,14 @@ export async function checkSelfUpdate() {
|
|
|
263
289
|
try {
|
|
264
290
|
const data = await fetchPackageMetadata(PKG_NAME);
|
|
265
291
|
const latest = data['dist-tags']?.latest;
|
|
266
|
-
if (!latest) return { hasUpdate: false, currentVersion: '0.2.
|
|
292
|
+
if (!latest) return { hasUpdate: false, currentVersion: '0.2.2', latestVersion: null };
|
|
267
293
|
|
|
268
|
-
const currentVersion = '0.2.
|
|
294
|
+
const currentVersion = '0.2.2'; // Should match package.json
|
|
269
295
|
const hasUpdate = latest !== currentVersion;
|
|
270
296
|
|
|
271
297
|
return { hasUpdate, currentVersion, latestVersion: latest };
|
|
272
298
|
} catch {
|
|
273
|
-
return { hasUpdate: false, currentVersion: '0.2.
|
|
299
|
+
return { hasUpdate: false, currentVersion: '0.2.2', latestVersion: null };
|
|
274
300
|
}
|
|
275
301
|
}
|
|
276
302
|
|