1 | import from2 from "from2";
|
2 | import fs from "fs";
|
3 | import libReport from "istanbul-lib-report";
|
4 | import merge2 from "merge2";
|
5 | import sysPath from "path";
|
6 | import stream from "stream";
|
7 | import Vinyl from "vinyl";
|
8 | import { ReportOptions, StreamReporter, VinylReporter } from "./reporter";
|
9 |
|
10 | export interface ContextOptions extends libReport.Context {
|
11 | writer: libReport.FileWriter;
|
12 | }
|
13 |
|
14 | function createContext(options: ContextOptions): any {
|
15 | const context: libReport.Context = libReport.createContext(options);
|
16 | if (context.writer !== options.writer) {
|
17 | Object.defineProperty(context, "writer", {value: options.writer});
|
18 | }
|
19 | return context;
|
20 | }
|
21 |
|
22 | export function wrapFileReporter(reporter: libReport.Visitor): VinylReporter {
|
23 | function reportVinyl(options: Readonly<ReportOptions>): NodeJS.ReadableStream {
|
24 | let done: boolean = false;
|
25 | return from2({objectMode: true}, (_: number, next: (err: null | Error, obj: Vinyl) => void): void => {
|
26 | if (done) {
|
27 | next(null, null as any);
|
28 | return;
|
29 | }
|
30 | const cwd: string = process.cwd();
|
31 | const writer: VinylWriter = new VinylWriter(cwd, cwd, next);
|
32 |
|
33 | const context: libReport.Context = createContext({
|
34 | writer,
|
35 | sourceFinder: options.sourceFinder,
|
36 | } as any as ContextOptions);
|
37 | const tree: libReport.Tree = libReport.summarizers.pkg(options.map);
|
38 | tree.visit(reporter, context);
|
39 | done = true;
|
40 | });
|
41 | }
|
42 |
|
43 | return {reportVinyl};
|
44 | }
|
45 |
|
46 | class VinylWriter implements libReport.FileWriter {
|
47 | public readonly base: string;
|
48 | public readonly dir: string;
|
49 | private readonly next: (err: null | Error, obj: Vinyl) => void;
|
50 |
|
51 | constructor(base: string, dir: string, next: (err: null | Error, obj: Vinyl) => void) {
|
52 | this.base = base;
|
53 | this.dir = dir;
|
54 | this.next = next;
|
55 | }
|
56 |
|
57 | public copyFile(source: string, dest: string): void {
|
58 | const content: Buffer = fs.readFileSync(source);
|
59 | const resolvedPath: string = sysPath.join(this.dir, dest);
|
60 | const vinyl: Vinyl = new Vinyl({
|
61 | cwd: this.base,
|
62 | base: this.base,
|
63 | path: resolvedPath,
|
64 | contents: content,
|
65 | });
|
66 | this.next(null, vinyl);
|
67 | }
|
68 |
|
69 | public writeFile(file: string | null): VinylContentWriter {
|
70 | if (typeof file !== "string") {
|
71 | throw new Error("NotSupported: StreamWriter#writeFile(file: null)");
|
72 | }
|
73 | const resolvedPath: string = sysPath.join(this.dir, file);
|
74 | return new VinylContentWriter((content: Buffer): void => {
|
75 | const vinyl: Vinyl = new Vinyl({
|
76 | cwd: this.base,
|
77 | base: this.base,
|
78 | path: resolvedPath,
|
79 | contents: content,
|
80 | });
|
81 | this.next(null, vinyl);
|
82 | });
|
83 | }
|
84 |
|
85 | public writeForDir(subdir: string): VinylWriter {
|
86 | return new VinylWriter(this.base, sysPath.join(this.dir, subdir), this.next);
|
87 | }
|
88 | }
|
89 |
|
90 | class VinylContentWriter implements libReport.ContentWriter {
|
91 | private readonly chunks: string[];
|
92 | private readonly done: (content: Buffer) => void;
|
93 |
|
94 | constructor(done: (content: Buffer) => void) {
|
95 | this.chunks = [];
|
96 | this.done = done;
|
97 | }
|
98 |
|
99 | public write(str: string): void {
|
100 | this.chunks.push(str);
|
101 | }
|
102 |
|
103 | public println(str: string) {
|
104 | this.write(str);
|
105 | this.write("\n");
|
106 | }
|
107 |
|
108 | public colorize(str: string): string {
|
109 | return str;
|
110 | }
|
111 |
|
112 | public close(): void {
|
113 | return this.done(Buffer.from(this.chunks.join("")));
|
114 | }
|
115 | }
|
116 |
|
117 | export function wrapStreamReporter(reporter: libReport.Visitor): StreamReporter {
|
118 | function reportStream(options: Readonly<ReportOptions>): NodeJS.ReadableStream {
|
119 | const duplex: stream.Duplex = new stream.PassThrough();
|
120 | const writer: StreamWriter = new StreamWriter(duplex);
|
121 | const context: libReport.Context = createContext({
|
122 | writer,
|
123 | sourceFinder: options.sourceFinder,
|
124 | } as any as ContextOptions);
|
125 | const tree: libReport.Tree = libReport.summarizers.pkg(options.map);
|
126 | tree.visit(reporter, context);
|
127 | return duplex;
|
128 | }
|
129 |
|
130 | return {reportStream};
|
131 | }
|
132 |
|
133 | class StreamWriter implements libReport.FileWriter {
|
134 | private readonly stream: NodeJS.WritableStream;
|
135 | private fileName: string | null | undefined;
|
136 |
|
137 | constructor(stream: NodeJS.WritableStream) {
|
138 | this.stream = stream;
|
139 | this.fileName = undefined;
|
140 | }
|
141 |
|
142 | public copyFile(source: string, dest: string): void {
|
143 | throw new Error("NotSupported: StreamWriter#copyFile");
|
144 | }
|
145 |
|
146 | public writeFile(file: string | null): StreamContentWriter {
|
147 | if (typeof file !== "string") {
|
148 | file = null;
|
149 | }
|
150 | if (this.fileName === undefined) {
|
151 | this.fileName = file;
|
152 | } else if (file !== this.fileName) {
|
153 | throw new Error(`NotSupported: Write to multiple different files: ${this.fileName}, ${file}`);
|
154 | }
|
155 |
|
156 | return new StreamContentWriter(this.stream);
|
157 | }
|
158 |
|
159 | public writeForDir(subdir: string): StreamWriter {
|
160 | throw new Error("NotSupported: StreamWriter#writeForDir");
|
161 | }
|
162 | }
|
163 |
|
164 | class StreamContentWriter implements libReport.ContentWriter {
|
165 | private readonly stream: NodeJS.WritableStream;
|
166 |
|
167 | constructor(stream: NodeJS.WritableStream) {
|
168 | this.stream = stream;
|
169 | }
|
170 |
|
171 | public write(str: string): void {
|
172 | this.stream.write(Buffer.from(str));
|
173 | }
|
174 |
|
175 | public println(str: string) {
|
176 | this.write(str);
|
177 | this.write("\n");
|
178 | }
|
179 |
|
180 | public colorize(str: string): string {
|
181 | return str;
|
182 | }
|
183 |
|
184 | public close(): void {
|
185 | this.stream.end();
|
186 | }
|
187 | }
|
188 |
|
189 | export function toVinylOnlyReporter(reporter: StreamReporter, fileName: string): VinylReporter {
|
190 | function reportVinyl(options: Readonly<ReportOptions>): NodeJS.ReadableStream {
|
191 | let done: boolean = false;
|
192 | const wrappedStream: NodeJS.ReadableStream = from2(
|
193 | {objectMode: true},
|
194 | (_: number, next: (err: null | Error, obj: Vinyl) => void): void => {
|
195 | if (done) {
|
196 | next(null, null as any);
|
197 | return;
|
198 | }
|
199 | const stream: NodeJS.ReadableStream = reporter.reportStream(options);
|
200 | next(null, streamToVinyl(stream, fileName));
|
201 | done = true;
|
202 | },
|
203 | );
|
204 | const streams: NodeJS.ReadableStream[] = [wrappedStream];
|
205 | if (reporter.reportVinyl !== undefined) {
|
206 | streams.push(reporter.reportVinyl(options));
|
207 | }
|
208 | return merge2(streams);
|
209 | }
|
210 |
|
211 | return {reportVinyl};
|
212 | }
|
213 |
|
214 | function streamToVinyl(stream: NodeJS.ReadableStream, fileName: string): Vinyl {
|
215 | const cwd: string = process.cwd();
|
216 | const resolvedPath: string = sysPath.join(cwd, fileName);
|
217 | return new Vinyl({
|
218 | cwd,
|
219 | base: cwd,
|
220 | path: resolvedPath,
|
221 | contents: stream,
|
222 | });
|
223 | }
|