@grainulation/mill 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/exporters/pdf.js +5 -2
- package/lib/serve-mcp.js +50 -1
- package/package.json +1 -1
package/lib/exporters/pdf.js
CHANGED
|
@@ -71,13 +71,16 @@ async function exportFromMarkdown(inputPath, outputPath) {
|
|
|
71
71
|
async function exportFromHtml(inputPath, outputPath) {
|
|
72
72
|
const out = deriveOutputPath(inputPath, outputPath);
|
|
73
73
|
// Use a small inline puppeteer script via npx
|
|
74
|
+
// Escape single quotes, backticks, and ${} to prevent template literal injection
|
|
75
|
+
const escapePath = (p) =>
|
|
76
|
+
p.replace(/'/g, "\\'").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
74
77
|
const script = `
|
|
75
78
|
const puppeteer = require('puppeteer');
|
|
76
79
|
(async () => {
|
|
77
80
|
const browser = await puppeteer.launch({ headless: 'new' });
|
|
78
81
|
const page = await browser.newPage();
|
|
79
|
-
await page.goto('file://${inputPath
|
|
80
|
-
await page.pdf({ path: '${out
|
|
82
|
+
await page.goto('file://${escapePath(inputPath)}', { waitUntil: 'networkidle0' });
|
|
83
|
+
await page.pdf({ path: '${escapePath(out)}', format: 'A4', printBackground: true });
|
|
81
84
|
await browser.close();
|
|
82
85
|
})();
|
|
83
86
|
`;
|
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
|
|
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
|
|
|
@@ -130,6 +139,39 @@ async function toolConvert(dir, args) {
|
|
|
130
139
|
data.meta = data.sprint_meta;
|
|
131
140
|
}
|
|
132
141
|
|
|
142
|
+
// Merge claim content from claims.json when compilation.json lacks it.
|
|
143
|
+
// Older compilation.json files omitted the content field from resolved_claims.
|
|
144
|
+
const claimsNeedContent =
|
|
145
|
+
data.claims &&
|
|
146
|
+
data.claims.length > 0 &&
|
|
147
|
+
data.claims.some((c) => !c.content && !c.text);
|
|
148
|
+
if (claimsNeedContent) {
|
|
149
|
+
const claimsFile = path.join(dir, "claims.json");
|
|
150
|
+
if (fs.existsSync(claimsFile)) {
|
|
151
|
+
try {
|
|
152
|
+
const raw = JSON.parse(fs.readFileSync(claimsFile, "utf8"));
|
|
153
|
+
const fullClaims = raw.claims || [];
|
|
154
|
+
const contentMap = {};
|
|
155
|
+
for (const fc of fullClaims) {
|
|
156
|
+
if (fc.id && fc.content) contentMap[fc.id] = fc;
|
|
157
|
+
}
|
|
158
|
+
for (const claim of data.claims) {
|
|
159
|
+
if (!claim.content && !claim.text && contentMap[claim.id]) {
|
|
160
|
+
claim.content = contentMap[claim.id].content;
|
|
161
|
+
if (
|
|
162
|
+
contentMap[claim.id].confidence != null &&
|
|
163
|
+
claim.confidence == null
|
|
164
|
+
) {
|
|
165
|
+
claim.confidence = contentMap[claim.id].confidence;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// Best-effort merge — if claims.json is unreadable, continue without content
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
133
175
|
// Run conversion
|
|
134
176
|
let result;
|
|
135
177
|
try {
|
|
@@ -141,6 +183,13 @@ async function toolConvert(dir, args) {
|
|
|
141
183
|
// Write output if path provided
|
|
142
184
|
if (output) {
|
|
143
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
|
+
}
|
|
144
193
|
const outDir = path.dirname(outPath);
|
|
145
194
|
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
146
195
|
fs.writeFileSync(outPath, result);
|