@pho9ubenaa/siro 0.5.0 → 0.6.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 +81 -0
- package/README.md +55 -87
- package/dist/cli.mjs +24 -18
- package/dist/index.d.mts +112 -65
- package/dist/index.mjs +2 -2
- package/dist/{node-io-CaqobxYp.mjs → node-io-CAszdmko.mjs} +1138 -1048
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as vb from "valibot";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/core/contracts/paths.d.ts
|
|
4
4
|
declare const AbsPathBrand: unique symbol;
|
|
5
5
|
declare const RelPathBrand: unique symbol;
|
|
6
6
|
type AbsPath = string & {
|
|
@@ -9,10 +9,9 @@ type AbsPath = string & {
|
|
|
9
9
|
type RelPath = string & {
|
|
10
10
|
readonly [RelPathBrand]: true;
|
|
11
11
|
};
|
|
12
|
-
declare const asAbsPath: (value: string) => AbsPath;
|
|
13
12
|
declare const asRelPath: (value: string) => RelPath;
|
|
14
13
|
//#endregion
|
|
15
|
-
//#region src/
|
|
14
|
+
//#region src/core/contracts/config-value.d.ts
|
|
16
15
|
declare const CODEC_KINDS: readonly ["json", "npmrc", "toml", "yaml"];
|
|
17
16
|
type CodecKind = (typeof CODEC_KINDS)[number];
|
|
18
17
|
/** Scalar value that can be written back to a config file. */
|
|
@@ -28,24 +27,19 @@ type ConfigReadValue = unknown;
|
|
|
28
27
|
/** Read an own key path; absent or non-mapping parents yield undefined. */
|
|
29
28
|
declare const getByPath: (config: ParsedConfig, keyPath: KeyPath) => ConfigReadValue;
|
|
30
29
|
//#endregion
|
|
31
|
-
//#region src/
|
|
32
|
-
/**
|
|
33
|
-
interface
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/** Resolve one ordinary child using this filesystem's native name semantics, returning its enumerated name. No symlinks. Undefined for absence; other errors propagate. Without this optional port, Deno literal prefixes use exact names from readDirectories. */
|
|
37
|
-
resolveDirectory?: (parent: AbsPath, name: string) => string | undefined;
|
|
38
|
-
/** Read a regular file (including a symlink to one); undefined only for ENOENT. Other errors and non-file entries must throw. */
|
|
39
|
-
readText: (path: AbsPath) => string | undefined;
|
|
40
|
-
/** True for a regular file (including a symlink to one); false only for ENOENT. Other errors and non-file entries must throw. */
|
|
41
|
-
exists: (path: AbsPath) => boolean;
|
|
30
|
+
//#region src/core/contracts/config-file-ref.d.ts
|
|
31
|
+
/** A repository-relative configuration location, independent of any rule. */
|
|
32
|
+
interface ConfigFileRef {
|
|
33
|
+
readonly kind: CodecKind;
|
|
34
|
+
readonly path: RelPath;
|
|
42
35
|
}
|
|
43
36
|
//#endregion
|
|
44
|
-
//#region src/
|
|
37
|
+
//#region src/core/contracts/pms.d.ts
|
|
45
38
|
/**
|
|
46
39
|
* Single source of truth for package managers and severities.
|
|
47
|
-
* Types, runtime sets, and ordering
|
|
48
|
-
*
|
|
40
|
+
* Types, runtime sets, and ordering derive from these tuples. Adding a manager
|
|
41
|
+
* still requires local detection signals, applicable rule bindings and verified
|
|
42
|
+
* policy/availability evidence; the tuple alone does not implement support.
|
|
49
43
|
*/
|
|
50
44
|
declare const PMS: readonly ["npm", "pnpm", "yarn", "bun", "deno", "aube"];
|
|
51
45
|
type PM = (typeof PMS)[number];
|
|
@@ -54,11 +48,7 @@ type Severity = (typeof SEVERITIES)[number];
|
|
|
54
48
|
declare const isPM: (value: string) => value is PM;
|
|
55
49
|
declare const isSeverity: (value: string) => value is Severity;
|
|
56
50
|
//#endregion
|
|
57
|
-
//#region src/
|
|
58
|
-
declare const PROJECT_TYPES: readonly ["application", "package"];
|
|
59
|
-
type ProjectType = (typeof PROJECT_TYPES)[number];
|
|
60
|
-
//#endregion
|
|
61
|
-
//#region src/domain/schemas/package-json.d.ts
|
|
51
|
+
//#region src/core/contracts/package-json.d.ts
|
|
62
52
|
declare const PackageJsonSchema: vb.LooseObjectSchema<{
|
|
63
53
|
readonly files: vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>;
|
|
64
54
|
readonly name: vb.OptionalSchema<vb.StringSchema<undefined>, undefined>;
|
|
@@ -66,12 +56,17 @@ declare const PackageJsonSchema: vb.LooseObjectSchema<{
|
|
|
66
56
|
readonly private: vb.OptionalSchema<vb.BooleanSchema<undefined>, undefined>;
|
|
67
57
|
readonly publishConfig: vb.OptionalSchema<vb.LooseObjectSchema<{
|
|
68
58
|
readonly access: vb.OptionalSchema<vb.PicklistSchema<["public", "restricted", "private"], undefined>, undefined>;
|
|
59
|
+
readonly provenance: vb.OptionalSchema<vb.BooleanSchema<undefined>, undefined>;
|
|
69
60
|
}, undefined>, undefined>;
|
|
70
61
|
readonly trustedDependencies: vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>;
|
|
71
62
|
}, undefined>;
|
|
72
63
|
type PackageJson = vb.InferOutput<typeof PackageJsonSchema>;
|
|
73
64
|
//#endregion
|
|
74
|
-
//#region src/
|
|
65
|
+
//#region src/core/contracts/project-type.d.ts
|
|
66
|
+
declare const PROJECT_TYPES: readonly ["application", "package"];
|
|
67
|
+
type ProjectType = (typeof PROJECT_TYPES)[number];
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/core/contracts/repo-context.d.ts
|
|
75
70
|
/** Read-only view of a repository, passed to every rule's `check`. */
|
|
76
71
|
interface RepoContext {
|
|
77
72
|
readonly root: AbsPath;
|
|
@@ -87,11 +82,7 @@ interface RuleContext extends RepoContext {
|
|
|
87
82
|
readConfig: (file: ConfigFileRef) => ParsedConfig;
|
|
88
83
|
}
|
|
89
84
|
//#endregion
|
|
90
|
-
//#region src/
|
|
91
|
-
interface ConfigFileRef {
|
|
92
|
-
readonly kind: CodecKind;
|
|
93
|
-
readonly path: RelPath;
|
|
94
|
-
}
|
|
85
|
+
//#region src/core/contracts/rule.d.ts
|
|
95
86
|
interface SetKeyOperation {
|
|
96
87
|
readonly op: 'setKey';
|
|
97
88
|
readonly file: ConfigFileRef;
|
|
@@ -127,7 +118,7 @@ type CheckStatus = {
|
|
|
127
118
|
readonly state: 'violations'; /** Independent findings; a rule chooses its own grouping granularity. */
|
|
128
119
|
readonly violations: readonly [ViolationStatus, ...ViolationStatus[]];
|
|
129
120
|
};
|
|
130
|
-
/** Display-only
|
|
121
|
+
/** Display-only annotations: presence and wording never change policy. */
|
|
131
122
|
interface VersionNote {
|
|
132
123
|
readonly configAvailableSince?: string;
|
|
133
124
|
readonly defaultSafeSince?: string;
|
|
@@ -154,42 +145,90 @@ interface Rule<Id extends string = string> {
|
|
|
154
145
|
}
|
|
155
146
|
declare const defineRule: <const Id extends string>(rule: Rule<Id>) => Rule<Id>;
|
|
156
147
|
//#endregion
|
|
157
|
-
//#region src/
|
|
158
|
-
|
|
159
|
-
|
|
148
|
+
//#region src/core/contracts/file-system.d.ts
|
|
149
|
+
/** IO boundary: swap in memfs (or any other backend) for tests. */
|
|
150
|
+
interface FileSystem {
|
|
151
|
+
/** Ordinary native child directory names, excluding symlinks. Required for recursive discovery. Errors must propagate. */
|
|
152
|
+
readDirectories: (path: AbsPath) => readonly string[];
|
|
153
|
+
/** Read a regular file (including a symlink to one); undefined only for ENOENT. Other errors and non-file entries must throw. */
|
|
154
|
+
readText: (path: AbsPath) => string | undefined;
|
|
155
|
+
/** True for a regular file (including a symlink to one); false only for ENOENT. Other errors and non-file entries must throw. */
|
|
156
|
+
exists: (path: AbsPath) => boolean;
|
|
157
|
+
}
|
|
160
158
|
//#endregion
|
|
161
|
-
//#region src/
|
|
162
|
-
/** Output sink
|
|
159
|
+
//#region src/core/contracts/io.d.ts
|
|
160
|
+
/** Output sink. Synchronous return values are ignored; returned promises are awaited. */
|
|
163
161
|
interface IO {
|
|
164
|
-
stdout: (line: string) =>
|
|
165
|
-
stderr: (line: string) =>
|
|
162
|
+
stdout: (line: string) => unknown;
|
|
163
|
+
stderr: (line: string) => unknown;
|
|
166
164
|
}
|
|
167
165
|
//#endregion
|
|
168
|
-
//#region src/
|
|
166
|
+
//#region src/core/contracts/lint-result.d.ts
|
|
169
167
|
interface Finding {
|
|
170
168
|
readonly ruleId: string;
|
|
171
|
-
|
|
169
|
+
/** Absent for PM-neutral publication checks. */
|
|
170
|
+
readonly pm?: PM;
|
|
171
|
+
/** Evaluation directory relative to cwd; root is '.'. */
|
|
172
|
+
readonly directory: string;
|
|
172
173
|
readonly severity: Severity;
|
|
173
174
|
readonly message: string;
|
|
174
175
|
readonly file?: string;
|
|
175
176
|
readonly remediation?: Remediation;
|
|
176
177
|
readonly expected?: ConfigValue;
|
|
177
178
|
readonly actual?: ConfigReadValue;
|
|
178
|
-
/** Official PM doc anchor for the setting this finding is about, if any. */
|
|
179
179
|
readonly docs?: string;
|
|
180
180
|
}
|
|
181
|
+
interface PolicyTarget {
|
|
182
|
+
readonly pm: PM;
|
|
183
|
+
/** Missing means unknown, not the installed binary's version. */
|
|
184
|
+
readonly version?: string;
|
|
185
|
+
}
|
|
186
|
+
interface Inspection {
|
|
187
|
+
readonly manifests: readonly {
|
|
188
|
+
readonly path: string;
|
|
189
|
+
readonly projectType: ProjectType; /** Empty means unknown: target-dependent availability is not evaluated. */
|
|
190
|
+
readonly targets: readonly PolicyTarget[];
|
|
191
|
+
}[];
|
|
192
|
+
readonly installationRoots: readonly {
|
|
193
|
+
readonly directory: string;
|
|
194
|
+
readonly targets: readonly PolicyTarget[];
|
|
195
|
+
}[];
|
|
196
|
+
}
|
|
181
197
|
interface LintResult {
|
|
182
198
|
readonly findings: readonly Finding[];
|
|
183
199
|
readonly summary: Readonly<Record<Severity, number>>;
|
|
200
|
+
/** Selected inputs, not an attestation that all security controls passed. */
|
|
201
|
+
readonly inspection: Inspection;
|
|
184
202
|
}
|
|
185
203
|
//#endregion
|
|
186
|
-
//#region src/
|
|
204
|
+
//#region src/core/contracts/reporter.d.ts
|
|
205
|
+
interface ReportContext {
|
|
206
|
+
readonly cwd: AbsPath;
|
|
207
|
+
}
|
|
187
208
|
interface Reporter<Name extends string = string> {
|
|
188
209
|
readonly name: Name;
|
|
189
|
-
format: (result: LintResult, io: IO) => void | Promise<void>;
|
|
210
|
+
format: (result: LintResult, io: IO, context: ReportContext) => void | Promise<void>;
|
|
190
211
|
}
|
|
191
212
|
//#endregion
|
|
192
|
-
//#region src/
|
|
213
|
+
//#region src/core/inspection-options.d.ts
|
|
214
|
+
type InstallationRootInput = string | {
|
|
215
|
+
readonly path: string;
|
|
216
|
+
readonly pm?: PM;
|
|
217
|
+
readonly pmVersion?: string;
|
|
218
|
+
};
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/core/contracts/date-time.d.ts
|
|
221
|
+
/** Host time and native date parsing, including the host timezone for offsetless input. */
|
|
222
|
+
interface DateTime {
|
|
223
|
+
now: () => number;
|
|
224
|
+
parse: (value: string) => number;
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/core/rules/builtin-rules.d.ts
|
|
228
|
+
declare const createBuiltinRules: (time: DateTime) => 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">, Rule<"unsupported-settings">];
|
|
229
|
+
type BuiltinRuleId = ReturnType<typeof createBuiltinRules>[number]['id'];
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/core/siro-config.d.ts
|
|
193
232
|
/** Per-rule setting. `'off'` disables; a Severity overrides the default level. */
|
|
194
233
|
type RuleSetting = Severity | 'off';
|
|
195
234
|
/**
|
|
@@ -203,6 +242,9 @@ type RuleSetting = Severity | 'off';
|
|
|
203
242
|
* })
|
|
204
243
|
*/
|
|
205
244
|
interface SiroConfig {
|
|
245
|
+
readonly exclude?: readonly string[];
|
|
246
|
+
/** Replaces the default ['.']; [] disables installation checks, not manifest checks. */
|
|
247
|
+
readonly installationRoots?: readonly InstallationRootInput[];
|
|
206
248
|
readonly pms?: readonly PM[];
|
|
207
249
|
/** Exact stable target versions; does not select managers or inspect installed binaries. */
|
|
208
250
|
readonly pmVersions?: Readonly<Partial<Record<PM, string>>>;
|
|
@@ -214,22 +256,33 @@ interface SiroConfig {
|
|
|
214
256
|
/** Identity helper for type-checked config files. */
|
|
215
257
|
declare const defineConfig: (config: SiroConfig) => SiroConfig;
|
|
216
258
|
//#endregion
|
|
217
|
-
//#region src/
|
|
259
|
+
//#region src/core/lint.d.ts
|
|
218
260
|
interface LintOptions {
|
|
219
261
|
readonly cwd: AbsPath;
|
|
220
262
|
readonly fs?: FileSystem;
|
|
263
|
+
/** Applies only to cwd, never to discovered children or additional installation roots. */
|
|
221
264
|
readonly pm?: PM;
|
|
222
|
-
/** Exact stable version for `pm`; overrides config.pmVersions and packageManager. */
|
|
223
265
|
readonly pmVersion?: string;
|
|
224
|
-
|
|
225
|
-
|
|
266
|
+
readonly exclude?: readonly string[];
|
|
267
|
+
/** Replaces config/default ['.']; [] disables installation checks. */
|
|
268
|
+
readonly installationRoots?: readonly InstallationRootInput[];
|
|
226
269
|
readonly projectType?: ProjectType;
|
|
227
270
|
/** Explicit configuration; the library never imports files from the target repository. */
|
|
228
271
|
readonly config?: SiroConfig;
|
|
229
272
|
}
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/core/lint-command.d.ts
|
|
275
|
+
interface LintCommandOptions extends LintOptions {
|
|
276
|
+
readonly reporter?: string | Reporter;
|
|
277
|
+
readonly severity?: Severity;
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/runtime.d.ts
|
|
281
|
+
/** Public Node API: callers can replace the filesystem without assembling the application. */
|
|
230
282
|
declare const lint: (options: LintOptions) => LintResult;
|
|
283
|
+
declare const lintCommand: (options: LintCommandOptions, io: IO) => Promise<number>;
|
|
231
284
|
//#endregion
|
|
232
|
-
//#region src/
|
|
285
|
+
//#region src/load-config.d.ts
|
|
233
286
|
/** Load the first matching config; executable imports always use the real filesystem. */
|
|
234
287
|
declare const loadConfig: (cwd: AbsPath, nodeVersion?: string) => Promise<SiroConfig | undefined>;
|
|
235
288
|
//#endregion
|
|
@@ -250,21 +303,12 @@ declare const jsonReporter: Reporter<'json'>;
|
|
|
250
303
|
declare const prettyReporter: Reporter<'pretty'>;
|
|
251
304
|
//#endregion
|
|
252
305
|
//#region src/adapters/reporters/registry.d.ts
|
|
253
|
-
declare const BUILTINS: readonly [Reporter<"pretty">, Reporter<"json">, Reporter<"github">];
|
|
254
306
|
/** Names of every built-in reporter, in stable display order. */
|
|
255
307
|
declare const BUILTIN_REPORTER_NAMES: readonly BuiltinReporterName[];
|
|
256
308
|
/** Literal union of every built-in reporter name. */
|
|
257
|
-
type BuiltinReporterName = (typeof
|
|
258
|
-
//#endregion
|
|
259
|
-
//#region src/application/commands/lint.d.ts
|
|
260
|
-
interface LintCommandOptions extends LintOptions {
|
|
261
|
-
readonly reporter?: string | Reporter;
|
|
262
|
-
readonly severity?: Severity;
|
|
263
|
-
}
|
|
264
|
-
/** Evaluate and report. Executable config loading belongs to the CLI adapter. */
|
|
265
|
-
declare const lintCommand: (options: LintCommandOptions, io: IO) => Promise<number>;
|
|
309
|
+
type BuiltinReporterName = (typeof prettyReporter | typeof jsonReporter | typeof githubReporter)['name'];
|
|
266
310
|
//#endregion
|
|
267
|
-
//#region src/
|
|
311
|
+
//#region src/core/config-files.d.ts
|
|
268
312
|
/** Known configuration locations and their parsers. */
|
|
269
313
|
declare const CONFIG_FILES: {
|
|
270
314
|
readonly aubeWorkspace: {
|
|
@@ -297,7 +341,8 @@ declare const CONFIG_FILES: {
|
|
|
297
341
|
};
|
|
298
342
|
};
|
|
299
343
|
//#endregion
|
|
300
|
-
//#region src/
|
|
344
|
+
//#region src/core/rules/builders/require-config-key.d.ts
|
|
345
|
+
/** One public helper binding: setting requirement, safe-default policy and proposal. */
|
|
301
346
|
interface RequireConfigKeySpec {
|
|
302
347
|
readonly file: ConfigFileRef;
|
|
303
348
|
readonly keyPath: KeyPath;
|
|
@@ -307,11 +352,10 @@ interface RequireConfigKeySpec {
|
|
|
307
352
|
readonly docs?: string;
|
|
308
353
|
readonly severity?: Severity;
|
|
309
354
|
accept?: (actual: unknown) => boolean;
|
|
310
|
-
/**
|
|
311
|
-
* An omitted value that is safe across every supported version and target
|
|
312
|
-
* environment emits info. Version-dependent defaults retain full severity.
|
|
313
|
-
*/
|
|
355
|
+
/** Recorded default value; does not establish safety without `defaultSafety`. */
|
|
314
356
|
readonly documentedDefault?: ConfigValue;
|
|
357
|
+
/** Only unconditional defaults may reduce severity; omitted means conditional. */
|
|
358
|
+
readonly defaultSafety?: 'unconditional' | 'conditional';
|
|
315
359
|
/**
|
|
316
360
|
* Severity used when `documentedDefault` satisfies the requirement. Defaults
|
|
317
361
|
* to `'info'` (advisory). Set `'off'` to silence the finding entirely.
|
|
@@ -335,7 +379,7 @@ declare const overrideBindings: <Id extends string>(rule: Rule<Id>, overrides: P
|
|
|
335
379
|
/** Build a Rule from a per-PM table of {file, keyPath, value, message}. */
|
|
336
380
|
declare const requireConfigKey: <const Id extends string>(options: RequireConfigKeyOptions<Id>) => Rule<Id>;
|
|
337
381
|
//#endregion
|
|
338
|
-
//#region src/
|
|
382
|
+
//#region src/core/contracts/errors.d.ts
|
|
339
383
|
declare class SiroError extends Error {
|
|
340
384
|
readonly exitCode: number;
|
|
341
385
|
constructor(message: string, exitCode: number);
|
|
@@ -350,5 +394,8 @@ declare class UsageError extends SiroError {
|
|
|
350
394
|
//#region src/version.d.ts
|
|
351
395
|
declare const version: string;
|
|
352
396
|
//#endregion
|
|
353
|
-
|
|
397
|
+
//#region src/adapters/node-paths.d.ts
|
|
398
|
+
declare const asAbsPath: (value: string) => AbsPath;
|
|
399
|
+
//#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 };
|
|
354
401
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
//#region src/
|
|
1
|
+
import { A as asRelPath, D as nodeFileSystem, M as SiroError, N as UsageError, S as isSeverity, T as version, _ as PROJECT_TYPES, b as SEVERITIES, c as jsonReporter, d as overrideBindings, f as requireConfigKey, g as getByPath, h as defineRule, i as BUILTIN_REPORTER_NAMES, j as ConfigError, k as asAbsPath, m as loadConfig, n as lint, p as CONFIG_FILES, r as lintCommand, s as prettyReporter, t as nodeIO, u as githubReporter, x as isPM, y as PMS } from "./node-io-CAszdmko.mjs";
|
|
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
|