@unchainedshop/ticketing 4.0.0-rc.9 → 4.0.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.
Files changed (60) hide show
  1. package/lib/express.js +1 -1
  2. package/lib/express.js.map +1 -1
  3. package/lib/fastify.js +3 -3
  4. package/lib/fastify.js.map +1 -1
  5. package/lib/magic-key.js +4 -4
  6. package/lib/magic-key.js.map +1 -1
  7. package/lib/mobile-tickets/apple-handler-express.js +63 -64
  8. package/lib/mobile-tickets/apple-handler-express.js.map +1 -1
  9. package/lib/mobile-tickets/apple-handler-fastify.d.ts.map +1 -1
  10. package/lib/mobile-tickets/apple-handler-fastify.js +93 -42
  11. package/lib/mobile-tickets/apple-handler-fastify.js.map +1 -1
  12. package/lib/mobile-tickets/apple-wallet.d.ts +3 -3
  13. package/lib/mobile-tickets/apple-wallet.d.ts.map +1 -1
  14. package/lib/mobile-tickets/apple-wallet.js +3 -3
  15. package/lib/mobile-tickets/apple-wallet.js.map +1 -1
  16. package/lib/mobile-tickets/apple-webservice.d.ts +7 -0
  17. package/lib/mobile-tickets/apple-webservice.d.ts.map +1 -0
  18. package/lib/mobile-tickets/apple-webservice.js +177 -0
  19. package/lib/mobile-tickets/apple-webservice.js.map +1 -0
  20. package/lib/mobile-tickets/google-handler-express.d.ts.map +1 -1
  21. package/lib/mobile-tickets/google-handler-express.js +13 -15
  22. package/lib/mobile-tickets/google-handler-express.js.map +1 -1
  23. package/lib/mobile-tickets/google-handler-fastify.d.ts.map +1 -1
  24. package/lib/mobile-tickets/google-handler-fastify.js +21 -8
  25. package/lib/mobile-tickets/google-handler-fastify.js.map +1 -1
  26. package/lib/mobile-tickets/google-webservice.d.ts +7 -0
  27. package/lib/mobile-tickets/google-webservice.d.ts.map +1 -0
  28. package/lib/mobile-tickets/google-webservice.js +52 -0
  29. package/lib/mobile-tickets/google-webservice.js.map +1 -0
  30. package/lib/module.d.ts +8 -8
  31. package/lib/module.d.ts.map +1 -1
  32. package/lib/module.js +6 -3
  33. package/lib/module.js.map +1 -1
  34. package/lib/pdf-tickets/print-handler-express.d.ts.map +1 -1
  35. package/lib/pdf-tickets/print-handler-express.js +4 -3
  36. package/lib/pdf-tickets/print-handler-express.js.map +1 -1
  37. package/lib/pdf-tickets/print-handler-fastify.d.ts.map +1 -1
  38. package/lib/pdf-tickets/print-handler-fastify.js +8 -2
  39. package/lib/pdf-tickets/print-handler-fastify.js.map +1 -1
  40. package/lib/pdf-tickets/print-webservice.d.ts +7 -0
  41. package/lib/pdf-tickets/print-webservice.d.ts.map +1 -0
  42. package/lib/pdf-tickets/print-webservice.js +31 -0
  43. package/lib/pdf-tickets/print-webservice.js.map +1 -0
  44. package/lib/template-registry.d.ts +2 -2
  45. package/lib/template-registry.d.ts.map +1 -1
  46. package/package.json +11 -11
  47. package/readme.md +1 -1
  48. package/src/express.ts +1 -1
  49. package/src/fastify.ts +3 -3
  50. package/src/magic-key.ts +4 -4
  51. package/src/mobile-tickets/apple-handler-express.ts +71 -71
  52. package/src/mobile-tickets/apple-handler-fastify.ts +103 -51
  53. package/src/mobile-tickets/apple-wallet.ts +3 -3
  54. package/src/mobile-tickets/google-handler-express.ts +14 -15
  55. package/src/mobile-tickets/google-handler-fastify.ts +23 -11
  56. package/src/module.ts +7 -4
  57. package/src/pdf-tickets/print-handler-express.ts +6 -3
  58. package/src/pdf-tickets/print-handler-fastify.ts +9 -2
  59. package/src/template-registry.ts +2 -2
  60. package/tsconfig.json +2 -1
@@ -38,15 +38,21 @@ const appleWalletHandler: RouteHandlerMethod = async (
38
38
  });
39
39
 
40
40
  if (!token) {
41
+ logger.error('Token not found', { tokenId });
41
42
  reply.status(404);
42
- return reply.send('Token not found');
43
+ return reply.send();
43
44
  }
44
45
 
45
46
  const { hash } = req.query as Record<string, string>;
46
47
  const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
47
- if (hash !== correctHash) {
48
+ if (!hash || hash !== correctHash) {
49
+ logger.error('Token hash invalid for current owner', { tokenId });
48
50
  reply.status(403);
49
- return reply.send('Token hash invalid for current owner');
51
+ return reply.send({
52
+ success: false,
53
+ message: 'Token hash invalid for current owner',
54
+ name: 'HASH_MISMATCH',
55
+ });
50
56
  }
51
57
 
52
58
  const passFile = await modules.passes.upsertAppleWalletPass(token, resolvedContext);
@@ -55,6 +61,15 @@ const appleWalletHandler: RouteHandlerMethod = async (
55
61
  const signedUrl = await fileUploadAdapter.createDownloadURL(passFile);
56
62
  const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
57
63
 
64
+ if (!url) {
65
+ reply.status(500);
66
+ return reply.send({
67
+ success: false,
68
+ message: 'Could not create download URL',
69
+ name: 'URL_SIGNING_FAILED',
70
+ });
71
+ }
72
+
58
73
  const response = await fetch(url);
59
74
  const data = await response.arrayBuffer();
60
75
  const uint8View = new Uint8Array(data);
@@ -64,10 +79,14 @@ const appleWalletHandler: RouteHandlerMethod = async (
64
79
  reply.header('Content-Disposition', `attachment; filename=${tokenId}.pkpass`);
65
80
  return reply.send(uint8View);
66
81
  } catch (e) {
67
- console.error(e);
82
+ logger.error(e);
83
+ reply.status(500);
84
+ return reply.send({
85
+ success: false,
86
+ message: 'Error generating pass',
87
+ name: 'PASS_GENERATION_ERROR',
88
+ });
68
89
  }
69
- reply.status(500);
70
- return reply.send();
71
90
  }
72
91
 
73
92
  const [, apiVersion, endpoint, ...pathComponents] = path.split("/"); /* eslint-disable-line */
@@ -83,9 +102,10 @@ const appleWalletHandler: RouteHandlerMethod = async (
83
102
  if (
84
103
  !isAuthenticationTokenCorrect(
85
104
  req,
86
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
105
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
87
106
  )
88
107
  ) {
108
+ logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
89
109
  reply.status(401);
90
110
  return reply.send();
91
111
  }
@@ -101,11 +121,15 @@ const appleWalletHandler: RouteHandlerMethod = async (
101
121
 
102
122
  if (!newRegistration) {
103
123
  reply.status(200);
104
- return reply.send();
124
+ return reply.send({
125
+ success: true,
126
+ });
105
127
  }
106
128
 
107
129
  reply.status(201);
108
- return reply.send();
130
+ return reply.send({
131
+ success: true,
132
+ });
109
133
  }
110
134
  } else if (req.method === 'GET') {
111
135
  // Get the List of Updatable Passes
@@ -120,21 +144,19 @@ const appleWalletHandler: RouteHandlerMethod = async (
120
144
  deviceLibraryIdentifier,
121
145
  passesUpdatedSince,
122
146
  );
123
- const serialNumbers = passes.map((t) => t.meta.serialNumber);
147
+ const serialNumbers = passes.map((t) => t.meta?.serialNumber).filter(Boolean) as string[];
124
148
 
125
149
  if (serialNumbers?.length) {
126
150
  reply.status(200);
127
- reply.header('content-type', 'application/json');
128
- return reply.send(
129
- JSON.stringify({
130
- serialNumbers,
131
- lastUpdated,
132
- }),
133
- );
151
+ return reply.send({
152
+ success: true,
153
+ serialNumbers,
154
+ lastUpdated,
155
+ });
134
156
  }
135
157
 
136
158
  reply.status(204);
137
- return reply.send();
159
+ return reply.send({ success: true });
138
160
  } else if (req.method === 'DELETE') {
139
161
  // Unregister Device
140
162
  const [deviceLibraryIdentifier, , passTypeIdentifier, serialNumber] = pathComponents;
@@ -145,11 +167,16 @@ const appleWalletHandler: RouteHandlerMethod = async (
145
167
  if (
146
168
  !isAuthenticationTokenCorrect(
147
169
  req,
148
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
170
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
149
171
  )
150
172
  ) {
173
+ logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
151
174
  reply.status(401);
152
- return reply.send();
175
+ return reply.send({
176
+ success: false,
177
+ message: 'Unauthorized',
178
+ name: 'UNAUTHORIZED',
179
+ });
153
180
  }
154
181
 
155
182
  await modules.passes.unregisterDeviceForAppleWalletPass(
@@ -160,7 +187,9 @@ const appleWalletHandler: RouteHandlerMethod = async (
160
187
 
161
188
  // Unregistered
162
189
  reply.status(200);
163
- return reply.send();
190
+ return reply.send({
191
+ success: true,
192
+ });
164
193
  }
165
194
  }
166
195
  } else if (endpoint === 'log') {
@@ -172,7 +201,9 @@ const appleWalletHandler: RouteHandlerMethod = async (
172
201
  }
173
202
  });
174
203
  reply.status(200);
175
- return reply.send();
204
+ return reply.send({
205
+ success: true,
206
+ });
176
207
  } else if (endpoint === 'passes') {
177
208
  if (req.method === 'GET') {
178
209
  // Get an updated Pass
@@ -180,43 +211,64 @@ const appleWalletHandler: RouteHandlerMethod = async (
180
211
 
181
212
  const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
182
213
 
183
- if (pass) {
184
- if (
185
- !isAuthenticationTokenCorrect(
186
- req,
187
- (pass.meta.rawData as any)._id || (pass.meta.rawData as any).tokenId,
188
- )
189
- ) {
190
- reply.status(401);
191
- return reply.send();
192
- }
214
+ if (!pass) {
215
+ reply.status(404);
216
+ return reply.send();
217
+ }
193
218
 
194
- const { updated, created } = pass;
219
+ if (
220
+ !isAuthenticationTokenCorrect(
221
+ req,
222
+ (pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
223
+ )
224
+ ) {
225
+ logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
226
+ reply.status(401);
227
+ return reply.send({
228
+ success: false,
229
+ message: 'Unauthorized',
230
+ name: 'UNAUTHORIZED',
231
+ });
232
+ }
195
233
 
196
- const lastModifiedDate = new Date(updated || created);
197
- lastModifiedDate.setMilliseconds(0);
234
+ const { updated, created } = pass;
198
235
 
199
- const ifModifiedSinceDate = new Date(req.headers['if-modified-since']);
200
- ifModifiedSinceDate.setMilliseconds(0);
236
+ const lastModifiedDate = new Date(updated || created);
237
+ lastModifiedDate.setMilliseconds(0);
201
238
 
202
- if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
203
- reply.status(304);
204
- return reply.send();
205
- }
239
+ const ifModifiedSinceDate = new Date(req.headers['if-modified-since']!);
240
+ ifModifiedSinceDate.setMilliseconds(0);
206
241
 
207
- const fileUploadAdapter = getFileAdapter();
208
- const signedUrl = await fileUploadAdapter.createDownloadURL(pass);
209
- const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
242
+ if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
243
+ reply.status(304);
244
+ return reply.send({
245
+ success: true,
246
+ message: 'Not modified',
247
+ name: 'NOT_MODIFIED',
248
+ });
249
+ }
210
250
 
211
- const result = await fetch(url);
212
- const data = await result.arrayBuffer();
213
- const uint8View = new Uint8Array(data);
251
+ const fileUploadAdapter = getFileAdapter();
252
+ const signedUrl = await fileUploadAdapter.createDownloadURL(pass);
253
+ const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
214
254
 
215
- reply.status(200);
216
- reply.header('content-type', 'application/vnd.apple.pkpass');
217
- reply.header('last-modified', lastModifiedDate.toUTCString());
218
- return reply.send(uint8View);
255
+ if (!url) {
256
+ reply.status(500);
257
+ return reply.send({
258
+ success: false,
259
+ message: 'Could not create download URL',
260
+ name: 'URL_SIGNING_FAILED',
261
+ });
219
262
  }
263
+
264
+ const result = await fetch(url);
265
+ const data = await result.arrayBuffer();
266
+ const uint8View = new Uint8Array(data);
267
+
268
+ reply.status(200);
269
+ reply.header('content-type', 'application/vnd.apple.pkpass');
270
+ reply.header('last-modified', lastModifiedDate.toUTCString());
271
+ return reply.send(uint8View);
220
272
  }
221
273
  }
222
274
 
@@ -1,4 +1,4 @@
1
- import apn from '@hyperlink/node-apn';
1
+ import apn from '@parse/node-apn';
2
2
  import { Readable } from 'node:stream';
3
3
 
4
4
  export const pushToApplePushNotificationService = async (deviceTokens) => {
@@ -14,7 +14,7 @@ export const pushToApplePushNotificationService = async (deviceTokens) => {
14
14
  };
15
15
 
16
16
  export const buildPassBinary = async (
17
- chainTokenId: string,
17
+ tokenSerialNumber: string,
18
18
  pass: {
19
19
  serialNumber: string;
20
20
  asBuffer: () => Promise<Buffer>;
@@ -23,7 +23,7 @@ export const buildPassBinary = async (
23
23
  const passBuffer = await pass.asBuffer();
24
24
  const rawFile = {
25
25
  _id: pass.serialNumber,
26
- filename: `${chainTokenId}-${new Date().getTime()}.pkpass`,
26
+ filename: `${tokenSerialNumber}-${new Date().getTime()}.pkpass`,
27
27
  createReadStream: () => Readable.from(passBuffer),
28
28
  mimetype: 'application/vnd.apple.pkpass',
29
29
  };
@@ -12,13 +12,13 @@ export const googleWalletHandler = async (
12
12
  const { modules } = resolvedContext;
13
13
  logger.info(`${req.path} (${JSON.stringify(req.query)})`);
14
14
 
15
- if (req.path.startsWith('/download/')) {
15
+ if (req.path.includes('/download/')) {
16
16
  try {
17
- const [, , tokenId] = req.path.split('/');
17
+ const { tokenId } = req.params as { tokenId: string };
18
+ const { hash } = req.query as { hash?: string };
18
19
 
19
20
  if (!tokenId) {
20
- res.writeHead(404);
21
- res.end();
21
+ res.status(404).end();
22
22
  return;
23
23
  }
24
24
 
@@ -27,32 +27,31 @@ export const googleWalletHandler = async (
27
27
  });
28
28
 
29
29
  if (!token) {
30
- res.writeHead(404);
31
- res.end('Token not found');
30
+ res.status(404).send('Token not found');
32
31
  return;
33
32
  }
34
33
 
35
- const { hash } = req.query;
36
34
  const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
37
- if (hash !== correctHash) {
38
- res.writeHead(403);
39
- res.end('Token hash invalid for current owner');
35
+ if (!hash || hash !== correctHash) {
36
+ res.status(403).send('Token hash invalid for current owner');
40
37
  return;
41
38
  }
42
39
 
43
40
  const pass = await modules.passes.upsertGoogleWalletPass(token, resolvedContext);
44
41
 
42
+ if (!pass) {
43
+ throw new Error('Could not create Google Wallet pass');
44
+ }
45
+
45
46
  res.redirect(await pass.asURL());
46
47
  return;
47
48
  } catch (e) {
48
- console.error(e);
49
+ logger.error(e);
49
50
  }
50
- res.writeHead(500);
51
- res.end();
51
+ res.status(500).end();
52
52
  return;
53
53
  }
54
- res.writeHead(404);
55
- res.end();
54
+ res.status(404).end();
56
55
  };
57
56
 
58
57
  export default googleWalletHandler;
@@ -16,11 +16,10 @@ const googleWalletHandler: RouteHandlerMethod = async (
16
16
  const path = req.url;
17
17
 
18
18
  logger.info(`${path} (${JSON.stringify(req.query)})`);
19
-
20
- if (path.startsWith('/download/')) {
19
+ if (path.includes('/download/')) {
21
20
  try {
22
- const [, , tokenId] = path.split('/');
23
-
21
+ const { tokenId } = req.params as { tokenId: string };
22
+ const { hash } = req.query as { hash?: string };
24
23
  if (!tokenId) {
25
24
  reply.status(404);
26
25
  return reply.send();
@@ -31,24 +30,37 @@ const googleWalletHandler: RouteHandlerMethod = async (
31
30
  });
32
31
 
33
32
  if (!token) {
33
+ logger.error('Token not found', { tokenId });
34
34
  reply.status(404);
35
35
  return reply.send('Token not found');
36
36
  }
37
-
38
- const { hash } = req.query as Record<string, string>;
39
37
  const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
40
- if (hash !== correctHash) {
38
+ if (!hash || hash !== correctHash) {
39
+ logger.error('Token hash invalid for current owner', { tokenId });
41
40
  reply.status(403);
42
- return reply.send('Token hash invalid for current owner');
41
+ return reply.send({
42
+ success: false,
43
+ message: 'Token hash invalid for current owner',
44
+ name: 'HASH_MISMATCH',
45
+ });
43
46
  }
44
47
 
45
48
  const pass = await modules.passes.upsertGoogleWalletPass(token, resolvedContext);
49
+
50
+ if (!pass) {
51
+ throw new Error('Could not create Google Wallet pass');
52
+ }
53
+
46
54
  return reply.redirect(await pass.asURL());
47
55
  } catch (e) {
48
- console.error(e);
56
+ logger.error(e);
57
+ reply.status(500);
58
+ return reply.send({
59
+ success: false,
60
+ message: 'Error generating pass',
61
+ name: 'PASS_GENERATION_ERROR',
62
+ });
49
63
  }
50
- reply.status(500);
51
- return reply.send();
52
64
  }
53
65
  reply.status(404);
54
66
  return reply.send();
package/src/module.ts CHANGED
@@ -37,7 +37,7 @@ const configurePasses = async ({ db }: ModuleInput<Record<string, never>>) => {
37
37
  const pass = await createAppleWalletPass(token, unchainedAPI);
38
38
  const rawFile = Promise.resolve(
39
39
  // wrap in promise to make stream upload work
40
- await buildPassBinary(token.chainTokenId, pass as any),
40
+ await buildPassBinary(token.tokenSerialNumber, pass as any),
41
41
  );
42
42
 
43
43
  const previousFile = await MediaObjects.findOne({
@@ -84,7 +84,7 @@ const configurePasses = async ({ db }: ModuleInput<Record<string, never>>) => {
84
84
  return pass;
85
85
  };
86
86
 
87
- const findAppleWalletPass = async (passTypeIdentifier, serialNumber): Promise<File> => {
87
+ const findAppleWalletPass = async (passTypeIdentifier, serialNumber) => {
88
88
  const mediaObject = await MediaObjects.findOne({
89
89
  path: APPLE_WALLET_PASSES_FILE_DIRECTORY,
90
90
  'meta.passTypeIdentifier': passTypeIdentifier,
@@ -172,7 +172,7 @@ const configurePasses = async ({ db }: ModuleInput<Record<string, never>>) => {
172
172
 
173
173
  for (const pass of allPasses) {
174
174
  // Check if binary is already invalidated, if so, skip
175
- const rawData = pass.meta.rawData as TokenSurrogate;
175
+ const rawData = pass.meta?.rawData as TokenSurrogate;
176
176
  if (rawData.invalidatedDate) continue;
177
177
 
178
178
  const redeemedToken = allTokens.find((t) => t._id === rawData._id && t.invalidatedDate);
@@ -188,7 +188,10 @@ const configurePasses = async ({ db }: ModuleInput<Record<string, never>>) => {
188
188
  const buildMagicKey = async (orderId: string) => {
189
189
  const msgUint8 = new TextEncoder().encode([orderId, process.env.UNCHAINED_SECRET].join(''));
190
190
  const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);
191
- return Buffer.from(hashBuffer).toString('hex');
191
+ // Convert ArrayBuffer to hex string without using Buffer
192
+ return Array.from(new Uint8Array(hashBuffer))
193
+ .map((b) => b.toString(16).padStart(2, '0'))
194
+ .join('');
192
195
  };
193
196
 
194
197
  const cancelTicket = async (tokenId: string) => {
@@ -4,6 +4,10 @@ import { Context } from '@unchainedshop/api';
4
4
  import { RendererTypes, getRenderer } from '../template-registry.js';
5
5
  import type { Request, Response } from 'express';
6
6
 
7
+ import { createLogger } from '@unchainedshop/logger';
8
+
9
+ const logger = createLogger('unchained:ticketing');
10
+
7
11
  export async function printTicketsHandler(req: Request & { unchainedContext: Context }, res: Response) {
8
12
  const { variant, orderId, otp } = req.query || {};
9
13
 
@@ -22,9 +26,8 @@ export async function printTicketsHandler(req: Request & { unchainedContext: Con
22
26
  res.setHeader('Content-Type', 'application/pdf');
23
27
  pdfStream.pipe(res);
24
28
  } catch (error) {
25
- console.error(error);
26
- res.status(403);
27
- res.end();
29
+ logger.error(error);
30
+ res.status(403).end();
28
31
  }
29
32
  }
30
33
 
@@ -3,6 +3,9 @@ import { actions } from '@unchainedshop/api/lib/roles/index.js';
3
3
  import { RendererTypes, getRenderer } from '../template-registry.js';
4
4
  import { TicketingAPI } from '../types.js';
5
5
  import type { FastifyRequest, RouteHandlerMethod } from 'fastify';
6
+ import { createLogger } from '@unchainedshop/logger';
7
+
8
+ const logger = createLogger('unchained:ticketing:print-handler');
6
9
 
7
10
  const printTicketsHandler: RouteHandlerMethod = async (
8
11
  req: FastifyRequest & {
@@ -27,9 +30,13 @@ const printTicketsHandler: RouteHandlerMethod = async (
27
30
  reply.header('content-type', 'application/pdf');
28
31
  return reply.send(pdfStream);
29
32
  } catch (error) {
30
- console.error(error);
33
+ logger.error(error);
31
34
  reply.status(403);
32
- return reply.send();
35
+ return reply.send({
36
+ success: false,
37
+ message: 'Error generating PDF',
38
+ name: 'PDF_GENERATION_ERROR',
39
+ });
33
40
  }
34
41
  };
35
42
 
@@ -16,8 +16,8 @@ export type PassRenderer = (
16
16
  token: TokenSurrogate,
17
17
  context: UnchainedCore,
18
18
  ) => Promise<{
19
- asURL?: () => Promise<string>;
20
- asBuffer?: () => Promise<Buffer>;
19
+ asURL: () => Promise<string>;
20
+ asBuffer: () => Promise<Buffer>;
21
21
  serialNumber?: string;
22
22
  passTypeIdentifier?: string;
23
23
  }>;
package/tsconfig.json CHANGED
@@ -3,7 +3,8 @@
3
3
  "compilerOptions": {
4
4
  "declarationDir": "./lib",
5
5
  "rootDir": "./src",
6
- "outDir": "./lib"
6
+ "outDir": "./lib",
7
+
7
8
  },
8
9
  "exclude": ["**/*.test.ts", "**/*.test.js", "tests", "lib"]
9
10
  }