@grainulation/mill 1.0.4 → 1.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/bin/mill.js CHANGED
@@ -476,7 +476,17 @@ async function runCiArtifacts(args) {
476
476
  }
477
477
 
478
478
  function runServe(args) {
479
+ const fs = require("node:fs");
479
480
  const serverPath = path.join(LIB_DIR, "server.js");
481
+ if (!fs.existsSync(serverPath)) {
482
+ console.error(
483
+ "mill: server.js is not included in the npm package.\n" +
484
+ "The serve command requires a full clone of the repository.\n\n" +
485
+ " git clone https://github.com/grainulation/mill.git\n" +
486
+ " cd mill && node bin/mill.js serve"
487
+ );
488
+ process.exit(1);
489
+ }
480
490
  const child = fork(serverPath, args, { stdio: "inherit" });
481
491
  child.on("exit", (code) => process.exit(code ?? 0));
482
492
  process.on("SIGTERM", () => child.kill("SIGTERM"));
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * PDF worker — standalone script that converts an HTML file to PDF using puppeteer.
6
+ *
7
+ * Usage: node pdf-worker.js <inputPath> <outputPath>
8
+ *
9
+ * Extracted from pdf.js to avoid constructing JS as a string and executing
10
+ * via `node -e`, which triggers Socket AI-anomaly alerts.
11
+ */
12
+
13
+ const inputPath = process.argv[2];
14
+ const outputPath = process.argv[3];
15
+
16
+ if (!inputPath || !outputPath) {
17
+ process.stderr.write("Usage: node pdf-worker.js <inputPath> <outputPath>\n");
18
+ process.exit(1);
19
+ }
20
+
21
+ (async () => {
22
+ const puppeteer = require("puppeteer");
23
+ const browser = await puppeteer.launch({ headless: "new" });
24
+ const page = await browser.newPage();
25
+ await page.goto(`file://${inputPath}`, { waitUntil: "networkidle0" });
26
+ await page.pdf({ path: outputPath, format: "A4", printBackground: true });
27
+ await browser.close();
28
+ })();
@@ -5,7 +5,7 @@
5
5
  // which needs network access. All other mill export formats are zero-dep.
6
6
 
7
7
  const path = require("node:path");
8
- const { execFile } = require("node:child_process");
8
+ const { execFile, execFileSync } = require("node:child_process");
9
9
 
10
10
  /**
11
11
  * Export HTML or Markdown files to PDF.
@@ -70,19 +70,9 @@ async function exportFromMarkdown(inputPath, outputPath) {
70
70
 
71
71
  async function exportFromHtml(inputPath, outputPath) {
72
72
  const out = deriveOutputPath(inputPath, outputPath);
73
- // Use a small inline puppeteer script via npx
74
- const script = `
75
- const puppeteer = require('puppeteer');
76
- (async () => {
77
- const browser = await puppeteer.launch({ headless: 'new' });
78
- const page = await browser.newPage();
79
- await page.goto('file://${inputPath.replace(/'/g, "\\'")}', { waitUntil: 'networkidle0' });
80
- await page.pdf({ path: '${out.replace(/'/g, "\\'")}', format: 'A4', printBackground: true });
81
- await browser.close();
82
- })();
83
- `;
73
+ const workerPath = path.join(__dirname, "pdf-worker.js");
84
74
  try {
85
- await exec("node", ["-e", script]);
75
+ execFileSync("node", [workerPath, inputPath, out], { timeout: 120_000 });
86
76
  } catch (err) {
87
77
  throw npxToolError("puppeteer", err);
88
78
  }
package/lib/serve-mcp.js CHANGED
@@ -96,7 +96,16 @@ async function toolConvert(dir, args) {
96
96
  }
97
97
 
98
98
  // Resolve source file
99
- const sourceFile = source || path.join(dir, "compilation.json");
99
+ const sourceFile = source
100
+ ? path.resolve(dir, source)
101
+ : path.join(dir, "compilation.json");
102
+ // Prevent path traversal — source must stay within workspace
103
+ if (sourceFile !== dir && !sourceFile.startsWith(dir + path.sep)) {
104
+ return {
105
+ status: "error",
106
+ message: `Source path escapes workspace directory.`,
107
+ };
108
+ }
100
109
  const fallbackFile = path.join(dir, "claims.json");
101
110
  let dataPath = sourceFile;
102
111
 
@@ -174,6 +183,13 @@ async function toolConvert(dir, args) {
174
183
  // Write output if path provided
175
184
  if (output) {
176
185
  const outPath = path.resolve(dir, output);
186
+ // Prevent path traversal — output must stay within workspace
187
+ if (outPath !== dir && !outPath.startsWith(dir + path.sep)) {
188
+ return {
189
+ status: "error",
190
+ message: `Output path "${output}" escapes workspace directory.`,
191
+ };
192
+ }
177
193
  const outDir = path.dirname(outPath);
178
194
  if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
179
195
  fs.writeFileSync(outPath, result);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grainulation/mill",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
4
4
  "description": "Turn wheat sprint artifacts into shareable formats",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -8,9 +8,6 @@
8
8
  },
9
9
  "exports": {
10
10
  ".": "./lib/index.js",
11
- "./server": {
12
- "import": "./lib/server.js"
13
- },
14
11
  "./formats/markdown": "./lib/formats/markdown.js",
15
12
  "./formats/csv": "./lib/formats/csv.js",
16
13
  "./formats/json-ld": "./lib/formats/json-ld.js",
@@ -18,12 +15,18 @@
18
15
  },
19
16
  "files": [
20
17
  "bin/",
21
- "lib/",
22
- "public/",
18
+ "lib/index.js",
19
+ "lib/formats.js",
20
+ "lib/json-ld-common.js",
21
+ "lib/serve-mcp.js",
22
+ "lib/exporters/",
23
+ "lib/publishers/",
24
+ "lib/formats/",
23
25
  "CHANGELOG.md",
24
26
  "LICENSE",
25
27
  "CODE_OF_CONDUCT.md",
26
- "CONTRIBUTING.md"
28
+ "CONTRIBUTING.md",
29
+ "public/grainulation-tokens.css"
27
30
  ],
28
31
  "scripts": {
29
32
  "test": "node test/basic.test.js",