@reportforge/playwright-pdf 0.2.2 → 0.3.1

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/index.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
2
2
 
3
- type TemplateId = 'minimal' | 'detailed' | 'executive';
3
+ declare const TEMPLATE_IDS: readonly ["minimal", "detailed", "executive"];
4
+ type TemplateId = (typeof TEMPLATE_IDS)[number];
4
5
  /**
5
6
  * License plan. Subscription is the only product — every active license is the
6
7
  * same plan. The "no license" case is represented by the absence of a
@@ -219,9 +220,11 @@ interface ReporterOptions {
219
220
  */
220
221
  interface LicenseInfo {
221
222
  plan: LicensePlan;
222
- source: 'jwt-online' | 'jwt-cache';
223
+ source: 'jwt-online' | 'jwt-cache' | 'demo';
223
224
  expiry: Date;
224
225
  key: string;
226
+ /** Raw Ed25519-signed JWT from the server. Absent only in demo mode. */
227
+ jwt?: string;
225
228
  machinesUsed?: number;
226
229
  machineLimit?: number;
227
230
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
2
2
 
3
- type TemplateId = 'minimal' | 'detailed' | 'executive';
3
+ declare const TEMPLATE_IDS: readonly ["minimal", "detailed", "executive"];
4
+ type TemplateId = (typeof TEMPLATE_IDS)[number];
4
5
  /**
5
6
  * License plan. Subscription is the only product — every active license is the
6
7
  * same plan. The "no license" case is represented by the absence of a
@@ -219,9 +220,11 @@ interface ReporterOptions {
219
220
  */
220
221
  interface LicenseInfo {
221
222
  plan: LicensePlan;
222
- source: 'jwt-online' | 'jwt-cache';
223
+ source: 'jwt-online' | 'jwt-cache' | 'demo';
223
224
  expiry: Date;
224
225
  key: string;
226
+ /** Raw Ed25519-signed JWT from the server. Absent only in demo mode. */
227
+ jwt?: string;
225
228
  machinesUsed?: number;
226
229
  machineLimit?: number;
227
230
  }
package/dist/index.js CHANGED
@@ -10740,6 +10740,7 @@ var LicenseClient = class {
10740
10740
  plan: "subscription",
10741
10741
  source: "jwt-online",
10742
10742
  key: normalizedKey,
10743
+ jwt: result.jwt,
10743
10744
  expiry: new Date(payload.exp * 1e3),
10744
10745
  machinesUsed: result.machinesUsed,
10745
10746
  machineLimit: result.machineLimit
@@ -10822,6 +10823,7 @@ function toLicenseInfo(payload, cached, normalizedKey, source) {
10822
10823
  plan: "subscription",
10823
10824
  source,
10824
10825
  key: normalizedKey,
10826
+ jwt: cached.jwt,
10825
10827
  expiry: new Date(payload.exp * 1e3),
10826
10828
  // Surface the snapshot from the last activate/refresh. Undefined for
10827
10829
  // caches written by older reporter versions before these fields were
@@ -10839,6 +10841,32 @@ init_cjs_shims();
10839
10841
 
10840
10842
  // src/collector/SuiteWalker.ts
10841
10843
  init_cjs_shims();
10844
+
10845
+ // src/collector/stats-utils.ts
10846
+ init_cjs_shims();
10847
+ function statsFromTests(tests) {
10848
+ return {
10849
+ total: tests.length,
10850
+ passed: tests.filter((t) => t.status === "passed").length,
10851
+ failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
10852
+ skipped: tests.filter((t) => t.status === "skipped").length,
10853
+ flaky: tests.filter((t) => t.status === "flaky").length
10854
+ };
10855
+ }
10856
+ function aggregateStats(parts) {
10857
+ return parts.reduce(
10858
+ (acc, s) => ({
10859
+ total: acc.total + s.total,
10860
+ passed: acc.passed + s.passed,
10861
+ failed: acc.failed + s.failed,
10862
+ skipped: acc.skipped + s.skipped,
10863
+ flaky: acc.flaky + s.flaky
10864
+ }),
10865
+ { total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
10866
+ );
10867
+ }
10868
+
10869
+ // src/collector/SuiteWalker.ts
10842
10870
  var SuiteWalker = class {
10843
10871
  build(rootSuite, resultMap) {
10844
10872
  return rootSuite.suites.map(
@@ -10849,15 +10877,15 @@ var SuiteWalker = class {
10849
10877
  const suites = projectSuite.suites.map(
10850
10878
  (s) => this.buildSuite(s, resultMap)
10851
10879
  );
10852
- const stats = this.aggregateStats(suites.map((s) => s.stats));
10880
+ const stats = aggregateStats(suites.map((s) => s.stats));
10853
10881
  return { name: projectSuite.title || "default", suites, stats };
10854
10882
  }
10855
10883
  buildSuite(suite, resultMap) {
10856
10884
  const childSuites = suite.suites.map((s) => this.buildSuite(s, resultMap));
10857
10885
  const tests = suite.tests.map((t) => this.buildTest(t, resultMap));
10858
- const stats = this.aggregateStats([
10886
+ const stats = aggregateStats([
10859
10887
  ...childSuites.map((s) => s.stats),
10860
- this.statsFromTests(tests)
10888
+ statsFromTests(tests)
10861
10889
  ]);
10862
10890
  return {
10863
10891
  title: suite.title,
@@ -10894,27 +10922,6 @@ var SuiteWalker = class {
10894
10922
  if (actual === "failed" && expected === "failed") return "passed";
10895
10923
  return "failed";
10896
10924
  }
10897
- statsFromTests(tests) {
10898
- return {
10899
- total: tests.length,
10900
- passed: tests.filter((t) => t.status === "passed").length,
10901
- failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
10902
- skipped: tests.filter((t) => t.status === "skipped").length,
10903
- flaky: tests.filter((t) => t.status === "flaky").length
10904
- };
10905
- }
10906
- aggregateStats(parts) {
10907
- return parts.reduce(
10908
- (acc, s) => ({
10909
- total: acc.total + s.total,
10910
- passed: acc.passed + s.passed,
10911
- failed: acc.failed + s.failed,
10912
- skipped: acc.skipped + s.skipped,
10913
- flaky: acc.flaky + s.flaky
10914
- }),
10915
- { total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
10916
- );
10917
- }
10918
10925
  };
10919
10926
 
10920
10927
  // src/utils/env.ts
@@ -11250,7 +11257,7 @@ var ShardMerger = class {
11250
11257
  }
11251
11258
  return Array.from(projectMap.entries()).map(([name, projectSuites]) => {
11252
11259
  const suites = this.mergeFileSuites(projectSuites);
11253
- const stats = this.aggregateStats(suites.map((s) => s.stats));
11260
+ const stats = aggregateStats(suites.map((s) => s.stats));
11254
11261
  return { name, suites, stats };
11255
11262
  });
11256
11263
  }
@@ -11271,9 +11278,9 @@ var ShardMerger = class {
11271
11278
  const suitePath = [ref.title ?? ""];
11272
11279
  const tests = allSpecs.map((spec) => this.specToTestNode(spec, projectName, suitePath));
11273
11280
  const childSuiteNodes = this.buildDescribeSuites(allChildSuites, projectName, suitePath);
11274
- const stats = this.aggregateStats([
11281
+ const stats = aggregateStats([
11275
11282
  ...childSuiteNodes.map((s) => s.stats),
11276
- this.statsFromTests(tests)
11283
+ statsFromTests(tests)
11277
11284
  ]);
11278
11285
  return {
11279
11286
  title: ref.title ?? ref.file ?? "unknown",
@@ -11290,9 +11297,9 @@ var ShardMerger = class {
11290
11297
  const suitePath = [...parentPath, suite.title];
11291
11298
  const tests = suite.specs.map((spec) => this.specToTestNode(spec, projectName, suitePath));
11292
11299
  const childSuites = this.buildDescribeSuites(suite.suites ?? [], projectName, suitePath);
11293
- const stats = this.aggregateStats([
11300
+ const stats = aggregateStats([
11294
11301
  ...childSuites.map((s) => s.stats),
11295
- this.statsFromTests(tests)
11302
+ statsFromTests(tests)
11296
11303
  ]);
11297
11304
  return {
11298
11305
  title: suite.title,
@@ -11447,27 +11454,6 @@ var ShardMerger = class {
11447
11454
  suiteResults
11448
11455
  };
11449
11456
  }
11450
- statsFromTests(tests) {
11451
- return {
11452
- total: tests.length,
11453
- passed: tests.filter((t) => t.status === "passed").length,
11454
- failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
11455
- skipped: tests.filter((t) => t.status === "skipped").length,
11456
- flaky: tests.filter((t) => t.status === "flaky").length
11457
- };
11458
- }
11459
- aggregateStats(parts) {
11460
- return parts.reduce(
11461
- (acc, s) => ({
11462
- total: acc.total + s.total,
11463
- passed: acc.passed + s.passed,
11464
- failed: acc.failed + s.failed,
11465
- skipped: acc.skipped + s.skipped,
11466
- flaky: acc.flaky + s.flaky
11467
- }),
11468
- { total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
11469
- );
11470
- }
11471
11457
  };
11472
11458
 
11473
11459
  // src/pdf/PdfGenerator.ts
@@ -12137,10 +12123,31 @@ var PdfGenerator = class {
12137
12123
  *
12138
12124
  * @param data - Assembled `ReportData` (projects, stats, failures, charts).
12139
12125
  * @param options - Validated reporter options.
12140
- * @param _licenseInfo - Kept for API symmetry; unused in this method.
12126
+ * @param licenseInfo - Verified license. JWT must be present and valid for
12127
+ * non-demo sources; `source: 'demo'` is the only allowed JWT-free path.
12141
12128
  * @returns Absolute paths of written PDF files.
12142
12129
  */
12143
- async generateAll(data, options, _licenseInfo) {
12130
+ async generateAll(data, options, licenseInfo) {
12131
+ if (false) {
12132
+ if (licenseInfo.expiry <= /* @__PURE__ */ new Date()) {
12133
+ throw new Error("[reportforge] Demo license expired \u2014 PDF generation skipped.");
12134
+ }
12135
+ } else {
12136
+ if (!licenseInfo.jwt) {
12137
+ throw new Error("[reportforge] License JWT missing \u2014 PDF generation skipped.");
12138
+ }
12139
+ if (!LICENSE_PUBLIC_KEY_PEM) {
12140
+ throw new Error("[reportforge] LICENSE_PUBLIC_KEY_PEM missing \u2014 cannot verify license JWT.");
12141
+ }
12142
+ try {
12143
+ const payload = new JwtVerifier(LICENSE_PUBLIC_KEY_PEM).verify(licenseInfo.jwt);
12144
+ if (payload.exp * 1e3 <= Date.now()) {
12145
+ throw new Error("JWT expired");
12146
+ }
12147
+ } catch (err) {
12148
+ throw new Error(`[reportforge] License JWT verification failed: ${err.message}`);
12149
+ }
12150
+ }
12144
12151
  const sources = this.resolveTemplateSources(options);
12145
12152
  const isMulti = sources.length > 1;
12146
12153
  for (const source of sources) {
@@ -12670,7 +12677,7 @@ var PdfReporter = class {
12670
12677
  this.dataCollector = new DataCollector();
12671
12678
  this.pdfGenerator = new PdfGenerator();
12672
12679
  this.options = parseOptions(rawOptions);
12673
- const version = true ? "0.2.2" : "0.x";
12680
+ const version = true ? "0.3.1" : "0.x";
12674
12681
  logger.info(`@reportforge/playwright-pdf v${version} initialised`);
12675
12682
  this.licenseClient = new LicenseClient({
12676
12683
  licenseKey: this.options.licenseKey,
package/dist/index.mjs CHANGED
@@ -10741,6 +10741,7 @@ var LicenseClient = class {
10741
10741
  plan: "subscription",
10742
10742
  source: "jwt-online",
10743
10743
  key: normalizedKey,
10744
+ jwt: result.jwt,
10744
10745
  expiry: new Date(payload.exp * 1e3),
10745
10746
  machinesUsed: result.machinesUsed,
10746
10747
  machineLimit: result.machineLimit
@@ -10823,6 +10824,7 @@ function toLicenseInfo(payload, cached, normalizedKey, source) {
10823
10824
  plan: "subscription",
10824
10825
  source,
10825
10826
  key: normalizedKey,
10827
+ jwt: cached.jwt,
10826
10828
  expiry: new Date(payload.exp * 1e3),
10827
10829
  // Surface the snapshot from the last activate/refresh. Undefined for
10828
10830
  // caches written by older reporter versions before these fields were
@@ -10840,6 +10842,32 @@ init_esm_shims();
10840
10842
 
10841
10843
  // src/collector/SuiteWalker.ts
10842
10844
  init_esm_shims();
10845
+
10846
+ // src/collector/stats-utils.ts
10847
+ init_esm_shims();
10848
+ function statsFromTests(tests) {
10849
+ return {
10850
+ total: tests.length,
10851
+ passed: tests.filter((t) => t.status === "passed").length,
10852
+ failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
10853
+ skipped: tests.filter((t) => t.status === "skipped").length,
10854
+ flaky: tests.filter((t) => t.status === "flaky").length
10855
+ };
10856
+ }
10857
+ function aggregateStats(parts) {
10858
+ return parts.reduce(
10859
+ (acc, s) => ({
10860
+ total: acc.total + s.total,
10861
+ passed: acc.passed + s.passed,
10862
+ failed: acc.failed + s.failed,
10863
+ skipped: acc.skipped + s.skipped,
10864
+ flaky: acc.flaky + s.flaky
10865
+ }),
10866
+ { total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
10867
+ );
10868
+ }
10869
+
10870
+ // src/collector/SuiteWalker.ts
10843
10871
  var SuiteWalker = class {
10844
10872
  build(rootSuite, resultMap) {
10845
10873
  return rootSuite.suites.map(
@@ -10850,15 +10878,15 @@ var SuiteWalker = class {
10850
10878
  const suites = projectSuite.suites.map(
10851
10879
  (s) => this.buildSuite(s, resultMap)
10852
10880
  );
10853
- const stats = this.aggregateStats(suites.map((s) => s.stats));
10881
+ const stats = aggregateStats(suites.map((s) => s.stats));
10854
10882
  return { name: projectSuite.title || "default", suites, stats };
10855
10883
  }
10856
10884
  buildSuite(suite, resultMap) {
10857
10885
  const childSuites = suite.suites.map((s) => this.buildSuite(s, resultMap));
10858
10886
  const tests = suite.tests.map((t) => this.buildTest(t, resultMap));
10859
- const stats = this.aggregateStats([
10887
+ const stats = aggregateStats([
10860
10888
  ...childSuites.map((s) => s.stats),
10861
- this.statsFromTests(tests)
10889
+ statsFromTests(tests)
10862
10890
  ]);
10863
10891
  return {
10864
10892
  title: suite.title,
@@ -10895,27 +10923,6 @@ var SuiteWalker = class {
10895
10923
  if (actual === "failed" && expected === "failed") return "passed";
10896
10924
  return "failed";
10897
10925
  }
10898
- statsFromTests(tests) {
10899
- return {
10900
- total: tests.length,
10901
- passed: tests.filter((t) => t.status === "passed").length,
10902
- failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
10903
- skipped: tests.filter((t) => t.status === "skipped").length,
10904
- flaky: tests.filter((t) => t.status === "flaky").length
10905
- };
10906
- }
10907
- aggregateStats(parts) {
10908
- return parts.reduce(
10909
- (acc, s) => ({
10910
- total: acc.total + s.total,
10911
- passed: acc.passed + s.passed,
10912
- failed: acc.failed + s.failed,
10913
- skipped: acc.skipped + s.skipped,
10914
- flaky: acc.flaky + s.flaky
10915
- }),
10916
- { total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
10917
- );
10918
- }
10919
10926
  };
10920
10927
 
10921
10928
  // src/utils/env.ts
@@ -11251,7 +11258,7 @@ var ShardMerger = class {
11251
11258
  }
11252
11259
  return Array.from(projectMap.entries()).map(([name, projectSuites]) => {
11253
11260
  const suites = this.mergeFileSuites(projectSuites);
11254
- const stats = this.aggregateStats(suites.map((s) => s.stats));
11261
+ const stats = aggregateStats(suites.map((s) => s.stats));
11255
11262
  return { name, suites, stats };
11256
11263
  });
11257
11264
  }
@@ -11272,9 +11279,9 @@ var ShardMerger = class {
11272
11279
  const suitePath = [ref.title ?? ""];
11273
11280
  const tests = allSpecs.map((spec) => this.specToTestNode(spec, projectName, suitePath));
11274
11281
  const childSuiteNodes = this.buildDescribeSuites(allChildSuites, projectName, suitePath);
11275
- const stats = this.aggregateStats([
11282
+ const stats = aggregateStats([
11276
11283
  ...childSuiteNodes.map((s) => s.stats),
11277
- this.statsFromTests(tests)
11284
+ statsFromTests(tests)
11278
11285
  ]);
11279
11286
  return {
11280
11287
  title: ref.title ?? ref.file ?? "unknown",
@@ -11291,9 +11298,9 @@ var ShardMerger = class {
11291
11298
  const suitePath = [...parentPath, suite.title];
11292
11299
  const tests = suite.specs.map((spec) => this.specToTestNode(spec, projectName, suitePath));
11293
11300
  const childSuites = this.buildDescribeSuites(suite.suites ?? [], projectName, suitePath);
11294
- const stats = this.aggregateStats([
11301
+ const stats = aggregateStats([
11295
11302
  ...childSuites.map((s) => s.stats),
11296
- this.statsFromTests(tests)
11303
+ statsFromTests(tests)
11297
11304
  ]);
11298
11305
  return {
11299
11306
  title: suite.title,
@@ -11448,27 +11455,6 @@ var ShardMerger = class {
11448
11455
  suiteResults
11449
11456
  };
11450
11457
  }
11451
- statsFromTests(tests) {
11452
- return {
11453
- total: tests.length,
11454
- passed: tests.filter((t) => t.status === "passed").length,
11455
- failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
11456
- skipped: tests.filter((t) => t.status === "skipped").length,
11457
- flaky: tests.filter((t) => t.status === "flaky").length
11458
- };
11459
- }
11460
- aggregateStats(parts) {
11461
- return parts.reduce(
11462
- (acc, s) => ({
11463
- total: acc.total + s.total,
11464
- passed: acc.passed + s.passed,
11465
- failed: acc.failed + s.failed,
11466
- skipped: acc.skipped + s.skipped,
11467
- flaky: acc.flaky + s.flaky
11468
- }),
11469
- { total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
11470
- );
11471
- }
11472
11458
  };
11473
11459
 
11474
11460
  // src/pdf/PdfGenerator.ts
@@ -12138,10 +12124,31 @@ var PdfGenerator = class {
12138
12124
  *
12139
12125
  * @param data - Assembled `ReportData` (projects, stats, failures, charts).
12140
12126
  * @param options - Validated reporter options.
12141
- * @param _licenseInfo - Kept for API symmetry; unused in this method.
12127
+ * @param licenseInfo - Verified license. JWT must be present and valid for
12128
+ * non-demo sources; `source: 'demo'` is the only allowed JWT-free path.
12142
12129
  * @returns Absolute paths of written PDF files.
12143
12130
  */
12144
- async generateAll(data, options, _licenseInfo) {
12131
+ async generateAll(data, options, licenseInfo) {
12132
+ if (false) {
12133
+ if (licenseInfo.expiry <= /* @__PURE__ */ new Date()) {
12134
+ throw new Error("[reportforge] Demo license expired \u2014 PDF generation skipped.");
12135
+ }
12136
+ } else {
12137
+ if (!licenseInfo.jwt) {
12138
+ throw new Error("[reportforge] License JWT missing \u2014 PDF generation skipped.");
12139
+ }
12140
+ if (!LICENSE_PUBLIC_KEY_PEM) {
12141
+ throw new Error("[reportforge] LICENSE_PUBLIC_KEY_PEM missing \u2014 cannot verify license JWT.");
12142
+ }
12143
+ try {
12144
+ const payload = new JwtVerifier(LICENSE_PUBLIC_KEY_PEM).verify(licenseInfo.jwt);
12145
+ if (payload.exp * 1e3 <= Date.now()) {
12146
+ throw new Error("JWT expired");
12147
+ }
12148
+ } catch (err) {
12149
+ throw new Error(`[reportforge] License JWT verification failed: ${err.message}`);
12150
+ }
12151
+ }
12145
12152
  const sources = this.resolveTemplateSources(options);
12146
12153
  const isMulti = sources.length > 1;
12147
12154
  for (const source of sources) {
@@ -12671,7 +12678,7 @@ var PdfReporter = class {
12671
12678
  this.dataCollector = new DataCollector();
12672
12679
  this.pdfGenerator = new PdfGenerator();
12673
12680
  this.options = parseOptions(rawOptions);
12674
- const version = true ? "0.2.2" : "0.x";
12681
+ const version = true ? "0.3.1" : "0.x";
12675
12682
  logger.info(`@reportforge/playwright-pdf v${version} initialised`);
12676
12683
  this.licenseClient = new LicenseClient({
12677
12684
  licenseKey: this.options.licenseKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reportforge/playwright-pdf",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Enterprise-ready PDF reports for Playwright Test — minimal, detailed, and executive templates with CI/CD integrations",
5
5
  "license": "MIT",
6
6
  "author": "ReportForge",
@@ -33,6 +33,9 @@
33
33
  "require": "./dist/index.js"
34
34
  }
35
35
  },
36
+ "bin": {
37
+ "reportforge-demo": "./dist/demo/cli.js"
38
+ },
36
39
  "publishConfig": {
37
40
  "access": "public"
38
41
  },