@systemfsoftware/stryker-js-html-reporter 4.0.5 → 5.0.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 +24 -0
- package/README.md +4 -2
- package/dist/index.d.mts +14 -5
- package/dist/index.mjs +72 -40
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @systemfsoftware/stryker-js-html-reporter
|
|
2
2
|
|
|
3
|
+
## 5.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Each package's main entry point now groups its exports into namespaces named after a capability, such as `Plugin`, `TestRunner` and `Report`.
|
|
8
|
+
|
|
9
|
+
- Import the namespace and qualify each name, for example `Plugin.TestRunnerRpcs` after importing `Plugin` from the plugin interface.
|
|
10
|
+
- Import instrumenter schemas such as `Location` and `MutantStatus` from the instrumenter's `Mutant` namespace, and plugin-interface names from the plugin interface, instead of through another package's entry point.
|
|
11
|
+
- The engine's `/config`, `/events` and `/promises` entry points are unchanged.
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- Each run stage, checker call, report write and test-runner mutant run now records an OpenTelemetry span named after its cell. The span has `.read` and `.write` child spans and an `app.<name>.decision` or `app.<name>.failure` attribute holding the outcome. It also feeds an `app.<name>.duration` histogram labelled `result_class`.
|
|
16
|
+
|
|
17
|
+
- `makeHtmlReporter` can now be called with the reporter options alone, `makeHtmlReporter(options)`, and handed the reporter init when the run starts. The existing two-argument call, `makeHtmlReporter(options, init)`, keeps working exactly as before and remains the form the engine calls.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- The HTML report opens in the browser again. A cut-off logo image left the page markup unfinished, so the report never loaded and the page stayed blank.
|
|
22
|
+
|
|
23
|
+
- Updated dependencies:
|
|
24
|
+
- @systemfsoftware/stryker-js-instrumenter@9.0.0
|
|
25
|
+
- @systemfsoftware/stryker-js-plugin-interface@8.0.0
|
|
26
|
+
|
|
3
27
|
## 4.0.5
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -12,12 +12,14 @@ in-process reporter. The reporter exists so a programmatic consumer can assemble
|
|
|
12
12
|
its own host:
|
|
13
13
|
|
|
14
14
|
```ts
|
|
15
|
-
import {
|
|
15
|
+
import { HtmlReporter } from '@systemfsoftware/stryker-js-html-reporter'
|
|
16
|
+
|
|
17
|
+
HtmlReporter.makeHtmlReporter
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
## Related
|
|
19
21
|
|
|
20
|
-
- [`@systemfsoftware/stryker-js`][engine] — the run engine that emits the events this reporter presents, and the binary that binds `makeHtmlReporter` as a built-in reporter
|
|
22
|
+
- [`@systemfsoftware/stryker-js`][engine] — the run engine that emits the events this reporter presents, and the binary that binds `HtmlReporter.makeHtmlReporter` as a built-in reporter
|
|
21
23
|
|
|
22
24
|
## License
|
|
23
25
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { Sandwich, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
2
|
+
import { Options, Reporter } from "@systemfsoftware/stryker-js-plugin-interface";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import "effect/FileSystem";
|
|
5
|
+
import "effect/Path";
|
|
6
|
+
import "effect/Schema";
|
|
7
|
+
import { Schema } from "effect";
|
|
8
|
+
//#region src/write-html-report.cell.d.ts
|
|
9
|
+
declare const makeHtmlReporter: ((options: Options.StrykerOptions) => (init: Reporter.ReporterInit) => (events: AsyncIterable<Reporter.ReporterEvent>) => Effect.Effect<void, Reporter.ReporterFailed>) & ((options: Options.StrykerOptions, init: Reporter.ReporterInit) => (events: AsyncIterable<Reporter.ReporterEvent>) => Effect.Effect<void, Reporter.ReporterFailed>);
|
|
10
|
+
declare namespace mod_d_exports {
|
|
11
|
+
export { makeHtmlReporter };
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { mod_d_exports as HtmlReporter };
|
package/dist/index.mjs
CHANGED
|
@@ -1,29 +1,48 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
1
2
|
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
|
|
2
3
|
import * as NodePath from "@effect/platform-node-shared/NodePath";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { Sandwich, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
5
|
+
import { ErrorText } from "@systemfsoftware/stryker-js-instrumenter";
|
|
6
|
+
import { Reporter } from "@systemfsoftware/stryker-js-plugin-interface";
|
|
5
7
|
import * as Effect from "effect/Effect";
|
|
6
8
|
import * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import { dual } from "effect/Function";
|
|
7
10
|
import * as Layer from "effect/Layer";
|
|
8
11
|
import * as Match from "effect/Match";
|
|
12
|
+
import * as Option$1 from "effect/Option";
|
|
9
13
|
import * as Path from "effect/Path";
|
|
10
|
-
import * as Ref from "effect/Ref";
|
|
11
14
|
import * as S from "effect/Schema";
|
|
12
15
|
import * as Stream from "effect/Stream";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
import { Option, Schema } from "effect";
|
|
17
|
+
import * as Result from "effect/Result";
|
|
18
|
+
//#region src/render-html-report.workflow.ts
|
|
19
|
+
const RenderHtmlReportTypeId = Symbol.for("@systemfsoftware/stryker-js-html-reporter/RenderHtmlReportDecision");
|
|
20
|
+
var RenderHtmlReport = class extends Schema.TaggedClass()("RenderHtmlReport", {
|
|
21
|
+
fileName: Schema.String,
|
|
22
|
+
inlinedBundle: Schema.UndefinedOr(Schema.String)
|
|
23
|
+
}) {
|
|
24
|
+
static [Workflow.InstrumentationBrand] = { fileName: "stryker.html_report.file_name" };
|
|
25
|
+
};
|
|
26
|
+
var BundleInlined = class extends Schema.TaggedClass()("BundleInlined", { bundle: Schema.String }) {
|
|
27
|
+
[RenderHtmlReportTypeId] = RenderHtmlReportTypeId;
|
|
28
|
+
};
|
|
29
|
+
var BundleFromDisk = class extends Schema.TaggedClass()("BundleFromDisk", {}) {
|
|
30
|
+
[RenderHtmlReportTypeId] = RenderHtmlReportTypeId;
|
|
31
|
+
};
|
|
32
|
+
const RenderHtmlReportDecision = Schema.Union([BundleInlined, BundleFromDisk]);
|
|
33
|
+
const renderHtmlReport = Workflow.make({
|
|
34
|
+
command: RenderHtmlReport,
|
|
35
|
+
decision: RenderHtmlReportDecision,
|
|
36
|
+
error: Schema.Never,
|
|
37
|
+
decide: ({ inlinedBundle }) => Result.succeed(Option.match(Option.fromNullishOr(inlinedBundle), {
|
|
38
|
+
onNone: () => BundleFromDisk.make({}),
|
|
39
|
+
onSome: (bundle) => BundleInlined.make({ bundle })
|
|
40
|
+
}))
|
|
41
|
+
});
|
|
20
42
|
//#endregion
|
|
21
|
-
//#region src/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
function buildReportHtml(report, scriptContent) {
|
|
26
|
-
return `<!DOCTYPE html>
|
|
43
|
+
//#region src/write-html-report.cell.ts
|
|
44
|
+
const escapeHtmlTags = (json) => json.replace(/</g, "<\"+\"");
|
|
45
|
+
const buildReportHtml = (report, scriptContent) => `<!DOCTYPE html>
|
|
27
46
|
<html>
|
|
28
47
|
<head>
|
|
29
48
|
<meta charset="utf-8">
|
|
@@ -32,7 +51,7 @@ function buildReportHtml(report, scriptContent) {
|
|
|
32
51
|
<\/script>
|
|
33
52
|
</head>
|
|
34
53
|
<body>
|
|
35
|
-
<svg style="width: 80px; position:fixed; right:10px; bottom:10px; z-index:10" class="stryker-image" viewBox="0 0 1458 1458" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><path fill="none" d="M0 0h1458v1458H0z"/><clipPath id="a"><path d="M0 0h1458v1458H0z"/></clipPath><g clip-path="url(#a)"><path d="M1458 729c0 402.655-326.345 729-729 729S0 1131.655 0 729C0 326.445 326.345 0 729 0s729 326.345 729 729" fill="#e74c3c" fill-rule="nonzero"/><path d="M778.349 1456.15L576.6 1254.401l233-105 85-78.668v-64.332l-257-257-44-187-50-208 251.806-82.793L1076.6 389.401l380.14 379.15c-19.681 367.728-311.914 663.049-678.391 687.599z" fill-opacity=".3"/><path d="M753.4 329.503c41.79 0 74.579 7.83 97
|
|
54
|
+
<svg style="width: 80px; position:fixed; right:10px; bottom:10px; z-index:10" class="stryker-image" viewBox="0 0 1458 1458" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><path fill="none" d="M0 0h1458v1458H0z"/><clipPath id="a"><path d="M0 0h1458v1458H0z"/></clipPath><g clip-path="url(#a)"><path d="M1458 729c0 402.655-326.345 729-729 729S0 1131.655 0 729C0 326.445 326.345 0 729 0s729 326.345 729 729" fill="#e74c3c" fill-rule="nonzero"/><path d="M778.349 1456.15L576.6 1254.401l233-105 85-78.668v-64.332l-257-257-44-187-50-208 251.806-82.793L1076.6 389.401l380.14 379.15c-19.681 367.728-311.914 663.049-678.391 687.599z" fill-opacity=".3"/><path d="M753.4 329.503c41.79 0 74.579 7.83 97.925 25.444 23.571 18.015 41.69 43.956 55.167 77.097l11.662 28.679 165.733-58.183-14.137-32.13c-26.688-60.655-64.896-108.61-114.191-144.011-49.329-35.423-117.458-54.302-204.859-54.302-50.78 0-95.646 7.376-134.767 21.542-40.093 14.671-74.09 34.79-102.239 60.259-28.84 26.207-50.646 57.06-65.496 92.701-14.718 35.052-22.101 72.538-22.101 112.401 0 72.536 20.667 133.294 61.165 182.704 38.624 47.255 98.346 88.037 179.861 121.291 42.257 17.475 78.715 33.125 109.227 46.994 27.193 12.361 49.294 26.124 66.157 41.751 15.309 14.186 26.497 30.584 33.63 49.258 7.721 20.214 11.16 45.69 11.16 76.402 0 28.021-4.251 51.787-13.591 71.219-8.832 18.374-20.171 33.178-34.523 44.219-14.787 11.374-31.193 19.591-49.393 24.466-19.68 5.359-39.14 7.993-58.69 7.993-29.359 0-54.387-3.407-75.182-10.747-20.112-7.013-37.144-16.144-51.259-27.486-13.618-11.009-24.971-23.766-33.744-38.279-9.64-15.8-17.272-31.924-23.032-48.408l-10.965-31.376-161.669 60.585 10.734 30.124c10.191 28.601 24.197 56.228 42.059 82.748 18.208 27.144 41.322 51.369 69.525 72.745 27.695 21.075 60.904 38.218 99.481 51.041 37.777 12.664 82.004 19.159 132.552 19.159 49.998 0 95.818-8.321 137.611-24.622 42.228-16.471 78.436-38.992 108.835-67.291 30.719-28.597 54.631-62.103 71.834-100.642 17.263-38.56 25.923-79.392 25.923-122.248 0-54.339-8.368-100.37-24.208-138.32-16.29-38.759-38.252-71.661-65.948-98.797-26.965-26.418-58.269-48.835-93.858-67.175-33.655-17.241-69.196-33.11-106.593-47.533-35.934-13.429-65.822-26.601-89.948-39.525-22.153-11.868-40.009-24.21-53.547-37.309-11.429-11.13-19.83-23.678-24.718-37.664-5.413-15.49-7.98-33.423-7.98-53.577 0-40.883 11.293-71.522 37.086-90.539 28.443-20.825 64.985-30.658 109.311-30.658z" fill="#f1c40f" fill-rule="nonzero"/><path d="M720 0h18v113h-18zM1458 738v-18h-113v18h113zM720 1345h18v113h-18zM113 738v-18H0v18h113z"/></g></svg>
|
|
36
55
|
<mutation-test-report-app titlePostfix="Stryker">
|
|
37
56
|
Your browser doesn't support <a href="https://caniuse.com/#search=custom%20elements">custom elements</a>.
|
|
38
57
|
Please use a latest version of an evergreen browser (Firefox, Chrome, Safari, Opera, Edge, etc).
|
|
@@ -48,33 +67,46 @@ function buildReportHtml(report, scriptContent) {
|
|
|
48
67
|
<\/script>
|
|
49
68
|
</body>
|
|
50
69
|
</html>`;
|
|
51
|
-
}
|
|
52
|
-
const buildHtmlDocument = (command) => HtmlDocument.make({ html: buildReportHtml(command.report, command.scriptContent) });
|
|
53
70
|
const BUNDLE_SPECIFIER = "mutation-testing-elements/dist/mutation-test-elements.js";
|
|
54
|
-
const nodeFsPathLayer = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
|
|
55
71
|
const inlinedBundle = () => Match.value(typeof __STRYKER_HTML_REPORTER_CLIENT_BUNDLE__).pipe(Match.when("undefined", () => void 0), Match.orElse(() => __STRYKER_HTML_REPORTER_CLIENT_BUNDLE__));
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
report: event.report,
|
|
61
|
-
scriptContent: bundle
|
|
62
|
-
})).html));
|
|
63
|
-
const failAsHtmlReporter = (cause) => ReporterFailed.make({
|
|
64
|
-
reporterName: "html",
|
|
65
|
-
event: "mutationTestReportReady",
|
|
66
|
-
cause: errorToString(cause)
|
|
72
|
+
const readBundleFromDisk = Effect.gen(function* () {
|
|
73
|
+
const fs = yield* FileSystem.FileSystem;
|
|
74
|
+
const bundlePath = yield* (yield* Path.Path).fromFileUrl(new URL(import.meta.resolve(BUNDLE_SPECIFIER)));
|
|
75
|
+
return yield* fs.readFileString(bundlePath);
|
|
67
76
|
});
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
72
|
-
|
|
77
|
+
const writeHtmlFile = (fileName, html) => Effect.gen(function* () {
|
|
78
|
+
const fs = yield* FileSystem.FileSystem;
|
|
79
|
+
const path = yield* Path.Path;
|
|
80
|
+
yield* fs.makeDirectory(path.dirname(fileName), { recursive: true });
|
|
81
|
+
yield* fs.writeFileString(fileName, html);
|
|
82
|
+
});
|
|
83
|
+
const readRenderCommand = (input) => Effect.succeed({
|
|
84
|
+
_tag: "RenderHtmlReport",
|
|
85
|
+
fileName: input.fileName,
|
|
86
|
+
inlinedBundle: inlinedBundle(),
|
|
87
|
+
report: input.report
|
|
88
|
+
});
|
|
89
|
+
const writeHtmlReport = Sandwich.named("html_report.write")(readRenderCommand).decide(renderHtmlReport).write({
|
|
90
|
+
BundleInlined: ({ bundle }, command) => writeHtmlFile(command.fileName, buildReportHtml(command.report, bundle)),
|
|
91
|
+
BundleFromDisk: (_decision, command) => Effect.gen(function* () {
|
|
92
|
+
const bundle = yield* readBundleFromDisk;
|
|
93
|
+
yield* writeHtmlFile(command.fileName, buildReportHtml(command.report, bundle));
|
|
94
|
+
}),
|
|
95
|
+
CommandRejected: (rejected) => Effect.die(rejected)
|
|
96
|
+
});
|
|
97
|
+
const nodeFsPathLayer = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
|
|
98
|
+
const failAsHtmlReporter = (cause) => Reporter.ReporterFailed.make({
|
|
73
99
|
reporterName: "html",
|
|
74
100
|
event: "mutationTestReportReady",
|
|
75
|
-
cause:
|
|
101
|
+
cause: Option$1.getOrElse(Option$1.map(ErrorText.ErrorText.fromCause(cause), (rendered) => rendered.text), () => "")
|
|
76
102
|
});
|
|
77
|
-
const drainEvents = (fileName, events) =>
|
|
78
|
-
|
|
103
|
+
const drainEvents = (fileName, events) => Stream.runForEach(Stream.fromAsyncIterable(events, failAsHtmlReporter).pipe(Stream.filter(S.is(Reporter.MutationTestReportReady))), (ready) => writeHtmlReport.run({
|
|
104
|
+
fileName,
|
|
105
|
+
report: ready.report
|
|
106
|
+
}).pipe(Effect.mapError(failAsHtmlReporter)));
|
|
107
|
+
const makeHtmlReporter = dual(2, (options, _init) => (events) => Layer.build(nodeFsPathLayer).pipe(Effect.flatMap((platform) => Effect.provideContext(drainEvents(options.htmlReporter.fileName, events), platform)), Effect.scoped));
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/HtmlReporter/mod.ts
|
|
110
|
+
var HtmlReporter_exports = /* @__PURE__ */ __exportAll({ makeHtmlReporter: () => makeHtmlReporter });
|
|
79
111
|
//#endregion
|
|
80
|
-
export {
|
|
112
|
+
export { HtmlReporter_exports as HtmlReporter };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/stryker-js-html-reporter",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/systemfsoftware/stryker-js-effect.git",
|
|
@@ -14,21 +14,21 @@
|
|
|
14
14
|
"dist"
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@effect/platform-node-shared": "4.0.0-rc.
|
|
18
|
-
"@systemfsoftware/effect-cell-types": "^
|
|
17
|
+
"@effect/platform-node-shared": "4.0.0-rc.117",
|
|
18
|
+
"@systemfsoftware/effect-cell-types": "^10.1.1",
|
|
19
19
|
"mutation-testing-elements": "3.9.0",
|
|
20
|
-
"@systemfsoftware/stryker-js-instrumenter": "^
|
|
21
|
-
"@systemfsoftware/stryker-js-plugin-interface": "^
|
|
20
|
+
"@systemfsoftware/stryker-js-instrumenter": "^9.0.0",
|
|
21
|
+
"@systemfsoftware/stryker-js-plugin-interface": "^8.0.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@effect/tsgo": "^0.45.0",
|
|
25
25
|
"@microsoft/api-extractor": "^7",
|
|
26
26
|
"@systemfsoftware/arethetypeswrong-cli": "^4.2.0",
|
|
27
|
-
"@systemfsoftware/effect-gherkin-spec": "^4.
|
|
28
|
-
"@systemfsoftware/oxlint-config-recommended": "^
|
|
29
|
-
"@systemfsoftware/tsconfig": "^
|
|
27
|
+
"@systemfsoftware/effect-gherkin-spec": "^4.3.1",
|
|
28
|
+
"@systemfsoftware/oxlint-config-recommended": "^2.0.2",
|
|
29
|
+
"@systemfsoftware/tsconfig": "^2.0.1",
|
|
30
30
|
"@types/node": "^24",
|
|
31
|
-
"effect": "4.0.0-rc.
|
|
31
|
+
"effect": "4.0.0-rc.117",
|
|
32
32
|
"oxlint": "^1",
|
|
33
33
|
"oxlint-tsgolint": "^7",
|
|
34
34
|
"rimraf": "^6.1.3",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"provenance": true
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"effect": "4.0.0-rc.
|
|
50
|
+
"effect": "4.0.0-rc.117"
|
|
51
51
|
},
|
|
52
52
|
"exports": {
|
|
53
53
|
".": {
|
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
"scripts": {
|
|
60
60
|
"clean": "rimraf dist",
|
|
61
61
|
"build": "tsdown",
|
|
62
|
-
"typecheck": "tsc
|
|
62
|
+
"typecheck": "tsc -b",
|
|
63
63
|
"attw": "attw --pack .",
|
|
64
64
|
"test": "vitest run",
|
|
65
65
|
"lint": "f=${OXLINT_FORMAT:-${AGENT:+agent}}; oxlint --format=${f:-default}",
|
|
66
66
|
"api:check": "api-extractor run",
|
|
67
67
|
"api:update": "api-extractor run --local",
|
|
68
|
-
"lint:tsgo": "effect-tsgo diagnostics --project tsconfig.json --format ${TSGO_FORMAT:-text}"
|
|
68
|
+
"lint:tsgo": "effect-tsgo diagnostics --project tsconfig.app.json --format ${TSGO_FORMAT:-text} && effect-tsgo diagnostics --project tsconfig.test.json --format ${TSGO_FORMAT:-text}"
|
|
69
69
|
}
|
|
70
70
|
}
|