@cenk1cenk2/md-printer 3.2.24 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +213 -57
  2. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.mjs +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorateMetadata.mjs +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorateParam.mjs +1 -1
  5. package/dist/commands/index.mjs +3 -1
  6. package/dist/components/Localized.svelte +18 -0
  7. package/dist/components/SignatureBlock.svelte +8 -46
  8. package/dist/components/Translate.svelte +15 -0
  9. package/dist/components/extension.ts +8 -0
  10. package/dist/components/index.ts +3 -0
  11. package/dist/constants/attachment.constants.mjs +7 -0
  12. package/dist/constants/index.mjs +3 -2
  13. package/dist/constants/template.constants.mjs +26 -2
  14. package/dist/interfaces/attachment.interface.mjs +1 -0
  15. package/dist/modules/md-printer/md-printer.handler.mjs +4 -2
  16. package/dist/modules/md-printer/md-printer.module.mjs +1 -1
  17. package/dist/modules/md-printer/md-printer.schema.mjs +12 -1
  18. package/dist/services/i18n.service.mjs +6 -4
  19. package/dist/services/index.mjs +2 -1
  20. package/dist/services/markdown.service.mjs +1 -1
  21. package/dist/services/pdf/pdf.service.mjs +124 -0
  22. package/dist/services/printer.module.mjs +3 -1
  23. package/dist/services/printer.service.mjs +55 -11
  24. package/dist/services/renderer/browser.service.mjs +3 -3
  25. package/dist/services/renderer/renderer.service.mjs +3 -3
  26. package/dist/services/session/preview.service.mjs +2 -2
  27. package/dist/services/session/session.service.mjs +11 -11
  28. package/dist/services/signature/adapters/a-trust.adapter.mjs +2 -2
  29. package/dist/services/signature/signature.service.mjs +2 -2
  30. package/dist/services/template/assets.mjs +1 -1
  31. package/dist/services/template/render.entry.ts +6 -2
  32. package/dist/services/template/svelte.plugin.mjs +12 -7
  33. package/dist/services/template/template.schema.mjs +27 -0
  34. package/dist/services/template/template.service.mjs +123 -26
  35. package/oclif.manifest.json +16 -2
  36. package/package.json +14 -24
  37. package/templates/default/Template.svelte +0 -83
  38. package/templates/default/settings.json +0 -7
  39. package/templates/invoice/Template.svelte +0 -247
  40. package/templates/invoice/components/Client.svelte +0 -42
  41. package/templates/invoice/components/Company.svelte +0 -41
  42. package/templates/invoice/components/Items.svelte +0 -66
  43. package/templates/invoice/components/Notes.svelte +0 -21
  44. package/templates/invoice/components/Payment.svelte +0 -25
  45. package/templates/invoice/components/Signature.svelte +0 -34
  46. package/templates/invoice/components/Totals.svelte +0 -47
  47. package/templates/invoice/i18n/de.yaml +0 -30
  48. package/templates/invoice/i18n/en.yaml +0 -30
  49. package/templates/invoice/round.js +0 -2
  50. package/templates/invoice/settings.json +0 -11
  51. package/templates/kilic.dev/Template.svelte +0 -102
  52. package/templates/kilic.dev/settings.json +0 -7
  53. package/templates/mail/Template.svelte +0 -166
  54. package/templates/mail/settings.json +0 -7
@@ -1,10 +1,10 @@
1
1
  import "../../services/index.mjs";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
3
3
  import { BrowserService } from "../../services/renderer/browser.service.mjs";
4
4
  import { PrinterService } from "../../services/printer.service.mjs";
5
5
  import { PreviewService } from "../../services/session/preview.service.mjs";
6
6
  import { SessionService } from "../../services/session/session.service.mjs";
7
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
7
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
8
8
  import { Injectable } from "@nestjs/common";
9
9
  import { mkdir } from "fs/promises";
10
10
  import { dirname } from "path";
@@ -34,6 +34,8 @@ let MdPrinterHandler = class MdPrinterHandler {
34
34
  outputFiletype: flags["output-filetype"],
35
35
  stdout: flags.stdout,
36
36
  template: flags.template,
37
+ templateType: flags["template-type"],
38
+ templateDirectory: flags["template-directory"],
37
39
  title: flags.title,
38
40
  language: flags.language,
39
41
  continuous: flags.continuous,
@@ -1,7 +1,7 @@
1
1
  import { MdPrinterHandler } from "./md-printer.handler.mjs";
2
2
  import { PrinterModule } from "../../services/printer.module.mjs";
3
3
  import "../../services/index.mjs";
4
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
5
  import { Module } from "@nestjs/common";
6
6
  import { CommandContext, LogLevels, LoggerModule, LogoModule } from "@cenk1cenk2/oclif-common";
7
7
  // src/modules/md-printer/md-printer.module.ts
@@ -1,6 +1,7 @@
1
1
  import { InputFileType, OutputFileType } from "../../constants/file.constants.mjs";
2
2
  import { PreviewHost } from "../../constants/preview.constants.mjs";
3
3
  import { SignatureProvider } from "../../constants/signature.constants.mjs";
4
+ import "../../constants/template.constants.mjs";
4
5
  import { z } from "zod";
5
6
  // src/modules/md-printer/md-printer.schema.ts
6
7
  const MdPrinterSchema = z.object({
@@ -9,7 +10,9 @@ const MdPrinterSchema = z.object({
9
10
  ["input-filetype"]: z.enum(InputFileType).optional().describe("Input file type. Detected from the file extension, markdown for stdin and unknown extensions."),
10
11
  ["output-filetype"]: z.enum(OutputFileType).optional().describe("Output file type, defaults to pdf. Overwrites the one defined in front-matter."),
11
12
  stdout: z.boolean().default(false).describe("Write the output to stdout instead of a file."),
12
- template: z.string().optional().describe("Built-in template name or path to a template directory, defaults to \"default\". Overwrites the one defined in front-matter."),
13
+ template: z.string().optional().describe("Built-in template name or path to a template directory, defaults to \"default\", read as --template-type when given. Overwrites the one defined in front-matter."),
14
+ ["template-type"]: z.string().optional().describe("How to read --template, one of builtin, package or directory, like the type of the object form in front-matter."),
15
+ ["template-directory"]: z.string().optional().describe("Directory inside the package to use as the template, only valid with --template-type package."),
13
16
  title: z.string().optional().describe("Document title. Overwrites the one defined in front-matter or the template."),
14
17
  language: z.string().optional().describe("Language for template translations. Overwrites the one defined in front-matter and template settings."),
15
18
  browser: z.string().optional().describe("Browser executable used for printing. Discovered automatically when omitted (Brave, Chrome, Chromium, Edge)."),
@@ -35,6 +38,14 @@ const MdPrinterSchema = z.object({
35
38
  code: "custom",
36
39
  message: "Reading from --stdin takes only the output file argument."
37
40
  });
41
+ if (flags["template-type"] && !flags.template) ctx.addIssue({
42
+ code: "custom",
43
+ message: "--template-type needs --template to read."
44
+ });
45
+ if (flags["template-directory"] !== void 0 && flags["template-type"] !== "package") ctx.addIssue({
46
+ code: "custom",
47
+ message: "--template-directory is only valid with --template-type package."
48
+ });
38
49
  if (flags.stdout && flags.preview) ctx.addIssue({
39
50
  code: "custom",
40
51
  message: "Writing to stdout is not supported with --preview."
@@ -1,6 +1,6 @@
1
1
  import { I18N_ACCEPTED_TYPES } from "../constants/template.constants.mjs";
2
- import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
4
4
  import { Injectable } from "@nestjs/common";
5
5
  import { readdir } from "fs/promises";
6
6
  import { basename, extname, join } from "path";
@@ -14,14 +14,16 @@ let I18nService = class I18nService {
14
14
  this.fs = fs;
15
15
  this.parser = parser;
16
16
  }
17
- async load(directory, options) {
17
+ /** Loads the folders in order, a key of a later folder replacing the same key of an earlier one while nested keys merge. */
18
+ async load(directories, options) {
18
19
  const instance = i18next.createInstance();
19
20
  await instance.init({
20
21
  lng: options.language,
21
22
  fallbackLng: options.fallback ?? options.language ?? "en",
22
- resources: this.fs.exists(directory) ? await this.resources(directory) : {},
23
+ resources: {},
23
24
  interpolation: { escapeValue: false }
24
25
  });
26
+ for (const directory of directories.filter((directory) => this.fs.exists(directory))) for (const [language, { translation }] of Object.entries(await this.resources(directory))) instance.addResourceBundle(language, "translation", translation, true, true);
25
27
  return instance.getFixedT(options.language);
26
28
  }
27
29
  async resources(directory) {
@@ -1,5 +1,6 @@
1
1
  import { I18nService } from "./i18n.service.mjs";
2
2
  import { MarkdownService } from "./markdown.service.mjs";
3
+ import { PdfService } from "./pdf/pdf.service.mjs";
3
4
  import { PrinterModule } from "./printer.module.mjs";
4
5
  import { PrinterService } from "./printer.service.mjs";
5
6
  import { IncompleteSignatureError, SignatureVerificationError, parseSignedData } from "./signature/cms.mjs";
@@ -12,4 +13,4 @@ import { PreviewService } from "./session/preview.service.mjs";
12
13
  import { SessionService } from "./session/session.service.mjs";
13
14
  import "./signature/index.mjs";
14
15
  import { TemplateService } from "./template/template.service.mjs";
15
- export { ATrustAdapter, BrowserService, I18nService, IncompleteSignatureError, MarkdownService, PreviewService, PrinterModule, PrinterService, RendererService, SessionService, SignatureAdapter, SignatureService, SignatureVerificationError, TemplateService, parseSignedData };
16
+ export { ATrustAdapter, BrowserService, I18nService, IncompleteSignatureError, MarkdownService, PdfService, PreviewService, PrinterModule, PrinterService, RendererService, SessionService, SignatureAdapter, SignatureService, SignatureVerificationError, TemplateService, parseSignedData };
@@ -1,5 +1,5 @@
1
1
  import { DIRECTIVE_TAG, PAGE_BREAK_CLASS } from "../constants/template.constants.mjs";
2
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
2
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
3
3
  import { Injectable } from "@nestjs/common";
4
4
  import { toHtml } from "hast-util-to-html";
5
5
  import { all } from "lowlight";
@@ -0,0 +1,124 @@
1
+ import { resolveConfined } from "../template/assets.mjs";
2
+ import "../../constants/attachment.constants.mjs";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
+ import { Injectable } from "@nestjs/common";
6
+ import { readFile } from "fs/promises";
7
+ import { resolve } from "path";
8
+ import { LoggerService } from "@cenk1cenk2/oclif-common";
9
+ import * as mupdf from "mupdf";
10
+ // src/services/pdf/pdf.service.ts
11
+ var _PdfService;
12
+ let PdfService = class PdfService {
13
+ static {
14
+ _PdfService = this;
15
+ }
16
+ logger;
17
+ static IMAGE_MAX_DIMENSION = 2e3;
18
+ static JPEG_QUALITY = 75;
19
+ constructor(logger) {
20
+ this.logger = logger;
21
+ this.logger.setup(this.constructor.name);
22
+ }
23
+ async process(pdf, options) {
24
+ if (!options.attachments.length && !options.compress) return {
25
+ pdf,
26
+ assets: []
27
+ };
28
+ const document = new mupdf.PDFDocument(pdf);
29
+ const pages = document.countPages();
30
+ const assets = [];
31
+ const loaded = await Promise.all(options.attachments.map(async (attachment) => {
32
+ const { content, path } = await this.load(attachment.file, options.directory);
33
+ if (path) assets.push(path);
34
+ return {
35
+ attachment,
36
+ document: this.open(attachment.file, content),
37
+ anchor: this.anchor(attachment, pages)
38
+ };
39
+ }));
40
+ for (const { attachment, document: source, anchor } of loaded.map((item, index) => ({
41
+ ...item,
42
+ index
43
+ })).sort((a, b) => b.anchor - a.anchor || b.index - a.index)) {
44
+ if (attachment.compress && !options.compress) this.compress(source);
45
+ for (let page = 0; page < source.countPages(); page++) document.graftPage(anchor + page, source, page);
46
+ this.logger.debug("Attached %s with %d pages at page %d.", attachment.file, source.countPages(), anchor + 1);
47
+ }
48
+ if (options.compress) this.compress(document);
49
+ const output = Buffer.from(document.saveToBuffer(options.compress ? "garbage=compact,compress,compress-fonts" : "garbage=compact,compress").asUint8Array());
50
+ this.logger.debug("Processed PDF: %d bytes to %d bytes.", pdf.length, output.length);
51
+ return {
52
+ pdf: output,
53
+ assets
54
+ };
55
+ }
56
+ anchor(attachment, pages) {
57
+ if (attachment.position === "before") return 0;
58
+ if (typeof attachment.position !== "number") return pages;
59
+ if (attachment.position > pages + 1) throw new Error(`Attachment ${attachment.file} is placed at page ${attachment.position}, but the document has ${pages} pages.`);
60
+ return attachment.position - 1;
61
+ }
62
+ async load(file, directory) {
63
+ if (/^https?:\/\//.test(file)) {
64
+ const response = await fetch(file);
65
+ if (!response.ok) throw new Error(`Attachment can not be fetched: ${file}, ${response.status} ${response.statusText}`);
66
+ return { content: new Uint8Array(await response.arrayBuffer()) };
67
+ }
68
+ const path = await resolveConfined(resolve(directory, file), directory);
69
+ if (!path) throw new Error(`Attachment does not exist or is outside of the input directory: ${file}`);
70
+ return {
71
+ content: await readFile(path),
72
+ path
73
+ };
74
+ }
75
+ open(file, content) {
76
+ if (Buffer.from(content.subarray(0, 5)).toString() !== "%PDF-") throw new Error(`Attachment is not a PDF: ${file}`);
77
+ return new mupdf.PDFDocument(content);
78
+ }
79
+ /**
80
+ * Re-encodes the images of a document as JPEG, scaled down to fit a printed page, whenever that makes them smaller.
81
+ * Soft masks, stencil masks and images below 8 bits per component keep their lossless encoding.
82
+ */
83
+ compress(document) {
84
+ const images = [];
85
+ const masks = /* @__PURE__ */ new Set();
86
+ for (let number = 1; number < document.countObjects(); number++) {
87
+ const object = document.newIndirect(number);
88
+ if (!object.isStream()) continue;
89
+ const subtype = object.get("Subtype");
90
+ if (!subtype.isName() || subtype.asName() !== "Image") continue;
91
+ const mask = object.get("SMask");
92
+ if (mask.isIndirect()) masks.add(mask.asIndirect());
93
+ images.push(object);
94
+ }
95
+ for (const object of images) {
96
+ if (masks.has(object.asIndirect()) || object.get("ImageMask").valueOf() === true) continue;
97
+ const image = document.loadImage(object);
98
+ if (image.getBitsPerComponent() < 8) continue;
99
+ let pixmap = image.toPixmap();
100
+ if (pixmap.getAlpha()) continue;
101
+ if (!pixmap.getColorSpace().isGray() && !pixmap.getColorSpace().isRGB()) pixmap = pixmap.convertToColorSpace(mupdf.ColorSpace.DeviceRGB);
102
+ const [width, height] = [pixmap.getWidth(), pixmap.getHeight()];
103
+ const scale = Math.min(1, _PdfService.IMAGE_MAX_DIMENSION / Math.max(width, height));
104
+ if (scale < 1) pixmap = pixmap.warp([
105
+ [0, 0],
106
+ [width, 0],
107
+ [width, height],
108
+ [0, height]
109
+ ], Math.round(width * scale), Math.round(height * scale));
110
+ const jpeg = pixmap.asJPEG(_PdfService.JPEG_QUALITY);
111
+ if (jpeg.length >= object.readRawStream().length) continue;
112
+ object.writeRawStream(jpeg);
113
+ object.put("Filter", document.newName("DCTDecode"));
114
+ object.delete("DecodeParms");
115
+ object.delete("Decode");
116
+ object.put("Width", pixmap.getWidth());
117
+ object.put("Height", pixmap.getHeight());
118
+ object.put("BitsPerComponent", 8);
119
+ object.put("ColorSpace", document.newName(pixmap.getColorSpace().isGray() ? "DeviceGray" : "DeviceRGB"));
120
+ }
121
+ }
122
+ };
123
+ PdfService = _PdfService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService])], PdfService);
124
+ export { PdfService };
@@ -1,5 +1,6 @@
1
1
  import { I18nService } from "./i18n.service.mjs";
2
2
  import { MarkdownService } from "./markdown.service.mjs";
3
+ import { PdfService } from "./pdf/pdf.service.mjs";
3
4
  import { PrinterService } from "./printer.service.mjs";
4
5
  import { BrowserService } from "./renderer/browser.service.mjs";
5
6
  import { RendererService } from "./renderer/renderer.service.mjs";
@@ -9,7 +10,7 @@ import { ATrustAdapter } from "./signature/adapters/a-trust.adapter.mjs";
9
10
  import { SignatureService } from "./signature/signature.service.mjs";
10
11
  import { TemplateService } from "./template/template.service.mjs";
11
12
  import { PRINTER_MODULE_OPTIONS } from "../constants/template.constants.mjs";
12
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
13
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
13
14
  import { ConfigurableModuleBuilder, Module } from "@nestjs/common";
14
15
  import { FilesystemModule, JsonParser, ParserModule, YamlParser } from "@cenk1cenk2/oclif-common";
15
16
  // src/services/printer.module.ts
@@ -22,6 +23,7 @@ PrinterModule = __decorate([Module({
22
23
  TemplateService,
23
24
  BrowserService,
24
25
  RendererService,
26
+ PdfService,
25
27
  ATrustAdapter,
26
28
  SignatureService,
27
29
  PrinterService,
@@ -1,16 +1,19 @@
1
1
  import { I18nService } from "./i18n.service.mjs";
2
2
  import { MarkdownService } from "./markdown.service.mjs";
3
+ import { PdfService } from "./pdf/pdf.service.mjs";
3
4
  import { RendererService } from "./renderer/renderer.service.mjs";
4
5
  import { SignatureVerificationError } from "./signature/cms.mjs";
5
6
  import { SignatureService } from "./signature/signature.service.mjs";
6
7
  import { inlineHtml } from "./template/assets.mjs";
8
+ import { TemplateSourceSchema, issues } from "./template/template.schema.mjs";
7
9
  import { TemplateService } from "./template/template.service.mjs";
10
+ import { AttachmentPosition } from "../constants/attachment.constants.mjs";
8
11
  import { DEFAULT_PDF_OPTIONS } from "../constants/template.constants.mjs";
9
12
  import { OutputFileType } from "../constants/file.constants.mjs";
10
- import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
11
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
13
+ import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
14
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
12
15
  import { Injectable } from "@nestjs/common";
13
- import { basename, dirname, extname, join, resolve } from "path";
16
+ import { basename, dirname, extname, resolve } from "path";
14
17
  import { ConfigService, FileSystemService, LoggerService } from "@cenk1cenk2/oclif-common";
15
18
  import { cloneDeep, mergeWith, uniq } from "es-toolkit";
16
19
  import { parse } from "yaml";
@@ -23,8 +26,9 @@ let PrinterService = class PrinterService {
23
26
  markdown;
24
27
  template;
25
28
  renderer;
29
+ pdf;
26
30
  signature;
27
- constructor(fs, logger, config, i18n, markdown, template, renderer, signature) {
31
+ constructor(fs, logger, config, i18n, markdown, template, renderer, pdf, signature) {
28
32
  this.fs = fs;
29
33
  this.logger = logger;
30
34
  this.config = config;
@@ -32,6 +36,7 @@ let PrinterService = class PrinterService {
32
36
  this.markdown = markdown;
33
37
  this.template = template;
34
38
  this.renderer = renderer;
39
+ this.pdf = pdf;
35
40
  this.signature = signature;
36
41
  this.logger.setup(this.constructor.name);
37
42
  }
@@ -61,13 +66,14 @@ let PrinterService = class PrinterService {
61
66
  }
62
67
  async options(flags, input) {
63
68
  const { data } = input;
64
- const template = this.template.resolve(flags.template ?? data.template ?? "default");
65
- const settings = await this.template.settings(template);
69
+ const source = this.templateSource(this.templateFlags(flags) ?? data.template) ?? "default";
70
+ const { directory: template, settings, files } = await this.template.resolve(source, input.directory);
66
71
  const output = flags.outputFiletype ?? data.outputFiletype ?? "pdf";
67
72
  if (!Object.values(OutputFileType).includes(output)) throw new Error(`Output file type is not supported: ${output}, use one of ${Object.values(OutputFileType).join(", ")}.`);
68
73
  const dest = flags.stdout ? void 0 : flags.output ?? data.dest ?? (input.file ? `${basename(input.file, extname(input.file))}.${output}` : void 0);
69
74
  return {
70
75
  template,
76
+ files,
71
77
  output,
72
78
  dest: dest && resolve(dest),
73
79
  title: flags.title ?? data.documentTitle,
@@ -77,11 +83,13 @@ let PrinterService = class PrinterService {
77
83
  highlight: settings.highlightStyle ?? "atom-one-dark",
78
84
  pdf: this.pdfOptions(settings.pdfOptions, data.pdfOptions),
79
85
  i18n: settings.i18n ?? {},
80
- sign: this.signOptions(flags, settings.sign, data.sign)
86
+ sign: this.signOptions(flags, settings.sign, data.sign),
87
+ attachments: this.attachments(data.attachments),
88
+ compress: data.compress === true
81
89
  };
82
90
  }
83
91
  bundle(options) {
84
- return this.template.bundle(options.template);
92
+ return this.template.bundle(options.files);
85
93
  }
86
94
  /**
87
95
  * Resolves the signing time and the visible signature description, which the template renders before the document is signed.
@@ -98,7 +106,7 @@ let PrinterService = class PrinterService {
98
106
  const { input, options } = ctx;
99
107
  const document = await this.template.render(options.template, bundle, {
100
108
  data: input.data,
101
- t: await this.i18n.load(join(options.template, "i18n"), {
109
+ t: await this.i18n.load(options.files.i18n, {
102
110
  ...options.i18n,
103
111
  language: options.language
104
112
  }),
@@ -115,6 +123,7 @@ let PrinterService = class PrinterService {
115
123
  }
116
124
  async output(ctx, document) {
117
125
  if (ctx.options.output === "html") {
126
+ if (ctx.options.attachments.length || ctx.options.compress) throw new Error("Attachments and compression are only supported for PDF output.");
118
127
  const { content, assets } = await inlineHtml(document.html, ctx.input.directory, { confined: true });
119
128
  return {
120
129
  content: Buffer.from(content),
@@ -127,9 +136,14 @@ let PrinterService = class PrinterService {
127
136
  media: ctx.options.media,
128
137
  continuous: ctx.options.continuous
129
138
  });
139
+ const processed = await this.pdf.process(pdf, {
140
+ directory: ctx.input.directory,
141
+ attachments: ctx.options.attachments,
142
+ compress: ctx.options.compress
143
+ });
130
144
  return {
131
- content: pdf,
132
- assets
145
+ content: processed.pdf,
146
+ assets: [...assets, ...processed.assets]
133
147
  };
134
148
  }
135
149
  sign(ctx, pdf) {
@@ -146,6 +160,35 @@ let PrinterService = class PrinterService {
146
160
  throw err;
147
161
  });
148
162
  }
163
+ templateFlags({ template, templateType, templateDirectory }) {
164
+ if (!templateType) return template;
165
+ return {
166
+ type: templateType,
167
+ [templateType === "directory" ? "path" : "name"]: template,
168
+ ...templateDirectory !== void 0 && { directory: templateDirectory }
169
+ };
170
+ }
171
+ templateSource(value) {
172
+ if (value === void 0 || value === null) return;
173
+ if (typeof value === "string") return value;
174
+ const { success, data, error } = TemplateSourceSchema.safeParse(value);
175
+ if (!success) throw new Error(`Template is invalid: ${issues(error)}`);
176
+ return data;
177
+ }
178
+ attachments(value) {
179
+ if (value === void 0 || value === null) return [];
180
+ if (!Array.isArray(value)) throw new Error("Front-matter attachments must be a list of { file, position, compress }.");
181
+ return value.map((attachment, index) => {
182
+ if (typeof attachment?.file !== "string" || !attachment.file) throw new Error(`Attachment ${index + 1} needs a file path or URL.`);
183
+ const position = attachment.position ?? "after";
184
+ if (!Object.values(AttachmentPosition).includes(position) && !(Number.isInteger(position) && position >= 1)) throw new Error(`Attachment ${attachment.file} has an invalid position: ${position}, use ${Object.values(AttachmentPosition).join(", ")} or a page number.`);
185
+ return {
186
+ file: attachment.file,
187
+ position,
188
+ compress: attachment.compress === true
189
+ };
190
+ });
191
+ }
149
192
  signOptions(flags, ...sources) {
150
193
  return {
151
194
  ...Object.assign({}, ...sources),
@@ -176,6 +219,7 @@ PrinterService = __decorate([Injectable(), __decorateMetadata("design:paramtypes
176
219
  typeof MarkdownService === "undefined" ? Object : MarkdownService,
177
220
  typeof TemplateService === "undefined" ? Object : TemplateService,
178
221
  typeof RendererService === "undefined" ? Object : RendererService,
222
+ typeof PdfService === "undefined" ? Object : PdfService,
179
223
  typeof SignatureService === "undefined" ? Object : SignatureService
180
224
  ])], PrinterService);
181
225
  export { PrinterService };
@@ -1,8 +1,8 @@
1
1
  import { discoverBrowser } from "./browser.mjs";
2
2
  import { PRINTER_MODULE_OPTIONS } from "../../constants/template.constants.mjs";
3
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
4
- import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateParam.mjs";
5
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateParam.mjs";
5
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
6
6
  import { Inject, Injectable } from "@nestjs/common";
7
7
  import { LoggerService } from "@cenk1cenk2/oclif-common";
8
8
  import puppeteer from "puppeteer-core";
@@ -1,8 +1,8 @@
1
- import { BrowserService } from "./browser.service.mjs";
2
1
  import { mimeType, resolveConfined } from "../template/assets.mjs";
2
+ import { BrowserService } from "./browser.service.mjs";
3
3
  import { PAGE_BREAK_CLASS } from "../../constants/template.constants.mjs";
4
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
5
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
4
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
5
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
6
6
  import { Injectable } from "@nestjs/common";
7
7
  import { readFile } from "fs/promises";
8
8
  import { resolve } from "path";
@@ -2,8 +2,8 @@ import { isLoopbackRequest } from "../loopback.mjs";
2
2
  import { resolveConfined } from "../template/assets.mjs";
3
3
  import { TemplateService } from "../template/template.service.mjs";
4
4
  import "../../constants/file.constants.mjs";
5
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
6
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
5
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
6
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
7
7
  import { Injectable } from "@nestjs/common";
8
8
  import { mkdtemp, rm } from "fs/promises";
9
9
  import { join } from "path";
@@ -1,10 +1,11 @@
1
1
  import { PrinterService } from "../printer.service.mjs";
2
2
  import "../../constants/template.constants.mjs";
3
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
4
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
5
  import { Injectable } from "@nestjs/common";
6
6
  import { dirname, join, resolve } from "path";
7
7
  import { LoggerService } from "@cenk1cenk2/oclif-common";
8
+ import { isEqual } from "es-toolkit";
8
9
  import { EventEmitter } from "events";
9
10
  import { watch } from "chokidar";
10
11
  import { debounce } from "perfect-debounce";
@@ -53,7 +54,7 @@ let SessionService = class SessionService extends EventEmitter {
53
54
  const file = resolve(path);
54
55
  if (this.ignored.has(file)) return;
55
56
  this.logger.debug("Changed: %s", file);
56
- if (this.modules.has(file) || this.bundle && dirname(file) === join(this.bundle.template, "components")) this.bundle = void 0;
57
+ if (this.modules.has(file) || this.bundle?.files.components.includes(dirname(file))) this.bundle = void 0;
57
58
  this.build();
58
59
  }
59
60
  async run() {
@@ -62,18 +63,17 @@ let SessionService = class SessionService extends EventEmitter {
62
63
  try {
63
64
  const input = await this.printer.input(this.flags);
64
65
  const options = await this.printer.options(this.flags, input);
65
- const template = join(options.template, "Template.svelte");
66
- this.modules.add(template);
66
+ const { files } = options;
67
+ this.modules = /* @__PURE__ */ new Set([...this.modules, files.template]);
67
68
  this.watch([
68
69
  input.file,
69
- template,
70
+ files.template,
71
+ ...files.candidates,
70
72
  join(options.template, "settings.json"),
71
- join(options.template, "i18n"),
72
- join(options.template, "components"),
73
73
  options.sign.certificate && resolve(options.sign.certificate)
74
74
  ]);
75
- if (this.bundle?.template !== options.template) this.bundle = {
76
- template: options.template,
75
+ if (!isEqual(this.bundle?.files, files)) this.bundle = {
76
+ files,
77
77
  bundle: this.printer.bundle(options)
78
78
  };
79
79
  const cached = this.bundle;
@@ -82,7 +82,7 @@ let SessionService = class SessionService extends EventEmitter {
82
82
  throw err;
83
83
  });
84
84
  if (this.bundle !== cached) return;
85
- this.modules = /* @__PURE__ */ new Set([template, ...bundle.modules]);
85
+ this.modules = /* @__PURE__ */ new Set([files.template, ...bundle.modules]);
86
86
  this.watch(bundle.modules);
87
87
  const ctx = {
88
88
  input,
@@ -4,8 +4,8 @@ import { BrowserService } from "../../renderer/browser.service.mjs";
4
4
  import { IncompleteSignatureError, parseSignedData } from "../cms.mjs";
5
5
  import { SignatureAdapter } from "../signature.adapter.mjs";
6
6
  import "../../../constants/signature.constants.mjs";
7
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
8
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
7
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
8
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
9
9
  import { Injectable } from "@nestjs/common";
10
10
  import { readFile } from "fs/promises";
11
11
  import { CommandContext, LoggerService } from "@cenk1cenk2/oclif-common";
@@ -1,7 +1,7 @@
1
1
  import { ATrustAdapter } from "./adapters/a-trust.adapter.mjs";
2
2
  import { IncompleteSignatureError, SignatureVerificationError, parseSignedData } from "./cms.mjs";
3
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
4
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
5
  import { Injectable } from "@nestjs/common";
6
6
  import { readFile } from "fs/promises";
7
7
  import { isAbsolute, join } from "path";
@@ -2,11 +2,11 @@ import { readFile, realpath } from "fs/promises";
2
2
  import { isAbsolute, relative, sep } from "path";
3
3
  import { toHtml } from "hast-util-to-html";
4
4
  import { visit } from "unist-util-visit";
5
- import { fileURLToPath, pathToFileURL } from "url";
6
5
  import { fromHtml } from "hast-util-from-html";
7
6
  import { transform, transformStyleAttribute } from "lightningcss";
8
7
  import mime from "mime";
9
8
  import { parseSrcset, stringifySrcset } from "srcset";
9
+ import { fileURLToPath, pathToFileURL } from "url";
10
10
  // src/services/template/assets.ts
11
11
  const REFERENCES = {
12
12
  img: ["src", "srcSet"],
@@ -1,13 +1,17 @@
1
+ import modules from 'md-printer:extension'
1
2
  import Template from 'md-printer:template'
2
3
  import components from 'md-printer:template-components'
3
4
  import { render } from 'svelte/server'
4
5
 
6
+ import Nodes from '../../components/Nodes.svelte'
5
7
  import Document from './Document.svelte'
6
8
  import Margin from './Margin.svelte'
7
- import Nodes from '../../components/Nodes.svelte'
8
9
  import type { DocumentShellProps, MarginShellProps, MarkdownNode, RenderedPart, TemplateProps } from '@interfaces'
9
10
 
10
- export { components }
11
+ // a CommonJS module also comes with module.exports as the default
12
+ const extension = Object.fromEntries(modules.flatMap((module) => Object.entries(module).filter(([name]) => name !== 'default')))
13
+
14
+ export { components, extension }
11
15
 
12
16
  /**
13
17
  * Entry of every template bundle, compiled at runtime together with the template it renders.
@@ -1,7 +1,7 @@
1
1
  import { dataUri, isInlinableAsset } from "./assets.mjs";
2
2
  import "../../constants/template.constants.mjs";
3
3
  import { readFile, readdir } from "fs/promises";
4
- import { basename, dirname, extname, join } from "path";
4
+ import { basename, extname, join } from "path";
5
5
  import { pathToFileURL } from "url";
6
6
  import { existsSync } from "fs";
7
7
  import { createRequire } from "module";
@@ -10,9 +10,10 @@ import { compile, preprocess } from "svelte/compiler";
10
10
  /**
11
11
  * `svelte` resolves to md-printer's copy, since templates can live outside any `node_modules`. `<style lang="postcss">` blocks are collected
12
12
  * instead of compiled per component, so the template's stylesheet goes through Tailwind once. `md-printer:template-components` maps the shared
13
- * components and the template's `components/*.svelte` by name, the template's winning.
13
+ * components and the `*.svelte` files of the template's components directory by name, the template's winning. `md-printer:extension` is the extension module of the
14
+ * template, or an empty module without one.
14
15
  */
15
- function sveltePlugin({ template, components, root, stylesheets }) {
16
+ function sveltePlugin({ template, components, templateComponents, root, stylesheets, extension }) {
16
17
  const require = createRequire(root);
17
18
  return {
18
19
  name: "md-printer:svelte",
@@ -20,6 +21,7 @@ function sveltePlugin({ template, components, root, stylesheets }) {
20
21
  if (id === "md-printer:template") return template;
21
22
  if (id === "@cenk1cenk2/md-printer/components") return components;
22
23
  if (id === "md-printer:template-components") return `\0${id}`;
24
+ if (id === "md-printer:extension") return `\0${id}`;
23
25
  if (id === "svelte" || id.startsWith("svelte/")) return {
24
26
  id: pathToFileURL(require.resolve(id)).href,
25
27
  external: true
@@ -28,15 +30,18 @@ function sveltePlugin({ template, components, root, stylesheets }) {
28
30
  },
29
31
  async load(id) {
30
32
  if (id === `\0md-printer:template-components`) {
31
- const directory = join(dirname(template), "components");
32
- const files = existsSync(directory) ? (await readdir(directory)).filter((file) => extname(file) === ".svelte").sort() : [];
33
+ const files = (await Promise.all((templateComponents ?? []).map(async (folder) => existsSync(folder) ? (await readdir(folder)).filter((file) => extname(file) === ".svelte").sort().map((file) => join(folder, file)) : []))).flat();
33
34
  return [
34
35
  `import * as shared from ${JSON.stringify("@cenk1cenk2/md-printer/components")}`,
35
- ...files.map((file, index) => `import $${index} from ${JSON.stringify(join(directory, file))}`),
36
- "const { Markdown, ...builtins } = shared",
36
+ ...files.map((file, index) => `import $${index} from ${JSON.stringify(file)}`),
37
+ "const { Markdown, getExtension, ...builtins } = shared",
37
38
  `export default { ...builtins, ${files.map((file, index) => `${JSON.stringify(basename(file, ".svelte"))}: $${index}`).join(", ")} }`
38
39
  ].join("\n");
39
40
  }
41
+ if (id === `\0md-printer:extension`) {
42
+ const modules = extension ?? [];
43
+ return [...modules.map((file, index) => `import * as $${index} from ${JSON.stringify(file)}`), `export default [${modules.map((_, index) => `$${index}`).join(", ")}]`].join("\n");
44
+ }
40
45
  if (!isInlinableAsset(id)) return null;
41
46
  return `export default ${JSON.stringify(dataUri(id, await readFile(id)))}`;
42
47
  },