@json-to-office/jto 0.14.0 → 0.16.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/dist/cli.js +130 -37
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{HomePage-BTmw2QAf.js → HomePage-Czwc5NK6.js} +3 -3
- package/dist/client/assets/{HomePage-BTmw2QAf.js.map → HomePage-Czwc5NK6.js.map} +1 -1
- package/dist/client/assets/{JsonEditorPage-CRONWw-J.js → JsonEditorPage-CwOeiOy6.js} +3 -3
- package/dist/client/assets/{JsonEditorPage-CRONWw-J.js.map → JsonEditorPage-CwOeiOy6.js.map} +1 -1
- package/dist/client/assets/{MonacoPluginProvider-DhOIgTne.js → MonacoPluginProvider-DQcmmuko.js} +3 -3
- package/dist/client/assets/{MonacoPluginProvider-DhOIgTne.js.map → MonacoPluginProvider-DQcmmuko.js.map} +1 -1
- package/dist/client/assets/{button-ebCBEwlw.js → button-DfjYrtU-.js} +2 -2
- package/dist/client/assets/{button-ebCBEwlw.js.map → button-DfjYrtU-.js.map} +1 -1
- package/dist/client/assets/{editor-CB9BVJGn.js → editor-BPfMyucg.js} +2 -2
- package/dist/client/assets/{editor-CB9BVJGn.js.map → editor-BPfMyucg.js.map} +1 -1
- package/dist/client/assets/{editor-monaco-json-DaGk8WIJ.js → editor-monaco-json-cWylTaeJ.js} +2 -2
- package/dist/client/assets/{editor-monaco-json-DaGk8WIJ.js.map → editor-monaco-json-cWylTaeJ.js.map} +1 -1
- package/dist/client/assets/index-Dd4Xxzh5.js +5 -0
- package/dist/client/assets/index-Dd4Xxzh5.js.map +1 -0
- package/dist/client/assets/{preview-B4bcm38l.js → preview-Cdpjm_5u.js} +2 -2
- package/dist/client/assets/{preview-B4bcm38l.js.map → preview-Cdpjm_5u.js.map} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/render-server.d.ts +2 -0
- package/dist/render-server.js +255 -0
- package/dist/render-server.js.map +1 -0
- package/package.json +9 -9
- package/dist/client/assets/index-Cy63RxyT.js +0 -5
- package/dist/client/assets/index-Cy63RxyT.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1517,17 +1517,99 @@ var init_rate_limit = __esm({
|
|
|
1517
1517
|
}
|
|
1518
1518
|
});
|
|
1519
1519
|
|
|
1520
|
+
// src/server/rasterize-route.ts
|
|
1521
|
+
import { Type as Type2 } from "@sinclair/typebox";
|
|
1522
|
+
import { bodyLimit } from "hono/body-limit";
|
|
1523
|
+
import { HTTPException as HTTPException3 } from "hono/http-exception";
|
|
1524
|
+
import {
|
|
1525
|
+
clampVisualDpi,
|
|
1526
|
+
DEFAULT_VISUAL_DPI,
|
|
1527
|
+
MIN_VISUAL_DPI,
|
|
1528
|
+
MAX_VISUAL_DPI
|
|
1529
|
+
} from "@json-to-office/shared";
|
|
1530
|
+
import { createLibreOfficePptxRasterizer } from "@json-to-office/jto-cli";
|
|
1531
|
+
function getSharedRasterizer() {
|
|
1532
|
+
if (!sharedRasterizer) {
|
|
1533
|
+
sharedRasterizer = createLibreOfficePptxRasterizer();
|
|
1534
|
+
}
|
|
1535
|
+
return sharedRasterizer;
|
|
1536
|
+
}
|
|
1537
|
+
function registerRasterizeRoute(router, options = {}) {
|
|
1538
|
+
const getRasterizer = options.getRasterizer ?? getSharedRasterizer;
|
|
1539
|
+
router.post(
|
|
1540
|
+
"/rasterize",
|
|
1541
|
+
...options.preMiddleware ?? [],
|
|
1542
|
+
bodyLimit({
|
|
1543
|
+
maxSize: 32 * 1024 * 1024,
|
|
1544
|
+
onError: () => {
|
|
1545
|
+
throw new HTTPException3(413, { message: "Request body too large" });
|
|
1546
|
+
}
|
|
1547
|
+
}),
|
|
1548
|
+
jsonOnly,
|
|
1549
|
+
tbValidator(RasterizeRequestSchema),
|
|
1550
|
+
async (c) => {
|
|
1551
|
+
const { presentation, dpi } = getValidated(c, "json");
|
|
1552
|
+
try {
|
|
1553
|
+
const result = await getRasterizer()({
|
|
1554
|
+
presentation,
|
|
1555
|
+
dpi: clampVisualDpi(dpi ?? DEFAULT_VISUAL_DPI)
|
|
1556
|
+
});
|
|
1557
|
+
return c.json(result);
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
options.onError?.(error);
|
|
1560
|
+
if (error instanceof HTTPException3) throw error;
|
|
1561
|
+
const msg = error instanceof Error ? error.message.toLowerCase() : String(error);
|
|
1562
|
+
if (msg.includes("not found") || msg.includes("rasterization needs")) {
|
|
1563
|
+
throw new HTTPException3(503, { message: error.message });
|
|
1564
|
+
}
|
|
1565
|
+
if (msg.includes("invalid") || msg.includes("validation")) {
|
|
1566
|
+
throw new HTTPException3(400, { message: error.message });
|
|
1567
|
+
}
|
|
1568
|
+
throw new HTTPException3(500, {
|
|
1569
|
+
message: "Internal server error during rasterization"
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
var RasterizeRequestSchema, sharedRasterizer, jsonOnly;
|
|
1576
|
+
var init_rasterize_route = __esm({
|
|
1577
|
+
"src/server/rasterize-route.ts"() {
|
|
1578
|
+
"use strict";
|
|
1579
|
+
init_esm_shims();
|
|
1580
|
+
init_typebox_validator();
|
|
1581
|
+
RasterizeRequestSchema = Type2.Object(
|
|
1582
|
+
{
|
|
1583
|
+
presentation: Type2.Object({}, { additionalProperties: true }),
|
|
1584
|
+
dpi: Type2.Optional(
|
|
1585
|
+
Type2.Number({ minimum: MIN_VISUAL_DPI, maximum: MAX_VISUAL_DPI })
|
|
1586
|
+
)
|
|
1587
|
+
},
|
|
1588
|
+
{ additionalProperties: false }
|
|
1589
|
+
);
|
|
1590
|
+
jsonOnly = async (c, next) => {
|
|
1591
|
+
const contentType = c.req.header("content-type");
|
|
1592
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
1593
|
+
throw new HTTPException3(400, {
|
|
1594
|
+
message: "Content-Type must be application/json"
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
await next();
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1520
1602
|
// src/server/routes/format.ts
|
|
1521
1603
|
import { Hono as Hono2 } from "hono";
|
|
1522
|
-
import { HTTPException as
|
|
1523
|
-
import { bodyLimit } from "hono/body-limit";
|
|
1604
|
+
import { HTTPException as HTTPException4 } from "hono/http-exception";
|
|
1605
|
+
import { bodyLimit as bodyLimit2 } from "hono/body-limit";
|
|
1524
1606
|
import { PluginRegistry as PluginRegistry2 } from "@json-to-office/jto-cli";
|
|
1525
1607
|
function createFormatRouter(adapter) {
|
|
1526
1608
|
const router = new Hono2();
|
|
1527
1609
|
const contentTypeMw = async (c, next) => {
|
|
1528
1610
|
const contentType = c.req.header("content-type");
|
|
1529
1611
|
if (!contentType || !contentType.includes("application/json")) {
|
|
1530
|
-
throw new
|
|
1612
|
+
throw new HTTPException4(400, {
|
|
1531
1613
|
message: "Content-Type must be application/json"
|
|
1532
1614
|
});
|
|
1533
1615
|
}
|
|
@@ -1591,11 +1673,11 @@ function createFormatRouter(adapter) {
|
|
|
1591
1673
|
if (error instanceof Error) {
|
|
1592
1674
|
const msg = error.message.toLowerCase();
|
|
1593
1675
|
if (msg.includes("invalid") || msg.includes("validation") || msg.includes("missing required") || msg.includes("unknown component")) {
|
|
1594
|
-
throw new
|
|
1676
|
+
throw new HTTPException4(400, { message: error.message });
|
|
1595
1677
|
}
|
|
1596
1678
|
}
|
|
1597
|
-
if (error instanceof
|
|
1598
|
-
throw new
|
|
1679
|
+
if (error instanceof HTTPException4) throw error;
|
|
1680
|
+
throw new HTTPException4(500, {
|
|
1599
1681
|
message: `Internal server error during ${adapter.label} generation`
|
|
1600
1682
|
});
|
|
1601
1683
|
}
|
|
@@ -1628,12 +1710,12 @@ function createFormatRouter(adapter) {
|
|
|
1628
1710
|
if (adapter.name === "docx") {
|
|
1629
1711
|
router.post(
|
|
1630
1712
|
"/diff",
|
|
1631
|
-
|
|
1713
|
+
bodyLimit2({
|
|
1632
1714
|
// Two full documents per request — same cap rationale as
|
|
1633
1715
|
// /preview/libreoffice-from-json, doubled.
|
|
1634
1716
|
maxSize: 32 * 1024 * 1024,
|
|
1635
1717
|
onError: () => {
|
|
1636
|
-
throw new
|
|
1718
|
+
throw new HTTPException4(413, { message: "Request body too large" });
|
|
1637
1719
|
}
|
|
1638
1720
|
}),
|
|
1639
1721
|
rateLimiter({
|
|
@@ -1651,7 +1733,7 @@ function createFormatRouter(adapter) {
|
|
|
1651
1733
|
try {
|
|
1652
1734
|
return JSON.parse(def);
|
|
1653
1735
|
} catch {
|
|
1654
|
-
throw new
|
|
1736
|
+
throw new HTTPException4(400, {
|
|
1655
1737
|
message: `${label} document is not valid JSON`
|
|
1656
1738
|
});
|
|
1657
1739
|
}
|
|
@@ -1681,7 +1763,7 @@ function createFormatRouter(adapter) {
|
|
|
1681
1763
|
}
|
|
1682
1764
|
const revisionDate = options?.date ? new Date(options.date) : /* @__PURE__ */ new Date();
|
|
1683
1765
|
if (isNaN(revisionDate.getTime())) {
|
|
1684
|
-
throw new
|
|
1766
|
+
throw new HTTPException4(400, {
|
|
1685
1767
|
message: `Invalid date: "${options?.date}" (expected ISO 8601)`
|
|
1686
1768
|
});
|
|
1687
1769
|
}
|
|
@@ -1700,12 +1782,12 @@ function createFormatRouter(adapter) {
|
|
|
1700
1782
|
meta: { timestamp: (/* @__PURE__ */ new Date()).toISOString(), requestId }
|
|
1701
1783
|
});
|
|
1702
1784
|
} catch (error) {
|
|
1703
|
-
if (error instanceof
|
|
1785
|
+
if (error instanceof HTTPException4) throw error;
|
|
1704
1786
|
logger.error("Document diff failed", { error, requestId });
|
|
1705
1787
|
if (error instanceof Error && error.message.includes("top-level component")) {
|
|
1706
|
-
throw new
|
|
1788
|
+
throw new HTTPException4(400, { message: error.message });
|
|
1707
1789
|
}
|
|
1708
|
-
throw new
|
|
1790
|
+
throw new HTTPException4(500, {
|
|
1709
1791
|
message: "Internal server error during document diff"
|
|
1710
1792
|
});
|
|
1711
1793
|
}
|
|
@@ -1728,12 +1810,12 @@ function createFormatRouter(adapter) {
|
|
|
1728
1810
|
const body = await c.req.parseBody();
|
|
1729
1811
|
const file = body.file;
|
|
1730
1812
|
if (!file || typeof file === "string") {
|
|
1731
|
-
throw new
|
|
1813
|
+
throw new HTTPException4(400, {
|
|
1732
1814
|
message: `No ${adapter.name.toUpperCase()} file provided`
|
|
1733
1815
|
});
|
|
1734
1816
|
}
|
|
1735
1817
|
if (file.size === 0) {
|
|
1736
|
-
throw new
|
|
1818
|
+
throw new HTTPException4(400, {
|
|
1737
1819
|
message: `${adapter.name.toUpperCase()} file is empty`
|
|
1738
1820
|
});
|
|
1739
1821
|
}
|
|
@@ -1753,18 +1835,18 @@ function createFormatRouter(adapter) {
|
|
|
1753
1835
|
error,
|
|
1754
1836
|
requestId
|
|
1755
1837
|
});
|
|
1756
|
-
if (error instanceof
|
|
1838
|
+
if (error instanceof HTTPException4) throw error;
|
|
1757
1839
|
if (error instanceof LibreOfficeBinaryNotFoundError) {
|
|
1758
|
-
throw new
|
|
1840
|
+
throw new HTTPException4(503, {
|
|
1759
1841
|
message: "LibreOffice is not available. Install LibreOffice or set LIBREOFFICE_PATH."
|
|
1760
1842
|
});
|
|
1761
1843
|
}
|
|
1762
1844
|
if (error instanceof LibreOfficeTimeoutError || error instanceof LibreOfficeConversionError || error instanceof LibreOfficeOutputNotFoundError) {
|
|
1763
|
-
throw new
|
|
1845
|
+
throw new HTTPException4(500, {
|
|
1764
1846
|
message: "LibreOffice preview conversion failed."
|
|
1765
1847
|
});
|
|
1766
1848
|
}
|
|
1767
|
-
throw new
|
|
1849
|
+
throw new HTTPException4(500, {
|
|
1768
1850
|
message: "Internal server error during preview conversion"
|
|
1769
1851
|
});
|
|
1770
1852
|
}
|
|
@@ -1772,13 +1854,13 @@ function createFormatRouter(adapter) {
|
|
|
1772
1854
|
);
|
|
1773
1855
|
router.post(
|
|
1774
1856
|
"/preview/libreoffice-from-json",
|
|
1775
|
-
|
|
1857
|
+
bodyLimit2({
|
|
1776
1858
|
// Doc JSON + custom themes. 16 MB accommodates real-world docs that
|
|
1777
1859
|
// inline base64 image assets (logos, screenshots, chart images); the
|
|
1778
1860
|
// earlier 2 MB cap rejected legitimate payloads with 413.
|
|
1779
1861
|
maxSize: 16 * 1024 * 1024,
|
|
1780
1862
|
onError: () => {
|
|
1781
|
-
throw new
|
|
1863
|
+
throw new HTTPException4(413, { message: "Request body too large" });
|
|
1782
1864
|
}
|
|
1783
1865
|
}),
|
|
1784
1866
|
rateLimiter({
|
|
@@ -1816,18 +1898,18 @@ function createFormatRouter(adapter) {
|
|
|
1816
1898
|
error,
|
|
1817
1899
|
requestId
|
|
1818
1900
|
});
|
|
1819
|
-
if (error instanceof
|
|
1901
|
+
if (error instanceof HTTPException4) throw error;
|
|
1820
1902
|
if (error instanceof LibreOfficeBinaryNotFoundError) {
|
|
1821
|
-
throw new
|
|
1903
|
+
throw new HTTPException4(503, {
|
|
1822
1904
|
message: "LibreOffice is not available. Install LibreOffice or set LIBREOFFICE_PATH."
|
|
1823
1905
|
});
|
|
1824
1906
|
}
|
|
1825
1907
|
if (error instanceof LibreOfficeTimeoutError || error instanceof LibreOfficeConversionError || error instanceof LibreOfficeOutputNotFoundError) {
|
|
1826
|
-
throw new
|
|
1908
|
+
throw new HTTPException4(500, {
|
|
1827
1909
|
message: "LibreOffice preview conversion failed."
|
|
1828
1910
|
});
|
|
1829
1911
|
}
|
|
1830
|
-
throw new
|
|
1912
|
+
throw new HTTPException4(500, {
|
|
1831
1913
|
message: "Internal server error during preview conversion"
|
|
1832
1914
|
});
|
|
1833
1915
|
}
|
|
@@ -1867,8 +1949,8 @@ function createFormatRouter(adapter) {
|
|
|
1867
1949
|
error,
|
|
1868
1950
|
requestId
|
|
1869
1951
|
});
|
|
1870
|
-
if (error instanceof
|
|
1871
|
-
throw new
|
|
1952
|
+
if (error instanceof HTTPException4) throw error;
|
|
1953
|
+
throw new HTTPException4(500, {
|
|
1872
1954
|
message: "Failed to get standard components definition"
|
|
1873
1955
|
});
|
|
1874
1956
|
}
|
|
@@ -1896,7 +1978,7 @@ function createFormatRouter(adapter) {
|
|
|
1896
1978
|
});
|
|
1897
1979
|
} catch (error) {
|
|
1898
1980
|
logger.error("Failed to get cache statistics", { error });
|
|
1899
|
-
throw new
|
|
1981
|
+
throw new HTTPException4(500, {
|
|
1900
1982
|
message: "Failed to get cache statistics"
|
|
1901
1983
|
});
|
|
1902
1984
|
}
|
|
@@ -1918,7 +2000,7 @@ function createFormatRouter(adapter) {
|
|
|
1918
2000
|
});
|
|
1919
2001
|
} catch (error) {
|
|
1920
2002
|
logger.error("Failed to get cache analytics", { error });
|
|
1921
|
-
throw new
|
|
2003
|
+
throw new HTTPException4(500, {
|
|
1922
2004
|
message: "Failed to get cache analytics"
|
|
1923
2005
|
});
|
|
1924
2006
|
}
|
|
@@ -1936,9 +2018,19 @@ function createFormatRouter(adapter) {
|
|
|
1936
2018
|
});
|
|
1937
2019
|
} catch (error) {
|
|
1938
2020
|
logger.error("Failed to clear cache", { error });
|
|
1939
|
-
throw new
|
|
2021
|
+
throw new HTTPException4(500, { message: "Failed to clear cache" });
|
|
1940
2022
|
}
|
|
1941
2023
|
});
|
|
2024
|
+
registerRasterizeRoute(router, {
|
|
2025
|
+
preMiddleware: [
|
|
2026
|
+
rateLimiter({
|
|
2027
|
+
limit: process.env.NODE_ENV === "production" ? 10 : 1e3,
|
|
2028
|
+
window: 15 * 60 * 1e3,
|
|
2029
|
+
keyGenerator: (c) => c.req.header("X-Real-IP") || c.req.header("X-Forwarded-For")?.split(",").pop()?.trim() || "anonymous"
|
|
2030
|
+
})
|
|
2031
|
+
],
|
|
2032
|
+
onError: (error) => logger.error("Visual rasterization failed", { error })
|
|
2033
|
+
});
|
|
1942
2034
|
return router;
|
|
1943
2035
|
}
|
|
1944
2036
|
var init_format = __esm({
|
|
@@ -1950,6 +2042,7 @@ var init_format = __esm({
|
|
|
1950
2042
|
init_typebox_validator();
|
|
1951
2043
|
init_logger();
|
|
1952
2044
|
init_rate_limit();
|
|
2045
|
+
init_rasterize_route();
|
|
1953
2046
|
init_libreoffice_converter();
|
|
1954
2047
|
}
|
|
1955
2048
|
});
|
|
@@ -2712,8 +2805,8 @@ var init_ai = __esm({
|
|
|
2712
2805
|
// src/server/routes/fonts.ts
|
|
2713
2806
|
import { createHash } from "crypto";
|
|
2714
2807
|
import { Hono as Hono5 } from "hono";
|
|
2715
|
-
import { HTTPException as
|
|
2716
|
-
import { bodyLimit as
|
|
2808
|
+
import { HTTPException as HTTPException5 } from "hono/http-exception";
|
|
2809
|
+
import { bodyLimit as bodyLimit3 } from "hono/body-limit";
|
|
2717
2810
|
import {
|
|
2718
2811
|
SAFE_FONTS,
|
|
2719
2812
|
POPULAR_GOOGLE_FONTS as POPULAR_GOOGLE_FONTS2,
|
|
@@ -2747,11 +2840,11 @@ var init_fonts = __esm({
|
|
|
2747
2840
|
});
|
|
2748
2841
|
fontsRouter.post(
|
|
2749
2842
|
"/materialize",
|
|
2750
|
-
|
|
2843
|
+
bodyLimit3({
|
|
2751
2844
|
// Body is just {family, weights, italics} — 16 KB is generous.
|
|
2752
2845
|
maxSize: 16 * 1024,
|
|
2753
2846
|
onError: () => {
|
|
2754
|
-
throw new
|
|
2847
|
+
throw new HTTPException5(413, { message: "Request body too large" });
|
|
2755
2848
|
}
|
|
2756
2849
|
}),
|
|
2757
2850
|
rateLimiter({
|
|
@@ -2867,7 +2960,7 @@ var init_auth = __esm({
|
|
|
2867
2960
|
});
|
|
2868
2961
|
|
|
2869
2962
|
// src/server/middleware/hono/error-handler.ts
|
|
2870
|
-
import { HTTPException as
|
|
2963
|
+
import { HTTPException as HTTPException6 } from "hono/http-exception";
|
|
2871
2964
|
var errorHandler;
|
|
2872
2965
|
var init_error_handler = __esm({
|
|
2873
2966
|
"src/server/middleware/hono/error-handler.ts"() {
|
|
@@ -2877,7 +2970,7 @@ var init_error_handler = __esm({
|
|
|
2877
2970
|
init_config();
|
|
2878
2971
|
errorHandler = (err, c) => {
|
|
2879
2972
|
const requestId = c.get("requestId") || "unknown";
|
|
2880
|
-
if (err instanceof
|
|
2973
|
+
if (err instanceof HTTPException6) {
|
|
2881
2974
|
return c.json(
|
|
2882
2975
|
{
|
|
2883
2976
|
success: false,
|
|
@@ -3463,7 +3556,7 @@ ${chalk.cyan("Health:")} ${url}/health
|
|
|
3463
3556
|
}
|
|
3464
3557
|
|
|
3465
3558
|
// src/cli.ts
|
|
3466
|
-
var PACKAGE_VERSION = true ? "0.
|
|
3559
|
+
var PACKAGE_VERSION = true ? "0.16.0" : "dev-mode";
|
|
3467
3560
|
var program = new Command2();
|
|
3468
3561
|
program.name("jto").description("JSON to Office CLI - Generate .docx and .pptx from JSON").version(PACKAGE_VERSION);
|
|
3469
3562
|
registerCoreCommands(program, {
|