@naman_deep_singh/server-utils 1.0.7 → 1.1.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.
Files changed (51) hide show
  1. package/README.md +147 -7
  2. package/dist/{index.d.ts → cjs/index.d.ts} +2 -3
  3. package/dist/{index.js → cjs/index.js} +1 -3
  4. package/dist/{middleware.js → cjs/middleware.js} +37 -10
  5. package/dist/{periodic-health.d.ts → cjs/periodic-health.d.ts} +0 -1
  6. package/dist/{periodic-health.js → cjs/periodic-health.js} +0 -4
  7. package/dist/{server.js → cjs/server.js} +1 -1
  8. package/dist/{utils.js → cjs/utils.js} +14 -8
  9. package/dist/esm/health.d.ts +5 -0
  10. package/dist/esm/health.js +40 -0
  11. package/dist/esm/index.d.ts +46 -0
  12. package/dist/esm/index.js +58 -0
  13. package/dist/esm/middleware.d.ts +37 -0
  14. package/dist/esm/middleware.js +229 -0
  15. package/dist/esm/periodic-health.d.ts +11 -0
  16. package/dist/esm/periodic-health.js +64 -0
  17. package/dist/esm/server.d.ts +69 -0
  18. package/dist/esm/server.js +271 -0
  19. package/dist/esm/shutdown.d.ts +5 -0
  20. package/dist/esm/shutdown.js +52 -0
  21. package/dist/esm/types.d.ts +70 -0
  22. package/dist/esm/types.js +1 -0
  23. package/dist/esm/utils.d.ts +3 -0
  24. package/dist/esm/utils.js +38 -0
  25. package/dist/types/health.d.ts +5 -0
  26. package/dist/types/index.d.ts +46 -0
  27. package/dist/types/middleware.d.ts +37 -0
  28. package/dist/types/periodic-health.d.ts +11 -0
  29. package/dist/types/server.d.ts +69 -0
  30. package/dist/types/shutdown.d.ts +5 -0
  31. package/dist/types/types.d.ts +70 -0
  32. package/dist/types/utils.d.ts +3 -0
  33. package/package.json +22 -7
  34. package/src/health.ts +0 -47
  35. package/src/index.ts +0 -127
  36. package/src/middleware.ts +0 -275
  37. package/src/periodic-health.ts +0 -87
  38. package/src/server.ts +0 -412
  39. package/src/shutdown.ts +0 -69
  40. package/src/types.ts +0 -80
  41. package/src/utils.ts +0 -34
  42. package/tsconfig.json +0 -21
  43. /package/dist/{health.d.ts → cjs/health.d.ts} +0 -0
  44. /package/dist/{health.js → cjs/health.js} +0 -0
  45. /package/dist/{middleware.d.ts → cjs/middleware.d.ts} +0 -0
  46. /package/dist/{server.d.ts → cjs/server.d.ts} +0 -0
  47. /package/dist/{shutdown.d.ts → cjs/shutdown.d.ts} +0 -0
  48. /package/dist/{shutdown.js → cjs/shutdown.js} +0 -0
  49. /package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
  50. /package/dist/{types.js → cjs/types.js} +0 -0
  51. /package/dist/{utils.d.ts → cjs/utils.d.ts} +0 -0
package/src/server.ts DELETED
@@ -1,412 +0,0 @@
1
- import express from 'express';
2
- import { Server } from 'http';
3
- import { ServerConfig, SocketIOConfig, SocketInstance } from './types';
4
- import { createGracefulShutdown } from './shutdown';
5
- import { PeriodicHealthMonitor, createPeriodicHealthMonitor } from './periodic-health';
6
- import crypto from 'crypto';
7
-
8
- export interface GrpcService {
9
- service: Record<string, unknown>;
10
- implementation: Record<string, (...args: unknown[]) => unknown>;
11
- }
12
-
13
- export interface RpcMethod {
14
- [key: string]: (params: unknown[], callback: (error: Error | null, result?: unknown) => void) => void;
15
- }
16
-
17
- export interface WebhookConfig {
18
- path: string;
19
- secret?: string;
20
- handler: (payload: Record<string, unknown>, headers: Record<string, string | string[]>) => void | Promise<void>;
21
- }
22
-
23
- export interface GrpcServerInstance {
24
- start(): void;
25
- forceShutdown(): void;
26
- addService(service: unknown, implementation: unknown): void;
27
- bindAsync(address: string, credentials: unknown, callback: () => void): void;
28
- }
29
-
30
-
31
-
32
- export interface ServerInstanceConfig extends Required<Omit<ServerConfig, 'socketIO' | 'name' | 'version'>> {
33
- name: string;
34
- version: string;
35
- startTime: Date;
36
- socketIO?: SocketIOConfig;
37
- }
38
-
39
- export interface ServerInstance {
40
- app: express.Application;
41
- server?: Server;
42
- config: ServerInstanceConfig;
43
- start(): Promise<ServerInstance>;
44
- stop(): Promise<void>;
45
- getInfo(): ServerInfo;
46
-
47
- // Multi-protocol support
48
- addGrpcService(service: Record<string, unknown>, implementation: Record<string, (...args: unknown[]) => unknown>, port?: number): void;
49
- addRpcMethods(methods: RpcMethod, path?: string): void;
50
- addWebhook(config: WebhookConfig): void;
51
- addSocketIO(config?: SocketIOConfig): unknown;
52
- }
53
-
54
- export interface ServerInfo {
55
- name: string;
56
- version: string;
57
- port: number;
58
- uptime: number;
59
- status: 'starting' | 'running' | 'stopping' | 'stopped';
60
- startTime: Date;
61
- }
62
-
63
- export class ExpressServer implements ServerInstance {
64
- public app: express.Application;
65
- public server?: Server;
66
- public config: ServerInstanceConfig;
67
- private status: 'starting' | 'running' | 'stopping' | 'stopped' = 'stopped';
68
- private grpcServices: GrpcService[] = [];
69
- private grpcServer?: GrpcServerInstance;
70
- private rpcMethods: RpcMethod = {};
71
- private socketIO?: { close(): void };
72
- private healthMonitor?: PeriodicHealthMonitor;
73
-
74
- constructor(
75
- name: string = 'Express Server',
76
- version: string = '1.0.0',
77
- config: ServerConfig = {}
78
- ) {
79
- this.app = express();
80
- this.config = {
81
- name,
82
- version,
83
- startTime: new Date(),
84
- port: config.port || 3000,
85
- cors: config.cors ?? true,
86
- helmet: config.helmet ?? true,
87
- json: config.json ?? true,
88
- cookieParser: config.cookieParser ?? false,
89
- customMiddleware: config.customMiddleware || [],
90
- healthCheck: config.healthCheck ?? true,
91
- gracefulShutdown: config.gracefulShutdown ?? true,
92
- socketIO: config.socketIO,
93
- periodicHealthCheck: config.periodicHealthCheck || { enabled: false }
94
- };
95
-
96
- // Apply middleware based on configuration
97
- this.setupMiddleware();
98
-
99
- // Setup periodic health monitoring
100
- this.setupPeriodicHealthMonitoring();
101
- }
102
-
103
- private setupMiddleware(): void {
104
- // Apply CORS if enabled
105
- if (this.config.cors) {
106
- try {
107
- const cors = require('cors');
108
- const corsOptions = typeof this.config.cors === 'object' ? this.config.cors : undefined;
109
- this.app.use(cors(corsOptions));
110
- } catch (error) {
111
- console.warn(`${this.config.name}: CORS middleware not available. Install cors package.`);
112
- }
113
- }
114
-
115
- // Apply Helmet if enabled
116
- if (this.config.helmet) {
117
- try {
118
- const helmet = require('helmet');
119
- this.app.use(helmet());
120
- } catch (error) {
121
- console.warn(`${this.config.name}: Helmet middleware not available. Install helmet package.`);
122
- }
123
- }
124
-
125
- // Apply JSON parser if enabled
126
- if (this.config.json) {
127
- this.app.use(express.json());
128
- }
129
-
130
- // Apply cookie parser if enabled
131
- if (this.config.cookieParser) {
132
- try {
133
- const cookieParser = require('cookie-parser');
134
- this.app.use(cookieParser());
135
- } catch (error) {
136
- console.warn(`${this.config.name}: Cookie parser middleware not available. Install cookie-parser package.`);
137
- }
138
- }
139
-
140
- // Apply custom middleware
141
- if (this.config.customMiddleware && this.config.customMiddleware.length > 0) {
142
- this.config.customMiddleware.forEach(middleware => {
143
- this.app.use(middleware);
144
- });
145
- }
146
-
147
- // Add health check if enabled
148
- if (this.config.healthCheck) {
149
- const healthPath = typeof this.config.healthCheck === 'string' ? this.config.healthCheck : '/health';
150
- this.app.get(healthPath, (req, res) => {
151
- res.status(200).json({
152
- status: 'healthy',
153
- service: this.config.name,
154
- version: this.config.version,
155
- uptime: Date.now() - this.config.startTime.getTime(),
156
- timestamp: new Date().toISOString()
157
- });
158
- });
159
- }
160
- }
161
-
162
- private setupPeriodicHealthMonitoring(): void {
163
- if (this.config.periodicHealthCheck?.enabled) {
164
- this.healthMonitor = createPeriodicHealthMonitor(
165
- this.config.periodicHealthCheck,
166
- this.config.name
167
- );
168
- }
169
- }
170
-
171
- async start(): Promise<ServerInstance> {
172
- this.status = 'starting';
173
-
174
- return new Promise((resolve, reject) => {
175
- try {
176
- this.server = this.app.listen(this.config.port, () => {
177
- this.status = 'running';
178
- console.log(`🚀 ${this.config.name} v${this.config.version} running on http://localhost:${this.config.port}`);
179
-
180
- if (this.config.gracefulShutdown) {
181
- createGracefulShutdown(this.server!, {
182
- onShutdown: async () => {
183
- this.status = 'stopping';
184
- // Stop health monitoring during shutdown
185
- if (this.healthMonitor) {
186
- this.healthMonitor.stop();
187
- }
188
- }
189
- });
190
- }
191
-
192
- // Start periodic health monitoring after server is running
193
- if (this.healthMonitor) {
194
- this.healthMonitor.start();
195
- }
196
-
197
- resolve(this);
198
- });
199
-
200
- this.server.on('error', reject);
201
- } catch (error: unknown) {
202
- this.status = 'stopped';
203
- reject(error);
204
- }
205
- });
206
- }
207
-
208
- async stop(): Promise<void> {
209
- this.status = 'stopping';
210
-
211
- // Stop gRPC server if running
212
- if (this.grpcServer) {
213
- this.grpcServer.forceShutdown();
214
- }
215
-
216
- // Stop periodic health monitoring
217
- if (this.healthMonitor) {
218
- this.healthMonitor.stop();
219
- }
220
-
221
- // Stop Socket.IO server if running
222
- if (this.socketIO) {
223
- this.socketIO.close();
224
- }
225
-
226
- if (!this.server) {
227
- this.status = 'stopped';
228
- return;
229
- }
230
-
231
- return new Promise((resolve) => {
232
- this.server!.close(() => {
233
- this.status = 'stopped';
234
- console.log(`👋 ${this.config.name} stopped`);
235
- resolve();
236
- });
237
- });
238
- }
239
-
240
- getInfo(): ServerInfo {
241
- return {
242
- name: this.config.name,
243
- version: this.config.version,
244
- port: this.config.port,
245
- uptime: Date.now() - this.config.startTime.getTime(),
246
- status: this.status,
247
- startTime: this.config.startTime
248
- };
249
- }
250
-
251
- addGrpcService(service: Record<string, unknown>, implementation: Record<string, (...args: unknown[]) => unknown>, port: number = 50051): void {
252
- this.grpcServices.push({ service, implementation });
253
-
254
- // Lazy load gRPC to avoid dependency issues
255
- if (!this.grpcServer) {
256
- try {
257
- // eslint-disable-next-line @typescript-eslint/no-var-requires
258
- const grpc = require('@grpc/grpc-js') as {
259
- Server: new () => {
260
- start(): void;
261
- forceShutdown(): void;
262
- addService(service: unknown, implementation: unknown): void;
263
- bindAsync(address: string, credentials: unknown, callback: () => void): void;
264
- };
265
- ServerCredentials: { createInsecure(): unknown };
266
- };
267
- this.grpcServer = new grpc.Server();
268
-
269
- // Add all services
270
- this.grpcServices.forEach(({ service, implementation }) => {
271
- this.grpcServer!.addService(service, implementation);
272
- });
273
-
274
- this.grpcServer.bindAsync(
275
- `0.0.0.0:${port}`,
276
- grpc.ServerCredentials.createInsecure(),
277
- () => {
278
- this.grpcServer!.start();
279
- console.log(`🔗 ${this.config.name} gRPC server running on port ${port}`);
280
- }
281
- );
282
- } catch (error: unknown) {
283
- console.warn(`${this.config.name}: gRPC not available. Install @grpc/grpc-js to use gRPC features.`);
284
- }
285
- }
286
- }
287
-
288
- addRpcMethods(methods: RpcMethod, path: string = '/rpc'): void {
289
- Object.assign(this.rpcMethods, methods);
290
-
291
- try {
292
- // eslint-disable-next-line @typescript-eslint/no-var-requires
293
- const jayson = require('jayson') as {
294
- server: (methods: RpcMethod) => {
295
- middleware(): express.RequestHandler;
296
- };
297
- };
298
- const rpcServer = jayson.server(this.rpcMethods);
299
- this.app.use(path, rpcServer.middleware());
300
- console.log(`📡 ${this.config.name} JSON-RPC server mounted on ${path}`);
301
- } catch (error: unknown) {
302
- console.warn(`${this.config.name}: JSON-RPC not available. Install jayson to use RPC features.`);
303
- }
304
- }
305
-
306
- addWebhook(config: WebhookConfig): void {
307
- this.app.post(config.path, express.raw({ type: 'application/json' }), async (req, res) => {
308
- try {
309
- // Verify signature if secret provided
310
- if (config.secret) {
311
- const signature = req.headers['x-hub-signature-256'] || req.headers['x-signature-256'];
312
- if (signature) {
313
- const expectedSignature = crypto
314
- .createHmac('sha256', config.secret)
315
- .update(req.body)
316
- .digest('hex');
317
-
318
- const providedSignature = Array.isArray(signature) ? signature[0] : signature;
319
- if (!providedSignature.includes(expectedSignature)) {
320
- return res.status(401).json({ error: 'Invalid signature' });
321
- }
322
- }
323
- }
324
-
325
- // Parse JSON payload
326
- const payload = JSON.parse(req.body.toString());
327
-
328
- // Call handler
329
- await config.handler(payload, req.headers as Record<string, string | string[]>);
330
-
331
- res.status(200).json({ success: true });
332
- } catch (error: unknown) {
333
- console.error('Webhook error:', error);
334
- res.status(500).json({ error: 'Webhook processing failed' });
335
- }
336
- });
337
-
338
- console.log(`🪝 ${this.config.name} webhook registered at ${config.path}${config.secret ? ' (with signature verification)' : ''}`);
339
- }
340
-
341
- addSocketIO(config: SocketIOConfig = {}): unknown {
342
- if (!this.server) {
343
- throw new Error(`${this.config.name}: Server must be started before adding Socket.IO`);
344
- }
345
-
346
- try {
347
- // eslint-disable-next-line @typescript-eslint/no-var-requires
348
- const { Server } = require('socket.io') as {
349
- Server: new (server: Server, options?: {
350
- cors?: {
351
- origin?: string | string[] | boolean;
352
- methods?: string[];
353
- credentials?: boolean;
354
- };
355
- path?: string;
356
- }) => {
357
- on: (event: string, handler: (socket: unknown) => void) => void;
358
- close: () => void;
359
- };
360
- };
361
-
362
- // Configure CORS
363
- const corsConfig = config.cors === true
364
- ? { origin: '*', methods: ['GET', 'POST'] }
365
- : config.cors || undefined;
366
-
367
- // Create Socket.IO server
368
- const io = new Server(this.server, {
369
- cors: config.cors ? corsConfig : undefined,
370
- path: config.path || '/socket.io'
371
- });
372
-
373
- // Store reference for cleanup
374
- this.socketIO = io;
375
-
376
- // Handle connections
377
- io.on('connection', (socket: unknown) => {
378
- const typedSocket = socket as SocketInstance;
379
- console.log(`🔌 ${this.config.name}: Socket connected [${typedSocket.id}]`);
380
-
381
- // Call user-defined connection handler
382
- if (config.onConnection) {
383
- config.onConnection(socket);
384
- }
385
-
386
- // Handle disconnection
387
- typedSocket.on('disconnect', (reason) => {
388
- console.log(`🔌 ${this.config.name}: Socket disconnected [${typedSocket.id}] - ${reason}`);
389
-
390
- // Call user-defined disconnection handler
391
- if (config.onDisconnection) {
392
- config.onDisconnection(socket, reason as string);
393
- }
394
- });
395
- });
396
-
397
- console.log(`🔌 ${this.config.name} Socket.IO server attached${config.path ? ` at ${config.path}` : ''}${config.cors ? ' (CORS enabled)' : ''}`);
398
- return io;
399
- } catch (error: unknown) {
400
- console.warn(`${this.config.name}: Socket.IO not available. Install socket.io to use WebSocket features.`);
401
- return null;
402
- }
403
- }
404
- }
405
-
406
- export function createServer(
407
- name?: string,
408
- version?: string,
409
- config?: ServerConfig
410
- ): ServerInstance {
411
- return new ExpressServer(name, version, config);
412
- }
package/src/shutdown.ts DELETED
@@ -1,69 +0,0 @@
1
- import { Server } from 'http';
2
- import { GracefulShutdownConfig, ServerPlugin } from './types';
3
-
4
- export function createGracefulShutdown(server: Server, config: GracefulShutdownConfig = {}): void {
5
- const { timeout = 10000, onShutdown, serverName, serverVersion } = config;
6
- const nameVersion = serverName && serverVersion ? `${serverName} v${serverVersion}` : 'Server';
7
-
8
- const shutdown = async (signal: string) => {
9
- console.log(`🛑 ${nameVersion} received ${signal}, shutting down gracefully...`);
10
-
11
- const shutdownTimer = setTimeout(() => {
12
- console.log(`⏰ ${nameVersion} shutdown timeout reached, forcing exit`);
13
- process.exit(1);
14
- }, timeout);
15
-
16
- try {
17
- // Run custom shutdown logic
18
- if (onShutdown) {
19
- await onShutdown();
20
- }
21
-
22
- // Close server
23
- server.close(() => {
24
- clearTimeout(shutdownTimer);
25
- console.log(`👋 ${nameVersion} closed. Exiting now.`);
26
- process.exit(0);
27
- });
28
- } catch (error) {
29
- clearTimeout(shutdownTimer);
30
- console.error(`❌ ${nameVersion} error during shutdown:`, error);
31
- process.exit(1);
32
- }
33
- };
34
-
35
- process.on('SIGINT', () => shutdown('SIGINT'));
36
- process.on('SIGTERM', () => shutdown('SIGTERM'));
37
- }
38
-
39
- export function withGracefulShutdown(config: GracefulShutdownConfig = {}): ServerPlugin {
40
- return (app, serverConfig) => {
41
- // This plugin needs to be applied after server.listen()
42
- // Store config for later use
43
- (app as any).__gracefulShutdownConfig = config;
44
- };
45
- }
46
-
47
- export function startServerWithShutdown(
48
- app: any,
49
- port: number,
50
- shutdownConfig: GracefulShutdownConfig = {},
51
- serverName?: string,
52
- serverVersion?: string
53
- ): Server {
54
- const server = app.listen(port, () => {
55
- const nameVersion = serverName && serverVersion ? `${serverName} v${serverVersion}` : 'Server';
56
- console.log(`🚀 ${nameVersion} running on http://localhost:${port}`);
57
- });
58
-
59
- // Apply graceful shutdown from stored config or provided config
60
- const config = app.__gracefulShutdownConfig || shutdownConfig;
61
- const enhancedConfig = {
62
- ...config,
63
- serverName,
64
- serverVersion
65
- };
66
- createGracefulShutdown(server, enhancedConfig);
67
-
68
- return server;
69
- }
package/src/types.ts DELETED
@@ -1,80 +0,0 @@
1
- import express from 'express';
2
-
3
- export interface CorsOptions {
4
- origin?: string | string[] | boolean | RegExp | ((origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => void);
5
- methods?: string | string[];
6
- allowedHeaders?: string | string[];
7
- exposedHeaders?: string | string[];
8
- credentials?: boolean;
9
- maxAge?: number;
10
- preflightContinue?: boolean;
11
- optionsSuccessStatus?: number;
12
- }
13
-
14
- export interface ServerConfig {
15
- port?: number;
16
- cors?: boolean | CorsOptions;
17
- helmet?: boolean;
18
- json?: boolean;
19
- cookieParser?: boolean;
20
- customMiddleware?: express.RequestHandler[];
21
- healthCheck?: boolean | string;
22
- gracefulShutdown?: boolean;
23
- socketIO?: SocketIOConfig;
24
- periodicHealthCheck?: PeriodicHealthCheckConfig;
25
- name?: string;
26
- version?: string;
27
- }
28
-
29
- export interface PeriodicHealthCheckConfig {
30
- enabled?: boolean;
31
- interval?: number;
32
- services?: HealthCheckService[];
33
- }
34
-
35
- export interface HealthCheckService {
36
- name: string;
37
- url: string;
38
- timeout?: number;
39
- }
40
-
41
- export interface SocketIOConfig {
42
- enabled?: boolean;
43
- cors?: boolean | {
44
- origin?: string | string[] | boolean;
45
- methods?: string[];
46
- credentials?: boolean;
47
- };
48
- onConnection?: (socket: unknown) => void;
49
- onDisconnection?: (socket: unknown, reason: string) => void;
50
- path?: string;
51
- }
52
-
53
- export interface HealthCheckConfig {
54
- path?: string;
55
- customChecks?: HealthCheck[];
56
- }
57
-
58
- export interface HealthCheck {
59
- name: string;
60
- check: () => Promise<boolean>;
61
- }
62
-
63
- export interface GracefulShutdownConfig {
64
- timeout?: number;
65
- onShutdown?: () => Promise<void>;
66
- serverName?: string;
67
- serverVersion?: string;
68
- }
69
-
70
- export interface SocketInstance {
71
- id: string;
72
- emit: (event: string, data?: unknown) => void;
73
- on: (event: string, handler: (...args: unknown[]) => void) => void;
74
- broadcast: {
75
- emit: (event: string, data?: unknown) => void;
76
- };
77
- disconnect: () => void;
78
- }
79
-
80
- export type ServerPlugin = (app: express.Application, config: ServerConfig) => void;
package/src/utils.ts DELETED
@@ -1,34 +0,0 @@
1
- // Environment utilities
2
- export function getEnv(key: string, defaultValue?: string): string {
3
- const value = process.env[key];
4
- if (value === undefined && defaultValue === undefined) {
5
- throw new Error(`Environment variable ${key} is required`);
6
- }
7
- return value || defaultValue!;
8
- }
9
-
10
- export function getEnvNumber(key: string, defaultValue?: number): number {
11
- const value = process.env[key];
12
- if (value === undefined) {
13
- if (defaultValue === undefined) {
14
- throw new Error(`Environment variable ${key} is required`);
15
- }
16
- return defaultValue;
17
- }
18
- const parsed = parseInt(value, 10);
19
- if (isNaN(parsed)) {
20
- throw new Error(`Environment variable ${key} must be a number`);
21
- }
22
- return parsed;
23
- }
24
-
25
- export function getEnvBoolean(key: string, defaultValue?: boolean): boolean {
26
- const value = process.env[key];
27
- if (value === undefined) {
28
- if (defaultValue === undefined) {
29
- throw new Error(`Environment variable ${key} is required`);
30
- }
31
- return defaultValue;
32
- }
33
- return value.toLowerCase() === 'true';
34
- }
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "CommonJS",
5
- "moduleResolution": "node",
6
- "rootDir": "./src",
7
- "outDir": "./dist",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "allowSyntheticDefaultImports": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "declaration": true,
14
- "baseUrl": ".",
15
- "paths": {
16
- "*": ["*", "*.ts", "*.js"]
17
- }
18
- },
19
- "include": ["src/**/*"],
20
- "exclude": ["node_modules", "dist"]
21
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes