@uurtech/jdf-cli 0.1.4 → 0.1.5
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/dist/index.js +202 -18
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,31 +1,63 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import
|
|
3
|
+
import path2 from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import Ajv from 'ajv';
|
|
6
6
|
import addFormats from 'ajv-formats';
|
|
7
|
+
import JSZip from 'jszip';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// ../../packages/jdf-core/src/manifest.ts
|
|
10
|
+
var JDFX_MANIFEST_VERSION = "1.0.0";
|
|
11
|
+
var JDFX_DOCUMENT_PATH = "document.json";
|
|
12
|
+
var JDFX_MANIFEST_PATH = "manifest.json";
|
|
13
|
+
var JDFX_ASSET_DIR = "assets";
|
|
14
|
+
|
|
15
|
+
// src/commands/validate.ts
|
|
16
|
+
var __dirname$1 = path2.dirname(fileURLToPath(import.meta.url));
|
|
9
17
|
function resolveSchemaPath() {
|
|
10
|
-
const bundled =
|
|
18
|
+
const bundled = path2.resolve(__dirname$1, "jdf-schema.json");
|
|
11
19
|
if (fs.existsSync(bundled)) return bundled;
|
|
12
|
-
const dev =
|
|
20
|
+
const dev = path2.resolve(__dirname$1, "../../../../spec/jdf-schema.json");
|
|
13
21
|
return dev;
|
|
14
22
|
}
|
|
15
23
|
var SCHEMA_PATH = resolveSchemaPath();
|
|
24
|
+
async function loadDocument(filePath) {
|
|
25
|
+
if (filePath.toLowerCase().endsWith(".jdfx")) {
|
|
26
|
+
const zip = await JSZip.loadAsync(fs.readFileSync(filePath));
|
|
27
|
+
const docFile = zip.file(JDFX_DOCUMENT_PATH);
|
|
28
|
+
if (!docFile) {
|
|
29
|
+
console.error(`\u2717 Bundle missing ${JDFX_DOCUMENT_PATH}`);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const doc = JSON.parse(await docFile.async("string"));
|
|
33
|
+
let manifest = null;
|
|
34
|
+
const mf = zip.file(JDFX_MANIFEST_PATH);
|
|
35
|
+
if (mf) {
|
|
36
|
+
try {
|
|
37
|
+
manifest = JSON.parse(await mf.async("string"));
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const assetCount = Object.values(zip.files).filter((f) => !f.dir && f.name.startsWith("assets/")).length;
|
|
42
|
+
return { doc, bundle: { manifest, assetCount } };
|
|
43
|
+
}
|
|
44
|
+
return { doc: JSON.parse(fs.readFileSync(filePath, "utf-8")) };
|
|
45
|
+
}
|
|
16
46
|
async function validate(file) {
|
|
17
|
-
const filePath =
|
|
47
|
+
const filePath = path2.resolve(file);
|
|
18
48
|
if (!fs.existsSync(filePath)) {
|
|
19
49
|
console.error(`File not found: ${filePath}`);
|
|
20
50
|
return false;
|
|
21
51
|
}
|
|
22
|
-
let
|
|
52
|
+
let loaded;
|
|
23
53
|
try {
|
|
24
|
-
|
|
54
|
+
loaded = await loadDocument(filePath);
|
|
25
55
|
} catch (e) {
|
|
26
|
-
console.error(`Invalid
|
|
56
|
+
console.error(`Invalid file: ${e.message}`);
|
|
27
57
|
return false;
|
|
28
58
|
}
|
|
59
|
+
if (!loaded) return false;
|
|
60
|
+
const { doc, bundle } = loaded;
|
|
29
61
|
if (!fs.existsSync(SCHEMA_PATH)) {
|
|
30
62
|
console.error(`Schema not found at ${SCHEMA_PATH}`);
|
|
31
63
|
return false;
|
|
@@ -39,33 +71,161 @@ async function validate(file) {
|
|
|
39
71
|
const d = doc;
|
|
40
72
|
const pageCount = Array.isArray(d.pages) ? d.pages.length : 0;
|
|
41
73
|
const elCount = Array.isArray(d.pages) ? d.pages.reduce((acc, p) => acc + (Array.isArray(p?.elements) ? p.elements.length : 0), 0) : 0;
|
|
42
|
-
console.log(`\u2713 Valid: ${
|
|
43
|
-
console.log(` Format: ${d.$jdf}`);
|
|
74
|
+
console.log(`\u2713 Valid: ${path2.basename(filePath)}`);
|
|
75
|
+
console.log(` Format: ${d.$jdf}${bundle ? " (jdfx bundle)" : ""}`);
|
|
44
76
|
console.log(` Title: ${d.meta?.title}`);
|
|
45
77
|
console.log(` Pages: ${pageCount}`);
|
|
46
78
|
console.log(` Elements: ${elCount}`);
|
|
79
|
+
if (bundle) {
|
|
80
|
+
console.log(` Assets: ${bundle.assetCount}`);
|
|
81
|
+
if (bundle.manifest?.generator) {
|
|
82
|
+
console.log(` Generator: ${bundle.manifest.generator}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
47
85
|
return true;
|
|
48
86
|
}
|
|
49
|
-
console.error(`\u2717 Invalid: ${
|
|
87
|
+
console.error(`\u2717 Invalid: ${path2.basename(filePath)}`);
|
|
50
88
|
for (const err of validateFn.errors || []) {
|
|
51
89
|
const loc = err.instancePath || "(root)";
|
|
52
90
|
console.error(` ${loc} \u2014 ${err.message}`);
|
|
53
91
|
}
|
|
54
92
|
return false;
|
|
55
93
|
}
|
|
94
|
+
var GENERATOR = "@uurtech/jdf-cli";
|
|
95
|
+
function decodeBase64(s) {
|
|
96
|
+
return Buffer.from(s, "base64");
|
|
97
|
+
}
|
|
98
|
+
function extractAssets(doc) {
|
|
99
|
+
const assets = [];
|
|
100
|
+
const cloned = JSON.parse(JSON.stringify(doc));
|
|
101
|
+
let counter = 0;
|
|
102
|
+
function walk(els) {
|
|
103
|
+
if (!els) return;
|
|
104
|
+
for (const el of els) {
|
|
105
|
+
if (el?.type === "image" && typeof el.src === "string" && el.src.startsWith("data:")) {
|
|
106
|
+
const m = el.src.match(/^data:([^;]+);base64,(.*)$/);
|
|
107
|
+
if (m) {
|
|
108
|
+
counter++;
|
|
109
|
+
const id = `asset-${counter}`;
|
|
110
|
+
const mimeType = m[1];
|
|
111
|
+
const ext = mimeType.split("/")[1]?.replace("jpeg", "jpg") || "bin";
|
|
112
|
+
assets.push({ id, bytes: decodeBase64(m[2]), mimeType, ext });
|
|
113
|
+
delete el.src;
|
|
114
|
+
el.resource = id;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (el?.elements) walk(el.elements);
|
|
118
|
+
if (el?.children) walk(el.children);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const page of cloned.pages || []) walk(page.elements);
|
|
122
|
+
if (cloned.resources?.images) {
|
|
123
|
+
for (const [key, res] of Object.entries(cloned.resources.images)) {
|
|
124
|
+
if (!res || typeof res !== "object" || !("data" in res) || !res.data) continue;
|
|
125
|
+
const data = String(res.data);
|
|
126
|
+
const m = data.match(/^data:([^;]+);base64,(.*)$/);
|
|
127
|
+
const b64 = m ? m[2] : data;
|
|
128
|
+
const mimeType = m ? m[1] : res.mimeType || "image/png";
|
|
129
|
+
const ext = mimeType.split("/")[1]?.replace("jpeg", "jpg") || "bin";
|
|
130
|
+
counter++;
|
|
131
|
+
const id = key || `asset-${counter}`;
|
|
132
|
+
assets.push({ id, bytes: decodeBase64(b64), mimeType, ext });
|
|
133
|
+
}
|
|
134
|
+
cloned.resources.images = {};
|
|
135
|
+
}
|
|
136
|
+
return { doc: cloned, assets };
|
|
137
|
+
}
|
|
138
|
+
async function packJdfx(doc) {
|
|
139
|
+
const { doc: rewritten, assets } = extractAssets(doc);
|
|
140
|
+
const zip = new JSZip();
|
|
141
|
+
const assetEntries = assets.map((a) => {
|
|
142
|
+
const p = `${JDFX_ASSET_DIR}/${a.id}.${a.ext}`;
|
|
143
|
+
zip.file(p, a.bytes);
|
|
144
|
+
return { id: a.id, path: p, mimeType: a.mimeType, size: a.bytes.length };
|
|
145
|
+
});
|
|
146
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
147
|
+
const manifest = {
|
|
148
|
+
format: "jdfx",
|
|
149
|
+
version: JDFX_MANIFEST_VERSION,
|
|
150
|
+
document: JDFX_DOCUMENT_PATH,
|
|
151
|
+
created: now,
|
|
152
|
+
modified: now,
|
|
153
|
+
generator: GENERATOR,
|
|
154
|
+
assets: assetEntries
|
|
155
|
+
};
|
|
156
|
+
zip.file(JDFX_DOCUMENT_PATH, JSON.stringify(rewritten, null, 2));
|
|
157
|
+
zip.file(JDFX_MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
158
|
+
const bytes = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE", compressionOptions: { level: 6 } });
|
|
159
|
+
return { bytes, manifest };
|
|
160
|
+
}
|
|
161
|
+
function shouldUseJdfx(doc) {
|
|
162
|
+
const images = doc.resources?.images ?? {};
|
|
163
|
+
for (const v of Object.values(images)) {
|
|
164
|
+
if (v && typeof v === "object" && "data" in v && v.data) return true;
|
|
165
|
+
}
|
|
166
|
+
function walk(els) {
|
|
167
|
+
if (!els) return false;
|
|
168
|
+
for (const el of els) {
|
|
169
|
+
if (el?.type === "image" && typeof el.src === "string" && el.src.startsWith("data:")) return true;
|
|
170
|
+
if (el?.elements && walk(el.elements)) return true;
|
|
171
|
+
if (el?.children && walk(el.children)) return true;
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
for (const page of doc.pages || []) {
|
|
176
|
+
if (walk(page.elements)) return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/commands/import-md.ts
|
|
56
182
|
async function importMarkdown(inputPath, outputPath) {
|
|
57
|
-
const input =
|
|
58
|
-
const output = outputPath ? path.resolve(outputPath) : input.replace(/\.(md|markdown)$/i, ".jdf");
|
|
183
|
+
const input = path2.resolve(inputPath);
|
|
59
184
|
console.log(`Importing: ${input}`);
|
|
60
|
-
console.log(`Output: ${output}`);
|
|
61
185
|
const content = fs.readFileSync(input, "utf-8");
|
|
62
|
-
const doc = convertMarkdownToJdf(content,
|
|
63
|
-
|
|
64
|
-
|
|
186
|
+
const doc = convertMarkdownToJdf(content, path2.basename(input, path2.extname(input)), path2.dirname(input));
|
|
187
|
+
let output;
|
|
188
|
+
if (outputPath) {
|
|
189
|
+
output = path2.resolve(outputPath);
|
|
190
|
+
} else {
|
|
191
|
+
const stem = input.replace(/\.(md|markdown)$/i, "");
|
|
192
|
+
output = stem + (shouldUseJdfx(doc) ? ".jdfx" : ".jdf");
|
|
193
|
+
}
|
|
194
|
+
console.log(`Output: ${output}`);
|
|
195
|
+
if (output.toLowerCase().endsWith(".jdfx")) {
|
|
196
|
+
const { bytes, manifest } = await packJdfx(doc);
|
|
197
|
+
fs.writeFileSync(output, bytes);
|
|
198
|
+
console.log(`
|
|
199
|
+
Done! Created ${doc.pages.length} page(s), ${manifest.assets.length} asset(s) bundled`);
|
|
200
|
+
} else {
|
|
201
|
+
fs.writeFileSync(output, JSON.stringify(doc, null, 2));
|
|
202
|
+
console.log(`
|
|
65
203
|
Done! Created ${doc.pages.length} page(s)`);
|
|
204
|
+
}
|
|
66
205
|
console.log(`Open with: open -a "JDF Reader" "${output}"`);
|
|
67
206
|
}
|
|
68
|
-
|
|
207
|
+
var MIME_BY_EXT2 = {
|
|
208
|
+
png: "image/png",
|
|
209
|
+
jpg: "image/jpeg",
|
|
210
|
+
jpeg: "image/jpeg",
|
|
211
|
+
gif: "image/gif",
|
|
212
|
+
webp: "image/webp",
|
|
213
|
+
svg: "image/svg+xml",
|
|
214
|
+
bmp: "image/bmp"
|
|
215
|
+
};
|
|
216
|
+
function resolveImageSrc(src, baseDir) {
|
|
217
|
+
if (/^(https?:|data:|file:)/i.test(src)) return src;
|
|
218
|
+
const abs = path2.isAbsolute(src) ? src : path2.resolve(baseDir, src);
|
|
219
|
+
try {
|
|
220
|
+
const bytes = fs.readFileSync(abs);
|
|
221
|
+
const ext = path2.extname(abs).slice(1).toLowerCase();
|
|
222
|
+
const mime = MIME_BY_EXT2[ext] || "application/octet-stream";
|
|
223
|
+
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
224
|
+
} catch {
|
|
225
|
+
return src;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function convertMarkdownToJdf(md, title, baseDir = process.cwd()) {
|
|
69
229
|
const maxY = 247;
|
|
70
230
|
const contentWidth = 166;
|
|
71
231
|
const pages = [];
|
|
@@ -105,6 +265,30 @@ function convertMarkdownToJdf(md, title) {
|
|
|
105
265
|
i++;
|
|
106
266
|
continue;
|
|
107
267
|
}
|
|
268
|
+
const imageMatch = line.match(/^\s*!\[([^\]]*)\]\(\s*([^)\s]+)[^)]*\)\s*$/);
|
|
269
|
+
if (imageMatch) {
|
|
270
|
+
const alt = imageMatch[1];
|
|
271
|
+
const src = resolveImageSrc(imageMatch[2], baseDir);
|
|
272
|
+
const height2 = 60;
|
|
273
|
+
if (y + height2 > maxY) {
|
|
274
|
+
pages.push({ id: `page-${pageNum}`, elements });
|
|
275
|
+
elements = [];
|
|
276
|
+
y = 5;
|
|
277
|
+
pageNum++;
|
|
278
|
+
}
|
|
279
|
+
elements.push({
|
|
280
|
+
type: "image",
|
|
281
|
+
src,
|
|
282
|
+
alt,
|
|
283
|
+
position: { x: 0, y },
|
|
284
|
+
width: contentWidth,
|
|
285
|
+
height: height2,
|
|
286
|
+
fit: "contain"
|
|
287
|
+
});
|
|
288
|
+
y += height2 + 4;
|
|
289
|
+
i++;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
108
292
|
if (line.match(/^\s*[-*+]\s/)) {
|
|
109
293
|
const items = [];
|
|
110
294
|
while (i < lines.length && lines[i].match(/^\s*[-*+]\s/)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uurtech/jdf-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Command-line tool for the JDF (JSON Document Format) — validate and convert documents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ugur Kazdal",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"ajv": "^8.17.1",
|
|
48
|
-
"ajv-formats": "^3.0.1"
|
|
48
|
+
"ajv-formats": "^3.0.1",
|
|
49
|
+
"jszip": "^3.10.1"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@jdf/core": "workspace:*",
|