@pho9ubenaa/siro 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -30
- package/README.md +47 -36
- package/dist/cli.js +2 -3
- package/dist/cli.mjs +134 -296
- package/dist/index.d.mts +143 -414
- package/dist/index.mjs +3 -4
- package/dist/node-io-BdyNbvzO.mjs +2211 -0
- package/package.json +14 -17
- package/dist/node-io-Cb8nixgE.mjs +0 -2509
package/dist/index.d.mts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import * as vb from "valibot";
|
|
2
2
|
|
|
3
|
+
//#region src/domain/entities/config-value.d.ts
|
|
4
|
+
declare const CODEC_KINDS: readonly ["json", "npmrc", "toml", "yaml"];
|
|
5
|
+
type CodecKind = (typeof CODEC_KINDS)[number];
|
|
6
|
+
/** Scalar value that can be written back to a config file. */
|
|
7
|
+
type ConfigValue = string | number | boolean;
|
|
8
|
+
/** Parsed keys are unvalidated; each rule narrows the values it reads. */
|
|
9
|
+
interface ParsedConfig {
|
|
10
|
+
readonly [key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** A (possibly nested) key path, guaranteed to have at least one segment. */
|
|
13
|
+
type KeyPath = readonly [string, ...string[]];
|
|
14
|
+
/** Parser values may include nested arrays and TOML dates. */
|
|
15
|
+
type ConfigReadValue = unknown;
|
|
16
|
+
/** Read an own key path; absent or non-mapping parents yield undefined. */
|
|
17
|
+
declare const getByPath: (config: ParsedConfig, keyPath: KeyPath) => ConfigReadValue;
|
|
18
|
+
//#endregion
|
|
3
19
|
//#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
20
|
declare const AbsPathBrand: unique symbol;
|
|
17
21
|
declare const RelPathBrand: unique symbol;
|
|
18
22
|
type AbsPath = string & {
|
|
@@ -21,10 +25,8 @@ type AbsPath = string & {
|
|
|
21
25
|
type RelPath = string & {
|
|
22
26
|
readonly [RelPathBrand]: true;
|
|
23
27
|
};
|
|
24
|
-
|
|
25
|
-
declare const
|
|
26
|
-
/** Tag a string as repo-root-relative. The caller vouches for the shape. */
|
|
27
|
-
declare const asRelPath: (path: string) => RelPath;
|
|
28
|
+
declare const asAbsPath: (value: string) => AbsPath;
|
|
29
|
+
declare const asRelPath: (value: string) => RelPath;
|
|
28
30
|
//#endregion
|
|
29
31
|
//#region src/domain/ports/file-system.d.ts
|
|
30
32
|
/** IO boundary: swap in memfs (or any other backend) for tests. */
|
|
@@ -33,53 +35,6 @@ interface FileSystem {
|
|
|
33
35
|
exists: (path: AbsPath) => boolean;
|
|
34
36
|
}
|
|
35
37
|
//#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
38
|
//#region src/domain/entities/pms.d.ts
|
|
84
39
|
/**
|
|
85
40
|
* Single source of truth for package managers and severities.
|
|
@@ -93,134 +48,108 @@ type Severity = (typeof SEVERITIES)[number];
|
|
|
93
48
|
declare const isPM: (value: string) => value is PM;
|
|
94
49
|
declare const isSeverity: (value: string) => value is Severity;
|
|
95
50
|
//#endregion
|
|
51
|
+
//#region src/domain/entities/project-type.d.ts
|
|
52
|
+
declare const PROJECT_TYPES: readonly ["application", "package"];
|
|
53
|
+
type ProjectType = (typeof PROJECT_TYPES)[number];
|
|
54
|
+
//#endregion
|
|
96
55
|
//#region src/domain/schemas/package-json.d.ts
|
|
97
56
|
declare const PackageJsonSchema: vb.LooseObjectSchema<{
|
|
98
|
-
readonly files: vb.
|
|
99
|
-
readonly name: vb.
|
|
100
|
-
readonly packageManager: vb.
|
|
101
|
-
readonly private: vb.OptionalSchema<vb.
|
|
102
|
-
readonly publishConfig: vb.
|
|
103
|
-
readonly access: vb.
|
|
104
|
-
}, undefined>, undefined
|
|
105
|
-
readonly trustedDependencies: vb.
|
|
57
|
+
readonly files: vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>;
|
|
58
|
+
readonly name: vb.OptionalSchema<vb.StringSchema<undefined>, undefined>;
|
|
59
|
+
readonly packageManager: vb.OptionalSchema<vb.StringSchema<undefined>, undefined>;
|
|
60
|
+
readonly private: vb.OptionalSchema<vb.BooleanSchema<undefined>, undefined>;
|
|
61
|
+
readonly publishConfig: vb.OptionalSchema<vb.LooseObjectSchema<{
|
|
62
|
+
readonly access: vb.OptionalSchema<vb.PicklistSchema<["public", "restricted", "private"], undefined>, undefined>;
|
|
63
|
+
}, undefined>, undefined>;
|
|
64
|
+
readonly trustedDependencies: vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>;
|
|
106
65
|
}, undefined>;
|
|
107
66
|
type PackageJson = vb.InferOutput<typeof PackageJsonSchema>;
|
|
108
67
|
//#endregion
|
|
109
68
|
//#region src/domain/ports/repo-context.d.ts
|
|
110
|
-
/** Read-only view of a repository, passed to every rule's `check
|
|
69
|
+
/** Read-only view of a repository, passed to every rule's `check`. */
|
|
111
70
|
interface RepoContext {
|
|
112
71
|
readonly root: AbsPath;
|
|
113
72
|
exists: (relPath: RelPath) => boolean;
|
|
114
73
|
readText: (relPath: RelPath) => string | undefined;
|
|
115
74
|
readonly packageJson: PackageJson | undefined;
|
|
75
|
+
readonly projectType?: ProjectType;
|
|
76
|
+
}
|
|
77
|
+
/** Settings read during one evaluation share its parser and cache. */
|
|
78
|
+
interface RuleContext extends RepoContext {
|
|
79
|
+
readConfig: (file: ConfigFileRef) => ParsedConfig;
|
|
116
80
|
}
|
|
117
81
|
//#endregion
|
|
118
82
|
//#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 = {
|
|
83
|
+
interface ConfigFileRef {
|
|
126
84
|
readonly kind: CodecKind;
|
|
127
85
|
readonly path: RelPath;
|
|
86
|
+
}
|
|
87
|
+
interface SetKeyOperation {
|
|
88
|
+
readonly op: 'setKey';
|
|
89
|
+
readonly file: ConfigFileRef;
|
|
90
|
+
readonly keyPath: KeyPath;
|
|
91
|
+
readonly value: ConfigValue;
|
|
92
|
+
}
|
|
93
|
+
/** A check chooses one remedy for the state it observed. siro does not apply it. */
|
|
94
|
+
type Remediation = {
|
|
95
|
+
readonly kind: 'automatic';
|
|
96
|
+
readonly operations: readonly [SetKeyOperation, ...SetKeyOperation[]];
|
|
97
|
+
readonly steps?: never;
|
|
128
98
|
} | {
|
|
129
|
-
readonly kind: '
|
|
130
|
-
readonly
|
|
99
|
+
readonly kind: 'manual';
|
|
100
|
+
readonly steps: readonly [string, ...string[]];
|
|
101
|
+
readonly operations?: never;
|
|
131
102
|
};
|
|
132
|
-
/** Result of evaluating a single rule binding against a repo. */
|
|
133
103
|
type CheckStatus = {
|
|
134
104
|
readonly state: 'ok';
|
|
105
|
+
} | {
|
|
106
|
+
readonly state: 'na';
|
|
135
107
|
} | {
|
|
136
108
|
readonly state: 'violation';
|
|
137
|
-
readonly message: string;
|
|
109
|
+
readonly message: string; /** Override the primary file when the violation concerns another input. */
|
|
110
|
+
readonly file?: RelPath;
|
|
138
111
|
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
|
-
*/
|
|
112
|
+
readonly actual?: ConfigReadValue; /** User configuration takes precedence over this per-result severity. */
|
|
147
113
|
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;
|
|
114
|
+
readonly remediation?: Remediation;
|
|
174
115
|
};
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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[];
|
|
116
|
+
/** Display-only package-manager version metadata. */
|
|
117
|
+
interface VersionNote {
|
|
118
|
+
readonly configAvailableSince?: string;
|
|
119
|
+
readonly defaultSafeSince?: string;
|
|
120
|
+
readonly note?: string;
|
|
202
121
|
}
|
|
203
|
-
interface
|
|
204
|
-
readonly file
|
|
205
|
-
readonly fixKind: 'advisory';
|
|
206
|
-
/** Official package-manager doc URL for the setting this binding describes. */
|
|
122
|
+
interface RuleBinding {
|
|
123
|
+
readonly file?: ConfigFileRef;
|
|
207
124
|
readonly docs?: string;
|
|
208
|
-
/** See {@link AutoRuleBinding.severity}. */
|
|
209
125
|
readonly severity?: Severity;
|
|
210
|
-
|
|
211
|
-
|
|
126
|
+
readonly versionNote?: VersionNote;
|
|
127
|
+
check: (ctx: RuleContext, config: ParsedConfig) => CheckStatus;
|
|
212
128
|
}
|
|
213
|
-
type RuleBinding = AutoRuleBinding | AdvisoryRuleBinding;
|
|
214
129
|
/** A package-manager-agnostic security intent, realized per PM via `bindings`. */
|
|
215
|
-
interface Rule {
|
|
216
|
-
readonly id:
|
|
130
|
+
interface Rule<Id extends string = string> {
|
|
131
|
+
readonly id: Id;
|
|
217
132
|
readonly title: string;
|
|
218
133
|
readonly description: string;
|
|
219
134
|
readonly severity: Severity;
|
|
220
135
|
readonly docs?: string;
|
|
136
|
+
/** Project types this rule applies to; omission means both types. */
|
|
137
|
+
readonly projectTypes?: readonly ProjectType[];
|
|
221
138
|
/** Absence of a PM key means the rule does not apply (N/A) to that PM. */
|
|
222
139
|
readonly bindings: Partial<Record<PM, RuleBinding>>;
|
|
223
140
|
}
|
|
141
|
+
declare const defineRule: <const Id extends string>(rule: Rule<Id>) => Rule<Id>;
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/domain/builtin-rules.d.ts
|
|
144
|
+
declare const rules: readonly [Rule<"advisory-check">, Rule<"approved-git-repos">, Rule<"audit-suppression">, Rule<"block-auto-install">, Rule<"block-exotic-subdeps">, Rule<"bun-security-scanner">, Rule<"checksum-verification">, Rule<"commit-lockfile">, Rule<"dependency-overrides">, Rule<"disable-lifecycle-scripts">, Rule<"enforce-strict-ssl">, Rule<"files-field">, Rule<"frozen-lockfile">, Rule<"frozen-store">, Rule<"hardened-mode">, Rule<"minimum-release-age">, Rule<"named-registries">, Rule<"paranoid-mode">, Rule<"patched-dependencies">, Rule<"pin-exact-versions">, Rule<"provenance">, Rule<"publish-access">, Rule<"store-server">, Rule<"strict-allow-scripts">, Rule<"strict-release-age">, Rule<"strict-store-integrity">, Rule<"trust-policy">];
|
|
145
|
+
type BuiltinRuleId = (typeof rules)[number]['id'];
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/domain/ports/io.d.ts
|
|
148
|
+
/** Output sink for commands; injectable so tests can drive a string buffer. */
|
|
149
|
+
interface IO {
|
|
150
|
+
stdout: (line: string) => void;
|
|
151
|
+
stderr: (line: string) => void;
|
|
152
|
+
}
|
|
224
153
|
//#endregion
|
|
225
154
|
//#region src/domain/entities/lint-result.d.ts
|
|
226
155
|
interface Finding {
|
|
@@ -229,22 +158,7 @@ interface Finding {
|
|
|
229
158
|
readonly severity: Severity;
|
|
230
159
|
readonly message: string;
|
|
231
160
|
readonly file?: string;
|
|
232
|
-
readonly
|
|
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
|
-
*/
|
|
161
|
+
readonly remediation?: Remediation;
|
|
248
162
|
readonly expected?: ConfigValue;
|
|
249
163
|
readonly actual?: ConfigReadValue;
|
|
250
164
|
/** Official PM doc anchor for the setting this finding is about, if any. */
|
|
@@ -256,32 +170,54 @@ interface LintResult {
|
|
|
256
170
|
}
|
|
257
171
|
//#endregion
|
|
258
172
|
//#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
173
|
interface Reporter<Name extends string = string> {
|
|
274
174
|
readonly name: Name;
|
|
275
175
|
format: (result: LintResult, io: IO) => void;
|
|
276
176
|
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/domain/entities/siro-config.d.ts
|
|
179
|
+
/** Per-rule setting. `'off'` disables; a Severity overrides the default level. */
|
|
180
|
+
type RuleSetting = Severity | 'off';
|
|
277
181
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
182
|
+
* User-facing config returned by `defineConfig` in `siro.config.{ts,mjs,js}`.
|
|
183
|
+
*
|
|
184
|
+
* defineConfig({
|
|
185
|
+
* pms: ['npm', 'pnpm'], // restrict detection
|
|
186
|
+
* rules: { provenance: 'off' }, // disable / override severity
|
|
187
|
+
* customRules: [myRule], // extend
|
|
188
|
+
* reporters: [mySarifReporter], // extend
|
|
189
|
+
* })
|
|
283
190
|
*/
|
|
284
|
-
|
|
191
|
+
interface SiroConfig {
|
|
192
|
+
readonly pms?: readonly PM[];
|
|
193
|
+
readonly projectType?: ProjectType;
|
|
194
|
+
readonly rules?: Readonly<Partial<Record<BuiltinRuleId | (string & Record<never, never>), RuleSetting>>>;
|
|
195
|
+
readonly customRules?: readonly Rule[];
|
|
196
|
+
readonly reporters?: readonly Reporter[];
|
|
197
|
+
}
|
|
198
|
+
/** Identity helper for type-checked config files. */
|
|
199
|
+
declare const defineConfig: (config: SiroConfig) => SiroConfig;
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/application/lint.d.ts
|
|
202
|
+
interface LintOptions {
|
|
203
|
+
readonly cwd: AbsPath;
|
|
204
|
+
readonly fs?: FileSystem;
|
|
205
|
+
readonly pm?: PM;
|
|
206
|
+
readonly projectType?: ProjectType;
|
|
207
|
+
/** Explicit configuration; the library never imports files from the target repository. */
|
|
208
|
+
readonly config?: SiroConfig;
|
|
209
|
+
}
|
|
210
|
+
declare const lint: (options: LintOptions) => LintResult;
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/adapters/config-loader.d.ts
|
|
213
|
+
/** Load the first matching config; executable imports always use the real filesystem. */
|
|
214
|
+
declare const loadConfig: (cwd: AbsPath, nodeVersion?: string) => Promise<SiroConfig | undefined>;
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/adapters/node-file-system.d.ts
|
|
217
|
+
declare const nodeFileSystem: FileSystem;
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/adapters/node-io.d.ts
|
|
220
|
+
declare const nodeIO: IO;
|
|
285
221
|
//#endregion
|
|
286
222
|
//#region src/adapters/reporters/github.d.ts
|
|
287
223
|
/** Emit GitHub Actions workflow commands (annotations on PRs). */
|
|
@@ -299,60 +235,17 @@ declare const BUILTINS: readonly [Reporter<"pretty">, Reporter<"json">, Reporter
|
|
|
299
235
|
declare const BUILTIN_REPORTER_NAMES: readonly BuiltinReporterName[];
|
|
300
236
|
/** Literal union of every built-in reporter name. */
|
|
301
237
|
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
238
|
//#endregion
|
|
310
239
|
//#region src/application/commands/lint.d.ts
|
|
311
|
-
interface LintOptions {
|
|
312
|
-
|
|
313
|
-
|
|
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 imported 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;
|
|
240
|
+
interface LintCommandOptions extends LintOptions {
|
|
241
|
+
readonly reporter?: string | Reporter;
|
|
242
|
+
readonly severity?: Severity;
|
|
341
243
|
}
|
|
342
|
-
/**
|
|
343
|
-
declare const lintCommand: (options:
|
|
244
|
+
/** Evaluate and report. Executable config loading belongs to the CLI adapter. */
|
|
245
|
+
declare const lintCommand: (options: LintCommandOptions, io: IO) => Promise<number>;
|
|
344
246
|
//#endregion
|
|
345
247
|
//#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
|
-
*/
|
|
248
|
+
/** Known configuration locations and their parsers. */
|
|
356
249
|
declare const CONFIG_FILES: {
|
|
357
250
|
readonly aubeWorkspace: {
|
|
358
251
|
readonly kind: "yaml";
|
|
@@ -384,137 +277,19 @@ declare const CONFIG_FILES: {
|
|
|
384
277
|
};
|
|
385
278
|
};
|
|
386
279
|
//#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
280
|
//#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
281
|
interface RequireConfigKeySpec {
|
|
489
282
|
readonly file: ConfigFileRef;
|
|
490
283
|
readonly keyPath: KeyPath;
|
|
491
|
-
/**
|
|
284
|
+
/** Expected value and proposed replacement; `accept` may allow other values. */
|
|
492
285
|
readonly value: ConfigValue;
|
|
493
286
|
readonly message: string;
|
|
494
287
|
readonly docs?: string;
|
|
495
288
|
readonly severity?: Severity;
|
|
496
289
|
accept?: (actual: unknown) => boolean;
|
|
497
290
|
/**
|
|
498
|
-
*
|
|
499
|
-
*
|
|
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".
|
|
291
|
+
* An omitted value that is safe across every supported version and target
|
|
292
|
+
* environment emits info. Version-dependent defaults retain full severity.
|
|
518
293
|
*/
|
|
519
294
|
readonly documentedDefault?: ConfigValue;
|
|
520
295
|
/**
|
|
@@ -524,82 +299,36 @@ interface RequireConfigKeySpec {
|
|
|
524
299
|
readonly defaultSatisfiedSeverity?: Severity | 'off';
|
|
525
300
|
readonly versionNote?: VersionNote;
|
|
526
301
|
}
|
|
527
|
-
interface RequireConfigKeyOptions {
|
|
528
|
-
readonly id:
|
|
302
|
+
interface RequireConfigKeyOptions<Id extends string = string> {
|
|
303
|
+
readonly id: Id;
|
|
529
304
|
readonly title: string;
|
|
530
305
|
readonly description: string;
|
|
531
306
|
readonly severity: Severity;
|
|
532
307
|
readonly docs?: string;
|
|
308
|
+
readonly projectTypes?: Rule['projectTypes'];
|
|
533
309
|
/** Bindings keyed by PM. PMs absent from this map are treated as N/A. */
|
|
534
310
|
readonly bindings: Partial<Record<PM, RequireConfigKeySpec>>;
|
|
535
311
|
/** Return false to short-circuit `check` as N/A (e.g. private packages). */
|
|
536
312
|
applies?: (ctx: RepoContext) => boolean;
|
|
537
313
|
}
|
|
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;
|
|
314
|
+
declare const overrideBindings: <Id extends string>(rule: Rule<Id>, overrides: Partial<Rule["bindings"]>) => Rule<Id>;
|
|
547
315
|
/** 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[];
|
|
316
|
+
declare const requireConfigKey: <const Id extends string>(options: RequireConfigKeyOptions<Id>) => Rule<Id>;
|
|
552
317
|
//#endregion
|
|
553
|
-
//#region src/
|
|
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
|
-
*/
|
|
318
|
+
//#region src/shared/errors.d.ts
|
|
574
319
|
declare class SiroError extends Error {
|
|
575
|
-
/** Recommended process exit code for this error. */
|
|
576
320
|
readonly exitCode: number;
|
|
577
321
|
constructor(message: string, exitCode: number);
|
|
578
322
|
}
|
|
579
|
-
//#endregion
|
|
580
|
-
//#region src/shared/config-error.d.ts
|
|
581
323
|
declare class ConfigError extends SiroError {
|
|
582
324
|
constructor(message: string);
|
|
583
325
|
}
|
|
584
|
-
//#endregion
|
|
585
|
-
//#region src/shared/usage-error.d.ts
|
|
586
326
|
declare class UsageError extends SiroError {
|
|
587
327
|
constructor(message: string);
|
|
588
328
|
}
|
|
589
329
|
//#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
330
|
//#region src/version.d.ts
|
|
602
|
-
declare const version
|
|
331
|
+
declare const version: string;
|
|
603
332
|
//#endregion
|
|
604
|
-
export { type AbsPath,
|
|
333
|
+
export { type AbsPath, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, CONFIG_FILES, type CheckStatus, ConfigError, type ConfigFileRef, type ConfigReadValue, type ConfigValue, type FileSystem, type Finding, type IO, type KeyPath, type LintCommandOptions, type LintOptions, type LintResult, type PM, PMS, PROJECT_TYPES, type ParsedConfig, type ProjectType, type RelPath, type Remediation, type RepoContext, type Reporter, type RequireConfigKeyOptions, type Rule, type RuleBinding, type RuleContext, type RuleSetting, SEVERITIES, type SetKeyOperation, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
|
|
605
334
|
//# sourceMappingURL=index.d.mts.map
|