@unfoldingword/door43-preview-renderers 1.5.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/LICENSE +21 -0
- package/README.md +493 -0
- package/dist/index.esm.js +94813 -0
- package/dist/index.js +94847 -0
- package/package.json +94 -0
- package/src/cli.js +300 -0
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unfoldingword/door43-preview-renderers",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "JavaScript library for gathering content from various repositories and rendering HTML snippets for web previews and PDFs",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.esm.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"door43-renderers": "./src/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/index.js",
|
|
16
|
+
"dist/index.esm.js",
|
|
17
|
+
"src/cli.js"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"fetch-logos": "node scripts/fetch-logos.mjs",
|
|
21
|
+
"prebuild": "node -e \"const f='src/assets/logos.generated.js';require('fs').existsSync(f)||require('child_process').execSync('node scripts/fetch-logos.mjs',{stdio:'inherit'})\"",
|
|
22
|
+
"build": "rollup -c",
|
|
23
|
+
"dev": "rollup -c -w",
|
|
24
|
+
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
|
25
|
+
"test:watch": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --watch",
|
|
26
|
+
"test:coverage": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --coverage",
|
|
27
|
+
"cli": "node src/cli.js",
|
|
28
|
+
"lint": "eslint src --ext .js,.jsx",
|
|
29
|
+
"format": "prettier --write \"src/**/*.{js,jsx,json,md}\"",
|
|
30
|
+
"styleguide": "styleguidist server --config styleguide.config.cjs",
|
|
31
|
+
"styleguide:dev": "styleguidist server --config styleguide.config.cjs",
|
|
32
|
+
"styleguide:build": "pnpm run build && styleguidist build --config styleguide.config.cjs",
|
|
33
|
+
"prepare": "pnpm run build"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"door43",
|
|
37
|
+
"preview",
|
|
38
|
+
"renderer",
|
|
39
|
+
"html",
|
|
40
|
+
"pdf",
|
|
41
|
+
"bible",
|
|
42
|
+
"translation"
|
|
43
|
+
],
|
|
44
|
+
"author": "unfoldingWord",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/unfoldingWord/door43-preview-renderers.git"
|
|
49
|
+
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/unfoldingWord/door43-preview-renderers/issues"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/unfoldingWord/door43-preview-renderers#readme",
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@babel/core": "^7.23.0",
|
|
56
|
+
"@babel/preset-env": "^7.23.0",
|
|
57
|
+
"@babel/preset-react": "^7.23.0",
|
|
58
|
+
"@rollup/plugin-babel": "^6.0.4",
|
|
59
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
60
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
61
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
62
|
+
"babel-loader": "^9.1.3",
|
|
63
|
+
"css-loader": "^6.8.1",
|
|
64
|
+
"eslint": "^8.51.0",
|
|
65
|
+
"jest": "^29.7.0",
|
|
66
|
+
"netlify-cli": "^23.11.1",
|
|
67
|
+
"prettier": "^3.0.3",
|
|
68
|
+
"react": "^18.2.0",
|
|
69
|
+
"react-dom": "^18.2.0",
|
|
70
|
+
"react-styleguidist": "^13.1.2",
|
|
71
|
+
"rollup": "^4.1.4",
|
|
72
|
+
"style-loader": "^3.3.3",
|
|
73
|
+
"webpack": "^5.89.0"
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"axios": "^1.6.0",
|
|
77
|
+
"js-yaml": "^4.1.1",
|
|
78
|
+
"jszip": "^3.10.1",
|
|
79
|
+
"markdown-it": "^14.1.1",
|
|
80
|
+
"proskomma-core": "^0.10.17",
|
|
81
|
+
"proskomma-json-tools": "0.8.12",
|
|
82
|
+
"tsv-quote-converters": "^1.1.18",
|
|
83
|
+
"usfm-alignment-remover": "^0.1.6",
|
|
84
|
+
"usfm-js": "^3.4.3"
|
|
85
|
+
},
|
|
86
|
+
"peerDependencies": {
|
|
87
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
88
|
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
89
|
+
},
|
|
90
|
+
"engines": {
|
|
91
|
+
"node": ">=16.0.0",
|
|
92
|
+
"pnpm": ">=8.0.0"
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI tool for door43-preview-renderers
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* cli.js getAllCatalogEntries --owner unfoldingWord --repo en_tn --ref v80 --book gen [--output file.json] [--dcs-api-url URL]
|
|
8
|
+
* cli.js getResourceData --owner unfoldingWord --repo en_tn --ref v80 --book gen [--output file.json] [--dcs-api-url URL]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { getAllCatalogEntries } from './getAllCatalogEntries.js';
|
|
14
|
+
import { getResourceData } from './getResourceData.js';
|
|
15
|
+
import { renderHtmlData } from './renderHtmlData.js';
|
|
16
|
+
import { renderHTML } from './renderHTML.js';
|
|
17
|
+
import { renderPdf } from './pdf/renderPdf.js';
|
|
18
|
+
|
|
19
|
+
// Map the CLI's short page-size keys to PAGE_SIZES keys understood by renderHTML/renderPdf.
|
|
20
|
+
function resolveCliPageSize(shortKey) {
|
|
21
|
+
const map = {
|
|
22
|
+
A4: 'A4_PORTRAIT',
|
|
23
|
+
A5: 'A5_PORTRAIT',
|
|
24
|
+
USL: 'US_LETTER_PORTRAIT',
|
|
25
|
+
TRADE: 'TRADE',
|
|
26
|
+
CQ: 'CROWN_QUARTO',
|
|
27
|
+
};
|
|
28
|
+
return map[(shortKey || 'A4').toUpperCase()] || 'A4_PORTRAIT';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Parse command line arguments
|
|
32
|
+
function parseArgs(args) {
|
|
33
|
+
const parsed = {
|
|
34
|
+
command: args[0],
|
|
35
|
+
params: {},
|
|
36
|
+
quiet: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (let i = 1; i < args.length; i++) {
|
|
40
|
+
const arg = args[i];
|
|
41
|
+
|
|
42
|
+
// Handle flags without values
|
|
43
|
+
if (arg === '--quiet' || arg === '-q') {
|
|
44
|
+
parsed.quiet = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Handle key-value pairs
|
|
49
|
+
if (arg.startsWith('--')) {
|
|
50
|
+
const key = arg.replace(/^--/, '').replace(/-/g, '_');
|
|
51
|
+
const value = args[i + 1];
|
|
52
|
+
|
|
53
|
+
if (key === 'book') {
|
|
54
|
+
parsed.params.bookId = value;
|
|
55
|
+
} else if (key === 'dcs_api_url') {
|
|
56
|
+
parsed.params.dcsApiUrl = value;
|
|
57
|
+
} else if (key === 'output') {
|
|
58
|
+
parsed.output = value;
|
|
59
|
+
} else if (key === 'page_size') {
|
|
60
|
+
parsed.params.pageSize = value;
|
|
61
|
+
} else if (key === 'columns') {
|
|
62
|
+
parsed.params.columns = parseInt(value, 10) || 1;
|
|
63
|
+
} else {
|
|
64
|
+
parsed.params[key] = value;
|
|
65
|
+
}
|
|
66
|
+
i++; // Skip the value in next iteration
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Display help message
|
|
74
|
+
function showHelp() {
|
|
75
|
+
process.stdout.write(`
|
|
76
|
+
door43-preview-renderers CLI
|
|
77
|
+
|
|
78
|
+
Usage:
|
|
79
|
+
cli.js <command> [options]
|
|
80
|
+
|
|
81
|
+
Commands:
|
|
82
|
+
getAllCatalogEntries Get all catalog entries for rendering
|
|
83
|
+
getResourceData Get resource data for a specific book
|
|
84
|
+
renderHtml Fetch resource data and render to HTML sections (JSON)
|
|
85
|
+
assemblePrint Fetch, render, and assemble a print-ready HTML document
|
|
86
|
+
generatePdf Fetch, render, assemble, and render a PDF via WeasyPrint (requires the weasyprint binary)
|
|
87
|
+
|
|
88
|
+
Options:
|
|
89
|
+
--owner <owner> Repository owner (required)
|
|
90
|
+
--repo <repo> Repository name (required)
|
|
91
|
+
--ref <ref> Git reference (branch/tag/commit)
|
|
92
|
+
--book <bookId> Book ID (e.g., gen, exo, mat, 1ti)
|
|
93
|
+
--dcs-api-url <url> Custom DCS API URL (optional)
|
|
94
|
+
--output <file> Output file path (optional, defaults to stdout)
|
|
95
|
+
--quiet, -q Suppress logging output (no API URLs or progress messages)
|
|
96
|
+
--page-size <size> Page size for assemblePrint (A4, A5, USL, TRADE, CQ; default A4)
|
|
97
|
+
--columns <n> Number of columns for assemblePrint (default 1)
|
|
98
|
+
--pdf-service-url <url> generatePdf: use a hosted weasyprint-pdf service instead of the local binary
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
# Get catalog entries and output to stdout
|
|
102
|
+
node src/cli.js getAllCatalogEntries --owner unfoldingWord --repo en_tn --ref v80 --book gen
|
|
103
|
+
|
|
104
|
+
# Get catalog entries with quiet mode (no logging, just JSON)
|
|
105
|
+
node src/cli.js getAllCatalogEntries --owner unfoldingWord --repo en_tn --ref v80 --book gen --quiet
|
|
106
|
+
|
|
107
|
+
# Get catalog entries and save to file
|
|
108
|
+
node src/cli.js getAllCatalogEntries --owner unfoldingWord --repo en_tn --ref v80 --book gen --output result.json
|
|
109
|
+
|
|
110
|
+
# Quiet mode for piping to jq
|
|
111
|
+
node src/cli.js getAllCatalogEntries --owner BSOJ --repo ar_twl --ref v5 --book 1jn --quiet | jq '.catalogEntries[] | {owner, repo: .name, subject}'
|
|
112
|
+
|
|
113
|
+
# Get resource data
|
|
114
|
+
node src/cli.js getResourceData --owner unfoldingWord --repo en_tn --ref v80 --book gen
|
|
115
|
+
|
|
116
|
+
# Render HTML sections (JSON output with body, toc, css, etc.)
|
|
117
|
+
node src/cli.js renderHtml --owner unfoldingWord --repo en_tn --ref v88 --book tit --quiet
|
|
118
|
+
|
|
119
|
+
# Assemble print-ready HTML document (outputs complete HTML)
|
|
120
|
+
node src/cli.js assemblePrint --owner unfoldingWord --repo en_tn --ref v88 --book tit --output tit_tn.html
|
|
121
|
+
|
|
122
|
+
# Assemble with page size options
|
|
123
|
+
node src/cli.js assemblePrint --owner unfoldingWord --repo en_obs --ref v9 --page-size TRADE --output obs.html
|
|
124
|
+
|
|
125
|
+
# Generate a PDF directly (no browser; requires weasyprint installed)
|
|
126
|
+
node src/cli.js generatePdf --owner unfoldingWord --repo en_tn --ref v88 --book tit --output tit_tn.pdf
|
|
127
|
+
|
|
128
|
+
# Use custom DCS API URL
|
|
129
|
+
node src/cli.js getAllCatalogEntries --owner BSOJ --repo ar_twl --ref v5 --book 1jn --dcs-api-url https://git.door43.org/api/v1
|
|
130
|
+
|
|
131
|
+
Test Cases:
|
|
132
|
+
# Valid case
|
|
133
|
+
node src/cli.js getAllCatalogEntries --owner unfoldingWord --repo en_tn --ref v80 --book gen
|
|
134
|
+
|
|
135
|
+
# Invalid book ID (should fail)
|
|
136
|
+
node src/cli.js getAllCatalogEntries --owner fr_gl --repo fr_tn --ref v2 --book 1th
|
|
137
|
+
|
|
138
|
+
# Valid case
|
|
139
|
+
node src/cli.js getAllCatalogEntries --owner fr_gl --repo gr_tn --ref v2 --book 1ti
|
|
140
|
+
|
|
141
|
+
# Aligned Bible case (currently fails, needs fix)
|
|
142
|
+
node src/cli.js getAllCatalogEntries --owner BSOJ --repo ar_twl --ref v5 --book 1jn
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Main function
|
|
147
|
+
async function main() {
|
|
148
|
+
const args = process.argv.slice(2);
|
|
149
|
+
|
|
150
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
151
|
+
showHelp();
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const { command, params, output, quiet } = parseArgs(args);
|
|
156
|
+
|
|
157
|
+
// Validate required parameters
|
|
158
|
+
if (!params.owner || !params.repo) {
|
|
159
|
+
console.error('Error: --owner and --repo are required');
|
|
160
|
+
console.error('Use --help for usage information');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
let result;
|
|
166
|
+
|
|
167
|
+
switch (command) {
|
|
168
|
+
case 'getAllCatalogEntries': {
|
|
169
|
+
if (!quiet) {
|
|
170
|
+
console.error(`Fetching catalog entries for ${params.owner}/${params.repo}...`);
|
|
171
|
+
}
|
|
172
|
+
const books = params.bookId ? [params.bookId] : [];
|
|
173
|
+
result = await getAllCatalogEntries(
|
|
174
|
+
{ owner: params.owner, repo: params.repo, ref: params.ref, books },
|
|
175
|
+
{ dcs_api_url: params.dcsApiUrl || 'https://git.door43.org/api/v1', quiet }
|
|
176
|
+
);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case 'getResourceData': {
|
|
181
|
+
if (!quiet) {
|
|
182
|
+
console.error(`Fetching resource data for ${params.owner}/${params.repo}...`);
|
|
183
|
+
}
|
|
184
|
+
const resourceBooks = params.bookId ? [params.bookId] : [];
|
|
185
|
+
result = await getResourceData(
|
|
186
|
+
{ owner: params.owner, repo: params.repo, ref: params.ref, books: resourceBooks },
|
|
187
|
+
{ dcs_api_url: params.dcsApiUrl || 'https://git.door43.org/api/v1', quiet }
|
|
188
|
+
);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
case 'renderHtml': {
|
|
193
|
+
if (!quiet) {
|
|
194
|
+
console.error(`Rendering HTML for ${params.owner}/${params.repo}...`);
|
|
195
|
+
}
|
|
196
|
+
const renderBooks = params.bookId ? [params.bookId] : [];
|
|
197
|
+
const resourceData = await getResourceData(
|
|
198
|
+
{ owner: params.owner, repo: params.repo, ref: params.ref, books: renderBooks },
|
|
199
|
+
{ dcs_api_url: params.dcsApiUrl || 'https://git.door43.org/api/v1', quiet }
|
|
200
|
+
);
|
|
201
|
+
// HtmlData package (identity + sections); the body can be large.
|
|
202
|
+
result = renderHtmlData(resourceData, { books: renderBooks });
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'assemblePrint': {
|
|
207
|
+
if (!quiet) {
|
|
208
|
+
console.error(`Assembling print document for ${params.owner}/${params.repo}...`);
|
|
209
|
+
}
|
|
210
|
+
const printBooks = params.bookId ? [params.bookId] : [];
|
|
211
|
+
const resourceData = await getResourceData(
|
|
212
|
+
{ owner: params.owner, repo: params.repo, ref: params.ref, books: printBooks },
|
|
213
|
+
{ dcs_api_url: params.dcsApiUrl || 'https://git.door43.org/api/v1', quiet }
|
|
214
|
+
);
|
|
215
|
+
const htmlData = renderHtmlData(resourceData, { books: printBooks });
|
|
216
|
+
const html = renderHTML(htmlData, {
|
|
217
|
+
media: 'print',
|
|
218
|
+
columns: params.columns || 1,
|
|
219
|
+
print: { pageSize: resolveCliPageSize(params.pageSize) },
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// For assemblePrint, output the raw HTML, not JSON
|
|
223
|
+
if (output) {
|
|
224
|
+
const outputPath = path.resolve(output);
|
|
225
|
+
fs.writeFileSync(outputPath, html);
|
|
226
|
+
console.error(`✓ Print-ready HTML written to ${outputPath}`);
|
|
227
|
+
process.exit(0);
|
|
228
|
+
} else {
|
|
229
|
+
process.stdout.write(html + '\n', () => {
|
|
230
|
+
process.exit(0);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return; // Skip JSON formatting below
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
case 'generatePdf': {
|
|
237
|
+
if (!output) {
|
|
238
|
+
console.error('Error: generatePdf requires --output <file.pdf>');
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
if (!quiet) {
|
|
242
|
+
console.error(`Generating PDF for ${params.owner}/${params.repo}...`);
|
|
243
|
+
}
|
|
244
|
+
const pdfBooks = params.bookId ? [params.bookId] : [];
|
|
245
|
+
const resourceData = await getResourceData(
|
|
246
|
+
{ owner: params.owner, repo: params.repo, ref: params.ref, books: pdfBooks },
|
|
247
|
+
{ dcs_api_url: params.dcsApiUrl || 'https://git.door43.org/api/v1', quiet }
|
|
248
|
+
);
|
|
249
|
+
const htmlData = renderHtmlData(resourceData, { books: pdfBooks });
|
|
250
|
+
const pdfOutputPath = path.resolve(output);
|
|
251
|
+
await renderPdf(htmlData, {
|
|
252
|
+
pageSize: resolveCliPageSize(params.pageSize),
|
|
253
|
+
columns: params.columns || 1,
|
|
254
|
+
outputPath: pdfOutputPath,
|
|
255
|
+
// Offload to a hosted weasyprint-pdf service instead of the local binary.
|
|
256
|
+
pdfServiceUrl: params.pdf_service_url,
|
|
257
|
+
quiet,
|
|
258
|
+
});
|
|
259
|
+
console.error(`✓ PDF written to ${pdfOutputPath}`);
|
|
260
|
+
process.exit(0);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
default:
|
|
265
|
+
console.error(`Error: Unknown command "${command}"`);
|
|
266
|
+
console.error(
|
|
267
|
+
'Valid commands: getAllCatalogEntries, getResourceData, renderHtml, assemblePrint, generatePdf'
|
|
268
|
+
);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Format output
|
|
273
|
+
const jsonOutput = JSON.stringify(result, null, 2);
|
|
274
|
+
|
|
275
|
+
// Write to file or stdout
|
|
276
|
+
if (output) {
|
|
277
|
+
const outputPath = path.resolve(output);
|
|
278
|
+
fs.writeFileSync(outputPath, jsonOutput);
|
|
279
|
+
console.error(`✓ Output written to ${outputPath}`);
|
|
280
|
+
process.exit(0);
|
|
281
|
+
} else {
|
|
282
|
+
// Write to stdout and wait for it to flush before exiting
|
|
283
|
+
process.stdout.write(jsonOutput + '\n', () => {
|
|
284
|
+
process.exit(0);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('Error:', error.message);
|
|
289
|
+
if (error.response) {
|
|
290
|
+
console.error('API Response:', error.response.status, error.response.statusText);
|
|
291
|
+
if (error.response.data) {
|
|
292
|
+
console.error('Details:', error.response.data);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Run main function
|
|
300
|
+
main();
|