@cenk1cenk2/md-printer 2.21.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +333 -48
  2. package/bin/dev.js +0 -2
  3. package/bin/run.js +0 -1
  4. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs +1 -2
  5. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs +1 -2
  6. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.mjs +8 -0
  7. package/dist/commands/index.mjs +32 -345
  8. package/dist/components/Date.svelte +1 -0
  9. package/dist/components/Markdown.svelte +20 -0
  10. package/dist/components/Nodes.svelte +32 -0
  11. package/dist/components/Page.svelte +20 -0
  12. package/dist/components/PageBreak.svelte +5 -0
  13. package/dist/components/PageNumber.svelte +1 -0
  14. package/dist/components/SignatureBlock.svelte +56 -0
  15. package/dist/components/Title.svelte +1 -0
  16. package/dist/components/TotalPages.svelte +1 -0
  17. package/dist/components/index.ts +8 -0
  18. package/dist/constants/file.constants.mjs +1 -8
  19. package/dist/constants/index.mjs +5 -4
  20. package/dist/constants/preview.constants.mjs +9 -0
  21. package/dist/constants/signature.constants.mjs +7 -0
  22. package/dist/constants/template.constants.mjs +48 -10
  23. package/dist/interfaces/commands/index.mjs +1 -1
  24. package/dist/interfaces/commands/run.interface.mjs +1 -1
  25. package/dist/interfaces/index.mjs +1 -1
  26. package/dist/interfaces/markdown.interface.mjs +1 -0
  27. package/dist/interfaces/renderer.interface.mjs +1 -0
  28. package/dist/interfaces/session.interface.mjs +1 -0
  29. package/dist/interfaces/signature.interface.mjs +1 -1
  30. package/dist/interfaces/template.interface.mjs +1 -0
  31. package/dist/modules/md-printer/md-printer.handler.mjs +113 -0
  32. package/dist/modules/md-printer/md-printer.module.mjs +21 -0
  33. package/dist/modules/md-printer/md-printer.schema.mjs +50 -0
  34. package/dist/services/i18n.service.mjs +12 -15
  35. package/dist/services/index.mjs +9 -2
  36. package/dist/services/loopback.mjs +18 -0
  37. package/dist/services/markdown.service.mjs +123 -0
  38. package/dist/services/printer.module.mjs +39 -0
  39. package/dist/services/printer.service.mjs +171 -0
  40. package/dist/services/renderer/browser.mjs +41 -0
  41. package/dist/services/renderer/browser.service.mjs +56 -0
  42. package/dist/services/renderer/renderer.service.mjs +198 -0
  43. package/dist/services/session/Preview.svelte +238 -0
  44. package/dist/services/session/preview.service.mjs +187 -0
  45. package/dist/services/session/session.service.mjs +115 -0
  46. package/dist/services/signature/adapters/a-trust.adapter.mjs +32 -36
  47. package/dist/services/signature/index.mjs +1 -2
  48. package/dist/services/signature/signature.adapter.mjs +1 -2
  49. package/dist/services/signature/signature.service.mjs +6 -10
  50. package/dist/services/template/Document.svelte +21 -0
  51. package/dist/services/template/Margin.svelte +15 -0
  52. package/dist/services/template/assets.mjs +127 -0
  53. package/dist/services/template/render.entry.ts +32 -0
  54. package/dist/services/template/svelte.plugin.mjs +66 -0
  55. package/dist/services/template/template.service.mjs +211 -0
  56. package/oclif.manifest.json +49 -36
  57. package/package.json +69 -35
  58. package/templates/default/Template.svelte +83 -0
  59. package/templates/default/settings.json +1 -3
  60. package/templates/invoice/Template.svelte +247 -0
  61. package/templates/invoice/components/Client.svelte +42 -0
  62. package/templates/invoice/components/Company.svelte +41 -0
  63. package/templates/invoice/components/Items.svelte +66 -0
  64. package/templates/invoice/components/Notes.svelte +21 -0
  65. package/templates/invoice/components/Payment.svelte +25 -0
  66. package/templates/invoice/components/Signature.svelte +34 -0
  67. package/templates/invoice/components/Totals.svelte +47 -0
  68. package/templates/invoice/round.js +2 -0
  69. package/templates/invoice/settings.json +1 -4
  70. package/templates/kilic.dev/Template.svelte +102 -0
  71. package/templates/kilic.dev/settings.json +1 -3
  72. package/templates/mail/Template.svelte +166 -0
  73. package/templates/mail/settings.json +1 -3
  74. package/dist/hooks/not-found.hook.mjs +0 -7
  75. package/dist/hooks/update-notifier.hook.mjs +0 -7
  76. package/dist/interfaces/pupeteer.mjs +0 -1
  77. package/templates/default/main.css +0 -73
  78. package/templates/invoice/.gitignore +0 -1
  79. package/templates/invoice/footer.html +0 -18
  80. package/templates/invoice/tailwind.css +0 -164
  81. package/templates/invoice/template.html.j2 +0 -226
  82. package/templates/kilic.dev/footer.html +0 -26
  83. package/templates/kilic.dev/header.html +0 -16
  84. package/templates/kilic.dev/main.css +0 -74
  85. package/templates/mail/.gitignore +0 -1
  86. package/templates/mail/tailwind.css +0 -145
  87. package/templates/mail/template.html.j2 +0 -14
@@ -1,25 +1,22 @@
1
1
  import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
2
2
  import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
3
3
  import { ATrustAdapter } from "./adapters/a-trust.adapter.mjs";
4
- import { isAbsolute, join } from "path";
5
- import { FileSystemService } from "@cenk1cenk2/oclif-common";
6
4
  import { Injectable } from "@nestjs/common";
5
+ import { readFile } from "fs/promises";
6
+ import { isAbsolute, join } from "path";
7
7
  import { plainAddPlaceholder } from "@signpdf/placeholder-plain";
8
8
  import { SUBFILTER_ETSI_CADES_DETACHED, findByteRange, removeTrailingNewLine } from "@signpdf/utils";
9
9
  import { X509Certificate } from "crypto";
10
10
  import { homedir } from "os";
11
-
12
11
  //#region src/services/signature/signature.service.ts
13
12
  var _SignatureService;
14
13
  let SignatureService = class SignatureService {
15
14
  static {
16
15
  _SignatureService = this;
17
16
  }
18
- fs;
19
17
  static LENGTH = 16384;
20
18
  adapters;
21
- constructor(fs, aTrust) {
22
- this.fs = fs;
19
+ constructor(aTrust) {
23
20
  this.adapters = [aTrust];
24
21
  }
25
22
  get providers() {
@@ -27,7 +24,7 @@ let SignatureService = class SignatureService {
27
24
  }
28
25
  async certificate(file, date) {
29
26
  const path = file.replace(/^~(?=\/|$)/, homedir());
30
- const certificate = new X509Certificate(await this.fs.extra.readFile(isAbsolute(path) ? path : join(process.cwd(), path)));
27
+ const certificate = new X509Certificate(await readFile(isAbsolute(path) ? path : join(process.cwd(), path)));
31
28
  if (date < certificate.validFromDate || date > certificate.validToDate) throw new Error(`Signing certificate is not valid at ${date.toISOString()}, it is valid from ${certificate.validFrom} to ${certificate.validTo}.`);
32
29
  return certificate;
33
30
  }
@@ -91,7 +88,6 @@ let SignatureService = class SignatureService {
91
88
  return name.split("\n").find((part) => part.startsWith("CN="))?.slice(3);
92
89
  }
93
90
  };
94
- SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof FileSystemService === "undefined" ? Object : FileSystemService, typeof ATrustAdapter === "undefined" ? Object : ATrustAdapter])], SignatureService);
95
-
91
+ SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof ATrustAdapter === "undefined" ? Object : ATrustAdapter])], SignatureService);
96
92
  //#endregion
97
- export { SignatureService };
93
+ export { SignatureService };
@@ -0,0 +1,21 @@
1
+ <!--
2
+ @component
3
+ HTML document around the rendered template body.
4
+ -->
5
+ <script>
6
+ /** @type {import('@interfaces').DocumentShellProps} */
7
+ let { lang, title, head, body } = $props()
8
+ </script>
9
+
10
+ <html {lang}>
11
+ <head>
12
+ <meta charset="utf-8" />
13
+ {#if title !== undefined}
14
+ <title>{title}</title>
15
+ {/if}
16
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- styles and head elements rendered by md-printer -->
17
+ {@html head}
18
+ </head>
19
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- the template body rendered by md-printer -->
20
+ <body>{@html body}</body>
21
+ </html>
@@ -0,0 +1,15 @@
1
+ <!--
2
+ @component
3
+ Wrapper for a header or footer snippet printed into Chrome's isolated page margin, which ignores the page stylesheet and defaults to a tiny font.
4
+ -->
5
+ <script>
6
+ /** @type {import('@interfaces').MarginShellProps} */
7
+ let { styles, body } = $props()
8
+ </script>
9
+
10
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- styles collected by md-printer for the isolated margin document -->
11
+ {@html styles}
12
+ <div style="font-size: 10px; width: 100%; box-sizing: border-box; -webkit-print-color-adjust: exact; print-color-adjust: exact">
13
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -- the header or footer snippet rendered by md-printer -->
14
+ {@html body}
15
+ </div>
@@ -0,0 +1,127 @@
1
+ import { readFile, realpath } from "fs/promises";
2
+ import { isAbsolute, relative, sep } from "path";
3
+ import { toHtml } from "hast-util-to-html";
4
+ import { visit } from "unist-util-visit";
5
+ import { fileURLToPath, pathToFileURL } from "url";
6
+ import { fromHtml } from "hast-util-from-html";
7
+ import { transform, transformStyleAttribute } from "lightningcss";
8
+ import mime from "mime";
9
+ import { parseSrcset, stringifySrcset } from "srcset";
10
+ //#region src/services/template/assets.ts
11
+ const REFERENCES = {
12
+ img: ["src", "srcSet"],
13
+ source: ["src", "srcSet"],
14
+ input: ["src"],
15
+ video: ["poster"],
16
+ image: ["href", "xLinkHref"]
17
+ };
18
+ function mimeType(path) {
19
+ return mime.getType(path) ?? "application/octet-stream";
20
+ }
21
+ /**
22
+ * Images and fonts, the assets that can be embedded into a page as data URIs.
23
+ */
24
+ function isInlinableAsset(path) {
25
+ return /^(image|font)\//.test(mime.getType(path) ?? "");
26
+ }
27
+ function dataUri(path, content) {
28
+ return `data:${mimeType(path)};base64,${content.toString("base64")}`;
29
+ }
30
+ /**
31
+ * Resolves symlinks first, so a link inside the directory can not reach a file outside of it.
32
+ */
33
+ async function resolveConfined(path, base) {
34
+ const [real, root] = await Promise.all([realpath(path), realpath(base)]).catch(() => []);
35
+ if (!real) return;
36
+ const rel = relative(root, real);
37
+ return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel) ? void 0 : real;
38
+ }
39
+ /**
40
+ * Replaces local image references in HTML with data URIs: the image attributes, `style` attributes and `<style>` elements.
41
+ * Remote references are fetched as well when `remote` is set, which Chrome's isolated header/footer templates need.
42
+ * Document content is `confined` to its directory, the same rule the PDF renderer applies to the assets a page loads.
43
+ */
44
+ async function inlineHtml(markup, base, options = {}) {
45
+ const assets = /* @__PURE__ */ new Set();
46
+ const inline = inliner(base, options, assets);
47
+ const tree = fromHtml(markup, { fragment: !/^<!doctype/i.test(markup) });
48
+ const pending = [];
49
+ visit(tree, "element", (node) => {
50
+ const { properties } = node;
51
+ for (const name of REFERENCES[node.tagName] ?? []) {
52
+ const value = properties[name];
53
+ if (typeof value !== "string") continue;
54
+ pending.push((async () => {
55
+ properties[name] = name === "srcSet" ? stringifySrcset(await Promise.all(parseSrcset(value).map(async (candidate) => ({
56
+ ...candidate,
57
+ url: await inline(candidate.url) ?? candidate.url
58
+ })))) : await inline(value) ?? value;
59
+ })());
60
+ }
61
+ if (typeof properties.style === "string") {
62
+ const style = properties.style;
63
+ pending.push(stylesheet(style, inline, true).then((css) => {
64
+ properties.style = css;
65
+ }));
66
+ }
67
+ if (node.tagName === "style") {
68
+ for (const child of node.children) if (child.type === "text") pending.push(stylesheet(child.value, inline).then((css) => {
69
+ child.value = css;
70
+ }));
71
+ }
72
+ });
73
+ await Promise.all(pending);
74
+ return {
75
+ content: toHtml(tree, { characterReferences: { useNamedReferences: true } }),
76
+ assets: [...assets]
77
+ };
78
+ }
79
+ /**
80
+ * Replaces local `url()` references in CSS with data URIs, with the same options as `inlineHtml`.
81
+ */
82
+ async function inlineCss(css, base, options = {}) {
83
+ const assets = /* @__PURE__ */ new Set();
84
+ return {
85
+ content: await stylesheet(css, inliner(base, options, assets)),
86
+ assets: [...assets]
87
+ };
88
+ }
89
+ function inliner(base, { remote = false, confined = false, cache = /* @__PURE__ */ new Map() }, assets) {
90
+ return async (reference) => {
91
+ const asset = await (cache.get(reference) ?? cache.set(reference, load(reference, base, remote, confined)).get(reference));
92
+ if (asset?.path) assets.add(asset.path);
93
+ return asset?.uri;
94
+ };
95
+ }
96
+ async function stylesheet(css, inline, attribute = false) {
97
+ const options = {
98
+ filename: "style.css",
99
+ code: Buffer.from(css),
100
+ analyzeDependencies: { preserveImports: true },
101
+ errorRecovery: true
102
+ };
103
+ const { code, dependencies } = attribute ? transformStyleAttribute({
104
+ ...options,
105
+ analyzeDependencies: true
106
+ }) : transform(options);
107
+ return (await Promise.all(dependencies.map(async (dependency) => [`"${dependency.placeholder}"`, JSON.stringify(dependency.type === "url" && await inline(dependency.url) || dependency.url)]))).reduce((result, [placeholder, value]) => result.replace(placeholder, () => value), code.toString());
108
+ }
109
+ async function load(reference, base, remote, confined) {
110
+ const url = reference.startsWith("#") ? void 0 : URL.parse(reference, pathToFileURL(`${base}${sep}`));
111
+ if (url?.protocol === "http:" || url?.protocol === "https:") {
112
+ if (!remote) return;
113
+ const response = await fetch(url).catch(() => void 0);
114
+ if (!response?.ok) return;
115
+ return { uri: `data:${response.headers.get("content-type") ?? mimeType(url.pathname)};base64,${Buffer.from(await response.arrayBuffer()).toString("base64")}` };
116
+ }
117
+ if (url?.protocol !== "file:" || url.host) return;
118
+ const path = fileURLToPath(url);
119
+ const file = confined ? await resolveConfined(path, base) : path;
120
+ if (!file || !isInlinableAsset(path)) return;
121
+ return readFile(file).then((content) => ({
122
+ uri: dataUri(path, content),
123
+ path: file
124
+ }), () => void 0);
125
+ }
126
+ //#endregion
127
+ export { dataUri, inlineCss, inlineHtml, isInlinableAsset, mimeType, resolveConfined };
@@ -0,0 +1,32 @@
1
+ import Template from 'md-printer:template'
2
+ import components from 'md-printer:template-components'
3
+ import { render } from 'svelte/server'
4
+
5
+ import Document from './Document.svelte'
6
+ import Margin from './Margin.svelte'
7
+ import Nodes from '../../components/Nodes.svelte'
8
+ import type { DocumentShellProps, MarginShellProps, MarkdownNode, RenderedPart, TemplateProps } from '@interfaces'
9
+
10
+ export { components }
11
+
12
+ /**
13
+ * Entry of every template bundle, compiled at runtime together with the template it renders.
14
+ */
15
+ export function part(props: TemplateProps, context: [string, unknown][]): Promise<RenderedPart> {
16
+ return Promise.resolve(render(Template, { props, context: new Map(context) }))
17
+ }
18
+
19
+ /**
20
+ * The markdown as one HTML string, for templates that place it with `{@html content}`. Plain HTML is kept as is, without Svelte's hydration markers.
21
+ */
22
+ export function content(nodes: MarkdownNode[], context: [string, unknown][]): string {
23
+ return nodes.map((node) => (node.type === 'html' ? node.html : render(Nodes, { props: { nodes: [node] }, context: new Map(context) }).body)).join('')
24
+ }
25
+
26
+ export function document(props: DocumentShellProps): string {
27
+ return `<!doctype html>${render(Document, { props }).body}`
28
+ }
29
+
30
+ export function margin(props: MarginShellProps): string {
31
+ return render(Margin, { props }).body
32
+ }
@@ -0,0 +1,66 @@
1
+ import "../../constants/template.constants.mjs";
2
+ import { dataUri, isInlinableAsset } from "./assets.mjs";
3
+ import { readFile, readdir } from "fs/promises";
4
+ import { basename, dirname, extname, join } from "path";
5
+ import { pathToFileURL } from "url";
6
+ import { existsSync } from "fs";
7
+ import { createRequire } from "module";
8
+ import { compile, preprocess } from "svelte/compiler";
9
+ //#region src/services/template/svelte.plugin.ts
10
+ /**
11
+ * `svelte` resolves to md-printer's copy, since templates can live outside any `node_modules`. `<style lang="postcss">` blocks are collected
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.
14
+ */
15
+ function sveltePlugin({ template, components, root, stylesheets }) {
16
+ const require = createRequire(root);
17
+ return {
18
+ name: "md-printer:svelte",
19
+ resolveId(id) {
20
+ if (id === "md-printer:template") return template;
21
+ if (id === "@cenk1cenk2/md-printer/components") return components;
22
+ if (id === "md-printer:template-components") return `\0${id}`;
23
+ if (id === "svelte" || id.startsWith("svelte/")) return {
24
+ id: pathToFileURL(require.resolve(id)).href,
25
+ external: true
26
+ };
27
+ return null;
28
+ },
29
+ async load(id) {
30
+ 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
+ return [
34
+ `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",
37
+ `export default { ...builtins, ${files.map((file, index) => `${JSON.stringify(basename(file, ".svelte"))}: $${index}`).join(", ")} }`
38
+ ].join("\n");
39
+ }
40
+ if (!isInlinableAsset(id)) return null;
41
+ return `export default ${JSON.stringify(dataUri(id, await readFile(id)))}`;
42
+ },
43
+ async transform(code, id) {
44
+ if (extname(id) !== ".svelte") return null;
45
+ const { code: source } = await preprocess(code, { style: ({ content, attributes }) => {
46
+ if (attributes.lang !== "postcss") return;
47
+ stylesheets.push({
48
+ file: id,
49
+ source: content
50
+ });
51
+ return { code: "" };
52
+ } }, { filename: id });
53
+ const { js } = compile(source, {
54
+ filename: id,
55
+ generate: "server",
56
+ css: "injected"
57
+ });
58
+ return {
59
+ code: js.code,
60
+ map: js.map
61
+ };
62
+ }
63
+ };
64
+ }
65
+ //#endregion
66
+ export { sveltePlugin };
@@ -0,0 +1,211 @@
1
+ import "../../constants/template.constants.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
4
+ import { inlineCss, inlineHtml } from "./assets.mjs";
5
+ import { sveltePlugin } from "./svelte.plugin.mjs";
6
+ import { Injectable } from "@nestjs/common";
7
+ import { dirname, isAbsolute, join, resolve } from "path";
8
+ import { FileSystemService } from "@cenk1cenk2/oclif-common";
9
+ import { fileURLToPath } from "url";
10
+ import { homedir } from "os";
11
+ import { compile } from "@tailwindcss/node";
12
+ import { Scanner } from "@tailwindcss/oxide";
13
+ import { kebabCase } from "change-case";
14
+ import enhancedResolve from "enhanced-resolve";
15
+ import { decodeHTML } from "entities";
16
+ import { readFileSync } from "fs";
17
+ import { createRequire } from "module";
18
+ import { rolldown } from "rolldown";
19
+ import { render } from "svelte/server";
20
+ //#region src/services/template/template.service.ts
21
+ var _TemplateService;
22
+ let TemplateService = class TemplateService {
23
+ static {
24
+ _TemplateService = this;
25
+ }
26
+ fs;
27
+ static ROOT = fileURLToPath(new URL("../../../", import.meta.url));
28
+ static COMPONENTS = fileURLToPath(new URL(`../../components/index.ts`, import.meta.url));
29
+ static ENTRY = fileURLToPath(new URL("./render.entry.ts", import.meta.url));
30
+ require = createRequire(_TemplateService.ROOT);
31
+ stylesheets = enhancedResolve.create.sync({
32
+ extensions: [".css"],
33
+ mainFields: ["style"],
34
+ conditionNames: ["style"]
35
+ });
36
+ modules = enhancedResolve.create.sync({
37
+ extensions: [
38
+ ".js",
39
+ ".json",
40
+ ".node",
41
+ ".ts"
42
+ ],
43
+ conditionNames: ["node", "import"]
44
+ });
45
+ constructor(fs) {
46
+ this.fs = fs;
47
+ }
48
+ resolve(template) {
49
+ const path = template.replace(/^~(?=\/|$)/, homedir());
50
+ const directory = isAbsolute(path) || path.startsWith(".") || /[\\/]/.test(path) ? resolve(path) : join(_TemplateService.ROOT, "templates", path);
51
+ if (!this.fs.exists(join(directory, "Template.svelte"))) throw new Error(`Template does not exist, expected Template.svelte in: ${directory}`);
52
+ return directory;
53
+ }
54
+ async settings(directory) {
55
+ const file = join(directory, "settings.json");
56
+ if (!this.fs.exists(file)) return {};
57
+ return JSON.parse(await this.fs.read(file));
58
+ }
59
+ async bundle(directory) {
60
+ const stylesheets = [];
61
+ const { module: entry, modules } = await this.compile(_TemplateService.ENTRY, join(directory, "Template.svelte"), stylesheets);
62
+ return {
63
+ entry,
64
+ modules,
65
+ stylesheets,
66
+ directives: new Map(Object.keys(entry.components).map((name) => [kebabCase(name), name]))
67
+ };
68
+ }
69
+ async page(file) {
70
+ const { module } = await this.compile(file, file, []);
71
+ return `<!doctype html>${render(module.default).body}`;
72
+ }
73
+ async render(directory, bundle, document, markdown, options) {
74
+ const context = [
75
+ ["md-printer:document", document],
76
+ ["md-printer:markdown", markdown],
77
+ ["md-printer:components", bundle.entry.components]
78
+ ];
79
+ const props = {
80
+ ...document,
81
+ content: bundle.entry.content(markdown, context)
82
+ };
83
+ const [body, header, footer] = await Promise.all([
84
+ "body",
85
+ "header",
86
+ "footer"
87
+ ].map((mode) => bundle.entry.part(props, [...context, ["md-printer:mode", mode]])));
88
+ const dependencies = [];
89
+ const cache = /* @__PURE__ */ new Map();
90
+ const css = `<style>${await this.css(directory, bundle, [
91
+ body,
92
+ header,
93
+ footer
94
+ ].map((part) => part.body).join(""), options.highlight, dependencies)}</style>`;
95
+ const extracted = body.head.match(/<title>([\s\S]*?)<\/title>/)?.[1];
96
+ const title = options.title ?? (extracted && decodeHTML(extracted)) ?? options.fallbackTitle;
97
+ const head = body.head.replace(/<title>[\s\S]*?<\/title>/, "");
98
+ const styles = [...head.matchAll(/<style[^>]*>[\s\S]*?<\/style>/g)].map(([style]) => style).join("") + css;
99
+ return {
100
+ html: bundle.entry.document({
101
+ lang: options.language,
102
+ title,
103
+ head: head + css,
104
+ body: body.body
105
+ }),
106
+ header: await this.margin(bundle, header, styles, directory, cache, dependencies),
107
+ footer: await this.margin(bundle, footer, styles, directory, cache, dependencies),
108
+ dependencies
109
+ };
110
+ }
111
+ async compile(input, template, stylesheets) {
112
+ const build = await rolldown({
113
+ input,
114
+ platform: "node",
115
+ logLevel: "silent",
116
+ plugins: [sveltePlugin({
117
+ template,
118
+ components: _TemplateService.COMPONENTS,
119
+ root: _TemplateService.ROOT,
120
+ stylesheets
121
+ })]
122
+ });
123
+ try {
124
+ const { output: [chunk] } = await build.generate({
125
+ format: "esm",
126
+ sourcemap: "inline"
127
+ });
128
+ return {
129
+ module: await import(`data:text/javascript;base64,${Buffer.from(chunk.code).toString("base64")}`),
130
+ modules: chunk.moduleIds.filter((id) => isAbsolute(id))
131
+ };
132
+ } finally {
133
+ await build.close();
134
+ }
135
+ }
136
+ async margin(bundle, part, styles, directory, cache, dependencies) {
137
+ if (!part.body.replace(/<!--[\s\S]*?-->/g, "").trim()) return;
138
+ const { content, assets } = await inlineHtml(bundle.entry.margin({
139
+ styles,
140
+ body: part.body
141
+ }), directory, {
142
+ remote: true,
143
+ cache
144
+ });
145
+ dependencies.push(...assets);
146
+ return content;
147
+ }
148
+ async css(directory, bundle, html, highlight, dependencies) {
149
+ const theme = `:root { ${readFileSync(this.highlightTheme(highlight), "utf8")} pre:has(> code.hljs) { padding: 0; background: transparent; } }`;
150
+ const compiler = await compile([theme, ...bundle.stylesheets.map(({ source }) => source)].join("\n"), {
151
+ base: directory,
152
+ from: bundle.stylesheets[0]?.file ?? join(directory, "Template.svelte"),
153
+ shouldRewriteUrls: true,
154
+ onDependency: (path) => dependencies.push(path),
155
+ customCssResolver: async (id, base) => this.resolveFrom(this.stylesheets, id, [
156
+ base,
157
+ directory,
158
+ _TemplateService.ROOT
159
+ ]),
160
+ customJsResolver: async (id, base) => this.resolveFrom(this.modules, id, [
161
+ base,
162
+ directory,
163
+ _TemplateService.ROOT
164
+ ])
165
+ });
166
+ const scanner = new Scanner({ sources: [
167
+ ...compiler.sources.map(({ base, pattern, negated }) => ({
168
+ base,
169
+ pattern,
170
+ negated
171
+ })),
172
+ {
173
+ base: directory,
174
+ pattern: "**/*",
175
+ negated: false
176
+ },
177
+ {
178
+ base: dirname(_TemplateService.COMPONENTS),
179
+ pattern: "**/*",
180
+ negated: false
181
+ }
182
+ ] });
183
+ const { content, assets } = await inlineCss(compiler.build([...scanner.scan(), ...scanner.scanFiles([{
184
+ content: html,
185
+ extension: "html"
186
+ }])]), directory);
187
+ dependencies.push(...assets);
188
+ return content;
189
+ }
190
+ /**
191
+ * Tries every base in turn, since a template directory usually has no `node_modules` and falls back to md-printer's own dependencies.
192
+ */
193
+ resolveFrom(resolver, id, bases) {
194
+ for (const base of bases) try {
195
+ const resolved = resolver(base, id);
196
+ if (resolved) return resolved;
197
+ } catch {
198
+ continue;
199
+ }
200
+ }
201
+ highlightTheme(name) {
202
+ try {
203
+ return this.require.resolve(`highlight.js/styles/${name}.css`);
204
+ } catch {
205
+ throw new Error(`Unknown highlight style "${name}", see the files in ${dirname(this.require.resolve("highlight.js/styles/github.css"))}.`);
206
+ }
207
+ }
208
+ };
209
+ TemplateService = _TemplateService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof FileSystemService === "undefined" ? Object : FileSystemService])], TemplateService);
210
+ //#endregion
211
+ export { TemplateService };