@cenk1cenk2/md-printer 3.0.0 → 3.1.1
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 +4 -3
- package/bin/dev.js +0 -2
- package/bin/run.js +0 -1
- package/dist/commands/index.mjs +31 -182
- package/dist/constants/index.mjs +3 -1
- package/dist/constants/preview.constants.mjs +9 -0
- package/dist/constants/signature.constants.mjs +7 -0
- package/dist/modules/md-printer/md-printer.handler.mjs +113 -0
- package/dist/modules/md-printer/md-printer.module.mjs +21 -0
- package/dist/modules/md-printer/md-printer.schema.mjs +50 -0
- package/dist/services/i18n.service.mjs +9 -8
- package/dist/services/printer.module.mjs +24 -26
- package/dist/services/printer.service.mjs +6 -6
- package/dist/services/renderer/app-window.mjs +44 -0
- package/dist/services/renderer/browser.service.mjs +1 -1
- package/dist/services/renderer/renderer.service.mjs +1 -1
- package/dist/services/session/preview.service.mjs +8 -24
- package/dist/services/session/session.service.mjs +1 -1
- package/dist/services/signature/adapters/a-trust.adapter.mjs +44 -13
- package/dist/services/signature/signature.service.mjs +36 -9
- package/dist/services/template/template.service.mjs +1 -1
- package/oclif.manifest.json +5 -3
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ md-printer document.md out.pdf --preview --no-open
|
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
- `-p, --preview` rebuilds when the input file, the template (components, imports, `settings.json`, translations) or a local asset used by the page changes. Every build rewrites the output file, the same one a normal run writes; reading from stdin without an output argument only previews. `-O` is not supported.
|
|
144
|
-
- It starts a local preview server and opens it in an app window of the discovered browser, using a throwaway profile. The page has HTML, Split and PDF tabs. Build errors, such as Svelte compile errors, are shown in an overlay and cleared by the next successful build. Closing the window ends the session,
|
|
144
|
+
- It starts a local preview server and opens it in an app window of the discovered browser, using a throwaway profile. The page has HTML, Split and PDF tabs. Build errors, such as Svelte compile errors, are shown in an overlay and cleared by the next successful build. Closing the window ends the session. Ctrl+C ends it too, after closing the browser, the preview server and the watcher, and exits with 130; a second Ctrl+C exits at once.
|
|
145
145
|
- `--host` sets the loopback host, default `127.0.0.1`.
|
|
146
146
|
- `--port` sets the port, default `0` (random). A port in use fails the run with an error.
|
|
147
147
|
- It serves the files next to the input, except dotfiles such as `.env`.
|
|
@@ -303,7 +303,7 @@ USAGE
|
|
|
303
303
|
silent|direct|fatal|error|warn|info|verbose|debug|trace] [-I] [-f
|
|
304
304
|
md|yaml|yml] [-F pdf|html] [-O] [-t <value>] [-T <value>] [-l <value>] [-b
|
|
305
305
|
<value>] [--continuous] [-p] [--host 127.0.0.1|localhost|::1] [--port
|
|
306
|
-
<value>] [--open] [--sign] [--sign-provider
|
|
306
|
+
<value>] [--open] [--sign] [--sign-provider a-trust] [--sign-certificate
|
|
307
307
|
<value>]
|
|
308
308
|
|
|
309
309
|
ARGUMENTS
|
|
@@ -349,9 +349,10 @@ FLAGS
|
|
|
349
349
|
--sign-certificate=<value> Signer certificate (DER or PEM) used for the
|
|
350
350
|
visible signature block and to verify the
|
|
351
351
|
returned signature.
|
|
352
|
-
--sign-provider=<
|
|
352
|
+
--sign-provider=<option> Signature provider, defaults to a-trust.
|
|
353
353
|
Overwrites the one defined in front-matter and
|
|
354
354
|
template settings.
|
|
355
|
+
<options: a-trust>
|
|
355
356
|
|
|
356
357
|
CLI FLAGS
|
|
357
358
|
--log-level=<option> [default: INFO, env: LOG_LEVEL] Set the log level of the
|
package/bin/dev.js
CHANGED
|
@@ -26,8 +26,6 @@ async function main() {
|
|
|
26
26
|
|
|
27
27
|
await import('source-map-support').then((lib) => lib.install())
|
|
28
28
|
|
|
29
|
-
await import('@cenk1cenk2/oclif-common').then((mod) => mod.setup())
|
|
30
|
-
|
|
31
29
|
const { execute } = await import('@oclif/core')
|
|
32
30
|
|
|
33
31
|
return execute({ development: true, dir: import.meta.url })
|
package/bin/run.js
CHANGED
package/dist/commands/index.mjs
CHANGED
|
@@ -1,190 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { PrinterModule } from "../services/printer.module.mjs";
|
|
7
|
-
import "../services/index.mjs";
|
|
8
|
-
import { mkdir } from "fs/promises";
|
|
9
|
-
import { dirname } from "path";
|
|
10
|
-
import { text } from "stream/consumers";
|
|
11
|
-
import writeFileAtomic from "write-file-atomic";
|
|
12
|
-
import { Args, CliModule, Command, Flags, JsonParser, ParserService, YamlParser } from "@cenk1cenk2/oclif-common";
|
|
1
|
+
import __decorate from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
2
|
+
import { MdPrinterHandler } from "../modules/md-printer/md-printer.handler.mjs";
|
|
3
|
+
import { MdPrinterModule } from "../modules/md-printer/md-printer.module.mjs";
|
|
4
|
+
import { MdPrinterSchema } from "../modules/md-printer/md-printer.schema.mjs";
|
|
5
|
+
import { Args, Command, Flags, UseHandler } from "@cenk1cenk2/oclif-common";
|
|
13
6
|
//#region src/commands/index.ts
|
|
14
|
-
|
|
7
|
+
let MDPrinter = class MDPrinter extends Command {
|
|
15
8
|
static description = "Renders a markdown or yaml file through a Svelte template into a PDF or HTML document.";
|
|
16
9
|
static flags = {
|
|
17
|
-
stdin: Flags.boolean({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}),
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
description: "Write the output to stdout instead of a file."
|
|
34
|
-
}),
|
|
35
|
-
template: Flags.string({
|
|
36
|
-
char: "t",
|
|
37
|
-
description: "Built-in template name or path to a template directory, defaults to \"default\". Overwrites the one defined in front-matter."
|
|
38
|
-
}),
|
|
39
|
-
title: Flags.string({
|
|
40
|
-
char: "T",
|
|
41
|
-
description: "Document title. Overwrites the one defined in front-matter or the template."
|
|
42
|
-
}),
|
|
43
|
-
language: Flags.string({
|
|
44
|
-
char: "l",
|
|
45
|
-
description: "Language for template translations. Overwrites the one defined in front-matter and template settings."
|
|
46
|
-
}),
|
|
47
|
-
browser: Flags.string({
|
|
48
|
-
char: "b",
|
|
49
|
-
description: "Browser executable used for printing. Discovered automatically when omitted (Brave, Chrome, Chromium, Edge)."
|
|
50
|
-
}),
|
|
51
|
-
continuous: Flags.boolean({ description: "Render the PDF as a single continuous page without page breaks." }),
|
|
52
|
-
preview: Flags.boolean({
|
|
53
|
-
char: "p",
|
|
54
|
-
description: "Rebuild whenever the input, the template or its assets change, show the HTML and the PDF in a live preview and rewrite the output file when there is one."
|
|
55
|
-
}),
|
|
56
|
-
host: Flags.string({
|
|
57
|
-
description: "Loopback host for the preview server, it serves the files next to the input and is never exposed to the network.",
|
|
58
|
-
options: [
|
|
59
|
-
"127.0.0.1",
|
|
60
|
-
"localhost",
|
|
61
|
-
"::1"
|
|
62
|
-
],
|
|
63
|
-
default: "127.0.0.1"
|
|
64
|
-
}),
|
|
65
|
-
port: Flags.integer({
|
|
66
|
-
description: "Port for the preview server, random when 0.",
|
|
67
|
-
default: 0
|
|
68
|
-
}),
|
|
69
|
-
open: Flags.boolean({
|
|
70
|
-
description: "Open the preview window, --no-open keeps rebuilding and writing without one.",
|
|
71
|
-
default: true,
|
|
72
|
-
allowNo: true
|
|
73
|
-
}),
|
|
74
|
-
sign: Flags.boolean({ description: "Sign the PDF with a qualified electronic signature through ID Austria (A-Trust)." }),
|
|
75
|
-
["sign-provider"]: Flags.string({ description: "Signature provider, defaults to a-trust. Overwrites the one defined in front-matter and template settings." }),
|
|
76
|
-
["sign-certificate"]: Flags.string({ description: "Signer certificate (DER or PEM) used for the visible signature block and to verify the returned signature." })
|
|
10
|
+
stdin: Flags.boolean({ char: "I" }),
|
|
11
|
+
["input-filetype"]: Flags.string({ char: "f" }),
|
|
12
|
+
["output-filetype"]: Flags.string({ char: "F" }),
|
|
13
|
+
stdout: Flags.boolean({ char: "O" }),
|
|
14
|
+
template: Flags.string({ char: "t" }),
|
|
15
|
+
title: Flags.string({ char: "T" }),
|
|
16
|
+
language: Flags.string({ char: "l" }),
|
|
17
|
+
browser: Flags.string({ char: "b" }),
|
|
18
|
+
continuous: Flags.boolean(),
|
|
19
|
+
preview: Flags.boolean({ char: "p" }),
|
|
20
|
+
host: Flags.string(),
|
|
21
|
+
port: Flags.integer(),
|
|
22
|
+
open: Flags.boolean({ allowNo: true }),
|
|
23
|
+
sign: Flags.boolean(),
|
|
24
|
+
["sign-provider"]: Flags.string(),
|
|
25
|
+
["sign-certificate"]: Flags.string()
|
|
77
26
|
};
|
|
78
27
|
static args = {
|
|
79
|
-
file: Args.string({
|
|
80
|
-
|
|
81
|
-
ignoreStdin: true
|
|
82
|
-
}),
|
|
83
|
-
output: Args.string({
|
|
84
|
-
description: "Output file. Overwrites the one defined in front-matter.",
|
|
85
|
-
required: false,
|
|
86
|
-
ignoreStdin: true
|
|
87
|
-
})
|
|
28
|
+
file: Args.string({ ignoreStdin: true }),
|
|
29
|
+
output: Args.string({ ignoreStdin: true })
|
|
88
30
|
};
|
|
89
|
-
printer;
|
|
90
|
-
browser;
|
|
91
|
-
register(_cli, options) {
|
|
92
|
-
const cli = CliModule.forRoot({
|
|
93
|
-
...options,
|
|
94
|
-
logger: { stderr: true }
|
|
95
|
-
});
|
|
96
|
-
cli.imports.push(PrinterModule.forRoot({ browser: this.flags.browser }));
|
|
97
|
-
return cli;
|
|
98
|
-
}
|
|
99
|
-
async shouldRunBefore() {
|
|
100
|
-
await this.app.get(ParserService).register(JsonParser, YamlParser);
|
|
101
|
-
this.printer = this.app.get(PrinterService);
|
|
102
|
-
this.browser = this.app.get(BrowserService);
|
|
103
|
-
}
|
|
104
|
-
async run() {
|
|
105
|
-
const flags = {
|
|
106
|
-
file: this.flags.stdin ? void 0 : this.args.file,
|
|
107
|
-
output: this.flags.stdin ? this.args.output ?? this.args.file : this.args.output,
|
|
108
|
-
stdin: this.flags.stdin ? await text(process.stdin) : void 0,
|
|
109
|
-
inputFiletype: this.flags["input-filetype"],
|
|
110
|
-
outputFiletype: this.flags["output-filetype"],
|
|
111
|
-
stdout: this.flags.stdout,
|
|
112
|
-
template: this.flags.template,
|
|
113
|
-
title: this.flags.title,
|
|
114
|
-
language: this.flags.language,
|
|
115
|
-
continuous: this.flags.continuous,
|
|
116
|
-
sign: this.flags.sign,
|
|
117
|
-
signProvider: this.flags["sign-provider"],
|
|
118
|
-
signCertificate: this.flags["sign-certificate"]
|
|
119
|
-
};
|
|
120
|
-
if (this.flags.preview) await this.session(flags);
|
|
121
|
-
else await this.print(flags);
|
|
122
|
-
}
|
|
123
|
-
async print(flags) {
|
|
124
|
-
const input = await this.printer.input(flags);
|
|
125
|
-
const ctx = {
|
|
126
|
-
input,
|
|
127
|
-
options: await this.printer.options(flags, input)
|
|
128
|
-
};
|
|
129
|
-
this.logger.debug("Options: %o", ctx.options);
|
|
130
|
-
await this.printer.prepare(ctx);
|
|
131
|
-
const document = await this.printer.document(ctx, await this.printer.bundle(ctx.options));
|
|
132
|
-
let { content } = await this.printer.output(ctx, document);
|
|
133
|
-
if (ctx.signature) {
|
|
134
|
-
content = await this.printer.sign(ctx, content);
|
|
135
|
-
this.logger.info("Document is signed.");
|
|
136
|
-
}
|
|
137
|
-
await this.write(ctx, content);
|
|
138
|
-
}
|
|
139
|
-
async session(flags) {
|
|
140
|
-
if (flags.stdout) throw new Error("Writing to stdout is not supported with --preview.");
|
|
141
|
-
const session = this.app.get(SessionService);
|
|
142
|
-
const preview = this.app.get(PreviewService);
|
|
143
|
-
let writes = Promise.resolve();
|
|
144
|
-
session.on("building", () => preview.building());
|
|
145
|
-
session.on("failed", (err) => {
|
|
146
|
-
this.logger.error(err.message);
|
|
147
|
-
preview.failed(err);
|
|
148
|
-
});
|
|
149
|
-
session.on("ready", async (build) => {
|
|
150
|
-
preview.ready(build);
|
|
151
|
-
if (!build.ctx.options.dest) {
|
|
152
|
-
this.logger.info("Rebuilt in %dms.", build.ms);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
session.ignore(build.ctx.options.dest);
|
|
156
|
-
writes = writes.then(() => this.write(build.ctx, build.output)).catch((err) => this.logger.error(err.message));
|
|
157
|
-
await writes;
|
|
158
|
-
});
|
|
159
|
-
if (flags.sign) this.logger.warn("Signing is skipped while previewing, run without --preview to sign the document.");
|
|
160
|
-
process.removeAllListeners("SIGINT");
|
|
161
|
-
process.removeAllListeners("SIGTERM");
|
|
162
|
-
const stopped = new Promise((resolve) => {
|
|
163
|
-
process.once("SIGINT", () => resolve());
|
|
164
|
-
process.once("SIGTERM", () => resolve());
|
|
165
|
-
});
|
|
166
|
-
await session.start(flags);
|
|
167
|
-
const url = await preview.listen({
|
|
168
|
-
host: this.flags.host,
|
|
169
|
-
port: this.flags.port
|
|
170
|
-
});
|
|
171
|
-
this.logger.info("Preview is running at %s, press Ctrl+C to stop.", url);
|
|
172
|
-
await Promise.race([stopped, this.flags.open ? preview.open(this.browser.executable(), url) : new Promise(() => void 0)]);
|
|
173
|
-
await preview.close();
|
|
174
|
-
await session.stop();
|
|
175
|
-
await writes;
|
|
176
|
-
}
|
|
177
|
-
async write(ctx, content) {
|
|
178
|
-
const { dest } = ctx.options;
|
|
179
|
-
if (!dest) {
|
|
180
|
-
if (!this.flags.stdout) throw new Error("No output file to write, pass an output argument, set dest in front-matter or use --stdout.");
|
|
181
|
-
process.stdout.write(content);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
await mkdir(dirname(dest), { recursive: true });
|
|
185
|
-
await writeFileAtomic(dest, content);
|
|
186
|
-
this.logger.info("Written: %s", dest);
|
|
187
|
-
}
|
|
188
31
|
};
|
|
32
|
+
MDPrinter = __decorate([UseHandler({
|
|
33
|
+
module: MdPrinterModule,
|
|
34
|
+
handler: MdPrinterHandler,
|
|
35
|
+
schema: MdPrinterSchema
|
|
36
|
+
})], MDPrinter);
|
|
37
|
+
var commands_default = MDPrinter;
|
|
189
38
|
//#endregion
|
|
190
|
-
export {
|
|
39
|
+
export { commands_default as default };
|
package/dist/constants/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
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, TemplateFiles } from "./template.constants.mjs";
|
|
2
2
|
import { InputFileType, OutputFileType } from "./file.constants.mjs";
|
|
3
|
-
|
|
3
|
+
import { PreviewHost } from "./preview.constants.mjs";
|
|
4
|
+
import { SignatureProvider } from "./signature.constants.mjs";
|
|
5
|
+
export { 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, TemplateFiles };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/constants/preview.constants.ts
|
|
2
|
+
let PreviewHost = /* @__PURE__ */ function(PreviewHost) {
|
|
3
|
+
PreviewHost["IPV4"] = "127.0.0.1";
|
|
4
|
+
PreviewHost["LOCALHOST"] = "localhost";
|
|
5
|
+
PreviewHost["IPV6"] = "::1";
|
|
6
|
+
return PreviewHost;
|
|
7
|
+
}({});
|
|
8
|
+
//#endregion
|
|
9
|
+
export { PreviewHost };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
2
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
3
|
+
import { BrowserService } from "../../services/renderer/browser.service.mjs";
|
|
4
|
+
import { PrinterService } from "../../services/printer.service.mjs";
|
|
5
|
+
import { PreviewService } from "../../services/session/preview.service.mjs";
|
|
6
|
+
import { SessionService } from "../../services/session/session.service.mjs";
|
|
7
|
+
import "../../services/index.mjs";
|
|
8
|
+
import { Injectable } from "@nestjs/common";
|
|
9
|
+
import { mkdir } from "fs/promises";
|
|
10
|
+
import { dirname } from "path";
|
|
11
|
+
import { text } from "stream/consumers";
|
|
12
|
+
import writeFileAtomic from "write-file-atomic";
|
|
13
|
+
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
14
|
+
//#region src/modules/md-printer/md-printer.handler.ts
|
|
15
|
+
let MdPrinterHandler = class MdPrinterHandler {
|
|
16
|
+
logger;
|
|
17
|
+
printer;
|
|
18
|
+
browser;
|
|
19
|
+
session;
|
|
20
|
+
preview;
|
|
21
|
+
constructor(logger, printer, browser, session, preview) {
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
this.printer = printer;
|
|
24
|
+
this.browser = browser;
|
|
25
|
+
this.session = session;
|
|
26
|
+
this.preview = preview;
|
|
27
|
+
}
|
|
28
|
+
async run(context) {
|
|
29
|
+
const { flags, args } = context;
|
|
30
|
+
const options = {
|
|
31
|
+
...args.file !== void 0 ? { file: args.file } : { stdin: await text(process.stdin) },
|
|
32
|
+
output: args.output,
|
|
33
|
+
inputFiletype: flags["input-filetype"],
|
|
34
|
+
outputFiletype: flags["output-filetype"],
|
|
35
|
+
stdout: flags.stdout,
|
|
36
|
+
template: flags.template,
|
|
37
|
+
title: flags.title,
|
|
38
|
+
language: flags.language,
|
|
39
|
+
continuous: flags.continuous,
|
|
40
|
+
sign: flags.sign,
|
|
41
|
+
signProvider: flags["sign-provider"],
|
|
42
|
+
signCertificate: flags["sign-certificate"]
|
|
43
|
+
};
|
|
44
|
+
if (flags.preview) await this.watch(context, options);
|
|
45
|
+
else await this.print(options);
|
|
46
|
+
}
|
|
47
|
+
async print(flags) {
|
|
48
|
+
const input = await this.printer.input(flags);
|
|
49
|
+
const ctx = {
|
|
50
|
+
input,
|
|
51
|
+
options: await this.printer.options(flags, input)
|
|
52
|
+
};
|
|
53
|
+
this.logger.debug("Options: %o", ctx.options);
|
|
54
|
+
await this.printer.prepare(ctx);
|
|
55
|
+
const document = await this.printer.document(ctx, await this.printer.bundle(ctx.options));
|
|
56
|
+
let { content } = await this.printer.output(ctx, document);
|
|
57
|
+
if (ctx.signature) {
|
|
58
|
+
content = await this.printer.sign(ctx, content);
|
|
59
|
+
this.logger.info("Document is signed.");
|
|
60
|
+
}
|
|
61
|
+
await this.write(ctx, content, flags.stdout);
|
|
62
|
+
}
|
|
63
|
+
async watch({ flags, signal }, options) {
|
|
64
|
+
let writes = Promise.resolve();
|
|
65
|
+
this.session.on("building", () => this.preview.building());
|
|
66
|
+
this.session.on("failed", (err) => {
|
|
67
|
+
this.logger.error(err.message);
|
|
68
|
+
this.preview.failed(err);
|
|
69
|
+
});
|
|
70
|
+
this.session.on("ready", async (build) => {
|
|
71
|
+
this.preview.ready(build);
|
|
72
|
+
if (!build.ctx.options.dest) {
|
|
73
|
+
this.logger.info("Rebuilt in %dms.", build.ms);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.session.ignore(build.ctx.options.dest);
|
|
77
|
+
writes = writes.then(() => this.write(build.ctx, build.output, options.stdout)).catch((err) => this.logger.error(err.message));
|
|
78
|
+
await writes;
|
|
79
|
+
});
|
|
80
|
+
if (options.sign) this.logger.warn("Signing is skipped while previewing, run without --preview to sign the document.");
|
|
81
|
+
const stopped = new Promise((resolve) => signal.addEventListener("abort", () => resolve(), { once: true }));
|
|
82
|
+
await this.session.start(options);
|
|
83
|
+
const url = await this.preview.listen({
|
|
84
|
+
host: flags.host,
|
|
85
|
+
port: flags.port
|
|
86
|
+
});
|
|
87
|
+
this.logger.info("Preview is running at %s, press Ctrl+C to stop.", url);
|
|
88
|
+
await Promise.race([stopped, flags.open ? this.preview.open(this.browser.executable(), url) : new Promise(() => void 0)]);
|
|
89
|
+
await this.preview.close();
|
|
90
|
+
await this.session.stop();
|
|
91
|
+
await writes;
|
|
92
|
+
}
|
|
93
|
+
async write(ctx, content, stdout) {
|
|
94
|
+
const { dest } = ctx.options;
|
|
95
|
+
if (!dest) {
|
|
96
|
+
if (!stdout) throw new Error("No output file to write, pass an output argument, set dest in front-matter or use --stdout.");
|
|
97
|
+
process.stdout.write(content);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
101
|
+
await writeFileAtomic(dest, content);
|
|
102
|
+
this.logger.info("Written: %s", dest);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
MdPrinterHandler = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [
|
|
106
|
+
typeof LoggerService === "undefined" ? Object : LoggerService,
|
|
107
|
+
typeof PrinterService === "undefined" ? Object : PrinterService,
|
|
108
|
+
typeof BrowserService === "undefined" ? Object : BrowserService,
|
|
109
|
+
typeof SessionService === "undefined" ? Object : SessionService,
|
|
110
|
+
typeof PreviewService === "undefined" ? Object : PreviewService
|
|
111
|
+
])], MdPrinterHandler);
|
|
112
|
+
//#endregion
|
|
113
|
+
export { MdPrinterHandler };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
2
|
+
import { PrinterModule } from "../../services/printer.module.mjs";
|
|
3
|
+
import "../../services/index.mjs";
|
|
4
|
+
import { MdPrinterHandler } from "./md-printer.handler.mjs";
|
|
5
|
+
import { Module } from "@nestjs/common";
|
|
6
|
+
import { CommandContext, LogLevels, LoggerModule, LogoModule } from "@cenk1cenk2/oclif-common";
|
|
7
|
+
//#region src/modules/md-printer/md-printer.module.ts
|
|
8
|
+
let MdPrinterModule = class MdPrinterModule {};
|
|
9
|
+
MdPrinterModule = __decorate([Module({
|
|
10
|
+
imports: [
|
|
11
|
+
LoggerModule.forRoot({ stderr: Object.values(LogLevels) }),
|
|
12
|
+
LogoModule.forRoot(),
|
|
13
|
+
PrinterModule.forRootAsync({
|
|
14
|
+
inject: [CommandContext],
|
|
15
|
+
useFactory: ({ flags }) => ({ browser: flags.browser })
|
|
16
|
+
})
|
|
17
|
+
],
|
|
18
|
+
providers: [MdPrinterHandler]
|
|
19
|
+
})], MdPrinterModule);
|
|
20
|
+
//#endregion
|
|
21
|
+
export { MdPrinterModule };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { InputFileType, OutputFileType } from "../../constants/file.constants.mjs";
|
|
2
|
+
import { PreviewHost } from "../../constants/preview.constants.mjs";
|
|
3
|
+
import { SignatureProvider } from "../../constants/signature.constants.mjs";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/modules/md-printer/md-printer.schema.ts
|
|
6
|
+
const MdPrinterSchema = z.object({
|
|
7
|
+
flags: z.object({
|
|
8
|
+
stdin: z.boolean().default(false).describe("Read the input from stdin."),
|
|
9
|
+
["input-filetype"]: z.enum(InputFileType).optional().describe("Input file type. Detected from the file extension, markdown for stdin and unknown extensions."),
|
|
10
|
+
["output-filetype"]: z.enum(OutputFileType).optional().describe("Output file type, defaults to pdf. Overwrites the one defined in front-matter."),
|
|
11
|
+
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
|
+
title: z.string().optional().describe("Document title. Overwrites the one defined in front-matter or the template."),
|
|
14
|
+
language: z.string().optional().describe("Language for template translations. Overwrites the one defined in front-matter and template settings."),
|
|
15
|
+
browser: z.string().optional().describe("Browser executable used for printing. Discovered automatically when omitted (Brave, Chrome, Chromium, Edge)."),
|
|
16
|
+
continuous: z.boolean().default(false).describe("Render the PDF as a single continuous page without page breaks."),
|
|
17
|
+
preview: z.boolean().default(false).describe("Rebuild whenever the input, the template or its assets change, show the HTML and the PDF in a live preview and rewrite the output file when there is one."),
|
|
18
|
+
host: z.enum(PreviewHost).default("127.0.0.1").describe("Loopback host for the preview server, it serves the files next to the input and is never exposed to the network."),
|
|
19
|
+
port: z.number().int().min(0).max(65535).default(0).describe("Port for the preview server, random when 0."),
|
|
20
|
+
open: z.boolean().default(true).describe("Open the preview window, --no-open keeps rebuilding and writing without one."),
|
|
21
|
+
sign: z.boolean().default(false).describe("Sign the PDF with a qualified electronic signature through ID Austria (A-Trust)."),
|
|
22
|
+
["sign-provider"]: z.enum(SignatureProvider).optional().describe("Signature provider, defaults to a-trust. Overwrites the one defined in front-matter and template settings."),
|
|
23
|
+
["sign-certificate"]: z.string().optional().describe("Signer certificate (DER or PEM) used for the visible signature block and to verify the returned signature.")
|
|
24
|
+
}),
|
|
25
|
+
args: z.object({
|
|
26
|
+
file: z.string().optional().describe("Markdown or yaml file to render."),
|
|
27
|
+
output: z.string().optional().describe("Output file. Overwrites the one defined in front-matter.")
|
|
28
|
+
})
|
|
29
|
+
}).superRefine(({ flags, args }, ctx) => {
|
|
30
|
+
if (!flags.stdin && !args.file) ctx.addIssue({
|
|
31
|
+
code: "custom",
|
|
32
|
+
message: "Either --stdin or file argument must be provided."
|
|
33
|
+
});
|
|
34
|
+
if (flags.stdin && args.output) ctx.addIssue({
|
|
35
|
+
code: "custom",
|
|
36
|
+
message: "Reading from --stdin takes only the output file argument."
|
|
37
|
+
});
|
|
38
|
+
if (flags.stdout && flags.preview) ctx.addIssue({
|
|
39
|
+
code: "custom",
|
|
40
|
+
message: "Writing to stdout is not supported with --preview."
|
|
41
|
+
});
|
|
42
|
+
}).transform(({ flags, args }) => ({
|
|
43
|
+
flags,
|
|
44
|
+
args: flags.stdin ? {
|
|
45
|
+
file: void 0,
|
|
46
|
+
output: args.file
|
|
47
|
+
} : args
|
|
48
|
+
}));
|
|
49
|
+
//#endregion
|
|
50
|
+
export { MdPrinterSchema };
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { I18N_ACCEPTED_TYPES } from "../constants/template.constants.mjs";
|
|
2
2
|
import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
3
3
|
import __decorate from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
|
-
import { basename, extname, join } from "path";
|
|
5
|
-
import { ConfigService, FileSystemService } from "@cenk1cenk2/oclif-common";
|
|
6
4
|
import { Injectable } from "@nestjs/common";
|
|
5
|
+
import { readdir } from "fs/promises";
|
|
6
|
+
import { basename, extname, join } from "path";
|
|
7
|
+
import { FileSystemService, ParserService } from "@cenk1cenk2/oclif-common";
|
|
7
8
|
import i18next from "i18next";
|
|
8
9
|
//#region src/services/i18n.service.ts
|
|
9
10
|
let I18nService = class I18nService {
|
|
10
11
|
fs;
|
|
11
|
-
|
|
12
|
-
constructor(fs,
|
|
12
|
+
parser;
|
|
13
|
+
constructor(fs, parser) {
|
|
13
14
|
this.fs = fs;
|
|
14
|
-
this.
|
|
15
|
+
this.parser = parser;
|
|
15
16
|
}
|
|
16
17
|
async load(directory, options) {
|
|
17
18
|
const instance = i18next.createInstance();
|
|
@@ -25,12 +26,12 @@ let I18nService = class I18nService {
|
|
|
25
26
|
}
|
|
26
27
|
async resources(directory) {
|
|
27
28
|
const resources = {};
|
|
28
|
-
await Promise.all((await
|
|
29
|
-
resources[basename(file, extname(file))] = { translation: await this.
|
|
29
|
+
await Promise.all((await readdir(directory)).filter((file) => I18N_ACCEPTED_TYPES.includes(extname(file))).map(async (file) => {
|
|
30
|
+
resources[basename(file, extname(file))] = { translation: await this.parser.read(join(directory, file)) };
|
|
30
31
|
}));
|
|
31
32
|
return resources;
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
|
-
I18nService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof FileSystemService === "undefined" ? Object : FileSystemService, typeof
|
|
35
|
+
I18nService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof FileSystemService === "undefined" ? Object : FileSystemService, typeof ParserService === "undefined" ? Object : ParserService])], I18nService);
|
|
35
36
|
//#endregion
|
|
36
37
|
export { I18nService };
|
|
@@ -10,32 +10,30 @@ import { TemplateService } from "./template/template.service.mjs";
|
|
|
10
10
|
import { PrinterService } from "./printer.service.mjs";
|
|
11
11
|
import { PreviewService } from "./session/preview.service.mjs";
|
|
12
12
|
import { SessionService } from "./session/session.service.mjs";
|
|
13
|
-
import { Module } from "@nestjs/common";
|
|
13
|
+
import { ConfigurableModuleBuilder, Module } from "@nestjs/common";
|
|
14
|
+
import { FilesystemModule, JsonParser, ParserModule, YamlParser } from "@cenk1cenk2/oclif-common";
|
|
14
15
|
//#region src/services/printer.module.ts
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
PrinterModule = _PrinterModule = __decorate([Module({})], PrinterModule);
|
|
16
|
+
let PrinterModule = class PrinterModule extends new ConfigurableModuleBuilder({ optionsInjectionToken: PRINTER_MODULE_OPTIONS }).setClassMethodName("forRoot").build().ConfigurableModuleClass {};
|
|
17
|
+
PrinterModule = __decorate([Module({
|
|
18
|
+
imports: [FilesystemModule.forRoot(), ParserModule.forRoot({ parsers: [JsonParser, YamlParser] })],
|
|
19
|
+
providers: [
|
|
20
|
+
I18nService,
|
|
21
|
+
MarkdownService,
|
|
22
|
+
TemplateService,
|
|
23
|
+
BrowserService,
|
|
24
|
+
RendererService,
|
|
25
|
+
ATrustAdapter,
|
|
26
|
+
SignatureService,
|
|
27
|
+
PrinterService,
|
|
28
|
+
SessionService,
|
|
29
|
+
PreviewService
|
|
30
|
+
],
|
|
31
|
+
exports: [
|
|
32
|
+
PrinterService,
|
|
33
|
+
BrowserService,
|
|
34
|
+
SessionService,
|
|
35
|
+
PreviewService
|
|
36
|
+
]
|
|
37
|
+
})], PrinterModule);
|
|
40
38
|
//#endregion
|
|
41
39
|
export { PrinterModule };
|
|
@@ -8,9 +8,10 @@ import { inlineHtml } from "./template/assets.mjs";
|
|
|
8
8
|
import { RendererService } from "./renderer/renderer.service.mjs";
|
|
9
9
|
import { SignatureService } from "./signature/signature.service.mjs";
|
|
10
10
|
import { TemplateService } from "./template/template.service.mjs";
|
|
11
|
-
import { basename, dirname, extname, join, resolve } from "path";
|
|
12
|
-
import { FileSystemService, LoggerService, MergeStrategy, merge } from "@cenk1cenk2/oclif-common";
|
|
13
11
|
import { Injectable } from "@nestjs/common";
|
|
12
|
+
import { basename, dirname, extname, join, resolve } from "path";
|
|
13
|
+
import { FileSystemService, LoggerService } from "@cenk1cenk2/oclif-common";
|
|
14
|
+
import { cloneDeep, mergeWith, uniq } from "es-toolkit";
|
|
14
15
|
import { parse } from "yaml";
|
|
15
16
|
//#region src/services/printer.service.ts
|
|
16
17
|
let PrinterService = class PrinterService {
|
|
@@ -34,12 +35,11 @@ let PrinterService = class PrinterService {
|
|
|
34
35
|
async input(flags) {
|
|
35
36
|
let file;
|
|
36
37
|
let source;
|
|
37
|
-
if (flags.file) {
|
|
38
|
+
if (flags.file !== void 0) {
|
|
38
39
|
file = resolve(flags.file);
|
|
39
40
|
if (!this.fs.exists(file)) throw new Error(`File does not exist: ${file}`);
|
|
40
41
|
source = await this.fs.read(file);
|
|
41
|
-
} else
|
|
42
|
-
else throw new Error("Either --stdin or file argument must be provided.");
|
|
42
|
+
} else source = flags.stdin;
|
|
43
43
|
const type = flags.inputFiletype ?? (file ? extname(file).replace(/^\./, "") : "md");
|
|
44
44
|
if (type === "yaml" || type === "yml") return {
|
|
45
45
|
file,
|
|
@@ -145,7 +145,7 @@ let PrinterService = class PrinterService {
|
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
pdfOptions(...sources) {
|
|
148
|
-
const pdf =
|
|
148
|
+
const pdf = sources.reduce((merged, source) => mergeWith(merged, source ?? {}, (current, value) => Array.isArray(current) && Array.isArray(value) ? uniq([...current, ...value]) : void 0), cloneDeep(DEFAULT_PDF_OPTIONS));
|
|
149
149
|
if (typeof pdf.margin === "string") {
|
|
150
150
|
const [top, right = top, bottom = top, left = right] = pdf.margin.trim().split(/\s+/);
|
|
151
151
|
pdf.margin = {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import { once } from "events";
|
|
6
|
+
//#region src/services/renderer/app-window.ts
|
|
7
|
+
/**
|
|
8
|
+
* A browser window in app mode with a throwaway profile, so it shares no tabs, cookies or history with the browser of the user.
|
|
9
|
+
*/
|
|
10
|
+
var AppWindow = class AppWindow {
|
|
11
|
+
process;
|
|
12
|
+
profile;
|
|
13
|
+
/**
|
|
14
|
+
* Resolves when the window exits, rejects when the browser can not be started.
|
|
15
|
+
*/
|
|
16
|
+
closed;
|
|
17
|
+
constructor(process, profile) {
|
|
18
|
+
this.process = process;
|
|
19
|
+
this.profile = profile;
|
|
20
|
+
this.closed = new Promise((resolve, reject) => {
|
|
21
|
+
process.once("exit", () => resolve());
|
|
22
|
+
process.once("error", reject);
|
|
23
|
+
});
|
|
24
|
+
this.closed.catch(() => void 0);
|
|
25
|
+
}
|
|
26
|
+
static async open(executable, url) {
|
|
27
|
+
const profile = await mkdtemp(join(tmpdir(), "md-printer-window-"));
|
|
28
|
+
return new AppWindow(spawn(executable, [
|
|
29
|
+
`--app=${url}`,
|
|
30
|
+
`--user-data-dir=${profile}`,
|
|
31
|
+
"--no-first-run",
|
|
32
|
+
"--no-default-browser-check"
|
|
33
|
+
], { stdio: "ignore" }), profile);
|
|
34
|
+
}
|
|
35
|
+
async close() {
|
|
36
|
+
if (this.process.exitCode === null && this.process.signalCode === null && this.process.kill()) await once(this.process, "exit");
|
|
37
|
+
await rm(this.profile, {
|
|
38
|
+
recursive: true,
|
|
39
|
+
force: true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { AppWindow };
|
|
@@ -3,8 +3,8 @@ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/hel
|
|
|
3
3
|
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
4
|
import { discoverBrowser } from "./browser.mjs";
|
|
5
5
|
import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.mjs";
|
|
6
|
-
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
7
6
|
import { Inject, Injectable } from "@nestjs/common";
|
|
7
|
+
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
8
8
|
import puppeteer from "puppeteer-core";
|
|
9
9
|
//#region src/services/renderer/browser.service.ts
|
|
10
10
|
let BrowserService = class BrowserService {
|
|
@@ -3,10 +3,10 @@ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/hel
|
|
|
3
3
|
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
4
|
import { BrowserService } from "./browser.service.mjs";
|
|
5
5
|
import { mimeType, resolveConfined } from "../template/assets.mjs";
|
|
6
|
+
import { Injectable } from "@nestjs/common";
|
|
6
7
|
import { readFile } from "fs/promises";
|
|
7
8
|
import { resolve } from "path";
|
|
8
9
|
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
9
|
-
import { Injectable } from "@nestjs/common";
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
11
11
|
//#region src/services/renderer/renderer.service.ts
|
|
12
12
|
var _RendererService;
|
|
@@ -3,14 +3,12 @@ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/hel
|
|
|
3
3
|
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
4
|
import { resolveConfined } from "../template/assets.mjs";
|
|
5
5
|
import { isLoopbackRequest } from "../loopback.mjs";
|
|
6
|
+
import { AppWindow } from "../renderer/app-window.mjs";
|
|
6
7
|
import { TemplateService } from "../template/template.service.mjs";
|
|
7
|
-
import {
|
|
8
|
+
import { Injectable } from "@nestjs/common";
|
|
8
9
|
import { join } from "path";
|
|
9
10
|
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
10
|
-
import { Injectable } from "@nestjs/common";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
|
-
import { tmpdir } from "os";
|
|
13
|
-
import { spawn } from "child_process";
|
|
14
12
|
import { once } from "events";
|
|
15
13
|
import { createServer } from "http";
|
|
16
14
|
import serveStatic from "serve-static";
|
|
@@ -22,7 +20,6 @@ let PreviewService = class PreviewService {
|
|
|
22
20
|
clients = /* @__PURE__ */ new Set();
|
|
23
21
|
build;
|
|
24
22
|
error;
|
|
25
|
-
profile;
|
|
26
23
|
window;
|
|
27
24
|
constructor(logger, template) {
|
|
28
25
|
this.logger = logger;
|
|
@@ -60,39 +57,26 @@ let PreviewService = class PreviewService {
|
|
|
60
57
|
this.send("failed", this.error);
|
|
61
58
|
}
|
|
62
59
|
async open(executable, url) {
|
|
63
|
-
this.
|
|
64
|
-
this.window
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"--no-first-run",
|
|
68
|
-
"--no-default-browser-check"
|
|
69
|
-
], { stdio: "ignore" });
|
|
70
|
-
await new Promise((done) => {
|
|
71
|
-
this.window.on("exit", () => done());
|
|
72
|
-
this.window.on("error", (err) => {
|
|
73
|
-
this.logger.warn("Can not open the preview window, open it manually: %s (%s)", url, err.message);
|
|
74
|
-
});
|
|
60
|
+
this.window = await AppWindow.open(executable, url);
|
|
61
|
+
await this.window.closed.catch((err) => {
|
|
62
|
+
this.logger.warn("Can not open the preview window, open it manually: %s (%s)", url, err.message);
|
|
63
|
+
return new Promise(() => void 0);
|
|
75
64
|
});
|
|
76
65
|
}
|
|
77
66
|
async onModuleDestroy() {
|
|
78
67
|
await this.close();
|
|
79
68
|
}
|
|
80
69
|
async close() {
|
|
81
|
-
const { server, window
|
|
70
|
+
const { server, window } = this;
|
|
82
71
|
this.server = void 0;
|
|
83
72
|
this.window = void 0;
|
|
84
|
-
this.profile = void 0;
|
|
85
73
|
for (const client of this.clients) client.end();
|
|
86
74
|
this.clients.clear();
|
|
87
|
-
|
|
75
|
+
await window?.close();
|
|
88
76
|
if (server?.listening) {
|
|
89
77
|
server.close();
|
|
90
78
|
await once(server, "close");
|
|
91
79
|
}
|
|
92
|
-
if (profile) await rm(profile, {
|
|
93
|
-
recursive: true,
|
|
94
|
-
force: true
|
|
95
|
-
});
|
|
96
80
|
}
|
|
97
81
|
async handle(req, res, shell) {
|
|
98
82
|
if (!isLoopbackRequest(req)) {
|
|
@@ -2,9 +2,9 @@ import "../../constants/template.constants.mjs";
|
|
|
2
2
|
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
3
3
|
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
4
|
import { PrinterService } from "../printer.service.mjs";
|
|
5
|
+
import { Injectable } from "@nestjs/common";
|
|
5
6
|
import { dirname, join, resolve } from "path";
|
|
6
7
|
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
7
|
-
import { Injectable } from "@nestjs/common";
|
|
8
8
|
import { EventEmitter } from "events";
|
|
9
9
|
import { watch } from "chokidar";
|
|
10
10
|
import { debounce } from "perfect-debounce";
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import "../../../constants/signature.constants.mjs";
|
|
1
2
|
import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
2
3
|
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
|
+
import { BrowserNotFoundError } from "../../renderer/browser.mjs";
|
|
5
|
+
import { BrowserService } from "../../renderer/browser.service.mjs";
|
|
3
6
|
import { isLoopbackRequest } from "../../loopback.mjs";
|
|
7
|
+
import { AppWindow } from "../../renderer/app-window.mjs";
|
|
4
8
|
import { SignatureAdapter } from "../signature.adapter.mjs";
|
|
5
|
-
import { readFile } from "fs/promises";
|
|
6
|
-
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
7
9
|
import { Injectable } from "@nestjs/common";
|
|
10
|
+
import { readFile } from "fs/promises";
|
|
11
|
+
import { CommandContext, LoggerService } from "@cenk1cenk2/oclif-common";
|
|
8
12
|
import { spawn } from "child_process";
|
|
9
13
|
import { once } from "events";
|
|
10
14
|
import { createServer } from "http";
|
|
@@ -12,10 +16,14 @@ import getRawBody from "raw-body";
|
|
|
12
16
|
//#region src/services/signature/adapters/a-trust.adapter.ts
|
|
13
17
|
let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
|
|
14
18
|
logger;
|
|
19
|
+
context;
|
|
20
|
+
browser;
|
|
15
21
|
name = "a-trust";
|
|
16
|
-
constructor(logger) {
|
|
22
|
+
constructor(logger, context, browser) {
|
|
17
23
|
super();
|
|
18
24
|
this.logger = logger;
|
|
25
|
+
this.context = context;
|
|
26
|
+
this.browser = browser;
|
|
19
27
|
this.logger.setup(this.constructor.name);
|
|
20
28
|
}
|
|
21
29
|
async sign(request) {
|
|
@@ -51,8 +59,10 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
|
|
|
51
59
|
}
|
|
52
60
|
async collect(login) {
|
|
53
61
|
let resolve;
|
|
54
|
-
|
|
62
|
+
let reject;
|
|
63
|
+
const result = new Promise((done, fail) => {
|
|
55
64
|
resolve = done;
|
|
65
|
+
reject = fail;
|
|
56
66
|
});
|
|
57
67
|
const page = await readFile(new URL("./a-trust.html", import.meta.url));
|
|
58
68
|
const server = createServer(async (req, res) => {
|
|
@@ -84,23 +94,44 @@ let ATrustAdapter = class ATrustAdapter extends SignatureAdapter {
|
|
|
84
94
|
return res.end("This does not look like a signature, it should start with MII.");
|
|
85
95
|
}
|
|
86
96
|
res.writeHead(200);
|
|
87
|
-
res.end("Signature received
|
|
97
|
+
res.end("Signature received.");
|
|
88
98
|
resolve(Buffer.from(cms, "base64"));
|
|
89
99
|
});
|
|
90
100
|
server.listen(0, "127.0.0.1");
|
|
91
101
|
await once(server, "listening");
|
|
92
102
|
const url = `http://127.0.0.1:${server.address().port}/`;
|
|
93
103
|
this.logger.info("Sign the document in the browser, then paste the signature back: %s", url);
|
|
94
|
-
this.open(url)
|
|
95
|
-
|
|
104
|
+
const window = await this.open(url).catch((err) => {
|
|
105
|
+
server.close();
|
|
106
|
+
throw err;
|
|
107
|
+
});
|
|
108
|
+
this.context.signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("Signing is aborted.")), { once: true });
|
|
109
|
+
window?.closed.then(() => reject(/* @__PURE__ */ new Error("The signing window is closed before the signature arrived.")), (err) => this.logger.warn("Can not open the signing window, open the link manually: %s (%s)", url, err.message));
|
|
110
|
+
return result.finally(async () => {
|
|
111
|
+
server.close();
|
|
112
|
+
await window?.close();
|
|
113
|
+
});
|
|
96
114
|
}
|
|
97
|
-
open(url) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
115
|
+
async open(url) {
|
|
116
|
+
let executable;
|
|
117
|
+
try {
|
|
118
|
+
executable = this.browser.executable();
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (!(err instanceof BrowserNotFoundError)) throw err;
|
|
121
|
+
this.logger.warn("Can not find a browser to sign in a window of its own, close the tabs of any earlier signing before you continue. %s", err.message);
|
|
122
|
+
spawn("xdg-open", [url], {
|
|
123
|
+
stdio: "ignore",
|
|
124
|
+
detached: true
|
|
125
|
+
}).on("error", (err) => this.logger.warn("Can not open the browser, open the link manually: %s", err.message)).unref();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
return AppWindow.open(executable, url);
|
|
102
129
|
}
|
|
103
130
|
};
|
|
104
|
-
ATrustAdapter = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [
|
|
131
|
+
ATrustAdapter = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [
|
|
132
|
+
typeof LoggerService === "undefined" ? Object : LoggerService,
|
|
133
|
+
typeof CommandContext === "undefined" ? Object : CommandContext,
|
|
134
|
+
typeof BrowserService === "undefined" ? Object : BrowserService
|
|
135
|
+
])], ATrustAdapter);
|
|
105
136
|
//#endregion
|
|
106
137
|
export { ATrustAdapter };
|
|
@@ -1,24 +1,24 @@
|
|
|
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
|
-
import { X509Certificate } from "crypto";
|
|
9
|
+
import { X509Certificate, webcrypto } from "crypto";
|
|
10
10
|
import { homedir } from "os";
|
|
11
|
+
import { ContentInfo, SignedData, getAlgorithmByOID } from "pkijs";
|
|
11
12
|
//#region src/services/signature/signature.service.ts
|
|
12
13
|
var _SignatureService;
|
|
13
14
|
let SignatureService = class SignatureService {
|
|
14
15
|
static {
|
|
15
16
|
_SignatureService = this;
|
|
16
17
|
}
|
|
17
|
-
fs;
|
|
18
18
|
static LENGTH = 16384;
|
|
19
|
+
static MESSAGE_DIGEST = "1.2.840.113549.1.9.4";
|
|
19
20
|
adapters;
|
|
20
|
-
constructor(
|
|
21
|
-
this.fs = fs;
|
|
21
|
+
constructor(aTrust) {
|
|
22
22
|
this.adapters = [aTrust];
|
|
23
23
|
}
|
|
24
24
|
get providers() {
|
|
@@ -26,7 +26,7 @@ let SignatureService = class SignatureService {
|
|
|
26
26
|
}
|
|
27
27
|
async certificate(file, date) {
|
|
28
28
|
const path = file.replace(/^~(?=\/|$)/, homedir());
|
|
29
|
-
const certificate = new X509Certificate(await
|
|
29
|
+
const certificate = new X509Certificate(await readFile(isAbsolute(path) ? path : join(process.cwd(), path)));
|
|
30
30
|
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}.`);
|
|
31
31
|
return certificate;
|
|
32
32
|
}
|
|
@@ -41,7 +41,7 @@ let SignatureService = class SignatureService {
|
|
|
41
41
|
async sign(pdf, options) {
|
|
42
42
|
const request = this.prepare(pdf, options);
|
|
43
43
|
const cms = await this.adapter(options.provider).sign(request);
|
|
44
|
-
|
|
44
|
+
await this.verify(request, cms, options.certificate);
|
|
45
45
|
return this.embed(request, cms);
|
|
46
46
|
}
|
|
47
47
|
adapter(provider) {
|
|
@@ -76,6 +76,30 @@ let SignatureService = class SignatureService {
|
|
|
76
76
|
excluded: [from, to]
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
+
async verify({ pdf, excluded: [from, to] }, cms, certificate) {
|
|
80
|
+
const content = new Uint8Array(Buffer.concat([pdf.subarray(0, from), pdf.subarray(to + 1)])).buffer;
|
|
81
|
+
let signed;
|
|
82
|
+
try {
|
|
83
|
+
signed = new SignedData({ schema: ContentInfo.fromBER(new Uint8Array(cms)).content });
|
|
84
|
+
} catch (err) {
|
|
85
|
+
throw new Error(`The returned signature is not a CMS signature: ${err.message}`, { cause: err });
|
|
86
|
+
}
|
|
87
|
+
const [info] = signed.signerInfos;
|
|
88
|
+
const digest = info?.signedAttrs?.attributes.find((attribute) => attribute.type === _SignatureService.MESSAGE_DIGEST);
|
|
89
|
+
if (!digest) throw new Error("The returned signature does not carry the digest of the document it signs.");
|
|
90
|
+
const algorithm = getAlgorithmByOID(info.digestAlgorithm.algorithmId, true, "digestAlgorithm");
|
|
91
|
+
if (!Buffer.from(digest.values[0].valueBlock.valueHexView).equals(Buffer.from(await webcrypto.subtle.digest(algorithm.name, content)))) throw new Error("The returned signature belongs to a different document, it is likely left over from an earlier signing.");
|
|
92
|
+
const { signatureVerified, signerCertificate } = await signed.verify({
|
|
93
|
+
signer: 0,
|
|
94
|
+
data: content,
|
|
95
|
+
extendedMode: true
|
|
96
|
+
}).catch((err) => {
|
|
97
|
+
throw new Error(`Can not verify the returned signature: ${err.message}`, { cause: err });
|
|
98
|
+
});
|
|
99
|
+
const signer = new X509Certificate(Buffer.from(signerCertificate.toSchema().toBER()));
|
|
100
|
+
if (certificate && !signer.raw.equals(certificate.raw)) throw new Error([`The returned signature is signed by ${this.identify(signer)}, but the configured certificate is ${this.identify(certificate)}.`, "The signature block would show the wrong signer."].join(" "));
|
|
101
|
+
if (!signatureVerified) throw new Error(`The returned signature does not verify against the certificate of ${this.identify(signer)}.`);
|
|
102
|
+
}
|
|
79
103
|
embed({ pdf, excluded: [from, to] }, cms) {
|
|
80
104
|
const signature = cms.toString("hex");
|
|
81
105
|
const length = to - from - 1;
|
|
@@ -86,10 +110,13 @@ let SignatureService = class SignatureService {
|
|
|
86
110
|
pdf.subarray(to)
|
|
87
111
|
]);
|
|
88
112
|
}
|
|
113
|
+
identify(certificate) {
|
|
114
|
+
return `${this.commonName(certificate.subject)} (serial ${certificate.serialNumber})`;
|
|
115
|
+
}
|
|
89
116
|
commonName(name) {
|
|
90
117
|
return name.split("\n").find((part) => part.startsWith("CN="))?.slice(3);
|
|
91
118
|
}
|
|
92
119
|
};
|
|
93
|
-
SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof
|
|
120
|
+
SignatureService = _SignatureService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof ATrustAdapter === "undefined" ? Object : ATrustAdapter])], SignatureService);
|
|
94
121
|
//#endregion
|
|
95
122
|
export { SignatureService };
|
|
@@ -3,9 +3,9 @@ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/hel
|
|
|
3
3
|
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
4
|
import { inlineCss, inlineHtml } from "./assets.mjs";
|
|
5
5
|
import { sveltePlugin } from "./svelte.plugin.mjs";
|
|
6
|
+
import { Injectable } from "@nestjs/common";
|
|
6
7
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
7
8
|
import { FileSystemService } from "@cenk1cenk2/oclif-common";
|
|
8
|
-
import { Injectable } from "@nestjs/common";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
import { homedir } from "os";
|
|
11
11
|
import { compile } from "@tailwindcss/node";
|
package/oclif.manifest.json
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"output": {
|
|
11
11
|
"description": "Output file. Overwrites the one defined in front-matter.",
|
|
12
|
-
"name": "output"
|
|
13
|
-
"required": false
|
|
12
|
+
"name": "output"
|
|
14
13
|
}
|
|
15
14
|
},
|
|
16
15
|
"description": "Renders a markdown or yaml file through a Svelte template into a PDF or HTML document.",
|
|
@@ -176,6 +175,9 @@
|
|
|
176
175
|
"name": "sign-provider",
|
|
177
176
|
"hasDynamicHelp": false,
|
|
178
177
|
"multiple": false,
|
|
178
|
+
"options": [
|
|
179
|
+
"a-trust"
|
|
180
|
+
],
|
|
179
181
|
"type": "option"
|
|
180
182
|
},
|
|
181
183
|
"sign-certificate": {
|
|
@@ -196,5 +198,5 @@
|
|
|
196
198
|
"enableJsonFlag": false
|
|
197
199
|
}
|
|
198
200
|
},
|
|
199
|
-
"version": "3.
|
|
201
|
+
"version": "3.1.1"
|
|
200
202
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cenk1cenk2/md-printer",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Render markdown or yaml through Svelte templates into PDF or HTML with Puppeteer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"target": "./dist/commands/index.mjs"
|
|
30
30
|
},
|
|
31
31
|
"plugins": [
|
|
32
|
-
"@oclif/plugin-help"
|
|
32
|
+
"@oclif/plugin-help",
|
|
33
|
+
"@cenk1cenk2/oclif-common"
|
|
33
34
|
],
|
|
34
35
|
"topicSeparator": " "
|
|
35
36
|
},
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"cenk1cenk2"
|
|
49
50
|
],
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@cenk1cenk2/oclif-common": "^
|
|
52
|
+
"@cenk1cenk2/oclif-common": "^8.0.0",
|
|
52
53
|
"@fontsource/montserrat": "^5.3.0",
|
|
53
54
|
"@listr2/manager": "^4.3.1",
|
|
54
55
|
"@nestjs/common": "^12.1.2",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"chokidar": "^5.0.0",
|
|
64
65
|
"enhanced-resolve": "^5.26.0",
|
|
65
66
|
"entities": "^8.1.0",
|
|
66
|
-
"
|
|
67
|
+
"es-toolkit": "^1.52.0",
|
|
67
68
|
"github-markdown-css": "^5.9.0",
|
|
68
69
|
"hast-util-from-html": "^2.0.3",
|
|
69
70
|
"hast-util-to-html": "^9.0.5",
|
|
@@ -74,6 +75,7 @@
|
|
|
74
75
|
"lowlight": "^3.3.0",
|
|
75
76
|
"mime": "^4.1.1",
|
|
76
77
|
"perfect-debounce": "^2.1.0",
|
|
78
|
+
"pkijs": "^3.4.1",
|
|
77
79
|
"property-information": "^7.2.0",
|
|
78
80
|
"puppeteer-core": "^25.13.0",
|
|
79
81
|
"raw-body": "^4.0.0",
|
|
@@ -98,19 +100,20 @@
|
|
|
98
100
|
"vfile-matter": "^5.0.1",
|
|
99
101
|
"which": "^7.0.0",
|
|
100
102
|
"write-file-atomic": "^8.0.0",
|
|
101
|
-
"yaml": "^2.9.1"
|
|
103
|
+
"yaml": "^2.9.1",
|
|
104
|
+
"zod": "^4.6.7"
|
|
102
105
|
},
|
|
103
106
|
"devDependencies": {
|
|
104
107
|
"@cenk1cenk2/cz-cc": "^3.0.6",
|
|
105
108
|
"@cenk1cenk2/eslint-config": "^3.3.9",
|
|
106
109
|
"@tsconfig/node24": "^24.0.5",
|
|
107
|
-
"@types/fs-extra": "^11.0.4",
|
|
108
110
|
"@types/hast": "^3.0.5",
|
|
109
111
|
"@types/mdast": "^4.0.4",
|
|
110
112
|
"@types/node": "^24.19.1",
|
|
111
113
|
"@types/serve-static": "^2.2.0",
|
|
112
114
|
"@types/which": "^3.0.4",
|
|
113
115
|
"@types/write-file-atomic": "^4.0.3",
|
|
116
|
+
"asn1js": "^3.0.10",
|
|
114
117
|
"eslint": "^10.12.0",
|
|
115
118
|
"eslint-plugin-svelte": "^3.23.1",
|
|
116
119
|
"execa": "^10.1.0",
|