mduck 1.0.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mduck CHANGED
@@ -1,2 +1,49 @@
1
- #!/bin/sh
2
- exec bun "$(dirname "$0")/mduck.js" "$@" 2>/dev/null || exec node "$(dirname "$0")/mduck.js" "$@"
1
+ #!/bin/sh -
2
+ ':'; /*-
3
+ test1=$(bun --version 2>&1) && exec bun "$0" "$@"
4
+ test2=$(node --version 2>&1) && exec node "$0" "$@"
5
+ exec printf '%s\n' "$test1" "$test2" 1>&2
6
+ */
7
+
8
+ import { execFileSync } from "child_process";
9
+ import { arch, platform } from "os";
10
+ import { resolve, dirname } from "path";
11
+ import { fileURLToPath } from "url";
12
+ import { existsSync } from "fs";
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const plat = platform();
16
+ const a = arch();
17
+ const ext = plat === "win32" ? ".exe" : "";
18
+ const pkgName = `mduck-${plat}-${a}`;
19
+
20
+ const candidates = [
21
+ // Typical node_modules layout (npx, npm install -g, etc.)
22
+ resolve(__dirname, "..", "node_modules", "@simplyhexagonal", pkgName, "bin", `mduck${ext}`),
23
+ // pnpm / hoisted layout
24
+ resolve(__dirname, "..", "..", "@simplyhexagonal", pkgName, "bin", `mduck${ext}`),
25
+ // Global install with lib/node_modules
26
+ resolve(__dirname, "..", "lib", "node_modules", "@simplyhexagonal", pkgName, "bin", `mduck${ext}`),
27
+ // Bun global layout
28
+ resolve(__dirname, "..", "..", "..", "@simplyhexagonal", pkgName, "bin", `mduck${ext}`),
29
+ ];
30
+
31
+ const binary = candidates.find((c) => existsSync(c));
32
+
33
+ if (!binary) {
34
+ const supported = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-x64"];
35
+ console.error(`\n mduck: no binary found for ${plat}-${a}\n`);
36
+ console.error(` Supported platforms: ${supported.join(", ")}`);
37
+ console.error(` Searched:`);
38
+ for (const c of candidates) {
39
+ console.error(` - ${c}`);
40
+ }
41
+ console.error(`\n Try reinstalling: npm install -g mduck\n`);
42
+ process.exit(1);
43
+ }
44
+
45
+ try {
46
+ execFileSync(binary, process.argv.slice(2), { stdio: "inherit" });
47
+ } catch (e) {
48
+ process.exit(e.status ?? 1);
49
+ }
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "mduck",
3
- "version": "1.0.6",
3
+ "version": "2.0.0",
4
4
  "description": "A CLI tool to convert Markdown files into HTML and PDF documents using Tailwind CSS v4 for styling.",
5
5
  "files": [
6
6
  "bin/",
7
- "src/template.html",
8
7
  "README.md"
9
8
  ],
10
9
  "keywords": [
@@ -19,26 +18,33 @@
19
18
  ],
20
19
  "author": "Jean M. Lescure",
21
20
  "license": "Apache-2.0",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/simplyhexagonal/mduck.git"
24
+ },
22
25
  "bin": {
23
26
  "mduck": "./bin/mduck"
24
27
  },
25
28
  "scripts": {
26
- "build": "tsup --config tsup.config.ts && chmod +x ./bin/mduck ./bin/mduck.js",
29
+ "build": "bun run build.ts",
30
+ "publish-all": "bun run publish.ts",
31
+ "publish-all:dry": "bun run publish.ts --dry-run",
32
+ "prepack": "bun run build",
27
33
  "dev": "bun --watch run ./src/index.ts convert test/input/test.md test/output/"
28
34
  },
29
35
  "type": "module",
30
- "devDependencies": {
31
- "@types/bun": "latest",
32
- "tsup": "^8.5.1"
36
+ "optionalDependencies": {
37
+ "@simplyhexagonal/mduck-darwin-arm64": "2.0.0",
38
+ "@simplyhexagonal/mduck-darwin-x64": "2.0.0",
39
+ "@simplyhexagonal/mduck-linux-arm64": "2.0.0",
40
+ "@simplyhexagonal/mduck-linux-x64": "2.0.0",
41
+ "@simplyhexagonal/mduck-win32-x64": "2.0.0"
33
42
  },
34
- "peerDependencies": {
35
- "typescript": "^5"
36
- },
37
- "dependencies": {
43
+ "devDependencies": {
38
44
  "@tailwindcss/typography": "^0.5.19",
39
45
  "citty": "^0.2.1",
40
46
  "marked": "^17.0.4",
41
- "puppeteer": "^24.38.0",
47
+ "puppeteer-core": "^24.38.0",
42
48
  "tailwindcss": "^4.2.1"
43
49
  }
44
50
  }
package/bin/mduck.cmd DELETED
@@ -1,9 +0,0 @@
1
- @echo off
2
- setlocal enabledelayedexpansion
3
- set scriptdir=%~dp0
4
- set scriptpath=!scriptdir!mduck.js
5
-
6
- bun "!scriptpath!" %* 2>nul
7
- if !errorlevel! equ 0 goto :eof
8
-
9
- node "!scriptpath!" %*
package/bin/mduck.js DELETED
@@ -1,317 +0,0 @@
1
- // src/index.ts
2
- import { mkdir, readFile, stat, writeFile } from "fs/promises";
3
- import { basename, dirname, extname, isAbsolute, join, resolve, sep } from "path";
4
- import { createRequire } from "module";
5
- import { pathToFileURL } from "url";
6
- import { defineCommand, runMain } from "citty";
7
- import { marked } from "marked";
8
- import puppeteer from "puppeteer";
9
- import { compile } from "tailwindcss";
10
-
11
- // src/template.html
12
- var template_default = '<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>Document</title>\n</head>\n<body>\n <article class="prose lg:prose-xl print:prose-sm mx-auto my-8 print:m-0 px-4">\n [BODY]\n </article>\n <style>\n @media print {\n @page {\n margin: 2cm !important; /* Sets a 2cm margin on all sides of every printed page */\n }\n }\n table {\n border: 1px solid black;\n border-collapse: collapse;\n }\n th, td {\n border: 1px solid black;\n padding: 0.5em;\n }\n </style>\n</body>\n</html>\n';
13
-
14
- // package.json
15
- var package_default = {
16
- name: "mduck",
17
- version: "1.0.5",
18
- description: "A CLI tool to convert Markdown files into HTML and PDF documents using Tailwind CSS v4 for styling.",
19
- files: [
20
- "bin/",
21
- "src/template.html",
22
- "README.md"
23
- ],
24
- keywords: [
25
- "markdown",
26
- "md",
27
- "html",
28
- "pdf",
29
- "converter",
30
- "cli",
31
- "tailwind",
32
- "puppeteer"
33
- ],
34
- author: "Jean M. Lescure",
35
- license: "Apache-2.0",
36
- bin: {
37
- mduck: "./bin/mduck.js"
38
- },
39
- scripts: {
40
- build: "tsup --config tsup.config.ts",
41
- dev: "bun --watch run ./src/index.ts convert test/input/test.md test/output/"
42
- },
43
- type: "module",
44
- devDependencies: {
45
- "@types/bun": "latest",
46
- tsup: "^8.5.1"
47
- },
48
- peerDependencies: {
49
- typescript: "^5"
50
- },
51
- dependencies: {
52
- "@tailwindcss/typography": "^0.5.19",
53
- citty: "^0.2.1",
54
- marked: "^17.0.4",
55
- puppeteer: "^24.38.0",
56
- tailwindcss: "^4.2.1"
57
- }
58
- };
59
-
60
- // src/index.ts
61
- var BODY_PLACEHOLDER = "[BODY]";
62
- var DEFAULT_TEMPLATE_HTML = template_default;
63
- var DEFAULT_EJECTED_TEMPLATE_FILENAME = "mduck.template.html";
64
- var RUNTIME_ENTRY_DIR = dirname(resolve(process.argv[1] ?? process.cwd()));
65
- var require2 = createRequire(resolve(RUNTIME_ENTRY_DIR, "__mduck_require__.cjs"));
66
- var TAILWIND_INPUT_CSS = `@import "tailwindcss";
67
- @plugin "@tailwindcss/typography";`;
68
- var pageBreak = {
69
- name: "pageBreak",
70
- level: "inline",
71
- start: (src) => src.indexOf("[PAGE-BREAK]"),
72
- tokenizer: (src) => {
73
- if (src.startsWith("[PAGE-BREAK]")) {
74
- return { type: "pageBreak", raw: "[PAGE-BREAK]" };
75
- }
76
- },
77
- renderer: () => '<div class="break-after-page"></div>'
78
- };
79
- var toAbsolutePath = (filePath) => {
80
- return isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
81
- };
82
- var inferOutputPath = (inputPath) => {
83
- const inputExt = extname(inputPath).toLowerCase();
84
- if (inputExt === ".md") {
85
- return inputPath.slice(0, -3) + ".html";
86
- }
87
- return `${inputPath}.html`;
88
- };
89
- var inferHtmlBasename = (inputPath) => {
90
- return basename(inferOutputPath(inputPath));
91
- };
92
- var inferPdfPath = (htmlOutputPath) => {
93
- const outputExt = extname(htmlOutputPath).toLowerCase();
94
- if (!outputExt) {
95
- return `${htmlOutputPath}.pdf`;
96
- }
97
- return `${htmlOutputPath.slice(0, -outputExt.length)}.pdf`;
98
- };
99
- var renderDocument = (template, markdownHtml) => {
100
- const body = markdownHtml;
101
- if (template.includes(BODY_PLACEHOLDER)) {
102
- return template.replace(BODY_PLACEHOLDER, body);
103
- }
104
- const bodyCloseTag = "</body>";
105
- if (template.includes(bodyCloseTag)) {
106
- return template.replace(bodyCloseTag, `${body}
107
- ${bodyCloseTag}`);
108
- }
109
- return `${template}
110
- ${body}`;
111
- };
112
- var collectTailwindCandidates = (html) => {
113
- const candidates = /* @__PURE__ */ new Set(["prose", "lg:prose-xl", "mx-auto", "my-8", "px-4"]);
114
- const classRegex = /class\s*=\s*"([^"]+)"/g;
115
- for (const match of html.matchAll(classRegex)) {
116
- const classValue = match[1];
117
- for (const className of classValue?.split(/\s+/) ?? []) {
118
- const trimmed = className.trim();
119
- if (trimmed) {
120
- candidates.add(trimmed);
121
- }
122
- }
123
- }
124
- return Array.from(candidates);
125
- };
126
- var resolveCssImportPath = (id, base) => {
127
- if (id === "tailwindcss") {
128
- return require2.resolve("tailwindcss/index.css", { paths: [base, process.cwd()] });
129
- }
130
- if (id.startsWith("tailwindcss/")) {
131
- const resolvedId = id.endsWith(".css") ? id : `${id}.css`;
132
- return require2.resolve(resolvedId, { paths: [base, process.cwd()] });
133
- }
134
- try {
135
- return require2.resolve(id, { paths: [base] });
136
- } catch {
137
- return require2.resolve(id, { paths: [process.cwd()] });
138
- }
139
- };
140
- var compileTailwindCss = async (html) => {
141
- const compiler = await compile(TAILWIND_INPUT_CSS, {
142
- base: process.cwd(),
143
- from: "mduck.inline.css",
144
- loadModule: async (id, base) => {
145
- const resolvedPath = resolveCssImportPath(id, base);
146
- const imported = await import(pathToFileURL(resolvedPath).href);
147
- const module = imported.default ?? imported;
148
- return { path: resolvedPath, base: dirname(resolvedPath), module };
149
- },
150
- loadStylesheet: async (id, base) => {
151
- const resolvedPath = resolveCssImportPath(id, base);
152
- const content = await readFile(resolvedPath, "utf8");
153
- return { path: resolvedPath, base: dirname(resolvedPath), content };
154
- }
155
- });
156
- return compiler.build(collectTailwindCandidates(html));
157
- };
158
- var inlineCssInHead = (html, css) => {
159
- const styleTag = `<style>${css}</style>`;
160
- const headCloseTag = "</head>";
161
- if (html.includes(headCloseTag)) {
162
- return html.replace(headCloseTag, ` ${styleTag}
163
- ${headCloseTag}`);
164
- }
165
- return `${styleTag}
166
- ${html}`;
167
- };
168
- var renderPdf = async (html, outputPath) => {
169
- const browser = await puppeteer.launch();
170
- try {
171
- const page = await browser.newPage();
172
- await page.setContent(html, { waitUntil: "networkidle0" });
173
- await page.pdf({
174
- path: outputPath,
175
- format: "Letter",
176
- printBackground: true,
177
- preferCSSPageSize: true
178
- });
179
- } finally {
180
- await browser.close();
181
- }
182
- };
183
- var resolveEjectedTemplatePath = async (outputArg) => {
184
- if (!outputArg) {
185
- return resolve(process.cwd(), DEFAULT_EJECTED_TEMPLATE_FILENAME);
186
- }
187
- const resolvedTargetPath = toAbsolutePath(outputArg);
188
- const targetStats = await stat(resolvedTargetPath).catch(() => null);
189
- const looksLikeDirectory = outputArg.endsWith(sep) || outputArg.endsWith("/");
190
- const isDirectoryTarget = Boolean(targetStats?.isDirectory() || looksLikeDirectory);
191
- if (isDirectoryTarget) {
192
- return join(resolvedTargetPath, DEFAULT_EJECTED_TEMPLATE_FILENAME);
193
- }
194
- return resolvedTargetPath;
195
- };
196
- var runEjectTemplate = async (outputArg) => {
197
- const templateOutputPath = await resolveEjectedTemplatePath(outputArg);
198
- await mkdir(dirname(templateOutputPath), { recursive: true });
199
- await writeFile(templateOutputPath, DEFAULT_TEMPLATE_HTML, "utf8");
200
- console.log(`Wrote template to: ${templateOutputPath}`);
201
- };
202
- var runVersion = async () => {
203
- const version = typeof package_default.version === "string" ? package_default.version : "unknown";
204
- console.log(version);
205
- };
206
- var runConvert = async (inputArg, outputArg, templateArg) => {
207
- marked.use({ extensions: [pageBreak] });
208
- const inputPath = toAbsolutePath(inputArg);
209
- let outputPath = toAbsolutePath(outputArg ?? inferOutputPath(inputPath));
210
- let activeTemplateHtml = DEFAULT_TEMPLATE_HTML;
211
- const inputStats = await stat(inputPath).catch(() => null);
212
- if (!inputStats) {
213
- throw new Error(`Input file does not exist: ${inputPath}`);
214
- }
215
- if (!inputStats.isFile()) {
216
- throw new Error(`Input path is not a file: ${inputPath}`);
217
- }
218
- if (outputArg) {
219
- const outputStats = await stat(outputPath).catch(() => null);
220
- if (outputStats?.isDirectory()) {
221
- outputPath = join(outputPath, inferHtmlBasename(inputPath));
222
- }
223
- }
224
- if (templateArg) {
225
- const templatePath = toAbsolutePath(templateArg);
226
- const templateStats = await stat(templatePath).catch(() => null);
227
- if (!templateStats) {
228
- throw new Error(`Template file does not exist: ${templatePath}`);
229
- }
230
- if (!templateStats.isFile()) {
231
- throw new Error(`Template path is not a file: ${templatePath}`);
232
- }
233
- activeTemplateHtml = await readFile(templatePath, "utf8");
234
- }
235
- const markdown = await readFile(inputPath, "utf8");
236
- const markdownHtml = await marked.parse(markdown);
237
- const documentHtml = renderDocument(activeTemplateHtml, markdownHtml);
238
- const tailwindCss = await compileTailwindCss(documentHtml);
239
- const html = inlineCssInHead(documentHtml, tailwindCss);
240
- const pdfOutputPath = inferPdfPath(outputPath);
241
- console.log("Rendering HTML...");
242
- await mkdir(dirname(outputPath), { recursive: true });
243
- await writeFile(outputPath, html, "utf8");
244
- console.log(`Wrote HTML to: ${outputPath}`);
245
- console.log("Rendering PDF...");
246
- await mkdir(dirname(pdfOutputPath), { recursive: true });
247
- await renderPdf(html, pdfOutputPath);
248
- console.log(`Wrote PDF to: ${pdfOutputPath}`);
249
- };
250
- var convertCommand = defineCommand({
251
- meta: {
252
- name: "convert",
253
- description: "Convert Markdown files to HTML and PDF."
254
- },
255
- args: {
256
- inputFile: {
257
- type: "positional",
258
- description: "Input markdown file path",
259
- required: true
260
- },
261
- outputFile: {
262
- type: "positional",
263
- description: "Optional output HTML file path (or existing directory)",
264
- required: false
265
- },
266
- template: {
267
- type: "string",
268
- description: "Optional HTML template path (absolute or relative)",
269
- required: false,
270
- alias: "t"
271
- }
272
- },
273
- run: async ({ args }) => {
274
- const inputArg = String(args.inputFile);
275
- const outputArg = typeof args.outputFile === "string" ? args.outputFile : void 0;
276
- const templateArg = typeof args.template === "string" ? args.template : void 0;
277
- await runConvert(inputArg, outputArg, templateArg);
278
- }
279
- });
280
- var ejectTemplateCommand = defineCommand({
281
- meta: {
282
- name: "eject-template",
283
- description: "Write the default HTML template to disk."
284
- },
285
- args: {
286
- outputTemplatePath: {
287
- type: "positional",
288
- description: "Optional output file path or target directory",
289
- required: false
290
- }
291
- },
292
- run: async ({ args }) => {
293
- const outputArg = typeof args.outputTemplatePath === "string" ? args.outputTemplatePath : void 0;
294
- await runEjectTemplate(outputArg);
295
- }
296
- });
297
- var versionCommand = defineCommand({
298
- meta: {
299
- name: "version",
300
- description: "Print current CLI version."
301
- },
302
- run: async () => {
303
- await runVersion();
304
- }
305
- });
306
- var main = defineCommand({
307
- meta: {
308
- name: "mduck",
309
- description: "Convert Markdown files to HTML/PDF and manage templates."
310
- },
311
- subCommands: {
312
- convert: convertCommand,
313
- "eject-template": ejectTemplateCommand,
314
- version: versionCommand
315
- }
316
- });
317
- await runMain(main);
package/src/template.html DELETED
@@ -1,28 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Document</title>
7
- </head>
8
- <body>
9
- <article class="prose lg:prose-xl print:prose-sm mx-auto my-8 print:m-0 px-4">
10
- [BODY]
11
- </article>
12
- <style>
13
- @media print {
14
- @page {
15
- margin: 2cm !important; /* Sets a 2cm margin on all sides of every printed page */
16
- }
17
- }
18
- table {
19
- border: 1px solid black;
20
- border-collapse: collapse;
21
- }
22
- th, td {
23
- border: 1px solid black;
24
- padding: 0.5em;
25
- }
26
- </style>
27
- </body>
28
- </html>