pmsec 0.3.0 → 0.4.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 +5 -4
- package/package.json +8 -3
- package/src/cli.mjs +30 -7
- package/src/tools/bun.mjs +2 -1
- package/src/tools/cargo.mjs +2 -1
- package/src/tools/mise.mjs +14 -4
- package/src/tools/npm.mjs +15 -4
- package/src/tools/pnpm.mjs +16 -4
- package/src/tools/uv.mjs +2 -1
- package/src/tools/yarn.mjs +14 -4
- package/src/util/extras.mjs +32 -0
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ npx --registry=https://registry.npmjs.org/ --min-release-age=0 pmsec check
|
|
|
28
28
|
uvx --index https://pypi.org/simple --exclude-newer-package pmsec=2099-01-01 pmsec check
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Zero runtime dependencies, ESM, requires Node
|
|
31
|
+
Zero runtime dependencies, ESM, requires Node 22+.
|
|
32
32
|
|
|
33
33
|
## Commands
|
|
34
34
|
|
|
@@ -37,13 +37,14 @@ Zero runtime dependencies, ESM, requires Node 20+.
|
|
|
37
37
|
| `pmsec check [--min N]` | Read each tool's config; exit 1 if any tool is below `N` days or unset. |
|
|
38
38
|
| `pmsec set <DAYS>` | Write `DAYS`-day cooldown to every selected tool. Always proceeds; if the runtime is too old to honor the key, prints a `⚠` line under the success line. |
|
|
39
39
|
| `pmsec unset` | Remove only the cooldown key from each config (other keys preserved). |
|
|
40
|
+
| `pmsec --version` | Print the installed pmsec version. |
|
|
40
41
|
|
|
41
42
|
Options: `--tool npm,pnpm,yarn,bun,cargo,mise,uv`, `--json`.
|
|
42
43
|
|
|
43
44
|
When the target file is owned by another user (typical: `~/.npmrc` left
|
|
44
|
-
root-owned by an old `sudo npm config set`),
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
root-owned by an old `sudo npm config set`), the write fails with `EACCES`
|
|
46
|
+
and pmsec prints the exact `chown` command needed to restore ownership —
|
|
47
|
+
re-run after applying it. pmsec never escalates privileges itself.
|
|
47
48
|
|
|
48
49
|
See the [project README](https://github.com/HikaruEgashira/pmsec) for the
|
|
49
50
|
full table of keys, units, paths, and environment overrides.
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmsec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"description": "Inspect and apply install-time cooldown
|
|
7
|
+
"description": "Inspect and apply install-time cooldown for npm, pnpm, yarn, bun, cargo, mise, and uv.",
|
|
8
8
|
"homepage": "https://github.com/HikaruEgashira/pmsec#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=
|
|
20
|
+
"node": ">=22"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
23
|
"pmsec": "./bin/cli.mjs"
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"min-release-age",
|
|
37
37
|
"exclude-newer",
|
|
38
38
|
"npm",
|
|
39
|
+
"pnpm",
|
|
40
|
+
"yarn",
|
|
41
|
+
"bun",
|
|
42
|
+
"cargo",
|
|
43
|
+
"mise",
|
|
39
44
|
"uv",
|
|
40
45
|
"pmsec"
|
|
41
46
|
]
|
package/src/cli.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import * as npm from "./tools/npm.mjs";
|
|
3
4
|
import * as pnpm from "./tools/pnpm.mjs";
|
|
4
5
|
import * as yarn from "./tools/yarn.mjs";
|
|
@@ -9,6 +10,9 @@ import * as uv from "./tools/uv.mjs";
|
|
|
9
10
|
|
|
10
11
|
const TOOLS = [npm, pnpm, yarn, bun, cargo, mise, uv];
|
|
11
12
|
const DEFAULT_MIN = 7;
|
|
13
|
+
export const VERSION = JSON.parse(
|
|
14
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
15
|
+
).version;
|
|
12
16
|
|
|
13
17
|
const USAGE = `pmsec <command> [options]
|
|
14
18
|
|
|
@@ -21,6 +25,7 @@ Options:
|
|
|
21
25
|
--tool TOOL[,TOOL] Restrict to specific tools (npm,pnpm,yarn,bun,cargo,mise,uv)
|
|
22
26
|
--min DAYS Minimum acceptable days for check (default ${DEFAULT_MIN})
|
|
23
27
|
--json Emit JSON output
|
|
28
|
+
-V, --version Show version
|
|
24
29
|
-h, --help Show this help
|
|
25
30
|
|
|
26
31
|
Examples:
|
|
@@ -30,11 +35,12 @@ Examples:
|
|
|
30
35
|
`;
|
|
31
36
|
|
|
32
37
|
function parse(argv) {
|
|
33
|
-
const opts = { command: null, days: null, min: DEFAULT_MIN, json: false, only: null, help: false };
|
|
38
|
+
const opts = { command: null, days: null, min: DEFAULT_MIN, json: false, only: null, help: false, version: false };
|
|
34
39
|
const positional = [];
|
|
35
40
|
for (let i = 0; i < argv.length; i++) {
|
|
36
41
|
const a = argv[i];
|
|
37
42
|
if (a === "-h" || a === "--help") opts.help = true;
|
|
43
|
+
else if (a === "-V" || a === "--version") opts.version = true;
|
|
38
44
|
else if (a === "--json") opts.json = true;
|
|
39
45
|
else if (a === "--min") opts.min = Number(argv[++i]);
|
|
40
46
|
else if (a.startsWith("--min=")) opts.min = Number(a.slice(6));
|
|
@@ -65,7 +71,12 @@ function preflightWarn(t) {
|
|
|
65
71
|
async function gatherStatus(targets, env, home, platform) {
|
|
66
72
|
return Promise.all(targets.map(async t => {
|
|
67
73
|
const r = await t.read(env, home, platform);
|
|
68
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
tool: t.name, key: t.key, path: r.path,
|
|
76
|
+
configured: r.configured, days: r.days,
|
|
77
|
+
extras: r.extras ?? [],
|
|
78
|
+
warn: preflightWarn(t)
|
|
79
|
+
};
|
|
69
80
|
}));
|
|
70
81
|
}
|
|
71
82
|
|
|
@@ -76,17 +87,25 @@ function renderHuman(rows, min) {
|
|
|
76
87
|
const value = r.configured ?? "(unset)";
|
|
77
88
|
const tail = r.warn ? `\n ⚠ ${r.warn}` : "";
|
|
78
89
|
lines.push(`${status} ${r.tool.padEnd(4)} ${r.key} = ${value} [${r.path}]${tail}`);
|
|
90
|
+
for (const e of r.extras) {
|
|
91
|
+
const exStatus = e.configured === null ? "MISSING" : e.ok ? "OK " : "STALE ";
|
|
92
|
+
const exValue = e.configured ?? "(unset)";
|
|
93
|
+
lines.push(`${exStatus} ${r.tool.padEnd(4)} ${e.key} = ${exValue} [${r.path}]`);
|
|
94
|
+
}
|
|
79
95
|
}
|
|
80
96
|
return lines.join("\n") + "\n";
|
|
81
97
|
}
|
|
82
98
|
|
|
83
99
|
async function runCheck(targets, { min, json }, env, home, platform, out, err) {
|
|
84
100
|
const rows = await gatherStatus(targets, env, home, platform);
|
|
85
|
-
const
|
|
86
|
-
|
|
101
|
+
const failingPrimary = rows.filter(r => r.days === null || r.days < min);
|
|
102
|
+
const failingExtras = rows.flatMap(r => r.extras.filter(e => !e.ok));
|
|
103
|
+
const ok = failingPrimary.length === 0 && failingExtras.length === 0;
|
|
104
|
+
if (json) out.write(JSON.stringify({ min, rows, ok }, null, 2) + "\n");
|
|
87
105
|
else out.write(renderHuman(rows, min));
|
|
88
|
-
if (
|
|
89
|
-
|
|
106
|
+
if (failingPrimary.length) err.write(`pmsec: ${failingPrimary.length} tool(s) below ${min} days\n`);
|
|
107
|
+
if (failingExtras.length) err.write(`pmsec: ${failingExtras.length} hardening setting(s) not at safe value\n`);
|
|
108
|
+
return ok ? 0 : 1;
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
function shQuote(s) { return s ? `'${String(s).replace(/'/g, `'\\''`)}'` : ""; }
|
|
@@ -102,7 +121,10 @@ function explainFsError(e, tool) {
|
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
async function runSet(targets, days, json, env, home, platform, out, err) {
|
|
105
|
-
if (!Number.isInteger(days) || days <= 0)
|
|
124
|
+
if (!Number.isInteger(days) || days <= 0) {
|
|
125
|
+
err.write(`pmsec: set requires integer DAYS > 0\n`);
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
|
106
128
|
const results = [];
|
|
107
129
|
const failures = [];
|
|
108
130
|
const warnings = [];
|
|
@@ -162,6 +184,7 @@ export async function run(argv, {
|
|
|
162
184
|
let opts;
|
|
163
185
|
try { opts = parse(argv); }
|
|
164
186
|
catch (e) { err.write(`pmsec: ${e.message}\n`); return 2; }
|
|
187
|
+
if (opts.version) { out.write(`pmsec ${VERSION}\n`); return 0; }
|
|
165
188
|
if (opts.help || !opts.command) { out.write(USAGE); return opts.help ? 0 : 2; }
|
|
166
189
|
let targets;
|
|
167
190
|
try { targets = selectTools(opts.only); }
|
package/src/tools/bun.mjs
CHANGED
|
@@ -8,6 +8,7 @@ export const key = "minimumReleaseAge";
|
|
|
8
8
|
export const section = "install";
|
|
9
9
|
export const docs = "https://bun.com/docs/runtime/bunfig#install";
|
|
10
10
|
export const minBin = [1, 3, 0];
|
|
11
|
+
export const extras = [];
|
|
11
12
|
|
|
12
13
|
export function path(env, home) { return bunConfigPath(env, home); }
|
|
13
14
|
|
|
@@ -23,7 +24,7 @@ export async function read(env, home) {
|
|
|
23
24
|
const raw = await readSafe(p);
|
|
24
25
|
const value = readKey(raw, key, { section });
|
|
25
26
|
const seconds = value === null ? null : Number(value);
|
|
26
|
-
return { path: p, configured: value, days: seconds === null || Number.isNaN(seconds) ? null : Math.floor(seconds / 86400) };
|
|
27
|
+
return { path: p, configured: value, days: seconds === null || Number.isNaN(seconds) ? null : Math.floor(seconds / 86400), extras: [] };
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export async function write(days, env, home) {
|
package/src/tools/cargo.mjs
CHANGED
|
@@ -6,6 +6,7 @@ export const name = "cargo";
|
|
|
6
6
|
export const key = "minimum-release-age";
|
|
7
7
|
export const section = "install";
|
|
8
8
|
export const docs = "https://rust-lang.github.io/rfcs/3801-package-cooldown.html";
|
|
9
|
+
export const extras = [];
|
|
9
10
|
|
|
10
11
|
export function path(env, home) { return cargoConfigPath(env, home); }
|
|
11
12
|
|
|
@@ -21,7 +22,7 @@ export async function read(env, home) {
|
|
|
21
22
|
const p = path(env, home);
|
|
22
23
|
const raw = await readSafe(p);
|
|
23
24
|
const value = readKey(raw, key, { section });
|
|
24
|
-
return { path: p, configured: value, days: parseDays(value) };
|
|
25
|
+
return { path: p, configured: value, days: parseDays(value), extras: [] };
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export async function write(days, env, home) {
|
package/src/tools/mise.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
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
|
+
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
4
5
|
import { detectVersion, gte } from "../util/version.mjs";
|
|
5
6
|
export const name = "mise";
|
|
6
7
|
export const key = "minimum_release_age";
|
|
7
8
|
export const section = "settings";
|
|
8
9
|
export const docs = "https://mise.jdx.dev/configuration/settings.html#minimum_release_age";
|
|
9
10
|
export const minBin = [2026, 4, 22];
|
|
11
|
+
export const extras = [
|
|
12
|
+
{ key: "paranoid", expected: "true", line: "paranoid = true", section }
|
|
13
|
+
];
|
|
10
14
|
|
|
11
15
|
export function path(env, home, platform) { return miseConfigPath(env, home, platform); }
|
|
12
16
|
|
|
@@ -33,13 +37,17 @@ export async function read(env, home, platform) {
|
|
|
33
37
|
const p = path(env, home, platform);
|
|
34
38
|
const raw = await readSafe(p);
|
|
35
39
|
const value = readKey(raw, key, { section });
|
|
36
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
path: p, configured: value, days: parseDays(value),
|
|
42
|
+
extras: readExtras(raw, extras)
|
|
43
|
+
};
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
export async function write(days, env, home, platform) {
|
|
40
47
|
const p = path(env, home, platform);
|
|
41
48
|
const before = await readSafe(p);
|
|
42
|
-
|
|
49
|
+
let after = setKey(before, key, `${key} = "${days}d"`, { section });
|
|
50
|
+
after = applyExtras(after, extras);
|
|
43
51
|
await writeAtomic(p, after);
|
|
44
52
|
return { path: p, before, after };
|
|
45
53
|
}
|
|
@@ -47,7 +55,9 @@ export async function write(days, env, home, platform) {
|
|
|
47
55
|
export async function unset(env, home, platform) {
|
|
48
56
|
const p = path(env, home, platform);
|
|
49
57
|
const before = await readSafe(p);
|
|
50
|
-
const
|
|
51
|
-
|
|
58
|
+
const cooldown = removeKey(before, key, { section });
|
|
59
|
+
const ex = removeExtras(cooldown.text, extras);
|
|
60
|
+
const removed = cooldown.removed || ex.removed;
|
|
61
|
+
if (removed) await writeAtomic(p, ex.text);
|
|
52
62
|
return { path: p, removed };
|
|
53
63
|
}
|
package/src/tools/npm.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
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
|
+
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
4
5
|
import { detectVersion, gte } from "../util/version.mjs";
|
|
5
6
|
|
|
6
7
|
export const name = "npm";
|
|
7
8
|
export const key = "min-release-age";
|
|
8
9
|
export const docs = "https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age";
|
|
9
10
|
export const minBin = [11, 10, 0];
|
|
11
|
+
export const extras = [
|
|
12
|
+
{ key: "audit-level", expected: "high", line: "audit-level=high" }
|
|
13
|
+
];
|
|
10
14
|
|
|
11
15
|
export function path(env, home) { return npmrcPath(env, home); }
|
|
12
16
|
|
|
@@ -21,13 +25,18 @@ export async function read(env, home) {
|
|
|
21
25
|
const p = path(env, home);
|
|
22
26
|
const raw = await readSafe(p);
|
|
23
27
|
const value = readKey(raw, key);
|
|
24
|
-
return {
|
|
28
|
+
return {
|
|
29
|
+
path: p, configured: value,
|
|
30
|
+
days: value === null ? null : Number(value),
|
|
31
|
+
extras: readExtras(raw, extras)
|
|
32
|
+
};
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
export async function write(days, env, home) {
|
|
28
36
|
const p = path(env, home);
|
|
29
37
|
const before = await readSafe(p);
|
|
30
|
-
|
|
38
|
+
let after = setKey(before, key, `${key}=${days}`);
|
|
39
|
+
after = applyExtras(after, extras);
|
|
31
40
|
await writeAtomic(p, after);
|
|
32
41
|
return { path: p, before, after };
|
|
33
42
|
}
|
|
@@ -35,7 +44,9 @@ export async function write(days, env, home) {
|
|
|
35
44
|
export async function unset(env, home) {
|
|
36
45
|
const p = path(env, home);
|
|
37
46
|
const before = await readSafe(p);
|
|
38
|
-
const
|
|
39
|
-
|
|
47
|
+
const cooldown = removeKey(before, key);
|
|
48
|
+
const ex = removeExtras(cooldown.text, extras);
|
|
49
|
+
const removed = cooldown.removed || ex.removed;
|
|
50
|
+
if (removed) await writeAtomic(p, ex.text);
|
|
40
51
|
return { path: p, removed };
|
|
41
52
|
}
|
package/src/tools/pnpm.mjs
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
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
|
+
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
4
5
|
import { detectVersion, gte } from "../util/version.mjs";
|
|
5
6
|
|
|
6
7
|
export const name = "pnpm";
|
|
7
8
|
export const key = "minimum-release-age";
|
|
8
9
|
export const docs = "https://pnpm.io/settings#minimumreleaseage";
|
|
9
10
|
export const minBin = [10, 6, 0];
|
|
11
|
+
export const extras = [
|
|
12
|
+
{ key: "trust-policy", expected: "no-downgrade", line: "trust-policy=no-downgrade" },
|
|
13
|
+
{ key: "block-exotic-subdeps", expected: "true", line: "block-exotic-subdeps=true" }
|
|
14
|
+
];
|
|
10
15
|
|
|
11
16
|
export function path(env, home) { return npmrcPath(env, home); }
|
|
12
17
|
|
|
@@ -22,13 +27,18 @@ export async function read(env, home) {
|
|
|
22
27
|
const raw = await readSafe(p);
|
|
23
28
|
const value = readKey(raw, key);
|
|
24
29
|
const minutes = value === null ? null : Number(value);
|
|
25
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
path: p, configured: value,
|
|
32
|
+
days: minutes === null || Number.isNaN(minutes) ? null : Math.floor(minutes / (60 * 24)),
|
|
33
|
+
extras: readExtras(raw, extras)
|
|
34
|
+
};
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
export async function write(days, env, home) {
|
|
29
38
|
const p = path(env, home);
|
|
30
39
|
const before = await readSafe(p);
|
|
31
|
-
|
|
40
|
+
let after = setKey(before, key, `${key}=${days * 24 * 60}`);
|
|
41
|
+
after = applyExtras(after, extras);
|
|
32
42
|
await writeAtomic(p, after);
|
|
33
43
|
return { path: p, before, after };
|
|
34
44
|
}
|
|
@@ -36,7 +46,9 @@ export async function write(days, env, home) {
|
|
|
36
46
|
export async function unset(env, home) {
|
|
37
47
|
const p = path(env, home);
|
|
38
48
|
const before = await readSafe(p);
|
|
39
|
-
const
|
|
40
|
-
|
|
49
|
+
const cooldown = removeKey(before, key);
|
|
50
|
+
const ex = removeExtras(cooldown.text, extras);
|
|
51
|
+
const removed = cooldown.removed || ex.removed;
|
|
52
|
+
if (removed) await writeAtomic(p, ex.text);
|
|
41
53
|
return { path: p, removed };
|
|
42
54
|
}
|
package/src/tools/uv.mjs
CHANGED
|
@@ -7,6 +7,7 @@ export const name = "uv";
|
|
|
7
7
|
export const key = "exclude-newer";
|
|
8
8
|
export const docs = "https://docs.astral.sh/uv/reference/settings/#exclude-newer";
|
|
9
9
|
export const minBin = [0, 9, 17];
|
|
10
|
+
export const extras = [];
|
|
10
11
|
|
|
11
12
|
export function path(env, home, platform) { return uvConfigPath(env, home, platform); }
|
|
12
13
|
|
|
@@ -29,7 +30,7 @@ export async function read(env, home, platform) {
|
|
|
29
30
|
const p = path(env, home, platform);
|
|
30
31
|
const raw = await readSafe(p);
|
|
31
32
|
const value = readKey(raw, key);
|
|
32
|
-
return { path: p, configured: value, days: parseDays(value) };
|
|
33
|
+
return { path: p, configured: value, days: parseDays(value), extras: [] };
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export async function write(days, env, home, platform) {
|
package/src/tools/yarn.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
4
5
|
import { detectVersion, gte } from "../util/version.mjs";
|
|
5
6
|
|
|
6
7
|
export const name = "yarn";
|
|
@@ -8,6 +9,9 @@ export const key = "npmMinimalAgeGate";
|
|
|
8
9
|
export const docs = "https://yarnpkg.com/configuration/yarnrc#npmMinimalAgeGate";
|
|
9
10
|
export const minBin = [4, 10, 0];
|
|
10
11
|
const SEP = ":";
|
|
12
|
+
export const extras = [
|
|
13
|
+
{ key: "enableHardenedMode", expected: "true", line: "enableHardenedMode: true", sep: SEP }
|
|
14
|
+
];
|
|
11
15
|
|
|
12
16
|
export function path(env, home) { return yarnrcPath(env, home); }
|
|
13
17
|
|
|
@@ -30,13 +34,17 @@ export async function read(env, home) {
|
|
|
30
34
|
const p = path(env, home);
|
|
31
35
|
const raw = await readSafe(p);
|
|
32
36
|
const value = readKey(raw, key, { sep: SEP });
|
|
33
|
-
return {
|
|
37
|
+
return {
|
|
38
|
+
path: p, configured: value, days: parseDays(value),
|
|
39
|
+
extras: readExtras(raw, extras)
|
|
40
|
+
};
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export async function write(days, env, home) {
|
|
37
44
|
const p = path(env, home);
|
|
38
45
|
const before = await readSafe(p);
|
|
39
|
-
|
|
46
|
+
let after = setKey(before, key, `${key}: "${days}d"`, { sep: SEP });
|
|
47
|
+
after = applyExtras(after, extras);
|
|
40
48
|
await writeAtomic(p, after);
|
|
41
49
|
return { path: p, before, after };
|
|
42
50
|
}
|
|
@@ -44,7 +52,9 @@ export async function write(days, env, home) {
|
|
|
44
52
|
export async function unset(env, home) {
|
|
45
53
|
const p = path(env, home);
|
|
46
54
|
const before = await readSafe(p);
|
|
47
|
-
const
|
|
48
|
-
|
|
55
|
+
const cooldown = removeKey(before, key, { sep: SEP });
|
|
56
|
+
const ex = removeExtras(cooldown.text, extras);
|
|
57
|
+
const removed = cooldown.removed || ex.removed;
|
|
58
|
+
if (removed) await writeAtomic(p, ex.text);
|
|
49
59
|
return { path: p, removed };
|
|
50
60
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Per-tool "extras" are fixed-value hardening keys written alongside the main
|
|
2
|
+
// cooldown key on `set`, removed on `unset`, and validated on `check`.
|
|
3
|
+
// Each entry: { key, expected, line, sep?, section? } — `line` is the verbatim
|
|
4
|
+
// text to write (handles per-format quoting/typing); `expected` is the bare
|
|
5
|
+
// value compared against readKey output.
|
|
6
|
+
import { readKey, setKey, removeKey } from "./lines.mjs";
|
|
7
|
+
|
|
8
|
+
export function readExtras(raw, extras) {
|
|
9
|
+
return extras.map(e => {
|
|
10
|
+
const cur = readKey(raw, e.key, { sep: e.sep, section: e.section });
|
|
11
|
+
return { key: e.key, configured: cur, expected: e.expected, ok: cur === e.expected };
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function applyExtras(text, extras) {
|
|
16
|
+
let out = text;
|
|
17
|
+
for (const e of extras) {
|
|
18
|
+
out = setKey(out, e.key, e.line, { sep: e.sep, section: e.section });
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function removeExtras(text, extras) {
|
|
24
|
+
let out = text;
|
|
25
|
+
let removed = false;
|
|
26
|
+
for (const e of extras) {
|
|
27
|
+
const r = removeKey(out, e.key, { sep: e.sep, section: e.section });
|
|
28
|
+
if (r.removed) removed = true;
|
|
29
|
+
out = r.text;
|
|
30
|
+
}
|
|
31
|
+
return { text: out, removed };
|
|
32
|
+
}
|