@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.
Files changed (27) hide show
  1. package/dist/cli.js +167 -40
  2. package/dist/cli.js.map +1 -1
  3. package/dist/client/assets/HomePage-DdoBbkci.js +99 -0
  4. package/dist/client/assets/HomePage-DdoBbkci.js.map +1 -0
  5. package/dist/client/assets/{JsonEditorPage-CzMTUrNe.js → JsonEditorPage-DzwrMvnL.js} +3 -3
  6. package/dist/client/assets/{JsonEditorPage-CzMTUrNe.js.map → JsonEditorPage-DzwrMvnL.js.map} +1 -1
  7. package/dist/client/assets/{MonacoPluginProvider-CmMAmp3u.js → MonacoPluginProvider-YJNcGGax.js} +3 -3
  8. package/dist/client/assets/{MonacoPluginProvider-CmMAmp3u.js.map → MonacoPluginProvider-YJNcGGax.js.map} +1 -1
  9. package/dist/client/assets/{button-BEg2__r6.js → button-D-eAs5li.js} +2 -2
  10. package/dist/client/assets/{button-BEg2__r6.js.map → button-D-eAs5li.js.map} +1 -1
  11. package/dist/client/assets/{editor-DPs_adn-.js → editor-3sZ4Fpsu.js} +2 -2
  12. package/dist/client/assets/{editor-DPs_adn-.js.map → editor-3sZ4Fpsu.js.map} +1 -1
  13. package/dist/client/assets/{editor-monaco-json-xnoKHnLp.js → editor-monaco-json-Cm5QFfZ9.js} +2 -2
  14. package/dist/client/assets/{editor-monaco-json-xnoKHnLp.js.map → editor-monaco-json-Cm5QFfZ9.js.map} +1 -1
  15. package/dist/client/assets/{index-DgKS-gEZ.js → index-Cfi2fdyV.js} +3 -3
  16. package/dist/client/assets/{index-DgKS-gEZ.js.map → index-Cfi2fdyV.js.map} +1 -1
  17. package/dist/client/assets/preview-CC4_sT2f.js +3 -0
  18. package/dist/client/assets/preview-CC4_sT2f.js.map +1 -0
  19. package/dist/client/index.html +1 -1
  20. package/dist/render-server.d.ts +2 -1
  21. package/dist/render-server.js +149 -36
  22. package/dist/render-server.js.map +1 -1
  23. package/package.json +7 -7
  24. package/dist/client/assets/HomePage-g8gOpx6G.js +0 -99
  25. package/dist/client/assets/HomePage-g8gOpx6G.js.map +0 -1
  26. package/dist/client/assets/preview-RxIHvUjz.js +0 -3
  27. 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 { createLibreOfficePptxRasterizer } from "@json-to-office/jto-cli";
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
- router.post(
1925
- "/rasterize",
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
- let safeBaseDir;
1938
- if (baseDir !== void 0) {
1939
- const resolved = path6.resolve(baseDir);
1940
- const cwd = process.cwd();
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
- const result = await getRasterizer()({
2021
+ return getRasterizer()({
1957
2022
  presentation,
1958
- dpi: clampVisualDpi(dpi ?? DEFAULT_VISUAL_DPI),
2023
+ dpi: effectiveDpi,
1959
2024
  baseDir: safeBaseDir
1960
2025
  });
1961
- return c.json(result);
1962
- } catch (error) {
1963
- options.onError?.(error);
1964
- if (error instanceof HTTPException3) throw error;
1965
- if (error instanceof UnsafeOutboundSourceError) {
1966
- throw new HTTPException3(400, { message: error.message });
1967
- }
1968
- const msg = error instanceof Error ? error.message.toLowerCase() : String(error);
1969
- if (msg.includes("not found") || msg.includes("rasterization needs")) {
1970
- throw new HTTPException3(503, { message: error.message });
1971
- }
1972
- if (msg.includes("invalid") || msg.includes("validation")) {
1973
- throw new HTTPException3(400, { message: error.message });
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
- throw new HTTPException3(500, {
1976
- message: "Internal server error during rasterization"
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
- const config2 = typeof jsonDefinition === "string" ? JSON.parse(jsonDefinition) : jsonDefinition;
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.getStandardComponentsDefinition) {
2546
- const standardComponents = await generatorResult.getStandardComponentsDefinition(config2);
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.28.1" : "dev-mode";
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, {