pmsec 0.8.0 → 0.9.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/package.json +1 -1
- package/src/cli.mjs +34 -37
- package/src/tools/bun.mjs +13 -18
- package/src/tools/cargo.mjs +10 -11
- package/src/tools/mise.mjs +14 -19
- package/src/tools/npm.mjs +14 -19
- package/src/tools/pnpm.mjs +25 -17
- package/src/tools/uv.mjs +13 -18
- package/src/tools/yarn.mjs +14 -19
- package/src/util/io.mjs +1 -1
- package/src/util/version.mjs +11 -1
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { shellQuote } from "./util/io.mjs";
|
|
3
4
|
import * as npm from "./tools/npm.mjs";
|
|
4
5
|
import * as pnpm from "./tools/pnpm.mjs";
|
|
5
6
|
import * as yarn from "./tools/yarn.mjs";
|
|
@@ -11,7 +12,7 @@ import * as uv from "./tools/uv.mjs";
|
|
|
11
12
|
const TOOLS = [npm, pnpm, yarn, bun, cargo, mise, uv];
|
|
12
13
|
// Default cooldown for the hardening bundle. Override per-invocation with
|
|
13
14
|
// `--days N`; the default tracks the safest value we'd recommend.
|
|
14
|
-
const BUNDLE_DAYS =
|
|
15
|
+
const BUNDLE_DAYS = 1;
|
|
15
16
|
export const VERSION = JSON.parse(
|
|
16
17
|
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
17
18
|
).version;
|
|
@@ -30,7 +31,7 @@ Commands:
|
|
|
30
31
|
|
|
31
32
|
Options:
|
|
32
33
|
--tool TOOL[,TOOL] Restrict to specific tools (npm,pnpm,yarn,bun,cargo,mise,uv)
|
|
33
|
-
--days N Override cooldown days (default
|
|
34
|
+
--days N Override cooldown days (default 1)
|
|
34
35
|
--force Overwrite stricter existing cooldowns (otherwise enable is monotonic)
|
|
35
36
|
--json Emit JSON output
|
|
36
37
|
-V, --version Show version
|
|
@@ -79,20 +80,20 @@ function selectTools(only) {
|
|
|
79
80
|
return found;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
function preflightWarn(t) {
|
|
83
|
+
function preflightWarn(t, ctx) {
|
|
83
84
|
if (typeof t.preflight !== "function") return null;
|
|
84
|
-
const pf = t.preflight();
|
|
85
|
+
const pf = t.preflight(ctx);
|
|
85
86
|
return pf?.message ?? null;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
async function gatherStatus(targets,
|
|
89
|
+
async function gatherStatus(targets, ctx) {
|
|
89
90
|
return Promise.all(targets.map(async t => {
|
|
90
|
-
const r = await t.read(
|
|
91
|
+
const r = await t.read(ctx);
|
|
91
92
|
return {
|
|
92
93
|
tool: t.name, key: t.key, path: r.path,
|
|
93
94
|
configured: r.configured, days: r.days,
|
|
94
95
|
extras: r.extras ?? [],
|
|
95
|
-
warn: preflightWarn(t)
|
|
96
|
+
warn: preflightWarn(t, ctx)
|
|
96
97
|
};
|
|
97
98
|
}));
|
|
98
99
|
}
|
|
@@ -113,8 +114,8 @@ function renderHuman(rows, min) {
|
|
|
113
114
|
return lines.join("\n") + "\n";
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
async function runCheck(targets, { json, days },
|
|
117
|
-
const rows = await gatherStatus(targets,
|
|
117
|
+
async function runCheck(targets, { json, days }, ctx, out, err) {
|
|
118
|
+
const rows = await gatherStatus(targets, ctx);
|
|
118
119
|
const failingPrimary = rows.filter(r => r.days === null || r.days < days);
|
|
119
120
|
const failingExtras = rows.flatMap(r => r.extras.filter(e => !e.ok));
|
|
120
121
|
const ok = failingPrimary.length === 0 && failingExtras.length === 0;
|
|
@@ -125,41 +126,38 @@ async function runCheck(targets, { json, days }, env, home, platform, out, err)
|
|
|
125
126
|
return ok ? 0 : 1;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
function shQuote(s) { return s ? `'${String(s).replace(/'/g, `'\\''`)}'` : ""; }
|
|
129
|
-
|
|
130
129
|
function explainFsError(e, tool) {
|
|
131
130
|
if (e?.code === "EACCES" || e?.code === "EPERM") {
|
|
132
131
|
const p = e.path ?? "";
|
|
133
|
-
const q =
|
|
132
|
+
const q = p ? shellQuote(p) : "";
|
|
134
133
|
return `${tool}: cannot write ${p} (${e.code}). Check file ownership: \`ls -la ${q}\` — if owned by root, run \`sudo chown -h $(id -u):$(id -g) ${q}\`.`;
|
|
135
134
|
}
|
|
136
135
|
if (e?.code === "EROFS") return `${tool}: ${e.path ?? ""} is on a read-only filesystem (EROFS).`;
|
|
137
136
|
return `${tool}: ${e?.message ?? e}`;
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
async function runEnable(targets, json, days, force,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const warn = preflightWarn(t);
|
|
146
|
-
if (warn) warnings.push({ tool: t.name, warn });
|
|
139
|
+
async function runEnable(targets, json, days, force, ctx, out, err) {
|
|
140
|
+
// Each tool writes a different config file; safe to run in parallel.
|
|
141
|
+
// Promise.all preserves input order in the results array.
|
|
142
|
+
const results = await Promise.all(targets.map(async t => {
|
|
143
|
+
const warn = preflightWarn(t, ctx);
|
|
147
144
|
try {
|
|
148
145
|
let effective = days;
|
|
149
146
|
let kept = false;
|
|
150
147
|
if (!force) {
|
|
151
|
-
const current = await t.read(
|
|
148
|
+
const current = await t.read(ctx);
|
|
152
149
|
const currentDays = current.days ?? 0;
|
|
153
150
|
effective = Math.max(currentDays, days);
|
|
154
151
|
kept = currentDays >= days && currentDays > 0;
|
|
155
152
|
}
|
|
156
|
-
const r = await t.write(effective,
|
|
157
|
-
|
|
153
|
+
const r = await t.write(effective, ctx);
|
|
154
|
+
return { tool: t.name, path: r.path, days: effective, requested: days, kept, forced: force, ok: true, warn };
|
|
158
155
|
} catch (e) {
|
|
159
|
-
|
|
160
|
-
results.push({ tool: t.name, path: e?.path ?? null, days, requested: days, kept: false, forced: force, ok: false, error: explainFsError(e, t.name), warn });
|
|
156
|
+
return { tool: t.name, path: e?.path ?? null, days, requested: days, kept: false, forced: force, ok: false, error: explainFsError(e, t.name), warn };
|
|
161
157
|
}
|
|
162
|
-
}
|
|
158
|
+
}));
|
|
159
|
+
const failures = results.filter(r => !r.ok).map(r => ({ tool: r.tool, error: r.error }));
|
|
160
|
+
const warnings = results.filter(r => r.warn).map(r => ({ tool: r.tool, warn: r.warn }));
|
|
163
161
|
if (json) out.write(JSON.stringify({ enabled: true, bundleDays: days, results, warnings, ok: failures.length === 0 }, null, 2) + "\n");
|
|
164
162
|
else for (const r of results) {
|
|
165
163
|
if (r.ok) {
|
|
@@ -176,18 +174,16 @@ async function runEnable(targets, json, days, force, env, home, platform, out, e
|
|
|
176
174
|
return failures.length ? 1 : 0;
|
|
177
175
|
}
|
|
178
176
|
|
|
179
|
-
async function runDisable(targets, json,
|
|
180
|
-
const results =
|
|
181
|
-
const failures = [];
|
|
182
|
-
for (const t of targets) {
|
|
177
|
+
async function runDisable(targets, json, ctx, out, err) {
|
|
178
|
+
const results = await Promise.all(targets.map(async t => {
|
|
183
179
|
try {
|
|
184
|
-
const r = await t.unset(
|
|
185
|
-
|
|
180
|
+
const r = await t.unset(ctx);
|
|
181
|
+
return { tool: t.name, path: r.path, removed: r.removed, ok: true };
|
|
186
182
|
} catch (e) {
|
|
187
|
-
|
|
188
|
-
results.push({ tool: t.name, path: e?.path ?? null, removed: false, ok: false, error: explainFsError(e, t.name) });
|
|
183
|
+
return { tool: t.name, path: e?.path ?? null, removed: false, ok: false, error: explainFsError(e, t.name) };
|
|
189
184
|
}
|
|
190
|
-
}
|
|
185
|
+
}));
|
|
186
|
+
const failures = results.filter(r => !r.ok).map(r => ({ tool: r.tool, error: r.error }));
|
|
191
187
|
if (json) out.write(JSON.stringify({ results, ok: failures.length === 0 }, null, 2) + "\n");
|
|
192
188
|
else for (const r of results) {
|
|
193
189
|
if (!r.ok) out.write(`FAIL ${r.tool.padEnd(4)} ${r.error}\n`);
|
|
@@ -212,10 +208,11 @@ export async function run(argv, {
|
|
|
212
208
|
let targets;
|
|
213
209
|
try { targets = selectTools(opts.only); }
|
|
214
210
|
catch (e) { err.write(`pmsec: ${e.message}\n`); return 2; }
|
|
211
|
+
const ctx = { env, home, platform };
|
|
215
212
|
try {
|
|
216
|
-
if (opts.command === "check") return await runCheck(targets, opts,
|
|
217
|
-
if (opts.command === "enable") return await runEnable(targets, opts.json, opts.days, opts.force,
|
|
218
|
-
if (opts.command === "disable") return await runDisable(targets, opts.json,
|
|
213
|
+
if (opts.command === "check") return await runCheck(targets, opts, ctx, out, err);
|
|
214
|
+
if (opts.command === "enable") return await runEnable(targets, opts.json, opts.days, opts.force, ctx, out, err);
|
|
215
|
+
if (opts.command === "disable") return await runDisable(targets, opts.json, ctx, out, err);
|
|
219
216
|
err.write(`pmsec: unknown command "${opts.command}"\n`);
|
|
220
217
|
return 2;
|
|
221
218
|
} catch (e) {
|
package/src/tools/bun.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { bunConfigPath } from "../util/paths.mjs";
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { buildPreflight } from "../util/version.mjs";
|
|
5
5
|
|
|
6
6
|
export const name = "bun";
|
|
7
7
|
export const key = "minimumReleaseAge";
|
|
@@ -10,33 +10,28 @@ export const docs = "https://bun.com/docs/runtime/bunfig#install";
|
|
|
10
10
|
export const minBin = [1, 3, 0];
|
|
11
11
|
export const extras = [];
|
|
12
12
|
|
|
13
|
-
export function path(
|
|
13
|
+
export function path(ctx) { return bunConfigPath(ctx.env, ctx.home); }
|
|
14
14
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
if (v === null) return { ok: true, message: null };
|
|
18
|
-
if (gte(v, minBin)) return { ok: true, version: v.raw, message: null };
|
|
19
|
-
return { ok: true, warn: true, version: v.raw, message: `bun ${v.raw} < ${minBin.join(".")}: minimumReleaseAge is silently ignored. Upgrade bun to enforce the cooldown.` };
|
|
20
|
-
}
|
|
15
|
+
export const preflight = buildPreflight(name, minBin,
|
|
16
|
+
"minimumReleaseAge is silently ignored. Upgrade bun to enforce the cooldown.");
|
|
21
17
|
|
|
22
|
-
export async function read(
|
|
23
|
-
const p = path(
|
|
18
|
+
export async function read(ctx) {
|
|
19
|
+
const p = path(ctx);
|
|
24
20
|
const raw = await readSafe(p);
|
|
25
21
|
const value = readKey(raw, key, { section });
|
|
26
22
|
const seconds = value === null ? null : Number(value);
|
|
27
23
|
return { path: p, configured: value, days: seconds === null || Number.isNaN(seconds) ? null : Math.floor(seconds / 86400), extras: [] };
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
export async function write(days,
|
|
31
|
-
const p = path(
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return { path: p, before, after };
|
|
26
|
+
export async function write(days, ctx) {
|
|
27
|
+
const p = path(ctx);
|
|
28
|
+
const text = setKey(await readSafe(p), key, `${key} = ${days * 86400}`, { section });
|
|
29
|
+
await writeAtomic(p, text);
|
|
30
|
+
return { path: p };
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
export async function unset(
|
|
39
|
-
const p = path(
|
|
33
|
+
export async function unset(ctx) {
|
|
34
|
+
const p = path(ctx);
|
|
40
35
|
const before = await readSafe(p);
|
|
41
36
|
const { text: after, removed } = removeKey(before, key, { section });
|
|
42
37
|
if (removed) await writeAtomic(p, after);
|
package/src/tools/cargo.mjs
CHANGED
|
@@ -8,7 +8,7 @@ export const section = "install";
|
|
|
8
8
|
export const docs = "https://rust-lang.github.io/rfcs/3801-package-cooldown.html";
|
|
9
9
|
export const extras = [];
|
|
10
10
|
|
|
11
|
-
export function path(
|
|
11
|
+
export function path(ctx) { return cargoConfigPath(ctx.env, ctx.home); }
|
|
12
12
|
|
|
13
13
|
function parseDays(value) {
|
|
14
14
|
if (value === null) return null;
|
|
@@ -18,23 +18,22 @@ function parseDays(value) {
|
|
|
18
18
|
return /^(w|weeks?)$/i.test(m[2]) ? n * 7 : n;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export async function read(
|
|
22
|
-
const p = path(
|
|
21
|
+
export async function read(ctx) {
|
|
22
|
+
const p = path(ctx);
|
|
23
23
|
const raw = await readSafe(p);
|
|
24
24
|
const value = readKey(raw, key, { section });
|
|
25
25
|
return { path: p, configured: value, days: parseDays(value), extras: [] };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export async function write(days,
|
|
29
|
-
const p = path(
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return { path: p, before, after };
|
|
28
|
+
export async function write(days, ctx) {
|
|
29
|
+
const p = path(ctx);
|
|
30
|
+
const text = setKey(await readSafe(p), key, `${key} = "${days}d"`, { section });
|
|
31
|
+
await writeAtomic(p, text);
|
|
32
|
+
return { path: p };
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
export async function unset(
|
|
37
|
-
const p = path(
|
|
35
|
+
export async function unset(ctx) {
|
|
36
|
+
const p = path(ctx);
|
|
38
37
|
const before = await readSafe(p);
|
|
39
38
|
const { text: after, removed } = removeKey(before, key, { section });
|
|
40
39
|
if (removed) await writeAtomic(p, after);
|
package/src/tools/mise.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { miseConfigPath } from "../util/paths.mjs";
|
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
4
|
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { buildPreflight } from "../util/version.mjs";
|
|
6
6
|
export const name = "mise";
|
|
7
7
|
export const key = "minimum_release_age";
|
|
8
8
|
export const section = "settings";
|
|
@@ -12,14 +12,10 @@ export const extras = [
|
|
|
12
12
|
{ key: "paranoid", expected: "true", line: "paranoid = true", section }
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
-
export function path(
|
|
15
|
+
export function path(ctx) { return miseConfigPath(ctx.env, ctx.home, ctx.platform); }
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
if (v === null) return { ok: true, message: null };
|
|
20
|
-
if (gte(v, minBin)) return { ok: true, version: v.raw, message: null };
|
|
21
|
-
return { ok: true, warn: true, version: v.raw, message: `mise ${v.raw} < ${minBin.join(".")}: setting was named install_before before 2026.4.22 and minimum_release_age is silently ignored on older mise. Upgrade mise (\`mise self-update\`) to enforce the cooldown.` };
|
|
22
|
-
}
|
|
17
|
+
export const preflight = buildPreflight(name, minBin,
|
|
18
|
+
"setting was named install_before before 2026.4.22 and minimum_release_age is silently ignored on older mise. Upgrade mise (`mise self-update`) to enforce the cooldown.");
|
|
23
19
|
|
|
24
20
|
function parseDays(value) {
|
|
25
21
|
if (value === null) return null;
|
|
@@ -33,8 +29,8 @@ function parseDays(value) {
|
|
|
33
29
|
return n;
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
export async function read(
|
|
37
|
-
const p = path(
|
|
32
|
+
export async function read(ctx) {
|
|
33
|
+
const p = path(ctx);
|
|
38
34
|
const raw = await readSafe(p);
|
|
39
35
|
const value = readKey(raw, key, { section });
|
|
40
36
|
return {
|
|
@@ -43,17 +39,16 @@ export async function read(env, home, platform) {
|
|
|
43
39
|
};
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
export async function write(days,
|
|
47
|
-
const p = path(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return { path: p, before, after };
|
|
42
|
+
export async function write(days, ctx) {
|
|
43
|
+
const p = path(ctx);
|
|
44
|
+
let text = setKey(await readSafe(p), key, `${key} = "${days}d"`, { section });
|
|
45
|
+
text = applyExtras(text, extras);
|
|
46
|
+
await writeAtomic(p, text);
|
|
47
|
+
return { path: p };
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
export async function unset(
|
|
56
|
-
const p = path(
|
|
50
|
+
export async function unset(ctx) {
|
|
51
|
+
const p = path(ctx);
|
|
57
52
|
const before = await readSafe(p);
|
|
58
53
|
const cooldown = removeKey(before, key, { section });
|
|
59
54
|
const ex = removeExtras(cooldown.text, extras);
|
package/src/tools/npm.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { npmrcPath } from "../util/paths.mjs";
|
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
4
|
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { buildPreflight } from "../util/version.mjs";
|
|
6
6
|
|
|
7
7
|
export const name = "npm";
|
|
8
8
|
export const key = "min-release-age";
|
|
@@ -12,17 +12,13 @@ export const extras = [
|
|
|
12
12
|
{ key: "audit-level", expected: "high", line: "audit-level=high" }
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
-
export function path(
|
|
15
|
+
export function path(ctx) { return npmrcPath(ctx.env, ctx.home); }
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
if (v === null) return { ok: true, message: null };
|
|
20
|
-
if (gte(v, minBin)) return { ok: true, version: v.raw, message: null };
|
|
21
|
-
return { ok: true, warn: true, version: v.raw, message: `npm ${v.raw} < ${minBin.join(".")}: min-release-age is silently ignored. Upgrade npm to enforce the cooldown.` };
|
|
22
|
-
}
|
|
17
|
+
export const preflight = buildPreflight(name, minBin,
|
|
18
|
+
"min-release-age is silently ignored. Upgrade npm to enforce the cooldown.");
|
|
23
19
|
|
|
24
|
-
export async function read(
|
|
25
|
-
const p = path(
|
|
20
|
+
export async function read(ctx) {
|
|
21
|
+
const p = path(ctx);
|
|
26
22
|
const raw = await readSafe(p);
|
|
27
23
|
const value = readKey(raw, key);
|
|
28
24
|
return {
|
|
@@ -32,17 +28,16 @@ export async function read(env, home) {
|
|
|
32
28
|
};
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
export async function write(days,
|
|
36
|
-
const p = path(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return { path: p, before, after };
|
|
31
|
+
export async function write(days, ctx) {
|
|
32
|
+
const p = path(ctx);
|
|
33
|
+
let text = setKey(await readSafe(p), key, `${key}=${days}`);
|
|
34
|
+
text = applyExtras(text, extras);
|
|
35
|
+
await writeAtomic(p, text);
|
|
36
|
+
return { path: p };
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
export async function unset(
|
|
45
|
-
const p = path(
|
|
39
|
+
export async function unset(ctx) {
|
|
40
|
+
const p = path(ctx);
|
|
46
41
|
const before = await readSafe(p);
|
|
47
42
|
const cooldown = removeKey(before, key);
|
|
48
43
|
const ex = removeExtras(cooldown.text, extras);
|
package/src/tools/pnpm.mjs
CHANGED
|
@@ -17,25 +17,34 @@ export const extras = [
|
|
|
17
17
|
{ key: "strict-dep-builds", expected: "true", line: "strict-dep-builds=true" }
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
-
export function path(
|
|
20
|
+
export function path(ctx) { return pnpmRcPath(ctx.env, ctx.home, ctx.platform); }
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Cache `pnpm --version` for the lifetime of the process. preflight() and
|
|
23
|
+
// read() both want it; without memoization a single `pmsec check` spawns
|
|
24
|
+
// pnpm twice. Cache key is the override env value so tests with different
|
|
25
|
+
// `PMSEC_PNPM_VERSION` settings invalidate naturally.
|
|
26
|
+
let _versionCache = null;
|
|
27
|
+
function pnpmVersion(ctx) {
|
|
28
|
+
const override = ctx.env?.PMSEC_PNPM_VERSION ?? null;
|
|
29
|
+
if (_versionCache && _versionCache.override === override) return _versionCache.value;
|
|
30
|
+
const v = detectVersion("pnpm", ["--version"], { env: ctx.env, overrideKey: "PMSEC_PNPM_VERSION" });
|
|
31
|
+
_versionCache = { override, value: v };
|
|
32
|
+
return v;
|
|
24
33
|
}
|
|
25
34
|
|
|
26
|
-
export function preflight(
|
|
27
|
-
const v = pnpmVersion(
|
|
35
|
+
export function preflight(ctx) {
|
|
36
|
+
const v = pnpmVersion(ctx);
|
|
28
37
|
if (v === null) return { ok: true, message: null };
|
|
29
38
|
if (gte(v, minBin)) return { ok: true, version: v.raw, message: null };
|
|
30
39
|
return { ok: true, warn: true, version: v.raw, message: `pnpm ${v.raw} < ${minBin.join(".")}: minimum-release-age is silently ignored. Upgrade pnpm to enforce the cooldown.` };
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
export async function read(
|
|
34
|
-
const p = path(
|
|
42
|
+
export async function read(ctx) {
|
|
43
|
+
const p = path(ctx);
|
|
35
44
|
const raw = await readSafe(p);
|
|
36
45
|
const value = readKey(raw, key);
|
|
37
46
|
const minutes = value === null ? null : Number(value);
|
|
38
|
-
const v = pnpmVersion(
|
|
47
|
+
const v = pnpmVersion(ctx);
|
|
39
48
|
const defaults = new Map(extras.filter(x => x.defaultSinceMajor).map(x => [x.key, x.defaultSinceMajor]));
|
|
40
49
|
const evaluated = readExtras(raw, extras).map(e => {
|
|
41
50
|
const def = defaults.get(e.key);
|
|
@@ -51,17 +60,16 @@ export async function read(env, home, platform) {
|
|
|
51
60
|
};
|
|
52
61
|
}
|
|
53
62
|
|
|
54
|
-
export async function write(days,
|
|
55
|
-
const p = path(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return { path: p, before, after };
|
|
63
|
+
export async function write(days, ctx) {
|
|
64
|
+
const p = path(ctx);
|
|
65
|
+
let text = setKey(await readSafe(p), key, `${key}=${days * 24 * 60}`);
|
|
66
|
+
text = applyExtras(text, extras);
|
|
67
|
+
await writeAtomic(p, text);
|
|
68
|
+
return { path: p };
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
export async function unset(
|
|
64
|
-
const p = path(
|
|
71
|
+
export async function unset(ctx) {
|
|
72
|
+
const p = path(ctx);
|
|
65
73
|
const before = await readSafe(p);
|
|
66
74
|
const cooldown = removeKey(before, key);
|
|
67
75
|
const ex = removeExtras(cooldown.text, extras);
|
package/src/tools/uv.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { uvConfigPath } from "../util/paths.mjs";
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { buildPreflight } from "../util/version.mjs";
|
|
5
5
|
|
|
6
6
|
export const name = "uv";
|
|
7
7
|
export const key = "exclude-newer";
|
|
@@ -9,14 +9,10 @@ export const docs = "https://docs.astral.sh/uv/reference/settings/#exclude-newer
|
|
|
9
9
|
export const minBin = [0, 9, 17];
|
|
10
10
|
export const extras = [];
|
|
11
11
|
|
|
12
|
-
export function path(
|
|
12
|
+
export function path(ctx) { return uvConfigPath(ctx.env, ctx.home, ctx.platform); }
|
|
13
13
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
if (v === null) return { ok: true, message: null };
|
|
17
|
-
if (gte(v, minBin)) return { ok: true, version: v.raw, message: null };
|
|
18
|
-
return { ok: true, warn: true, version: v.raw, message: `uv ${v.raw} < ${minBin.join(".")}: writing exclude-newer = "N days" will break this uv until you \`uv self update\` (file will fail to parse).` };
|
|
19
|
-
}
|
|
14
|
+
export const preflight = buildPreflight(name, minBin,
|
|
15
|
+
"writing exclude-newer = \"N days\" will break this uv until you `uv self update` (file will fail to parse).");
|
|
20
16
|
|
|
21
17
|
function parseDays(value) {
|
|
22
18
|
if (value === null) return null;
|
|
@@ -26,23 +22,22 @@ function parseDays(value) {
|
|
|
26
22
|
return /^(week|weeks|w)$/i.test(m[2]) ? n * 7 : n;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
export async function read(
|
|
30
|
-
const p = path(
|
|
25
|
+
export async function read(ctx) {
|
|
26
|
+
const p = path(ctx);
|
|
31
27
|
const raw = await readSafe(p);
|
|
32
28
|
const value = readKey(raw, key);
|
|
33
29
|
return { path: p, configured: value, days: parseDays(value), extras: [] };
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
export async function write(days,
|
|
37
|
-
const p = path(
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return { path: p, before, after };
|
|
32
|
+
export async function write(days, ctx) {
|
|
33
|
+
const p = path(ctx);
|
|
34
|
+
const text = setKey(await readSafe(p), key, `${key} = "${days} days"`);
|
|
35
|
+
await writeAtomic(p, text);
|
|
36
|
+
return { path: p };
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
export async function unset(
|
|
45
|
-
const p = path(
|
|
39
|
+
export async function unset(ctx) {
|
|
40
|
+
const p = path(ctx);
|
|
46
41
|
const before = await readSafe(p);
|
|
47
42
|
const { text: after, removed } = removeKey(before, key);
|
|
48
43
|
if (removed) await writeAtomic(p, after);
|
package/src/tools/yarn.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { yarnrcPath } from "../util/paths.mjs";
|
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
4
|
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { buildPreflight } from "../util/version.mjs";
|
|
6
6
|
|
|
7
7
|
export const name = "yarn";
|
|
8
8
|
export const key = "npmMinimalAgeGate";
|
|
@@ -13,14 +13,10 @@ export const extras = [
|
|
|
13
13
|
{ key: "enableHardenedMode", expected: "true", line: "enableHardenedMode: true", sep: SEP }
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
-
export function path(
|
|
16
|
+
export function path(ctx) { return yarnrcPath(ctx.env, ctx.home); }
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
if (v === null) return { ok: true, message: null };
|
|
21
|
-
if (gte(v, minBin)) return { ok: true, version: v.raw, message: null };
|
|
22
|
-
return { ok: true, warn: true, version: v.raw, message: `yarn ${v.raw} < ${minBin.join(".")}: npmMinimalAgeGate is silently ignored. Upgrade yarn (v4.10+) to enforce the cooldown.` };
|
|
23
|
-
}
|
|
18
|
+
export const preflight = buildPreflight(name, minBin,
|
|
19
|
+
"npmMinimalAgeGate is silently ignored. Upgrade yarn (v4.10+) to enforce the cooldown.");
|
|
24
20
|
|
|
25
21
|
function parseDays(value) {
|
|
26
22
|
if (value === null) return null;
|
|
@@ -30,8 +26,8 @@ function parseDays(value) {
|
|
|
30
26
|
return /^(w|weeks?)$/i.test(m[2]) ? n * 7 : n;
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
export async function read(
|
|
34
|
-
const p = path(
|
|
29
|
+
export async function read(ctx) {
|
|
30
|
+
const p = path(ctx);
|
|
35
31
|
const raw = await readSafe(p);
|
|
36
32
|
const value = readKey(raw, key, { sep: SEP });
|
|
37
33
|
return {
|
|
@@ -40,17 +36,16 @@ export async function read(env, home) {
|
|
|
40
36
|
};
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
export async function write(days,
|
|
44
|
-
const p = path(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return { path: p, before, after };
|
|
39
|
+
export async function write(days, ctx) {
|
|
40
|
+
const p = path(ctx);
|
|
41
|
+
let text = setKey(await readSafe(p), key, `${key}: "${days}d"`, { sep: SEP });
|
|
42
|
+
text = applyExtras(text, extras);
|
|
43
|
+
await writeAtomic(p, text);
|
|
44
|
+
return { path: p };
|
|
50
45
|
}
|
|
51
46
|
|
|
52
|
-
export async function unset(
|
|
53
|
-
const p = path(
|
|
47
|
+
export async function unset(ctx) {
|
|
48
|
+
const p = path(ctx);
|
|
54
49
|
const before = await readSafe(p);
|
|
55
50
|
const cooldown = removeKey(before, key, { sep: SEP });
|
|
56
51
|
const ex = removeExtras(cooldown.text, extras);
|
package/src/util/io.mjs
CHANGED
|
@@ -23,7 +23,7 @@ function underHome(p, home) {
|
|
|
23
23
|
return r === h || r.startsWith(h + sep);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function shellQuote(s) { return `'${String(s).replace(/'/g, `'\\''`)}'`; }
|
|
26
|
+
export function shellQuote(s) { return `'${String(s).replace(/'/g, `'\\''`)}'`; }
|
|
27
27
|
|
|
28
28
|
async function reclaim(path, home, err) {
|
|
29
29
|
if (process.platform === "win32" || typeof process.getuid !== "function") return false;
|
package/src/util/version.mjs
CHANGED
|
@@ -19,7 +19,7 @@ export function detectVersion(bin, args = ["--version"], { env, overrideKey } =
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
try {
|
|
22
|
-
const res = spawnSync(bin, args, { encoding: "utf8" });
|
|
22
|
+
const res = spawnSync(bin, args, { encoding: "utf8", timeout: 5000, killSignal: "SIGKILL" });
|
|
23
23
|
if (res.status !== 0) return null;
|
|
24
24
|
return parseSemver(res.stdout);
|
|
25
25
|
} catch { return null; }
|
|
@@ -32,3 +32,13 @@ export function gte(v, target) {
|
|
|
32
32
|
if (v.minor !== b) return v.minor > b;
|
|
33
33
|
return v.patch >= c;
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
export function buildPreflight(name, minBin, suffix) {
|
|
37
|
+
return () => {
|
|
38
|
+
const v = detectVersion(name);
|
|
39
|
+
if (v === null) return { ok: true, message: null };
|
|
40
|
+
if (gte(v, minBin)) return { ok: true, version: v.raw, message: null };
|
|
41
|
+
return { ok: true, warn: true, version: v.raw,
|
|
42
|
+
message: `${name} ${v.raw} < ${minBin.join(".")}: ${suffix}` };
|
|
43
|
+
};
|
|
44
|
+
}
|