@json-to-office/jto 1.2.0 → 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.
Files changed (51) hide show
  1. package/dist/cli.js +178 -31
  2. package/dist/cli.js.map +1 -1
  3. package/dist/client/assets/{HomePage-jkIz_s7u.js → HomePage-ChJyDqzj.js} +44 -44
  4. package/dist/client/assets/HomePage-ChJyDqzj.js.map +1 -0
  5. package/dist/client/assets/{JsonEditorPage-DR8UoROz.js → JsonEditorPage-COHI5Wez.js} +3 -3
  6. package/dist/client/assets/{JsonEditorPage-DR8UoROz.js.map → JsonEditorPage-COHI5Wez.js.map} +1 -1
  7. package/dist/client/assets/{MonacoPluginProvider-CE5NPGwN.js → MonacoPluginProvider-prawCY6T.js} +3 -3
  8. package/dist/client/assets/MonacoPluginProvider-prawCY6T.js.map +1 -0
  9. package/dist/client/assets/editor-BO-KsLDk.js +9 -0
  10. package/dist/client/assets/editor-BO-KsLDk.js.map +1 -0
  11. package/dist/client/assets/editor-monaco-json-CJh63Fsz.js +24 -0
  12. package/dist/client/assets/editor-monaco-json-CJh63Fsz.js.map +1 -0
  13. package/dist/client/assets/editor-refs-store-CVLA_D5G.js +2 -0
  14. package/dist/client/assets/{editor-refs-store-DKOJcTAk.js.map → editor-refs-store-CVLA_D5G.js.map} +1 -1
  15. package/dist/client/assets/index-CrzIwHLS.css +1 -0
  16. package/dist/client/assets/index-otgy8_us.js +5 -0
  17. package/dist/client/assets/index-otgy8_us.js.map +1 -0
  18. package/dist/client/assets/preview-3fuCtJiz.js +2 -0
  19. package/dist/client/assets/preview-3fuCtJiz.js.map +1 -0
  20. package/dist/client/assets/{ui-vendor-DpjQWJgX.js → ui-vendor-Bq4-oNFL.js} +92 -57
  21. package/dist/client/assets/ui-vendor-Bq4-oNFL.js.map +1 -0
  22. package/dist/client/assets/useQualityAnalysis-Ci_jNmHv.js +6 -0
  23. package/dist/client/assets/useQualityAnalysis-Ci_jNmHv.js.map +1 -0
  24. package/dist/client/index.html +3 -3
  25. package/dist/client/templates/data-report-presentation.pptx.json +22 -22
  26. package/dist/client/templates/minimalist-pitch-deck.pptx.json +52 -52
  27. package/dist/client/templates/modern-annual-report-1.docx.json +2 -2
  28. package/dist/client/templates/modern-annual-report-2.docx.json +2 -2
  29. package/dist/client/templates/modern-annual-report-3.docx.json +1 -1
  30. package/dist/client/templates/tech-report.docx.json +8 -8
  31. package/dist/client/templates/themes/corporate.pptx.theme.json +12 -2
  32. package/dist/client/templates/themes/minimal.pptx.theme.json +12 -2
  33. package/dist/client/templates/themes/vibrant.pptx.theme.json +12 -2
  34. package/package.json +8 -7
  35. package/dist/client/assets/HomePage-jkIz_s7u.js.map +0 -1
  36. package/dist/client/assets/MonacoPluginProvider-CE5NPGwN.js.map +0 -1
  37. package/dist/client/assets/editor-edmvhW9F.js +0 -9
  38. package/dist/client/assets/editor-edmvhW9F.js.map +0 -1
  39. package/dist/client/assets/editor-monaco-json-CAlvfPc7.js +0 -24
  40. package/dist/client/assets/editor-monaco-json-CAlvfPc7.js.map +0 -1
  41. package/dist/client/assets/editor-refs-store-DKOJcTAk.js +0 -2
  42. package/dist/client/assets/index-CNvLzaJ7.css +0 -1
  43. package/dist/client/assets/index-DLlW4q4v.js +0 -5
  44. package/dist/client/assets/index-DLlW4q4v.js.map +0 -1
  45. package/dist/client/assets/preview-zo-CHAU0.js +0 -2
  46. package/dist/client/assets/preview-zo-CHAU0.js.map +0 -1
  47. package/dist/client/assets/ui-vendor-DpjQWJgX.js.map +0 -1
  48. package/dist/client/templates/Alternative deck 16_9.pptx.json +0 -3103
  49. package/dist/client/templates/Brand template 16_9.pptx.json +0 -2013
  50. package/dist/client/templates/Company deck 16_9.pptx.json +0 -6384
  51. package/dist/client/templates/Company deck 4_3.pptx.json +0 -5426
package/dist/cli.js CHANGED
@@ -225,6 +225,30 @@ import {
225
225
  documentFontRegistry,
226
226
  themeFontRegistry
227
227
  } from "@json-to-office/shared";
228
+ import {
229
+ QualityGateError
230
+ } from "@json-to-office/quality";
231
+ function toQualityWarnings(diagnostics) {
232
+ return diagnostics.map((finding) => ({
233
+ component: "quality",
234
+ message: `[${finding.code}] ${finding.message}`,
235
+ severity: finding.severity === "info" ? "info" : "warning",
236
+ context: {
237
+ ...finding.context ?? {},
238
+ code: finding.code,
239
+ path: finding.path,
240
+ originalSeverity: finding.severity,
241
+ ...finding.ruleId && { ruleId: finding.ruleId },
242
+ ...finding.category && { category: finding.category },
243
+ ...finding.certainty && { certainty: finding.certainty },
244
+ blocking: finding.blocking,
245
+ ...finding.suggestion && { suggestion: finding.suggestion },
246
+ ...finding.relatedPaths && { relatedPaths: finding.relatedPaths },
247
+ ...finding.evidence && { evidence: finding.evidence },
248
+ ...finding.fixes && { fixes: finding.fixes }
249
+ }
250
+ }));
251
+ }
228
252
  function collectReferencedNames(config2, customThemes, adapterName) {
229
253
  const collect = adapterName === "docx" ? collectFontNamesFromDocx : collectFontNamesFromPptx;
230
254
  const names = /* @__PURE__ */ new Set();
@@ -444,6 +468,56 @@ var init_generator = __esm({
444
468
  const bypassCache = options?.bypassCache === true || extraEntries.length > 0 || declaresRegistry;
445
469
  const baseDir = typeof options?.baseDir === "string" ? options.baseDir : void 0;
446
470
  const renderer = typeof options?.renderer === "string" ? options.renderer : void 0;
471
+ const resolvedFonts = [];
472
+ const coreWarnings = [];
473
+ const needsFontOpts = extraEntries.length > 0 || declaresRegistry || fontMode !== void 0 || fontSubstitution !== void 0 || callerStrict !== void 0;
474
+ const fontOpts = needsFontOpts ? {
475
+ ...extraEntries.length > 0 && { extraEntries },
476
+ ...fontMode && { mode: fontMode },
477
+ ...fontSubstitution && { substitution: fontSubstitution },
478
+ ...callerStrict !== void 0 && { strict: callerStrict },
479
+ onResolved: (resolved) => {
480
+ resolvedFonts.push(...resolved);
481
+ }
482
+ } : void 0;
483
+ const registry = PluginRegistry.getInstance();
484
+ const quality = options?.quality;
485
+ const prepared = !registry.hasPlugins() && this.adapter.prepareDocument && this.adapter.validateDocument(config2).valid ? await this.adapter.prepareDocument(config2, {
486
+ customThemes,
487
+ fonts: fontOpts,
488
+ baseDir,
489
+ renderer,
490
+ warnings: coreWarnings
491
+ }) : void 0;
492
+ const qualityOptions = {
493
+ customThemes,
494
+ fonts: fontOpts,
495
+ baseDir,
496
+ renderer,
497
+ quality,
498
+ prepared
499
+ };
500
+ let qualityWarnings = [];
501
+ try {
502
+ if (this.adapter.analyzeQuality) {
503
+ const analysis = await this.adapter.analyzeQuality(
504
+ config2,
505
+ qualityOptions
506
+ );
507
+ if (analysis.blocked) throw new QualityGateError(analysis);
508
+ qualityWarnings = toQualityWarnings(analysis.diagnostics);
509
+ }
510
+ } catch (error) {
511
+ const errorCode = error?.code;
512
+ if (errorCode === "QUALITY_GATE_FAILED" || errorCode === "QUALITY_PROFILE_INCOMPATIBLE" || // An unusable policy is the caller's configuration, not a hiccup.
513
+ errorCode === "QUALITY_POLICY_INVALID" || quality?.policy?.onRuleError === "throw" || // A caller who asked for a gate must not receive an ungated document:
514
+ // if the analysis failed, the gate never ran. Only the advisory case
515
+ // (no gate requested) degrades to a warning.
516
+ quality?.policy?.gate && quality.policy.gate !== "none") {
517
+ throw error;
518
+ }
519
+ logger.warn("Quality analysis failed", { error });
520
+ }
447
521
  const cacheKeyData = {
448
522
  config: config2,
449
523
  customThemes: customThemes && Object.keys(customThemes).length > 0 ? customThemes : null,
@@ -466,30 +540,17 @@ var init_generator = __esm({
466
540
  filename: `${config2.metadata?.title || this.adapter.label}${this.adapter.extension}`,
467
541
  fileId: Date.now().toString(),
468
542
  buffer: cached.buffer,
469
- // Warnings ride along with the bytes: a cache hit describes the same
470
- // document, so it must report the same font problems.
543
+ // Render warnings ride with bytes. Quality is request policy, so it
544
+ // is recomputed and merged even when the rendered artifact is cached.
471
545
  cached: true,
472
- warnings: cached.warnings
546
+ warnings: cached.warnings || qualityWarnings.length > 0 ? [...cached.warnings ?? [], ...qualityWarnings] : null
473
547
  };
474
548
  }
475
549
  }
476
550
  logger.info(`Generating ${this.adapter.label}`, {
477
551
  title: config2.metadata?.title
478
552
  });
479
- const registry = PluginRegistry.getInstance();
480
553
  let buffer;
481
- const resolvedFonts = [];
482
- const needsFontOpts = extraEntries.length > 0 || declaresRegistry || fontMode !== void 0 || fontSubstitution !== void 0 || callerStrict !== void 0;
483
- const fontOpts = needsFontOpts ? {
484
- ...extraEntries.length > 0 && { extraEntries },
485
- ...fontMode && { mode: fontMode },
486
- ...fontSubstitution && { substitution: fontSubstitution },
487
- ...callerStrict !== void 0 && { strict: callerStrict },
488
- onResolved: (r) => {
489
- resolvedFonts.push(...r);
490
- }
491
- } : void 0;
492
- const coreWarnings = [];
493
554
  if (registry.hasPlugins()) {
494
555
  const plugins = registry.getPlugins();
495
556
  const generator = await this.adapter.createGenerator(plugins, {
@@ -497,7 +558,8 @@ var init_generator = __esm({
497
558
  fonts: fontOpts,
498
559
  baseDir,
499
560
  renderer,
500
- warnings: coreWarnings
561
+ warnings: coreWarnings,
562
+ quality
501
563
  });
502
564
  buffer = await generator.generateBuffer(config2);
503
565
  } else {
@@ -506,7 +568,9 @@ var init_generator = __esm({
506
568
  fonts: fontOpts,
507
569
  baseDir,
508
570
  renderer,
509
- warnings: coreWarnings
571
+ warnings: coreWarnings,
572
+ quality,
573
+ prepared
510
574
  });
511
575
  }
512
576
  const CANONICAL_WEIGHTS = /* @__PURE__ */ new Set([
@@ -532,10 +596,14 @@ var init_generator = __esm({
532
596
  context: { code: "FONT_NONCANONICAL_WEIGHT" }
533
597
  });
534
598
  }
535
- const allWarnings = [...coreWarnings, ...extraWarnings];
599
+ const renderWarnings = [...coreWarnings, ...extraWarnings];
600
+ const allWarnings = [...renderWarnings, ...qualityWarnings];
536
601
  this.cacheService.set(
537
602
  cacheKey,
538
- { buffer, warnings: allWarnings.length > 0 ? allWarnings : null },
603
+ {
604
+ buffer,
605
+ warnings: renderWarnings.length > 0 ? renderWarnings : null
606
+ },
539
607
  config2,
540
608
  { bypassCache: bypassCache || hasDynamicContent }
541
609
  );
@@ -548,9 +616,35 @@ var init_generator = __esm({
548
616
  resolvedFonts
549
617
  };
550
618
  }
551
- async validate(jsonDefinition) {
619
+ async validate(jsonDefinition, options = {}) {
552
620
  const config2 = typeof jsonDefinition === "string" ? JSON.parse(jsonDefinition) : jsonDefinition;
553
- return this.adapter.validateDocument(config2);
621
+ const result = this.adapter.validateDocument(config2);
622
+ if (!result.valid) return result;
623
+ if (this.adapter.analyzeQuality) {
624
+ try {
625
+ const prepared = this.adapter.prepareDocument ? await this.adapter.prepareDocument(config2, options) : void 0;
626
+ const qualityAnalysis = await this.adapter.analyzeQuality(
627
+ config2,
628
+ prepared ? { ...options, prepared } : options
629
+ );
630
+ return {
631
+ ...result,
632
+ valid: result.valid && !qualityAnalysis.blocked,
633
+ qualityAnalysis
634
+ };
635
+ } catch (error) {
636
+ const errorCode = error?.code;
637
+ const quality = options.quality;
638
+ if (errorCode === "QUALITY_GATE_FAILED" || errorCode === "QUALITY_PROFILE_INCOMPATIBLE" || errorCode === "QUALITY_POLICY_INVALID" || quality?.policy?.onRuleError === "throw" || // Same rule `generate` applies: an analysis that never ran leaves a
639
+ // requested gate unevaluated, and answering `valid: true` there would
640
+ // promise a generation this service would then refuse.
641
+ quality?.policy?.gate && quality.policy.gate !== "none") {
642
+ throw error;
643
+ }
644
+ logger.warn("Quality analysis failed", { error });
645
+ }
646
+ }
647
+ return result;
554
648
  }
555
649
  destroy() {
556
650
  if (this.cacheInvalidationHandler) {
@@ -992,7 +1086,15 @@ var init_health = __esm({
992
1086
  const cacheService = getContainer().get("cacheService");
993
1087
  cacheStats = cacheService.getStats();
994
1088
  } catch {
995
- cacheStats = { enabled: false, hits: 0, misses: 0, hitRate: 0, evictions: 0, size: 0, itemCount: 0 };
1089
+ cacheStats = {
1090
+ enabled: false,
1091
+ hits: 0,
1092
+ misses: 0,
1093
+ hitRate: 0,
1094
+ evictions: 0,
1095
+ size: 0,
1096
+ itemCount: 0
1097
+ };
996
1098
  }
997
1099
  return c.json({
998
1100
  status: "ok",
@@ -1022,7 +1124,7 @@ var init_health = __esm({
1022
1124
 
1023
1125
  // src/server/schemas/loose.ts
1024
1126
  import { Type } from "@sinclair/typebox";
1025
- var FontOptionsSchema, LooseDocumentGenerationRequestSchema, LooseDocumentValidationRequestSchema, LooseDocumentDiffRequestSchema;
1127
+ var FontOptionsSchema, QualityOptionsSchema, LooseDocumentGenerationRequestSchema, LooseDocumentValidationRequestSchema, LooseDocumentDiffRequestSchema;
1026
1128
  var init_loose = __esm({
1027
1129
  "src/server/schemas/loose.ts"() {
1028
1130
  "use strict";
@@ -1043,6 +1145,18 @@ var init_loose = __esm({
1043
1145
  },
1044
1146
  { additionalProperties: false }
1045
1147
  );
1148
+ QualityOptionsSchema = Type.Object(
1149
+ {
1150
+ profile: Type.Optional(
1151
+ Type.Object(
1152
+ { id: Type.String({ minLength: 1, maxLength: 128 }) },
1153
+ { additionalProperties: true }
1154
+ )
1155
+ ),
1156
+ policy: Type.Optional(Type.Object({}, { additionalProperties: true }))
1157
+ },
1158
+ { additionalProperties: false }
1159
+ );
1046
1160
  LooseDocumentGenerationRequestSchema = Type.Object(
1047
1161
  {
1048
1162
  jsonDefinition: Type.Union([
@@ -1062,6 +1176,7 @@ var init_loose = __esm({
1062
1176
  // enum: the registry owns the list, answers an unknown id with the
1063
1177
  // ids that exist, and the route turns that into a 400.
1064
1178
  renderer: Type.Optional(Type.String({ maxLength: 64 })),
1179
+ quality: Type.Optional(QualityOptionsSchema),
1065
1180
  // Name of the discovered document this definition came from; the
1066
1181
  // server maps it to that document's directory so relative asset
1067
1182
  // paths resolve against it (#142). Names, never paths — a client
@@ -1081,7 +1196,16 @@ var init_loose = __esm({
1081
1196
  // Allow JSON string
1082
1197
  Type.Object({}, { additionalProperties: true })
1083
1198
  // Allow any object
1084
- ])
1199
+ ]),
1200
+ options: Type.Optional(
1201
+ Type.Object(
1202
+ {
1203
+ renderer: Type.Optional(Type.String({ maxLength: 64 })),
1204
+ quality: Type.Optional(QualityOptionsSchema)
1205
+ },
1206
+ { additionalProperties: false }
1207
+ )
1208
+ )
1085
1209
  },
1086
1210
  { additionalProperties: true }
1087
1211
  );
@@ -1950,6 +2074,17 @@ import {
1950
2074
  import { dirname } from "path";
1951
2075
  function throwIfClientError(error) {
1952
2076
  const code = error?.code;
2077
+ if (code === "QUALITY_GATE_FAILED") {
2078
+ throw new HTTPException4(400, {
2079
+ message: error.message,
2080
+ cause: error
2081
+ });
2082
+ }
2083
+ if (code === "QUALITY_PROFILE_INCOMPATIBLE" || // A gate or severity the policy parser cannot read came in on the
2084
+ // request, so the request is what is at fault.
2085
+ code === "QUALITY_POLICY_INVALID") {
2086
+ throw new HTTPException4(400, { message: error.message });
2087
+ }
1953
2088
  if (code === "UNSUPPORTED_RENDERER_FEATURE" || code === "UNCOMPILED_COMPONENT" || // An id nobody registered is the caller naming a backend that does not
1954
2089
  // exist. The message already lists the ones that do.
1955
2090
  code === "UNKNOWN_RENDERER") {
@@ -2044,6 +2179,7 @@ function createFormatRouter(adapter) {
2044
2179
  }
2045
2180
  const clientOptions = { ...options };
2046
2181
  delete clientOptions.baseDir;
2182
+ delete clientOptions.prepared;
2047
2183
  const result = await generatorService.generate({
2048
2184
  jsonDefinition: effectiveDefinition,
2049
2185
  customThemes,
@@ -2091,13 +2227,10 @@ function createFormatRouter(adapter) {
2091
2227
  tbValidator(LooseDocumentValidationRequestSchema),
2092
2228
  async (c) => {
2093
2229
  const generatorService = getContainer().get("generatorService");
2094
- const { jsonDefinition } = getValidated(
2095
- c,
2096
- "json"
2097
- );
2230
+ const { jsonDefinition, options } = getValidated(c, "json");
2098
2231
  const requestId = c.get("requestId");
2099
2232
  try {
2100
- const result = await generatorService.validate(jsonDefinition);
2233
+ const result = await generatorService.validate(jsonDefinition, options);
2101
2234
  return c.json({
2102
2235
  success: result.valid,
2103
2236
  data: result,
@@ -2105,6 +2238,7 @@ function createFormatRouter(adapter) {
2105
2238
  });
2106
2239
  } catch (error) {
2107
2240
  logger.error("Validation failed", { error, requestId });
2241
+ throwIfClientError(error);
2108
2242
  throw error;
2109
2243
  }
2110
2244
  }
@@ -3499,6 +3633,19 @@ var init_error_handler = __esm({
3499
3633
  errorHandler = (err, c) => {
3500
3634
  const requestId = c.get("requestId") || "unknown";
3501
3635
  if (err instanceof HTTPException6) {
3636
+ const cause = err.cause;
3637
+ if (cause?.code === "QUALITY_GATE_FAILED") {
3638
+ return c.json(
3639
+ {
3640
+ success: false,
3641
+ error: err.message,
3642
+ code: cause.code,
3643
+ quality: cause.analysis,
3644
+ requestId
3645
+ },
3646
+ 400
3647
+ );
3648
+ }
3502
3649
  return c.json(
3503
3650
  {
3504
3651
  success: false,
@@ -4182,7 +4329,7 @@ function createDevCommand(adapter) {
4182
4329
  }
4183
4330
 
4184
4331
  // src/cli.ts
4185
- var PACKAGE_VERSION = true ? "1.2.0" : "dev-mode";
4332
+ var PACKAGE_VERSION = true ? "1.5.0" : "dev-mode";
4186
4333
  var program = new Command2();
4187
4334
  program.name("jto").description("JSON to Office CLI - Generate .docx and .pptx from JSON").version(PACKAGE_VERSION);
4188
4335
  registerCoreCommands(program, {