vintasend-medplum 0.4.0 → 0.4.3
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.
- package/README.md +269 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -11
- package/dist/medplum-adapter.d.ts +1 -0
- package/dist/medplum-adapter.d.ts.map +1 -0
- package/dist/medplum-adapter.js +2 -6
- package/dist/medplum-attachment-manager.d.ts +1 -0
- package/dist/medplum-attachment-manager.d.ts.map +1 -0
- package/dist/medplum-attachment-manager.js +10 -18
- package/dist/medplum-backend.d.ts +1 -0
- package/dist/medplum-backend.d.ts.map +1 -0
- package/dist/medplum-backend.js +44 -94
- package/dist/medplum-logger.d.ts +1 -0
- package/dist/medplum-logger.d.ts.map +1 -0
- package/dist/medplum-logger.js +1 -5
- package/dist/pug-inline-email-template-renderer.d.ts +29 -0
- package/dist/pug-inline-email-template-renderer.d.ts.map +1 -0
- package/dist/pug-inline-email-template-renderer.js +68 -0
- package/dist/scripts/compile-pug-templates.d.ts +3 -0
- package/dist/scripts/compile-pug-templates.d.ts.map +1 -0
- package/dist/scripts/compile-pug-templates.js +71 -0
- package/package.json +17 -8
- package/src/scripts/compile-pug-templates.ts +90 -0
package/README.md
CHANGED
|
@@ -11,8 +11,61 @@ This implementation uses FHIR (Fast Healthcare Interoperability Resources) stand
|
|
|
11
11
|
- **MedplumNotificationBackend**: Stores notifications as FHIR `Communication` resources
|
|
12
12
|
- **MedplumNotificationAdapter**: Sends email notifications via Medplum's email API
|
|
13
13
|
- **MedplumAttachmentManager**: Manages file attachments using FHIR `Binary` and `Media` resources
|
|
14
|
+
- **PugInlineEmailTemplateRenderer**: Renders Pug email templates from pre-compiled JSON (ideal for production)
|
|
14
15
|
- **MedplumLogger**: Simple console-based logger
|
|
15
16
|
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Install the package
|
|
21
|
+
npm install vintasend-medplum @medplum/core
|
|
22
|
+
|
|
23
|
+
# Compile your Pug templates
|
|
24
|
+
npx compile-pug-templates ./templates ./src/compiled-templates.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { MedplumClient } from '@medplum/core';
|
|
29
|
+
import {
|
|
30
|
+
MedplumNotificationBackend,
|
|
31
|
+
MedplumNotificationAdapter,
|
|
32
|
+
PugInlineEmailTemplateRenderer,
|
|
33
|
+
MedplumLogger
|
|
34
|
+
} from 'vintasend-medplum';
|
|
35
|
+
import { NotificationService } from 'vintasend';
|
|
36
|
+
import compiledTemplates from './compiled-templates.json';
|
|
37
|
+
|
|
38
|
+
// Initialize Medplum client
|
|
39
|
+
const medplum = new MedplumClient({
|
|
40
|
+
baseUrl: 'https://api.medplum.com',
|
|
41
|
+
clientId: 'your-client-id',
|
|
42
|
+
clientSecret: 'your-client-secret',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Create services
|
|
46
|
+
const renderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
|
|
47
|
+
const adapter = new MedplumNotificationAdapter(medplum, renderer);
|
|
48
|
+
const backend = new MedplumNotificationBackend(medplum);
|
|
49
|
+
const logger = new MedplumLogger();
|
|
50
|
+
|
|
51
|
+
// Initialize notification service
|
|
52
|
+
const notificationService = new NotificationService(backend, [adapter], logger);
|
|
53
|
+
|
|
54
|
+
// Send a notification
|
|
55
|
+
await notificationService.createNotification({
|
|
56
|
+
userId: 'Patient/123',
|
|
57
|
+
notificationType: 'EMAIL',
|
|
58
|
+
contextName: 'welcome',
|
|
59
|
+
contextParameters: { firstName: 'John' },
|
|
60
|
+
title: 'Welcome!',
|
|
61
|
+
bodyTemplate: 'welcome.pug',
|
|
62
|
+
subjectTemplate: 'subjects/welcome.pug',
|
|
63
|
+
sendAfter: new Date(),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await notificationService.processPendingNotifications();
|
|
67
|
+
```
|
|
68
|
+
|
|
16
69
|
## How It Works
|
|
17
70
|
|
|
18
71
|
### FHIR Resource Mapping
|
|
@@ -94,6 +147,76 @@ npm install vintasend-medplum @medplum/core
|
|
|
94
147
|
|
|
95
148
|
## Setup
|
|
96
149
|
|
|
150
|
+
### Template Compilation
|
|
151
|
+
|
|
152
|
+
VintaSend Medplum uses pre-compiled Pug email templates that are embedded in your application as JSON. This approach ensures templates are bundled with your code and don't require file system access at runtime.
|
|
153
|
+
|
|
154
|
+
**Step 1: Organize Your Templates**
|
|
155
|
+
|
|
156
|
+
Create a directory structure for your templates:
|
|
157
|
+
```
|
|
158
|
+
templates/
|
|
159
|
+
welcome.pug
|
|
160
|
+
password-reset.pug
|
|
161
|
+
notifications/
|
|
162
|
+
order-confirmation.pug
|
|
163
|
+
shipment-update.pug
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Step 2: Compile Templates**
|
|
167
|
+
|
|
168
|
+
Run the compilation script using npx:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npx compile-pug-templates [input-directory] [output-file]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Both arguments are optional:
|
|
175
|
+
- `input-directory`: Directory containing .pug templates (default: `./templates`)
|
|
176
|
+
- `output-file`: Output JSON file path (default: `compiled-templates.json`)
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
```bash
|
|
180
|
+
# Use default values (./templates → compiled-templates.json)
|
|
181
|
+
npx compile-pug-templates
|
|
182
|
+
|
|
183
|
+
# Specify only input directory (output to compiled-templates.json)
|
|
184
|
+
npx compile-pug-templates ./email-templates
|
|
185
|
+
|
|
186
|
+
# Specify both arguments
|
|
187
|
+
npx compile-pug-templates ./templates ./src/compiled-templates.json
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Or add it to your package.json scripts:
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"scripts": {
|
|
194
|
+
"compile-templates": "compile-pug-templates ./templates ./src/compiled-templates.json"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
This generates a JSON file where keys are relative paths and values are template contents:
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"welcome.pug": "doctype html\nhtml\n body\n h1 Welcome {{firstName}}!",
|
|
203
|
+
"notifications/order-confirmation.pug": "doctype html\n..."
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Step 3: Import and Use Compiled Templates**
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { PugInlineEmailTemplateRenderer } from 'vintasend-medplum';
|
|
211
|
+
import compiledTemplates from './compiled-templates.json';
|
|
212
|
+
|
|
213
|
+
// Create the template renderer with compiled templates
|
|
214
|
+
const templateRenderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
|
|
215
|
+
|
|
216
|
+
// Use with notification adapter
|
|
217
|
+
const adapter = new MedplumNotificationAdapter(medplum, templateRenderer);
|
|
218
|
+
```
|
|
219
|
+
|
|
97
220
|
### Basic Configuration
|
|
98
221
|
|
|
99
222
|
```typescript
|
|
@@ -151,6 +274,56 @@ const backend = new MedplumNotificationBackend(medplum, {
|
|
|
151
274
|
|
|
152
275
|
## Usage Examples
|
|
153
276
|
|
|
277
|
+
### Configuring Templates
|
|
278
|
+
|
|
279
|
+
When creating notifications, reference your templates using the same paths used during compilation:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// If you compiled templates/welcome.pug
|
|
283
|
+
await notificationService.createNotification({
|
|
284
|
+
userId: 'Patient/123',
|
|
285
|
+
notificationType: 'EMAIL',
|
|
286
|
+
contextName: 'user-welcome',
|
|
287
|
+
contextParameters: {
|
|
288
|
+
firstName: 'John',
|
|
289
|
+
lastName: 'Doe',
|
|
290
|
+
},
|
|
291
|
+
title: 'Welcome to our platform!',
|
|
292
|
+
bodyTemplate: 'welcome.pug', // Path from compiled templates
|
|
293
|
+
subjectTemplate: 'subjects/welcome.pug', // Can be in subdirectories
|
|
294
|
+
sendAfter: new Date(),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// If you compiled templates/notifications/order-confirmation.pug
|
|
298
|
+
await notificationService.createNotification({
|
|
299
|
+
userId: 'Patient/456',
|
|
300
|
+
notificationType: 'EMAIL',
|
|
301
|
+
contextName: 'order-confirmation',
|
|
302
|
+
contextParameters: {
|
|
303
|
+
orderNumber: '12345',
|
|
304
|
+
totalAmount: '$99.99',
|
|
305
|
+
},
|
|
306
|
+
title: 'Order Confirmation',
|
|
307
|
+
bodyTemplate: 'notifications/order-confirmation.pug',
|
|
308
|
+
subjectTemplate: 'notifications/subjects/order-confirmation.pug',
|
|
309
|
+
sendAfter: new Date(),
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Template Example (welcome.pug):**
|
|
314
|
+
```pug
|
|
315
|
+
doctype html
|
|
316
|
+
html
|
|
317
|
+
head
|
|
318
|
+
title Welcome
|
|
319
|
+
body
|
|
320
|
+
h1 Welcome #{firstName} #{lastName}!
|
|
321
|
+
p Thank you for joining our platform.
|
|
322
|
+
p
|
|
323
|
+
| If you have any questions, feel free to
|
|
324
|
+
a(href="mailto:support@example.com") contact us
|
|
325
|
+
```
|
|
326
|
+
|
|
154
327
|
### Sending a Simple Notification
|
|
155
328
|
|
|
156
329
|
```typescript
|
|
@@ -395,6 +568,34 @@ constructor(
|
|
|
395
568
|
**Key Methods:**
|
|
396
569
|
- `send(notification, context)` - Send an email notification with attachments
|
|
397
570
|
|
|
571
|
+
### PugInlineEmailTemplateRenderer
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
class PugInlineEmailTemplateRenderer<Config extends BaseNotificationTypeConfig>
|
|
575
|
+
implements BaseEmailTemplateRenderer<Config>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Template renderer that compiles Pug templates from pre-compiled JSON strings instead of reading from file paths. This is ideal for production deployments where templates are embedded in the application.
|
|
579
|
+
|
|
580
|
+
**Constructor:**
|
|
581
|
+
```typescript
|
|
582
|
+
constructor(generatedTemplates: Record<string, string>)
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
**Parameters:**
|
|
586
|
+
- `generatedTemplates` - Object mapping template paths to template content strings (generated by `compile-pug-templates` script)
|
|
587
|
+
|
|
588
|
+
**Key Methods:**
|
|
589
|
+
- `render(notification, context)` - Compile and render both subject and body templates using the notification's template paths
|
|
590
|
+
|
|
591
|
+
**Example:**
|
|
592
|
+
```typescript
|
|
593
|
+
import compiledTemplates from './compiled-templates.json';
|
|
594
|
+
|
|
595
|
+
const renderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
|
|
596
|
+
const adapter = new MedplumNotificationAdapter(medplum, renderer);
|
|
597
|
+
```
|
|
598
|
+
|
|
398
599
|
### MedplumAttachmentManager
|
|
399
600
|
|
|
400
601
|
```typescript
|
|
@@ -430,7 +631,70 @@ Provides access to files stored in FHIR Binary resources.
|
|
|
430
631
|
|
|
431
632
|
## Best Practices
|
|
432
633
|
|
|
433
|
-
### 1.
|
|
634
|
+
### 1. Template Organization
|
|
635
|
+
|
|
636
|
+
Organize your templates in a clear directory structure:
|
|
637
|
+
|
|
638
|
+
```
|
|
639
|
+
templates/
|
|
640
|
+
subjects/ # Email subject templates
|
|
641
|
+
welcome.pug
|
|
642
|
+
order-confirmation.pug
|
|
643
|
+
bodies/ # Or organize by feature
|
|
644
|
+
welcome.pug
|
|
645
|
+
order-confirmation.pug
|
|
646
|
+
notifications/ # Group related templates
|
|
647
|
+
orders/
|
|
648
|
+
confirmation.pug
|
|
649
|
+
shipped.pug
|
|
650
|
+
users/
|
|
651
|
+
welcome.pug
|
|
652
|
+
password-reset.pug
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### 2. Template Naming Conventions
|
|
656
|
+
|
|
657
|
+
Use consistent, descriptive names:
|
|
658
|
+
```typescript
|
|
659
|
+
// ✅ Good - clear and descriptive
|
|
660
|
+
bodyTemplate: 'notifications/orders/confirmation.pug'
|
|
661
|
+
subjectTemplate: 'subjects/order-confirmation.pug'
|
|
662
|
+
|
|
663
|
+
// ❌ Bad - unclear purpose
|
|
664
|
+
bodyTemplate: 'template1.pug'
|
|
665
|
+
subjectTemplate: 'subj.pug'
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### 3. Template Variables
|
|
669
|
+
|
|
670
|
+
Document the expected context variables in each template:
|
|
671
|
+
|
|
672
|
+
```pug
|
|
673
|
+
//- templates/welcome.pug
|
|
674
|
+
//- Expected variables: firstName, lastName, loginUrl
|
|
675
|
+
doctype html
|
|
676
|
+
html
|
|
677
|
+
body
|
|
678
|
+
h1 Welcome #{firstName} #{lastName}!
|
|
679
|
+
a(href=loginUrl) Login to your account
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### 4. Compilation in Build Process
|
|
683
|
+
|
|
684
|
+
Add template compilation to your build pipeline:
|
|
685
|
+
|
|
686
|
+
```json
|
|
687
|
+
{
|
|
688
|
+
"scripts": {
|
|
689
|
+
"compile-templates": "compile-pug-templates ./templates ./src/compiled-templates.json",
|
|
690
|
+
"build": "npm run compile-templates && tsc"
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
This ensures templates are always compiled before building your application.
|
|
696
|
+
|
|
697
|
+
### 5. Use FHIR References Consistently
|
|
434
698
|
|
|
435
699
|
Always use proper FHIR reference format for user IDs:
|
|
436
700
|
|
|
@@ -444,7 +708,7 @@ userId: '123'
|
|
|
444
708
|
userId: 'user-456'
|
|
445
709
|
```
|
|
446
710
|
|
|
447
|
-
###
|
|
711
|
+
### 6. Configure Custom Extension URLs
|
|
448
712
|
|
|
449
713
|
Use your own domain for extension URLs in production:
|
|
450
714
|
|
|
@@ -454,7 +718,7 @@ const backend = new MedplumNotificationBackend(medplum, {
|
|
|
454
718
|
});
|
|
455
719
|
```
|
|
456
720
|
|
|
457
|
-
###
|
|
721
|
+
### 7. Handle Large Attachments Carefully
|
|
458
722
|
|
|
459
723
|
For large files, consider:
|
|
460
724
|
- Using file size limits
|
|
@@ -469,7 +733,7 @@ if (buffer.length > maxSize) {
|
|
|
469
733
|
}
|
|
470
734
|
```
|
|
471
735
|
|
|
472
|
-
###
|
|
736
|
+
### 8. Clean Up Orphaned Files
|
|
473
737
|
|
|
474
738
|
Regularly run cleanup to remove orphaned attachment files:
|
|
475
739
|
|
|
@@ -483,7 +747,7 @@ for (const file of orphaned) {
|
|
|
483
747
|
}
|
|
484
748
|
```
|
|
485
749
|
|
|
486
|
-
###
|
|
750
|
+
### 9. Use Pagination
|
|
487
751
|
|
|
488
752
|
Always paginate when fetching large result sets:
|
|
489
753
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { MedplumAttachmentManager, MedplumAttachmentFile } from './medplum-attachment-manager';
|
|
2
2
|
export { MedplumNotificationBackend, MedplumNotificationBackendFactory } from './medplum-backend';
|
|
3
3
|
export { MedplumNotificationAdapter } from './medplum-adapter';
|
|
4
|
+
export { PugInlineEmailTemplateRenderer, PugInlineEmailTemplateRendererFactory } from './pug-inline-email-template-renderer';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,0BAA0B,EAAE,iCAAiC,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,8BAA8B,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "MedplumAttachmentManager", { enumerable: true, get: function () { return medplum_attachment_manager_1.MedplumAttachmentManager; } });
|
|
6
|
-
Object.defineProperty(exports, "MedplumAttachmentFile", { enumerable: true, get: function () { return medplum_attachment_manager_1.MedplumAttachmentFile; } });
|
|
7
|
-
var medplum_backend_1 = require("./medplum-backend");
|
|
8
|
-
Object.defineProperty(exports, "MedplumNotificationBackend", { enumerable: true, get: function () { return medplum_backend_1.MedplumNotificationBackend; } });
|
|
9
|
-
Object.defineProperty(exports, "MedplumNotificationBackendFactory", { enumerable: true, get: function () { return medplum_backend_1.MedplumNotificationBackendFactory; } });
|
|
10
|
-
var medplum_adapter_1 = require("./medplum-adapter");
|
|
11
|
-
Object.defineProperty(exports, "MedplumNotificationAdapter", { enumerable: true, get: function () { return medplum_adapter_1.MedplumNotificationAdapter; } });
|
|
1
|
+
export { MedplumAttachmentManager, MedplumAttachmentFile } from './medplum-attachment-manager';
|
|
2
|
+
export { MedplumNotificationBackend, MedplumNotificationBackendFactory } from './medplum-backend';
|
|
3
|
+
export { MedplumNotificationAdapter } from './medplum-adapter';
|
|
4
|
+
export { PugInlineEmailTemplateRenderer, PugInlineEmailTemplateRendererFactory } from './pug-inline-email-template-renderer';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medplum-adapter.d.ts","sourceRoot":"","sources":["../src/medplum-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sFAAsF,CAAC;AACtI,OAAO,EAAE,uBAAuB,EAAE,MAAM,yEAAyE,CAAC;AAClH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE,qBAAa,0BAA0B,CACrC,gBAAgB,SAAS,yBAAyB,CAAC,MAAM,CAAC,EAC1D,MAAM,SAAS,0BAA0B,CACzC,SAAQ,uBAAuB,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAIvD,OAAO,CAAC,OAAO;IAHV,GAAG,EAAE,MAAM,GAAG,IAAI,CAAmB;gBAGlC,OAAO,EAAE,aAAa,EAC9B,gBAAgB,EAAE,gBAAgB;IAMpC,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAEK,IAAI,CAAC,YAAY,EAAE,uBAAuB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;cA0C7E,kBAAkB,CAChC,WAAW,EAAE,gBAAgB,EAAE,GAC9B,OAAO,CAAC,KAAK,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CAYJ"}
|
package/dist/medplum-adapter.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.MedplumNotificationAdapter = void 0;
|
|
4
|
-
const base_notification_adapter_1 = require("vintasend/dist/services/notification-adapters/base-notification-adapter");
|
|
5
|
-
class MedplumNotificationAdapter extends base_notification_adapter_1.BaseNotificationAdapter {
|
|
1
|
+
import { BaseNotificationAdapter } from 'vintasend/dist/services/notification-adapters/base-notification-adapter';
|
|
2
|
+
export class MedplumNotificationAdapter extends BaseNotificationAdapter {
|
|
6
3
|
constructor(medplum, templateRenderer) {
|
|
7
4
|
const notificationType = 'EMAIL';
|
|
8
5
|
super(templateRenderer, notificationType, false);
|
|
@@ -46,4 +43,3 @@ class MedplumNotificationAdapter extends base_notification_adapter_1.BaseNotific
|
|
|
46
43
|
}));
|
|
47
44
|
}
|
|
48
45
|
}
|
|
49
|
-
exports.MedplumNotificationAdapter = MedplumNotificationAdapter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medplum-attachment-manager.d.ts","sourceRoot":"","sources":["../src/medplum-attachment-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,oEAAoE,CAAC;AAC3G,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,cAAc,EACf,MAAM,iCAAiC,CAAC;AAEzC;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,qBAAqB;IACrD,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,aAAa;IAI1C;;;;;;;OAOG;IACG,UAAU,CACd,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,oBAAoB,CAAC;IA6DhC;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA0BnE;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB/C;;;;;OAKG;IACH,yBAAyB,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc;CAgBpF;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,cAAc;IAIxD,OAAO,CAAC,OAAO;IAHjB,OAAO,CAAC,QAAQ,CAAS;gBAGf,OAAO,EAAE,aAAa,EAC9B,SAAS,EAAE,MAAM;IAUnB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAQ7B;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAUvC;;;;;;;;OAQG;IACG,GAAG,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAO5C;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAG9B"}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MedplumAttachmentFile = exports.MedplumAttachmentManager = void 0;
|
|
4
|
-
const base_attachment_manager_1 = require("vintasend/dist/services/attachment-manager/base-attachment-manager");
|
|
1
|
+
import { BaseAttachmentManager } from 'vintasend/dist/services/attachment-manager/base-attachment-manager';
|
|
5
2
|
/**
|
|
6
3
|
* Medplum AttachmentManager implementation.
|
|
7
4
|
*
|
|
@@ -11,7 +8,7 @@ const base_attachment_manager_1 = require("vintasend/dist/services/attachment-ma
|
|
|
11
8
|
*
|
|
12
9
|
* Files are uploaded to Medplum's storage and can be accessed via URLs.
|
|
13
10
|
*/
|
|
14
|
-
class MedplumAttachmentManager extends
|
|
11
|
+
export class MedplumAttachmentManager extends BaseAttachmentManager {
|
|
15
12
|
constructor(medplum) {
|
|
16
13
|
super();
|
|
17
14
|
this.medplum = medplum;
|
|
@@ -25,7 +22,6 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
|
|
|
25
22
|
* @returns AttachmentFileRecord with metadata about the uploaded file
|
|
26
23
|
*/
|
|
27
24
|
async uploadFile(file, filename, contentType) {
|
|
28
|
-
var _a, _b;
|
|
29
25
|
// Convert file to Buffer
|
|
30
26
|
const buffer = await this.fileToBuffer(file);
|
|
31
27
|
// Calculate checksum for deduplication
|
|
@@ -74,8 +70,8 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
|
|
|
74
70
|
url: binaryUrl,
|
|
75
71
|
creation: createdMedia.content.creation,
|
|
76
72
|
},
|
|
77
|
-
createdAt:
|
|
78
|
-
updatedAt:
|
|
73
|
+
createdAt: createdMedia.meta?.lastUpdated ? new Date(createdMedia.meta.lastUpdated) : new Date(),
|
|
74
|
+
updatedAt: createdMedia.meta?.lastUpdated ? new Date(createdMedia.meta.lastUpdated) : new Date(),
|
|
79
75
|
};
|
|
80
76
|
}
|
|
81
77
|
/**
|
|
@@ -85,7 +81,6 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
|
|
|
85
81
|
* @returns The file record or null if not found
|
|
86
82
|
*/
|
|
87
83
|
async getFile(fileId) {
|
|
88
|
-
var _a, _b, _c, _d;
|
|
89
84
|
try {
|
|
90
85
|
const media = await this.medplum.readResource('Media', fileId);
|
|
91
86
|
if (!media.content || !media.id) {
|
|
@@ -96,16 +91,16 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
|
|
|
96
91
|
filename: media.content.title || 'untitled',
|
|
97
92
|
contentType: media.content.contentType || 'application/octet-stream',
|
|
98
93
|
size: media.content.size || 0,
|
|
99
|
-
checksum:
|
|
94
|
+
checksum: media.identifier?.[0]?.value || '',
|
|
100
95
|
storageMetadata: {
|
|
101
96
|
url: media.content.url,
|
|
102
97
|
creation: media.content.creation,
|
|
103
98
|
},
|
|
104
|
-
createdAt:
|
|
105
|
-
updatedAt:
|
|
99
|
+
createdAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
100
|
+
updatedAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
106
101
|
};
|
|
107
102
|
}
|
|
108
|
-
catch
|
|
103
|
+
catch {
|
|
109
104
|
return null;
|
|
110
105
|
}
|
|
111
106
|
}
|
|
@@ -117,11 +112,10 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
|
|
|
117
112
|
* @param fileId - The Media resource ID
|
|
118
113
|
*/
|
|
119
114
|
async deleteFile(fileId) {
|
|
120
|
-
var _a;
|
|
121
115
|
// Get Media resource to find the Binary URL
|
|
122
116
|
const media = await this.medplum.readResource('Media', fileId);
|
|
123
117
|
// Extract Binary ID from URL
|
|
124
|
-
const binaryUrl =
|
|
118
|
+
const binaryUrl = media.content?.url;
|
|
125
119
|
if (binaryUrl) {
|
|
126
120
|
const match = binaryUrl.match(/Binary\/([^/]+)/);
|
|
127
121
|
if (match) {
|
|
@@ -162,13 +156,12 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
|
|
|
162
156
|
return new MedplumAttachmentFile(this.medplum, binaryUrl);
|
|
163
157
|
}
|
|
164
158
|
}
|
|
165
|
-
exports.MedplumAttachmentManager = MedplumAttachmentManager;
|
|
166
159
|
/**
|
|
167
160
|
* Medplum AttachmentFile implementation.
|
|
168
161
|
*
|
|
169
162
|
* Provides access to files stored in Medplum Binary resources.
|
|
170
163
|
*/
|
|
171
|
-
class MedplumAttachmentFile {
|
|
164
|
+
export class MedplumAttachmentFile {
|
|
172
165
|
constructor(medplum, binaryUrl) {
|
|
173
166
|
this.medplum = medplum;
|
|
174
167
|
// Extract Binary ID from URL (format: "Binary/{id}")
|
|
@@ -225,4 +218,3 @@ class MedplumAttachmentFile {
|
|
|
225
218
|
await this.medplum.deleteResource('Binary', this.binaryId);
|
|
226
219
|
}
|
|
227
220
|
}
|
|
228
|
-
exports.MedplumAttachmentFile = MedplumAttachmentFile;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medplum-backend.d.ts","sourceRoot":"","sources":["../src/medplum-backend.ts"],"names":[],"mappings":"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"}
|
package/dist/medplum-backend.js
CHANGED
|
@@ -1,40 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.MedplumNotificationBackendFactory = exports.MedplumNotificationBackend = void 0;
|
|
37
|
-
class MedplumNotificationBackend {
|
|
1
|
+
export class MedplumNotificationBackend {
|
|
38
2
|
constructor(medplum, options = {
|
|
39
3
|
emailNotificationSubjectExtensionUrl: 'http://vintasend.com/fhir/StructureDefinition/email-notification-subject',
|
|
40
4
|
}) {
|
|
@@ -57,27 +21,26 @@ class MedplumNotificationBackend {
|
|
|
57
21
|
return this.attachmentManager;
|
|
58
22
|
}
|
|
59
23
|
mapToDatabaseNotification(communication) {
|
|
60
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
61
24
|
const notificationId = communication.id;
|
|
62
|
-
const referenceString =
|
|
25
|
+
const referenceString = communication.recipient?.[0]?.reference;
|
|
63
26
|
// Extract subjectTemplate from payload extension
|
|
64
|
-
const subjectTemplateExtension =
|
|
65
|
-
const subjectTemplate =
|
|
27
|
+
const subjectTemplateExtension = communication.payload?.[0]?.extension?.find((ext) => ext.url === this.options.emailNotificationSubjectExtensionUrl);
|
|
28
|
+
const subjectTemplate = subjectTemplateExtension?.valueString || null;
|
|
66
29
|
// Extract one-off notification fields from extensions
|
|
67
|
-
const emailOrPhoneExtension =
|
|
68
|
-
const firstNameExtension =
|
|
69
|
-
const lastNameExtension =
|
|
70
|
-
const emailOrPhone = emailOrPhoneExtension
|
|
71
|
-
const firstName = firstNameExtension
|
|
72
|
-
const lastName = lastNameExtension
|
|
30
|
+
const emailOrPhoneExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/emailOrPhone');
|
|
31
|
+
const firstNameExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/firstName');
|
|
32
|
+
const lastNameExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/lastName');
|
|
33
|
+
const emailOrPhone = emailOrPhoneExtension?.valueString;
|
|
34
|
+
const firstName = firstNameExtension?.valueString;
|
|
35
|
+
const lastName = lastNameExtension?.valueString;
|
|
73
36
|
const baseNotification = {
|
|
74
37
|
id: notificationId,
|
|
75
|
-
notificationType:
|
|
76
|
-
title:
|
|
77
|
-
contextName:
|
|
78
|
-
contextParameters: JSON.parse(
|
|
38
|
+
notificationType: communication.meta?.tag?.[2]?.code,
|
|
39
|
+
title: communication.topic?.text || null,
|
|
40
|
+
contextName: communication.meta?.tag?.[1]?.code,
|
|
41
|
+
contextParameters: JSON.parse(communication.note?.[0]?.text || '{}'),
|
|
79
42
|
sendAfter: communication.sent ? new Date(communication.sent) : null,
|
|
80
|
-
bodyTemplate:
|
|
43
|
+
bodyTemplate: communication.payload?.[0]?.contentString || '',
|
|
81
44
|
subjectTemplate: subjectTemplate,
|
|
82
45
|
extraParams: {},
|
|
83
46
|
status: communication.status === 'completed' ? 'SENT' : communication.status === 'stopped' ? 'FAILED' : 'PENDING_SEND',
|
|
@@ -85,8 +48,8 @@ class MedplumNotificationBackend {
|
|
|
85
48
|
adapterUsed: null,
|
|
86
49
|
sentAt: communication.status === 'completed' ? new Date(communication.sent || new Date()) : null,
|
|
87
50
|
readAt: null,
|
|
88
|
-
createdAt: new Date(
|
|
89
|
-
updatedAt: new Date(
|
|
51
|
+
createdAt: new Date(communication.meta?.lastUpdated || new Date()),
|
|
52
|
+
updatedAt: new Date(communication.meta?.lastUpdated || new Date()),
|
|
90
53
|
};
|
|
91
54
|
// Check if this is a one-off notification
|
|
92
55
|
if (emailOrPhone && firstName && lastName) {
|
|
@@ -168,7 +131,6 @@ class MedplumNotificationBackend {
|
|
|
168
131
|
.filter((n) => 'userId' in n);
|
|
169
132
|
}
|
|
170
133
|
async persistNotification(notification) {
|
|
171
|
-
var _a;
|
|
172
134
|
// Build base payload with body template
|
|
173
135
|
const payload = notification.bodyTemplate
|
|
174
136
|
? [
|
|
@@ -190,7 +152,7 @@ class MedplumNotificationBackend {
|
|
|
190
152
|
const communication = {
|
|
191
153
|
resourceType: 'Communication',
|
|
192
154
|
status: 'in-progress',
|
|
193
|
-
sent:
|
|
155
|
+
sent: notification.sendAfter?.toISOString(),
|
|
194
156
|
topic: { text: notification.title || undefined },
|
|
195
157
|
payload,
|
|
196
158
|
recipient: [{ reference: notification.userId }],
|
|
@@ -235,13 +197,12 @@ class MedplumNotificationBackend {
|
|
|
235
197
|
return communications.map((comm) => this.mapToDatabaseNotification(comm));
|
|
236
198
|
}
|
|
237
199
|
async bulkPersistNotifications(notifications) {
|
|
238
|
-
var _a;
|
|
239
200
|
const ids = [];
|
|
240
201
|
for (const notification of notifications) {
|
|
241
202
|
const communication = {
|
|
242
203
|
resourceType: 'Communication',
|
|
243
204
|
status: 'in-progress',
|
|
244
|
-
sent:
|
|
205
|
+
sent: notification.sendAfter?.toISOString(),
|
|
245
206
|
topic: { text: notification.title || undefined },
|
|
246
207
|
payload: notification.bodyTemplate
|
|
247
208
|
? [
|
|
@@ -270,7 +231,7 @@ class MedplumNotificationBackend {
|
|
|
270
231
|
}
|
|
271
232
|
async markAsSent(notificationId, checkIsPending = true) {
|
|
272
233
|
const notification = await this.getNotification(notificationId, false);
|
|
273
|
-
if (checkIsPending &&
|
|
234
|
+
if (checkIsPending && notification?.status !== 'PENDING_SEND') {
|
|
274
235
|
throw new Error('Notification is not pending');
|
|
275
236
|
}
|
|
276
237
|
// Use internal update that returns AnyDatabaseNotification
|
|
@@ -288,7 +249,7 @@ class MedplumNotificationBackend {
|
|
|
288
249
|
}
|
|
289
250
|
async markAsFailed(notificationId, checkIsPending = true) {
|
|
290
251
|
const notification = await this.getNotification(notificationId, false);
|
|
291
|
-
if (checkIsPending &&
|
|
252
|
+
if (checkIsPending && notification?.status !== 'PENDING_SEND') {
|
|
292
253
|
throw new Error('Notification is not pending');
|
|
293
254
|
}
|
|
294
255
|
// Use internal update that returns AnyDatabaseNotification
|
|
@@ -306,7 +267,7 @@ class MedplumNotificationBackend {
|
|
|
306
267
|
}
|
|
307
268
|
async markAsRead(notificationId, checkIsSent = true) {
|
|
308
269
|
const notification = await this.getNotification(notificationId, false);
|
|
309
|
-
if (checkIsSent &&
|
|
270
|
+
if (checkIsSent && notification?.status !== 'SENT') {
|
|
310
271
|
throw new Error('Notification is not sent');
|
|
311
272
|
}
|
|
312
273
|
// Ensure it's a regular notification (not one-off)
|
|
@@ -325,7 +286,7 @@ class MedplumNotificationBackend {
|
|
|
325
286
|
const communication = await this.medplum.readResource('Communication', notificationId);
|
|
326
287
|
return this.mapToDatabaseNotification(communication);
|
|
327
288
|
}
|
|
328
|
-
catch
|
|
289
|
+
catch {
|
|
329
290
|
return null;
|
|
330
291
|
}
|
|
331
292
|
}
|
|
@@ -352,10 +313,9 @@ class MedplumNotificationBackend {
|
|
|
352
313
|
.filter((notif) => 'userId' in notif && !notif.readAt);
|
|
353
314
|
}
|
|
354
315
|
async getUserEmailFromNotification(notificationId) {
|
|
355
|
-
var _a, _b, _c, _d;
|
|
356
316
|
try {
|
|
357
317
|
const communication = await this.medplum.readResource('Communication', notificationId);
|
|
358
|
-
const practitionerRef =
|
|
318
|
+
const practitionerRef = communication.recipient?.[0]?.reference;
|
|
359
319
|
if (!practitionerRef) {
|
|
360
320
|
return undefined;
|
|
361
321
|
}
|
|
@@ -366,7 +326,7 @@ class MedplumNotificationBackend {
|
|
|
366
326
|
return undefined;
|
|
367
327
|
}
|
|
368
328
|
const resource = await this.medplum.readResource(resourceType, id);
|
|
369
|
-
return
|
|
329
|
+
return resource.telecom?.find((t) => t.system === 'email')?.value;
|
|
370
330
|
}
|
|
371
331
|
catch (error) {
|
|
372
332
|
// eslint-disable-next-line no-console
|
|
@@ -384,7 +344,6 @@ class MedplumNotificationBackend {
|
|
|
384
344
|
}
|
|
385
345
|
/* One-off notification methods */
|
|
386
346
|
async persistOneOffNotification(notification) {
|
|
387
|
-
var _a;
|
|
388
347
|
// Build base payload with body template
|
|
389
348
|
const payload = notification.bodyTemplate
|
|
390
349
|
? [
|
|
@@ -406,7 +365,7 @@ class MedplumNotificationBackend {
|
|
|
406
365
|
const communication = {
|
|
407
366
|
resourceType: 'Communication',
|
|
408
367
|
status: 'in-progress',
|
|
409
|
-
sent:
|
|
368
|
+
sent: notification.sendAfter?.toISOString(),
|
|
410
369
|
topic: { text: notification.title || undefined },
|
|
411
370
|
payload,
|
|
412
371
|
note: [{ text: JSON.stringify(notification.contextParameters) }],
|
|
@@ -475,7 +434,7 @@ class MedplumNotificationBackend {
|
|
|
475
434
|
}
|
|
476
435
|
if (typeof file === 'string') {
|
|
477
436
|
// File path - read from filesystem
|
|
478
|
-
const fs = await
|
|
437
|
+
const fs = await import('node:fs/promises');
|
|
479
438
|
return await fs.readFile(file);
|
|
480
439
|
}
|
|
481
440
|
// ReadableStream or Readable
|
|
@@ -507,7 +466,6 @@ class MedplumNotificationBackend {
|
|
|
507
466
|
* Helper: Convert FHIR Media resource to AttachmentFileRecord
|
|
508
467
|
*/
|
|
509
468
|
mediaToAttachmentFileRecord(media) {
|
|
510
|
-
var _a, _b, _c, _d;
|
|
511
469
|
const content = media.content;
|
|
512
470
|
if (!content || !media.id) {
|
|
513
471
|
throw new Error('Invalid Media resource: missing content or id');
|
|
@@ -517,21 +475,20 @@ class MedplumNotificationBackend {
|
|
|
517
475
|
filename: content.title || 'untitled',
|
|
518
476
|
contentType: content.contentType || 'application/octet-stream',
|
|
519
477
|
size: content.size || 0,
|
|
520
|
-
checksum:
|
|
478
|
+
checksum: media.identifier?.[0]?.value || '',
|
|
521
479
|
storageMetadata: {
|
|
522
480
|
url: content.url,
|
|
523
481
|
creation: content.creation,
|
|
524
482
|
},
|
|
525
|
-
createdAt:
|
|
526
|
-
updatedAt:
|
|
483
|
+
createdAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
484
|
+
updatedAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
|
|
527
485
|
};
|
|
528
486
|
}
|
|
529
487
|
/**
|
|
530
488
|
* Helper: Create FHIR Attachment from file record
|
|
531
489
|
*/
|
|
532
490
|
async createFhirAttachment(fileRecord) {
|
|
533
|
-
|
|
534
|
-
const url = (_a = fileRecord.storageMetadata) === null || _a === void 0 ? void 0 : _a.url;
|
|
491
|
+
const url = fileRecord.storageMetadata?.url;
|
|
535
492
|
return {
|
|
536
493
|
contentType: fileRecord.contentType,
|
|
537
494
|
url: url,
|
|
@@ -552,7 +509,7 @@ class MedplumNotificationBackend {
|
|
|
552
509
|
const media = await this.medplum.readResource('Media', fileId);
|
|
553
510
|
return this.mediaToAttachmentFileRecord(media);
|
|
554
511
|
}
|
|
555
|
-
catch
|
|
512
|
+
catch {
|
|
556
513
|
return null;
|
|
557
514
|
}
|
|
558
515
|
}
|
|
@@ -564,7 +521,7 @@ class MedplumNotificationBackend {
|
|
|
564
521
|
}
|
|
565
522
|
return this.mediaToAttachmentFileRecord(results[0]);
|
|
566
523
|
}
|
|
567
|
-
catch
|
|
524
|
+
catch {
|
|
568
525
|
return null;
|
|
569
526
|
}
|
|
570
527
|
}
|
|
@@ -572,7 +529,6 @@ class MedplumNotificationBackend {
|
|
|
572
529
|
* Find multiple attachment files by their checksums in a single query
|
|
573
530
|
*/
|
|
574
531
|
async findAttachmentFilesByChecksums(checksums) {
|
|
575
|
-
var _a, _b;
|
|
576
532
|
if (checksums.length === 0) {
|
|
577
533
|
return new Map();
|
|
578
534
|
}
|
|
@@ -583,14 +539,14 @@ class MedplumNotificationBackend {
|
|
|
583
539
|
// Map results by checksum
|
|
584
540
|
const filesByChecksum = new Map();
|
|
585
541
|
for (const media of results) {
|
|
586
|
-
const checksum =
|
|
542
|
+
const checksum = media.identifier?.[0]?.value;
|
|
587
543
|
if (checksum) {
|
|
588
544
|
filesByChecksum.set(checksum, this.mediaToAttachmentFileRecord(media));
|
|
589
545
|
}
|
|
590
546
|
}
|
|
591
547
|
return filesByChecksum;
|
|
592
548
|
}
|
|
593
|
-
catch
|
|
549
|
+
catch {
|
|
594
550
|
return new Map();
|
|
595
551
|
}
|
|
596
552
|
}
|
|
@@ -684,7 +640,6 @@ class MedplumNotificationBackend {
|
|
|
684
640
|
return payload;
|
|
685
641
|
}
|
|
686
642
|
async deleteAttachmentFile(fileId) {
|
|
687
|
-
var _a;
|
|
688
643
|
// Check if file exists first
|
|
689
644
|
const fileRecord = await this.getAttachmentFile(fileId);
|
|
690
645
|
if (!fileRecord) {
|
|
@@ -695,7 +650,7 @@ class MedplumNotificationBackend {
|
|
|
695
650
|
const communications = await this.medplum.searchResources('Communication', {
|
|
696
651
|
_tag: 'notification',
|
|
697
652
|
});
|
|
698
|
-
const isReferenced = communications.some((comm) =>
|
|
653
|
+
const isReferenced = communications.some((comm) => comm.payload?.some((p) => p.contentAttachment?.url?.includes(fileId)));
|
|
699
654
|
if (isReferenced) {
|
|
700
655
|
throw new Error('Cannot delete attachment file: still referenced by notifications');
|
|
701
656
|
}
|
|
@@ -704,12 +659,12 @@ class MedplumNotificationBackend {
|
|
|
704
659
|
await this.attachmentManager.deleteFile(fileId);
|
|
705
660
|
}
|
|
706
661
|
// Delete Binary resource if it exists
|
|
707
|
-
if (
|
|
662
|
+
if (fileRecord.storageMetadata?.url) {
|
|
708
663
|
const binaryId = fileRecord.storageMetadata.url.replace('Binary/', '');
|
|
709
664
|
try {
|
|
710
665
|
await this.medplum.deleteResource('Binary', binaryId);
|
|
711
666
|
}
|
|
712
|
-
catch
|
|
667
|
+
catch {
|
|
713
668
|
// Binary may not exist or already deleted
|
|
714
669
|
}
|
|
715
670
|
}
|
|
@@ -717,7 +672,6 @@ class MedplumNotificationBackend {
|
|
|
717
672
|
await this.medplum.deleteResource('Media', fileId);
|
|
718
673
|
}
|
|
719
674
|
async getOrphanedAttachmentFiles() {
|
|
720
|
-
var _a;
|
|
721
675
|
// Get all Media resources tagged as attachment-files
|
|
722
676
|
const allMedia = await this.medplum.searchResources('Media', {
|
|
723
677
|
_tag: 'attachment-file',
|
|
@@ -730,7 +684,7 @@ class MedplumNotificationBackend {
|
|
|
730
684
|
const referencedIds = new Set();
|
|
731
685
|
for (const comm of communications) {
|
|
732
686
|
for (const payload of comm.payload || []) {
|
|
733
|
-
const url =
|
|
687
|
+
const url = payload.contentAttachment?.url;
|
|
734
688
|
if (url) {
|
|
735
689
|
// Extract Media ID from URL (assuming format like Media/{id})
|
|
736
690
|
const match = url.match(/Media\/([^/]+)/);
|
|
@@ -786,13 +740,13 @@ class MedplumNotificationBackend {
|
|
|
786
740
|
checksum: fileRecord.checksum,
|
|
787
741
|
createdAt: fileRecord.createdAt,
|
|
788
742
|
file: attachmentFile,
|
|
789
|
-
description: payloadData
|
|
743
|
+
description: payloadData?.description,
|
|
790
744
|
storageMetadata: fileRecord.storageMetadata,
|
|
791
745
|
});
|
|
792
746
|
}
|
|
793
747
|
return attachments;
|
|
794
748
|
}
|
|
795
|
-
catch
|
|
749
|
+
catch {
|
|
796
750
|
return [];
|
|
797
751
|
}
|
|
798
752
|
}
|
|
@@ -800,8 +754,7 @@ class MedplumNotificationBackend {
|
|
|
800
754
|
const communication = await this.medplum.readResource('Communication', notificationId);
|
|
801
755
|
// Check if attachment exists
|
|
802
756
|
const hasAttachment = (communication.payload || []).some((p) => {
|
|
803
|
-
|
|
804
|
-
const url = (_a = p.contentAttachment) === null || _a === void 0 ? void 0 : _a.url;
|
|
757
|
+
const url = p.contentAttachment?.url;
|
|
805
758
|
return url && url.includes(attachmentId);
|
|
806
759
|
});
|
|
807
760
|
if (!hasAttachment) {
|
|
@@ -809,8 +762,7 @@ class MedplumNotificationBackend {
|
|
|
809
762
|
}
|
|
810
763
|
// Filter out the attachment from payload
|
|
811
764
|
const updatedPayload = (communication.payload || []).filter((p) => {
|
|
812
|
-
|
|
813
|
-
const url = (_a = p.contentAttachment) === null || _a === void 0 ? void 0 : _a.url;
|
|
765
|
+
const url = p.contentAttachment?.url;
|
|
814
766
|
if (!url)
|
|
815
767
|
return true;
|
|
816
768
|
return !url.includes(attachmentId);
|
|
@@ -822,10 +774,8 @@ class MedplumNotificationBackend {
|
|
|
822
774
|
await this.medplum.updateResource(updated);
|
|
823
775
|
}
|
|
824
776
|
}
|
|
825
|
-
|
|
826
|
-
class MedplumNotificationBackendFactory {
|
|
777
|
+
export class MedplumNotificationBackendFactory {
|
|
827
778
|
create(medplum) {
|
|
828
779
|
return new MedplumNotificationBackend(medplum);
|
|
829
780
|
}
|
|
830
781
|
}
|
|
831
|
-
exports.MedplumNotificationBackendFactory = MedplumNotificationBackendFactory;
|
package/dist/medplum-logger.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medplum-logger.d.ts","sourceRoot":"","sources":["../src/medplum-logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAE9E,qBAAa,aAAc,YAAW,UAAU;IAC9C,OAAO,CAAC,MAAM,CAAiB;;IAM/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG5B"}
|
package/dist/medplum-logger.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MedplumLogger = void 0;
|
|
4
|
-
class MedplumLogger {
|
|
1
|
+
export class MedplumLogger {
|
|
5
2
|
constructor() {
|
|
6
3
|
this.logger = console;
|
|
7
4
|
}
|
|
@@ -15,4 +12,3 @@ class MedplumLogger {
|
|
|
15
12
|
this.logger.warn(message);
|
|
16
13
|
}
|
|
17
14
|
}
|
|
18
|
-
exports.MedplumLogger = MedplumLogger;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BaseEmailTemplateRenderer } from 'vintasend';
|
|
2
|
+
import type { JsonObject } from 'vintasend/dist/types/json-values';
|
|
3
|
+
import type { DatabaseNotification } from 'vintasend/dist/types/notification';
|
|
4
|
+
import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
|
|
5
|
+
import type { BaseLogger } from 'vintasend/dist/services/loggers/base-logger';
|
|
6
|
+
/**
|
|
7
|
+
* Custom email template renderer that compiles Pug templates from strings
|
|
8
|
+
* instead of reading from file paths.
|
|
9
|
+
*
|
|
10
|
+
* This is necessary for bot deployments where templates are embedded as constants
|
|
11
|
+
* rather than separate files.
|
|
12
|
+
*/
|
|
13
|
+
export declare class PugInlineEmailTemplateRenderer<Config extends BaseNotificationTypeConfig> implements BaseEmailTemplateRenderer<Config> {
|
|
14
|
+
private templates;
|
|
15
|
+
private logger;
|
|
16
|
+
constructor(generatedTemplates: Record<string, string>);
|
|
17
|
+
/**
|
|
18
|
+
* Inject logger (called by VintaSend when logger exists)
|
|
19
|
+
*/
|
|
20
|
+
injectLogger(logger: BaseLogger): void;
|
|
21
|
+
render(notification: DatabaseNotification<Config>, context: JsonObject): Promise<{
|
|
22
|
+
subject: string;
|
|
23
|
+
body: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export declare class PugInlineEmailTemplateRendererFactory<Config extends BaseNotificationTypeConfig> {
|
|
27
|
+
create(generatedTemplates: Record<string, string>): PugInlineEmailTemplateRenderer<Config>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=pug-inline-email-template-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pug-inline-email-template-renderer.d.ts","sourceRoot":"","sources":["../src/pug-inline-email-template-renderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAG9E;;;;;;GAMG;AACH,qBAAa,8BAA8B,CAAC,MAAM,SAAS,0BAA0B,CACnF,YAAW,yBAAyB,CAAC,MAAM,CAAC;IAE5C,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,MAAM,CAA2B;gBAE7B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAItD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIhC,MAAM,CACV,YAAY,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAC1C,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CA6C9C;AAED,qBAAa,qCAAqC,CAAC,MAAM,SAAS,0BAA0B;IAC1F,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,8BAA8B,CAAC,MAAM,CAAC;CAG3F"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as pug from 'pug';
|
|
2
|
+
/**
|
|
3
|
+
* Custom email template renderer that compiles Pug templates from strings
|
|
4
|
+
* instead of reading from file paths.
|
|
5
|
+
*
|
|
6
|
+
* This is necessary for bot deployments where templates are embedded as constants
|
|
7
|
+
* rather than separate files.
|
|
8
|
+
*/
|
|
9
|
+
export class PugInlineEmailTemplateRenderer {
|
|
10
|
+
constructor(generatedTemplates) {
|
|
11
|
+
this.logger = null;
|
|
12
|
+
this.templates = generatedTemplates;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Inject logger (called by VintaSend when logger exists)
|
|
16
|
+
*/
|
|
17
|
+
injectLogger(logger) {
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
}
|
|
20
|
+
async render(notification, context) {
|
|
21
|
+
// Check if body template is provided
|
|
22
|
+
const bodyTemplateKey = notification.bodyTemplate;
|
|
23
|
+
if (!bodyTemplateKey) {
|
|
24
|
+
throw new Error('Body template is required');
|
|
25
|
+
}
|
|
26
|
+
if (!(bodyTemplateKey in this.templates)) {
|
|
27
|
+
throw new Error(`Body template "${bodyTemplateKey}" not found in templates`);
|
|
28
|
+
}
|
|
29
|
+
// Check if subject template is provided
|
|
30
|
+
const subjectTemplateKey = notification.subjectTemplate;
|
|
31
|
+
if (!subjectTemplateKey) {
|
|
32
|
+
throw new Error('Subject template is required');
|
|
33
|
+
}
|
|
34
|
+
if (!(subjectTemplateKey in this.templates)) {
|
|
35
|
+
throw new Error(`Subject template "${subjectTemplateKey}" not found in templates`);
|
|
36
|
+
}
|
|
37
|
+
let body;
|
|
38
|
+
try {
|
|
39
|
+
// Compile and render the body template from string
|
|
40
|
+
const bodyTemplate = pug.compile(this.templates[bodyTemplateKey]);
|
|
41
|
+
body = bodyTemplate(context);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (this.logger) {
|
|
45
|
+
this.logger.error('[PugInlineEmailTemplateRenderer] Error rendering body template');
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
let subject;
|
|
50
|
+
try {
|
|
51
|
+
// Compile and render the subject template from string
|
|
52
|
+
const subjectTemplate = pug.compile(this.templates[subjectTemplateKey]);
|
|
53
|
+
subject = subjectTemplate(context);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (this.logger) {
|
|
57
|
+
this.logger.error('[PugInlineEmailTemplateRenderer] Error rendering subject template');
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
return { subject, body };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class PugInlineEmailTemplateRendererFactory {
|
|
65
|
+
create(generatedTemplates) {
|
|
66
|
+
return new PugInlineEmailTemplateRenderer(generatedTemplates);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-pug-templates.d.ts","sourceRoot":"","sources":["../../src/scripts/compile-pug-templates.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Recursively finds all .pug files in a directory
|
|
6
|
+
*/
|
|
7
|
+
function findPugFiles(dir, baseDir, files = new Map()) {
|
|
8
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullPath = path.join(dir, entry.name);
|
|
11
|
+
if (entry.isDirectory()) {
|
|
12
|
+
// Recursively search subdirectories
|
|
13
|
+
findPugFiles(fullPath, baseDir, files);
|
|
14
|
+
}
|
|
15
|
+
else if (entry.isFile() && entry.name.endsWith('.pug')) {
|
|
16
|
+
// Read the pug file content
|
|
17
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
18
|
+
// Use relative path from base directory as the key
|
|
19
|
+
// Normalize to POSIX-style path (forward slashes) for cross-platform consistency
|
|
20
|
+
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
21
|
+
files.set(relativePath, content);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return files;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Main function to compile pug templates to JSON
|
|
28
|
+
*/
|
|
29
|
+
function compilePugTemplates(inputDir = './templates', outputFile = 'compiled-templates.json') {
|
|
30
|
+
// Validate input directory exists
|
|
31
|
+
if (!fs.existsSync(inputDir)) {
|
|
32
|
+
console.error(`Error: Directory "${inputDir}" does not exist.`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (!fs.statSync(inputDir).isDirectory()) {
|
|
36
|
+
console.error(`Error: "${inputDir}" is not a directory.`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
console.log(`Searching for .pug files in: ${inputDir}`);
|
|
40
|
+
// Find all pug files
|
|
41
|
+
const pugFiles = findPugFiles(inputDir, inputDir);
|
|
42
|
+
console.log(`Found ${pugFiles.size} .pug file(s)`);
|
|
43
|
+
// Convert Map to plain object
|
|
44
|
+
const result = {};
|
|
45
|
+
for (const [filePath, content] of pugFiles.entries()) {
|
|
46
|
+
result[filePath] = content;
|
|
47
|
+
console.log(` - ${filePath}`);
|
|
48
|
+
}
|
|
49
|
+
// Write to output file
|
|
50
|
+
const outputContent = JSON.stringify(result, null, 2);
|
|
51
|
+
fs.writeFileSync(outputFile, outputContent, 'utf-8');
|
|
52
|
+
console.log(`\nSuccessfully compiled templates to: ${outputFile}`);
|
|
53
|
+
}
|
|
54
|
+
// Parse command line arguments
|
|
55
|
+
const args = process.argv.slice(2);
|
|
56
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
57
|
+
console.log('Usage: compile-pug-templates [input-directory] [output-file]');
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log('Arguments:');
|
|
60
|
+
console.log(' input-directory Directory containing .pug templates (default: ./templates)');
|
|
61
|
+
console.log(' output-file Output JSON file path (default: compiled-templates.json)');
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('Examples:');
|
|
64
|
+
console.log(' compile-pug-templates');
|
|
65
|
+
console.log(' compile-pug-templates ./templates');
|
|
66
|
+
console.log(' compile-pug-templates ./templates ./compiled-templates.json');
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
const [inputDir, outputFile] = args;
|
|
70
|
+
// Run the compilation
|
|
71
|
+
compilePugTemplates(inputDir, outputFile);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vintasend-medplum",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,26 +8,35 @@
|
|
|
8
8
|
"prepublishOnly": "npm run build",
|
|
9
9
|
"test": "jest",
|
|
10
10
|
"test:watch": "jest --watch",
|
|
11
|
-
"test:coverage": "jest --coverage"
|
|
11
|
+
"test:coverage": "jest --coverage",
|
|
12
|
+
"compile-templates": "ts-node src/scripts/compile-pug-templates.ts"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"src/scripts/compile-pug-templates.ts"
|
|
15
17
|
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"compile-pug-templates": "dist/scripts/compile-pug-templates.js"
|
|
20
|
+
},
|
|
16
21
|
"author": "Hugo Bessa",
|
|
17
22
|
"license": "MIT",
|
|
18
23
|
"peerDependencies": {
|
|
19
|
-
"@medplum/core": "^
|
|
20
|
-
"@medplum/fhirtypes": "^
|
|
24
|
+
"@medplum/core": "^5.0.10",
|
|
25
|
+
"@medplum/fhirtypes": "^5.0.10",
|
|
21
26
|
"pdfmake": "^0.2.23",
|
|
22
|
-
"
|
|
27
|
+
"pug": "^3.0.3",
|
|
28
|
+
"vintasend": "^0.4.3"
|
|
23
29
|
},
|
|
24
30
|
"devDependencies": {
|
|
25
|
-
"@medplum/definitions": "^
|
|
26
|
-
"@medplum/mock": "^
|
|
31
|
+
"@medplum/definitions": "^5.0.10",
|
|
32
|
+
"@medplum/mock": "^5.0.10",
|
|
27
33
|
"@types/jest": "^30.0.0",
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
28
35
|
"@types/pdfmake": "^0.2.12",
|
|
36
|
+
"@types/pug": "^2.0.10",
|
|
29
37
|
"jest": "^30.2.0",
|
|
30
38
|
"ts-jest": "^29.4.6",
|
|
39
|
+
"ts-node": "^10.9.2",
|
|
31
40
|
"typescript": "^5.9.3"
|
|
32
41
|
}
|
|
33
42
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
interface PugTemplatesMap {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Recursively finds all .pug files in a directory
|
|
11
|
+
*/
|
|
12
|
+
function findPugFiles(dir: string, baseDir: string, files: Map<string, string> = new Map()): Map<string, string> {
|
|
13
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
14
|
+
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(dir, entry.name);
|
|
17
|
+
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
// Recursively search subdirectories
|
|
20
|
+
findPugFiles(fullPath, baseDir, files);
|
|
21
|
+
} else if (entry.isFile() && entry.name.endsWith('.pug')) {
|
|
22
|
+
// Read the pug file content
|
|
23
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
24
|
+
// Use relative path from base directory as the key
|
|
25
|
+
// Normalize to POSIX-style path (forward slashes) for cross-platform consistency
|
|
26
|
+
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
27
|
+
files.set(relativePath, content);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return files;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Main function to compile pug templates to JSON
|
|
36
|
+
*/
|
|
37
|
+
function compilePugTemplates(inputDir: string = './templates', outputFile: string = 'compiled-templates.json'): void {
|
|
38
|
+
// Validate input directory exists
|
|
39
|
+
if (!fs.existsSync(inputDir)) {
|
|
40
|
+
console.error(`Error: Directory "${inputDir}" does not exist.`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!fs.statSync(inputDir).isDirectory()) {
|
|
45
|
+
console.error(`Error: "${inputDir}" is not a directory.`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`Searching for .pug files in: ${inputDir}`);
|
|
50
|
+
|
|
51
|
+
// Find all pug files
|
|
52
|
+
const pugFiles = findPugFiles(inputDir, inputDir);
|
|
53
|
+
|
|
54
|
+
console.log(`Found ${pugFiles.size} .pug file(s)`);
|
|
55
|
+
|
|
56
|
+
// Convert Map to plain object
|
|
57
|
+
const result: PugTemplatesMap = {};
|
|
58
|
+
for (const [filePath, content] of pugFiles.entries()) {
|
|
59
|
+
result[filePath] = content;
|
|
60
|
+
console.log(` - ${filePath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Write to output file
|
|
64
|
+
const outputContent = JSON.stringify(result, null, 2);
|
|
65
|
+
fs.writeFileSync(outputFile, outputContent, 'utf-8');
|
|
66
|
+
|
|
67
|
+
console.log(`\nSuccessfully compiled templates to: ${outputFile}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Parse command line arguments
|
|
71
|
+
const args = process.argv.slice(2);
|
|
72
|
+
|
|
73
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
74
|
+
console.log('Usage: compile-pug-templates [input-directory] [output-file]');
|
|
75
|
+
console.log('');
|
|
76
|
+
console.log('Arguments:');
|
|
77
|
+
console.log(' input-directory Directory containing .pug templates (default: ./templates)');
|
|
78
|
+
console.log(' output-file Output JSON file path (default: compiled-templates.json)');
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log('Examples:');
|
|
81
|
+
console.log(' compile-pug-templates');
|
|
82
|
+
console.log(' compile-pug-templates ./templates');
|
|
83
|
+
console.log(' compile-pug-templates ./templates ./compiled-templates.json');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const [inputDir, outputFile] = args;
|
|
88
|
+
|
|
89
|
+
// Run the compilation
|
|
90
|
+
compilePugTemplates(inputDir, outputFile);
|