@pho9ubenaa/siro 0.6.0 → 0.6.1
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 +19 -0
- package/README.md +8 -2
- package/dist/cli.mjs +44 -10
- package/dist/index.d.mts +20 -1
- package/dist/index.mjs +2 -2
- package/dist/{node-io-CAszdmko.mjs → node-io-BpAHyeBG.mjs} +427 -143
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.1] — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- Add `--no-config` to skip executable repository configuration, and
|
|
6
|
+
`--strict-filesystem` / API `rejectSymlinks` to reject native input-path symlinks.
|
|
7
|
+
Defaults retain automatic config and symlink resolution; neither option is a sandbox.
|
|
8
|
+
- Add finite scan/output budgets, caller overrides (`limits`, CLI `--max-*`) and
|
|
9
|
+
`DEFAULT_SCAN_LIMITS` / `ScanLimits` exports. Oversized/deep inputs now fail with
|
|
10
|
+
exit 2; output overflow exits 70 rather than producing a truncated success report.
|
|
11
|
+
- Omit source excerpts from JSON/YAML/TOML syntax diagnostics. Observed finding
|
|
12
|
+
values are not secret-redacted.
|
|
13
|
+
- Fix argument-limit failures when inspecting large Deno scoped import mappings.
|
|
14
|
+
- Update locked transitive dependencies to brace-expansion 5.0.12, markdown-it 14.3.1
|
|
15
|
+
and fast-uri 3.1.8 for upstream security fixes.
|
|
16
|
+
- Correct first-run safety examples to use `npx` without a global install.
|
|
17
|
+
- Separate build/verification from OIDC staging of the verified artifact, with
|
|
18
|
+
release identity, digest and main-ancestry checks.
|
|
19
|
+
|
|
20
|
+
Node requirements, direct dependency versions and decoded JSON schema 3 remain unchanged.
|
|
21
|
+
|
|
3
22
|
## [0.6.0] — 2026-09-29
|
|
4
23
|
|
|
5
24
|
### Breaking changes and inspection scope
|
package/README.md
CHANGED
|
@@ -33,8 +33,9 @@ Discovery does not imply that every package's installation settings were inspect
|
|
|
33
33
|
siro detects managers from `packageManager`, lockfiles, and configuration files. If it cannot
|
|
34
34
|
detect one, choose it explicitly, for example `npx @pho9ubenaa/siro lint --pm npm`.
|
|
35
35
|
The CLI may download code through `npx` and imports a repository's `siro.config.*` as executable
|
|
36
|
-
code.
|
|
37
|
-
|
|
36
|
+
code by default. For an unfamiliar project, use `npx @pho9ubenaa/siro lint --no-config --strict-filesystem`
|
|
37
|
+
in an isolated environment without credentials. These flags do not sandbox npx or
|
|
38
|
+
extensions; see the [threat model](docs/threat-model.md).
|
|
38
39
|
|
|
39
40
|
## Read findings and add CI
|
|
40
41
|
|
|
@@ -70,6 +71,11 @@ For exclusions and rule overrides, see the [configuration examples](docs/configu
|
|
|
70
71
|
| `--severity <error\|warn\|info>` | Set both the display and CI failure threshold; default failure threshold is `error`. |
|
|
71
72
|
| `--reporter <pretty\|json\|github>` | Choose terminal, JSON, or GitHub Actions output; `--json` is a JSON shortcut. |
|
|
72
73
|
|
|
74
|
+
`--no-config` disables executable configuration; `--strict-filesystem` rejects
|
|
75
|
+
symlink input paths in native data reads. Scans also have finite caller-controlled
|
|
76
|
+
[file, tree, nesting, finding and output budgets](docs/configuration.md#strict-filesystem-and-scan-budgets).
|
|
77
|
+
Overflow fails the check rather than silently skipping inputs.
|
|
78
|
+
|
|
73
79
|
For a walkthrough and deeper reference, use these guides:
|
|
74
80
|
|
|
75
81
|
- [Getting started](docs/getting-started.md) walks through findings and CI; [configuration](docs/configuration.md) covers local PM/version selection, discovery and explicit installation scope, executable config, exit codes, and migration from the removed `--workspaces` flag.
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as
|
|
2
|
+
import { C as isSeverity, D as asAbsPath, E as version, F as SiroError, I as UsageError, M as DEFAULT_SCAN_LIMITS, N as resolveScanLimits, S as isPM, T as SUPPORTED_NODE_RANGE, _ as PROJECT_TYPES, a as DEFAULT_REPORTER_NAME, b as PMS, i as BUILTIN_REPORTER_NAMES, j as isNodeError, k as assertDirectory, l as safeText, m as loadConfig, o as JSON_REPORTER_NAME, r as lintCommand, t as nodeIO, v as isProjectType, w as isSupportedNodeVersion, x as SEVERITIES, y as isStableVersion } from "./node-io-BpAHyeBG.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
@@ -31,6 +31,7 @@ const COMMANDS = ["lint", "check"];
|
|
|
31
31
|
const isCommandName = (value) => COMMANDS.some((cmd) => cmd === value);
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region src/cli/parse-args.ts
|
|
34
|
+
const LIMIT_FLAGS = new Map(Object.keys(DEFAULT_SCAN_LIMITS).map((key) => [key.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`), key]));
|
|
34
35
|
const REPEATABLE_FLAGS = new Set(["exclude", "installation-root"]);
|
|
35
36
|
const VALUE_FLAGS = new Set([
|
|
36
37
|
"pm",
|
|
@@ -38,12 +39,15 @@ const VALUE_FLAGS = new Set([
|
|
|
38
39
|
"project-type",
|
|
39
40
|
"reporter",
|
|
40
41
|
"severity",
|
|
41
|
-
...REPEATABLE_FLAGS
|
|
42
|
+
...REPEATABLE_FLAGS,
|
|
43
|
+
...LIMIT_FLAGS.keys()
|
|
42
44
|
]);
|
|
43
45
|
const BOOLEAN_FLAGS = new Set([
|
|
44
46
|
"help",
|
|
45
47
|
"version",
|
|
46
|
-
"json"
|
|
48
|
+
"json",
|
|
49
|
+
"no-config",
|
|
50
|
+
"strict-filesystem"
|
|
47
51
|
]);
|
|
48
52
|
const parseCommand = (argv) => {
|
|
49
53
|
const { tokens } = parseArgs({
|
|
@@ -78,7 +82,11 @@ const parseCommand = (argv) => {
|
|
|
78
82
|
}
|
|
79
83
|
if (BOOLEAN_FLAGS.has(token.name)) if (token.value !== void 0) error ??= `Flag ${token.rawName} does not accept a value.`;
|
|
80
84
|
else {
|
|
81
|
-
if (
|
|
85
|
+
if ([
|
|
86
|
+
"json",
|
|
87
|
+
"no-config",
|
|
88
|
+
"strict-filesystem"
|
|
89
|
+
].includes(token.name) && flags.has(token.name)) error ??= `${token.rawName} must be specified only once.`;
|
|
82
90
|
flags.set(token.name, true);
|
|
83
91
|
}
|
|
84
92
|
else if (VALUE_FLAGS.has(token.name)) {
|
|
@@ -116,15 +124,29 @@ const parseCommand = (argv) => {
|
|
|
116
124
|
if (flags.has("reporter") && flags.has("json")) throw new UsageError("Invalid reporter selection: use either --reporter or --json.");
|
|
117
125
|
const reporter = flags.get("reporter");
|
|
118
126
|
const pmVersion = flags.get("pm-version");
|
|
127
|
+
const pm = parsePmFlag(flags.get("pm"));
|
|
128
|
+
if (pmVersion !== void 0 && (!pm || !isStableVersion(pmVersion))) throw new UsageError("--pm-version requires --pm and an exact stable version.");
|
|
129
|
+
const limits = {};
|
|
130
|
+
for (const [flag, key] of LIMIT_FLAGS) {
|
|
131
|
+
const value = flags.get(flag);
|
|
132
|
+
if (value !== void 0) {
|
|
133
|
+
if (typeof value !== "string" || !/^\d+$/u.test(value)) throw new UsageError(`${flag} must be a positive safe integer.`);
|
|
134
|
+
limits[key] = Number(value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
resolveScanLimits(limits);
|
|
119
138
|
return {
|
|
120
139
|
kind: "lint",
|
|
121
140
|
cwd: asAbsPath(path.resolve(cwd ?? process.cwd())),
|
|
122
|
-
pm
|
|
141
|
+
pm,
|
|
123
142
|
pmVersion: typeof pmVersion === "string" ? pmVersion : void 0,
|
|
124
143
|
exclude: repeated.get("exclude"),
|
|
125
144
|
installationRoots: repeated.get("installation-root"),
|
|
126
145
|
projectType: parseProjectTypeFlag(flags.get("project-type")),
|
|
127
146
|
severity: parseSeverityFlag(flags.get("severity")),
|
|
147
|
+
...flags.has("no-config") ? { noConfig: true } : {},
|
|
148
|
+
...flags.has("strict-filesystem") ? { rejectSymlinks: true } : {},
|
|
149
|
+
...Object.keys(limits).length ? { limits } : {},
|
|
128
150
|
reporter: typeof reporter === "string" ? reporter : flags.has("json") ? JSON_REPORTER_NAME : DEFAULT_REPORTER_NAME
|
|
129
151
|
};
|
|
130
152
|
};
|
|
@@ -136,6 +158,8 @@ const PROJECT_TYPES_LIST = PROJECT_TYPES.join("|");
|
|
|
136
158
|
const SEVERITIES_LIST = SEVERITIES.join("|");
|
|
137
159
|
const FLAG_LINES = {
|
|
138
160
|
json: " --json Shortcut for --reporter json",
|
|
161
|
+
safety: " --no-config Do not probe or execute repository siro.config.*\n --strict-filesystem Reject symlink input paths (not a containment sandbox)",
|
|
162
|
+
limits: " --max-file-bytes <n> Bound each input file (default 8388608)\n --max-total-bytes <n> Bound all input reads (default 67108864)\n --max-entries <n> Bound native directory entries (default 100000)\n --max-directories <n> Bound discovered directories (default 10000)\n --max-directory-depth <n> Bound discovery depth (default 128)\n --max-config-depth <n> Bound configuration nesting (default 128)\n --max-findings <n> Bound unfiltered findings (default 50000)\n --max-output-bytes <n> Bound report output (default 33554432)",
|
|
139
163
|
pm: ` --pm <name> Target a specific package manager (${PMS_LIST})`,
|
|
140
164
|
pmVersion: " --pm-version <x.y.z> Target an exact stable PM version (requires --pm)",
|
|
141
165
|
inspection: " --exclude <pattern> Exclude directories from recursive discovery (repeatable)\n --installation-root <path> Inspect local install policy here (repeatable; default .)",
|
|
@@ -161,12 +185,15 @@ const HELP_ROOT = [
|
|
|
161
185
|
" --help Show help for siro or a command",
|
|
162
186
|
"",
|
|
163
187
|
"LINT FLAGS",
|
|
188
|
+
FLAG_LINES.safety,
|
|
189
|
+
FLAG_LINES.limits,
|
|
164
190
|
FLAG_LINES.reporter,
|
|
165
191
|
FLAG_LINES.json,
|
|
166
192
|
FLAG_LINES.severity,
|
|
167
193
|
"",
|
|
168
194
|
"EXAMPLES",
|
|
169
195
|
" $ siro lint # report violations in cwd",
|
|
196
|
+
" $ siro lint --no-config --strict-filesystem # data-only inspection",
|
|
170
197
|
" $ siro lint --reporter github # GitHub Actions annotations",
|
|
171
198
|
" $ siro lint --severity warn # also fail on warnings",
|
|
172
199
|
"",
|
|
@@ -183,6 +210,8 @@ const HELP_LINT = [
|
|
|
183
210
|
" siro check [path] [flags] (alias)",
|
|
184
211
|
"",
|
|
185
212
|
"FLAGS",
|
|
213
|
+
FLAG_LINES.safety,
|
|
214
|
+
FLAG_LINES.limits,
|
|
186
215
|
FLAG_LINES.pm,
|
|
187
216
|
FLAG_LINES.pmVersion,
|
|
188
217
|
FLAG_LINES.inspection,
|
|
@@ -195,10 +224,11 @@ const HELP_LINT = [
|
|
|
195
224
|
" 0 No findings at or above the active threshold",
|
|
196
225
|
" 1 Findings at or above the threshold (default: error)",
|
|
197
226
|
" 2 Usage error (bad flag, broken siro.config.ts, unreadable path, …)",
|
|
198
|
-
" 70
|
|
227
|
+
" 70 Output failure/limit, or unexpected exception (including trusted extensions)",
|
|
199
228
|
"",
|
|
200
229
|
"EXAMPLES",
|
|
201
230
|
" $ siro lint # default: pretty reporter, fail on errors",
|
|
231
|
+
" $ siro lint --no-config --strict-filesystem # data-only inspection",
|
|
202
232
|
" $ siro lint --reporter github # GitHub Actions annotations",
|
|
203
233
|
" $ siro lint --severity warn # tighten the gate"
|
|
204
234
|
].join("\n");
|
|
@@ -224,12 +254,16 @@ const dispatch = async (cmd, io) => {
|
|
|
224
254
|
if (cmd.reason) await io.stderr(`${safeText(cmd.reason)}\n`);
|
|
225
255
|
await io.stderr(renderHelp());
|
|
226
256
|
return EXIT_USAGE;
|
|
227
|
-
case "lint":
|
|
257
|
+
case "lint": {
|
|
228
258
|
assertDirectory(cmd.cwd);
|
|
229
|
-
|
|
230
|
-
|
|
259
|
+
const { noConfig, ...options } = cmd;
|
|
260
|
+
if (noConfig) return lintCommand(options, io);
|
|
261
|
+
const config = await loadConfig(cmd.cwd);
|
|
262
|
+
return lintCommand({
|
|
263
|
+
...options,
|
|
231
264
|
config
|
|
232
|
-
}, io)
|
|
265
|
+
}, io);
|
|
266
|
+
}
|
|
233
267
|
default: throw new Error(`Unhandled command kind: ${String(cmd)}`);
|
|
234
268
|
}
|
|
235
269
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -201,9 +201,24 @@ interface LintResult {
|
|
|
201
201
|
readonly inspection: Inspection;
|
|
202
202
|
}
|
|
203
203
|
//#endregion
|
|
204
|
+
//#region src/core/contracts/scan-limits.d.ts
|
|
205
|
+
/** Finite per-scan budgets, controlled by the caller, never repository config. */
|
|
206
|
+
interface ScanLimits {
|
|
207
|
+
readonly maxFileBytes: number;
|
|
208
|
+
readonly maxTotalBytes: number;
|
|
209
|
+
readonly maxEntries: number;
|
|
210
|
+
readonly maxDirectories: number;
|
|
211
|
+
readonly maxDirectoryDepth: number;
|
|
212
|
+
readonly maxConfigDepth: number;
|
|
213
|
+
readonly maxFindings: number;
|
|
214
|
+
readonly maxOutputBytes: number;
|
|
215
|
+
}
|
|
216
|
+
declare const DEFAULT_SCAN_LIMITS: Readonly<ScanLimits>;
|
|
217
|
+
//#endregion
|
|
204
218
|
//#region src/core/contracts/reporter.d.ts
|
|
205
219
|
interface ReportContext {
|
|
206
220
|
readonly cwd: AbsPath;
|
|
221
|
+
readonly limits?: ScanLimits;
|
|
207
222
|
}
|
|
208
223
|
interface Reporter<Name extends string = string> {
|
|
209
224
|
readonly name: Name;
|
|
@@ -260,6 +275,10 @@ declare const defineConfig: (config: SiroConfig) => SiroConfig;
|
|
|
260
275
|
interface LintOptions {
|
|
261
276
|
readonly cwd: AbsPath;
|
|
262
277
|
readonly fs?: FileSystem;
|
|
278
|
+
/** Caller-controlled finite budgets; omitted values use DEFAULT_SCAN_LIMITS. */
|
|
279
|
+
readonly limits?: Partial<ScanLimits>;
|
|
280
|
+
/** Reject file/path symlinks in the native adapter; not an atomic containment sandbox. */
|
|
281
|
+
readonly rejectSymlinks?: boolean;
|
|
263
282
|
/** Applies only to cwd, never to discovered children or additional installation roots. */
|
|
264
283
|
readonly pm?: PM;
|
|
265
284
|
readonly pmVersion?: string;
|
|
@@ -397,5 +416,5 @@ declare const version: string;
|
|
|
397
416
|
//#region src/adapters/node-paths.d.ts
|
|
398
417
|
declare const asAbsPath: (value: string) => AbsPath;
|
|
399
418
|
//#endregion
|
|
400
|
-
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 Inspection, type InstallationRootInput, type KeyPath, type LintCommandOptions, type LintOptions, type LintResult, type PM, PMS, PROJECT_TYPES, type ParsedConfig, type PolicyTarget, type ProjectType, type RelPath, type Remediation, type RepoContext, type ReportContext, type Reporter, type RequireConfigKeyOptions, type RequireConfigKeySpec, type Rule, type RuleBinding, type RuleContext, type RuleSetting, SEVERITIES, type SetKeyOperation, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, type ViolationStatus, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
|
|
419
|
+
export { type AbsPath, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, CONFIG_FILES, type CheckStatus, ConfigError, type ConfigFileRef, type ConfigReadValue, type ConfigValue, DEFAULT_SCAN_LIMITS, type FileSystem, type Finding, type IO, type Inspection, type InstallationRootInput, type KeyPath, type LintCommandOptions, type LintOptions, type LintResult, type PM, PMS, PROJECT_TYPES, type ParsedConfig, type PolicyTarget, type ProjectType, type RelPath, type Remediation, type RepoContext, type ReportContext, type Reporter, type RequireConfigKeyOptions, type RequireConfigKeySpec, type Rule, type RuleBinding, type RuleContext, type RuleSetting, SEVERITIES, type ScanLimits, type SetKeyOperation, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, type ViolationStatus, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
|
|
401
420
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as nodeFileSystem, C as isSeverity, D as asAbsPath, E as version, F as SiroError, I as UsageError, M as DEFAULT_SCAN_LIMITS, O as asRelPath, P as ConfigError, S as isPM, _ as PROJECT_TYPES, b as PMS, c as jsonReporter, d as overrideBindings, f as requireConfigKey, g as getByPath, h as defineRule, i as BUILTIN_REPORTER_NAMES, m as loadConfig, n as lint, p as CONFIG_FILES, r as lintCommand, s as prettyReporter, t as nodeIO, u as githubReporter, x as SEVERITIES } from "./node-io-BpAHyeBG.mjs";
|
|
2
2
|
//#region src/core/siro-config.ts
|
|
3
3
|
/** Identity helper for type-checked config files. */
|
|
4
4
|
const defineConfig = (config) => config;
|
|
5
5
|
//#endregion
|
|
6
|
-
export { BUILTIN_REPORTER_NAMES, CONFIG_FILES, ConfigError, PMS, PROJECT_TYPES, SEVERITIES, SiroError, UsageError, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
|
|
6
|
+
export { BUILTIN_REPORTER_NAMES, CONFIG_FILES, ConfigError, DEFAULT_SCAN_LIMITS, PMS, PROJECT_TYPES, SEVERITIES, SiroError, UsageError, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
|
|
7
7
|
|
|
8
8
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, opendirSync, readSync, realpathSync, statSync } from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
|
-
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import satisfies from "semver/functions/satisfies.js";
|
|
5
5
|
import * as vb from "valibot";
|
|
@@ -9,6 +9,7 @@ import { Minimatch } from "minimatch";
|
|
|
9
9
|
import ini from "ini";
|
|
10
10
|
import { parse as parse$1 } from "smol-toml";
|
|
11
11
|
import { parseDocument } from "yaml";
|
|
12
|
+
import { types } from "node:util";
|
|
12
13
|
import pc from "picocolors";
|
|
13
14
|
//#region src/core/contracts/errors.ts
|
|
14
15
|
var SiroError = class extends Error {
|
|
@@ -31,6 +32,8 @@ var UsageError = class extends SiroError {
|
|
|
31
32
|
this.name = "UsageError";
|
|
32
33
|
}
|
|
33
34
|
};
|
|
35
|
+
/** Parser diagnostics constructed from constant classifications, never source excerpts. */
|
|
36
|
+
var ConfigParseError = class extends Error {};
|
|
34
37
|
/**
|
|
35
38
|
* Run `fn` and wrap any non-`ConfigError` failure as a `ConfigError` prefixed
|
|
36
39
|
* with `filePath`. A `ConfigError` that bubbles up from a nested call is re-thrown unchanged so
|
|
@@ -41,12 +44,187 @@ const wrapCodecError = (filePath, fn) => {
|
|
|
41
44
|
return fn();
|
|
42
45
|
} catch (error) {
|
|
43
46
|
if (error instanceof ConfigError) throw error;
|
|
44
|
-
|
|
45
|
-
if (error instanceof Error) msg = error.message;
|
|
46
|
-
throw new ConfigError(`${filePath}: ${msg}`);
|
|
47
|
+
throw new ConfigError(`${filePath}: ${error instanceof ConfigParseError ? error.message : "Invalid configuration."}`);
|
|
47
48
|
}
|
|
48
49
|
};
|
|
49
50
|
//#endregion
|
|
51
|
+
//#region src/core/contracts/records.ts
|
|
52
|
+
/** Configuration maps are ordinary objects or null-prototype dictionaries. */
|
|
53
|
+
const isPlainRecord = (value) => {
|
|
54
|
+
if (typeof value !== "object" || value === null) return false;
|
|
55
|
+
const prototype = Object.getPrototypeOf(value);
|
|
56
|
+
return prototype === Object.prototype || prototype === null;
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/core/contracts/scan-limits.ts
|
|
60
|
+
const DEFAULT_SCAN_LIMITS = Object.freeze({
|
|
61
|
+
maxFileBytes: 8 * 1024 * 1024,
|
|
62
|
+
maxTotalBytes: 64 * 1024 * 1024,
|
|
63
|
+
maxEntries: 1e5,
|
|
64
|
+
maxDirectories: 1e4,
|
|
65
|
+
maxDirectoryDepth: 128,
|
|
66
|
+
maxConfigDepth: 128,
|
|
67
|
+
maxFindings: 5e4,
|
|
68
|
+
maxOutputBytes: 32 * 1024 * 1024
|
|
69
|
+
});
|
|
70
|
+
const resolveScanLimits = (input) => {
|
|
71
|
+
if (input !== void 0 && (!isPlainRecord(input) || Object.keys(input).some((key) => !Object.hasOwn(DEFAULT_SCAN_LIMITS, key)))) throw new UsageError("limits must be an object of known scan limits.");
|
|
72
|
+
const limits = {
|
|
73
|
+
...DEFAULT_SCAN_LIMITS,
|
|
74
|
+
...input
|
|
75
|
+
};
|
|
76
|
+
for (const [key, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value <= 0) throw new UsageError(`${key} must be a positive safe integer.`);
|
|
77
|
+
return limits;
|
|
78
|
+
};
|
|
79
|
+
const SCAN_SCOPE_ADVICE = "Reduce the scan scope. If the target contains independent projects, scan each project directory separately.";
|
|
80
|
+
const checkLimit = (name, value, limits) => {
|
|
81
|
+
if (value > limits[name]) {
|
|
82
|
+
const advice = name === "maxFileBytes" ? "Reduce the size of the input file." : SCAN_SCOPE_ADVICE;
|
|
83
|
+
throw new ConfigError(`Inspection exceeds ${name} (${limits[name]}). ${advice}`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** Counts UTF-8 without allocating another copy of a potentially large input. */
|
|
87
|
+
const utf8Bytes = (text) => {
|
|
88
|
+
let bytes = 0;
|
|
89
|
+
for (const character of text) {
|
|
90
|
+
const code = character.codePointAt(0);
|
|
91
|
+
bytes += code <= 127 ? 1 : code <= 2047 ? 2 : code <= 65535 ? 3 : 4;
|
|
92
|
+
}
|
|
93
|
+
return bytes;
|
|
94
|
+
};
|
|
95
|
+
/** Iterative traversal; shared YAML mappings are legal, cycles are not. Root depth is 1. */
|
|
96
|
+
const checkConfigDepth = (value, maxDepth) => {
|
|
97
|
+
const active = /* @__PURE__ */ new Set();
|
|
98
|
+
const seen = /* @__PURE__ */ new Map();
|
|
99
|
+
const pending = [{
|
|
100
|
+
value,
|
|
101
|
+
depth: 1
|
|
102
|
+
}];
|
|
103
|
+
while (pending.length) {
|
|
104
|
+
const item = pending.pop();
|
|
105
|
+
if (item.value === null || typeof item.value !== "object") continue;
|
|
106
|
+
if (item.leave) {
|
|
107
|
+
active.delete(item.value);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (active.has(item.value)) throw new ConfigError("Configuration contains a circular reference.");
|
|
111
|
+
if (item.depth > maxDepth) throw new ConfigError(`Configuration exceeds maxConfigDepth (${maxDepth}). Simplify the nesting of the configuration.`);
|
|
112
|
+
if ((seen.get(item.value) ?? 0) >= item.depth) continue;
|
|
113
|
+
seen.set(item.value, item.depth);
|
|
114
|
+
active.add(item.value);
|
|
115
|
+
pending.push({
|
|
116
|
+
...item,
|
|
117
|
+
leave: true
|
|
118
|
+
});
|
|
119
|
+
for (const child of Object.values(item.value)) pending.push({
|
|
120
|
+
value: child,
|
|
121
|
+
depth: item.depth + 1
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/** Output budgets are output failures (70), not policy findings or incomplete input (2). */
|
|
126
|
+
const outputBudget = (maxBytes) => {
|
|
127
|
+
let bytes = 0;
|
|
128
|
+
return (text) => {
|
|
129
|
+
bytes += utf8Bytes(text);
|
|
130
|
+
if (bytes > maxBytes) throw new SiroError(`Output exceeds maxOutputBytes (${maxBytes}). ${SCAN_SCOPE_ADVICE}`, 70);
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/adapters/node-errors.ts
|
|
135
|
+
/** Only the Error and code properties are established by this guard. */
|
|
136
|
+
const isNodeError = (value) => value instanceof Error && "code" in value && typeof value.code === "string";
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/adapters/node-file-system.ts
|
|
139
|
+
/** Root ancestors are canonicalized; selected cwd and all below-root components must not be links. */
|
|
140
|
+
const strictPaths = (root) => {
|
|
141
|
+
const selected = path.resolve(root);
|
|
142
|
+
if (lstatSync(selected).isSymbolicLink()) throw new ConfigError("Strict filesystem rejects a symlink cwd.");
|
|
143
|
+
const canonical = realpathSync(selected);
|
|
144
|
+
return (file) => {
|
|
145
|
+
const relative = path.relative(root, file);
|
|
146
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new ConfigError("Strict filesystem path escapes cwd.");
|
|
147
|
+
let current = canonical;
|
|
148
|
+
for (const component of relative.split(path.sep).filter(Boolean)) {
|
|
149
|
+
current = path.join(current, component);
|
|
150
|
+
if (lstatSync(current).isSymbolicLink()) throw new ConfigError(`${file}: strict filesystem rejects symlinks.`);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
/** Construct fresh native budgets for each inspection. No counters survive another scan. */
|
|
155
|
+
const createNodeFileSystem = (limits = DEFAULT_SCAN_LIMITS, strictRoot) => {
|
|
156
|
+
const checkPath = strictRoot === void 0 ? () => {} : strictPaths(strictRoot);
|
|
157
|
+
let totalBytes = 0;
|
|
158
|
+
let entries = 0;
|
|
159
|
+
const regularFile = (file) => {
|
|
160
|
+
checkPath(file);
|
|
161
|
+
const stat = statSync(file);
|
|
162
|
+
if (!stat.isFile()) throw new ConfigError(`${file}: expected a regular file.`);
|
|
163
|
+
return stat;
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
readDirectories(directory) {
|
|
167
|
+
checkPath(directory);
|
|
168
|
+
const stream = opendirSync(directory);
|
|
169
|
+
const names = [];
|
|
170
|
+
try {
|
|
171
|
+
for (let entry = stream.readSync(); entry !== null; entry = stream.readSync()) {
|
|
172
|
+
checkLimit("maxEntries", ++entries, limits);
|
|
173
|
+
if (entry.isDirectory()) names.push(entry.name);
|
|
174
|
+
}
|
|
175
|
+
} finally {
|
|
176
|
+
stream.closeSync();
|
|
177
|
+
}
|
|
178
|
+
return names;
|
|
179
|
+
},
|
|
180
|
+
exists(file) {
|
|
181
|
+
try {
|
|
182
|
+
regularFile(file);
|
|
183
|
+
return true;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (isNodeError(error) && error.code === "ENOENT") return false;
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
readText(file) {
|
|
190
|
+
let fd;
|
|
191
|
+
try {
|
|
192
|
+
const stat = regularFile(file);
|
|
193
|
+
checkLimit("maxFileBytes", stat.size, limits);
|
|
194
|
+
checkLimit("maxTotalBytes", totalBytes + stat.size, limits);
|
|
195
|
+
fd = openSync(file, constants.O_RDONLY | constants.O_NONBLOCK | (strictRoot === void 0 ? 0 : constants.O_NOFOLLOW ?? 0));
|
|
196
|
+
if (!fstatSync(fd).isFile()) throw new ConfigError(`${file}: expected a regular file.`);
|
|
197
|
+
const chunks = [];
|
|
198
|
+
let size = 0;
|
|
199
|
+
for (;;) {
|
|
200
|
+
const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, limits.maxFileBytes - size + 1, limits.maxTotalBytes - totalBytes + 1));
|
|
201
|
+
const read = readSync(fd, buffer);
|
|
202
|
+
if (read === 0) break;
|
|
203
|
+
size += read;
|
|
204
|
+
totalBytes += read;
|
|
205
|
+
checkLimit("maxFileBytes", size, limits);
|
|
206
|
+
checkLimit("maxTotalBytes", totalBytes, limits);
|
|
207
|
+
chunks.push(buffer.subarray(0, read));
|
|
208
|
+
}
|
|
209
|
+
return Buffer.concat(chunks, size).toString("utf8");
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (isNodeError(error) && error.code === "ENOENT") return;
|
|
212
|
+
throw error;
|
|
213
|
+
} finally {
|
|
214
|
+
if (fd !== void 0) closeSync(fd);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
const nodeFileSystem = {
|
|
220
|
+
readDirectories: (directory) => createNodeFileSystem().readDirectories(directory),
|
|
221
|
+
exists: (file) => createNodeFileSystem().exists(file),
|
|
222
|
+
readText: (file) => createNodeFileSystem().readText(file)
|
|
223
|
+
};
|
|
224
|
+
const assertDirectory = (root) => {
|
|
225
|
+
if (!statSync(root).isDirectory()) throw new UsageError("The lint target must be a directory.");
|
|
226
|
+
};
|
|
227
|
+
//#endregion
|
|
50
228
|
//#region src/core/contracts/paths.ts
|
|
51
229
|
const isRelPath = (value) => typeof value === "string" && value.length > 0 && !value.includes("\0") && !/^[\\/]/u.test(value) && !/^[a-z]:/iu.test(value) && !value.split(/[\\/]/u).includes("..");
|
|
52
230
|
const asRelPath = (value) => {
|
|
@@ -75,44 +253,8 @@ const nodePaths = {
|
|
|
75
253
|
}
|
|
76
254
|
};
|
|
77
255
|
//#endregion
|
|
78
|
-
//#region src/adapters/node-errors.ts
|
|
79
|
-
/** Only the Error and code properties are established by this guard. */
|
|
80
|
-
const isNodeError = (value) => value instanceof Error && "code" in value && typeof value.code === "string";
|
|
81
|
-
//#endregion
|
|
82
|
-
//#region src/adapters/node-file-system.ts
|
|
83
|
-
const assertRegularFile = (filePath) => {
|
|
84
|
-
if (!statSync(filePath).isFile()) throw new ConfigError(`${filePath}: expected a regular file.`);
|
|
85
|
-
};
|
|
86
|
-
const nodeFileSystem = {
|
|
87
|
-
readDirectories(directory) {
|
|
88
|
-
return readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
89
|
-
},
|
|
90
|
-
exists(filePath) {
|
|
91
|
-
try {
|
|
92
|
-
assertRegularFile(filePath);
|
|
93
|
-
return true;
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (isNodeError(error) && error.code === "ENOENT") return false;
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
readText(filePath) {
|
|
100
|
-
try {
|
|
101
|
-
assertRegularFile(filePath);
|
|
102
|
-
return readFileSync(filePath, "utf8");
|
|
103
|
-
} catch (error) {
|
|
104
|
-
if (isNodeError(error) && error.code === "ENOENT") return;
|
|
105
|
-
throw error;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
const resolveIn = (root, relPath) => nodePaths.resolve(root, relPath);
|
|
110
|
-
const assertDirectory = (root) => {
|
|
111
|
-
if (!statSync(root).isDirectory()) throw new UsageError("The lint target must be a directory.");
|
|
112
|
-
};
|
|
113
|
-
//#endregion
|
|
114
256
|
//#region package.json
|
|
115
|
-
var version$1 = "0.6.
|
|
257
|
+
var version$1 = "0.6.1";
|
|
116
258
|
var engines = { "node": "^22.18.0 || ^24.0.0" };
|
|
117
259
|
//#endregion
|
|
118
260
|
//#region src/version.ts
|
|
@@ -122,14 +264,6 @@ const SUPPORTED_NODE_RANGE = engines.node;
|
|
|
122
264
|
//#region src/adapters/node-version.ts
|
|
123
265
|
const isSupportedNodeVersion = (version) => satisfies(version, SUPPORTED_NODE_RANGE);
|
|
124
266
|
//#endregion
|
|
125
|
-
//#region src/core/contracts/records.ts
|
|
126
|
-
/** Configuration maps are ordinary objects or null-prototype dictionaries. */
|
|
127
|
-
const isPlainRecord = (value) => {
|
|
128
|
-
if (typeof value !== "object" || value === null) return false;
|
|
129
|
-
const prototype = Object.getPrototypeOf(value);
|
|
130
|
-
return prototype === Object.prototype || prototype === null;
|
|
131
|
-
};
|
|
132
|
-
//#endregion
|
|
133
267
|
//#region src/core/contracts/pms.ts
|
|
134
268
|
/**
|
|
135
269
|
* Single source of truth for package managers and severities.
|
|
@@ -252,7 +386,7 @@ const getByPath = (config, keyPath) => {
|
|
|
252
386
|
/** Accept only mapping roots while preserving unvalidated values inside them. */
|
|
253
387
|
const toParsedConfig = (value) => {
|
|
254
388
|
if (isPlainRecord(value)) return value;
|
|
255
|
-
throw new
|
|
389
|
+
throw new ConfigParseError("Config root must be a mapping.");
|
|
256
390
|
};
|
|
257
391
|
//#endregion
|
|
258
392
|
//#region src/core/contracts/config-file-ref.ts
|
|
@@ -1794,7 +1928,7 @@ const unpinnedInlineImports = (config) => {
|
|
|
1794
1928
|
const scopes = getByPath(config, ["scopes"]);
|
|
1795
1929
|
if (scopes != null) {
|
|
1796
1930
|
if (!isPlainRecord(scopes)) throw new ConfigError("deno.json: scopes must be an object.");
|
|
1797
|
-
for (const [scope, mapping] of Object.entries(scopes))
|
|
1931
|
+
for (const [scope, mapping] of Object.entries(scopes)) for (const offender of collectUnpinnedImports(mapping, `scopes[${JSON.stringify(scope)}]`)) offenders.push(offender);
|
|
1798
1932
|
}
|
|
1799
1933
|
return offenders;
|
|
1800
1934
|
};
|
|
@@ -2401,16 +2535,7 @@ const runLint = (opts) => {
|
|
|
2401
2535
|
version: opts.pmVersions?.[pm]
|
|
2402
2536
|
}));
|
|
2403
2537
|
}
|
|
2404
|
-
|
|
2405
|
-
error: 0,
|
|
2406
|
-
info: 0,
|
|
2407
|
-
warn: 0
|
|
2408
|
-
};
|
|
2409
|
-
for (const finding of findings) summary[finding.severity] += 1;
|
|
2410
|
-
return {
|
|
2411
|
-
findings,
|
|
2412
|
-
summary
|
|
2413
|
-
};
|
|
2538
|
+
return { findings };
|
|
2414
2539
|
};
|
|
2415
2540
|
//#endregion
|
|
2416
2541
|
//#region src/core/contracts/deno-json.ts
|
|
@@ -2428,7 +2553,7 @@ const validateDenoMetadata = (config) => {
|
|
|
2428
2553
|
//#region src/core/parse-config-file.ts
|
|
2429
2554
|
const EMPTY_FILE = Object.freeze({});
|
|
2430
2555
|
/** Create a lazy parser for one repository context; callers own its lifetime. */
|
|
2431
|
-
const createConfigParser = (codecFor, ctx) => {
|
|
2556
|
+
const createConfigParser = (codecFor, ctx, maxDepth = DEFAULT_SCAN_LIMITS.maxConfigDepth) => {
|
|
2432
2557
|
const cache = /* @__PURE__ */ new Map();
|
|
2433
2558
|
return (file) => {
|
|
2434
2559
|
if (file === void 0) return EMPTY_FILE;
|
|
@@ -2441,31 +2566,34 @@ const createConfigParser = (codecFor, ctx) => {
|
|
|
2441
2566
|
return EMPTY_FILE;
|
|
2442
2567
|
}
|
|
2443
2568
|
const parsed = wrapCodecError(file.path, () => codecFor(file.kind).parse(text));
|
|
2569
|
+
checkConfigDepth(parsed, maxDepth);
|
|
2444
2570
|
cache.set(cacheKey, parsed);
|
|
2445
2571
|
return parsed;
|
|
2446
2572
|
};
|
|
2447
2573
|
};
|
|
2448
2574
|
/** Pair the context with a fresh lazy parser; construction performs no reads. */
|
|
2449
|
-
const createRepositoryEvaluation = (ctx, codecFor) => ({
|
|
2575
|
+
const createRepositoryEvaluation = (ctx, codecFor, maxDepth = DEFAULT_SCAN_LIMITS.maxConfigDepth) => ({
|
|
2450
2576
|
ctx,
|
|
2451
|
-
parseConfig: createConfigParser(codecFor, ctx)
|
|
2577
|
+
parseConfig: createConfigParser(codecFor, ctx, maxDepth)
|
|
2452
2578
|
});
|
|
2453
2579
|
//#endregion
|
|
2454
2580
|
//#region src/core/discovery.ts
|
|
2455
2581
|
/** One context per selected directory, shared by publication and installation passes. */
|
|
2456
|
-
const discover = (cwd, fs, excluded, projectType, dependencies) => {
|
|
2457
|
-
if (typeof fs.readDirectories !== "function") throw new ConfigError("FileSystem.readDirectories is required for package discovery; no native filesystem fallback is used.");
|
|
2582
|
+
const discover = (cwd, fs, excluded, projectType, dependencies, limits = DEFAULT_SCAN_LIMITS) => {
|
|
2458
2583
|
const { paths, createRepoContext, codecFor } = dependencies;
|
|
2459
2584
|
const pending = [asRelPath(".")];
|
|
2460
2585
|
const directories = [];
|
|
2461
2586
|
while (pending.length) {
|
|
2462
2587
|
const directory = pending.pop();
|
|
2588
|
+
checkLimit("maxDirectories", directories.length + pending.length + 1, limits);
|
|
2589
|
+
checkLimit("maxDirectoryDepth", directory === "." ? 0 : directory.split("/").length, limits);
|
|
2463
2590
|
const absolute = paths.resolve(cwd, directory);
|
|
2464
2591
|
let repository;
|
|
2465
2592
|
const manifests = [];
|
|
2466
2593
|
try {
|
|
2467
|
-
repository = createRepositoryEvaluation(createRepoContext(absolute, fs, projectType), codecFor);
|
|
2594
|
+
repository = createRepositoryEvaluation(createRepoContext(absolute, fs, projectType), codecFor, limits.maxConfigDepth);
|
|
2468
2595
|
const { ctx, parseConfig } = repository;
|
|
2596
|
+
checkConfigDepth(ctx.packageJson, limits.maxConfigDepth);
|
|
2469
2597
|
if (ctx.packageJson !== void 0) manifests.push("package.json");
|
|
2470
2598
|
if (ctx.readText(CONFIG_FILES.denoJson.path) !== void 0) {
|
|
2471
2599
|
validateDenoMetadata(parseConfig(CONFIG_FILES.denoJson));
|
|
@@ -2487,6 +2615,8 @@ const discover = (cwd, fs, excluded, projectType, dependencies) => {
|
|
|
2487
2615
|
if (!Object.hasOwn(names, i)) throw new ConfigError(`${directory}: FileSystem.readDirectories must return a dense array.`);
|
|
2488
2616
|
const child = paths.child(directory, names[i]);
|
|
2489
2617
|
if (names[i] === ".git" || names[i] === "node_modules" || excluded(child) || children.has(child)) continue;
|
|
2618
|
+
checkLimit("maxDirectoryDepth", child.split("/").length, limits);
|
|
2619
|
+
checkLimit("maxDirectories", directories.length + pending.length + 1, limits);
|
|
2490
2620
|
children.add(child);
|
|
2491
2621
|
pending.push(child);
|
|
2492
2622
|
}
|
|
@@ -2564,6 +2694,38 @@ const rebaseFinding = (directory, finding) => {
|
|
|
2564
2694
|
};
|
|
2565
2695
|
};
|
|
2566
2696
|
//#endregion
|
|
2697
|
+
//#region src/core/bounded-file-system.ts
|
|
2698
|
+
/** Bounds returned data from trusted injected IO; cannot bound allocations inside that IO. */
|
|
2699
|
+
const boundedFileSystem = (fs, limits) => {
|
|
2700
|
+
const texts = /* @__PURE__ */ new Map();
|
|
2701
|
+
let bytes = 0;
|
|
2702
|
+
let entries = 0;
|
|
2703
|
+
return {
|
|
2704
|
+
exists: (path) => fs.exists(path),
|
|
2705
|
+
readText(path) {
|
|
2706
|
+
if (!texts.has(path)) {
|
|
2707
|
+
const text = fs.readText(path);
|
|
2708
|
+
if (text !== void 0) {
|
|
2709
|
+
const size = utf8Bytes(text);
|
|
2710
|
+
checkLimit("maxFileBytes", size, limits);
|
|
2711
|
+
bytes += size;
|
|
2712
|
+
checkLimit("maxTotalBytes", bytes, limits);
|
|
2713
|
+
}
|
|
2714
|
+
texts.set(path, text);
|
|
2715
|
+
}
|
|
2716
|
+
return texts.get(path);
|
|
2717
|
+
},
|
|
2718
|
+
readDirectories(path) {
|
|
2719
|
+
const names = fs.readDirectories(path);
|
|
2720
|
+
if (Array.isArray(names)) {
|
|
2721
|
+
entries += names.length;
|
|
2722
|
+
checkLimit("maxEntries", entries, limits);
|
|
2723
|
+
}
|
|
2724
|
+
return names;
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
};
|
|
2728
|
+
//#endregion
|
|
2567
2729
|
//#region src/core/lint.ts
|
|
2568
2730
|
const validateLintOptions = (options, paths) => {
|
|
2569
2731
|
if (!options || !paths.isAbsolute(options.cwd)) throw new UsageError("cwd must be an absolute filesystem path.");
|
|
@@ -2577,12 +2739,15 @@ const validateLintOptions = (options, paths) => {
|
|
|
2577
2739
|
const prepareLint = (options, dependencies) => {
|
|
2578
2740
|
validateLintOptions(options, dependencies.paths);
|
|
2579
2741
|
const config = options.config === void 0 ? void 0 : parseConfig(options.config);
|
|
2580
|
-
const
|
|
2742
|
+
const limits = resolveScanLimits(options.limits);
|
|
2743
|
+
const sourceFs = options.fs === void 0 ? dependencies.fileSystem : options.fs;
|
|
2744
|
+
if (typeof sourceFs.readDirectories !== "function") throw new ConfigError("FileSystem.readDirectories is required for package discovery; no native filesystem fallback is used.");
|
|
2745
|
+
const fs = boundedFileSystem(sourceFs, limits);
|
|
2581
2746
|
const excluded = dependencies.compileExclusions(parseExcludes(options.exclude !== void 0 ? options.exclude : config?.exclude ?? []));
|
|
2582
2747
|
const installations = parseInstallationRoots(options.installationRoots !== void 0 ? options.installationRoots : config?.installationRoots ?? ["."]);
|
|
2583
2748
|
const configured = applyConfig(dependencies.rules, config);
|
|
2584
2749
|
const needsCustomTarget = configured.rules.some((rule) => scopeOf(rule.id) === "custom");
|
|
2585
|
-
const discovered = discover(options.cwd, fs, excluded, options.projectType ?? config?.projectType, dependencies);
|
|
2750
|
+
const discovered = discover(options.cwd, fs, excluded, options.projectType ?? config?.projectType, dependencies, limits);
|
|
2586
2751
|
for (const entry of installations) if (!discovered.some((item) => item.directory === entry.path)) throw new UsageError(`${entry.path}: installation root must be an existing, selected ordinary directory using its exact enumerated spelling (no symlinks, excluded or hard-skipped paths).`);
|
|
2587
2752
|
return {
|
|
2588
2753
|
evaluation: {
|
|
@@ -2627,13 +2792,14 @@ const prepareLint = (options, dependencies) => {
|
|
|
2627
2792
|
};
|
|
2628
2793
|
}),
|
|
2629
2794
|
ruleSet: configured.rules,
|
|
2630
|
-
severityOverrides: configured.severityOverrides
|
|
2795
|
+
severityOverrides: configured.severityOverrides,
|
|
2796
|
+
limits
|
|
2631
2797
|
},
|
|
2632
2798
|
reporters: config?.reporters ?? []
|
|
2633
2799
|
};
|
|
2634
2800
|
};
|
|
2635
2801
|
const runPreparedLint = (evaluation) => {
|
|
2636
|
-
const { directories, ruleSet, severityOverrides } = evaluation;
|
|
2802
|
+
const { directories, ruleSet, severityOverrides, limits } = evaluation;
|
|
2637
2803
|
const findings = [];
|
|
2638
2804
|
const manifests = [];
|
|
2639
2805
|
const installationRoots = [];
|
|
@@ -2676,7 +2842,8 @@ const runPreparedLint = (evaluation) => {
|
|
|
2676
2842
|
throw error;
|
|
2677
2843
|
}
|
|
2678
2844
|
local.sort((a, b) => (ruleOrder.get(a.ruleId) ?? 0) - (ruleOrder.get(b.ruleId) ?? 0));
|
|
2679
|
-
findings.
|
|
2845
|
+
checkLimit("maxFindings", findings.length + local.length, limits);
|
|
2846
|
+
for (const finding of local) findings.push(rebaseFinding(directory, finding));
|
|
2680
2847
|
}
|
|
2681
2848
|
const summary = {
|
|
2682
2849
|
error: 0,
|
|
@@ -2739,6 +2906,7 @@ const lintCommand$1 = async (options, io, dependencies, reporters) => {
|
|
|
2739
2906
|
const result = runPreparedLint(prepared.evaluation);
|
|
2740
2907
|
const exitCode = exitCodeForLint(result, options.severity ?? "error");
|
|
2741
2908
|
const writes = [];
|
|
2909
|
+
const consumeOutput = outputBudget(prepared.evaluation.limits.maxOutputBytes);
|
|
2742
2910
|
let outputFailed = false;
|
|
2743
2911
|
let outputFailure;
|
|
2744
2912
|
const recordFailure = (error) => {
|
|
@@ -2749,6 +2917,7 @@ const lintCommand$1 = async (options, io, dependencies, reporters) => {
|
|
|
2749
2917
|
};
|
|
2750
2918
|
const track = (write) => (line) => {
|
|
2751
2919
|
try {
|
|
2920
|
+
consumeOutput(`${line}\n`);
|
|
2752
2921
|
const written = write(line);
|
|
2753
2922
|
writes.push(Promise.resolve(written).then(() => {}, recordFailure));
|
|
2754
2923
|
return written;
|
|
@@ -2761,7 +2930,10 @@ const lintCommand$1 = async (options, io, dependencies, reporters) => {
|
|
|
2761
2930
|
await reporter.format(filterBySeverity(result, options.severity ?? "info"), {
|
|
2762
2931
|
stdout: track((line) => io.stdout(line)),
|
|
2763
2932
|
stderr: track((line) => io.stderr(line))
|
|
2764
|
-
}, {
|
|
2933
|
+
}, {
|
|
2934
|
+
cwd: options.cwd,
|
|
2935
|
+
limits: prepared.evaluation.limits
|
|
2936
|
+
});
|
|
2765
2937
|
} finally {
|
|
2766
2938
|
await Promise.all(writes);
|
|
2767
2939
|
}
|
|
@@ -2794,32 +2966,29 @@ const parsePackageJson = (value) => {
|
|
|
2794
2966
|
throw new ConfigError(`package.json: ${field ? `${field}: ` : ""}${issue.message}`);
|
|
2795
2967
|
};
|
|
2796
2968
|
//#endregion
|
|
2797
|
-
//#region src/adapters/
|
|
2798
|
-
|
|
2969
|
+
//#region src/adapters/codecs/json.ts
|
|
2970
|
+
/** Strict JSON; runtime SyntaxError messages may disclose arbitrary source bytes. */
|
|
2971
|
+
const parseJson = (text, maxDepth = DEFAULT_SCAN_LIMITS.maxConfigDepth) => {
|
|
2972
|
+
let value;
|
|
2799
2973
|
try {
|
|
2800
|
-
|
|
2801
|
-
} catch
|
|
2802
|
-
|
|
2803
|
-
if (error instanceof Error) errMsg = error.message;
|
|
2804
|
-
throw new ConfigError(`package.json: invalid JSON — ${errMsg}`);
|
|
2974
|
+
value = JSON.parse(text.trim());
|
|
2975
|
+
} catch {
|
|
2976
|
+
throw new ConfigParseError("invalid JSON");
|
|
2805
2977
|
}
|
|
2978
|
+
checkConfigDepth(value, maxDepth);
|
|
2979
|
+
return value;
|
|
2806
2980
|
};
|
|
2807
|
-
const
|
|
2808
|
-
return
|
|
2809
|
-
};
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
const
|
|
2815
|
-
const
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
return texts.get(file);
|
|
2819
|
-
};
|
|
2820
|
-
const exists = (relPath) => fs.exists(resolveIn(root, relPath));
|
|
2821
|
-
let packageJson = void 0;
|
|
2822
|
-
if (typeof raw !== "undefined") packageJson = readPackageJson(raw);
|
|
2981
|
+
const jsonCodec = { parse(text) {
|
|
2982
|
+
return toParsedConfig(parseJson(text));
|
|
2983
|
+
} };
|
|
2984
|
+
//#endregion
|
|
2985
|
+
//#region src/adapters/repo-context.ts
|
|
2986
|
+
/** Read caching and budgets belong to the scan's supplied filesystem. */
|
|
2987
|
+
const createRepoContext = (root, fs, projectType, maxDepth = DEFAULT_SCAN_LIMITS.maxConfigDepth) => {
|
|
2988
|
+
const readText = (relative) => fs.readText(nodePaths.resolve(root, relative));
|
|
2989
|
+
const raw = readText(asRelPath("package.json"));
|
|
2990
|
+
const packageJson = raw === void 0 ? void 0 : parsePackageJson(wrapCodecError("package.json", () => parseJson(raw, maxDepth)));
|
|
2991
|
+
const exists = (relative) => fs.exists(nodePaths.resolve(root, relative));
|
|
2823
2992
|
return {
|
|
2824
2993
|
exists,
|
|
2825
2994
|
packageJson,
|
|
@@ -2842,37 +3011,71 @@ const coerce = (raw) => {
|
|
|
2842
3011
|
if (typeof raw !== "string") return raw;
|
|
2843
3012
|
return coerceBoolean(raw) ?? coerceInteger(raw) ?? raw;
|
|
2844
3013
|
};
|
|
3014
|
+
/**
|
|
3015
|
+
* Coerces unquoted scalars on read so rule checks can match by typed
|
|
3016
|
+
* value (`true === true`, `7 === 7`). Strings whose lexeme matches
|
|
3017
|
+
* `true`/`false`/`-?\d+` lose their string-ness — this is by design for
|
|
3018
|
+
* the npm schema, where rule values are booleans / integers / the empty
|
|
3019
|
+
* string. Don't model a customRule around a string like `"0"` on this
|
|
3020
|
+
* codec; pick yaml/json if you need lossless arbitrary-string reads.
|
|
3021
|
+
*/
|
|
3022
|
+
const iniCodec = { parse(text) {
|
|
3023
|
+
const raw = toParsedConfig(ini.parse(text));
|
|
3024
|
+
const config = {};
|
|
3025
|
+
for (const [key, value] of Object.entries(raw)) if (Array.isArray(value)) config[key] = value.map((elem) => coerce(elem));
|
|
3026
|
+
else if (typeof value === "object" && value !== null) config[key] = toParsedConfig(value);
|
|
3027
|
+
else config[key] = coerce(value);
|
|
3028
|
+
return config;
|
|
3029
|
+
} };
|
|
3030
|
+
//#endregion
|
|
3031
|
+
//#region src/adapters/codecs/toml.ts
|
|
3032
|
+
const tomlCodec = { parse(text) {
|
|
3033
|
+
if (text.trim() === "") return {};
|
|
3034
|
+
const input = text.startsWith("") ? text.slice(1) : text;
|
|
3035
|
+
let value;
|
|
3036
|
+
try {
|
|
3037
|
+
value = parse$1(input);
|
|
3038
|
+
} catch {
|
|
3039
|
+
throw new ConfigParseError("Invalid TOML syntax.");
|
|
3040
|
+
}
|
|
3041
|
+
return toParsedConfig(value);
|
|
3042
|
+
} };
|
|
2845
3043
|
//#endregion
|
|
2846
|
-
//#region src/adapters/codecs/
|
|
2847
|
-
const
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
const raw = toParsedConfig(ini.parse(text));
|
|
2854
|
-
const config = {};
|
|
2855
|
-
for (const [key, value] of Object.entries(raw)) if (Array.isArray(value)) config[key] = value.map((elem) => coerce(elem));
|
|
2856
|
-
else if (typeof value === "object" && value !== null) config[key] = toParsedConfig(value);
|
|
2857
|
-
else config[key] = coerce(value);
|
|
2858
|
-
return config;
|
|
2859
|
-
} },
|
|
2860
|
-
toml: { parse(text) {
|
|
2861
|
-
if (text.trim() === "") return {};
|
|
2862
|
-
return toParsedConfig(parse$1(text.startsWith("") ? text.slice(1) : text));
|
|
2863
|
-
} },
|
|
2864
|
-
yaml: { parse(text) {
|
|
2865
|
-
if (text.trim() === "") return {};
|
|
2866
|
-
const document = parseDocument(text);
|
|
2867
|
-
if (document.errors.length > 0) throw document.errors[0];
|
|
3044
|
+
//#region src/adapters/codecs/yaml.ts
|
|
3045
|
+
const parseYaml = (text, maxDepth = DEFAULT_SCAN_LIMITS.maxConfigDepth) => {
|
|
3046
|
+
if (text.trim() === "") return {};
|
|
3047
|
+
let value;
|
|
3048
|
+
try {
|
|
3049
|
+
const document = parseDocument(text, { prettyErrors: false });
|
|
3050
|
+
if (document.errors.length) throw new ConfigParseError("Invalid YAML syntax.");
|
|
2868
3051
|
if (document.contents === null) return {};
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
3052
|
+
value = document.toJS();
|
|
3053
|
+
} catch (error) {
|
|
3054
|
+
if (error instanceof ConfigParseError) throw error;
|
|
3055
|
+
if (error instanceof Error && error.message.includes("Excessive alias count")) throw new ConfigParseError("Excessive alias count in YAML.");
|
|
3056
|
+
throw new ConfigParseError("Invalid YAML.");
|
|
3057
|
+
}
|
|
3058
|
+
try {
|
|
3059
|
+
checkConfigDepth(value, maxDepth);
|
|
3060
|
+
} catch (error) {
|
|
3061
|
+
if (error instanceof ConfigError) throw error;
|
|
3062
|
+
throw new ConfigParseError("Invalid YAML structure.");
|
|
3063
|
+
}
|
|
3064
|
+
return toParsedConfig(value);
|
|
2873
3065
|
};
|
|
2874
|
-
|
|
2875
|
-
|
|
3066
|
+
//#endregion
|
|
3067
|
+
//#region src/adapters/codecs/store.ts
|
|
3068
|
+
const CODECS = {
|
|
3069
|
+
json: jsonCodec,
|
|
3070
|
+
npmrc: iniCodec,
|
|
3071
|
+
toml: tomlCodec,
|
|
3072
|
+
yaml: { parse: (text) => parseYaml(text) }
|
|
3073
|
+
};
|
|
3074
|
+
const createCodecFor = (limits) => (kind) => ({ parse(text) {
|
|
3075
|
+
const parsed = kind === "json" ? toParsedConfig(parseJson(text, limits.maxConfigDepth)) : kind === "yaml" ? parseYaml(text, limits.maxConfigDepth) : CODECS[kind].parse(text);
|
|
3076
|
+
checkConfigDepth(parsed, limits.maxConfigDepth);
|
|
3077
|
+
return parsed;
|
|
3078
|
+
} });
|
|
2876
3079
|
//#endregion
|
|
2877
3080
|
//#region src/adapters/reporters/github.ts
|
|
2878
3081
|
const COMMAND = {
|
|
@@ -2885,12 +3088,15 @@ const escapeProp = (raw) => escapeData(raw).replaceAll(":", "%3A").replaceAll(",
|
|
|
2885
3088
|
/** Emit GitHub Actions workflow commands (annotations on PRs). */
|
|
2886
3089
|
const githubReporter = {
|
|
2887
3090
|
async format(result, io, context) {
|
|
3091
|
+
const consume = outputBudget(context.limits?.maxOutputBytes ?? DEFAULT_SCAN_LIMITS.maxOutputBytes);
|
|
2888
3092
|
for (const finding of result.findings) {
|
|
2889
3093
|
let file = "";
|
|
2890
3094
|
if (finding.file) file = `file=${escapeProp(path.resolve(context.cwd, finding.file))},`;
|
|
2891
3095
|
let body = `[${finding.pm ?? "package"}] ${finding.directory}: ${finding.message}`;
|
|
2892
3096
|
if (finding.docs) body += ` (${finding.docs})`;
|
|
2893
|
-
|
|
3097
|
+
const line = `::${COMMAND[finding.severity]} ${file}title=${escapeProp(finding.ruleId)}::${escapeData(body)}`;
|
|
3098
|
+
consume(`${line}\n`);
|
|
3099
|
+
await io.stdout(line);
|
|
2894
3100
|
}
|
|
2895
3101
|
},
|
|
2896
3102
|
name: "github"
|
|
@@ -2902,6 +3108,66 @@ const safeText = (text) => text.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e
|
|
|
2902
3108
|
/** JSON syntax and decoded values stay identical; CI must not parse legacy commands in it. */
|
|
2903
3109
|
const safeJsonText = (json) => json.replace(/[\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/gu, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`).replaceAll("##[", "\\u0023\\u0023[");
|
|
2904
3110
|
//#endregion
|
|
3111
|
+
//#region src/adapters/reporters/bounded-json.ts
|
|
3112
|
+
const omitted = (value) => value === void 0 || typeof value === "function" || typeof value === "symbol";
|
|
3113
|
+
/** Serialize incrementally into bounded chunks before allocating the final JSON document.
|
|
3114
|
+
* JSON primitives use the native encoder; containers preserve its own-key/array/toJSON semantics.
|
|
3115
|
+
*/
|
|
3116
|
+
const boundedJson = (input, maxBytes, maxDepth) => {
|
|
3117
|
+
const consume = outputBudget(maxBytes);
|
|
3118
|
+
const chunks = [];
|
|
3119
|
+
const active = /* @__PURE__ */ new Set();
|
|
3120
|
+
const emit = (text) => {
|
|
3121
|
+
const escaped = safeJsonText(text);
|
|
3122
|
+
consume(escaped);
|
|
3123
|
+
chunks.push(escaped);
|
|
3124
|
+
};
|
|
3125
|
+
const normalize = (value, key) => {
|
|
3126
|
+
if (value !== null && (typeof value === "object" || typeof value === "function" || typeof value === "bigint")) {
|
|
3127
|
+
const toJSON = value.toJSON;
|
|
3128
|
+
if (typeof toJSON === "function") value = Reflect.apply(toJSON, value, [key]);
|
|
3129
|
+
}
|
|
3130
|
+
if (types.isNumberObject(value)) return +value;
|
|
3131
|
+
if (types.isStringObject(value)) return `${value}`;
|
|
3132
|
+
if (types.isBooleanObject(value)) return Boolean.prototype.valueOf.call(value);
|
|
3133
|
+
if (types.isBigIntObject(value)) return BigInt.prototype.valueOf.call(value);
|
|
3134
|
+
return value;
|
|
3135
|
+
};
|
|
3136
|
+
const serialize = (value, depth) => {
|
|
3137
|
+
if (typeof value === "bigint") throw new TypeError("Do not know how to serialize a BigInt");
|
|
3138
|
+
if (value === null || typeof value !== "object") {
|
|
3139
|
+
emit(JSON.stringify(value) ?? "null");
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
if (active.has(value)) throw new TypeError("Cannot serialize a circular report.");
|
|
3143
|
+
if (depth > maxDepth) throw new TypeError("Report nesting exceeds the configured depth.");
|
|
3144
|
+
active.add(value);
|
|
3145
|
+
const array = Array.isArray(value);
|
|
3146
|
+
emit(array ? "[" : "{");
|
|
3147
|
+
let count = 0;
|
|
3148
|
+
const entry = (key, raw) => {
|
|
3149
|
+
const child = normalize(raw, key);
|
|
3150
|
+
if (!array && omitted(child)) return;
|
|
3151
|
+
emit(`${count++ === 0 ? "\n" : ",\n"}${" ".repeat(depth)}`);
|
|
3152
|
+
if (!array) {
|
|
3153
|
+
emit(JSON.stringify(key));
|
|
3154
|
+
emit(": ");
|
|
3155
|
+
}
|
|
3156
|
+
serialize(omitted(child) ? null : child, depth + 1);
|
|
3157
|
+
};
|
|
3158
|
+
if (array) {
|
|
3159
|
+
const length = value.length;
|
|
3160
|
+
for (let index = 0; index < length; index++) entry(String(index), value[index]);
|
|
3161
|
+
} else for (const key of Object.keys(value)) entry(key, value[key]);
|
|
3162
|
+
if (count) emit(`\n${" ".repeat(depth - 1)}`);
|
|
3163
|
+
emit(array ? "]" : "}");
|
|
3164
|
+
active.delete(value);
|
|
3165
|
+
};
|
|
3166
|
+
serialize(normalize(input, ""), 1);
|
|
3167
|
+
consume("\n");
|
|
3168
|
+
return chunks.join("");
|
|
3169
|
+
};
|
|
3170
|
+
//#endregion
|
|
2905
3171
|
//#region src/adapters/reporters/json.ts
|
|
2906
3172
|
/**
|
|
2907
3173
|
* Versioned machine-readable output — the public contract consumed by
|
|
@@ -2910,14 +3176,14 @@ const safeJsonText = (json) => json.replace(/[\u007f-\u009f\u061c\u200e\u200f\u2
|
|
|
2910
3176
|
*/
|
|
2911
3177
|
const SCHEMA_VERSION = 3;
|
|
2912
3178
|
const jsonReporter = {
|
|
2913
|
-
async format(result, io) {
|
|
2914
|
-
await io.stdout(
|
|
3179
|
+
async format(result, io, context) {
|
|
3180
|
+
await io.stdout(boundedJson({
|
|
2915
3181
|
schemaVersion: SCHEMA_VERSION,
|
|
2916
3182
|
siroVersion: version,
|
|
2917
3183
|
findings: result.findings,
|
|
2918
3184
|
summary: result.summary,
|
|
2919
3185
|
inspection: result.inspection
|
|
2920
|
-
},
|
|
3186
|
+
}, context.limits?.maxOutputBytes ?? DEFAULT_SCAN_LIMITS.maxOutputBytes, (context.limits?.maxConfigDepth ?? DEFAULT_SCAN_LIMITS.maxConfigDepth) + 16));
|
|
2921
3187
|
},
|
|
2922
3188
|
name: "json"
|
|
2923
3189
|
};
|
|
@@ -2963,9 +3229,11 @@ const buildRenderCtx = (io) => {
|
|
|
2963
3229
|
};
|
|
2964
3230
|
};
|
|
2965
3231
|
const prettyReporter = {
|
|
2966
|
-
async format(result, io) {
|
|
3232
|
+
async format(result, io, context) {
|
|
2967
3233
|
const lines = [];
|
|
3234
|
+
const consume = outputBudget(context.limits?.maxOutputBytes ?? DEFAULT_SCAN_LIMITS.maxOutputBytes);
|
|
2968
3235
|
const collect = (line) => {
|
|
3236
|
+
consume(`${line}\n`);
|
|
2969
3237
|
lines.push(line);
|
|
2970
3238
|
};
|
|
2971
3239
|
const ctx = buildRenderCtx({
|
|
@@ -2998,20 +3266,36 @@ const JSON_REPORTER_NAME = jsonReporter.name;
|
|
|
2998
3266
|
const BUILTIN_REPORTER_NAMES = BUILTINS.map((rep) => rep.name);
|
|
2999
3267
|
/** Later registrations replace earlier reporters with the same name. */
|
|
3000
3268
|
const createRegistry = (extras = []) => new Map([...BUILTINS, ...extras].map((reporter) => [reporter.name, reporter]));
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3269
|
+
//#endregion
|
|
3270
|
+
//#region src/runtime.ts
|
|
3271
|
+
/** Read time at evaluation, never at module initialization. */
|
|
3272
|
+
const rules = createBuiltinRules({
|
|
3273
|
+
now: () => Date.now(),
|
|
3274
|
+
parse: (value) => Date.parse(value)
|
|
3275
|
+
});
|
|
3276
|
+
const dependenciesFor = (options) => {
|
|
3277
|
+
if (!options || !nodePaths.isAbsolute(options.cwd)) throw new UsageError("cwd must be an absolute filesystem path.");
|
|
3278
|
+
const limits = resolveScanLimits(options.limits);
|
|
3279
|
+
if (options.rejectSymlinks !== void 0 && typeof options.rejectSymlinks !== "boolean") throw new UsageError("rejectSymlinks must be a boolean.");
|
|
3280
|
+
const native = options.fs === void 0 || options.fs === nodeFileSystem;
|
|
3281
|
+
if (options.rejectSymlinks && !native) throw new UsageError("rejectSymlinks requires the native filesystem; an injected FileSystem is trusted code.");
|
|
3282
|
+
if (native) assertDirectory(options.cwd);
|
|
3283
|
+
return {
|
|
3284
|
+
rules,
|
|
3285
|
+
fileSystem: native ? createNodeFileSystem(limits, options.rejectSymlinks ? options.cwd : void 0) : options.fs,
|
|
3286
|
+
paths: nodePaths,
|
|
3287
|
+
createRepoContext: (root, fs, projectType) => createRepoContext(root, fs, projectType, limits.maxConfigDepth),
|
|
3288
|
+
codecFor: createCodecFor(limits),
|
|
3289
|
+
compileExclusions
|
|
3290
|
+
};
|
|
3011
3291
|
};
|
|
3292
|
+
const nativeOptions = (options) => options?.fs === nodeFileSystem ? {
|
|
3293
|
+
...options,
|
|
3294
|
+
fs: void 0
|
|
3295
|
+
} : options;
|
|
3012
3296
|
/** Public Node API: callers can replace the filesystem without assembling the application. */
|
|
3013
|
-
const lint = (options) => lint$1(options,
|
|
3014
|
-
const lintCommand = (options, io) => lintCommand$1(options, io,
|
|
3297
|
+
const lint = (options) => lint$1(nativeOptions(options), dependenciesFor(options));
|
|
3298
|
+
const lintCommand = async (options, io) => lintCommand$1(nativeOptions(options), io, dependenciesFor(options), {
|
|
3015
3299
|
defaultName: DEFAULT_REPORTER_NAME,
|
|
3016
3300
|
createRegistry
|
|
3017
3301
|
});
|
|
@@ -3058,6 +3342,6 @@ const createNodeIO = (stdout, stderr) => ({
|
|
|
3058
3342
|
});
|
|
3059
3343
|
const nodeIO = createNodeIO(process.stdout, process.stderr);
|
|
3060
3344
|
//#endregion
|
|
3061
|
-
export {
|
|
3345
|
+
export { nodeFileSystem as A, isSeverity as C, asAbsPath as D, version as E, SiroError as F, UsageError as I, DEFAULT_SCAN_LIMITS as M, resolveScanLimits as N, asRelPath as O, ConfigError as P, isPM as S, SUPPORTED_NODE_RANGE as T, PROJECT_TYPES as _, DEFAULT_REPORTER_NAME as a, PMS as b, jsonReporter as c, overrideBindings as d, requireConfigKey as f, getByPath as g, defineRule as h, BUILTIN_REPORTER_NAMES as i, isNodeError as j, assertDirectory as k, safeText as l, loadConfig as m, lint as n, JSON_REPORTER_NAME as o, CONFIG_FILES as p, lintCommand as r, prettyReporter as s, nodeIO as t, githubReporter as u, isProjectType as v, isSupportedNodeVersion as w, SEVERITIES as x, isStableVersion as y };
|
|
3062
3346
|
|
|
3063
|
-
//# sourceMappingURL=node-io-
|
|
3347
|
+
//# sourceMappingURL=node-io-BpAHyeBG.mjs.map
|