@json-to-office/jto 0.28.1 → 0.30.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 +167 -40
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/HomePage-DdoBbkci.js +99 -0
- package/dist/client/assets/HomePage-DdoBbkci.js.map +1 -0
- package/dist/client/assets/{JsonEditorPage-CzMTUrNe.js → JsonEditorPage-DzwrMvnL.js} +3 -3
- package/dist/client/assets/{JsonEditorPage-CzMTUrNe.js.map → JsonEditorPage-DzwrMvnL.js.map} +1 -1
- package/dist/client/assets/{MonacoPluginProvider-CmMAmp3u.js → MonacoPluginProvider-YJNcGGax.js} +3 -3
- package/dist/client/assets/{MonacoPluginProvider-CmMAmp3u.js.map → MonacoPluginProvider-YJNcGGax.js.map} +1 -1
- package/dist/client/assets/{button-BEg2__r6.js → button-D-eAs5li.js} +2 -2
- package/dist/client/assets/{button-BEg2__r6.js.map → button-D-eAs5li.js.map} +1 -1
- package/dist/client/assets/{editor-DPs_adn-.js → editor-3sZ4Fpsu.js} +2 -2
- package/dist/client/assets/{editor-DPs_adn-.js.map → editor-3sZ4Fpsu.js.map} +1 -1
- package/dist/client/assets/{editor-monaco-json-xnoKHnLp.js → editor-monaco-json-Cm5QFfZ9.js} +2 -2
- package/dist/client/assets/{editor-monaco-json-xnoKHnLp.js.map → editor-monaco-json-Cm5QFfZ9.js.map} +1 -1
- package/dist/client/assets/{index-DgKS-gEZ.js → index-Cfi2fdyV.js} +3 -3
- package/dist/client/assets/{index-DgKS-gEZ.js.map → index-Cfi2fdyV.js.map} +1 -1
- package/dist/client/assets/preview-CC4_sT2f.js +3 -0
- package/dist/client/assets/preview-CC4_sT2f.js.map +1 -0
- package/dist/client/index.html +1 -1
- package/dist/render-server.d.ts +2 -1
- package/dist/render-server.js +149 -36
- package/dist/render-server.js.map +1 -1
- package/package.json +7 -7
- package/dist/client/assets/HomePage-g8gOpx6G.js +0 -99
- package/dist/client/assets/HomePage-g8gOpx6G.js.map +0 -1
- package/dist/client/assets/preview-RxIHvUjz.js +0 -3
- package/dist/client/assets/preview-RxIHvUjz.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1910,19 +1910,80 @@ import {
|
|
|
1910
1910
|
clampVisualDpi,
|
|
1911
1911
|
DEFAULT_VISUAL_DPI,
|
|
1912
1912
|
MIN_VISUAL_DPI,
|
|
1913
|
-
MAX_VISUAL_DPI
|
|
1913
|
+
MAX_VISUAL_DPI,
|
|
1914
|
+
MAX_RASTERIZE_BATCH_SLIDES
|
|
1914
1915
|
} from "@json-to-office/shared";
|
|
1915
|
-
import {
|
|
1916
|
+
import {
|
|
1917
|
+
createLibreOfficePptxRasterizer,
|
|
1918
|
+
createLibreOfficePptxBatchRasterizer
|
|
1919
|
+
} from "@json-to-office/jto-cli";
|
|
1916
1920
|
function getSharedRasterizer() {
|
|
1917
1921
|
if (!sharedRasterizer) {
|
|
1918
1922
|
sharedRasterizer = createLibreOfficePptxRasterizer();
|
|
1919
1923
|
}
|
|
1920
1924
|
return sharedRasterizer;
|
|
1921
1925
|
}
|
|
1926
|
+
function getSharedBatchRasterizer() {
|
|
1927
|
+
if (!sharedBatchRasterizer) {
|
|
1928
|
+
sharedBatchRasterizer = createLibreOfficePptxBatchRasterizer();
|
|
1929
|
+
}
|
|
1930
|
+
return sharedBatchRasterizer;
|
|
1931
|
+
}
|
|
1932
|
+
function estimateSlidePixels(presentation, dpi) {
|
|
1933
|
+
const props = presentation?.props;
|
|
1934
|
+
const dim = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
1935
|
+
const widthIn = dim(props?.slideWidth, DEFAULT_SLIDE_WIDTH_IN);
|
|
1936
|
+
const heightIn = dim(props?.slideHeight, DEFAULT_SLIDE_HEIGHT_IN);
|
|
1937
|
+
return widthIn * dpi * (heightIn * dpi);
|
|
1938
|
+
}
|
|
1939
|
+
function assertPixelBudget(slides) {
|
|
1940
|
+
let total = 0;
|
|
1941
|
+
for (const slide of slides) {
|
|
1942
|
+
const pixels = estimateSlidePixels(slide.presentation, slide.dpi);
|
|
1943
|
+
if (pixels > MAX_SLIDE_PIXELS) {
|
|
1944
|
+
throw new HTTPException3(400, {
|
|
1945
|
+
message: "Requested slide dimensions are too large"
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
total += pixels;
|
|
1949
|
+
}
|
|
1950
|
+
if (total > MAX_BATCH_PIXELS) {
|
|
1951
|
+
throw new HTTPException3(400, {
|
|
1952
|
+
message: "Requested batch raster size is too large"
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
function resolveSafeBaseDir(baseDir) {
|
|
1957
|
+
if (baseDir === void 0) return void 0;
|
|
1958
|
+
const resolved = path6.resolve(baseDir);
|
|
1959
|
+
const cwd = process.cwd();
|
|
1960
|
+
if (resolved !== cwd && !resolved.startsWith(cwd + path6.sep)) {
|
|
1961
|
+
throw new HTTPException3(400, {
|
|
1962
|
+
message: "baseDir must be inside the server working directory"
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
return resolved;
|
|
1966
|
+
}
|
|
1967
|
+
function toHttpException(error) {
|
|
1968
|
+
if (error instanceof HTTPException3) return error;
|
|
1969
|
+
if (error instanceof UnsafeOutboundSourceError) {
|
|
1970
|
+
return new HTTPException3(400, { message: error.message });
|
|
1971
|
+
}
|
|
1972
|
+
const msg = error instanceof Error ? error.message.toLowerCase() : String(error);
|
|
1973
|
+
if (msg.includes("not found") || msg.includes("rasterization needs")) {
|
|
1974
|
+
return new HTTPException3(503, { message: error.message });
|
|
1975
|
+
}
|
|
1976
|
+
if (msg.includes("invalid") || msg.includes("validation")) {
|
|
1977
|
+
return new HTTPException3(400, { message: error.message });
|
|
1978
|
+
}
|
|
1979
|
+
return new HTTPException3(500, {
|
|
1980
|
+
message: "Internal server error during rasterization"
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1922
1983
|
function registerRasterizeRoute(router, options = {}) {
|
|
1923
1984
|
const getRasterizer = options.getRasterizer ?? getSharedRasterizer;
|
|
1924
|
-
|
|
1925
|
-
|
|
1985
|
+
const getBatchRasterizer = options.getBatchRasterizer ?? getSharedBatchRasterizer;
|
|
1986
|
+
const shared = [
|
|
1926
1987
|
...options.preMiddleware ?? [],
|
|
1927
1988
|
bodyLimit({
|
|
1928
1989
|
maxSize: 32 * 1024 * 1024,
|
|
@@ -1930,22 +1991,26 @@ function registerRasterizeRoute(router, options = {}) {
|
|
|
1930
1991
|
throw new HTTPException3(413, { message: "Request body too large" });
|
|
1931
1992
|
}
|
|
1932
1993
|
}),
|
|
1933
|
-
jsonOnly
|
|
1994
|
+
jsonOnly
|
|
1995
|
+
];
|
|
1996
|
+
const guard = async (run) => {
|
|
1997
|
+
try {
|
|
1998
|
+
return await run();
|
|
1999
|
+
} catch (error) {
|
|
2000
|
+
options.onError?.(error);
|
|
2001
|
+
throw toHttpException(error);
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
router.post(
|
|
2005
|
+
"/rasterize",
|
|
2006
|
+
...shared,
|
|
1934
2007
|
tbValidator(RasterizeRequestSchema),
|
|
1935
2008
|
async (c) => {
|
|
1936
2009
|
const { presentation, dpi, baseDir } = getValidated(c, "json");
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
if (resolved !== cwd && !resolved.startsWith(cwd + path6.sep)) {
|
|
1942
|
-
throw new HTTPException3(400, {
|
|
1943
|
-
message: "baseDir must be inside the server working directory"
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
safeBaseDir = resolved;
|
|
1947
|
-
}
|
|
1948
|
-
try {
|
|
2010
|
+
const safeBaseDir = resolveSafeBaseDir(baseDir);
|
|
2011
|
+
const effectiveDpi = clampVisualDpi(dpi ?? DEFAULT_VISUAL_DPI);
|
|
2012
|
+
assertPixelBudget([{ presentation, dpi: effectiveDpi }]);
|
|
2013
|
+
const result = await guard(async () => {
|
|
1949
2014
|
if (options.sourcePolicy) {
|
|
1950
2015
|
assertSafeOutboundSources(
|
|
1951
2016
|
presentation,
|
|
@@ -1953,33 +2018,57 @@ function registerRasterizeRoute(router, options = {}) {
|
|
|
1953
2018
|
"presentation"
|
|
1954
2019
|
);
|
|
1955
2020
|
}
|
|
1956
|
-
|
|
2021
|
+
return getRasterizer()({
|
|
1957
2022
|
presentation,
|
|
1958
|
-
dpi:
|
|
2023
|
+
dpi: effectiveDpi,
|
|
1959
2024
|
baseDir: safeBaseDir
|
|
1960
2025
|
});
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2026
|
+
});
|
|
2027
|
+
return c.json(result);
|
|
2028
|
+
}
|
|
2029
|
+
);
|
|
2030
|
+
router.post(
|
|
2031
|
+
"/rasterize/batch",
|
|
2032
|
+
...shared,
|
|
2033
|
+
tbValidator(RasterizeBatchRequestSchema),
|
|
2034
|
+
async (c) => {
|
|
2035
|
+
const { slides, baseDir } = getValidated(c, "json");
|
|
2036
|
+
const safeBaseDir = resolveSafeBaseDir(baseDir);
|
|
2037
|
+
const effectiveSlides = slides.map((slide) => ({
|
|
2038
|
+
presentation: slide.presentation,
|
|
2039
|
+
dpi: clampVisualDpi(slide.dpi ?? DEFAULT_VISUAL_DPI)
|
|
2040
|
+
}));
|
|
2041
|
+
assertPixelBudget(effectiveSlides);
|
|
2042
|
+
const result = await guard(async () => {
|
|
2043
|
+
if (options.sourcePolicy) {
|
|
2044
|
+
slides.forEach(
|
|
2045
|
+
(slide, index) => assertSafeOutboundSources(
|
|
2046
|
+
slide.presentation,
|
|
2047
|
+
options.sourcePolicy,
|
|
2048
|
+
`slides[${index}].presentation`
|
|
2049
|
+
)
|
|
2050
|
+
);
|
|
1974
2051
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
2052
|
+
return getBatchRasterizer()({
|
|
2053
|
+
slides: effectiveSlides,
|
|
2054
|
+
baseDir: safeBaseDir
|
|
1977
2055
|
});
|
|
1978
|
-
}
|
|
2056
|
+
});
|
|
2057
|
+
return c.json({
|
|
2058
|
+
results: result.results.map((slide) => {
|
|
2059
|
+
if (slide.ok) return slide;
|
|
2060
|
+
options.onError?.(new Error(slide.error));
|
|
2061
|
+
return slide.stage === "build" ? { ok: false, error: slide.error, stage: slide.stage } : {
|
|
2062
|
+
ok: false,
|
|
2063
|
+
error: "Slide rasterization failed",
|
|
2064
|
+
stage: slide.stage
|
|
2065
|
+
};
|
|
2066
|
+
})
|
|
2067
|
+
});
|
|
1979
2068
|
}
|
|
1980
2069
|
);
|
|
1981
2070
|
}
|
|
1982
|
-
var RasterizeRequestSchema, sharedRasterizer, jsonOnly;
|
|
2071
|
+
var RasterizeRequestSchema, RasterizeBatchRequestSchema, sharedRasterizer, sharedBatchRasterizer, jsonOnly, DEFAULT_SLIDE_WIDTH_IN, DEFAULT_SLIDE_HEIGHT_IN, MAX_SLIDE_PIXELS, MAX_BATCH_PIXELS;
|
|
1983
2072
|
var init_rasterize_route = __esm({
|
|
1984
2073
|
"src/server/rasterize-route.ts"() {
|
|
1985
2074
|
"use strict";
|
|
@@ -1996,6 +2085,24 @@ var init_rasterize_route = __esm({
|
|
|
1996
2085
|
},
|
|
1997
2086
|
{ additionalProperties: false }
|
|
1998
2087
|
);
|
|
2088
|
+
RasterizeBatchRequestSchema = Type2.Object(
|
|
2089
|
+
{
|
|
2090
|
+
slides: Type2.Array(
|
|
2091
|
+
Type2.Object(
|
|
2092
|
+
{
|
|
2093
|
+
presentation: Type2.Object({}, { additionalProperties: true }),
|
|
2094
|
+
dpi: Type2.Optional(
|
|
2095
|
+
Type2.Number({ minimum: MIN_VISUAL_DPI, maximum: MAX_VISUAL_DPI })
|
|
2096
|
+
)
|
|
2097
|
+
},
|
|
2098
|
+
{ additionalProperties: false }
|
|
2099
|
+
),
|
|
2100
|
+
{ minItems: 1, maxItems: MAX_RASTERIZE_BATCH_SLIDES }
|
|
2101
|
+
),
|
|
2102
|
+
baseDir: Type2.Optional(Type2.String())
|
|
2103
|
+
},
|
|
2104
|
+
{ additionalProperties: false }
|
|
2105
|
+
);
|
|
1999
2106
|
jsonOnly = async (c, next) => {
|
|
2000
2107
|
const contentType = c.req.header("content-type");
|
|
2001
2108
|
if (!contentType || !contentType.includes("application/json")) {
|
|
@@ -2005,6 +2112,10 @@ var init_rasterize_route = __esm({
|
|
|
2005
2112
|
}
|
|
2006
2113
|
await next();
|
|
2007
2114
|
};
|
|
2115
|
+
DEFAULT_SLIDE_WIDTH_IN = 13.34;
|
|
2116
|
+
DEFAULT_SLIDE_HEIGHT_IN = 7.5;
|
|
2117
|
+
MAX_SLIDE_PIXELS = 64e6;
|
|
2118
|
+
MAX_BATCH_PIXELS = 256e6;
|
|
2008
2119
|
}
|
|
2009
2120
|
});
|
|
2010
2121
|
|
|
@@ -2535,15 +2646,22 @@ function createFormatRouter(adapter) {
|
|
|
2535
2646
|
try {
|
|
2536
2647
|
assertRequestSources(jsonDefinition, "jsonDefinition");
|
|
2537
2648
|
assertRequestSources(customThemes, "customThemes");
|
|
2538
|
-
|
|
2649
|
+
let config2;
|
|
2650
|
+
try {
|
|
2651
|
+
config2 = typeof jsonDefinition === "string" ? JSON.parse(jsonDefinition) : jsonDefinition;
|
|
2652
|
+
} catch {
|
|
2653
|
+
throw new HTTPException4(400, {
|
|
2654
|
+
message: "jsonDefinition is not valid JSON"
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2539
2657
|
const registry = PluginRegistry2.getInstance();
|
|
2540
2658
|
if (registry.hasPlugins()) {
|
|
2541
2659
|
const plugins = registry.getPlugins();
|
|
2542
2660
|
const generatorResult = await adapter.createGenerator(plugins, {
|
|
2543
2661
|
theme: customThemes ? Object.values(customThemes)[0] : void 0
|
|
2544
2662
|
});
|
|
2545
|
-
if (generatorResult.
|
|
2546
|
-
const standardComponents = await generatorResult.
|
|
2663
|
+
if (generatorResult.getStandardDefinition) {
|
|
2664
|
+
const standardComponents = await generatorResult.getStandardDefinition(config2);
|
|
2547
2665
|
return c.json({
|
|
2548
2666
|
success: true,
|
|
2549
2667
|
data: standardComponents,
|
|
@@ -2562,6 +2680,12 @@ function createFormatRouter(adapter) {
|
|
|
2562
2680
|
requestId
|
|
2563
2681
|
});
|
|
2564
2682
|
if (error instanceof HTTPException4) throw error;
|
|
2683
|
+
if (error instanceof Error) {
|
|
2684
|
+
const msg = error.message.toLowerCase();
|
|
2685
|
+
if (msg.includes("invalid") || msg.includes("validation") || msg.includes("missing required") || msg.includes("unknown component")) {
|
|
2686
|
+
throw new HTTPException4(400, { message: error.message });
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2565
2689
|
throw new HTTPException4(500, {
|
|
2566
2690
|
message: "Failed to get standard components definition"
|
|
2567
2691
|
});
|
|
@@ -2638,6 +2762,9 @@ function createFormatRouter(adapter) {
|
|
|
2638
2762
|
rateLimiter({
|
|
2639
2763
|
limit: process.env.NODE_ENV === "production" ? 10 : 1e3,
|
|
2640
2764
|
window: 15 * 60 * 1e3,
|
|
2765
|
+
// One bucket for /rasterize AND /rasterize/batch — without a shared
|
|
2766
|
+
// namespace the default per-path key would double the budget.
|
|
2767
|
+
namespace: "rasterize",
|
|
2641
2768
|
trustProxy: config.rateLimit.trustProxy
|
|
2642
2769
|
})
|
|
2643
2770
|
],
|
|
@@ -4311,7 +4438,7 @@ function createDevCommand(adapter) {
|
|
|
4311
4438
|
}
|
|
4312
4439
|
|
|
4313
4440
|
// src/cli.ts
|
|
4314
|
-
var PACKAGE_VERSION = true ? "0.
|
|
4441
|
+
var PACKAGE_VERSION = true ? "0.30.0" : "dev-mode";
|
|
4315
4442
|
var program = new Command2();
|
|
4316
4443
|
program.name("jto").description("JSON to Office CLI - Generate .docx and .pptx from JSON").version(PACKAGE_VERSION);
|
|
4317
4444
|
registerCoreCommands(program, {
|