docx-to-builder 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.
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/cli.js +38 -0
- package/docx-to-builder.py +1268 -0
- package/examples/sample-builder.js +943 -0
- package/examples/sample-template.docx +0 -0
- package/package.json +45 -0
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sample-Template Document Builder
|
|
3
|
+
* Auto-generated by docx-to-builder https://github.com/jermorrison22/docx-to-builder
|
|
4
|
+
* Source template: sample-template.docx
|
|
5
|
+
* Generated: 2026-03-28
|
|
6
|
+
*
|
|
7
|
+
* HOW TO USE:
|
|
8
|
+
* 1. Fill in the data object below (or pass your own)
|
|
9
|
+
* 2. Run: node sample-template-builder.js
|
|
10
|
+
* 3. Find the output at: output/sample-template-output.docx
|
|
11
|
+
*
|
|
12
|
+
* To regenerate this file from a new template:
|
|
13
|
+
* python3 docx-to-builder.py your-template.docx
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
Document, Packer, Paragraph, TextRun, ImageRun,
|
|
18
|
+
Table, TableRow, TableCell, WidthType, AlignmentType,
|
|
19
|
+
BorderStyle, Header, Footer, PageNumber,
|
|
20
|
+
TabStopType, TabStopPosition, convertInchesToTwip,
|
|
21
|
+
} from 'docx';
|
|
22
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
24
|
+
import { dirname, join } from 'path';
|
|
25
|
+
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
|
|
28
|
+
// ─── Brand colors extracted from template ────────────────────────────────────
|
|
29
|
+
const COLOR = {
|
|
30
|
+
blueFaint: 'EDF0F5',
|
|
31
|
+
blueLight: 'C0CADB',
|
|
32
|
+
grayDark: '444444',
|
|
33
|
+
navyDark: '2C4A6E',
|
|
34
|
+
rule: 'DDDDDD',
|
|
35
|
+
subtle: '8C8C8C',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const FONT = 'Calibri';
|
|
39
|
+
|
|
40
|
+
// ─── Header ───────────────────────────────────────────────────────────────────
|
|
41
|
+
function buildHeader(data) {
|
|
42
|
+
return new Header({
|
|
43
|
+
children: [
|
|
44
|
+
new Paragraph({
|
|
45
|
+
children: [
|
|
46
|
+
new TextRun({ text: 'Meridian Consulting — Confidential Proposal', font: FONT, size: 18, color: COLOR.subtle }),
|
|
47
|
+
new TextRun({ text: '\t', font: FONT, size: 18, color: COLOR.subtle }),
|
|
48
|
+
],
|
|
49
|
+
border: { bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.rule, space: 4 } },
|
|
50
|
+
tabStops: [{ type: TabStopType.RIGHT, position: TabStopPosition.MAX }],
|
|
51
|
+
}),
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Footer ───────────────────────────────────────────────────────────────────
|
|
57
|
+
function buildFooter() {
|
|
58
|
+
return new Footer({
|
|
59
|
+
children: [
|
|
60
|
+
new Paragraph({
|
|
61
|
+
children: [
|
|
62
|
+
new TextRun({ text: 'Meridian Consulting | meridianconsulting.example', font: FONT, size: 16, color: COLOR.subtle }),
|
|
63
|
+
new TextRun({ text: '\t', font: FONT, size: 16, color: COLOR.subtle }),
|
|
64
|
+
new TextRun({ children: [PageNumber.CURRENT], font: FONT, size: 16, color: COLOR.subtle }),
|
|
65
|
+
new TextRun({ text: ' of ', font: FONT, size: 16, color: COLOR.subtle }),
|
|
66
|
+
new TextRun({ children: [PageNumber.TOTAL_PAGES], font: FONT, size: 16, color: COLOR.subtle }),
|
|
67
|
+
],
|
|
68
|
+
border: { top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.rule, space: 4 } },
|
|
69
|
+
tabStops: [{ type: TabStopType.RIGHT, position: TabStopPosition.MAX }],
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Document content ────────────────────────────────────────────────────────
|
|
76
|
+
// Bracketed placeholders (e.g. [Client Name]) have been mapped to data.fieldName.
|
|
77
|
+
// Static template text is kept as-is. Replace any literal string with a data
|
|
78
|
+
// field if you need it to be dynamic.
|
|
79
|
+
|
|
80
|
+
function buildSection1(data) {
|
|
81
|
+
const elements = [];
|
|
82
|
+
|
|
83
|
+
// 'MERIDIAN CONSULTING'
|
|
84
|
+
elements.push(
|
|
85
|
+
new Paragraph({
|
|
86
|
+
children: [
|
|
87
|
+
new TextRun({ text: 'MERIDIAN CONSULTING', font: FONT, size: 28, bold: true, color: COLOR.navyDark }),
|
|
88
|
+
],
|
|
89
|
+
alignment: AlignmentType.CENTER,
|
|
90
|
+
spacing: { before: 720, after: 0 },
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
elements.push(
|
|
95
|
+
new Paragraph({
|
|
96
|
+
children: [],
|
|
97
|
+
spacing: { before: 0, after: 320 },
|
|
98
|
+
border: { bottom: { style: BorderStyle.SINGLE, size: 12, color: COLOR.navyDark, space: 4 } },
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// 'PROPOSAL'
|
|
103
|
+
elements.push(
|
|
104
|
+
new Paragraph({
|
|
105
|
+
children: [
|
|
106
|
+
new TextRun({ text: 'PROPOSAL', font: FONT, size: 24, allCaps: true, color: COLOR.navyDark }),
|
|
107
|
+
],
|
|
108
|
+
alignment: AlignmentType.CENTER,
|
|
109
|
+
spacing: { before: 0, after: 120 },
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// '[Proposal Title]'
|
|
114
|
+
elements.push(
|
|
115
|
+
new Paragraph({
|
|
116
|
+
children: [
|
|
117
|
+
new TextRun({ text: data.title, font: FONT, size: 52, bold: true, color: COLOR.navyDark }),
|
|
118
|
+
],
|
|
119
|
+
alignment: AlignmentType.CENTER,
|
|
120
|
+
spacing: { before: 0, after: 480 },
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
elements.push(
|
|
125
|
+
new Paragraph({
|
|
126
|
+
children: [],
|
|
127
|
+
spacing: { before: 0, after: 480 },
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Table (4 rows x 2 cols)
|
|
132
|
+
elements.push(
|
|
133
|
+
new Table({
|
|
134
|
+
alignment: AlignmentType.CENTER,
|
|
135
|
+
rows: [
|
|
136
|
+
new TableRow({
|
|
137
|
+
children: [
|
|
138
|
+
new TableCell({
|
|
139
|
+
width: { size: 50, type: WidthType.PERCENTAGE },
|
|
140
|
+
shading: { fill: COLOR.blueFaint },
|
|
141
|
+
borders: {
|
|
142
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
143
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
144
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
145
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
146
|
+
},
|
|
147
|
+
margins: {
|
|
148
|
+
top: 60,
|
|
149
|
+
bottom: 60,
|
|
150
|
+
left: 120,
|
|
151
|
+
right: 120,
|
|
152
|
+
},
|
|
153
|
+
children: [
|
|
154
|
+
new Paragraph({
|
|
155
|
+
children: [
|
|
156
|
+
new TextRun({ text: 'Prepared for', font: FONT, size: 20, bold: true, color: COLOR.navyDark }),
|
|
157
|
+
],
|
|
158
|
+
}),
|
|
159
|
+
],
|
|
160
|
+
}),
|
|
161
|
+
new TableCell({
|
|
162
|
+
width: { size: 50, type: WidthType.PERCENTAGE },
|
|
163
|
+
borders: {
|
|
164
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
165
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
166
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
167
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
168
|
+
},
|
|
169
|
+
margins: {
|
|
170
|
+
top: 60,
|
|
171
|
+
bottom: 60,
|
|
172
|
+
left: 120,
|
|
173
|
+
right: 120,
|
|
174
|
+
},
|
|
175
|
+
children: [
|
|
176
|
+
new Paragraph({
|
|
177
|
+
children: [
|
|
178
|
+
new TextRun({ text: data.clientName, font: FONT, size: 20, color: COLOR.grayDark }),
|
|
179
|
+
],
|
|
180
|
+
}),
|
|
181
|
+
],
|
|
182
|
+
}),
|
|
183
|
+
],
|
|
184
|
+
}),
|
|
185
|
+
new TableRow({
|
|
186
|
+
children: [
|
|
187
|
+
new TableCell({
|
|
188
|
+
width: { size: 50, type: WidthType.PERCENTAGE },
|
|
189
|
+
shading: { fill: COLOR.blueFaint },
|
|
190
|
+
borders: {
|
|
191
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
192
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
193
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
194
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
195
|
+
},
|
|
196
|
+
margins: {
|
|
197
|
+
top: 60,
|
|
198
|
+
bottom: 60,
|
|
199
|
+
left: 120,
|
|
200
|
+
right: 120,
|
|
201
|
+
},
|
|
202
|
+
children: [
|
|
203
|
+
new Paragraph({
|
|
204
|
+
children: [
|
|
205
|
+
new TextRun({ text: 'Date', font: FONT, size: 20, bold: true, color: COLOR.navyDark }),
|
|
206
|
+
],
|
|
207
|
+
}),
|
|
208
|
+
],
|
|
209
|
+
}),
|
|
210
|
+
new TableCell({
|
|
211
|
+
width: { size: 50, type: WidthType.PERCENTAGE },
|
|
212
|
+
borders: {
|
|
213
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
214
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
215
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
216
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
217
|
+
},
|
|
218
|
+
margins: {
|
|
219
|
+
top: 60,
|
|
220
|
+
bottom: 60,
|
|
221
|
+
left: 120,
|
|
222
|
+
right: 120,
|
|
223
|
+
},
|
|
224
|
+
children: [
|
|
225
|
+
new Paragraph({
|
|
226
|
+
children: [
|
|
227
|
+
new TextRun({ text: data.date, font: FONT, size: 20, color: COLOR.grayDark }),
|
|
228
|
+
],
|
|
229
|
+
}),
|
|
230
|
+
],
|
|
231
|
+
}),
|
|
232
|
+
],
|
|
233
|
+
}),
|
|
234
|
+
new TableRow({
|
|
235
|
+
children: [
|
|
236
|
+
new TableCell({
|
|
237
|
+
width: { size: 50, type: WidthType.PERCENTAGE },
|
|
238
|
+
shading: { fill: COLOR.blueFaint },
|
|
239
|
+
borders: {
|
|
240
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
241
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
242
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
243
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
244
|
+
},
|
|
245
|
+
margins: {
|
|
246
|
+
top: 60,
|
|
247
|
+
bottom: 60,
|
|
248
|
+
left: 120,
|
|
249
|
+
right: 120,
|
|
250
|
+
},
|
|
251
|
+
children: [
|
|
252
|
+
new Paragraph({
|
|
253
|
+
children: [
|
|
254
|
+
new TextRun({ text: 'Prepared by', font: FONT, size: 20, bold: true, color: COLOR.navyDark }),
|
|
255
|
+
],
|
|
256
|
+
}),
|
|
257
|
+
],
|
|
258
|
+
}),
|
|
259
|
+
new TableCell({
|
|
260
|
+
width: { size: 50, type: WidthType.PERCENTAGE },
|
|
261
|
+
borders: {
|
|
262
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
263
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
264
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
265
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
266
|
+
},
|
|
267
|
+
margins: {
|
|
268
|
+
top: 60,
|
|
269
|
+
bottom: 60,
|
|
270
|
+
left: 120,
|
|
271
|
+
right: 120,
|
|
272
|
+
},
|
|
273
|
+
children: [
|
|
274
|
+
new Paragraph({
|
|
275
|
+
children: [
|
|
276
|
+
new TextRun({ text: data.yourName, font: FONT, size: 20, color: COLOR.grayDark }),
|
|
277
|
+
],
|
|
278
|
+
}),
|
|
279
|
+
],
|
|
280
|
+
}),
|
|
281
|
+
],
|
|
282
|
+
}),
|
|
283
|
+
new TableRow({
|
|
284
|
+
children: [
|
|
285
|
+
new TableCell({
|
|
286
|
+
width: { size: 50, type: WidthType.PERCENTAGE },
|
|
287
|
+
shading: { fill: COLOR.blueFaint },
|
|
288
|
+
borders: {
|
|
289
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
290
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
291
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
292
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
293
|
+
},
|
|
294
|
+
margins: {
|
|
295
|
+
top: 60,
|
|
296
|
+
bottom: 60,
|
|
297
|
+
left: 120,
|
|
298
|
+
right: 120,
|
|
299
|
+
},
|
|
300
|
+
children: [
|
|
301
|
+
new Paragraph({
|
|
302
|
+
children: [
|
|
303
|
+
new TextRun({ text: 'Status', font: FONT, size: 20, bold: true, color: COLOR.navyDark }),
|
|
304
|
+
],
|
|
305
|
+
}),
|
|
306
|
+
],
|
|
307
|
+
}),
|
|
308
|
+
new TableCell({
|
|
309
|
+
width: { size: 50, type: WidthType.PERCENTAGE },
|
|
310
|
+
borders: {
|
|
311
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
312
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
313
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
314
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: COLOR.blueLight },
|
|
315
|
+
},
|
|
316
|
+
margins: {
|
|
317
|
+
top: 60,
|
|
318
|
+
bottom: 60,
|
|
319
|
+
left: 120,
|
|
320
|
+
right: 120,
|
|
321
|
+
},
|
|
322
|
+
children: [
|
|
323
|
+
new Paragraph({
|
|
324
|
+
children: [
|
|
325
|
+
new TextRun({ text: data.status, font: FONT, size: 20, color: COLOR.grayDark }),
|
|
326
|
+
],
|
|
327
|
+
}),
|
|
328
|
+
],
|
|
329
|
+
}),
|
|
330
|
+
],
|
|
331
|
+
}),
|
|
332
|
+
],
|
|
333
|
+
}),
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
elements.push(
|
|
337
|
+
new Paragraph({
|
|
338
|
+
children: [],
|
|
339
|
+
spacing: { before: 0, after: 320 },
|
|
340
|
+
}),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// 'CONFIDENTIAL — This document contains proprietary informatio'
|
|
344
|
+
elements.push(
|
|
345
|
+
new Paragraph({
|
|
346
|
+
children: [
|
|
347
|
+
new TextRun({ text: 'CONFIDENTIAL — This document contains proprietary information of Meridian Consulting.', font: FONT, size: 18, italics: true, color: COLOR.subtle }),
|
|
348
|
+
],
|
|
349
|
+
alignment: AlignmentType.CENTER,
|
|
350
|
+
spacing: { before: 0, after: 120 },
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
elements.push(new Paragraph({ pageBreakBefore: true }));
|
|
355
|
+
|
|
356
|
+
// '1. Executive Summary'
|
|
357
|
+
elements.push(
|
|
358
|
+
new Paragraph({
|
|
359
|
+
children: [
|
|
360
|
+
new TextRun({ text: '1. Executive Summary', font: FONT, size: 28, bold: true, allCaps: true, color: COLOR.navyDark }),
|
|
361
|
+
],
|
|
362
|
+
spacing: { before: 280, after: 0 },
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
elements.push(
|
|
367
|
+
new Paragraph({
|
|
368
|
+
children: [],
|
|
369
|
+
spacing: { before: 0, after: 100 },
|
|
370
|
+
border: { bottom: { style: BorderStyle.SINGLE, size: 12, color: COLOR.navyDark, space: 4 } },
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// 'Meridian Consulting is pleased to present this proposal to ['
|
|
375
|
+
elements.push(
|
|
376
|
+
new Paragraph({
|
|
377
|
+
children: [
|
|
378
|
+
new TextRun({ text: 'Meridian Consulting is pleased to present this proposal to ', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
379
|
+
new TextRun({ text: data.clientName, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
380
|
+
new TextRun({ text: ' for ', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
381
|
+
new TextRun({ text: data.serviceDescription, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
382
|
+
new TextRun({ text: '. This document outlines our proposed solution, scope of work, and commercial terms.', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
383
|
+
],
|
|
384
|
+
spacing: { before: 0, after: 120 },
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// '[Replace this paragraph with a 2–3 sentence summary of the c'
|
|
389
|
+
elements.push(
|
|
390
|
+
new Paragraph({
|
|
391
|
+
children: [
|
|
392
|
+
new TextRun({ text: '[Replace this paragraph with a 2–3 sentence summary of the client’s need and how Meridian Consulting addresses it.]', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
393
|
+
],
|
|
394
|
+
spacing: { before: 0, after: 120 },
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
elements.push(
|
|
399
|
+
new Paragraph({
|
|
400
|
+
children: [],
|
|
401
|
+
spacing: { before: 0, after: 160 },
|
|
402
|
+
}),
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
// '2. Proposed Services'
|
|
406
|
+
elements.push(
|
|
407
|
+
new Paragraph({
|
|
408
|
+
children: [
|
|
409
|
+
new TextRun({ text: '2. Proposed Services', font: FONT, size: 28, bold: true, allCaps: true, color: COLOR.navyDark }),
|
|
410
|
+
],
|
|
411
|
+
spacing: { before: 280, after: 0 },
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
elements.push(
|
|
416
|
+
new Paragraph({
|
|
417
|
+
children: [],
|
|
418
|
+
spacing: { before: 0, after: 100 },
|
|
419
|
+
border: { bottom: { style: BorderStyle.SINGLE, size: 12, color: COLOR.navyDark, space: 4 } },
|
|
420
|
+
}),
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// 'Overview'
|
|
424
|
+
elements.push(
|
|
425
|
+
new Paragraph({
|
|
426
|
+
children: [
|
|
427
|
+
new TextRun({ text: 'Overview', font: FONT, size: 24, bold: true, color: COLOR.grayDark }),
|
|
428
|
+
],
|
|
429
|
+
spacing: { before: 160, after: 60 },
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
// 'Meridian Consulting proposes the following services for [Cli'
|
|
434
|
+
elements.push(
|
|
435
|
+
new Paragraph({
|
|
436
|
+
children: [
|
|
437
|
+
new TextRun({ text: 'Meridian Consulting proposes the following services for ', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
438
|
+
new TextRun({ text: data.clientName, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
439
|
+
new TextRun({ text: '. ', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
440
|
+
new TextRun({ text: '[Replace with 1–2 sentences describing the service package.]', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
441
|
+
],
|
|
442
|
+
spacing: { before: 0, after: 120 },
|
|
443
|
+
}),
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
// 'Service Components'
|
|
447
|
+
elements.push(
|
|
448
|
+
new Paragraph({
|
|
449
|
+
children: [
|
|
450
|
+
new TextRun({ text: 'Service Components', font: FONT, size: 24, bold: true, color: COLOR.grayDark }),
|
|
451
|
+
],
|
|
452
|
+
spacing: { before: 160, after: 60 },
|
|
453
|
+
}),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
// '[Service / Component 1] — [Brief description]'
|
|
457
|
+
elements.push(
|
|
458
|
+
new Paragraph({
|
|
459
|
+
children: [
|
|
460
|
+
new TextRun({ text: data.serviceComponent1, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
461
|
+
new TextRun({ text: ' — ', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
462
|
+
new TextRun({ text: data.description, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
463
|
+
],
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
// '[Service / Component 2] — [Brief description]'
|
|
468
|
+
elements.push(
|
|
469
|
+
new Paragraph({
|
|
470
|
+
children: [
|
|
471
|
+
new TextRun({ text: data.serviceComponent2, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
472
|
+
new TextRun({ text: ' — ', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
473
|
+
new TextRun({ text: data.description, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
474
|
+
],
|
|
475
|
+
}),
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
// '[Service / Component 3] — [Brief description]'
|
|
479
|
+
elements.push(
|
|
480
|
+
new Paragraph({
|
|
481
|
+
children: [
|
|
482
|
+
new TextRun({ text: data.serviceComponent3, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
483
|
+
new TextRun({ text: ' — ', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
484
|
+
new TextRun({ text: data.description, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
485
|
+
],
|
|
486
|
+
}),
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
// 'Key Deliverables'
|
|
490
|
+
elements.push(
|
|
491
|
+
new Paragraph({
|
|
492
|
+
children: [
|
|
493
|
+
new TextRun({ text: 'Key Deliverables', font: FONT, size: 24, bold: true, color: COLOR.grayDark }),
|
|
494
|
+
],
|
|
495
|
+
spacing: { before: 160, after: 60 },
|
|
496
|
+
}),
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
// '[Describe the primary deliverables, e.g., go-live date, comp'
|
|
500
|
+
elements.push(
|
|
501
|
+
new Paragraph({
|
|
502
|
+
children: [
|
|
503
|
+
new TextRun({ text: '[Describe the primary deliverables, e.g., go-live date, completed reports, platform access.]', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
504
|
+
],
|
|
505
|
+
spacing: { before: 0, after: 120 },
|
|
506
|
+
}),
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
elements.push(
|
|
510
|
+
new Paragraph({
|
|
511
|
+
children: [],
|
|
512
|
+
spacing: { before: 0, after: 160 },
|
|
513
|
+
}),
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
// '3. Commercial Terms'
|
|
517
|
+
elements.push(
|
|
518
|
+
new Paragraph({
|
|
519
|
+
children: [
|
|
520
|
+
new TextRun({ text: '3. Commercial Terms', font: FONT, size: 28, bold: true, allCaps: true, color: COLOR.navyDark }),
|
|
521
|
+
],
|
|
522
|
+
spacing: { before: 280, after: 0 },
|
|
523
|
+
}),
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
elements.push(
|
|
527
|
+
new Paragraph({
|
|
528
|
+
children: [],
|
|
529
|
+
spacing: { before: 0, after: 100 },
|
|
530
|
+
border: { bottom: { style: BorderStyle.SINGLE, size: 12, color: COLOR.navyDark, space: 4 } },
|
|
531
|
+
}),
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
// 'Pricing'
|
|
535
|
+
elements.push(
|
|
536
|
+
new Paragraph({
|
|
537
|
+
children: [
|
|
538
|
+
new TextRun({ text: 'Pricing', font: FONT, size: 24, bold: true, color: COLOR.grayDark }),
|
|
539
|
+
],
|
|
540
|
+
spacing: { before: 160, after: 60 },
|
|
541
|
+
}),
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
// 'The following pricing reflects the proposed commercial struc'
|
|
545
|
+
elements.push(
|
|
546
|
+
new Paragraph({
|
|
547
|
+
children: [
|
|
548
|
+
new TextRun({ text: 'The following pricing reflects the proposed commercial structure for the services outlined above.', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
549
|
+
],
|
|
550
|
+
spacing: { before: 0, after: 120 },
|
|
551
|
+
}),
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
elements.push(
|
|
555
|
+
new Paragraph({
|
|
556
|
+
children: [],
|
|
557
|
+
spacing: { before: 0, after: 80 },
|
|
558
|
+
}),
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
// Table (4 rows x 3 cols)
|
|
562
|
+
elements.push(
|
|
563
|
+
new Table({
|
|
564
|
+
rows: [
|
|
565
|
+
new TableRow({
|
|
566
|
+
children: [
|
|
567
|
+
new TableCell({
|
|
568
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
569
|
+
borders: {
|
|
570
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
571
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
572
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
573
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
574
|
+
},
|
|
575
|
+
children: [
|
|
576
|
+
new Paragraph({
|
|
577
|
+
children: [
|
|
578
|
+
new TextRun({ text: 'Service / Description', font: FONT, size: 20, bold: true }),
|
|
579
|
+
],
|
|
580
|
+
}),
|
|
581
|
+
],
|
|
582
|
+
}),
|
|
583
|
+
new TableCell({
|
|
584
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
585
|
+
borders: {
|
|
586
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
587
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
588
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
589
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
590
|
+
},
|
|
591
|
+
children: [
|
|
592
|
+
new Paragraph({
|
|
593
|
+
children: [
|
|
594
|
+
new TextRun({ text: 'Amount', font: FONT, size: 20, bold: true }),
|
|
595
|
+
],
|
|
596
|
+
}),
|
|
597
|
+
],
|
|
598
|
+
}),
|
|
599
|
+
new TableCell({
|
|
600
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
601
|
+
borders: {
|
|
602
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
603
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
604
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
605
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
606
|
+
},
|
|
607
|
+
children: [
|
|
608
|
+
new Paragraph({
|
|
609
|
+
children: [
|
|
610
|
+
new TextRun({ text: 'Type', font: FONT, size: 20, bold: true }),
|
|
611
|
+
],
|
|
612
|
+
}),
|
|
613
|
+
],
|
|
614
|
+
}),
|
|
615
|
+
],
|
|
616
|
+
}),
|
|
617
|
+
new TableRow({
|
|
618
|
+
children: [
|
|
619
|
+
new TableCell({
|
|
620
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
621
|
+
borders: {
|
|
622
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
623
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
624
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
625
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
626
|
+
},
|
|
627
|
+
children: [
|
|
628
|
+
new Paragraph({
|
|
629
|
+
children: [
|
|
630
|
+
new TextRun({ text: data.serviceFee, font: FONT, size: 20 }),
|
|
631
|
+
],
|
|
632
|
+
}),
|
|
633
|
+
],
|
|
634
|
+
}),
|
|
635
|
+
new TableCell({
|
|
636
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
637
|
+
borders: {
|
|
638
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
639
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
640
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
641
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
642
|
+
},
|
|
643
|
+
children: [
|
|
644
|
+
new Paragraph({
|
|
645
|
+
children: [
|
|
646
|
+
new TextRun({ text: '$', font: FONT, size: 20 }),
|
|
647
|
+
new TextRun({ text: data.xxxx, font: FONT, size: 20 }),
|
|
648
|
+
],
|
|
649
|
+
}),
|
|
650
|
+
],
|
|
651
|
+
}),
|
|
652
|
+
new TableCell({
|
|
653
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
654
|
+
borders: {
|
|
655
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
656
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
657
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
658
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
659
|
+
},
|
|
660
|
+
children: [
|
|
661
|
+
new Paragraph({
|
|
662
|
+
children: [
|
|
663
|
+
new TextRun({ text: 'Monthly (MRR)', font: FONT, size: 20 }),
|
|
664
|
+
],
|
|
665
|
+
}),
|
|
666
|
+
],
|
|
667
|
+
}),
|
|
668
|
+
],
|
|
669
|
+
}),
|
|
670
|
+
new TableRow({
|
|
671
|
+
children: [
|
|
672
|
+
new TableCell({
|
|
673
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
674
|
+
borders: {
|
|
675
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
676
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
677
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
678
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
679
|
+
},
|
|
680
|
+
children: [
|
|
681
|
+
new Paragraph({
|
|
682
|
+
children: [
|
|
683
|
+
new TextRun({ text: data.setupFee, font: FONT, size: 20 }),
|
|
684
|
+
],
|
|
685
|
+
}),
|
|
686
|
+
],
|
|
687
|
+
}),
|
|
688
|
+
new TableCell({
|
|
689
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
690
|
+
borders: {
|
|
691
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
692
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
693
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
694
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
695
|
+
},
|
|
696
|
+
children: [
|
|
697
|
+
new Paragraph({
|
|
698
|
+
children: [
|
|
699
|
+
new TextRun({ text: '$', font: FONT, size: 20 }),
|
|
700
|
+
new TextRun({ text: data.xxxx, font: FONT, size: 20 }),
|
|
701
|
+
],
|
|
702
|
+
}),
|
|
703
|
+
],
|
|
704
|
+
}),
|
|
705
|
+
new TableCell({
|
|
706
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
707
|
+
borders: {
|
|
708
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
709
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
710
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
711
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
712
|
+
},
|
|
713
|
+
children: [
|
|
714
|
+
new Paragraph({
|
|
715
|
+
children: [
|
|
716
|
+
new TextRun({ text: 'One-Time (NRC)', font: FONT, size: 20 }),
|
|
717
|
+
],
|
|
718
|
+
}),
|
|
719
|
+
],
|
|
720
|
+
}),
|
|
721
|
+
],
|
|
722
|
+
}),
|
|
723
|
+
new TableRow({
|
|
724
|
+
children: [
|
|
725
|
+
new TableCell({
|
|
726
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
727
|
+
borders: {
|
|
728
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
729
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
730
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
731
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
732
|
+
},
|
|
733
|
+
children: [
|
|
734
|
+
new Paragraph({
|
|
735
|
+
children: [
|
|
736
|
+
new TextRun({ text: 'Estimated Monthly Total', font: FONT, size: 20, bold: true }),
|
|
737
|
+
],
|
|
738
|
+
}),
|
|
739
|
+
],
|
|
740
|
+
}),
|
|
741
|
+
new TableCell({
|
|
742
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
743
|
+
borders: {
|
|
744
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
745
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
746
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
747
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
748
|
+
},
|
|
749
|
+
children: [
|
|
750
|
+
new Paragraph({
|
|
751
|
+
children: [
|
|
752
|
+
new TextRun({ text: '$', font: FONT, size: 20, bold: true }),
|
|
753
|
+
new TextRun({ text: data.xxxx, font: FONT, size: 20, bold: true }),
|
|
754
|
+
new TextRun({ text: '/mo', font: FONT, size: 20, bold: true }),
|
|
755
|
+
],
|
|
756
|
+
}),
|
|
757
|
+
],
|
|
758
|
+
}),
|
|
759
|
+
new TableCell({
|
|
760
|
+
width: { size: 33, type: WidthType.PERCENTAGE },
|
|
761
|
+
borders: {
|
|
762
|
+
top: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
763
|
+
bottom: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
764
|
+
left: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
765
|
+
right: { style: BorderStyle.SINGLE, size: 4, color: 'DDDDDD' },
|
|
766
|
+
},
|
|
767
|
+
children: [
|
|
768
|
+
new Paragraph({
|
|
769
|
+
children: [],
|
|
770
|
+
}),
|
|
771
|
+
],
|
|
772
|
+
}),
|
|
773
|
+
],
|
|
774
|
+
}),
|
|
775
|
+
],
|
|
776
|
+
}),
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
elements.push(
|
|
780
|
+
new Paragraph({
|
|
781
|
+
children: [],
|
|
782
|
+
spacing: { before: 0, after: 160 },
|
|
783
|
+
}),
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
// '4. Next Steps'
|
|
787
|
+
elements.push(
|
|
788
|
+
new Paragraph({
|
|
789
|
+
children: [
|
|
790
|
+
new TextRun({ text: '4. Next Steps', font: FONT, size: 28, bold: true, allCaps: true, color: COLOR.navyDark }),
|
|
791
|
+
],
|
|
792
|
+
spacing: { before: 280, after: 0 },
|
|
793
|
+
}),
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
elements.push(
|
|
797
|
+
new Paragraph({
|
|
798
|
+
children: [],
|
|
799
|
+
spacing: { before: 0, after: 100 },
|
|
800
|
+
border: { bottom: { style: BorderStyle.SINGLE, size: 12, color: COLOR.navyDark, space: 4 } },
|
|
801
|
+
}),
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
// 'We welcome the opportunity to discuss this proposal further.'
|
|
805
|
+
elements.push(
|
|
806
|
+
new Paragraph({
|
|
807
|
+
children: [
|
|
808
|
+
new TextRun({ text: 'We welcome the opportunity to discuss this proposal further. ', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
809
|
+
new TextRun({ text: '[Add 1–2 sentences about desired next steps.]', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
810
|
+
],
|
|
811
|
+
spacing: { before: 0, after: 120 },
|
|
812
|
+
}),
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
// 'To proceed, please contact:'
|
|
816
|
+
elements.push(
|
|
817
|
+
new Paragraph({
|
|
818
|
+
children: [
|
|
819
|
+
new TextRun({ text: 'To proceed, please contact:', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
820
|
+
],
|
|
821
|
+
spacing: { before: 0, after: 120 },
|
|
822
|
+
}),
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
elements.push(
|
|
826
|
+
new Paragraph({
|
|
827
|
+
children: [],
|
|
828
|
+
spacing: { before: 0, after: 80 },
|
|
829
|
+
}),
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
// '[Your Name]'
|
|
833
|
+
elements.push(
|
|
834
|
+
new Paragraph({
|
|
835
|
+
children: [
|
|
836
|
+
new TextRun({ text: data.yourName, font: FONT, size: 22, bold: true, color: COLOR.grayDark }),
|
|
837
|
+
],
|
|
838
|
+
spacing: { before: 0, after: 120 },
|
|
839
|
+
}),
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
// '[Title], Meridian Consulting'
|
|
843
|
+
elements.push(
|
|
844
|
+
new Paragraph({
|
|
845
|
+
children: [
|
|
846
|
+
new TextRun({ text: data.title, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
847
|
+
new TextRun({ text: ', Meridian Consulting', font: FONT, size: 22, color: COLOR.grayDark }),
|
|
848
|
+
],
|
|
849
|
+
spacing: { before: 0, after: 120 },
|
|
850
|
+
}),
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
// '[email@meridianconsulting.example]'
|
|
854
|
+
elements.push(
|
|
855
|
+
new Paragraph({
|
|
856
|
+
children: [
|
|
857
|
+
new TextRun({ text: data.emailmeridianconsultingexample, font: FONT, size: 22, color: COLOR.grayDark }),
|
|
858
|
+
],
|
|
859
|
+
spacing: { before: 0, after: 120 },
|
|
860
|
+
}),
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
return elements;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function buildSection2(data) { // margins: T=1440 B=1440 L=1440 R=1440 (twips)
|
|
867
|
+
const elements = [];
|
|
868
|
+
|
|
869
|
+
return elements;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// ─── Main builder ────────────────────────────────────────────────────────────
|
|
873
|
+
export async function buildDocument(data, outputPath) {
|
|
874
|
+
const doc = new Document({
|
|
875
|
+
sections: [
|
|
876
|
+
{
|
|
877
|
+
properties: {
|
|
878
|
+
page: {
|
|
879
|
+
margin: {
|
|
880
|
+
top: convertInchesToTwip(1.000),
|
|
881
|
+
bottom: convertInchesToTwip(1.000),
|
|
882
|
+
left: convertInchesToTwip(1.000),
|
|
883
|
+
right: convertInchesToTwip(1.000),
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
headers: { default: buildHeader(data) },
|
|
888
|
+
footers: { default: buildFooter() },
|
|
889
|
+
children: buildSection1(data),
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
properties: {
|
|
893
|
+
page: {
|
|
894
|
+
margin: {
|
|
895
|
+
top: convertInchesToTwip(1.000),
|
|
896
|
+
bottom: convertInchesToTwip(1.000),
|
|
897
|
+
left: convertInchesToTwip(1.000),
|
|
898
|
+
right: convertInchesToTwip(1.000),
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
headers: { default: buildHeader(data) },
|
|
903
|
+
footers: { default: buildFooter() },
|
|
904
|
+
children: buildSection2(data),
|
|
905
|
+
},
|
|
906
|
+
],
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
const buffer = await Packer.toBuffer(doc);
|
|
910
|
+
writeFileSync(outputPath, buffer);
|
|
911
|
+
console.log('Document saved to: ' + outputPath);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// ─── Data object ─────────────────────────────────────────────────────────────
|
|
915
|
+
// Fields below were inferred from bracketed placeholders in your template.
|
|
916
|
+
// Add, remove, or rename fields to match your use case.
|
|
917
|
+
const data = {
|
|
918
|
+
title: 'Proposal Title',
|
|
919
|
+
clientName: 'Client Name',
|
|
920
|
+
date: 'Month Day, Year',
|
|
921
|
+
yourName: 'Your Name',
|
|
922
|
+
status: 'Draft / Final',
|
|
923
|
+
serviceDescription: 'brief description of services',
|
|
924
|
+
serviceComponent1: 'Service / Component 1',
|
|
925
|
+
description: 'Brief description',
|
|
926
|
+
serviceComponent2: 'Service / Component 2',
|
|
927
|
+
serviceComponent3: 'Service / Component 3',
|
|
928
|
+
serviceFee: 'Service Fee',
|
|
929
|
+
xxxx: 'X,XXX',
|
|
930
|
+
setupFee: 'Setup Fee',
|
|
931
|
+
emailmeridianconsultingexample: 'email@meridianconsulting.example',
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// ─── Run from command line ────────────────────────────────────────────────────
|
|
935
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
936
|
+
const outputArg = process.argv.indexOf('--output');
|
|
937
|
+
const outputPath = outputArg !== -1
|
|
938
|
+
? process.argv[outputArg + 1]
|
|
939
|
+
: join(__dirname, 'output/sample-template-output.docx');
|
|
940
|
+
|
|
941
|
+
mkdirSync(join(__dirname, 'output'), { recursive: true });
|
|
942
|
+
buildDocument(data, outputPath).catch(console.error);
|
|
943
|
+
}
|