@pho9ubenaa/siro 0.1.0-rc
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/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/cli.js +5 -0
- package/dist/cli.mjs +5 -0
- package/dist/index.d.mts +605 -0
- package/dist/index.mjs +2 -0
- package/dist/node-io-C_ERSDGJ.mjs +4 -0
- package/package.json +95 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0]
|
|
4
|
+
|
|
5
|
+
Initial public release. 27 rules across 6 package managers (npm, pnpm, yarn, bun, deno, aube).
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **27 security rules** covering lifecycle scripts, version pinning, lockfiles (`commit`/`frozen`), release age, publish provenance, `files`/`publishConfig`, SSL enforcement, checksum verification, exotic subdependency blocking, audit suppression review, store integrity, and Bun's security scanner API.
|
|
10
|
+
- **6 package managers**: npm, pnpm, yarn, bun, deno, aube — auto-detected from `packageManager` field, lockfiles, and config files.
|
|
11
|
+
- **PM-aware severities**: findings demoted to `info` when the PM's documented default already satisfies the rule.
|
|
12
|
+
- **Machine-readable JSON output** with `fix` operations and `manualSteps` for automated remediation.
|
|
13
|
+
- **Reporters**: `pretty` (default), `json` (CI), `github` (PR annotations); extensible via custom reporters.
|
|
14
|
+
- **Configuration file** (`siro.config.ts`): disable rules, override severities, restrict PMs, plug in custom rules and reporters.
|
|
15
|
+
- **`--severity` flag**: gate CI on `error` (default) or `warn` for stricter checks.
|
|
16
|
+
- **`--pm` flag**: target a specific package manager.
|
|
17
|
+
- **`--json` flag**: shortcut for `--reporter json`.
|
|
18
|
+
|
|
19
|
+
### Rules added
|
|
20
|
+
|
|
21
|
+
- `advisory-check` — aube `security.advisoryCheck`
|
|
22
|
+
- `approved-git-repos` — yarn `npmScopes.*.approvedGitRepos`
|
|
23
|
+
- `audit-suppression` — yarn `npmAuditExclude`
|
|
24
|
+
- `block-auto-install` — pnpm `autoInstallPeers`, aube `peerDependencies.autoInstall`
|
|
25
|
+
- `block-exotic-subdeps` — aube `blockExoticSubdeps`
|
|
26
|
+
- `bun-security-scanner` — bun `install.security`
|
|
27
|
+
- `checksum-verification` — pnpm `verifyStoreIntegrity`, aube `checksumVerification`
|
|
28
|
+
- `commit-lockfile` — npm `lockfileVersion` (lockfile committed to git)
|
|
29
|
+
- `dependency-overrides` — pnpm `pnpm.overrides`, aube `overrides`
|
|
30
|
+
- `disable-lifecycle-scripts` — npm `ignore-scripts`, pnpm `strictDepBuilds`, yarn `enableScripts`, bun `install.ignoreScripts`, deno `deno.json#/tasks`, aube `jailBuilds`
|
|
31
|
+
- `enforce-strict-ssl` — npm/yarn/pnpm/bun/aube `strict-ssl`
|
|
32
|
+
- `files-field` — npm `files` in package.json
|
|
33
|
+
- `frozen-lockfile` — npm `ci` enforcement, pnpm `frozen-lockfile`, yarn `freezeLockfile`, aube `preferFrozenLockfile`
|
|
34
|
+
- `frozen-store` — pnpm `store.frozen`
|
|
35
|
+
- `hardened-mode` — yarn `enableHardenedMode`
|
|
36
|
+
- `minimum-release-age` — pnpm `fetchRetries`, aube `minimumReleaseAge`
|
|
37
|
+
- `named-registries` — pnpm `registries.*.registry`
|
|
38
|
+
- `paranoid-mode` — aube `paranoid`
|
|
39
|
+
- `patched-dependencies` — pnpm `patchedDependencies`
|
|
40
|
+
- `pin-exact-versions` — npm `save-exact`, pnpm `save-exact`, deno `deno.json#/imports`
|
|
41
|
+
- `provenance` — npm/pnpm `publishConfig.provenance`
|
|
42
|
+
- `publish-access` — npm/pnpm `publishConfig.access`
|
|
43
|
+
- `publishable` — package.json `private`
|
|
44
|
+
- `store-server` — pnpm `store.server`
|
|
45
|
+
- `strict-allow-scripts` — aube `strictAllowScripts`
|
|
46
|
+
- `strict-release-age` — aube `strictReleaseAge`
|
|
47
|
+
- `strict-store-integrity` — pnpm `store.integrity`
|
|
48
|
+
- `trust-policy` — aube `trustPolicy`
|
|
49
|
+
|
|
50
|
+
### Tooling
|
|
51
|
+
|
|
52
|
+
- CI via GitHub Actions (Node 20, 22, 24 matrix)
|
|
53
|
+
- Pre-commit hooks: lint (oxlint), format (oxfmt), typecheck, related tests
|
|
54
|
+
- Pre-push hooks: full lint, typecheck, test coverage
|
|
55
|
+
- Commit message linting via commitlint (conventional commits)
|
|
56
|
+
- Dead code detection via knip
|
|
57
|
+
- Rule scaffold generator (`pnpm gen:rule`)
|
|
58
|
+
- Documentation generators (`pnpm gen:rules`, `pnpm gen:comparison`)
|
|
59
|
+
- Version module auto-generation from package.json
|
|
60
|
+
- Layering enforcement script
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-Present pHo9UBenaA
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# siro
|
|
2
|
+
|
|
3
|
+
[](https://github.com/pHo9UBenaA/siro/actions)
|
|
4
|
+
[](https://www.npmjs.com/package/@pho9ubenaa/siro)
|
|
5
|
+
[](https://github.com/pHo9UBenaA/siro/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
> Security best-practices linter for the npm ecosystem — npm, pnpm, yarn, bun, deno, aube.
|
|
8
|
+
|
|
9
|
+
[Getting started](docs/getting-started.md) ·
|
|
10
|
+
[Rules](docs/rules.md) ·
|
|
11
|
+
[Comparison](docs/comparison.md) ·
|
|
12
|
+
[Configuration](docs/configuration.md)
|
|
13
|
+
|
|
14
|
+
`siro` **lints** repos for supply-chain risks, graded `error` / `warn` / `info`,
|
|
15
|
+
and emits machine-readable remediation so an editor or agent skill
|
|
16
|
+
can apply the fixes.
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npx @pho9ubenaa/siro lint
|
|
20
|
+
npx @pho9ubenaa/siro lint --reporter json # machine-readable output with fix ops (see docs/json-output.md)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- **27 rules across 6 managers.** Covering lifecycle scripts, version pinning, lockfiles
|
|
26
|
+
(`commit`/`frozen`), release age, publish provenance, `files`/`publishConfig`, SSL enforcement,
|
|
27
|
+
checksum verification, exotic subdependency blocking, audit suppression review, store integrity,
|
|
28
|
+
Bun's security scanner API, and Yarn 4's hardened-mode — each mapped to the right setting per
|
|
29
|
+
package manager (`.npmrc`, `pnpm-workspace.yaml`, `.yarnrc.yml`, `bunfig.toml`, `deno.json`,
|
|
30
|
+
`aube-workspace.yaml`, `package.json`).
|
|
31
|
+
- **PM-aware severities.** When a manager's documented default already satisfies a rule (e.g.
|
|
32
|
+
Yarn's `enableScripts: false`, aube's `preferFrozenLockfile: true`), the finding is demoted
|
|
33
|
+
to `info` so CI noise stays proportional to real risk.
|
|
34
|
+
- **Machine-readable remediation.** Every finding carries `fix` operations and `manualSteps` in the
|
|
35
|
+
JSON output — hand it to an agent skill or editor plugin that edits the files and re-runs
|
|
36
|
+
`siro lint` until it exits `0`. See [JSON output](docs/json-output.md).
|
|
37
|
+
- **Lint with severities.** `error` fails CI by default; `--severity warn` tightens the gate.
|
|
38
|
+
- **Reporters.** `pretty` (default), `json` for CI, `github` for PR annotations; register your own.
|
|
39
|
+
- **Configurable.** Drop a `siro.config.ts` to disable rules, override severities, restrict PMs,
|
|
40
|
+
or plug in custom rules and reporters.
|
|
41
|
+
|
|
42
|
+
See the [rule reference](docs/rules.md) for what each check does and why, and the
|
|
43
|
+
[comparison matrix](docs/comparison.md) for per-manager support at a glance.
|
|
44
|
+
|
|
45
|
+
## Versioning policy
|
|
46
|
+
|
|
47
|
+
`siro` is **version-agnostic**: it never inspects the actual version of npm / pnpm / yarn / bun /
|
|
48
|
+
deno your project is running. Every rule is written against the **latest stable major** of each
|
|
49
|
+
manager — that's the configuration surface we lint and the defaults we trust.
|
|
50
|
+
|
|
51
|
+
When a rule's binding knows that a key was introduced or had its default tightened in a specific
|
|
52
|
+
version, the finding's message carries a `(available since pnpm 10.16.0; default safe since pnpm 11.0.0 (1440 minutes))`
|
|
53
|
+
suffix as documentation — but siro does not branch on it. If your project pins an older PM, the
|
|
54
|
+
right move is usually to upgrade; the lint output stays the same either way.
|
|
55
|
+
|
|
56
|
+
See [version matrix](docs/version-matrix.md) for the per-rule × per-PM version table
|
|
57
|
+
that backs those suffixes.
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
siro <lint|check> [path] [options]
|
|
63
|
+
|
|
64
|
+
--pm <npm|pnpm|yarn|bun|deno|aube> Target a specific package manager (auto-detected; required if detection finds nothing)
|
|
65
|
+
--reporter <pretty|json|github> Output format (default pretty)
|
|
66
|
+
--severity <error|warn|info> Show and fail on findings at or above this level
|
|
67
|
+
--json Shortcut for --reporter json
|
|
68
|
+
--version, --help
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`check` is an alias of `lint` (same flags, same exit codes) — provided so `siro check` reads naturally in CI scripts.
|
|
72
|
+
|
|
73
|
+
Exit codes: `0` clean · `1` findings at/above threshold · `2` usage error.
|
|
74
|
+
|
|
75
|
+
## How is this different from `npm audit` / `osv-scanner`?
|
|
76
|
+
|
|
77
|
+
Different layer of the supply-chain pipeline; you want both.
|
|
78
|
+
|
|
79
|
+
| Tool | What it checks | Where the data comes from |
|
|
80
|
+
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
81
|
+
| `npm audit` · osv-scanner · Snyk · Dependabot | **Known CVEs** in your installed dependency tree | GHSA / OSV.dev / vendor feeds |
|
|
82
|
+
| `siro` | **Your install pipeline's configuration** — postinstall scripts, version ranges, lockfile policy, publish provenance, files allow-list, etc. | Static analysis of `.npmrc`, `pnpm-workspace.yaml`, `.yarnrc.yml`, `bunfig.toml`, `deno.json`, `aube-workspace.yaml`, `package.json` |
|
|
83
|
+
|
|
84
|
+
`npm audit` tells you "this dependency has a known vulnerability". `siro` tells you "even if a brand-new vulnerability lands tomorrow, your install settings can't trust it without review". Run both in CI.
|
|
85
|
+
|
|
86
|
+
## Contributing
|
|
87
|
+
|
|
88
|
+
Adding a rule or a package manager is a localized change — see
|
|
89
|
+
[contributing guide](docs/contributing.md).
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
package/dist/cli.js
ADDED
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{C as e,E as t,O as n,S as r,k as i,m as a,n as o,p as s,t as c,v as l,w as u,x as d}from"./node-io-C_ERSDGJ.mjs";import ee from"node:path";import{cac as f}from"cac";import{pathToFileURL as p}from"node:url";const m=[`lint`,`check`],h=e=>m.some(t=>t===e),g=new Set(Object.entries({json:{takesValue:!1},pm:{takesValue:!0},reporter:{takesValue:!0},severity:{takesValue:!0}}).filter(([,e])=>e.takesValue).map(([e])=>e)),_=[`pm`,`reporter`,`json`,`severity`],te={check:_,lint:_},ne=[`version`,`help`],v=[`v`,`h`,`--`],y=e=>new Set([...te[e],...ne,...v]),b=(()=>{let e=[];for(let t of m)for(let n of y(t))e.push(n);return new Set(e)})(),x=(e,t)=>e.startsWith(`--`)&&!e.includes(`=`)&&g.has(e.slice(2))&&t!==void 0&&!t.startsWith(`-`),S=e=>e===`--help`||e===`-h`,C=(e,t,n)=>e===`--`?`break`:(S(e)?n.asksHelp=!0:x(e,t)?n.index+=1:!e.startsWith(`-`)&&n.first===``&&(n.first=e),`continue`),w=e=>{let t={asksHelp:!1,first:``,index:0};for(;t.index<e.length;t.index+=1){let n=e[t.index];if(typeof n==`string`&&C(n,e[t.index+1],t)===`break`)break}return{asksHelp:t.asksHelp,first:t.first||[].pop()}},T=e=>{if(e!==void 0&&h(e))return e},E=e=>{let{asksHelp:t,first:n}=w(e);return t?{target:T(n)}:[].pop()},D=e=>{for(let t=0;t<e.length;t+=1){let n=e[t];if(typeof n==`string`){if(n===`--`)return!1;if(n===`--version`||n===`-v`)return!0;x(n,e[t+1])&&(t+=1)}}return!1},O=(e,n,r)=>{for(let i of Object.keys(e))if(!n.has(i)){let e=``;throw r&&(e=` for '${r}'`),new t(`Unknown flag${e}: --${i}`)}},k=n=>{if(n!==void 0){if(n===!0)throw new t(`--pm requires a value (expected one of: ${d.join(`, `)})`);if(typeof n!=`string`||!e(n))throw new t(`Unknown package manager: ${n} (expected one of: ${d.join(`, `)})`);return n}},A=e=>{if(e!==void 0){if(e===!0)throw new t(`--severity requires a value (expected ${r.join(`|`)})`);if(typeof e!=`string`||!u(e))throw new t(`Invalid severity: ${String(e)} (expected ${r.join(`|`)})`);return e}},j=e=>{if(e.reporter===!0)throw new t(`--reporter requires a value (expected one of: ${a.join(`, `)}, or a reporter registered via siro.config.ts)`);if(typeof e.reporter==`string`){if(e.json===!0)throw new t(`Use either --reporter or --json, not both.`);return e.reporter}return e.json===!0?`json`:`pretty`},M=e=>{let n=Number(e.replace(/^v/u,``).split(`.`)[0]);if(Number.isFinite(n)&&n<20)throw new t(`Node 20+ required (you are on ${e}). Upgrade your runtime to use siro.`)},N=()=>{let e=f(`siro`);e.option(`--pm <name>`,`Target a specific package manager`).option(`--reporter <name>`,`Reporter (${a.join(`|`)})`).option(`--json`,`Shortcut for --reporter json`).option(`--severity <level>`,`Display/fail threshold (${r.join(`|`)})`).help().version(l);for(let t of m){let n=`${t} a repository`;t===`check`&&(n=`Alias of lint`),e.command(`${t} [cwd]`,n)}return e},P=(e,t)=>e?t[0]:t[1],F=(e,t)=>e||t[0],I=(e,t)=>e?t.slice(1):t.slice(2),L=e=>{let t=N(),n=t.parse([`node`,`siro`,...e],{run:!1}),r=n.options,i=t.matchedCommandName,a=P(i,n.args);return{commandCandidate:F(i,n.args),extraPositionals:I(i,n.args),flags:r,matchedCommand:i,positionalCwd:a}},re=d.join(`|`),R=a.join(`|`),z=r.join(`|`),B={json:` --json Shortcut for --reporter json`,pm:` --pm <name> Target a specific package manager (${re})`,reporter:` --reporter <name> Reporter (${R}; additional reporters can be registered via siro.config.ts)`,severity:` --severity <level> Show + fail on findings at or above this level (${z})`},V=[`siro — security best-practices for the npm ecosystem`,``,`USAGE`,` siro <command> [path] [flags]`,``,`COMMANDS`,` lint Report best-practice violations (alias: check)`,``,`GLOBAL FLAGS`,B.pm,` --version Print the siro version`,` --help Show help for siro or a command`,``,`LINT FLAGS`,B.reporter,B.json,B.severity,``,`EXAMPLES`,` $ siro lint # report violations in cwd`,` $ siro lint --reporter github # GitHub Actions annotations`,` $ siro lint --severity warn # also fail on warnings`,``,`LEARN MORE`,` Quickstart https://github.com/pHo9UBenaA/siro/blob/main/docs/getting-started.md`,` Rule reference https://github.com/pHo9UBenaA/siro/blob/main/docs/rules.md`,` Config options https://github.com/pHo9UBenaA/siro/blob/main/docs/configuration.md`].join(`
|
|
3
|
+
`),H=[`siro lint — report best-practice violations`,``,`USAGE`,` siro lint [path] [flags]`,` siro check [path] [flags] (alias)`,``,`FLAGS`,B.pm,B.reporter,B.json,B.severity,``,`EXIT CODES`,` 0 No findings at or above the active threshold`,` 1 Findings at or above the threshold (default: error)`,` 2 Usage error (bad flag, broken siro.config.ts, unreadable path, …)`,` 70 Uncaught exception (a siro bug, or a reporter / custom rule that threw)`,``,`EXAMPLES`,` $ siro lint # default: pretty reporter, fail on errors`,` $ siro lint --reporter github # GitHub Actions annotations`,` $ siro lint --severity warn # tighten the gate`].join(`
|
|
4
|
+
`),U=e=>e===void 0?V:e===`lint`||e===`check`?H:e,W=e=>{let n=e[`--`];if(Array.isArray(n)&&n.length>0)throw new t("siro takes no passthrough arguments after `--`.")},G=e=>e===void 0?{kind:`usage`}:e===`init`?{kind:`usage`,reason:"The 'init' command was removed: siro is lint-only. Run `siro lint --reporter json` and apply each finding's `fix` operations with your editor or an agent skill."}:typeof e!=`string`||!h(e)?{kind:`usage`,reason:`Unknown command: ${String(e)}`}:{kind:`command`,name:e},K=e=>{if(e.length>0){let n=``;throw e.length>1&&(n=`s`),new t(`Unexpected extra argument${n}: ${e.join(` `)}`)}},q=e=>{let t=process.cwd();return typeof e==`string`&&(t=e),i(ee.resolve(t))},J=e=>{let t=E(e);if(t!==void 0)return{kind:`help`,target:t.target};if(D(e))return{kind:`version`}},Y=(e,t,n)=>(O(e,y(n),n),{cwd:q(t),kind:`lint`,pm:k(e.pm),reporter:j(e),severity:A(e.severity)}),X=e=>{O(e,b),W(e)},ie=e=>{let t=J(e);if(t!==void 0)return t;let{commandCandidate:n,extraPositionals:r,flags:i,positionalCwd:a}=L(e);X(i);let o=G(n);return o.kind===`usage`?o:(K(r),Y(i,a,o.name))},ae=e=>(e.stdout(l),Promise.resolve(0)),oe=(e,t)=>(t.stdout(U(e)),Promise.resolve(0)),se=(e,t)=>(e!==void 0&&t.stderr(`${e}\n`),t.stderr(U()),Promise.resolve(2)),ce=(e,t)=>{switch(e.kind){case`version`:return ae(t);case`help`:return oe(e.target,t);case`usage`:return se(e.reason,t);case`lint`:return o(e,t);default:throw Error(`Unhandled command kind: ${String(e)}`)}},Z=(e,t)=>{if(e instanceof n)return t.stderr(e.message),e.exitCode;if(s(e)&&typeof e.errno==`number`)return t.stderr(`File system error: ${e.message}`),2;throw e},le=(e,t)=>e instanceof n?(t.stderr(e.message),Promise.resolve(e.exitCode)):s(e)&&typeof e.errno==`number`?(t.stderr(`File system error: ${e.message}`),Promise.resolve(2)):Promise.reject(e),Q=(e,t=c)=>{try{return M(process.versions.node),ce(ie(e),t).catch(e=>Z(e,t))}catch(e){return le(e,t)}},ue=e=>{process.exitCode=e},de=e=>{let t=String(e);e instanceof Error&&(t=e.stack??e.message),process.stderr.write(`${t}\n`),process.exitCode=70},[,$]=process.argv;if($&&import.meta.url===p($).href)try{ue(await Q(process.argv.slice(2)))}catch(e){de(e)}export{Q as run};
|
|
5
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
import * as vb from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/shared/paths.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Branded path types. Surface the distinction between
|
|
6
|
+
* - `AbsPath`: absolute filesystem paths handed to the FS boundary
|
|
7
|
+
* (`FileSystem.readText` etc.), and
|
|
8
|
+
* - `RelPath`: paths relative to a repo root, accepted by
|
|
9
|
+
* `RepoContext` and resolved via `resolveIn`.
|
|
10
|
+
*
|
|
11
|
+
* The brand is a structural tag with no runtime footprint — the cast in
|
|
12
|
+
* `asAbsPath` / `asRelPath` is the only place either type is minted, so
|
|
13
|
+
* passing a raw `string` to an FS or repo API now requires the caller to
|
|
14
|
+
* declare intent rather than silently mixing the two flavours.
|
|
15
|
+
*/
|
|
16
|
+
declare const AbsPathBrand: unique symbol;
|
|
17
|
+
declare const RelPathBrand: unique symbol;
|
|
18
|
+
type AbsPath = string & {
|
|
19
|
+
readonly [AbsPathBrand]: true;
|
|
20
|
+
};
|
|
21
|
+
type RelPath = string & {
|
|
22
|
+
readonly [RelPathBrand]: true;
|
|
23
|
+
};
|
|
24
|
+
/** Tag a string as absolute. The caller vouches for the shape. */
|
|
25
|
+
declare const asAbsPath: (path: string) => AbsPath;
|
|
26
|
+
/** Tag a string as repo-root-relative. The caller vouches for the shape. */
|
|
27
|
+
declare const asRelPath: (path: string) => RelPath;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/domain/ports/file-system.d.ts
|
|
30
|
+
/** IO boundary: swap in memfs (or any other backend) for tests. */
|
|
31
|
+
interface FileSystem {
|
|
32
|
+
readText: (path: AbsPath) => string | undefined;
|
|
33
|
+
exists: (path: AbsPath) => boolean;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/adapters/node-file-system.d.ts
|
|
37
|
+
declare const nodeFileSystem: FileSystem;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/domain/ports/io.d.ts
|
|
40
|
+
/** Output sink for commands; injectable so tests can drive a string buffer. */
|
|
41
|
+
interface IO {
|
|
42
|
+
stdout: (line: string) => void;
|
|
43
|
+
stderr: (line: string) => void;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/adapters/node-io.d.ts
|
|
47
|
+
declare const nodeIO: IO;
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/domain/entities/config-value.d.ts
|
|
50
|
+
type CodecKind = 'npmrc' | 'yaml' | 'toml' | 'json';
|
|
51
|
+
/** Scalar value that can be written back to a config file. */
|
|
52
|
+
type ConfigValue = string | number | boolean;
|
|
53
|
+
/** Scalar value as it appears when reading (codecs may produce `null`). */
|
|
54
|
+
type ConfigScalar = ConfigValue | null;
|
|
55
|
+
/** Recursive, structurally typed view of a parsed config file. */
|
|
56
|
+
interface ParsedConfig {
|
|
57
|
+
readonly [key: string]: ConfigScalar | readonly ConfigScalar[] | ParsedConfig;
|
|
58
|
+
}
|
|
59
|
+
/** A (possibly nested) key path, guaranteed to have at least one segment. */
|
|
60
|
+
type KeyPath = readonly [string, ...string[]];
|
|
61
|
+
/** A single key to set. */
|
|
62
|
+
interface KeyAssignment {
|
|
63
|
+
readonly keyPath: KeyPath;
|
|
64
|
+
readonly value: ConfigValue;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Value a rule's `check` saw at the target key, as returned by `getByPath`.
|
|
68
|
+
* Runtime caveat: YAML/TOML parsers can yield host values (e.g. `Date`)
|
|
69
|
+
* nested inside `ParsedConfig` even though the type does not name them —
|
|
70
|
+
* compare timestamps by `valueOf()`, not by type narrowing on this union.
|
|
71
|
+
*/
|
|
72
|
+
type ConfigReadValue = ConfigScalar | readonly ConfigScalar[] | ParsedConfig | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Look up a nested value by key path; `undefined` if any segment is missing.
|
|
75
|
+
*
|
|
76
|
+
* Part of the public surface (re-exported from `src/index.ts`): embedders
|
|
77
|
+
* writing custom rules receive the same `config: ParsedConfig` shape that
|
|
78
|
+
* built-in rules see, so they need the canonical traversal helper rather
|
|
79
|
+
* than re-implementing `null`-vs-missing semantics per rule.
|
|
80
|
+
*/
|
|
81
|
+
declare const getByPath: (config: ParsedConfig, keyPath: KeyPath) => ConfigScalar | readonly ConfigScalar[] | ParsedConfig | undefined;
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/domain/entities/pms.d.ts
|
|
84
|
+
/**
|
|
85
|
+
* Single source of truth for package managers and severities.
|
|
86
|
+
* Types, runtime sets, and ordering are all derived from these tuples — add a
|
|
87
|
+
* package manager here and the rest of the codebase follows.
|
|
88
|
+
*/
|
|
89
|
+
declare const PMS: readonly ["npm", "pnpm", "yarn", "bun", "deno", "aube"];
|
|
90
|
+
type PM = (typeof PMS)[number];
|
|
91
|
+
declare const SEVERITIES: readonly ["error", "warn", "info"];
|
|
92
|
+
type Severity = (typeof SEVERITIES)[number];
|
|
93
|
+
declare const isPM: (value: string) => value is PM;
|
|
94
|
+
declare const isSeverity: (value: string) => value is Severity;
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/domain/schemas/package-json.d.ts
|
|
97
|
+
declare const PackageJsonSchema: vb.LooseObjectSchema<{
|
|
98
|
+
readonly files: vb.SchemaWithFallback<vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>, undefined>;
|
|
99
|
+
readonly name: vb.SchemaWithFallback<vb.OptionalSchema<vb.StringSchema<undefined>, undefined>, undefined>;
|
|
100
|
+
readonly packageManager: vb.SchemaWithFallback<vb.OptionalSchema<vb.StringSchema<undefined>, undefined>, undefined>;
|
|
101
|
+
readonly private: vb.OptionalSchema<vb.SchemaWithFallback<vb.BooleanSchema<undefined>, true>, undefined>;
|
|
102
|
+
readonly publishConfig: vb.SchemaWithFallback<vb.OptionalSchema<vb.LooseObjectSchema<{
|
|
103
|
+
readonly access: vb.SchemaWithFallback<vb.OptionalSchema<vb.UnionSchema<[vb.LiteralSchema<"public", undefined>, vb.LiteralSchema<"restricted", undefined>], undefined>, undefined>, undefined>;
|
|
104
|
+
}, undefined>, undefined>, undefined>;
|
|
105
|
+
readonly trustedDependencies: vb.SchemaWithFallback<vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>, undefined>;
|
|
106
|
+
}, undefined>;
|
|
107
|
+
type PackageJson = vb.InferOutput<typeof PackageJsonSchema>;
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/domain/ports/repo-context.d.ts
|
|
110
|
+
/** Read-only view of a repository, passed to every rule's `check` and `fix`. */
|
|
111
|
+
interface RepoContext {
|
|
112
|
+
readonly root: AbsPath;
|
|
113
|
+
exists: (relPath: RelPath) => boolean;
|
|
114
|
+
readText: (relPath: RelPath) => string | undefined;
|
|
115
|
+
readonly packageJson: PackageJson | undefined;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/domain/entities/rule.d.ts
|
|
119
|
+
/**
|
|
120
|
+
* What a rule binding points at on disk: a parsed config file or an existence
|
|
121
|
+
* check (e.g. "a lockfile is committed"). `path` is a branded `RelPath` minted
|
|
122
|
+
* once at the ref's definition (CONFIG_FILES), so the readText boundary is
|
|
123
|
+
* type-checked and a stray absolute path can't slip in as a string.
|
|
124
|
+
*/
|
|
125
|
+
type ConfigFileRef = {
|
|
126
|
+
readonly kind: CodecKind;
|
|
127
|
+
readonly path: RelPath;
|
|
128
|
+
} | {
|
|
129
|
+
readonly kind: 'fileGlob';
|
|
130
|
+
readonly path: RelPath;
|
|
131
|
+
};
|
|
132
|
+
/** Result of evaluating a single rule binding against a repo. */
|
|
133
|
+
type CheckStatus = {
|
|
134
|
+
readonly state: 'ok';
|
|
135
|
+
} | {
|
|
136
|
+
readonly state: 'violation';
|
|
137
|
+
readonly message: string;
|
|
138
|
+
readonly expected?: ConfigValue;
|
|
139
|
+
readonly actual?: ConfigReadValue;
|
|
140
|
+
/**
|
|
141
|
+
* Dynamic severity for this specific violation, overriding both
|
|
142
|
+
* {@link AutoRuleBinding.severity} and {@link Rule.severity}. Lets a
|
|
143
|
+
* `check` distinguish "unset but PM-default safe" (advisory) from
|
|
144
|
+
* "explicitly weakened" (full severity). A user `rules` override in
|
|
145
|
+
* config still wins (see `applyConfig`).
|
|
146
|
+
*/
|
|
147
|
+
readonly severity?: Severity;
|
|
148
|
+
/**
|
|
149
|
+
* Manual remediation steps surfaced verbatim on the finding. Lets a
|
|
150
|
+
* `check` signal "the setKey fix ops cannot resolve this state; the
|
|
151
|
+
* user has to intervene" — e.g. when an explicit user override would
|
|
152
|
+
* have to be deleted first. When non-empty, `Finding.fix` is emitted
|
|
153
|
+
* empty so an external fixer never writes ops the user's existing
|
|
154
|
+
* config would defeat.
|
|
155
|
+
*/
|
|
156
|
+
readonly manualSteps?: readonly string[];
|
|
157
|
+
} | {
|
|
158
|
+
readonly state: 'na';
|
|
159
|
+
};
|
|
160
|
+
/** A remediation step produced by a rule's `fix`. */
|
|
161
|
+
type FixOp = {
|
|
162
|
+
readonly op: 'setKey';
|
|
163
|
+
readonly file: ConfigFileRef;
|
|
164
|
+
readonly keyPath: KeyPath;
|
|
165
|
+
readonly value: ConfigValue;
|
|
166
|
+
} | {
|
|
167
|
+
readonly op: 'ensureFileTracked';
|
|
168
|
+
readonly file: ConfigFileRef;
|
|
169
|
+
readonly message: string;
|
|
170
|
+
} | {
|
|
171
|
+
readonly op: 'note';
|
|
172
|
+
readonly message: string;
|
|
173
|
+
readonly file?: ConfigFileRef;
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* - `auto`: `fix` produces setKey ops an external fixer can apply
|
|
177
|
+
* mechanically (surfaced via `Finding.fix`; see docs/json-output.md).
|
|
178
|
+
* - `advisory`: `fix` only produces notes/ensureFileTracked hints.
|
|
179
|
+
*/
|
|
180
|
+
type FixKind = 'auto' | 'advisory';
|
|
181
|
+
type SetKeyOp = Extract<FixOp, {
|
|
182
|
+
op: 'setKey';
|
|
183
|
+
}>;
|
|
184
|
+
type AdvisoryOp = Extract<FixOp, {
|
|
185
|
+
op: 'note' | 'ensureFileTracked';
|
|
186
|
+
}>;
|
|
187
|
+
interface AutoRuleBinding {
|
|
188
|
+
readonly file: ConfigFileRef;
|
|
189
|
+
readonly fixKind: 'auto';
|
|
190
|
+
/** Official package-manager doc URL for the setting this binding writes. */
|
|
191
|
+
readonly docs?: string;
|
|
192
|
+
/**
|
|
193
|
+
* Per-binding severity. When set, it shadows {@link Rule.severity} for this
|
|
194
|
+
* PM only — used when a package manager's safe default already mitigates
|
|
195
|
+
* the threat (so the binding is informational) while another PM with the
|
|
196
|
+
* same rule still warrants the rule-wide level. A user config `rules`
|
|
197
|
+
* override always wins over both.
|
|
198
|
+
*/
|
|
199
|
+
readonly severity?: Severity;
|
|
200
|
+
check: (ctx: RepoContext, config: ParsedConfig) => CheckStatus;
|
|
201
|
+
fix: (ctx: RepoContext) => readonly SetKeyOp[];
|
|
202
|
+
}
|
|
203
|
+
interface AdvisoryRuleBinding {
|
|
204
|
+
readonly file: ConfigFileRef;
|
|
205
|
+
readonly fixKind: 'advisory';
|
|
206
|
+
/** Official package-manager doc URL for the setting this binding describes. */
|
|
207
|
+
readonly docs?: string;
|
|
208
|
+
/** See {@link AutoRuleBinding.severity}. */
|
|
209
|
+
readonly severity?: Severity;
|
|
210
|
+
check: (ctx: RepoContext, config: ParsedConfig) => CheckStatus;
|
|
211
|
+
fix: (ctx: RepoContext) => readonly AdvisoryOp[];
|
|
212
|
+
}
|
|
213
|
+
type RuleBinding = AutoRuleBinding | AdvisoryRuleBinding;
|
|
214
|
+
/** A package-manager-agnostic security intent, realized per PM via `bindings`. */
|
|
215
|
+
interface Rule {
|
|
216
|
+
readonly id: string;
|
|
217
|
+
readonly title: string;
|
|
218
|
+
readonly description: string;
|
|
219
|
+
readonly severity: Severity;
|
|
220
|
+
readonly docs?: string;
|
|
221
|
+
/** Absence of a PM key means the rule does not apply (N/A) to that PM. */
|
|
222
|
+
readonly bindings: Partial<Record<PM, RuleBinding>>;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/domain/entities/lint-result.d.ts
|
|
226
|
+
interface Finding {
|
|
227
|
+
readonly ruleId: string;
|
|
228
|
+
readonly pm: PM;
|
|
229
|
+
readonly severity: Severity;
|
|
230
|
+
readonly message: string;
|
|
231
|
+
readonly file?: string;
|
|
232
|
+
readonly fixable: boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Machine-readable remediation: the binding's fix ops, verbatim. Empty
|
|
235
|
+
* when `manualSteps` is present (writing the ops would be defeated by the
|
|
236
|
+
* user state that produced the steps). Advisory bindings contribute their
|
|
237
|
+
* `note` / `ensureFileTracked` ops here so external fixers can surface
|
|
238
|
+
* them. Part of the json output contract — see docs/json-output.md.
|
|
239
|
+
*/
|
|
240
|
+
readonly fix: readonly FixOp[];
|
|
241
|
+
/** Manual remediation steps, verbatim from the check (see CheckStatus). */
|
|
242
|
+
readonly manualSteps?: readonly string[];
|
|
243
|
+
/**
|
|
244
|
+
* Pinned to {@link ConfigValue} (no `null`) so it stays in lockstep with
|
|
245
|
+
* `CheckStatus.violation.expected`. Widening to ConfigScalar would have
|
|
246
|
+
* advertised a `null` outcome that no current rule can produce.
|
|
247
|
+
*/
|
|
248
|
+
readonly expected?: ConfigValue;
|
|
249
|
+
readonly actual?: ConfigReadValue;
|
|
250
|
+
/** Official PM doc anchor for the setting this finding is about, if any. */
|
|
251
|
+
readonly docs?: string;
|
|
252
|
+
}
|
|
253
|
+
interface LintResult {
|
|
254
|
+
readonly findings: readonly Finding[];
|
|
255
|
+
readonly summary: Readonly<Record<Severity, number>>;
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/domain/ports/reporter.d.ts
|
|
259
|
+
/**
|
|
260
|
+
* Renderer from `LintResult` to user-facing output.
|
|
261
|
+
*
|
|
262
|
+
* Reporters never touch the filesystem and never spawn processes — every
|
|
263
|
+
* output byte flows through the injected `IO` port. They MAY however
|
|
264
|
+
* consult `process.env` for environmental shape (TTY colour support,
|
|
265
|
+
* NO_COLOR / FORCE_COLOR, CI detection) because that information has no
|
|
266
|
+
* `IO`-port equivalent and treating it as I/O would force the application
|
|
267
|
+
* layer to thread a snapshot through every call site for a value the
|
|
268
|
+
* adapter can read in a single line.
|
|
269
|
+
*
|
|
270
|
+
* Implementations live in the adapter layer (src/adapters/reporters/),
|
|
271
|
+
* so the env access stays out of the domain.
|
|
272
|
+
*/
|
|
273
|
+
interface Reporter<Name extends string = string> {
|
|
274
|
+
readonly name: Name;
|
|
275
|
+
format: (result: LintResult, io: IO) => void;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Structural guard for an embedder-supplied reporter. The `Reporter` contract
|
|
279
|
+
* is compile-time only (`defineConfig` / typed options), so a hand-written JS
|
|
280
|
+
* config or a dynamically-built object can still pass a malformed value; this
|
|
281
|
+
* lets each boundary reject it with its own error type before `format` is
|
|
282
|
+
* called on something that isn't a function.
|
|
283
|
+
*/
|
|
284
|
+
declare const isReporterShape: (value: unknown) => value is Reporter;
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/adapters/reporters/github.d.ts
|
|
287
|
+
/** Emit GitHub Actions workflow commands (annotations on PRs). */
|
|
288
|
+
declare const githubReporter: Reporter<'github'>;
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/adapters/reporters/json.d.ts
|
|
291
|
+
declare const jsonReporter: Reporter<'json'>;
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/adapters/reporters/pretty.d.ts
|
|
294
|
+
declare const prettyReporter: Reporter<'pretty'>;
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/adapters/reporters/registry.d.ts
|
|
297
|
+
declare const BUILTINS: readonly [Reporter<"pretty">, Reporter<"json">, Reporter<"github">];
|
|
298
|
+
/** Names of every built-in reporter, in stable display order. */
|
|
299
|
+
declare const BUILTIN_REPORTER_NAMES: readonly BuiltinReporterName[];
|
|
300
|
+
/** Literal union of every built-in reporter name. */
|
|
301
|
+
type BuiltinReporterName = (typeof BUILTINS)[number]['name'];
|
|
302
|
+
/** Reporter lookup table — pass an explicit value to keep calls hermetic. */
|
|
303
|
+
interface ReporterRegistry {
|
|
304
|
+
get: (name: string) => Reporter | undefined;
|
|
305
|
+
list: () => readonly string[];
|
|
306
|
+
}
|
|
307
|
+
/** Build a registry from the builtins plus any extras (later wins on collision). */
|
|
308
|
+
declare const createRegistry: (extras?: readonly Reporter[]) => ReporterRegistry;
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/application/commands/lint.d.ts
|
|
311
|
+
interface LintOptions {
|
|
312
|
+
/**
|
|
313
|
+
* Absolute repo root. Callers are responsible for resolving any relative
|
|
314
|
+
* path before branding it; siro's CLI does this in `cli.ts`. Keeping the
|
|
315
|
+
* brand here lets the application layer stay free of `node:path`.
|
|
316
|
+
*/
|
|
317
|
+
cwd: AbsPath;
|
|
318
|
+
/** Restrict to a single PM; otherwise PMs are auto-detected. */
|
|
319
|
+
pm?: PM;
|
|
320
|
+
/** Reporter name or instance; defaults to `pretty`. */
|
|
321
|
+
reporter?: string | Reporter;
|
|
322
|
+
/** Extra reporters to make available by name (merged with the user config). */
|
|
323
|
+
reporters?: readonly Reporter[];
|
|
324
|
+
/**
|
|
325
|
+
* Programmatic custom rules to evaluate alongside the builtins and any
|
|
326
|
+
* `customRules` from the user config. Mirrors `SiroConfig.customRules`
|
|
327
|
+
* but is supplied at the call site, so embedders can compose rulesets
|
|
328
|
+
* without writing a config file. Ids must be unique across builtins,
|
|
329
|
+
* config-supplied custom rules, and this list — collisions throw a
|
|
330
|
+
* `ConfigError` (exit 2), matching how `loadConfig` rejects them.
|
|
331
|
+
*/
|
|
332
|
+
customRules?: readonly Rule[];
|
|
333
|
+
/** Show (and fail on) findings at or above this severity. */
|
|
334
|
+
severity?: Severity;
|
|
335
|
+
/**
|
|
336
|
+
* Inject a non-default FS (e.g. memfs in tests). Caveat: `siro.config.{ts,mjs,js}`
|
|
337
|
+
* is loaded by jiti from the REAL disk — only the config's existence is probed
|
|
338
|
+
* through this FS. A config that lives solely in an injected FS won't load.
|
|
339
|
+
*/
|
|
340
|
+
fs?: FileSystem;
|
|
341
|
+
}
|
|
342
|
+
/** `siro lint`: detect PMs, evaluate rules, report findings. */
|
|
343
|
+
declare const lintCommand: (options: LintOptions, io: IO) => Promise<number>;
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/domain/entities/config-files.d.ts
|
|
346
|
+
/**
|
|
347
|
+
* Canonical {@link ConfigFileRef}s for every package-manager config file siro
|
|
348
|
+
* knows how to read. Centralizing them keeps each rule module from hardcoding
|
|
349
|
+
* `kind` / `path` pairs, so renaming a file (or fixing a typo in its path) is a
|
|
350
|
+
* single-line change. This is also the one place `RelPath` is minted for a
|
|
351
|
+
* config ref — every downstream binding inherits the brand, so no rule needs
|
|
352
|
+
* an ad-hoc `asRelPath(ref.path)` cast at the FS boundary.
|
|
353
|
+
*
|
|
354
|
+
* Add a new entry here when introducing support for a new package manager.
|
|
355
|
+
*/
|
|
356
|
+
declare const CONFIG_FILES: {
|
|
357
|
+
readonly aubeWorkspace: {
|
|
358
|
+
readonly kind: "yaml";
|
|
359
|
+
readonly path: RelPath;
|
|
360
|
+
};
|
|
361
|
+
readonly bunfig: {
|
|
362
|
+
readonly kind: "toml";
|
|
363
|
+
readonly path: RelPath;
|
|
364
|
+
};
|
|
365
|
+
readonly denoJson: {
|
|
366
|
+
readonly kind: "json";
|
|
367
|
+
readonly path: RelPath;
|
|
368
|
+
};
|
|
369
|
+
readonly npmrc: {
|
|
370
|
+
readonly kind: "npmrc";
|
|
371
|
+
readonly path: RelPath;
|
|
372
|
+
};
|
|
373
|
+
readonly packageJson: {
|
|
374
|
+
readonly kind: "json";
|
|
375
|
+
readonly path: RelPath;
|
|
376
|
+
};
|
|
377
|
+
readonly pnpmWorkspace: {
|
|
378
|
+
readonly kind: "yaml";
|
|
379
|
+
readonly path: RelPath;
|
|
380
|
+
};
|
|
381
|
+
readonly yarnrc: {
|
|
382
|
+
readonly kind: "yaml";
|
|
383
|
+
readonly path: RelPath;
|
|
384
|
+
};
|
|
385
|
+
};
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/domain/entities/signals.d.ts
|
|
388
|
+
/**
|
|
389
|
+
* Filenames that identify a package manager. `lockfiles[0]` is the
|
|
390
|
+
* canonical/preferred one (used by lint messages and error
|
|
391
|
+
* messages); the rest are legacy/alternative forms the PM writes itself.
|
|
392
|
+
*
|
|
393
|
+
* `lockfiles` and `configs` are **detection evidence** — their presence means
|
|
394
|
+
* the PM is in use. `reusesLockfiles` is NOT evidence: it lists other PMs'
|
|
395
|
+
* lockfile shapes that this PM will reuse rather than writing its own (aube),
|
|
396
|
+
* so `commit-lockfile` accepts them but `detectPMs` ignores them — otherwise a
|
|
397
|
+
* plain pnpm/npm repo would false-positive as aube. Keeping the two lists
|
|
398
|
+
* separate is what lets detection stay a simple "any signal present?" check.
|
|
399
|
+
*/
|
|
400
|
+
interface PMSignals {
|
|
401
|
+
readonly lockfiles: readonly [string, ...string[]];
|
|
402
|
+
readonly configs: readonly string[];
|
|
403
|
+
/** Other PMs' lockfiles this PM reuses; satisfies commit-lockfile, not detection. */
|
|
404
|
+
readonly reusesLockfiles?: readonly string[];
|
|
405
|
+
}
|
|
406
|
+
declare const PM_SIGNALS: {
|
|
407
|
+
readonly aube: {
|
|
408
|
+
readonly configs: readonly [RelPath];
|
|
409
|
+
readonly lockfiles: readonly ["aube-lock.yaml"];
|
|
410
|
+
readonly reusesLockfiles: readonly ["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "bun.lock", "bun.lockb", "deno.lock"];
|
|
411
|
+
};
|
|
412
|
+
readonly bun: {
|
|
413
|
+
readonly configs: readonly [RelPath];
|
|
414
|
+
readonly lockfiles: readonly ["bun.lock", "bun.lockb"];
|
|
415
|
+
};
|
|
416
|
+
readonly deno: {
|
|
417
|
+
readonly configs: readonly [RelPath];
|
|
418
|
+
readonly lockfiles: readonly ["deno.lock"];
|
|
419
|
+
};
|
|
420
|
+
readonly npm: {
|
|
421
|
+
readonly configs: readonly [];
|
|
422
|
+
readonly lockfiles: readonly ["package-lock.json", "npm-shrinkwrap.json"];
|
|
423
|
+
};
|
|
424
|
+
readonly pnpm: {
|
|
425
|
+
readonly configs: readonly [RelPath];
|
|
426
|
+
readonly lockfiles: readonly ["pnpm-lock.yaml"];
|
|
427
|
+
};
|
|
428
|
+
readonly yarn: {
|
|
429
|
+
readonly configs: readonly [RelPath];
|
|
430
|
+
readonly lockfiles: readonly ["yarn.lock"];
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/domain/entities/rule-id.d.ts
|
|
435
|
+
/**
|
|
436
|
+
* Closed set of built-in rule IDs. Declared in `domain/` so SiroConfig can
|
|
437
|
+
* type `rules` keys without depending on the engine.
|
|
438
|
+
*/
|
|
439
|
+
declare const BUILTIN_RULE_IDS: readonly ["advisory-check", "approved-git-repos", "audit-suppression", "block-auto-install", "block-exotic-subdeps", "bun-security-scanner", "checksum-verification", "commit-lockfile", "dependency-overrides", "disable-lifecycle-scripts", "enforce-strict-ssl", "files-field", "frozen-lockfile", "frozen-store", "hardened-mode", "minimum-release-age", "named-registries", "paranoid-mode", "patched-dependencies", "pin-exact-versions", "provenance", "publish-access", "store-server", "strict-allow-scripts", "strict-release-age", "strict-store-integrity", "trust-policy"];
|
|
440
|
+
type BuiltinRuleId = (typeof BUILTIN_RULE_IDS)[number];
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/domain/entities/siro-config.d.ts
|
|
443
|
+
/** Per-rule setting. `'off'` disables; a Severity overrides the default level. */
|
|
444
|
+
type RuleSetting = Severity | 'off';
|
|
445
|
+
/**
|
|
446
|
+
* User-facing config returned by `defineConfig` in `siro.config.{ts,mjs,js}`.
|
|
447
|
+
*
|
|
448
|
+
* defineConfig({
|
|
449
|
+
* pms: ['npm', 'pnpm'], // restrict detection
|
|
450
|
+
* rules: { provenance: 'off' }, // disable / override severity
|
|
451
|
+
* customRules: [myRule], // extend
|
|
452
|
+
* reporters: [mySarifReporter], // extend
|
|
453
|
+
* })
|
|
454
|
+
*/
|
|
455
|
+
interface SiroConfig {
|
|
456
|
+
readonly pms?: readonly PM[];
|
|
457
|
+
readonly rules?: Readonly<Partial<Record<BuiltinRuleId | (string & Record<never, never>), RuleSetting>>>;
|
|
458
|
+
readonly customRules?: readonly Rule[];
|
|
459
|
+
readonly reporters?: readonly Reporter[];
|
|
460
|
+
}
|
|
461
|
+
/** Identity helper for type-checked config files. */
|
|
462
|
+
declare const defineConfig: (config: SiroConfig) => SiroConfig;
|
|
463
|
+
//#endregion
|
|
464
|
+
//#region src/domain/ports/config-codec.d.ts
|
|
465
|
+
/** Reads a single config-file format into siro's structural view. */
|
|
466
|
+
interface ConfigCodec {
|
|
467
|
+
parse: (text: string) => ParsedConfig;
|
|
468
|
+
}
|
|
469
|
+
/** Resolve the codec for a given file kind. Total over `CodecKind`. */
|
|
470
|
+
type CodecFor = (kind: CodecKind) => ConfigCodec;
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region src/domain/rules/builders/require-config-key.d.ts
|
|
473
|
+
/**
|
|
474
|
+
* Display-only metadata for a binding. siro itself is intentionally
|
|
475
|
+
* version-agnostic (we never branch on the user's actual PM version) — these
|
|
476
|
+
* fields exist so the rendered message can still tell the reader *when* a key
|
|
477
|
+
* appeared and *when* the PM started shipping a safe default. They MUST NOT
|
|
478
|
+
* be consulted by any code path that decides severity or applicability.
|
|
479
|
+
*/
|
|
480
|
+
interface VersionNote {
|
|
481
|
+
/** PM version that first shipped this config key (e.g. `'pnpm 10.16.0'`). */
|
|
482
|
+
readonly configAvailableSince?: string;
|
|
483
|
+
/** PM version whose built-in default first satisfies the rule (e.g. `'pnpm 11.0.0 (1440 minutes)'`). */
|
|
484
|
+
readonly defaultSafeSince?: string;
|
|
485
|
+
/** Free-form trailing note rendered last (e.g. `'replaces auto-install-peers'`). */
|
|
486
|
+
readonly note?: string;
|
|
487
|
+
}
|
|
488
|
+
interface RequireConfigKeySpec {
|
|
489
|
+
readonly file: ConfigFileRef;
|
|
490
|
+
readonly keyPath: KeyPath;
|
|
491
|
+
/** Value `fix` will write and (when no `accept` predicate is given) the value `check` expects. */
|
|
492
|
+
readonly value: ConfigValue;
|
|
493
|
+
readonly message: string;
|
|
494
|
+
readonly docs?: string;
|
|
495
|
+
readonly severity?: Severity;
|
|
496
|
+
accept?: (actual: unknown) => boolean;
|
|
497
|
+
/**
|
|
498
|
+
* Extra key writes appended after the auto-generated one (e.g. clearing
|
|
499
|
+
* save-prefix alongside save-exact on the same .npmrc). The target file
|
|
500
|
+
* is implicitly `spec.file` — extras can't redirect to a different file
|
|
501
|
+
* because `check` would then never validate them, leaving the fix ops
|
|
502
|
+
* and `lint` permanently out of step. A rule that legitimately needs to write
|
|
503
|
+
* across multiple files belongs in a hand-written binding (see
|
|
504
|
+
* `disable-lifecycle-scripts` → `overrideBindings`).
|
|
505
|
+
*/
|
|
506
|
+
readonly extraFix?: readonly KeyAssignment[];
|
|
507
|
+
/**
|
|
508
|
+
* PM-documented default for this key. When the user has not set the key
|
|
509
|
+
* AND this default would satisfy `accept`/`value`, the finding is
|
|
510
|
+
* downgraded to {@link defaultSatisfiedSeverity} (default `'info'`) — the
|
|
511
|
+
* threat is mitigated by the PM but explicit pinning is still recommended.
|
|
512
|
+
* - unset: no PM-default protection (legacy behaviour).
|
|
513
|
+
*
|
|
514
|
+
* A CONDITIONAL default (e.g. CI-only: pnpm `frozenLockfile`, aube
|
|
515
|
+
* `preferFrozenLockfile`) may use this field, but the binding's `message`
|
|
516
|
+
* MUST name the condition — the downgrade then reads "covered where it
|
|
517
|
+
* matters most", not "covered unconditionally".
|
|
518
|
+
*/
|
|
519
|
+
readonly documentedDefault?: ConfigValue;
|
|
520
|
+
/**
|
|
521
|
+
* Severity used when `documentedDefault` satisfies the requirement. Defaults
|
|
522
|
+
* to `'info'` (advisory). Set `'off'` to silence the finding entirely.
|
|
523
|
+
*/
|
|
524
|
+
readonly defaultSatisfiedSeverity?: Severity | 'off';
|
|
525
|
+
readonly versionNote?: VersionNote;
|
|
526
|
+
}
|
|
527
|
+
interface RequireConfigKeyOptions {
|
|
528
|
+
readonly id: string;
|
|
529
|
+
readonly title: string;
|
|
530
|
+
readonly description: string;
|
|
531
|
+
readonly severity: Severity;
|
|
532
|
+
readonly docs?: string;
|
|
533
|
+
/** Bindings keyed by PM. PMs absent from this map are treated as N/A. */
|
|
534
|
+
readonly bindings: Partial<Record<PM, RequireConfigKeySpec>>;
|
|
535
|
+
/** Return false to short-circuit `check` as N/A (e.g. private packages). */
|
|
536
|
+
applies?: (ctx: RepoContext) => boolean;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Replace one or more bindings on an existing rule, returning a fresh Rule
|
|
540
|
+
* object. Used by rules whose shape outgrows {@link requireConfigKey} for a
|
|
541
|
+
* subset of PMs — they build the simple slots via the builder, then splice
|
|
542
|
+
* the hand-written bindings in via this helper rather than rebuilding from
|
|
543
|
+
* scratch or mutating the source rule.
|
|
544
|
+
*/
|
|
545
|
+
declare const overrideBindings: (rule: Rule, overrides: Partial<Rule["bindings"]>) => Rule;
|
|
546
|
+
declare const renderVersionNoteMessage: (message: string, versionNote: VersionNote | undefined) => string;
|
|
547
|
+
/** Build a Rule from a per-PM table of {file, keyPath, value, message}. */
|
|
548
|
+
declare const requireConfigKey: (options: RequireConfigKeyOptions) => Rule;
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region src/domain/services/detect-pms.d.ts
|
|
551
|
+
declare const detectPMs: (ctx: RepoContext) => PM[];
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/domain/services/filter.d.ts
|
|
554
|
+
/** Keep only findings at or above `threshold`, recomputing the summary. */
|
|
555
|
+
declare const filterBySeverity: (result: LintResult, threshold: Severity) => LintResult;
|
|
556
|
+
/**
|
|
557
|
+
* Exit code for a lint run. Non-zero when any finding meets `threshold`
|
|
558
|
+
* (default: only `error` fails the run).
|
|
559
|
+
*/
|
|
560
|
+
declare const exitCodeForLint: (result: LintResult, threshold?: Severity) => number;
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/shared/siro-error.d.ts
|
|
563
|
+
/**
|
|
564
|
+
* Structured errors used across siro. The CLI maps them to exit codes in one
|
|
565
|
+
* place so individual commands never have to think about exit semantics.
|
|
566
|
+
* The exit-code table lives in docs/configuration.md S"Exit codes" and
|
|
567
|
+
* src/cli/help.ts (HELP_LINT) -- not restated here.
|
|
568
|
+
*
|
|
569
|
+
* One mapping note that belongs at this altitude: filesystem errno errors
|
|
570
|
+
* are routed to 2 by SHAPE (numeric `errno`) in cli run(), so an
|
|
571
|
+
* errno-bearing throw from a user extension also lands on 2 rather than the
|
|
572
|
+
* 70 crash path -- same remedy (fix the environment) either way.
|
|
573
|
+
*/
|
|
574
|
+
declare class SiroError extends Error {
|
|
575
|
+
/** Recommended process exit code for this error. */
|
|
576
|
+
readonly exitCode: number;
|
|
577
|
+
constructor(message: string, exitCode: number);
|
|
578
|
+
}
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/shared/config-error.d.ts
|
|
581
|
+
declare class ConfigError extends SiroError {
|
|
582
|
+
constructor(message: string);
|
|
583
|
+
}
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/shared/usage-error.d.ts
|
|
586
|
+
declare class UsageError extends SiroError {
|
|
587
|
+
constructor(message: string);
|
|
588
|
+
}
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/shared/errors.d.ts
|
|
591
|
+
/**
|
|
592
|
+
* Run `fn` and wrap any non-`ConfigError` failure as a `ConfigError` prefixed
|
|
593
|
+
* with `filePath`. Codec `parse` calls hit raw libraries that
|
|
594
|
+
* throw bare `Error`s; without this wrap the CLI would surface those as
|
|
595
|
+
* exit-1 internal errors and break CI that branches on exit codes. A
|
|
596
|
+
* `ConfigError` that bubbles up from a nested call is re-thrown unchanged so
|
|
597
|
+
* the original `path: message` framing is preserved.
|
|
598
|
+
*/
|
|
599
|
+
declare const wrapCodecError: <TResult>(filePath: string, fn: () => TResult) => TResult;
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/version.d.ts
|
|
602
|
+
declare const version = "0.1.0-rc";
|
|
603
|
+
//#endregion
|
|
604
|
+
export { type AbsPath, type AdvisoryRuleBinding, type AutoRuleBinding, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, CONFIG_FILES, type CheckStatus, type CodecFor, type ConfigCodec, ConfigError, type ConfigFileRef, type ConfigReadValue, type ConfigScalar, type ConfigValue, type FileSystem, type Finding, type FixKind, type FixOp, type IO, type KeyAssignment, type KeyPath, type LintOptions, type LintResult, type PM, PMS, type PMSignals, PM_SIGNALS, type PackageJson, type ParsedConfig, type RelPath, type RepoContext, type Reporter, type ReporterRegistry, type RequireConfigKeyOptions, type Rule, type RuleBinding, type RuleSetting, SEVERITIES, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, asAbsPath, asRelPath, createRegistry, defineConfig, detectPMs, exitCodeForLint, filterBySeverity, getByPath, githubReporter, isPM, isReporterShape, isSeverity, jsonReporter, lintCommand, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, renderVersionNoteMessage, requireConfigKey, version, wrapCodecError };
|
|
605
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{A as e,C as t,D as n,E as r,O as i,S as a,T as o,_ as s,a as c,b as l,c as u,d,f,g as p,h as m,i as h,k as g,l as _,m as v,n as y,o as b,r as x,s as S,t as C,u as w,v as T,w as E,x as D,y as O}from"./node-io-C_ERSDGJ.mjs";const k=e=>e;export{v as BUILTIN_REPORTER_NAMES,_ as CONFIG_FILES,n as ConfigError,D as PMS,c as PM_SIGNALS,a as SEVERITIES,i as SiroError,r as UsageError,g as asAbsPath,e as asRelPath,m as createRegistry,k as defineConfig,x as detectPMs,d as exitCodeForLint,f as filterBySeverity,w as getByPath,O as githubReporter,t as isPM,l as isReporterShape,E as isSeverity,s as jsonReporter,y as lintCommand,h as nodeFileSystem,C as nodeIO,b as overrideBindings,p as prettyReporter,S as renderVersionNoteMessage,u as requireConfigKey,T as version,o as wrapCodecError};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import e from"picocolors";import t from"ini";import{parse as n}from"smol-toml";import{parseDocument as r}from"yaml";import*as i from"valibot";import{accessSync as a,constants as o,readFileSync as s}from"node:fs";import c from"node:path";import{createJiti as l}from"jiti";const u=e=>e,d=e=>e;var f=class extends Error{exitCode;constructor(e,t){super(e),this.name=`SiroError`,this.exitCode=t}},p=class extends f{constructor(e){super(e,2),this.name=`ConfigError`}},m=class extends f{constructor(e){super(e,2),this.name=`UsageError`}};const h=(e,t)=>{try{return t()}catch(t){if(t instanceof p)throw t;let n=String(t);throw t instanceof Error&&(n=t.message),new p(`${e}: ${n}`)}},g=[`npm`,`pnpm`,`yarn`,`bun`,`deno`,`aube`],_=[`error`,`warn`,`info`],ee={error:3,info:1,warn:2},te=new Set(g),ne=new Set(_),v=e=>te.has(e),re=e=>ne.has(e),ie=/^(?<pmName>[a-z]+)(?:@.+)?$/u,ae=e=>{let t=ie.exec(e.trim()),n;if(t&&t.groups&&(n=t.groups.pmName),n!==void 0&&v(n))return n},y=e=>typeof e!=`object`||!e||!(`name`in e)||typeof e.name!=`string`?!1:`format`in e&&typeof e.format==`function`,oe={error:`error`,info:`notice`,warn:`warning`},b=e=>e.replaceAll(`%`,`%25`).replaceAll(`\r`,`%0D`).replaceAll(`
|
|
2
|
+
`,`%0A`).replaceAll(`:`,`%3A`).replaceAll(`,`,`%2C`),se=e=>e.replaceAll(`%`,`%25`).replaceAll(`\r`,`%0D`).replaceAll(`
|
|
3
|
+
`,`%0A`),ce={format(e,t){for(let n of e.findings){let e=``;n.file&&(e=`file=${b(n.file)},`);let r=`[${n.pm}] ${n.message}`;n.docs&&(r=`[${n.pm}] ${n.message} (${n.docs})`),t.stdout(`::${oe[n.severity]} ${e}title=${b(n.ruleId)}::${se(r)}`)}},name:`github`},le=`0.1.0-rc`,ue={format(e,t){t.stdout(JSON.stringify(Object.assign({},{schemaVersion:1,siroVersion:le},e),void 0,2))},name:`json`},de={error:`✖ error`,info:`ℹ info`,warn:`⚠ warn`},fe=()=>{let{env:t}=process;return t.NO_COLOR!==void 0&&t.NO_COLOR!==``?!1:`FORCE_COLOR`in t?t.FORCE_COLOR!==`0`&&t.FORCE_COLOR!==``:e.isColorSupported},pe=(e,t)=>{let n=``;e.file&&(n=t.colors.dim(` (${e.file})`)),t.io.stdout(`${t.tag[e.severity](de[e.severity])} [${e.pm}] ${t.colors.bold(e.ruleId)}${n}`),t.io.stdout(` ${e.message}`);for(let n of e.manualSteps??[])t.io.stdout(` ↳ ${n}`);e.docs&&t.io.stdout(t.colors.dim(` → ${e.docs}`))},me=t=>{let n=e.createColors(fe());return{colors:n,io:t,tag:{error:n.red,info:n.cyan,warn:n.yellow}}},he={format(e,t){let n=me(t);if(e.findings.length===0){t.stdout(n.colors.green(`✔ No security best-practice issues found.`));return}for(let t of e.findings)pe(t,n);let{error:r,warn:i,info:a}=e.summary;t.stdout(``),t.stdout(`Summary: ${r} error, ${i} warn, ${a} info`)},name:`pretty`},ge=[he,ue,ce],_e=ge.map(e=>e.name),ve=(e=[])=>{let t=new Map;for(let n of[...ge,...e]){if(!y(n))throw TypeError(`createRegistry: each reporter needs a string 'name' and a 'format' function.`);t.set(n.name,n)}return{get:e=>t.get(e),list:()=>[...t.keys()]}},x=e=>e instanceof Error&&`code`in e&&typeof e.code==`string`,ye=(e,t)=>ee[e]>=ee[t],be=(e,t)=>{let n=[],r={error:0,info:0,warn:0};for(let i of e.findings)ye(i.severity,t)&&(n.push(i),r[i.severity]+=1);return{findings:n,summary:r}},S=(e,t=`error`)=>+!!e.findings.some(e=>ye(e.severity,t)),C=e=>{if(typeof e!=`object`||!e||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===null||t===Object.prototype},w=(e,t)=>{let n=e;for(let e of t){if(!C(n))return;n=n[e]}return n},T=e=>C(e)?e:{},xe=/^-?\d+$/u,Se=e=>{if(e===`true`)return!0;if(e===`false`)return!1},Ce=e=>{if(xe.test(e))return Number(e)},E=e=>{if(typeof e==`boolean`||typeof e==`number`)return e;let t=String(e??``);return typeof e==`string`&&(t=e),Se(t)??Ce(t)??t},we={json:{parse(e){let t=e.trim();return t===``?{}:T(JSON.parse(t))}},npmrc:{parse(e){let n=T(t.parse(e)),r={};for(let[e,t]of Object.entries(n))Array.isArray(t)?r[e]=t.map(e=>E(e)):typeof t==`object`&&t?r[e]=T(t):r[e]=E(t);return r}},toml:{parse(e){return e.trim()===``?{}:T(n(e))}},yaml:{parse(e){return e.trim()===``?{}:T(r(e).toJS())}}},Te=e=>we[e],Ee=e=>(t,n)=>{let r=e(t,n);return r.state===`violation`&&r.severity!==void 0?{actual:r.actual,expected:r.expected,manualSteps:r.manualSteps,message:r.message,state:`violation`}:r},De=(e,t)=>{let n={};for(let t of g){let r=e.bindings[t];r!==void 0&&(n[t]=Object.assign({},r,{check:Ee(r.check),severity:void 0}))}return Object.assign({},e,{bindings:n,severity:t})},Oe=(e,t,n)=>{let r=t[e.id];r!==`off`&&(r===void 0?n.push(e):n.push(De(e,r)))},ke=(e,t)=>{let n;return t&&({customRules:n}=t),Array.from(e).concat(n??[])},Ae=(e,t)=>{let n=ke(e,t),r;if(t&&(r=t.rules),r===void 0)return n;let i=[];for(let e of n)Oe(e,r,i);return i},D={aubeWorkspace:{kind:`yaml`,path:d(`aube-workspace.yaml`)},bunfig:{kind:`toml`,path:d(`bunfig.toml`)},denoJson:{kind:`json`,path:d(`deno.json`)},npmrc:{kind:`npmrc`,path:d(`.npmrc`)},packageJson:{kind:`json`,path:d(`package.json`)},pnpmWorkspace:{kind:`yaml`,path:d(`pnpm-workspace.yaml`)},yarnrc:{kind:`yaml`,path:d(`.yarnrc.yml`)}},O=(e,t)=>Object.assign({},e,{bindings:Object.assign({},e.bindings,t)}),je=e=>{let t=[];return e.configAvailableSince!==void 0&&t.push(`available since ${e.configAvailableSince}`),e.defaultSafeSince!==void 0&&t.push(`default safe since ${e.defaultSafeSince}`),e.note!==void 0&&t.push(e.note),t},k=(e,t)=>{if(t===void 0)return e;let n=je(t);return n.length===0?e:`${e} (${n.join(`; `)})`},Me=e=>e.accept?e.accept(e.documentedDefault):e.documentedDefault===e.value,Ne=(e,t,n)=>{if(t!==void 0||e.documentedDefault===void 0||!Me(e))return;let r=e.defaultSatisfiedSeverity??`info`;return r===`off`?{state:`ok`}:{actual:t,expected:e.value,message:n,severity:r,state:`violation`}},Pe=(e,t,n)=>{for(let r of e.extraFix??[]){let e=w(t,r.keyPath);if(e!==r.value)return{actual:e,expected:r.value,message:n,state:`violation`}}},Fe=(e,t,n)=>{let r=t===e.value;if(e.accept&&(r=e.accept(t)),!r)return{actual:t,expected:e.value,message:n,state:`violation`}},Ie=(e,t,n)=>{let r=w(t,e.keyPath),i=Ne(e,r,n);if(i===void 0){let t=Fe(e,r,n);if(t!==void 0)return t}return Pe(e,t,n)??i??{state:`ok`}},Le=(e,t)=>({check(n,r){return t!==void 0&&!t(n)?{state:`na`}:Ie(e,r,k(e.message,e.versionNote))},docs:e.docs,file:e.file,fix(){let t=[{file:e.file,keyPath:e.keyPath,op:`setKey`,value:e.value}];for(let n of e.extraFix??[])t.push({file:e.file,keyPath:n.keyPath,op:`setKey`,value:n.value});return t},fixKind:`auto`,severity:e.severity}),A=e=>{let t={};for(let n of g){let r=e.bindings[n];r!==void 0&&(t[n]=Le(r,e.applies))}return{bindings:t,description:e.description,docs:e.docs,id:e.id,severity:e.severity,title:e.title}},{aubeWorkspace:Re}=D,ze=A({bindings:{aube:{accept:e=>e===`on`||e===`required`,docs:`https://aube.jdx.dev/security.html`,file:Re,keyPath:[`advisoryCheck`],message:"Set `advisoryCheck: on` (or `required`) in aube-workspace.yaml to check dependencies against known-malicious advisories.",value:`on`}},description:`Query the OSV database for known-malicious packages during dependency resolution.`,docs:`https://aube.jdx.dev/security.html`,id:`advisory-check`,severity:`warn`,title:`Enable malicious-package advisory checks`}),{yarnrc:j}=D,Be=k("Set `approvedGitRepositories: []` in .yarnrc.yml to block all git: protocol dependencies (or list approved repository globs).",{configAvailableSince:`yarn 4.14.0`}),Ve={bindings:{yarn:{check(e,t){let n=w(t,[`approvedGitRepositories`]);return Array.isArray(n)?{state:`ok`}:{actual:n,expected:`[]`,message:Be,state:`violation`}},docs:`https://yarnpkg.com/configuration/yarnrc#approvedGitRepositories`,file:j,fix(){return[{file:j,message:"Add `approvedGitRepositories: []` to .yarnrc.yml to block all git deps, or list specific approved repository URL globs.",op:`note`}]},fixKind:`advisory`}},description:`Restrict git: protocol dependencies to an explicit allowlist of approved repository URL patterns.`,docs:`https://yarnpkg.com/configuration/yarnrc#approvedGitRepositories`,id:`approved-git-repos`,severity:`warn`,title:`Approve git repository dependencies`},{yarnrc:M}=D,He=e=>Array.isArray(e)&&e.length>0,Ue=[`npmAuditIgnoreAdvisories`,`npmAuditExcludePackages`],We=e=>{let t=Ue.filter(t=>He(w(e,[t]))),[n]=t;return n===void 0?{state:`ok`}:{actual:w(e,[n]),message:`Review audit suppression in .yarnrc.yml (${t.join(`, `)}). Broad glob patterns can silently hide future vulnerabilities.`,state:`violation`}},Ge={bindings:{yarn:{check(e,t){return We(t)},docs:`https://yarnpkg.com/configuration/yarnrc#npmAuditIgnoreAdvisories`,file:M,fix(){return[{file:M,message:"Review entries in `npmAuditIgnoreAdvisories` and `npmAuditExcludePackages` — remove stale suppressions and overly broad glob patterns.",op:`note`}]},fixKind:`advisory`}},description:`Flag audit advisory suppressions that may silently hide future vulnerabilities via broad glob patterns.`,docs:`https://yarnpkg.com/configuration/yarnrc#npmAuditIgnoreAdvisories`,id:`audit-suppression`,severity:`info`,title:`Review audit suppression entries`},{bunfig:Ke}=D,qe=A({bindings:{bun:{docs:`https://bun.sh/docs/runtime/bunfig#install-auto`,file:Ke,keyPath:[`install`,`auto`],message:'Set `auto = "disable"` under [install] in bunfig.toml to prevent auto-install from bypassing security guards.',value:`disable`}},description:`Disable auto-install so dependencies are only installed through an explicit install step where security guards apply.`,docs:`https://bun.sh/docs/runtime/bunfig#install-auto`,id:`block-auto-install`,severity:`warn`,title:`Block automatic package installation`}),{aubeWorkspace:Je,npmrc:N,pnpmWorkspace:Ye}=D,P=e=>e===`root`||e===`none`,F="Set `allow-git=root` and `allow-remote=root` in .npmrc to block git/tarball URLs in transitive dependencies.",Xe={check(e,t){let n=w(t,[`allow-git`]);if(!P(n))return{actual:n,expected:`root`,message:F,state:`violation`};let r=w(t,[`allow-remote`]);return P(r)?{state:`ok`}:{actual:r,expected:`root`,message:F,state:`violation`}},docs:`https://docs.npmjs.com/cli/v11/using-npm/config#allow-git`,file:N,fix(){return[{file:N,keyPath:[`allow-git`],op:`setKey`,value:`root`},{file:N,keyPath:[`allow-remote`],op:`setKey`,value:`root`}]},fixKind:`auto`},Ze=O(A({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,documentedDefault:!0,file:Je,keyPath:[`blockExoticSubdeps`],message:"Set `blockExoticSubdeps: true` in aube-workspace.yaml to block git/tarball URLs in transitive dependencies.",value:!0},pnpm:{docs:`https://pnpm.io/settings#blockexoticsubdeps`,documentedDefault:!0,file:Ye,keyPath:[`blockExoticSubdeps`],message:"Set `blockExoticSubdeps: true` in pnpm-workspace.yaml to block git/tarball URLs in transitive dependencies.",value:!0,versionNote:{configAvailableSince:`pnpm 10.26.0`}}},description:`Refuse to install transitive dependencies sourced from git or tarball URLs, which bypass registry integrity checking.`,docs:`https://pnpm.io/settings#blockexoticsubdeps`,id:`block-exotic-subdeps`,severity:`warn`,title:`Block exotic subdependencies`}),{npm:Xe}),{bunfig:I}=D,Qe={configAvailableSince:`bun 1.3.0`},$e={bindings:{bun:{check(e,t){let n=w(t,[`install`,`security`,`scanner`]);return typeof n==`string`&&n.length>0?{state:`ok`}:{actual:n,message:k('Configure `[install.security] scanner = "..."` in bunfig.toml (e.g. `@socketsecurity/bun-security-scanner`) to scan new packages on install.',Qe),state:`violation`}},docs:`https://bun.com/docs/pm/security-scanner-api`,file:I,fix(){return[{file:I,message:'Add `[install.security] scanner = "@socketsecurity/bun-security-scanner"` (or another bun-compatible scanner) to bunfig.toml.',op:`note`}]},fixKind:`advisory`}},description:`Bun supports a Security Scanner API that intercepts new packages at install time (e.g. Socket Firewall).`,docs:`https://github.com/bodadotsh/npm-security-best-practices#preinstall-scanners`,id:`bun-security-scanner`,severity:`info`,title:`Enable a bun install-time security scanner`},{yarnrc:et}=D,tt=A({bindings:{yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#checksumBehavior`,documentedDefault:`throw`,file:et,keyPath:[`checksumBehavior`],message:"Set `checksumBehavior: throw` in .yarnrc.yml to reject packages whose checksum does not match the lockfile.",value:`throw`}},description:`Throw on checksum mismatches so tampered or corrupted packages are never silently installed.`,docs:`https://yarnpkg.com/configuration/yarnrc#checksumBehavior`,id:`checksum-verification`,severity:`warn`,title:`Verify package checksums`}),L={aube:{configs:[D.aubeWorkspace.path],lockfiles:[`aube-lock.yaml`],reusesLockfiles:[`pnpm-lock.yaml`,`package-lock.json`,`npm-shrinkwrap.json`,`yarn.lock`,`bun.lock`,`bun.lockb`,`deno.lock`]},bun:{configs:[D.bunfig.path],lockfiles:[`bun.lock`,`bun.lockb`]},deno:{configs:[D.denoJson.path],lockfiles:[`deno.lock`]},npm:{configs:[],lockfiles:[`package-lock.json`,`npm-shrinkwrap.json`]},pnpm:{configs:[D.pnpmWorkspace.path],lockfiles:[`pnpm-lock.yaml`]},yarn:{configs:[D.yarnrc.path],lockfiles:[`yarn.lock`]}},nt={aube:`https://aube.en.dev/package-manager/lockfiles`,bun:`https://bun.com/docs/install/lockfile`,deno:`https://docs.deno.com/runtime/fundamentals/modules/#integrity-checking-and-lock-files`,npm:`https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json`,pnpm:`https://pnpm.io/git#lockfiles`,yarn:`https://yarnpkg.com/getting-started/qa#should-lockfiles-be-committed-to-the-repository`},R=e=>{let{lockfiles:t,reusesLockfiles:n}=L[e],[r]=t,i={kind:`fileGlob`,path:d(r)},a=[...t,...n??[]];return{check(e){return a.some(t=>e.exists(d(t)))?{state:`ok`}:{message:`No lockfile found. Generate and commit ${r}.`,state:`violation`}},docs:nt[e],file:i,fix(){return[{file:i,message:`Install dependencies to generate ${r}, then commit it.`,op:`ensureFileTracked`}]},fixKind:`advisory`}},rt={bindings:{aube:R(`aube`),bun:R(`bun`),deno:R(`deno`),npm:R(`npm`),pnpm:R(`pnpm`),yarn:R(`yarn`)},description:"Lockfiles pin the full dependency tree and integrity hashes, enabling reproducible, verifiable installs (e.g. `npm ci`).",docs:`https://github.com/bodadotsh/npm-security-best-practices#2-include-lockfiles`,id:`commit-lockfile`,severity:`error`,title:`Commit a lockfile`},{aubeWorkspace:it,pnpmWorkspace:at}=D,ot=e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&Object.keys(e).length>0,st=(e,t)=>{let n=w(e,[`overrides`]);return ot(n)?{actual:n,message:`Review \`overrides\` in ${t}. Overrides can replace transitive dependencies with arbitrary versions or forks.`,state:`violation`}:{state:`ok`}},z=(e,t,n)=>({check(e,n){return st(n,t)},docs:n,file:e,fix(){return[{file:e,message:"Review each entry in `overrides` — verify pinned versions address a known CVE and are not redirecting to untrusted packages.",op:`note`}]},fixKind:`advisory`}),ct={bindings:{aube:z(it,`aube-workspace.yaml`,`https://aube.jdx.dev/settings.html`),pnpm:z(at,`pnpm-workspace.yaml`,`https://pnpm.io/settings#overrides`)},description:`Flag dependency overrides that can replace transitive packages with arbitrary versions or forks — a supply-chain injection vector.`,docs:`https://pnpm.io/settings#overrides`,id:`dependency-overrides`,severity:`info`,title:`Review dependency overrides`},{npmrc:lt,pnpmWorkspace:B,yarnrc:ut,aubeWorkspace:V,bunfig:dt}=D,ft={configAvailableSince:`pnpm 10.3.0`,defaultSafeSince:`pnpm 11.0.0`},H=e=>k(e,ft),pt={check(e,t){let n=w(t,[`dangerouslyAllowAllBuilds`]);if(n===!0)return{actual:n,expected:!1,manualSteps:["Remove `dangerouslyAllowAllBuilds: true` from pnpm-workspace.yaml (or set it to `false`). Setting `strictDepBuilds: true` alone has no effect while the bypass remains."],message:H("`dangerouslyAllowAllBuilds: true` in pnpm-workspace.yaml bypasses strictDepBuilds — remove it (or set it to false) to restore lifecycle-script gating."),state:`violation`};let r=w(t,[`strictDepBuilds`]);return r===!0?{state:`ok`}:r===void 0?{actual:r,expected:!0,message:H("Set `strictDepBuilds: true` in pnpm-workspace.yaml to pin lifecycle-script gating across versions."),severity:`info`,state:`violation`}:{actual:r,expected:!0,message:H("Set `strictDepBuilds: true` in pnpm-workspace.yaml to block silent skips of un-approved dep builds."),state:`violation`}},docs:`https://pnpm.io/settings#strictdepbuilds`,file:B,fix(){return[{file:B,keyPath:[`strictDepBuilds`],op:`setKey`,value:!0}]},fixKind:`auto`},mt={check(e,t){let n=w(t,[`jailBuilds`]);if(n!==!0)return{actual:n,expected:!0,message:"Set `jailBuilds: true` in aube-workspace.yaml to sandbox build scripts.",state:`violation`};let r=w(t,[`strictDepBuilds`]);return r===!0?{state:`ok`}:{actual:r,expected:!0,message:"Set `strictDepBuilds: true` in aube-workspace.yaml to require explicit allow/deny for lifecycle scripts.",state:`violation`}},docs:`https://aube.jdx.dev/security.html`,file:V,fix(){return[{file:V,keyPath:[`jailBuilds`],op:`setKey`,value:!0},{file:V,keyPath:[`strictDepBuilds`],op:`setKey`,value:!0}]},fixKind:`auto`},ht=k('Set `ignoreScripts = true` under [install] in bunfig.toml — or set `"trustedDependencies": []` in package.json — to opt out of the curated allow-list (postinstall is already blocked for untrusted packages by default).',{configAvailableSince:`bun 1.2.0`}),gt={check(e,t){let n=w(t,[`install`,`ignoreScripts`]);if(n===!0)return{state:`ok`};let r;return e.packageJson&&(r=e.packageJson.trustedDependencies),r!==void 0&&r.length===0?{state:`ok`}:{actual:n,expected:!0,message:ht,state:`violation`}},docs:`https://bun.com/docs/pm/lifecycle`,file:dt,fix(){return[{file:dt,keyPath:[`install`,`ignoreScripts`],op:`setKey`,value:!0}]},fixKind:`auto`,severity:`info`},_t=O(A({bindings:{npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#ignore-scripts`,file:lt,keyPath:[`ignore-scripts`],message:"Set `ignore-scripts=true` in .npmrc to block dependency lifecycle scripts.",value:!0},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableScripts`,documentedDefault:!1,file:ut,keyPath:[`enableScripts`],message:"Set `enableScripts: false` in .yarnrc.yml to pin the policy across versions.",value:!1,versionNote:{configAvailableSince:`yarn 2.0.0`,defaultSafeSince:`yarn 4.14.0`}}},description:`Malicious postinstall scripts are a primary supply-chain attack vector. Prevent automatic execution of dependency lifecycle scripts.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#3-disable-lifecycle-scripts`,id:`disable-lifecycle-scripts`,severity:`error`,title:`Disable dependency lifecycle scripts`}),{aube:mt,bun:gt,pnpm:pt}),{npmrc:vt,yarnrc:yt}=D,bt="Set `enableStrictSsl: true` in .yarnrc.yml to enforce SSL certificate validation for registry connections.",xt={check(e,t){let n=w(t,[`enableStrictSsl`]);if(n===!1)return{actual:!1,expected:!0,message:bt,state:`violation`};let r=w(t,[`unsafeHttpWhitelist`]);return Array.isArray(r)&&r.length>0?{actual:r,expected:``,manualSteps:["Review and remove entries from `unsafeHttpWhitelist` in .yarnrc.yml. Each entry permits unencrypted HTTP to that hostname."],message:"`unsafeHttpWhitelist` in .yarnrc.yml allows unencrypted HTTP connections — remove entries or clear the list to enforce HTTPS-only registry access.",state:`violation`}:n===void 0?{actual:n,expected:!0,message:bt,severity:`info`,state:`violation`}:{state:`ok`}},docs:`https://yarnpkg.com/configuration/yarnrc#enableStrictSsl`,file:yt,fix(){return[{file:yt,keyPath:[`enableStrictSsl`],op:`setKey`,value:!0}]},fixKind:`auto`},St=O(A({bindings:{npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-ssl`,documentedDefault:!0,file:vt,keyPath:[`strict-ssl`],message:"Set `strict-ssl=true` in .npmrc to enforce SSL certificate validation.",value:!0}},description:`Require SSL certificate validation so registry traffic cannot be intercepted or tampered with.`,docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-ssl`,id:`enforce-strict-ssl`,severity:`warn`,title:`Enforce SSL for registry connections`}),{yarn:xt}),U=e=>{let t=e.packageJson;return t===void 0?!1:t.private!==!0&&typeof t.name==`string`&&t.name.length>0},{packageJson:Ct,denoJson:wt}=D,W={check(e){if(!U(e))return{state:`na`};let t;return e.packageJson&&({files:t}=e.packageJson),Array.isArray(t)&&t.length>0?{state:`ok`}:{actual:t,message:"Add a `files` allow-list to package.json to limit what gets published.",state:`violation`}},docs:`https://docs.npmjs.com/cli/v11/configuring-npm/package-json#files`,file:Ct,fix(){return[{file:Ct,message:'Add a `files` array to package.json (e.g. ["dist"]) and verify with `npm pack --dry-run`.',op:`note`}]},fixKind:`advisory`},Tt={bindings:{aube:W,bun:W,deno:{check(e,t){if(typeof w(t,[`name`])!=`string`)return{state:`na`};let n=w(t,[`publish`,`include`]);return Array.isArray(n)&&n.length>0?{state:`ok`}:{actual:n,message:"Add `publish.include` to deno.json to limit what gets published.",state:`violation`}},docs:`https://docs.deno.com/runtime/reference/cli/publish/#how-publishing-works`,file:wt,fix(){return[{file:wt,message:"Add `publish.include` (and/or `publish.exclude`) to deno.json.",op:`note`}]},fixKind:`advisory`},npm:W,pnpm:W,yarn:W},description:"An explicit `files` array in package.json restricts what gets published, preventing accidental inclusion of secrets or local files.",docs:`https://github.com/bodadotsh/npm-security-best-practices#11-review-published-files`,id:`files-field`,severity:`info`,title:`Declare a published files allow-list`},{pnpmWorkspace:Et,yarnrc:Dt,bunfig:Ot,denoJson:kt,aubeWorkspace:At}=D,jt={check(e,t){if(w(t,[`lock`,`frozen`])===!0)return{state:`ok`};let n=w(t,[`lock`]),r=n!==void 0&&(typeof n!=`object`||!n||Array.isArray(n)),i={actual:w(t,[`lock`,`frozen`]),expected:!0,message:"Set `lock.frozen: true` in deno.json for reproducible, verified installs.",state:`violation`};return r?Object.assign({},i,{manualSteps:["deno.json `lock` is set to a non-object value; writing `lock.frozen: true` under it would overwrite that value. Set it yourself, or make `lock` an object first."]}):i},docs:`https://docs.deno.com/runtime/fundamentals/configuration/#lock`,file:kt,fix(){return[{file:kt,keyPath:[`lock`,`frozen`],op:`setKey`,value:!0}]},fixKind:`auto`},Mt=O(A({bindings:{aube:{docs:`https://aube.en.dev/settings/`,documentedDefault:!0,file:At,keyPath:[`preferFrozenLockfile`],message:`aube defaults preferFrozenLockfile to true — set it explicitly in aube-workspace.yaml to pin the policy across versions.`,value:!0},bun:{docs:`https://bun.com/docs/runtime/bunfig#install-frozenlockfile`,file:Ot,keyPath:[`install`,`frozenLockfile`],message:"Set `frozenLockfile = true` under [install] in bunfig.toml so installs fail when the lockfile would change.",value:!0,versionNote:{configAvailableSince:`bun 0.6.10`}},pnpm:{docs:`https://pnpm.io/settings#frozenlockfile`,documentedDefault:!0,file:Et,keyPath:[`frozenLockfile`],message:`pnpm defaults frozenLockfile to true in CI — set it explicitly in pnpm-workspace.yaml to pin the policy outside CI too.`,value:!0},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableImmutableInstalls`,documentedDefault:!0,file:Dt,keyPath:[`enableImmutableInstalls`],message:"Set `enableImmutableInstalls: true` in .yarnrc.yml to pin the policy outside CI too.",value:!0,versionNote:{configAvailableSince:`yarn 2.0.0`,defaultSafeSince:`yarn 3.0.0 in CI`}}},description:`Refuse to mutate the lockfile on install so unexpected dependency changes fail loudly.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#2-include-lockfiles`,id:`frozen-lockfile`,severity:`warn`,title:`Freeze the lockfile`}),{deno:jt}),{pnpmWorkspace:G}=D,Nt={bindings:{pnpm:{check(e,t){let n=w(t,[`frozenStore`]);return n===!0?{state:`ok`}:{actual:n,message:"Consider enabling `frozenStore` in pnpm-workspace.yaml. Without it, the content-addressable store can be mutated after initial population, weakening integrity guarantees.",state:`violation`}},docs:`https://pnpm.io/settings#frozenstore`,file:G,fix(){return[{file:G,message:"Set `frozenStore: true` to prevent writes to the store after initial population — especially useful in CI and deployment pipelines.",op:`note`}]},fixKind:`advisory`}},description:`Recommend enabling frozenStore to prevent mutations to the content-addressable store, strengthening supply-chain integrity in CI and deploy environments.`,docs:`https://pnpm.io/settings#frozenstore`,id:`frozen-store`,severity:`info`,title:`Enable frozen store`},{yarnrc:Pt}=D,Ft=A({bindings:{yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableHardenedMode`,documentedDefault:!0,file:Pt,keyPath:[`enableHardenedMode`],message:"yarn enables hardened mode automatically only for pull requests on public repositories — set `enableHardenedMode: true` in .yarnrc.yml to extend end-to-end install verification to every install.",value:!0,versionNote:{configAvailableSince:`yarn 4.0.0`,defaultSafeSince:`yarn 4.0.0 (conditional: auto-enabled for PRs on public repositories)`}}},description:`Yarn 4's enableHardenedMode performs end-to-end checksum, lockfile, and version verification at install time.`,docs:`https://yarnpkg.com/configuration/yarnrc#enableHardenedMode`,id:`hardened-mode`,severity:`warn`,title:`Enable hardened mode (yarn)`}),It=1440,K=3*It,Lt=3*86400,q=It,{npmrc:Rt,pnpmWorkspace:zt,yarnrc:Bt,bunfig:Vt,denoJson:Ht,aubeWorkspace:Ut}=D,J=e=>typeof e==`number`&&e>0,Wt=A({bindings:{aube:{accept:J,docs:`https://aube.en.dev/settings/`,documentedDefault:q,file:Ut,keyPath:[`minimumReleaseAge`],message:`Set minimumReleaseAge (~${K} minutes for a 3-day cooldown) in aube-workspace.yaml.`,value:K},bun:{accept:J,docs:`https://bun.com/docs/runtime/bunfig#install-minimumreleaseage`,file:Vt,keyPath:[`install`,`minimumReleaseAge`],message:`Set install.minimumReleaseAge (bun) to ~${Lt} seconds to quarantine brand-new releases.`,value:Lt,versionNote:{configAvailableSince:`bun 1.3.0`}},deno:{accept:e=>typeof e==`number`?e>0:typeof e==`string`?e!==``&&e!==`0`:typeof e==`object`&&!!e,docs:`https://docs.deno.com/runtime/reference/deno_json/`,file:Ht,keyPath:[`minimumDependencyAge`],message:`Set minimumDependencyAge (e.g. "P3D" for a 3-day cooldown) in deno.json.`,value:`P3D`},npm:{accept:J,docs:`https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age`,file:Rt,keyPath:[`min-release-age`],message:`Set min-release-age to ~3 days to quarantine brand-new releases.`,value:3,versionNote:{configAvailableSince:`npm 11.10.0`}},pnpm:{accept:J,docs:`https://pnpm.io/settings#minimumreleaseage`,documentedDefault:q,file:zt,keyPath:[`minimumReleaseAge`],message:`Set minimumReleaseAge (~${K} minutes for a 3-day cooldown) in pnpm-workspace.yaml.`,value:K,versionNote:{configAvailableSince:`pnpm 10.16.0`,defaultSafeSince:`pnpm 11.0.0 (1440 minutes)`}},yarn:{accept:J,docs:`https://yarnpkg.com/configuration/yarnrc#npmMinimalAgeGate`,documentedDefault:q,file:Bt,keyPath:[`npmMinimalAgeGate`],message:`Set npmMinimalAgeGate (~${K} minutes for a 3-day cooldown) in .yarnrc.yml.`,value:K,versionNote:{configAvailableSince:`yarn 4.10.0`,defaultSafeSince:`yarn 4.15.0 (1440 minutes)`}}},description:`Refuse to install releases newer than a cooldown window so freshly published (possibly compromised) versions are skipped.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#set-minimal-release-age`,id:`minimum-release-age`,severity:`warn`,title:`Set a minimum release age`}),{pnpmWorkspace:Gt}=D,Kt=e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&Object.keys(e).length>0,qt={bindings:{pnpm:{check(e,t){let n=w(t,[`namedRegistries`]);return Kt(n)?{actual:n,message:"Review `namedRegistries` in pnpm-workspace.yaml. Custom registry mappings can redirect package resolution away from the public registry, enabling dependency confusion attacks.",state:`violation`}:{state:`ok`}},docs:`https://pnpm.io/settings#namedregistries`,file:Gt,fix(){return[{file:Gt,message:`Verify each named registry points to a trusted source and that scoped packages cannot be hijacked via a public registry of the same name.`,op:`note`}]},fixKind:`advisory`}},description:`Flag named registry mappings that redirect package resolution to custom registries, which may enable dependency confusion attacks.`,docs:`https://pnpm.io/settings#namedregistries`,id:`named-registries`,severity:`info`,title:`Review named registry mappings`},{aubeWorkspace:Jt}=D,Yt=A({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,file:Jt,keyPath:[`paranoid`],message:"Set `paranoid: true` in aube-workspace.yaml to enable the strict-security bundle (trustPolicy, jailBuilds, minimumReleaseAgeStrict, strictStoreIntegrity, strictDepBuilds, advisoryCheck).",value:!0}},description:`Activate the strict-security setting bundle that forces trustPolicy, jailBuilds, minimumReleaseAgeStrict, strictStoreIntegrity, strictDepBuilds, and advisoryCheck on in one switch.`,docs:`https://aube.jdx.dev/security.html`,id:`paranoid-mode`,severity:`info`,title:`Enable paranoid security mode`}),{pnpmWorkspace:Xt}=D,Zt=e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&Object.keys(e).length>0,Qt={bindings:{pnpm:{check(e,t){let n=w(t,[`patchedDependencies`]);return Zt(n)?{actual:n,message:"Review `patchedDependencies` in pnpm-workspace.yaml. Patches modify dependency source code directly and bypass registry integrity checks.",state:`violation`}:{state:`ok`}},docs:`https://pnpm.io/settings#patcheddependencies`,file:Xt,fix(){return[{file:Xt,message:"Review each entry in `patchedDependencies` — verify patches address a known issue and have not been tampered with.",op:`note`}]},fixKind:`advisory`}},description:`Flag patched dependencies whose source code is modified by local patch files, bypassing registry integrity verification.`,docs:`https://pnpm.io/settings#patcheddependencies`,id:`patched-dependencies`,severity:`info`,title:`Review patched dependencies`},{npmrc:$t,pnpmWorkspace:en,yarnrc:tn,bunfig:nn,denoJson:rn}=D,an=e=>!Array.isArray(e),on=e=>(e.split(`-`,1)[0]??e).split(`.`).some(e=>e===`x`||e===`X`||e===`*`),sn=/[\^~]|>=|<=|<|>|\|\||\s/u,cn=e=>e===``||e===`*`||sn.test(e)||on(e),ln=e=>{if(e.startsWith(`http://`)||e.startsWith(`https://`))return!1;let t=e.lastIndexOf(`@`);return t<=0?!1:cn(e.slice(t+1))},un=e=>{let t=[];for(let[n,r]of Object.entries(e))typeof r==`string`&&ln(r)&&t.push(`${n}=${r}`);return t},dn=e=>{let t=e.slice(0,3).join(`, `),n=``;return e.length>3&&(n=` (and ${e.length-3} more)`),{message:`${e.length} deno imports use semver ranges: ${t}${n}. Use \`deno add --save-exact\` or pin manually.`,state:`violation`}},fn={state:`ok`},pn=e=>{let t=w(e,[`imports`]);if(!(typeof t!=`object`||!t||!an(t)))return t},mn={check(e,t){let n=pn(t);if(!n)return fn;let r=un(n);return r.length===0?fn:dn(r)},docs:`https://docs.deno.com/runtime/reference/cli/add/`,file:rn,fix(){return[{file:rn,message:"Run `deno add --save-exact <pkg>` for each ranged import in deno.json, or rewrite the `imports` entries to use exact versions.",op:`note`}]},fixKind:`advisory`},hn=O(A({bindings:{bun:{docs:`https://bun.com/docs/runtime/bunfig#install-exact`,file:nn,keyPath:[`install`,`exact`],message:"Set `exact = true` under [install] in bunfig.toml to pin exact versions.",value:!0,versionNote:{configAvailableSince:`bun 0.6.10`}},npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#save-exact`,extraFix:[{keyPath:[`save-prefix`],value:``}],file:$t,keyPath:[`save-exact`],message:"Set `save-exact=true` (and `save-prefix=`) in .npmrc to pin exact versions.",value:!0},pnpm:{docs:`https://pnpm.io/settings#saveprefix`,file:en,keyPath:[`savePrefix`],message:"Set `savePrefix: ''` in pnpm-workspace.yaml to pin exact versions.",value:``},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#defaultSemverRangePrefix`,file:tn,keyPath:[`defaultSemverRangePrefix`],message:"Set `defaultSemverRangePrefix: ''` in .yarnrc.yml to pin exact versions.",value:``,versionNote:{configAvailableSince:`yarn 2.0.0`}}},description:`Semver ranges (^, ~) auto-adopt new releases, including compromised ones. Save exact versions by default.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#1-pin-dependency-versions`,id:`pin-exact-versions`,severity:`error`,title:`Pin exact dependency versions`}),{deno:mn}),{npmrc:gn,yarnrc:_n}=D,Y={file:gn,keyPath:[`provenance`],message:"Set `provenance=true` (and publish from CI) to attest releases.",value:!0},vn=A({applies:U,bindings:{bun:Object.assign({},Y,{docs:`https://github.com/oven-sh/bun/issues/15601`,message:"Set `provenance=true` in .npmrc and publish via `bunx npm publish` from CI — `bun publish` does not emit provenance attestations natively (tracking: oven-sh/bun#15601)."}),npm:Object.assign({},Y,{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#provenance`,versionNote:{configAvailableSince:`npm 9.5.0`}}),pnpm:Object.assign({},Y,{docs:`https://pnpm.io/cli/publish`}),yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#npmPublishProvenance`,file:_n,keyPath:[`npmPublishProvenance`],message:"Set `npmPublishProvenance: true` (and publish from CI) to attest releases.",value:!0}},description:`Provenance statements (via Sigstore) tie a release to its source and build, letting consumers verify it was not tampered with.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#10-generate-provenance-statements`,id:`provenance`,severity:`warn`,title:`Publish with provenance`}),{packageJson:yn}=D,X={check(e){if(!U(e))return{state:`na`};let t;return e.packageJson&&e.packageJson.publishConfig&&({access:t}=e.packageJson.publishConfig),t===`public`||t===`restricted`?{state:`ok`}:{actual:t,message:"Set `publishConfig.access` in package.json to `public` or `restricted` to declare publish scope explicitly.",state:`violation`}},docs:`https://docs.npmjs.com/cli/v11/configuring-npm/package-json#publishconfig`,file:yn,fix(){return[{file:yn,message:'Add `"publishConfig": { "access": "public" }` (or `"restricted"`) to package.json so publishes never default unexpectedly.',op:`note`}]},fixKind:`advisory`},bn={bindings:{aube:X,bun:X,npm:X,pnpm:X,yarn:X},description:"Set `publishConfig.access` so a misconfigured scope or registry never accidentally publishes an internal package publicly.",docs:`https://github.com/bodadotsh/npm-security-best-practices#for-maintainers`,id:`publish-access`,severity:`info`,title:`Declare publish access explicitly`},{pnpmWorkspace:xn}=D,Sn={bindings:{pnpm:{check(e,t){let n=w(t,[`useRunningStoreServer`]);return n===!0?{actual:n,message:"Review `useRunningStoreServer` in pnpm-workspace.yaml. Delegating store operations to an external process introduces a trust boundary — the server process can serve tampered packages.",state:`violation`}:{state:`ok`}},docs:`https://pnpm.io/settings#userunningStoreserver`,file:xn,fix(){return[{file:xn,message:`Verify the store server is running in a trusted environment and that its communication channel is not exposed to untrusted networks.`,op:`note`}]},fixKind:`advisory`}},description:`Flag use of an external store server process, which introduces a trust boundary where tampered packages could be served.`,docs:`https://pnpm.io/settings#userunningStoreserver`,id:`store-server`,severity:`info`,title:`Review store server usage`},{npmrc:Cn}=D,wn=A({bindings:{npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-allow-scripts`,file:Cn,keyPath:[`strict-allow-scripts`],message:"Set `strict-allow-scripts=true` in .npmrc to block packages with unapproved install scripts.",value:!0}},description:`Turn install-script policy warnings into hard errors so unapproved lifecycle scripts block installation.`,docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-allow-scripts`,id:`strict-allow-scripts`,severity:`warn`,title:`Require explicit script allow-listing`}),{aubeWorkspace:Tn}=D,En=A({bindings:{aube:{docs:`https://aube.jdx.dev/settings/`,file:Tn,keyPath:[`minimumReleaseAgeStrict`],message:"Set `minimumReleaseAgeStrict: true` in aube-workspace.yaml to make the release age gate a hard block.",value:!0}},description:`Make the minimum release age gate a hard failure instead of falling back to the next-oldest satisfying version.`,docs:`https://aube.jdx.dev/security.html`,id:`strict-release-age`,severity:`info`,title:`Enforce strict release age gate`}),{aubeWorkspace:Dn}=D,On=A({bindings:{aube:{docs:`https://aube.jdx.dev/settings/`,file:Dn,keyPath:[`strictStoreIntegrity`],message:"Set `strictStoreIntegrity: true` in aube-workspace.yaml to refuse tarballs that lack integrity metadata.",value:!0}},description:`Refuse to import tarballs from the registry when the packument lacks a dist.integrity field, preventing silent integrity bypass.`,docs:`https://aube.jdx.dev/security.html`,id:`strict-store-integrity`,severity:`warn`,title:`Require tarball integrity metadata`}),{aubeWorkspace:kn,pnpmWorkspace:An}=D,jn={"advisory-check":ze,"approved-git-repos":Ve,"audit-suppression":Ge,"block-auto-install":qe,"block-exotic-subdeps":Ze,"bun-security-scanner":$e,"checksum-verification":tt,"commit-lockfile":rt,"dependency-overrides":ct,"disable-lifecycle-scripts":_t,"enforce-strict-ssl":St,"files-field":Tt,"frozen-lockfile":Mt,"frozen-store":Nt,"hardened-mode":Ft,"minimum-release-age":Wt,"named-registries":qt,"paranoid-mode":Yt,"patched-dependencies":Qt,"pin-exact-versions":hn,provenance:vn,"publish-access":bn,"store-server":Sn,"strict-allow-scripts":wn,"strict-release-age":En,"strict-store-integrity":On,"trust-policy":A({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,file:kn,keyPath:[`trustPolicy`],message:"Set `trustPolicy: no-downgrade` in aube-workspace.yaml to block packages whose trust level has decreased.",value:`no-downgrade`},pnpm:{docs:`https://pnpm.io/settings#trustpolicy`,file:An,keyPath:[`trustPolicy`],message:"Set `trustPolicy: no-downgrade` in pnpm-workspace.yaml to block packages whose trust level has decreased.",value:`no-downgrade`,versionNote:{configAvailableSince:`pnpm 10.21.0`}}},description:`Fail installation when a package trust level has decreased compared to previous releases, catching publisher credential downgrades.`,docs:`https://pnpm.io/settings#trustpolicy`,id:`trust-policy`,severity:`warn`,title:`Enforce trust policy on dependency updates`})},Z=Object.values(jn),Mn=(e,t)=>{let n=new Set,r=new Set;for(let i of e)(t.has(i.id)||r.has(i.id))&&n.add(i.id),r.add(i.id);return[...n]},Nn=(e,t)=>{let n=[];for(let r of Object.keys(e))t.has(r)||n.push(r);return n},Pn=(e,t,n=[])=>{let r=new Set(t.map(e=>e.id)),i=e.customRules??[],a=Mn(i,r),o=new Set(r);for(let e of n)o.add(e);for(let e of i)o.add(e.id);return{duplicates:a,unknown:Nn(e.rules??{},o)}},Fn=e=>{let t=``;throw e.length>1&&(t=`s`),new p(`siro.config: unknown rule id${t} in rules: ${e.map(e=>`'${e}'`).join(`, `)}`)},In=(e,t)=>{if(e===void 0)return;let{unknown:n}=Pn(e,Z,(t??[]).map(e=>e.id));n.length>0&&Fn(n)},[Q]=[],Ln=i.fallback(i.optional(i.string()),Q),Rn=i.looseObject({files:i.fallback(i.optional(i.array(i.string())),Q),name:Ln,packageManager:Ln,private:i.optional(i.fallback(i.boolean(),!0)),publishConfig:i.fallback(i.optional(i.looseObject({access:i.fallback(i.optional(i.union([i.literal(`public`),i.literal(`restricted`)])),Q)})),Q),trustedDependencies:i.fallback(i.optional(i.array(i.string())),Q)}),zn=e=>{let t=i.safeParse(Rn,e);if(t.success)return t.output},$={exists(e){try{return a(e,o.F_OK),!0}catch(e){if(x(e)&&e.code===`ENOENT`)return!1;throw e}},readText(e){try{return s(e,`utf8`)}catch(e){if(x(e)&&e.code===`ENOENT`)return;throw e}}},Bn=(e,t)=>u(c.join(e,t)),Vn=e=>{try{return JSON.parse(e)}catch(e){let t=String(e);throw e instanceof Error&&(t=e.message),new p(`package.json: invalid JSON — ${t}`)}},Hn=e=>zn(Vn(e.trim())),Un=(e,t=$)=>{let n=n=>t.readText(Bn(e,n)),r=n=>t.exists(Bn(e,n)),i=n(d(`package.json`)),a;return i!==void 0&&(a=Hn(i)),{exists:r,packageJson:a,readText:n,root:e}},Wn=[`siro.config.ts`,`siro.config.mjs`,`siro.config.js`],Gn=i.strictObject({customRules:i.optional(i.array(i.custom(()=>!0))),pms:i.optional(i.pipe(i.array(i.picklist(g)),i.minLength(1,`must not be empty (omit the key to auto-detect, or list at least one PM)`))),reporters:i.optional(i.array(i.custom(y,`must be a { name, format } reporter`))),rules:i.optional(i.record(i.string(),i.union([i.picklist(_),i.literal(`off`)])))},`unknown config key (check for a typo)`),Kn=e=>e instanceof Error?e.message:String(e),qn=e=>e.map(e=>{let t=(e.path??[]).map(e=>e.key).filter(e=>typeof e==`string`||typeof e==`number`).join(`.`);return t?`${t}: ${e.message}`:e.message}).join(`; `),Jn=(e,t,n)=>e.import(t).catch(e=>{throw new p(`Failed to load ${n}: ${Kn(e)}`)}),Yn=e=>typeof e==`object`&&e&&`default`in e?e.default:e,Xn=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e)){let n=typeof e;throw Array.isArray(e)&&(n=`an array`),new p(`${t} must export a config object (got ${n}).`)}return e},Zn=(e,t)=>{let n=i.safeParse(Gn,e);if(!n.success)throw new p(`${t}: ${qn(n.issues)}`);return n.output},Qn=(e,t)=>{let{duplicates:n}=Pn(e,Z);if(n.length>0){let e=``;throw n.length>1&&(e=`s`),new p(`${t}: duplicate customRule id${e}: ${n.map(e=>`'${e}'`).join(`, `)}. Pick a unique id (or use 'rules' to override an existing rule's severity).`)}},$n=(e,t,n)=>Jn(e,t,n).then(e=>{let t=Zn(Xn(Yn(e),n),n);return Qn(t,n),t}),er=(e,t=$)=>{let n=l(e,{moduleCache:!1}),r=Wn.find(n=>t.exists(u(c.join(e,n))));return r===void 0?Promise.resolve(void 0):$n(n,u(c.join(e,r)),r)},tr=(e,t)=>{let n=new Set,r=new Set;for(let i of e)(t.has(i.id)||n.has(i.id))&&r.add(i.id),n.add(i.id);return r},nr=e=>{let t=[...e],n=`rule id collides`;throw t.length>1&&(n=`rule ids collide`),new p(`customRules: ${n} with builtin, config, or another programmatic rule: ${t.map(e=>`'${e}'`).join(`, `)}. Pick a unique id (or use 'rules' to override an existing rule's severity).`)},rr=(e,t,n)=>{if(t===void 0||t.length===0)return e;let r=tr(t,new Set([...e.map(e=>e.id),...(n??[]).map(e=>e.id)]));return r.size>0&&nr(r),[...e,...t]},ir=(e,t,n)=>{for(let r of e){let e=n.get(r);if(e!==void 0)throw TypeError(`detection signal '${r}' is claimed by both '${e}' and '${t}'`);n.set(r,t)}},ar=(()=>{let e=new Map,t=new Map;for(let n of g){let{lockfiles:r,configs:i}=L[n],a=[...r,...i];ir(a,n,t),e.set(n,a)}return e})(),or=(e,t)=>{let n;if(e.packageJson&&(n=e.packageJson.packageManager),typeof n==`string`){let e=ae(n);e!==void 0&&t.add(e)}},sr=e=>{let t=new Set;or(e,t);for(let n of g){let r=ar.get(n);r&&r.some(t=>e.exists(d(t)))&&t.add(n)}return g.filter(e=>t.has(e))},cr=(e,t,n)=>{if(n){if(t.pmOverride)throw new m(`--pm ${t.pmOverride} is not in siro.config.ts pms (${n.join(`, `)}). Adjust the config or the flag.`);let r=`No package manager detected, and siro.config.ts restricts pms to ${n.join(`, `)}. Adjust the config or remove the restriction.`;throw e.length>0&&(r=`Detected PMs (${e.join(`, `)}) do not match siro.config.ts pms (${n.join(`, `)}). Adjust the config or remove the restriction.`),new m(r)}throw new m(`No package manager detected. Pass --pm <${g.join(`|`)}> to be explicit.`)},lr=(e,t)=>{let n=sr(e);t.pmOverride&&(n=[t.pmOverride]);let{allowed:r}=t,i=n;if(r){let e=new Set(r);i=n.filter(t=>e.has(t))}return{detected:n,pms:i}},ur=(e,t)=>{let{detected:n,pms:r}=lr(e,t);if(r.length>0)return r;throw cr(n,t,t.allowed)},dr=(e,t)=>{let n;e&&(n=e.customRules);let r=rr(Z,t,n);return In(e,t),Ae(r,e)},fr=(e,t)=>{let{cwd:n,fs:r,pm:i,customRules:a}=e,o=Un(n,r),s;return t&&(s=t.pms),{ctx:o,pms:ur(o,{allowed:s,pmOverride:i}),ruleSet:dr(t,a),userConfig:t}},pr=e=>er(e.cwd,e.fs).then(t=>fr(e,t)),mr=e=>{let{ctx:t,pms:n,ruleSet:r,parseConfig:i,onViolation:a}=e;for(let e of r)for(let r of n){let n=e.bindings[r];if(n!==void 0){let{parsed:o}=i(t,n.file),s=n.check(t,o);s.state===`violation`&&a({binding:n,pm:r,rule:e,status:s})}}},hr=Object.freeze({parsed:Object.freeze({})}),gr=(e,t,n)=>{let r=e.readText(t.path)??``,i={parsed:h(t.path,()=>n.codecFor(t.kind).parse(r))};return n.cache.set(n.cacheKey,i),i},_r=e=>{let t=new Map;return(n,r)=>{if(r.kind===`fileGlob`)return hr;let i=`${r.kind}:${r.path}`,a=t.get(i);return a===void 0?gr(n,r,{cache:t,cacheKey:i,codecFor:e}):a}},vr=(e,t,n)=>e.severity??t.severity??n.severity,yr=e=>{if(e!==void 0&&e.length>0)return e},br=(e,t)=>{let{rule:n,pm:r,binding:i,status:a}=e,o=vr(a,i,n),s=yr(a.manualSteps),c;i.file.kind!==`fileGlob`&&(c=i.file.path);let l=[];return s===void 0&&(l=i.fix(t)),{actual:a.actual,docs:i.docs??n.docs,expected:a.expected,file:c,fix:l,fixable:i.fixKind===`auto`&&s===void 0,manualSteps:s,message:a.message,pm:r,ruleId:n.id,severity:o}},xr=e=>{let{ctx:t,pms:n,ruleSet:r,codecFor:i}=e,a=[],o={error:0,info:0,warn:0};return mr({ctx:t,onViolation:e=>{let n=br(e,t);a.push(n),o[n.severity]+=1},parseConfig:_r(i),pms:n,ruleSet:r}),{findings:a,summary:o}},Sr=(e,t)=>{if(e===void 0)return t.get(`pretty`);if(typeof e==`string`)return t.get(e);if(!y(e))throw new m(`The 'reporter' option must be a name or a { name, format } object.`);return e},Cr=e=>{for(let t of e??[])if(!y(t))throw new m(`A reporter in 'reporters' needs a string 'name' and a 'format' function.`)},wr=(e,t)=>{let n=Sr(e,t);if(n===void 0)throw new m(`Unknown reporter: ${String(e)} (available: ${t.list().join(`, `)})`);return n},Tr=e=>{let t=xr({codecFor:Te,ctx:e.ctx,pms:e.pms,ruleSet:e.ruleSet}),n=e.options.severity??`info`,r=e.options.severity??`error`;return e.reporter.format(be(t,n),e.io),S(t,r)},Er=(e,t)=>pr({customRules:e.customRules,cwd:e.cwd,fs:e.fs,pm:e.pm}).then(({userConfig:n,ctx:r,pms:i,ruleSet:a})=>{Cr(e.reporters);let o=[];n&&n.reporters&&(o=n.reporters);let s=ve([...e.reporters??[],...o]);return Tr({ctx:r,io:t,options:e,pms:i,reporter:wr(e.reporter,s),ruleSet:a})}),Dr={stderr:e=>process.stderr.write(`${e}\n`),stdout:e=>process.stdout.write(`${e}\n`)};export{d as A,v as C,p as D,m as E,f as O,_ as S,h as T,ue as _,L as a,y as b,A as c,S as d,be as f,he as g,ve as h,$ as i,u as k,D as l,_e as m,Er as n,O as o,x as p,sr as r,k as s,Dr as t,w as u,le as v,re as w,g as x,ce as y};
|
|
4
|
+
//# sourceMappingURL=node-io-C_ERSDGJ.mjs.map
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pho9ubenaa/siro",
|
|
3
|
+
"version": "0.1.0-rc",
|
|
4
|
+
"description": "Security best-practices linter for the npm ecosystem (npm, pnpm, deno, bun, yarn, aube).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"aube",
|
|
7
|
+
"bun",
|
|
8
|
+
"cli",
|
|
9
|
+
"deno",
|
|
10
|
+
"hardening",
|
|
11
|
+
"lint",
|
|
12
|
+
"linter",
|
|
13
|
+
"npm",
|
|
14
|
+
"pnpm",
|
|
15
|
+
"security",
|
|
16
|
+
"supply-chain",
|
|
17
|
+
"yarn"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/pHo9UBenaA/siro#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/pHo9UBenaA/siro/issues"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/pHo9UBenaA/siro.git"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"siro": "./dist/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"!dist/*.map",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"CHANGELOG.md"
|
|
37
|
+
],
|
|
38
|
+
"type": "module",
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./dist/index.d.mts",
|
|
42
|
+
"import": "./dist/index.mjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"cac": "7.0.0",
|
|
50
|
+
"ini": "7.0.0",
|
|
51
|
+
"jiti": "2.7.0",
|
|
52
|
+
"picocolors": "1.1.1",
|
|
53
|
+
"smol-toml": "1.6.1",
|
|
54
|
+
"valibot": "1.4.1",
|
|
55
|
+
"yaml": "2.9.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@commitlint/cli": "21.0.2",
|
|
59
|
+
"@commitlint/config-conventional": "21.0.2",
|
|
60
|
+
"@types/ini": "4.1.1",
|
|
61
|
+
"@types/node": "25.9.1",
|
|
62
|
+
"@vitest/coverage-v8": "4.1.7",
|
|
63
|
+
"knip": "6.15.0",
|
|
64
|
+
"memfs": "4.57.3",
|
|
65
|
+
"oxfmt": "0.53.0",
|
|
66
|
+
"oxlint": "1.68.0",
|
|
67
|
+
"tinybench": "6.0.2",
|
|
68
|
+
"tsdown": "0.22.1",
|
|
69
|
+
"typedoc": "0.28.19",
|
|
70
|
+
"typedoc-plugin-markdown": "4.11.0",
|
|
71
|
+
"typescript": "6.0.3",
|
|
72
|
+
"unrun": "0.3.1",
|
|
73
|
+
"vitest": "4.1.7"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=20"
|
|
77
|
+
},
|
|
78
|
+
"scripts": {
|
|
79
|
+
"bench": "node ./scripts/bench/run.mjs",
|
|
80
|
+
"build": "node ./scripts/gen/version.mjs && tsdown && node ./scripts/gen/bin-wrapper.mjs",
|
|
81
|
+
"check": "oxlint --deny-warnings && oxfmt --check",
|
|
82
|
+
"format": "oxfmt --write",
|
|
83
|
+
"gen": "pnpm gen:version && pnpm gen:comparison && pnpm gen:rules && pnpm gen:api",
|
|
84
|
+
"gen:api": "typedoc",
|
|
85
|
+
"gen:comparison": "node ./scripts/gen/comparison.mjs",
|
|
86
|
+
"gen:rule": "node ./scripts/gen/rule.mjs",
|
|
87
|
+
"gen:rules": "node ./scripts/gen/rules.mjs",
|
|
88
|
+
"gen:version": "node ./scripts/gen/version.mjs",
|
|
89
|
+
"knip": "knip",
|
|
90
|
+
"test": "vitest run",
|
|
91
|
+
"test:coverage": "vitest run --coverage",
|
|
92
|
+
"test:watch": "vitest",
|
|
93
|
+
"typecheck": "tsc --noEmit && node ./scripts/check/layers.mjs"
|
|
94
|
+
}
|
|
95
|
+
}
|