mcp-excalidraw-server 1.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +424 -0
  3. package/dist/assets/Assistant-Bold-gm-uSS1B.woff2 +0 -0
  4. package/dist/assets/Assistant-Medium-DrcxCXg3.woff2 +0 -0
  5. package/dist/assets/Assistant-Regular-DVxZuzxb.woff2 +0 -0
  6. package/dist/assets/Assistant-SemiBold-SCI4bEL9.woff2 +0 -0
  7. package/dist/assets/Tableau10-B-NsZVaP.js +1 -0
  8. package/dist/assets/_commonjs-dynamic-modules-TDtrdbi3.js +1 -0
  9. package/dist/assets/ar-SA-G6X2FPQ2-BS3fMnev.js +10 -0
  10. package/dist/assets/arc-0Fwkaye8.js +1 -0
  11. package/dist/assets/array-BKyUJesY.js +1 -0
  12. package/dist/assets/az-AZ-76LH7QW2-DNgzWA8S.js +1 -0
  13. package/dist/assets/bg-BG-XCXSNQG7-CULoJKdI.js +5 -0
  14. package/dist/assets/blockDiagram-38ab4fdb-CVOkr1Ma.js +118 -0
  15. package/dist/assets/bn-BD-2XOGV67Q-9DmWphrb.js +5 -0
  16. package/dist/assets/c4Diagram-3d4e48cf-BwpfSRgq.js +10 -0
  17. package/dist/assets/ca-ES-6MX7JW3Y-gYoEYP36.js +8 -0
  18. package/dist/assets/channel-BgX0NHoH.js +1 -0
  19. package/dist/assets/classDiagram-70f12bd4-ftK2tRy5.js +2 -0
  20. package/dist/assets/classDiagram-v2-f2320105--Nu78BDB.js +2 -0
  21. package/dist/assets/clone-sj1RjrIX.js +1 -0
  22. package/dist/assets/createText-2e5e7dd3-DPYwmS_z.js +7 -0
  23. package/dist/assets/cs-CZ-2BRQDIVT-DgwhrQZi.js +11 -0
  24. package/dist/assets/da-DK-5WZEPLOC-BL1Ng5As.js +5 -0
  25. package/dist/assets/de-DE-XR44H4JA-Cw-ySDmq.js +8 -0
  26. package/dist/assets/directory-open-01563666-DWU9wJ6I.js +1 -0
  27. package/dist/assets/directory-open-4ed118d0-BzWybGaI.js +1 -0
  28. package/dist/assets/edges-e0da2a9e-CB2w3jS2.js +4 -0
  29. package/dist/assets/el-GR-BZB4AONW-BPJAfWZm.js +10 -0
  30. package/dist/assets/erDiagram-9861fffd-VwqM-D3G.js +51 -0
  31. package/dist/assets/es-ES-U4NZUMDT-oMrzPWSn.js +9 -0
  32. package/dist/assets/eu-ES-A7QVB2H4-DFshkDl1.js +11 -0
  33. package/dist/assets/fa-IR-HGAKTJCU-DVU2rysM.js +8 -0
  34. package/dist/assets/fi-FI-Z5N7JZ37-BFKGqZcw.js +6 -0
  35. package/dist/assets/file-open-002ab408-DIuFHtCF.js +1 -0
  36. package/dist/assets/file-open-7c801643-684qeFg4.js +1 -0
  37. package/dist/assets/file-save-3189631c-x92wctJd.js +1 -0
  38. package/dist/assets/file-save-745eba88-Bb9F9Kg7.js +1 -0
  39. package/dist/assets/flowDb-956e92f1-CZj2LmRK.js +10 -0
  40. package/dist/assets/flowDiagram-66a62f08-D_vhln5h.js +4 -0
  41. package/dist/assets/flowDiagram-v2-96b9c2cf-CShVLez9.js +1 -0
  42. package/dist/assets/flowchart-elk-definition-4a651766-BJYKnLZA.js +139 -0
  43. package/dist/assets/fr-FR-RHASNOE6-M3Leevu1.js +9 -0
  44. package/dist/assets/ganttDiagram-c361ad54-Dl0RXJiR.js +257 -0
  45. package/dist/assets/gitGraphDiagram-72cf32ee-Cckr93z5.js +70 -0
  46. package/dist/assets/gl-ES-HMX3MZ6V-CM77q-gv.js +10 -0
  47. package/dist/assets/graph-CsvWB58Y.js +1 -0
  48. package/dist/assets/he-IL-6SHJWFNN-B3UI_ebx.js +10 -0
  49. package/dist/assets/hi-IN-IWLTKZ5I-DUAO_Nd9.js +4 -0
  50. package/dist/assets/hu-HU-A5ZG7DT2-Dm7uZbxs.js +7 -0
  51. package/dist/assets/id-ID-SAP4L64H-sn247bNe.js +10 -0
  52. package/dist/assets/image-blob-reduce.esm-B6b2_-a4.js +7 -0
  53. package/dist/assets/index-3862675e-pSTifqE7.js +1 -0
  54. package/dist/assets/index-CMTjDsOp.js +97 -0
  55. package/dist/assets/infoDiagram-f8f76790-Cy0QfHmF.js +7 -0
  56. package/dist/assets/init-Gi6I4Gst.js +1 -0
  57. package/dist/assets/it-IT-JPQ66NNP-C3cx3eLC.js +11 -0
  58. package/dist/assets/ja-JP-DBVTYXUO-P2fPONfc.js +8 -0
  59. package/dist/assets/journeyDiagram-49397b02-AWYgjzgS.js +139 -0
  60. package/dist/assets/kaa-6HZHGXH3-wmoL7do0.js +1 -0
  61. package/dist/assets/kab-KAB-ZGHBKWFO-DNNu0Xkz.js +8 -0
  62. package/dist/assets/katex-ChWnQ-fc.js +261 -0
  63. package/dist/assets/kk-KZ-P5N5QNE5-BZEEoSCw.js +1 -0
  64. package/dist/assets/km-KH-HSX4SM5Z-4Sf3SjAI.js +11 -0
  65. package/dist/assets/ko-KR-MTYHY66A-DTzTxHW5.js +9 -0
  66. package/dist/assets/ku-TR-6OUDTVRD-eWqCXim6.js +9 -0
  67. package/dist/assets/layout-ErVDAsYA.js +1 -0
  68. package/dist/assets/line-Cvp4skCK.js +1 -0
  69. package/dist/assets/linear-DlBkj_hR.js +1 -0
  70. package/dist/assets/lt-LT-XHIRWOB4-BzqmXLnL.js +3 -0
  71. package/dist/assets/lv-LV-5QDEKY6T-alH_HCjW.js +7 -0
  72. package/dist/assets/main-B9Rh8YyQ.css +1 -0
  73. package/dist/assets/main-C1TkgC3p.js +254 -0
  74. package/dist/assets/mindmap-definition-fc14e90a-DQB0p-81.js +425 -0
  75. package/dist/assets/mr-IN-CRQNXWMA-Cq2VIpVa.js +13 -0
  76. package/dist/assets/my-MM-5M5IBNSE-CBPHDdNv.js +1 -0
  77. package/dist/assets/nb-NO-T6EIAALU-BUNp_A07.js +10 -0
  78. package/dist/assets/nl-NL-IS3SIHDZ-CNz8gWhx.js +8 -0
  79. package/dist/assets/nn-NO-6E72VCQL-BFz6U-AT.js +8 -0
  80. package/dist/assets/oc-FR-POXYY2M6-BjY1Oz8X.js +8 -0
  81. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  82. package/dist/assets/pa-IN-N4M65BXN-bLY9e9o3.js +4 -0
  83. package/dist/assets/path-CbwjOpE9.js +1 -0
  84. package/dist/assets/pica-JUO0Loj6.js +7 -0
  85. package/dist/assets/pieDiagram-8a3498a8-BD6Rsa6P.js +35 -0
  86. package/dist/assets/pl-PL-T2D74RX3-CpLUuJZ4.js +9 -0
  87. package/dist/assets/pt-BR-5N22H2LF-DXSmPIb5.js +9 -0
  88. package/dist/assets/pt-PT-UZXXM6DQ-CtilzVM5.js +9 -0
  89. package/dist/assets/quadrantDiagram-120e2f19-CvG-FK5I.js +7 -0
  90. package/dist/assets/requirementDiagram-deff3bca-CVh-bBIY.js +52 -0
  91. package/dist/assets/ro-RO-JPDTUUEW-4SIE29UH.js +11 -0
  92. package/dist/assets/roundRect-0PYZxl1G.js +1 -0
  93. package/dist/assets/ru-RU-B4JR7IUQ-yWKAM4lo.js +9 -0
  94. package/dist/assets/sankeyDiagram-04a897e0-Bb-ywaQF.js +8 -0
  95. package/dist/assets/sequenceDiagram-704730f1-BZu5e-R7.js +122 -0
  96. package/dist/assets/si-LK-N5RQ5JYF-D-XE0eOh.js +1 -0
  97. package/dist/assets/sk-SK-C5VTKIMK-DTdiYCrX.js +6 -0
  98. package/dist/assets/sl-SI-NN7IZMDC-qcTJE3hs.js +6 -0
  99. package/dist/assets/stateDiagram-587899a1-CoklS1cy.js +1 -0
  100. package/dist/assets/stateDiagram-v2-d93cdb3a-JBfFG9Hu.js +1 -0
  101. package/dist/assets/styles-6aaf32cf-Cg7CSuxw.js +207 -0
  102. package/dist/assets/styles-9a916d00-Ny9GZRfC.js +160 -0
  103. package/dist/assets/styles-c10674c1-BMpWBEge.js +116 -0
  104. package/dist/assets/subset-shared.chunk-tHzjs2ro.js +84 -0
  105. package/dist/assets/subset-worker.chunk-CuKy9sB1.js +1 -0
  106. package/dist/assets/sv-SE-XGPEYMSR-Bi6tm-oG.js +10 -0
  107. package/dist/assets/svgDrawCommon-08f97a94-CEEsVKXL.js +1 -0
  108. package/dist/assets/ta-IN-2NMHFXQM-C7Igaqv2.js +9 -0
  109. package/dist/assets/th-TH-HPSO5L25-BdAfwagC.js +2 -0
  110. package/dist/assets/timeline-definition-85554ec2-DsgJKruU.js +61 -0
  111. package/dist/assets/tr-TR-DEFEU3FU-BPBRoOZh.js +7 -0
  112. package/dist/assets/uk-UA-QMV73CPH-CeJGl_H3.js +6 -0
  113. package/dist/assets/vi-VN-M7AON7JQ-D537-quZ.js +5 -0
  114. package/dist/assets/xychartDiagram-e933f94c-DqfSpZR2.js +7 -0
  115. package/dist/assets/zh-CN-LNUGB5OW-Bd8rr_cr.js +10 -0
  116. package/dist/assets/zh-HK-E62DVLB3-Y1MdNiXR.js +1 -0
  117. package/dist/assets/zh-TW-RAJ6MFWO-BAzsCe6B.js +9 -0
  118. package/dist/frontend/index.html +241 -0
  119. package/package.json +78 -0
  120. package/src/index.js +1002 -0
  121. package/src/server.js +377 -0
  122. package/src/types.js +36 -0
  123. package/src/utils/logger.js +27 -0
package/src/server.js ADDED
@@ -0,0 +1,377 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import { WebSocketServer } from 'ws';
4
+ import { createServer } from 'http';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import dotenv from 'dotenv';
8
+ import logger from './utils/logger.js';
9
+ import {
10
+ elements,
11
+ generateId,
12
+ EXCALIDRAW_ELEMENT_TYPES
13
+ } from './types.js';
14
+ import { z } from 'zod';
15
+
16
+ // Load environment variables
17
+ dotenv.config();
18
+
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = path.dirname(__filename);
21
+
22
+ const app = express();
23
+ const server = createServer(app);
24
+ const wss = new WebSocketServer({ server });
25
+
26
+ // Middleware
27
+ app.use(cors());
28
+ app.use(express.json());
29
+
30
+ // Serve static files from the build directory
31
+ const staticDir = path.join(__dirname, '../dist');
32
+ app.use(express.static(staticDir));
33
+
34
+ // WebSocket connections
35
+ const clients = new Set();
36
+
37
+ // Broadcast to all connected clients
38
+ function broadcast(message) {
39
+ const data = JSON.stringify(message);
40
+ clients.forEach(client => {
41
+ if (client.readyState === client.OPEN) {
42
+ client.send(data);
43
+ }
44
+ });
45
+ }
46
+
47
+ // WebSocket connection handling
48
+ wss.on('connection', (ws) => {
49
+ clients.add(ws);
50
+ logger.info('New WebSocket connection established');
51
+
52
+ // Send current elements to new client
53
+ ws.send(JSON.stringify({
54
+ type: 'initial_elements',
55
+ elements: Array.from(elements.values())
56
+ }));
57
+
58
+ ws.on('close', () => {
59
+ clients.delete(ws);
60
+ logger.info('WebSocket connection closed');
61
+ });
62
+
63
+ ws.on('error', (error) => {
64
+ logger.error('WebSocket error:', error);
65
+ clients.delete(ws);
66
+ });
67
+ });
68
+
69
+ // Schema validation
70
+ const CreateElementSchema = z.object({
71
+ type: z.enum(Object.values(EXCALIDRAW_ELEMENT_TYPES)),
72
+ x: z.number(),
73
+ y: z.number(),
74
+ width: z.number().optional(),
75
+ height: z.number().optional(),
76
+ backgroundColor: z.string().optional(),
77
+ strokeColor: z.string().optional(),
78
+ strokeWidth: z.number().optional(),
79
+ roughness: z.number().optional(),
80
+ opacity: z.number().optional(),
81
+ text: z.string().optional(),
82
+ fontSize: z.number().optional(),
83
+ fontFamily: z.string().optional()
84
+ });
85
+
86
+ const UpdateElementSchema = z.object({
87
+ id: z.string(),
88
+ type: z.enum(Object.values(EXCALIDRAW_ELEMENT_TYPES)).optional(),
89
+ x: z.number().optional(),
90
+ y: z.number().optional(),
91
+ width: z.number().optional(),
92
+ height: z.number().optional(),
93
+ backgroundColor: z.string().optional(),
94
+ strokeColor: z.string().optional(),
95
+ strokeWidth: z.number().optional(),
96
+ roughness: z.number().optional(),
97
+ opacity: z.number().optional(),
98
+ text: z.string().optional(),
99
+ fontSize: z.number().optional(),
100
+ fontFamily: z.string().optional()
101
+ });
102
+
103
+ // API Routes
104
+
105
+ // Get all elements
106
+ app.get('/api/elements', (req, res) => {
107
+ try {
108
+ const elementsArray = Array.from(elements.values());
109
+ res.json({
110
+ success: true,
111
+ elements: elementsArray,
112
+ count: elementsArray.length
113
+ });
114
+ } catch (error) {
115
+ logger.error('Error fetching elements:', error);
116
+ res.status(500).json({
117
+ success: false,
118
+ error: error.message
119
+ });
120
+ }
121
+ });
122
+
123
+ // Create new element
124
+ app.post('/api/elements', (req, res) => {
125
+ try {
126
+ const params = CreateElementSchema.parse(req.body);
127
+ logger.info('Creating element via API', { type: params.type });
128
+
129
+ const id = generateId();
130
+ const element = {
131
+ id,
132
+ ...params,
133
+ createdAt: new Date().toISOString(),
134
+ updatedAt: new Date().toISOString(),
135
+ version: 1
136
+ };
137
+
138
+ elements.set(id, element);
139
+
140
+ // Broadcast to all connected clients
141
+ broadcast({
142
+ type: 'element_created',
143
+ element: element
144
+ });
145
+
146
+ res.json({
147
+ success: true,
148
+ element: element
149
+ });
150
+ } catch (error) {
151
+ logger.error('Error creating element:', error);
152
+ res.status(400).json({
153
+ success: false,
154
+ error: error.message
155
+ });
156
+ }
157
+ });
158
+
159
+ // Update element
160
+ app.put('/api/elements/:id', (req, res) => {
161
+ try {
162
+ const { id } = req.params;
163
+ const updates = UpdateElementSchema.parse({ id, ...req.body });
164
+
165
+ const existingElement = elements.get(id);
166
+ if (!existingElement) {
167
+ return res.status(404).json({
168
+ success: false,
169
+ error: `Element with ID ${id} not found`
170
+ });
171
+ }
172
+
173
+ const updatedElement = {
174
+ ...existingElement,
175
+ ...updates,
176
+ updatedAt: new Date().toISOString(),
177
+ version: existingElement.version + 1
178
+ };
179
+
180
+ elements.set(id, updatedElement);
181
+
182
+ // Broadcast to all connected clients
183
+ broadcast({
184
+ type: 'element_updated',
185
+ element: updatedElement
186
+ });
187
+
188
+ res.json({
189
+ success: true,
190
+ element: updatedElement
191
+ });
192
+ } catch (error) {
193
+ logger.error('Error updating element:', error);
194
+ res.status(400).json({
195
+ success: false,
196
+ error: error.message
197
+ });
198
+ }
199
+ });
200
+
201
+ // Delete element
202
+ app.delete('/api/elements/:id', (req, res) => {
203
+ try {
204
+ const { id } = req.params;
205
+
206
+ if (!elements.has(id)) {
207
+ return res.status(404).json({
208
+ success: false,
209
+ error: `Element with ID ${id} not found`
210
+ });
211
+ }
212
+
213
+ elements.delete(id);
214
+
215
+ // Broadcast to all connected clients
216
+ broadcast({
217
+ type: 'element_deleted',
218
+ elementId: id
219
+ });
220
+
221
+ res.json({
222
+ success: true,
223
+ message: `Element ${id} deleted successfully`
224
+ });
225
+ } catch (error) {
226
+ logger.error('Error deleting element:', error);
227
+ res.status(500).json({
228
+ success: false,
229
+ error: error.message
230
+ });
231
+ }
232
+ });
233
+
234
+ // Get element by ID
235
+ app.get('/api/elements/:id', (req, res) => {
236
+ try {
237
+ const { id } = req.params;
238
+ const element = elements.get(id);
239
+
240
+ if (!element) {
241
+ return res.status(404).json({
242
+ success: false,
243
+ error: `Element with ID ${id} not found`
244
+ });
245
+ }
246
+
247
+ res.json({
248
+ success: true,
249
+ element: element
250
+ });
251
+ } catch (error) {
252
+ logger.error('Error fetching element:', error);
253
+ res.status(500).json({
254
+ success: false,
255
+ error: error.message
256
+ });
257
+ }
258
+ });
259
+
260
+ // Query elements with filters
261
+ app.get('/api/elements/search', (req, res) => {
262
+ try {
263
+ const { type, ...filters } = req.query;
264
+ let results = Array.from(elements.values());
265
+
266
+ // Filter by type if specified
267
+ if (type) {
268
+ results = results.filter(element => element.type === type);
269
+ }
270
+
271
+ // Apply additional filters
272
+ if (Object.keys(filters).length > 0) {
273
+ results = results.filter(element => {
274
+ return Object.entries(filters).every(([key, value]) => {
275
+ return element[key] === value;
276
+ });
277
+ });
278
+ }
279
+
280
+ res.json({
281
+ success: true,
282
+ elements: results,
283
+ count: results.length
284
+ });
285
+ } catch (error) {
286
+ logger.error('Error querying elements:', error);
287
+ res.status(500).json({
288
+ success: false,
289
+ error: error.message
290
+ });
291
+ }
292
+ });
293
+
294
+ // Batch create elements
295
+ app.post('/api/elements/batch', (req, res) => {
296
+ try {
297
+ const { elements: elementsToCreate } = req.body;
298
+
299
+ if (!Array.isArray(elementsToCreate)) {
300
+ return res.status(400).json({
301
+ success: false,
302
+ error: 'Expected an array of elements'
303
+ });
304
+ }
305
+
306
+ const createdElements = [];
307
+
308
+ elementsToCreate.forEach(elementData => {
309
+ const params = CreateElementSchema.parse(elementData);
310
+ const id = generateId();
311
+ const element = {
312
+ id,
313
+ ...params,
314
+ createdAt: new Date().toISOString(),
315
+ updatedAt: new Date().toISOString(),
316
+ version: 1
317
+ };
318
+
319
+ elements.set(id, element);
320
+ createdElements.push(element);
321
+ });
322
+
323
+ // Broadcast to all connected clients
324
+ broadcast({
325
+ type: 'elements_batch_created',
326
+ elements: createdElements
327
+ });
328
+
329
+ res.json({
330
+ success: true,
331
+ elements: createdElements,
332
+ count: createdElements.length
333
+ });
334
+ } catch (error) {
335
+ logger.error('Error batch creating elements:', error);
336
+ res.status(400).json({
337
+ success: false,
338
+ error: error.message
339
+ });
340
+ }
341
+ });
342
+
343
+ // Serve the frontend
344
+ app.get('/', (req, res) => {
345
+ const htmlFile = path.join(__dirname, '../dist/index.html');
346
+ res.sendFile(htmlFile);
347
+ });
348
+
349
+ // Health check endpoint
350
+ app.get('/health', (req, res) => {
351
+ res.json({
352
+ status: 'healthy',
353
+ timestamp: new Date().toISOString(),
354
+ elements_count: elements.size,
355
+ websocket_clients: clients.size
356
+ });
357
+ });
358
+
359
+ // Error handling middleware
360
+ app.use((err, req, res, next) => {
361
+ logger.error('Unhandled error:', err);
362
+ res.status(500).json({
363
+ success: false,
364
+ error: 'Internal server error'
365
+ });
366
+ });
367
+
368
+ // Start server
369
+ const PORT = process.env.PORT || 3000;
370
+ const HOST = process.env.HOST || 'localhost';
371
+
372
+ server.listen(PORT, HOST, () => {
373
+ logger.info(`POC server running on http://${HOST}:${PORT}`);
374
+ logger.info(`WebSocket server running on ws://${HOST}:${PORT}`);
375
+ });
376
+
377
+ export default app;
package/src/types.js ADDED
@@ -0,0 +1,36 @@
1
+ // Excalidraw element types
2
+ export const EXCALIDRAW_ELEMENT_TYPES = {
3
+ RECTANGLE: 'rectangle',
4
+ ELLIPSE: 'ellipse',
5
+ DIAMOND: 'diamond',
6
+ ARROW: 'arrow',
7
+ TEXT: 'text',
8
+ LABEL: 'label',
9
+ FREEDRAW: 'freedraw',
10
+ LINE: 'line',
11
+ ARROW_LABEL: 'arrowLabel'
12
+ };
13
+
14
+ // In-memory storage for Excalidraw elements
15
+ export const elements = new Map();
16
+
17
+ // Validation function for Excalidraw elements
18
+ export function validateElement(element) {
19
+ const requiredFields = ['type', 'x', 'y'];
20
+ const hasRequiredFields = requiredFields.every(field => field in element);
21
+
22
+ if (!hasRequiredFields) {
23
+ throw new Error(`Missing required fields: ${requiredFields.join(', ')}`);
24
+ }
25
+
26
+ if (!Object.values(EXCALIDRAW_ELEMENT_TYPES).includes(element.type)) {
27
+ throw new Error(`Invalid element type: ${element.type}`);
28
+ }
29
+
30
+ return true;
31
+ }
32
+
33
+ // Helper function to generate unique IDs
34
+ export function generateId() {
35
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
36
+ }
@@ -0,0 +1,27 @@
1
+ import winston from 'winston';
2
+
3
+ const logger = winston.createLogger({
4
+ level: process.env.LOG_LEVEL || 'info',
5
+
6
+ format: winston.format.combine(
7
+ winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
8
+ winston.format.uncolorize(),
9
+ winston.format.printf(info =>
10
+ `${info.timestamp} [${info.level}] ${info.message}`
11
+ )
12
+ ),
13
+
14
+ transports: [
15
+ new winston.transports.Console({
16
+ level: 'warn', // only warn+error to stderr
17
+ stderrLevels: ['warn','error']
18
+ }),
19
+
20
+ new winston.transports.File({
21
+ filename: 'excalidraw.log', // all levels to file
22
+ level: 'debug'
23
+ })
24
+ ]
25
+ });
26
+
27
+ export default logger;