@open-wa/driver-puppeteer 5.0.0-alpha.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.
@@ -0,0 +1,470 @@
1
+ import {
2
+ IPage,
3
+ IElementHandle,
4
+ DriverCapabilities,
5
+ type DisposableHandle,
6
+ type IConsoleMessage,
7
+ type IFrame,
8
+ type IRequest,
9
+ type IRequestContinueOverrides,
10
+ type IRequestResponse,
11
+ type NavigationWaitUntil,
12
+ type PageEventHandler,
13
+ type WaitForFunctionOptions,
14
+ } from '@open-wa/driver-interface';
15
+ import type { ConsoleMessage, Frame, HTTPRequest, Page, CDPSession } from 'puppeteer';
16
+ import { PuppeteerElementHandle } from './PuppeteerElementHandle';
17
+
18
+ type ListenerWrapper = (...args: any[]) => void | Promise<void>;
19
+
20
+ class PuppeteerDisposableHandle implements DisposableHandle {
21
+ constructor(private readonly disposer: () => Promise<void> | void) { }
22
+
23
+ dispose(): Promise<void> | void {
24
+ return this.disposer();
25
+ }
26
+ }
27
+
28
+ class PuppeteerFrame implements IFrame {
29
+ constructor(private readonly frame: Frame, private readonly isMain: boolean) { }
30
+
31
+ url(): string {
32
+ return this.frame.url();
33
+ }
34
+
35
+ name(): string {
36
+ return this.frame.name();
37
+ }
38
+
39
+ isMainFrame(): boolean {
40
+ return this.isMain;
41
+ }
42
+
43
+ parentFrame(): IFrame | null {
44
+ const parent = this.frame.parentFrame();
45
+ return parent ? new PuppeteerFrame(parent, false) : null;
46
+ }
47
+
48
+ async content(): Promise<string> {
49
+ return this.frame.content();
50
+ }
51
+
52
+ unwrap(): Frame {
53
+ return this.frame;
54
+ }
55
+ }
56
+
57
+ function normalizeSnippet(input: string): string {
58
+ return input.replace(/\s+/g, ' ').trim().slice(0, 160);
59
+ }
60
+
61
+ function enrichEvaluateError(kind: 'evaluate' | 'evaluateScript', source: string, error: unknown): never {
62
+ const normalized = error instanceof Error ? error : new Error(String(error));
63
+ const preview = normalizeSnippet(source);
64
+ const enriched = new Error(
65
+ `[puppeteer:${kind}] ${normalized.message} | scriptBytes=${source.length} | scriptPreview=${preview}`,
66
+ );
67
+ enriched.stack = normalized.stack ?? enriched.message;
68
+ throw enriched;
69
+ }
70
+
71
+ function enrichWaitError(
72
+ kind: 'waitForSelector' | 'waitForFunction' | 'waitForFunctionFn',
73
+ details: Record<string, unknown>,
74
+ error: unknown,
75
+ ): never {
76
+ const normalized = error instanceof Error ? error : new Error(String(error));
77
+ const detailText = Object.entries(details)
78
+ .filter(([, value]) => typeof value !== 'undefined')
79
+ .map(([key, value]) => `${key}=${typeof value === 'string' ? value : JSON.stringify(value)}`)
80
+ .join(' | ');
81
+ const enriched = new Error(`[puppeteer:${kind}] ${normalized.message}${detailText ? ` | ${detailText}` : ''}`);
82
+ enriched.stack = normalized.stack ?? enriched.message;
83
+ throw enriched;
84
+ }
85
+
86
+ class PuppeteerRequest implements IRequest {
87
+ constructor(private readonly request: HTTPRequest, private readonly page: Page) { }
88
+
89
+ url(): string {
90
+ return this.request.url();
91
+ }
92
+
93
+ method(): string {
94
+ return this.request.method();
95
+ }
96
+
97
+ headers(): Record<string, string> {
98
+ return this.request.headers();
99
+ }
100
+
101
+ resourceType(): string | null {
102
+ return this.request.resourceType?.() ?? null;
103
+ }
104
+
105
+ isNavigationRequest(): boolean {
106
+ return this.request.isNavigationRequest();
107
+ }
108
+
109
+ frame(): IFrame | null {
110
+ const frame = this.request.frame();
111
+ return frame ? new PuppeteerFrame(frame, frame === this.page.mainFrame()) : null;
112
+ }
113
+
114
+ async abort(errorCode?: string): Promise<void> {
115
+ await this.request.abort(errorCode as never);
116
+ }
117
+
118
+ async continue(overrides?: IRequestContinueOverrides): Promise<void> {
119
+ await this.request.continue(overrides);
120
+ }
121
+
122
+ async respond(response: IRequestResponse): Promise<void> {
123
+ await this.request.respond({
124
+ ...response,
125
+ body: response.body instanceof Uint8Array ? Buffer.from(response.body) : response.body,
126
+ } as never);
127
+ }
128
+
129
+ unwrap(): HTTPRequest {
130
+ return this.request;
131
+ }
132
+ }
133
+
134
+ class PuppeteerConsoleMessage implements IConsoleMessage {
135
+ constructor(private readonly message: ConsoleMessage) { }
136
+
137
+ type(): string {
138
+ return this.message.type();
139
+ }
140
+
141
+ text(): string {
142
+ return this.message.text();
143
+ }
144
+
145
+ location(): { url?: string; lineNumber?: number; columnNumber?: number } {
146
+ const location = this.message.location();
147
+ return {
148
+ url: location.url,
149
+ lineNumber: location.lineNumber,
150
+ columnNumber: location.columnNumber,
151
+ };
152
+ }
153
+
154
+ args(): unknown[] {
155
+ return this.message.args();
156
+ }
157
+
158
+ unwrap(): ConsoleMessage {
159
+ return this.message;
160
+ }
161
+ }
162
+
163
+ export class PuppeteerPage implements IPage {
164
+ readonly name = 'puppeteer' as const;
165
+ private readonly listenerWrappers = new Map<string, Map<Function, Set<ListenerWrapper>>>();
166
+
167
+ constructor(
168
+ private page: Page,
169
+ private capabilities: DriverCapabilities
170
+ ) { }
171
+
172
+ async goto(url: string, options?: { waitUntil?: NavigationWaitUntil; timeoutMs?: number }): Promise<void> {
173
+ await this.page.goto(url, {
174
+ waitUntil: options?.waitUntil as any,
175
+ timeout: options?.timeoutMs,
176
+ });
177
+ }
178
+
179
+ url(): string {
180
+ return this.page.url();
181
+ }
182
+
183
+ async reload(): Promise<void> {
184
+ await this.page.reload();
185
+ }
186
+
187
+ mainFrame(): IFrame {
188
+ return new PuppeteerFrame(this.page.mainFrame(), true);
189
+ }
190
+
191
+ async evaluateOnNewDocument<Arg, Ret>(
192
+ fn: (arg: Arg) => Ret | Promise<Ret>,
193
+ arg: Arg
194
+ ): Promise<void> {
195
+ await this.page.evaluateOnNewDocument(fn as any, arg as any);
196
+ }
197
+
198
+ async evaluate<Arg, Ret>(fn: (arg: Arg) => Ret | Promise<Ret>, arg: Arg): Promise<Ret> {
199
+ try {
200
+ return await this.page.evaluate(fn as any, arg as any) as Ret;
201
+ } catch (error) {
202
+ const fnSource = typeof fn === 'function' ? fn.toString() : '[anonymous evaluate fn]';
203
+ return enrichEvaluateError('evaluate', fnSource, error);
204
+ }
205
+ }
206
+
207
+ async evaluateScript<Ret = unknown>(script: string): Promise<Ret> {
208
+ try {
209
+ return await this.page.evaluate(script) as Ret;
210
+ } catch (error) {
211
+ return enrichEvaluateError('evaluateScript', script, error);
212
+ }
213
+ }
214
+
215
+ async addInitScript(script: string): Promise<DisposableHandle> {
216
+ const registration = await (this.page as any).evaluateOnNewDocument(script);
217
+ const identifier = this.getInitScriptIdentifier(registration);
218
+ let disposed = false;
219
+
220
+ return new PuppeteerDisposableHandle(async () => {
221
+ if (disposed) {
222
+ return;
223
+ }
224
+
225
+ disposed = true;
226
+ if (identifier && typeof (this.page as any).removeScriptToEvaluateOnNewDocument === 'function') {
227
+ await (this.page as any).removeScriptToEvaluateOnNewDocument(identifier);
228
+ }
229
+ });
230
+ }
231
+
232
+ async setViewport(viewport: { width: number; height: number }): Promise<void> {
233
+ await this.page.setViewport(viewport);
234
+ }
235
+
236
+ async setUserAgent(ua: string): Promise<void> {
237
+ await this.page.setUserAgent(ua);
238
+ }
239
+
240
+ async setRequestInterception(enabled: boolean): Promise<void> {
241
+ this.require('requestInterception');
242
+ await this.page.setRequestInterception(enabled);
243
+ }
244
+
245
+ async waitForSelector(selector: string, options?: { timeoutMs?: number }): Promise<IElementHandle | null> {
246
+ try {
247
+ const element = await this.page.waitForSelector(selector, { timeout: options?.timeoutMs });
248
+ return element ? new PuppeteerElementHandle(element) : null;
249
+ } catch (error) {
250
+ return enrichWaitError('waitForSelector', {
251
+ selector,
252
+ timeoutMs: options?.timeoutMs,
253
+ }, error);
254
+ }
255
+ }
256
+
257
+ async waitForFunction<Arg>(script: string, options?: WaitForFunctionOptions): Promise<void>;
258
+ async waitForFunction<Arg>(fn: (arg: Arg) => boolean, arg: Arg, options?: WaitForFunctionOptions): Promise<void>;
259
+ async waitForFunction<Arg>(fnOrScript: string | ((arg: Arg) => boolean), argOrOptions?: Arg | WaitForFunctionOptions, maybeOptions?: WaitForFunctionOptions): Promise<void> {
260
+ if (typeof fnOrScript === 'string') {
261
+ const options = argOrOptions as WaitForFunctionOptions | undefined;
262
+ try {
263
+ await this.page.waitForFunction(fnOrScript, {
264
+ timeout: options?.timeoutMs,
265
+ polling: options?.polling as any,
266
+ });
267
+ return;
268
+ } catch (error) {
269
+ return enrichWaitError('waitForFunction', {
270
+ timeoutMs: options?.timeoutMs,
271
+ polling: options?.polling,
272
+ scriptBytes: fnOrScript.length,
273
+ scriptPreview: normalizeSnippet(fnOrScript),
274
+ }, error);
275
+ }
276
+ }
277
+
278
+ const options = maybeOptions;
279
+ try {
280
+ await this.page.waitForFunction(fnOrScript as any, {
281
+ timeout: options?.timeoutMs,
282
+ polling: options?.polling as any,
283
+ }, argOrOptions as any);
284
+ } catch (error) {
285
+ return enrichWaitError('waitForFunctionFn', {
286
+ timeoutMs: options?.timeoutMs,
287
+ polling: options?.polling,
288
+ fnPreview: normalizeSnippet(fnOrScript.toString()),
289
+ argPreview: typeof argOrOptions === 'undefined' ? undefined : normalizeSnippet(JSON.stringify(argOrOptions)),
290
+ }, error);
291
+ }
292
+ }
293
+
294
+ async $(selector: string): Promise<IElementHandle | null> {
295
+ const element = await this.page.$(selector);
296
+ return element ? new PuppeteerElementHandle(element) : null;
297
+ }
298
+
299
+ async $$(selector: string): Promise<IElementHandle[]> {
300
+ const elements = await this.page.$$(selector);
301
+ return elements.map(el => new PuppeteerElementHandle(el));
302
+ }
303
+
304
+ async click(selector: string): Promise<void> {
305
+ await this.page.click(selector);
306
+ }
307
+
308
+ async type(selector: string, text: string, options?: { delayMs?: number }): Promise<void> {
309
+ await this.page.type(selector, text, { delay: options?.delayMs });
310
+ }
311
+
312
+ async screenshot(options?: { type?: 'png' | 'jpeg'; fullPage?: boolean }): Promise<Uint8Array> {
313
+ const buffer = await this.page.screenshot(options);
314
+ return new Uint8Array(buffer);
315
+ }
316
+
317
+ async exposeFunction(name: string, fn: (...args: any[]) => any): Promise<void> {
318
+ await this.page.exposeFunction(name, fn);
319
+ }
320
+
321
+ on<K extends string>(event: K, handler: ((...args: any[]) => void | Promise<void>) | PageEventHandler<any>): DisposableHandle {
322
+ const wrapped = (...args: any[]) => {
323
+ const payload = this.wrapEventPayload(event, args);
324
+ if (event === 'close') {
325
+ return (handler as () => void | Promise<void>)();
326
+ }
327
+ return (handler as (payload: unknown) => void | Promise<void>)(payload);
328
+ };
329
+
330
+ this.getWrapperSet(event, handler as Function).add(wrapped);
331
+ this.page.on(event as any, wrapped as any);
332
+
333
+ return new PuppeteerDisposableHandle(() => {
334
+ this.removeWrappedListener(event, handler as Function, wrapped);
335
+ });
336
+ }
337
+
338
+ off<K extends string>(event: K, handler: ((...args: any[]) => void | Promise<void>) | PageEventHandler<any>): void {
339
+ const wrappers = this.listenerWrappers.get(event)?.get(handler as Function);
340
+ if (!wrappers || wrappers.size === 0) {
341
+ this.page.off(event as any, handler as any);
342
+ return;
343
+ }
344
+
345
+ for (const wrapped of wrappers) {
346
+ this.page.off(event as any, wrapped as any);
347
+ }
348
+
349
+ this.listenerWrappers.get(event)?.delete(handler as Function);
350
+ }
351
+
352
+ async close(): Promise<void> {
353
+ await this.page.close();
354
+ }
355
+
356
+ isClosed(): boolean {
357
+ return this.page.isClosed();
358
+ }
359
+
360
+ has<C extends string>(cap: C): boolean {
361
+ return (this.capabilities as any)[cap]?.supported === true;
362
+ }
363
+
364
+ require<C extends string>(cap: C): void {
365
+ if (!this.has(cap)) {
366
+ const capability = (this.capabilities as any)[cap];
367
+ throw new Error(
368
+ `Page does not support capability '${cap}'${capability?.reason ? `: ${capability.reason}` : ''
369
+ }`
370
+ );
371
+ }
372
+ }
373
+
374
+ async cdp(): Promise<CDPSession> {
375
+ this.require('cdp');
376
+ return await this.page.createCDPSession();
377
+ }
378
+
379
+ async setBypassServiceWorker(bypass: boolean): Promise<void> {
380
+ this.require('serviceWorkerBypass');
381
+ await this.page.setBypassServiceWorker(bypass);
382
+ }
383
+
384
+ async pdf(options?: { path?: string; format?: string }): Promise<Uint8Array> {
385
+ this.require('pdf');
386
+ const buffer = await this.page.pdf(options as any);
387
+ return new Uint8Array(buffer);
388
+ }
389
+
390
+ async startTracing(options?: { path?: string; screenshots?: boolean }): Promise<void> {
391
+ this.require('tracing');
392
+ await this.page.tracing.start(options);
393
+ }
394
+
395
+ async stopTracing(): Promise<Uint8Array> {
396
+ this.require('tracing');
397
+ const buffer = await this.page.tracing.stop();
398
+ return buffer ? new Uint8Array(buffer) : new Uint8Array();
399
+ }
400
+
401
+ unwrap(): Page {
402
+ return this.page;
403
+ }
404
+
405
+ private getWrapperSet(event: string, handler: Function): Set<ListenerWrapper> {
406
+ let wrappers = this.listenerWrappers.get(event);
407
+ if (!wrappers) {
408
+ wrappers = new Map();
409
+ this.listenerWrappers.set(event, wrappers);
410
+ }
411
+
412
+ let handlerWrappers = wrappers.get(handler);
413
+ if (!handlerWrappers) {
414
+ handlerWrappers = new Set();
415
+ wrappers.set(handler, handlerWrappers);
416
+ }
417
+
418
+ return handlerWrappers;
419
+ }
420
+
421
+ private removeWrappedListener(event: string, handler: Function, wrapped: ListenerWrapper): void {
422
+ this.page.off(event as any, wrapped as any);
423
+
424
+ const eventWrappers = this.listenerWrappers.get(event);
425
+ const handlerWrappers = eventWrappers?.get(handler);
426
+ if (!handlerWrappers) {
427
+ return;
428
+ }
429
+
430
+ handlerWrappers.delete(wrapped);
431
+ if (handlerWrappers.size === 0) {
432
+ eventWrappers?.delete(handler);
433
+ }
434
+ if (eventWrappers && eventWrappers.size === 0) {
435
+ this.listenerWrappers.delete(event);
436
+ }
437
+ }
438
+
439
+ private getInitScriptIdentifier(registration: unknown): string | undefined {
440
+ if (typeof registration === 'string') {
441
+ return registration;
442
+ }
443
+
444
+ if (registration && typeof registration === 'object') {
445
+ const candidate = (registration as { identifier?: unknown; id?: unknown }).identifier
446
+ ?? (registration as { identifier?: unknown; id?: unknown }).id;
447
+ return typeof candidate === 'string' ? candidate : undefined;
448
+ }
449
+
450
+ return undefined;
451
+ }
452
+
453
+ private wrapEventPayload(event: string, args: any[]): unknown {
454
+ const [payload] = args;
455
+ switch (event) {
456
+ case 'request':
457
+ case 'requestfailed':
458
+ case 'requestfinished':
459
+ return new PuppeteerRequest(payload as HTTPRequest, this.page);
460
+ case 'framenavigated':
461
+ return new PuppeteerFrame(payload as Frame, payload === this.page.mainFrame());
462
+ case 'console':
463
+ return new PuppeteerConsoleMessage(payload as ConsoleMessage);
464
+ case 'pageerror':
465
+ return payload as Error;
466
+ default:
467
+ return payload;
468
+ }
469
+ }
470
+ }
@@ -0,0 +1,149 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { PuppeteerPage } from '../PuppeteerPage';
3
+
4
+ type MockHandler = (...args: any[]) => void | Promise<void>;
5
+
6
+ class MockPuppeteerPage {
7
+ private readonly listeners = new Map<string, Set<MockHandler>>();
8
+
9
+ readonly mainFrameValue = {
10
+ url: () => 'https://example.test',
11
+ name: () => 'main',
12
+ parentFrame: () => null,
13
+ content: vi.fn(async () => '<html></html>'),
14
+ };
15
+
16
+ readonly setViewport = vi.fn(async () => undefined);
17
+ readonly setUserAgent = vi.fn(async () => undefined);
18
+ readonly setRequestInterception = vi.fn(async () => undefined);
19
+ readonly waitForSelector = vi.fn(async () => null);
20
+ readonly waitForFunction = vi.fn(async () => undefined);
21
+ readonly $ = vi.fn(async () => null);
22
+ readonly $$ = vi.fn(async () => []);
23
+ readonly click = vi.fn(async () => undefined);
24
+ readonly type = vi.fn(async () => undefined);
25
+ readonly screenshot = vi.fn(async () => Buffer.from([]));
26
+ readonly exposeFunction = vi.fn(async () => undefined);
27
+ readonly close = vi.fn(async () => undefined);
28
+ readonly goto = vi.fn(async () => undefined);
29
+ readonly reload = vi.fn(async () => undefined);
30
+ readonly evaluate = vi.fn(async () => undefined);
31
+ readonly url = vi.fn(() => 'about:blank');
32
+ readonly isClosed = vi.fn(() => false);
33
+ readonly evaluateOnNewDocument = vi.fn(async () => ({ identifier: 'init-script-1' }));
34
+ readonly removeScriptToEvaluateOnNewDocument = vi.fn(async () => undefined);
35
+
36
+ mainFrame() {
37
+ return this.mainFrameValue;
38
+ }
39
+
40
+ on(event: string, handler: MockHandler): void {
41
+ const handlers = this.listeners.get(event) ?? new Set<MockHandler>();
42
+ handlers.add(handler);
43
+ this.listeners.set(event, handlers);
44
+ }
45
+
46
+ off(event: string, handler: MockHandler): void {
47
+ this.listeners.get(event)?.delete(handler);
48
+ }
49
+
50
+ async emit(event: string, payload?: unknown): Promise<void> {
51
+ const handlers = [...(this.listeners.get(event) ?? [])];
52
+ await Promise.all(handlers.map((handler) => Promise.resolve(handler(payload))));
53
+ }
54
+ }
55
+
56
+ const capabilities = {
57
+ cdp: { supported: true },
58
+ requestInterception: { supported: true },
59
+ serviceWorkerBypass: { supported: true },
60
+ stealth: { supported: true },
61
+ pdf: { supported: true },
62
+ tracing: { supported: true },
63
+ persistentContext: { supported: true },
64
+ browserExtensions: { supported: true },
65
+ exposeBinding: { supported: true },
66
+ };
67
+
68
+ describe('PuppeteerPage', () => {
69
+ it('keeps duplicate listeners independently disposable', async () => {
70
+ const rawPage = new MockPuppeteerPage();
71
+ const page = new PuppeteerPage(rawPage as any, capabilities as any);
72
+ const handler = vi.fn();
73
+
74
+ const first = page.on('request', handler);
75
+ const second = page.on('request', handler);
76
+
77
+ const request = {
78
+ url: () => 'https://example.test',
79
+ method: () => 'GET',
80
+ headers: () => ({ accept: 'application/json' }),
81
+ resourceType: () => 'document',
82
+ isNavigationRequest: () => true,
83
+ frame: () => rawPage.mainFrameValue,
84
+ abort: vi.fn(async () => undefined),
85
+ continue: vi.fn(async () => undefined),
86
+ respond: vi.fn(async () => undefined),
87
+ };
88
+
89
+ await rawPage.emit('request', request);
90
+ expect(handler).toHaveBeenCalledTimes(2);
91
+
92
+ await first.dispose();
93
+ await rawPage.emit('request', request);
94
+ expect(handler).toHaveBeenCalledTimes(3);
95
+
96
+ await second.dispose();
97
+ await rawPage.emit('request', request);
98
+ expect(handler).toHaveBeenCalledTimes(3);
99
+ });
100
+
101
+ it('removes init scripts via returned identifiers and makes disposal idempotent', async () => {
102
+ const rawPage = new MockPuppeteerPage();
103
+ const page = new PuppeteerPage(rawPage as any, capabilities as any);
104
+
105
+ const handle = await page.addInitScript('window.__OPENWA__ = true;');
106
+
107
+ await handle.dispose();
108
+ await handle.dispose();
109
+
110
+ expect(rawPage.evaluateOnNewDocument).toHaveBeenCalledWith('window.__OPENWA__ = true;');
111
+ expect(rawPage.removeScriptToEvaluateOnNewDocument).toHaveBeenCalledTimes(1);
112
+ expect(rawPage.removeScriptToEvaluateOnNewDocument).toHaveBeenCalledWith('init-script-1');
113
+ });
114
+
115
+ it('wraps request payloads and normalizes Uint8Array response bodies', async () => {
116
+ const rawPage = new MockPuppeteerPage();
117
+ const page = new PuppeteerPage(rawPage as any, capabilities as any);
118
+ const respond = vi.fn(async () => undefined);
119
+ let wrappedRequest: any;
120
+
121
+ page.on('request', (request: any) => {
122
+ wrappedRequest = request;
123
+ });
124
+
125
+ await rawPage.emit('request', {
126
+ url: () => 'https://example.test/api',
127
+ method: () => 'POST',
128
+ headers: () => ({ 'content-type': 'application/json' }),
129
+ resourceType: () => 'xhr',
130
+ isNavigationRequest: () => false,
131
+ frame: () => rawPage.mainFrameValue,
132
+ abort: vi.fn(async () => undefined),
133
+ continue: vi.fn(async () => undefined),
134
+ respond,
135
+ });
136
+
137
+ await wrappedRequest.respond({
138
+ status: 200,
139
+ body: new Uint8Array([1, 2, 3]),
140
+ });
141
+
142
+ expect(wrappedRequest.url()).toBe('https://example.test/api');
143
+ expect(wrappedRequest.frame()?.isMainFrame()).toBe(true);
144
+ expect(respond).toHaveBeenCalledWith({
145
+ status: 200,
146
+ body: Buffer.from([1, 2, 3]),
147
+ });
148
+ });
149
+ });
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './PuppeteerDriver';
2
+ export * from './PuppeteerBrowser';
3
+ export * from './PuppeteerPage';
4
+ export * from './PuppeteerElementHandle';
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": [
8
+ "src/**/*"
9
+ ]
10
+ }