@cenk1cenk2/md-printer 3.3.0 → 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.
- package/README.md +181 -57
- package/dist/commands/index.mjs +2 -0
- package/dist/components/Localized.svelte +18 -0
- package/dist/components/SignatureBlock.svelte +8 -46
- package/dist/components/Translate.svelte +15 -0
- package/dist/components/extension.ts +8 -0
- package/dist/components/index.ts +3 -0
- package/dist/constants/index.mjs +2 -2
- package/dist/constants/template.constants.mjs +24 -1
- package/dist/modules/md-printer/md-printer.handler.mjs +2 -0
- package/dist/modules/md-printer/md-printer.schema.mjs +12 -1
- package/dist/services/i18n.service.mjs +4 -2
- package/dist/services/printer.service.mjs +22 -5
- package/dist/services/session/session.service.mjs +9 -9
- package/dist/services/template/render.entry.ts +5 -1
- package/dist/services/template/svelte.plugin.mjs +12 -7
- package/dist/services/template/template.schema.mjs +27 -0
- package/dist/services/template/template.service.mjs +107 -25
- package/oclif.manifest.json +16 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ Markdown files take YAML front-matter. For YAML input the same keys are top-leve
|
|
|
92
92
|
|
|
93
93
|
| Key | Description |
|
|
94
94
|
| ----------------- | --------------------------------------------------------------------------------------------- |
|
|
95
|
-
| `template` | Built-in template name
|
|
95
|
+
| `template` | Built-in template name, path to a template directory or an object, see [Template sources](#template-sources). |
|
|
96
96
|
| `dest` | Output file, relative to the current directory, not to the input file. |
|
|
97
97
|
| `outputFiletype` | `pdf` (default) or `html`. |
|
|
98
98
|
| `language` | Language for template translations. |
|
|
@@ -177,7 +177,7 @@ md-printer document.md out.pdf --preview --no-open
|
|
|
177
177
|
|
|
178
178
|
### Built-in templates
|
|
179
179
|
|
|
180
|
-
Every built-in template is a package of its own, `@cenk1cenk2/md-printer-template-<name
|
|
180
|
+
Every built-in template is a package of its own, `@cenk1cenk2/md-printer-template-<name>`. md-printer depends on all of them, so they install with it, and each one brings the fonts, icons and stylesheets it uses as its own dependencies. `default`, `kilic.dev` and `mail` are published from the `templates/` directory of this repository, `cv` from https://gitlab.kilic.dev/cenk/cv and `invoice` from https://gitlab.kilic.dev/cenk/invoices.
|
|
181
181
|
|
|
182
182
|
| Name | Description |
|
|
183
183
|
| ----------- | -------------------------------------------------------------------------------------------------------------- |
|
|
@@ -187,18 +187,83 @@ Every built-in template is a package of its own, `@cenk1cenk2/md-printer-templat
|
|
|
187
187
|
| `cv` | CV in the table layout of a classic Word CV, from front-matter (`name`, `suffix`, `title`, `tagline`, `photo`) and Markdown sections placed with the `::section`, `:::details` / `::detail` / `:::pair`, `:::entry`, `::::group` / `:::course`, `::skill`, `:::note` and `:::languages` / `::language` components. The PDF is tagged and has an outline, so screening tools read it as text. See `test/cv.md`. |
|
|
188
188
|
| `mail` | Plain content with no margin and Tailwind typography, meant for HTML output. |
|
|
189
189
|
|
|
190
|
+
### Template sources
|
|
191
|
+
|
|
192
|
+
`-t` and a string `template` in front-matter take a built-in name or a path. A value without a path separator is a built-in name, anything with `/` or starting with `.` or `~` is a path, relative to the current directory.
|
|
193
|
+
|
|
194
|
+
`template` in front-matter can also be an object selecting the source by `type`. This is the only way to use a template from an npm package.
|
|
195
|
+
|
|
196
|
+
```yaml
|
|
197
|
+
---
|
|
198
|
+
template:
|
|
199
|
+
type: package
|
|
200
|
+
name: '@cenk1cenk2/md-printer-template-cv'
|
|
201
|
+
---
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
| `type` | Keys | Description |
|
|
205
|
+
| ----------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
206
|
+
| `builtin` | `name` | The package `@cenk1cenk2/md-printer-template-<name>` from md-printer's own dependencies. |
|
|
207
|
+
| `package` | `name`, `directory?` | An npm package, resolved from the directory of the input file (its `node_modules`, walking up like Node does), then from the current directory, then from md-printer's own dependencies. The package directory is the template directory. `directory` selects a subdirectory of the package instead, and may not leave the package, symlinks included. |
|
|
208
|
+
| `directory` | `path` | A template directory, relative to the directory of the input file. |
|
|
209
|
+
|
|
210
|
+
The same on the command line, where flags win over front-matter. `--template-type` says how to read `-t`, and `--template-directory` is the `directory` of a package. A `--template-type directory` path is relative to the input file too. Without `--template-type`, `-t` is a built-in name or a path as above.
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
md-printer document.md --template-type package -t @cenk1cenk2/md-printer-template-cv
|
|
214
|
+
md-printer document.md --template-type package -t my-templates --template-directory letters
|
|
215
|
+
md-printer document.md --template-type builtin -t cv
|
|
216
|
+
md-printer document.md --template-type directory -t ./template
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
An unknown `type`, a missing or unknown key, or a `package` that is not installed fails the run. A package has to allow access to its `package.json`, so one with an `exports` map lists `./package.json` there.
|
|
220
|
+
|
|
190
221
|
### Authoring a template
|
|
191
222
|
|
|
192
|
-
A template is a directory. Pass it with `-t ./path
|
|
223
|
+
A template is a directory. Pass it with `-t ./path`, `template: ./path` or as one of the other [template sources](#template-sources). Its Svelte files and its TypeScript are kept apart, so ESLint, Prettier and tsconfig can target each group:
|
|
193
224
|
|
|
194
225
|
```
|
|
195
226
|
my-template/
|
|
196
|
-
|
|
197
|
-
settings.json
|
|
198
|
-
i18n/
|
|
199
|
-
|
|
227
|
+
package.json optional, see below
|
|
228
|
+
settings.json optional, always at the root
|
|
229
|
+
i18n/ optional, en.yaml, de.json, ...
|
|
230
|
+
template/
|
|
231
|
+
Template.svelte required
|
|
232
|
+
components/*.svelte optional, placed from markdown, see Markdown components
|
|
233
|
+
src/
|
|
234
|
+
extension.ts optional, see Extension module
|
|
235
|
+
*.ts optional, plain modules
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`files` in `settings.json` moves any of these. Every key is optional and defaults to the layout above:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"files": {
|
|
243
|
+
"template": "template/Template.svelte",
|
|
244
|
+
"components": "template/components",
|
|
245
|
+
"extension": "src/extension.ts",
|
|
246
|
+
"i18n": "i18n"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
200
249
|
```
|
|
201
250
|
|
|
251
|
+
- `components`, `extension` and `i18n` also take an array, read in order: a component of a later folder replaces one of the same name, an export of a later extension module replaces one of the same name, and a key of a later i18n folder replaces the same key while nested keys merge. Use this to share components or translations between templates:
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"files": {
|
|
256
|
+
"components": ["template/shared", "template/components"],
|
|
257
|
+
"extension": ["src/format.ts", "src/extension.ts"],
|
|
258
|
+
"i18n": ["i18n/shared", "i18n"]
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
- Paths are relative to the template directory. A path leaving it, a path that does not exist and an unknown key each fail the build.
|
|
264
|
+
- A template written for 3.x keeps working: for each key `files` does not set, when the default location is missing, the root location is used if it exists, that is `Template.svelte`, `components/` and `extension.*`. A path set in `files` never falls back.
|
|
265
|
+
- A package keeps what it does not publish out with an `.npmignore`, as the built-in templates do, for example `release.config.cjs` and `CHANGELOG.md`.
|
|
266
|
+
|
|
202
267
|
`Template.svelte` is a Svelte 5 component receiving these props:
|
|
203
268
|
|
|
204
269
|
| Prop | Description |
|
|
@@ -208,6 +273,7 @@ my-template/
|
|
|
208
273
|
| `t` | i18next translate function, loaded from `i18n/<language>.(yaml\|yml\|json)`. |
|
|
209
274
|
| `language` | The resolved language. |
|
|
210
275
|
| `signature` | `{ date, name?, issuer?, serial? }` while signing is enabled, otherwise `undefined`. |
|
|
276
|
+
| `extension` | The named exports of the [extension module](#extension-module), empty without one. |
|
|
211
277
|
|
|
212
278
|
Set the document title with `<svelte:head><title>...</title></svelte:head>`.
|
|
213
279
|
|
|
@@ -227,6 +293,31 @@ Images are imported, which inlines them as data URIs:
|
|
|
227
293
|
<img src={logo} alt="logo" />
|
|
228
294
|
```
|
|
229
295
|
|
|
296
|
+
#### Extension module
|
|
297
|
+
|
|
298
|
+
A template may contain one extension module, `src/extension.ts`, `.mts`, `.cts`, `.js`, `.mjs` or `.cjs`, so the template can bring its own functions. More than one at the default location fails the build, naming the files; to combine several modules, list them in `files.extension`.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
// src/extension.ts
|
|
302
|
+
export function money(value: number): string {
|
|
303
|
+
return `${value.toFixed(2)} EUR`
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
It is compiled in the same bundle as the template, so TypeScript works and its imports resolve like those of the template's own modules, from its `node_modules` first. Its named exports are available as the `extension` prop of `Template.svelte`, through the `md-printer:extension` context with `getContext`, and in any component with the `getExtension` helper:
|
|
308
|
+
|
|
309
|
+
```svelte
|
|
310
|
+
<script lang="ts">
|
|
311
|
+
import { getExtension } from '@cenk1cenk2/md-printer/components'
|
|
312
|
+
|
|
313
|
+
const { money } = getExtension<typeof import('../../src/extension.js')>()
|
|
314
|
+
</script>
|
|
315
|
+
|
|
316
|
+
<span>{money(12)}</span>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The default export is left out. An extension only exposes its exports, md-printer calls none of them by itself. `--preview` rebuilds when the module changes, is added or is removed.
|
|
320
|
+
|
|
230
321
|
#### Shared components
|
|
231
322
|
|
|
232
323
|
Import from `@cenk1cenk2/md-printer/components`:
|
|
@@ -240,7 +331,9 @@ Import from `@cenk1cenk2/md-printer/components`:
|
|
|
240
331
|
| `TotalPages` | Total page count, filled by Chrome. Header and footer only. |
|
|
241
332
|
| `Title` | Document title, filled by Chrome. Header and footer only. |
|
|
242
333
|
| `Date` | Print date, filled by Chrome. Header and footer only. |
|
|
243
|
-
| `
|
|
334
|
+
| `Localized` | Text for the language of the document from props named after languages, `<Localized en="Invoice" de="Rechnung" />` or `:localized{en="Invoice" de="Rechnung"}`, for words that need no entry in `i18n/`. Takes the language, then its base language (`de` for `de-AT`), then `fallback`, otherwise nothing. In a template a prop can also be a snippet for markup. Plain text without a wrapper element. |
|
|
335
|
+
| `Translate` | Text translated with the `i18n/` directory of the template, `<Translate key="present" />` or `::translate{key="present"}`, inline as `:translate{key="present"}`. Plain text without a wrapper element. Optional `fallback` for a missing key, otherwise the key itself is printed. |
|
|
336
|
+
| `SignatureBlock` | Hands the signature, `{ date, name?, issuer?, serial? }`, to its `children` snippet, so the template draws the visible block with its own markup and styles. Takes the `signature` prop, or as `::signature-block` the signature of the document. Renders nothing without a signature or without `children`, so a template gives `::signature-block` its markup with its own `template/components/SignatureBlock.svelte`, see below. |
|
|
244
337
|
|
|
245
338
|
```svelte
|
|
246
339
|
<script>
|
|
@@ -266,10 +359,32 @@ Import from `@cenk1cenk2/md-printer/components`:
|
|
|
266
359
|
|
|
267
360
|
#### Markdown components
|
|
268
361
|
|
|
269
|
-
Every `*.svelte` file in the template's `components/` directory can be placed from markdown, see [Components](#components). The shared components can be placed the same way, for example `::page-break` or `::signature-block`, except `Markdown`.
|
|
362
|
+
Every `*.svelte` file in the template's `template/components/` directory can be placed from markdown, see [Components](#components). The shared components can be placed the same way, for example `::page-break` or `::signature-block`, except `Markdown`.
|
|
363
|
+
|
|
364
|
+
A template replaces or extends any shared component, `SignatureBlock` and `Translate` included, with a file of the same name in `template/components/`:
|
|
365
|
+
|
|
366
|
+
- **Replace:** `template/components/SignatureBlock.svelte` is what `::signature-block` renders from then on. This is about directives: a component that imports `SignatureBlock` from `@cenk1cenk2/md-printer/components` keeps getting the original, so import `./components/SignatureBlock.svelte` to use the replacement in the template itself.
|
|
367
|
+
- **Extend:** that import always reaches md-printer's original, also from a file with the same name, so a component can wrap it:
|
|
368
|
+
|
|
369
|
+
```svelte
|
|
370
|
+
<!-- template/components/SignatureBlock.svelte -->
|
|
371
|
+
<script>
|
|
372
|
+
import { SignatureBlock } from '@cenk1cenk2/md-printer/components'
|
|
373
|
+
|
|
374
|
+
let props = $props()
|
|
375
|
+
</script>
|
|
376
|
+
|
|
377
|
+
<SignatureBlock {...props}>
|
|
378
|
+
{#snippet children(signature)}
|
|
379
|
+
<section class="my-8">
|
|
380
|
+
<strong>Digitally signed</strong> by {signature.name} on {signature.date}
|
|
381
|
+
</section>
|
|
382
|
+
{/snippet}
|
|
383
|
+
</SignatureBlock>
|
|
384
|
+
```
|
|
270
385
|
|
|
271
386
|
```svelte
|
|
272
|
-
<!-- components/Callout.svelte -->
|
|
387
|
+
<!-- template/components/Callout.svelte -->
|
|
273
388
|
<script>
|
|
274
389
|
import { getContext } from 'svelte'
|
|
275
390
|
|
|
@@ -309,7 +424,7 @@ md-printer invoice.yaml -t invoice --sign --sign-certificate ~/id-austria.pem
|
|
|
309
424
|
- Signing only works for PDF output and is skipped with `--preview`.
|
|
310
425
|
- The signing session has to run in your browser. md-printer opens a temporary local page in an app window of the discovered browser, using your normal profile (or with `xdg-open` when none is found), you log in with ID Austria in the A-Trust page, and paste the resulting signature (starts with `MII`) back into the local page. A paste that is cut short is refused on the page, so you can copy it again. It waits until the signature arrives, press Ctrl+C to abort.
|
|
311
426
|
- The returned signature is always verified before it is embedded: its digest must match this document and it must be cryptographically valid. The result is printed with the signer name, serial and issuer. A failed verification fails the run, and with `--log-level debug` the returned signature is written next to the output as `<output>.cms.der`.
|
|
312
|
-
- With a certificate, the signer name, issuer and serial are passed to the template as `signature`, so
|
|
427
|
+
- With a certificate, the signer name, issuer and serial are passed to the template as `signature`, so the template can show who signed and when through `SignatureBlock`. The certificate must be valid at signing time, and the returned signature is rejected if it was not made with it. Without a certificate only the date is available.
|
|
313
428
|
- Verify the result at https://www.signaturpruefung.gv.at.
|
|
314
429
|
|
|
315
430
|
## Migrating from 2.x
|
|
@@ -334,58 +449,67 @@ Renders a markdown or yaml file through a Svelte template into a PDF or HTML doc
|
|
|
334
449
|
USAGE
|
|
335
450
|
$ md-printer [FILE] [OUTPUT] [--log-level
|
|
336
451
|
silent|direct|fatal|error|warn|info|verbose|debug|trace] [-I] [-f
|
|
337
|
-
md|yaml|yml] [-F pdf|html] [-O] [-t <value>] [-
|
|
338
|
-
<value>] [
|
|
339
|
-
|
|
340
|
-
<value>]
|
|
452
|
+
md|yaml|yml] [-F pdf|html] [-O] [-t <value>] [--template-type <value>]
|
|
453
|
+
[--template-directory <value>] [-T <value>] [-l <value>] [-b <value>]
|
|
454
|
+
[--continuous] [-p] [--host 127.0.0.1|localhost|::1] [--port <value>]
|
|
455
|
+
[--open] [--sign] [--sign-provider a-trust] [--sign-certificate <value>]
|
|
341
456
|
|
|
342
457
|
ARGUMENTS
|
|
343
458
|
[FILE] Markdown or yaml file to render.
|
|
344
459
|
[OUTPUT] Output file. Overwrites the one defined in front-matter.
|
|
345
460
|
|
|
346
461
|
FLAGS
|
|
347
|
-
-F, --output-filetype=<option>
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
-I, --stdin
|
|
351
|
-
-O, --stdout
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
--
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
--
|
|
381
|
-
|
|
382
|
-
--
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
--sign-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
462
|
+
-F, --output-filetype=<option> Output file type, defaults to pdf.
|
|
463
|
+
Overwrites the one defined in front-matter.
|
|
464
|
+
<options: pdf|html>
|
|
465
|
+
-I, --stdin Read the input from stdin.
|
|
466
|
+
-O, --stdout Write the output to stdout instead of a
|
|
467
|
+
file.
|
|
468
|
+
-T, --title=<value> Document title. Overwrites the one defined
|
|
469
|
+
in front-matter or the template.
|
|
470
|
+
-b, --browser=<value> Browser executable used for printing.
|
|
471
|
+
Discovered automatically when omitted
|
|
472
|
+
(Brave, Chrome, Chromium, Edge).
|
|
473
|
+
-f, --input-filetype=<option> Input file type. Detected from the file
|
|
474
|
+
extension, markdown for stdin and unknown
|
|
475
|
+
extensions.
|
|
476
|
+
<options: md|yaml|yml>
|
|
477
|
+
-l, --language=<value> Language for template translations.
|
|
478
|
+
Overwrites the one defined in front-matter
|
|
479
|
+
and template settings.
|
|
480
|
+
-p, --preview Rebuild whenever the input, the template or
|
|
481
|
+
its assets change, show the HTML and the PDF
|
|
482
|
+
in a live preview and rewrite the output
|
|
483
|
+
file when there is one.
|
|
484
|
+
-t, --template=<value> Built-in template name or path to a template
|
|
485
|
+
directory, defaults to "default", read as
|
|
486
|
+
--template-type when given. Overwrites the
|
|
487
|
+
one defined in front-matter.
|
|
488
|
+
--continuous Render the PDF as a single continuous page
|
|
489
|
+
without page breaks.
|
|
490
|
+
--host=<option> [default: 127.0.0.1] Loopback host for the
|
|
491
|
+
preview server, it serves the files next to
|
|
492
|
+
the input and is never exposed to the
|
|
493
|
+
network.
|
|
494
|
+
<options: 127.0.0.1|localhost|::1>
|
|
495
|
+
--[no-]open Open the preview window, --no-open keeps
|
|
496
|
+
rebuilding and writing without one.
|
|
497
|
+
--port=<value> Port for the preview server, random when 0.
|
|
498
|
+
--sign Sign the PDF with a qualified electronic
|
|
499
|
+
signature through ID Austria (A-Trust).
|
|
500
|
+
--sign-certificate=<value> Signer certificate (DER or PEM) used for the
|
|
501
|
+
visible signature block and to verify the
|
|
502
|
+
returned signature.
|
|
503
|
+
--sign-provider=<option> Signature provider, defaults to a-trust.
|
|
504
|
+
Overwrites the one defined in front-matter
|
|
505
|
+
and template settings.
|
|
506
|
+
<options: a-trust>
|
|
507
|
+
--template-directory=<value> Directory inside the package to use as the
|
|
508
|
+
template, only valid with --template-type
|
|
509
|
+
package.
|
|
510
|
+
--template-type=<value> How to read --template, one of builtin,
|
|
511
|
+
package or directory, like the type of the
|
|
512
|
+
object form in front-matter.
|
|
389
513
|
|
|
390
514
|
CLI FLAGS
|
|
391
515
|
--log-level=<option> [default: INFO, env: LOG_LEVEL] Set the log level of the
|
package/dist/commands/index.mjs
CHANGED
|
@@ -12,6 +12,8 @@ let MDPrinter = class MDPrinter extends Command {
|
|
|
12
12
|
["output-filetype"]: Flags.string({ char: "F" }),
|
|
13
13
|
stdout: Flags.boolean({ char: "O" }),
|
|
14
14
|
template: Flags.string({ char: "t" }),
|
|
15
|
+
["template-type"]: Flags.string(),
|
|
16
|
+
["template-directory"]: Flags.string(),
|
|
15
17
|
title: Flags.string({ char: "T" }),
|
|
16
18
|
language: Flags.string({ char: "l" }),
|
|
17
19
|
browser: Flags.string({ char: "b" }),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Text picked by the language of the document from props named after languages, such as `en` and `de`, for words that need no entry in `i18n/`. Takes the
|
|
4
|
+
language as it is, then its base language, then `fallback`. In a template a prop can also be a snippet. Plain text without a wrapper element, like Translate.
|
|
5
|
+
-->
|
|
6
|
+
<script>
|
|
7
|
+
import { getContext } from 'svelte'
|
|
8
|
+
|
|
9
|
+
/** @type {{ fallback?: string | import('svelte').Snippet } & Record<string, string | import('svelte').Snippet | undefined>} */
|
|
10
|
+
let { fallback, ...languages } = $props()
|
|
11
|
+
|
|
12
|
+
/** @type {import('../interfaces/template.interface.js').DocumentContext} */
|
|
13
|
+
const { language } = getContext('md-printer:document')
|
|
14
|
+
|
|
15
|
+
const value = $derived(languages[language] ?? languages[language.split('-')[0]] ?? fallback)
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
{#if typeof value === 'function'}{@render value()}{:else}{value ?? ''}{/if}
|
|
@@ -1,56 +1,18 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@component
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Passes the signature of a digitally signed document to the `children` snippet, which draws the block, so the template owns its markup and styles. The
|
|
4
|
+
signature comes from the `signature` prop or, as a directive, from the document context. Renders nothing without a signature or without `children`.
|
|
5
5
|
-->
|
|
6
6
|
<script>
|
|
7
|
+
import { getContext } from 'svelte'
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* @type {{
|
|
9
|
-
* signature
|
|
10
|
-
*
|
|
11
|
-
* class?: string
|
|
11
|
+
* signature?: import('../interfaces/signature.interface.js').MdPrinterSignature,
|
|
12
|
+
* children?: import('svelte').Snippet<[import('../interfaces/signature.interface.js').MdPrinterSignature]>
|
|
12
13
|
* }}
|
|
13
14
|
*/
|
|
14
|
-
let { signature
|
|
15
|
-
|
|
16
|
-
const text = $derived({
|
|
17
|
-
title: 'Digitally signed',
|
|
18
|
-
by: 'Signed by',
|
|
19
|
-
date: 'Date',
|
|
20
|
-
issuer: 'Certificate',
|
|
21
|
-
verify: 'This document carries a qualified electronic signature. Verify it at',
|
|
22
|
-
...labels
|
|
23
|
-
})
|
|
15
|
+
let { signature = getContext('md-printer:document').signature, children } = $props()
|
|
24
16
|
</script>
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
<div class="flex items-center gap-3 px-5 py-3 bg-gray-900 text-white">
|
|
28
|
-
<svg class="w-5 h-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
29
|
-
<path d="M12 3l7 3v6c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6l7-3z" />
|
|
30
|
-
<path d="M9 12l2 2 4-4" />
|
|
31
|
-
</svg>
|
|
32
|
-
<span class="font-extrabold uppercase tracking-wider">{text.title}</span>
|
|
33
|
-
</div>
|
|
34
|
-
<div class="flex flex-col px-5 py-2">
|
|
35
|
-
{#if signature.name}
|
|
36
|
-
<div class="flex justify-between gap-4 py-2">
|
|
37
|
-
<span class="font-semibold text-gray-400">{text.by}</span>
|
|
38
|
-
<span class="font-semibold text-right">{signature.name}</span>
|
|
39
|
-
</div>
|
|
40
|
-
{/if}
|
|
41
|
-
<div class="flex justify-between gap-4 py-2 border-t border-t-gray-100/30">
|
|
42
|
-
<span class="font-semibold text-gray-400">{text.date}</span>
|
|
43
|
-
<span class="text-right">{signature.date}</span>
|
|
44
|
-
</div>
|
|
45
|
-
{#if signature.issuer}
|
|
46
|
-
<div class="flex justify-between gap-4 py-2 border-t border-t-gray-100/30">
|
|
47
|
-
<span class="font-semibold text-gray-400">{text.issuer}</span>
|
|
48
|
-
<span class="text-right text-[0.6rem]">{signature.issuer} · {signature.serial}</span>
|
|
49
|
-
</div>
|
|
50
|
-
{/if}
|
|
51
|
-
</div>
|
|
52
|
-
<p class="px-5 py-3 bg-gray-50 text-[0.6rem] text-gray-400 leading-snug">
|
|
53
|
-
{text.verify}
|
|
54
|
-
<a href="https://www.signaturpruefung.gv.at" class="font-semibold text-gray-900 underline">signaturpruefung.gv.at</a>
|
|
55
|
-
</p>
|
|
56
|
-
</div>
|
|
18
|
+
{#if signature && children}{@render children(signature)}{/if}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Text translated with the `i18n/` directory of the template. Plain text without a wrapper element, so it flows inside paragraphs and table cells.
|
|
4
|
+
-->
|
|
5
|
+
<script>
|
|
6
|
+
import { getContext } from 'svelte'
|
|
7
|
+
|
|
8
|
+
/** @type {{ key: string, fallback?: string }} */
|
|
9
|
+
let { key, fallback } = $props()
|
|
10
|
+
|
|
11
|
+
/** @type {import('../interfaces/template.interface.js').DocumentContext} */
|
|
12
|
+
const { t } = getContext('md-printer:document')
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{t(key, { defaultValue: fallback ?? key })}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getContext } from 'svelte'
|
|
2
|
+
|
|
3
|
+
import type { TemplateExtension } from '../interfaces/template.interface.js'
|
|
4
|
+
|
|
5
|
+
/** The named exports of the extension module of the template, empty without one. */
|
|
6
|
+
export function getExtension<T extends TemplateExtension = TemplateExtension>(): T {
|
|
7
|
+
return getContext('md-printer:extension')
|
|
8
|
+
}
|
package/dist/components/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export { getExtension } from './extension.js'
|
|
1
2
|
export { default as Date } from './Date.svelte'
|
|
3
|
+
export { default as Localized } from './Localized.svelte'
|
|
2
4
|
export { default as Markdown } from './Markdown.svelte'
|
|
3
5
|
export { default as Page } from './Page.svelte'
|
|
4
6
|
export { default as PageBreak } from './PageBreak.svelte'
|
|
@@ -6,3 +8,4 @@ export { default as PageNumber } from './PageNumber.svelte'
|
|
|
6
8
|
export { default as SignatureBlock } from './SignatureBlock.svelte'
|
|
7
9
|
export { default as Title } from './Title.svelte'
|
|
8
10
|
export { default as TotalPages } from './TotalPages.svelte'
|
|
11
|
+
export { default as Translate } from './Translate.svelte'
|
package/dist/constants/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AttachmentPosition } from "./attachment.constants.mjs";
|
|
2
|
-
import { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TEMPLATE_PACKAGE_PREFIX, TemplateFiles } from "./template.constants.mjs";
|
|
2
|
+
import { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TEMPLATE_EXTENSION_FILES, TEMPLATE_LAYOUT, TEMPLATE_PACKAGE_PREFIX, TemplateFiles, TemplateSourceType } from "./template.constants.mjs";
|
|
3
3
|
import { InputFileType, OutputFileType } from "./file.constants.mjs";
|
|
4
4
|
import { PreviewHost } from "./preview.constants.mjs";
|
|
5
5
|
import { SignatureProvider } from "./signature.constants.mjs";
|
|
6
|
-
export { AttachmentPosition, ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, InputFileType, MediaType, ModuleSpecifier, OutputFileType, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, PreviewHost, RenderMode, SignatureProvider, TAILWIND_STYLE_LANG, TEMPLATE_PACKAGE_PREFIX, TemplateFiles };
|
|
6
|
+
export { AttachmentPosition, ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, InputFileType, MediaType, ModuleSpecifier, OutputFileType, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, PreviewHost, RenderMode, SignatureProvider, TAILWIND_STYLE_LANG, TEMPLATE_EXTENSION_FILES, TEMPLATE_LAYOUT, TEMPLATE_PACKAGE_PREFIX, TemplateFiles, TemplateSourceType };
|
|
@@ -4,6 +4,21 @@ let TemplateFiles = /* @__PURE__ */ function(TemplateFiles) {
|
|
|
4
4
|
TemplateFiles["SETTINGS"] = "settings.json";
|
|
5
5
|
return TemplateFiles;
|
|
6
6
|
}({});
|
|
7
|
+
/** Where a template keeps its files unless `files` in its settings says otherwise, the root locations of 3.x come last. */
|
|
8
|
+
const TEMPLATE_LAYOUT = {
|
|
9
|
+
template: ["template/Template.svelte", "Template.svelte"],
|
|
10
|
+
components: ["template/components", "components"],
|
|
11
|
+
extension: ["src", "."],
|
|
12
|
+
i18n: ["i18n"]
|
|
13
|
+
};
|
|
14
|
+
const TEMPLATE_EXTENSION_FILES = [
|
|
15
|
+
"extension.ts",
|
|
16
|
+
"extension.mts",
|
|
17
|
+
"extension.cts",
|
|
18
|
+
"extension.js",
|
|
19
|
+
"extension.mjs",
|
|
20
|
+
"extension.cjs"
|
|
21
|
+
];
|
|
7
22
|
let RenderMode = /* @__PURE__ */ function(RenderMode) {
|
|
8
23
|
RenderMode["BODY"] = "body";
|
|
9
24
|
RenderMode["HEADER"] = "header";
|
|
@@ -17,6 +32,12 @@ let MediaType = /* @__PURE__ */ function(MediaType) {
|
|
|
17
32
|
}({});
|
|
18
33
|
/** Built-in templates are packages named with this prefix, which md-printer depends on. */
|
|
19
34
|
const TEMPLATE_PACKAGE_PREFIX = "@cenk1cenk2/md-printer-template-";
|
|
35
|
+
let TemplateSourceType = /* @__PURE__ */ function(TemplateSourceType) {
|
|
36
|
+
TemplateSourceType["BUILTIN"] = "builtin";
|
|
37
|
+
TemplateSourceType["PACKAGE"] = "package";
|
|
38
|
+
TemplateSourceType["DIRECTORY"] = "directory";
|
|
39
|
+
return TemplateSourceType;
|
|
40
|
+
}({});
|
|
20
41
|
let Directory = /* @__PURE__ */ function(Directory) {
|
|
21
42
|
Directory["COMPONENTS"] = "components";
|
|
22
43
|
Directory["I18N"] = "i18n";
|
|
@@ -26,6 +47,7 @@ let ModuleSpecifier = /* @__PURE__ */ function(ModuleSpecifier) {
|
|
|
26
47
|
ModuleSpecifier["TEMPLATE"] = "md-printer:template";
|
|
27
48
|
ModuleSpecifier["COMPONENTS"] = "@cenk1cenk2/md-printer/components";
|
|
28
49
|
ModuleSpecifier["TEMPLATE_COMPONENTS"] = "md-printer:template-components";
|
|
50
|
+
ModuleSpecifier["EXTENSION"] = "md-printer:extension";
|
|
29
51
|
return ModuleSpecifier;
|
|
30
52
|
}({});
|
|
31
53
|
let ContextKey = /* @__PURE__ */ function(ContextKey) {
|
|
@@ -33,6 +55,7 @@ let ContextKey = /* @__PURE__ */ function(ContextKey) {
|
|
|
33
55
|
ContextKey["DOCUMENT"] = "md-printer:document";
|
|
34
56
|
ContextKey["MARKDOWN"] = "md-printer:markdown";
|
|
35
57
|
ContextKey["COMPONENTS"] = "md-printer:components";
|
|
58
|
+
ContextKey["EXTENSION"] = "md-printer:extension";
|
|
36
59
|
return ContextKey;
|
|
37
60
|
}({});
|
|
38
61
|
const TAILWIND_STYLE_LANG = "postcss";
|
|
@@ -57,4 +80,4 @@ const DEFAULT_PDF_OPTIONS = {
|
|
|
57
80
|
left: "20mm"
|
|
58
81
|
}
|
|
59
82
|
};
|
|
60
|
-
export { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TEMPLATE_PACKAGE_PREFIX, TemplateFiles };
|
|
83
|
+
export { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TEMPLATE_EXTENSION_FILES, TEMPLATE_LAYOUT, TEMPLATE_PACKAGE_PREFIX, TemplateFiles, TemplateSourceType };
|
|
@@ -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,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."
|
|
@@ -14,14 +14,16 @@ let I18nService = class I18nService {
|
|
|
14
14
|
this.fs = fs;
|
|
15
15
|
this.parser = parser;
|
|
16
16
|
}
|
|
17
|
-
|
|
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:
|
|
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) {
|
|
@@ -5,6 +5,7 @@ import { RendererService } from "./renderer/renderer.service.mjs";
|
|
|
5
5
|
import { SignatureVerificationError } from "./signature/cms.mjs";
|
|
6
6
|
import { SignatureService } from "./signature/signature.service.mjs";
|
|
7
7
|
import { inlineHtml } from "./template/assets.mjs";
|
|
8
|
+
import { TemplateSourceSchema, issues } from "./template/template.schema.mjs";
|
|
8
9
|
import { TemplateService } from "./template/template.service.mjs";
|
|
9
10
|
import { AttachmentPosition } from "../constants/attachment.constants.mjs";
|
|
10
11
|
import { DEFAULT_PDF_OPTIONS } from "../constants/template.constants.mjs";
|
|
@@ -12,7 +13,7 @@ import { OutputFileType } from "../constants/file.constants.mjs";
|
|
|
12
13
|
import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
|
|
13
14
|
import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
|
|
14
15
|
import { Injectable } from "@nestjs/common";
|
|
15
|
-
import { basename, dirname, extname,
|
|
16
|
+
import { basename, dirname, extname, resolve } from "path";
|
|
16
17
|
import { ConfigService, FileSystemService, LoggerService } from "@cenk1cenk2/oclif-common";
|
|
17
18
|
import { cloneDeep, mergeWith, uniq } from "es-toolkit";
|
|
18
19
|
import { parse } from "yaml";
|
|
@@ -65,13 +66,14 @@ let PrinterService = class PrinterService {
|
|
|
65
66
|
}
|
|
66
67
|
async options(flags, input) {
|
|
67
68
|
const { data } = input;
|
|
68
|
-
const
|
|
69
|
-
const settings = await this.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);
|
|
70
71
|
const output = flags.outputFiletype ?? data.outputFiletype ?? "pdf";
|
|
71
72
|
if (!Object.values(OutputFileType).includes(output)) throw new Error(`Output file type is not supported: ${output}, use one of ${Object.values(OutputFileType).join(", ")}.`);
|
|
72
73
|
const dest = flags.stdout ? void 0 : flags.output ?? data.dest ?? (input.file ? `${basename(input.file, extname(input.file))}.${output}` : void 0);
|
|
73
74
|
return {
|
|
74
75
|
template,
|
|
76
|
+
files,
|
|
75
77
|
output,
|
|
76
78
|
dest: dest && resolve(dest),
|
|
77
79
|
title: flags.title ?? data.documentTitle,
|
|
@@ -87,7 +89,7 @@ let PrinterService = class PrinterService {
|
|
|
87
89
|
};
|
|
88
90
|
}
|
|
89
91
|
bundle(options) {
|
|
90
|
-
return this.template.bundle(options.
|
|
92
|
+
return this.template.bundle(options.files);
|
|
91
93
|
}
|
|
92
94
|
/**
|
|
93
95
|
* Resolves the signing time and the visible signature description, which the template renders before the document is signed.
|
|
@@ -104,7 +106,7 @@ let PrinterService = class PrinterService {
|
|
|
104
106
|
const { input, options } = ctx;
|
|
105
107
|
const document = await this.template.render(options.template, bundle, {
|
|
106
108
|
data: input.data,
|
|
107
|
-
t: await this.i18n.load(
|
|
109
|
+
t: await this.i18n.load(options.files.i18n, {
|
|
108
110
|
...options.i18n,
|
|
109
111
|
language: options.language
|
|
110
112
|
}),
|
|
@@ -158,6 +160,21 @@ let PrinterService = class PrinterService {
|
|
|
158
160
|
throw err;
|
|
159
161
|
});
|
|
160
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
|
+
}
|
|
161
178
|
attachments(value) {
|
|
162
179
|
if (value === void 0 || value === null) return [];
|
|
163
180
|
if (!Array.isArray(value)) throw new Error("Front-matter attachments must be a list of { file, position, compress }.");
|
|
@@ -5,6 +5,7 @@ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm
|
|
|
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
|
|
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
|
|
66
|
-
this.modules.
|
|
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?.
|
|
76
|
-
|
|
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,
|
|
@@ -1,3 +1,4 @@
|
|
|
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'
|
|
@@ -7,7 +8,10 @@ import Document from './Document.svelte'
|
|
|
7
8
|
import Margin from './Margin.svelte'
|
|
8
9
|
import type { DocumentShellProps, MarginShellProps, MarkdownNode, RenderedPart, TemplateProps } from '@interfaces'
|
|
9
10
|
|
|
10
|
-
|
|
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,
|
|
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
|
|
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
|
|
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(
|
|
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
|
},
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TemplateSourceType } from "../../constants/template.constants.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const TemplateSourceSchema = z.discriminatedUnion("type", [
|
|
4
|
+
z.strictObject({
|
|
5
|
+
type: z.literal("builtin"),
|
|
6
|
+
name: z.string().regex(/^[^\\/]+$/, "Built-in template names have no path separator.")
|
|
7
|
+
}),
|
|
8
|
+
z.strictObject({
|
|
9
|
+
type: z.literal("package"),
|
|
10
|
+
name: z.string().regex(/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/, "Not an npm package name."),
|
|
11
|
+
directory: z.string().optional()
|
|
12
|
+
}),
|
|
13
|
+
z.strictObject({
|
|
14
|
+
type: z.literal("directory"),
|
|
15
|
+
path: z.string().min(1)
|
|
16
|
+
})
|
|
17
|
+
], { error: () => `Use a string or an object with a type of ${Object.values(TemplateSourceType).join(", ")}.` });
|
|
18
|
+
const TemplateSettingsSchema = z.looseObject({ files: z.strictObject({
|
|
19
|
+
template: z.string().min(1).optional(),
|
|
20
|
+
components: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]).optional(),
|
|
21
|
+
extension: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]).optional(),
|
|
22
|
+
i18n: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]).optional()
|
|
23
|
+
}).optional() });
|
|
24
|
+
function issues({ issues }) {
|
|
25
|
+
return issues.map(({ path, message }) => [...path, message].join(": ")).join(", ");
|
|
26
|
+
}
|
|
27
|
+
export { TemplateSettingsSchema, TemplateSourceSchema, issues };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { inlineCss, inlineHtml } from "./assets.mjs";
|
|
1
|
+
import { inlineCss, inlineHtml, resolveConfined } from "./assets.mjs";
|
|
2
2
|
import { sveltePlugin } from "./svelte.plugin.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { TemplateSettingsSchema, issues } from "./template.schema.mjs";
|
|
4
|
+
import { TEMPLATE_EXTENSION_FILES, TEMPLATE_LAYOUT, TEMPLATE_PACKAGE_PREFIX } from "../../constants/template.constants.mjs";
|
|
4
5
|
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
|
|
5
6
|
import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
|
|
6
7
|
import { Injectable } from "@nestjs/common";
|
|
7
|
-
import { dirname, isAbsolute, join, resolve } from "path";
|
|
8
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
8
9
|
import { FileSystemService } from "@cenk1cenk2/oclif-common";
|
|
9
|
-
import { fileURLToPath } from "url";
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
10
11
|
import { homedir } from "os";
|
|
11
12
|
import { compile } from "@tailwindcss/node";
|
|
12
13
|
import { Scanner } from "@tailwindcss/oxide";
|
|
@@ -45,21 +46,20 @@ let TemplateService = class TemplateService {
|
|
|
45
46
|
constructor(fs) {
|
|
46
47
|
this.fs = fs;
|
|
47
48
|
}
|
|
48
|
-
resolve(template) {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!this.fs.exists(file)) return {};
|
|
57
|
-
return JSON.parse(await this.fs.read(file));
|
|
49
|
+
async resolve(template, base) {
|
|
50
|
+
const directory = await this.locate(template, base);
|
|
51
|
+
const settings = await this.settings(directory);
|
|
52
|
+
return {
|
|
53
|
+
directory,
|
|
54
|
+
settings,
|
|
55
|
+
files: await this.paths(directory, settings.files)
|
|
56
|
+
};
|
|
58
57
|
}
|
|
59
|
-
async bundle(
|
|
58
|
+
async bundle(files) {
|
|
60
59
|
const stylesheets = [];
|
|
61
|
-
const { module: entry, modules } = await this.compile(_TemplateService.ENTRY,
|
|
60
|
+
const { module: entry, modules } = await this.compile(_TemplateService.ENTRY, stylesheets, files);
|
|
62
61
|
return {
|
|
62
|
+
template: files.template,
|
|
63
63
|
entry,
|
|
64
64
|
modules,
|
|
65
65
|
stylesheets,
|
|
@@ -67,17 +67,22 @@ let TemplateService = class TemplateService {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
async page(file) {
|
|
70
|
-
const { module } = await this.compile(file,
|
|
70
|
+
const { module } = await this.compile(file, [], {
|
|
71
|
+
directory: dirname(file),
|
|
72
|
+
template: file
|
|
73
|
+
});
|
|
71
74
|
return `<!doctype html>${render(module.default).body}`;
|
|
72
75
|
}
|
|
73
76
|
async render(directory, bundle, document, markdown, options) {
|
|
74
77
|
const context = [
|
|
75
78
|
["md-printer:document", document],
|
|
76
79
|
["md-printer:markdown", markdown],
|
|
77
|
-
["md-printer:components", bundle.entry.components]
|
|
80
|
+
["md-printer:components", bundle.entry.components],
|
|
81
|
+
["md-printer:extension", bundle.entry.extension]
|
|
78
82
|
];
|
|
79
83
|
const props = {
|
|
80
84
|
...document,
|
|
85
|
+
extension: bundle.entry.extension,
|
|
81
86
|
content: bundle.entry.content(markdown, context)
|
|
82
87
|
};
|
|
83
88
|
const [body, header, footer] = await Promise.all([
|
|
@@ -108,16 +113,90 @@ let TemplateService = class TemplateService {
|
|
|
108
113
|
dependencies
|
|
109
114
|
};
|
|
110
115
|
}
|
|
116
|
+
locate(template, base) {
|
|
117
|
+
if (typeof template === "string") {
|
|
118
|
+
const path = this.expand(template);
|
|
119
|
+
return isAbsolute(path) || path.startsWith(".") || /[\\/]/.test(path) ? resolve(path) : this.builtin(path);
|
|
120
|
+
}
|
|
121
|
+
switch (template.type) {
|
|
122
|
+
case "builtin": return this.builtin(template.name);
|
|
123
|
+
case "package": return this.package(template, base);
|
|
124
|
+
case "directory": return resolve(base, this.expand(template.path));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
expand(path) {
|
|
128
|
+
return path.replace(/^~(?=\/|$)/, homedir());
|
|
129
|
+
}
|
|
111
130
|
/** A built-in template is a package of its own, so it brings its own fonts, icons and stylesheets as dependencies. */
|
|
112
131
|
builtin(name) {
|
|
113
132
|
const pkg = `${TEMPLATE_PACKAGE_PREFIX}${name}`;
|
|
114
133
|
try {
|
|
115
134
|
return dirname(this.require.resolve(`${pkg}/package.json`));
|
|
116
135
|
} catch {
|
|
117
|
-
throw new Error(`Template does not exist, expected the built-in template package ${pkg} or a path to a directory
|
|
136
|
+
throw new Error(`Template does not exist, expected the built-in template package ${pkg} or a path to a template directory.`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async package({ name, directory }, base) {
|
|
140
|
+
const root = this.installed(name, [.../* @__PURE__ */ new Set([base, process.cwd()]), _TemplateService.ROOT]);
|
|
141
|
+
if (directory === void 0) return root;
|
|
142
|
+
const confined = await resolveConfined(resolve(root, directory), root);
|
|
143
|
+
if (!confined) throw new Error(`Template directory "${directory}" of the package ${name} has to exist inside the package: ${root}`);
|
|
144
|
+
return confined;
|
|
145
|
+
}
|
|
146
|
+
installed(name, roots) {
|
|
147
|
+
for (const root of roots) try {
|
|
148
|
+
return dirname(createRequire(join(root, "package.json")).resolve(`${name}/package.json`));
|
|
149
|
+
} catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Template does not exist, the package ${name} is not installed in any of: ${roots.join(", ")}`);
|
|
153
|
+
}
|
|
154
|
+
async settings(directory) {
|
|
155
|
+
const file = join(directory, "settings.json");
|
|
156
|
+
if (!this.fs.exists(file)) return {};
|
|
157
|
+
const { success, data, error } = TemplateSettingsSchema.safeParse(JSON.parse(await this.fs.read(file)));
|
|
158
|
+
if (!success) throw new Error(`Template settings are invalid in ${file}: ${issues(error)}`);
|
|
159
|
+
return data;
|
|
160
|
+
}
|
|
161
|
+
async paths(directory, files = {}) {
|
|
162
|
+
const template = files.template ? await this.configured(directory, "template", files.template) : this.first(directory, TEMPLATE_LAYOUT.template);
|
|
163
|
+
const components = files.components ? await Promise.all([files.components].flat().map((path) => this.configured(directory, "components", path))) : [this.first(directory, TEMPLATE_LAYOUT.components) ?? join(directory, TEMPLATE_LAYOUT.components[0])];
|
|
164
|
+
const i18n = files.i18n ? await Promise.all([files.i18n].flat().map((path) => this.configured(directory, "i18n", path))) : [this.first(directory, TEMPLATE_LAYOUT.i18n) ?? join(directory, TEMPLATE_LAYOUT.i18n[0])];
|
|
165
|
+
const extension = files.extension ? await Promise.all([files.extension].flat().map((path) => this.configured(directory, "extension", path))) : [this.extension(directory)].filter((path) => path !== void 0);
|
|
166
|
+
if (!template) throw new Error(`Template does not exist, expected ${TEMPLATE_LAYOUT.template.join(" or ")} in: ${directory}`);
|
|
167
|
+
return {
|
|
168
|
+
directory,
|
|
169
|
+
template,
|
|
170
|
+
components,
|
|
171
|
+
i18n,
|
|
172
|
+
extension,
|
|
173
|
+
candidates: [
|
|
174
|
+
...files.template ? [template] : TEMPLATE_LAYOUT.template.map((path) => join(directory, path)),
|
|
175
|
+
...files.components ? components : TEMPLATE_LAYOUT.components.map((path) => join(directory, path)),
|
|
176
|
+
...files.i18n ? i18n : TEMPLATE_LAYOUT.i18n.map((path) => join(directory, path)),
|
|
177
|
+
...files.extension ? extension : TEMPLATE_LAYOUT.extension.flatMap((path) => TEMPLATE_EXTENSION_FILES.map((file) => join(directory, path, file)))
|
|
178
|
+
]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async configured(directory, key, value) {
|
|
182
|
+
const path = resolve(directory, value);
|
|
183
|
+
const rel = relative(directory, path);
|
|
184
|
+
const outside = rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel);
|
|
185
|
+
if (!outside && !this.fs.exists(path)) throw new Error(`Template settings point files.${key} to a path that does not exist: ${path}`);
|
|
186
|
+
if (outside || !await resolveConfined(path, directory)) throw new Error(`Template settings point files.${key} outside of the template directory: ${value}`);
|
|
187
|
+
return path;
|
|
188
|
+
}
|
|
189
|
+
first(directory, paths) {
|
|
190
|
+
return paths.map((path) => join(directory, path)).find((path) => this.fs.exists(path));
|
|
191
|
+
}
|
|
192
|
+
extension(directory) {
|
|
193
|
+
for (const location of TEMPLATE_LAYOUT.extension) {
|
|
194
|
+
const files = TEMPLATE_EXTENSION_FILES.filter((file) => this.fs.exists(join(directory, location, file)));
|
|
195
|
+
if (files.length > 1) throw new Error(`Template has more than one extension module, keep one of ${files.join(", ")} in: ${join(directory, location)}`);
|
|
196
|
+
if (files.length === 1) return join(directory, location, files[0]);
|
|
118
197
|
}
|
|
119
198
|
}
|
|
120
|
-
async compile(input,
|
|
199
|
+
async compile(input, stylesheets, files) {
|
|
121
200
|
const build = await rolldown({
|
|
122
201
|
input,
|
|
123
202
|
platform: "node",
|
|
@@ -125,14 +204,16 @@ let TemplateService = class TemplateService {
|
|
|
125
204
|
onLog(_, log, handler) {
|
|
126
205
|
if (log.code === "UNRESOLVED_IMPORT") handler("error", {
|
|
127
206
|
...log,
|
|
128
|
-
message: `${log.message} Install it in the template directory: ${
|
|
207
|
+
message: `${log.message} Install it in the template directory: ${files.directory}`
|
|
129
208
|
});
|
|
130
209
|
},
|
|
131
210
|
plugins: [sveltePlugin({
|
|
132
|
-
template,
|
|
211
|
+
template: files.template,
|
|
133
212
|
components: _TemplateService.COMPONENTS,
|
|
213
|
+
templateComponents: files.components,
|
|
134
214
|
root: _TemplateService.ROOT,
|
|
135
|
-
stylesheets
|
|
215
|
+
stylesheets,
|
|
216
|
+
extension: files.extension
|
|
136
217
|
})]
|
|
137
218
|
});
|
|
138
219
|
try {
|
|
@@ -140,8 +221,9 @@ let TemplateService = class TemplateService {
|
|
|
140
221
|
format: "esm",
|
|
141
222
|
sourcemap: "inline"
|
|
142
223
|
});
|
|
224
|
+
const code = chunk.code.replaceAll("createRequire(import.meta.url)", `createRequire(${JSON.stringify(pathToFileURL(files.template).href)})`);
|
|
143
225
|
return {
|
|
144
|
-
module: await import(`data:text/javascript;base64,${Buffer.from(
|
|
226
|
+
module: await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`),
|
|
145
227
|
modules: chunk.moduleIds.filter((id) => isAbsolute(id))
|
|
146
228
|
};
|
|
147
229
|
} finally {
|
|
@@ -164,7 +246,7 @@ let TemplateService = class TemplateService {
|
|
|
164
246
|
const theme = `:root { ${readFileSync(this.highlightTheme(highlight), "utf8")} pre:has(> code.hljs) { padding: 0; background: transparent; } }`;
|
|
165
247
|
const compiler = await compile([theme, ...bundle.stylesheets.map(({ source }) => source)].join("\n"), {
|
|
166
248
|
base: directory,
|
|
167
|
-
from: bundle.stylesheets[0]?.file ??
|
|
249
|
+
from: bundle.stylesheets[0]?.file ?? bundle.template,
|
|
168
250
|
shouldRewriteUrls: true,
|
|
169
251
|
onDependency: (path) => dependencies.push(path),
|
|
170
252
|
customCssResolver: async (id, base) => this.resolveFrom(this.stylesheets, id, [
|
package/oclif.manifest.json
CHANGED
|
@@ -94,12 +94,26 @@
|
|
|
94
94
|
},
|
|
95
95
|
"template": {
|
|
96
96
|
"char": "t",
|
|
97
|
-
"description": "Built-in template name or path to a template directory, defaults to \"default\". Overwrites the one defined in front-matter.",
|
|
97
|
+
"description": "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.",
|
|
98
98
|
"name": "template",
|
|
99
99
|
"hasDynamicHelp": false,
|
|
100
100
|
"multiple": false,
|
|
101
101
|
"type": "option"
|
|
102
102
|
},
|
|
103
|
+
"template-type": {
|
|
104
|
+
"description": "How to read --template, one of builtin, package or directory, like the type of the object form in front-matter.",
|
|
105
|
+
"name": "template-type",
|
|
106
|
+
"hasDynamicHelp": false,
|
|
107
|
+
"multiple": false,
|
|
108
|
+
"type": "option"
|
|
109
|
+
},
|
|
110
|
+
"template-directory": {
|
|
111
|
+
"description": "Directory inside the package to use as the template, only valid with --template-type package.",
|
|
112
|
+
"name": "template-directory",
|
|
113
|
+
"hasDynamicHelp": false,
|
|
114
|
+
"multiple": false,
|
|
115
|
+
"type": "option"
|
|
116
|
+
},
|
|
103
117
|
"title": {
|
|
104
118
|
"char": "T",
|
|
105
119
|
"description": "Document title. Overwrites the one defined in front-matter or the template.",
|
|
@@ -198,5 +212,5 @@
|
|
|
198
212
|
"enableJsonFlag": false
|
|
199
213
|
}
|
|
200
214
|
},
|
|
201
|
-
"version": "3.
|
|
215
|
+
"version": "3.4.0"
|
|
202
216
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cenk1cenk2/md-printer",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "Render markdown or yaml through Svelte templates into PDF or HTML with Puppeteer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"cenk1cenk2"
|
|
50
50
|
],
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@cenk1cenk2/md-printer-template-cv": "1.0.0",
|
|
53
|
-
"@cenk1cenk2/md-printer-template-default": "1.
|
|
54
|
-
"@cenk1cenk2/md-printer-template-invoice": "1.0.0",
|
|
55
|
-
"@cenk1cenk2/md-printer-template-kilic.dev": "1.
|
|
56
|
-
"@cenk1cenk2/md-printer-template-mail": "1.
|
|
52
|
+
"@cenk1cenk2/md-printer-template-cv": "^1.0.0",
|
|
53
|
+
"@cenk1cenk2/md-printer-template-default": "1.1.0",
|
|
54
|
+
"@cenk1cenk2/md-printer-template-invoice": "^1.0.0",
|
|
55
|
+
"@cenk1cenk2/md-printer-template-kilic.dev": "1.1.0",
|
|
56
|
+
"@cenk1cenk2/md-printer-template-mail": "1.1.0",
|
|
57
57
|
"@cenk1cenk2/oclif-common": "^8.0.2",
|
|
58
58
|
"@listr2/manager": "^4.3.3",
|
|
59
59
|
"@nestjs/common": "^12.3.2",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"tsdown": "^0.23.4",
|
|
124
124
|
"typescript": "^6.0.3",
|
|
125
125
|
"vite": "^8.3.5",
|
|
126
|
-
"vitest": "^5.0
|
|
126
|
+
"vitest": "^5.1.0"
|
|
127
127
|
},
|
|
128
128
|
"bugs": {
|
|
129
129
|
"url": "https://gitlab.kilic.dev/utils/md-printer/-/issues"
|