react-native-bundle-discovery-ui 2.7.0 → 2.9.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 (38) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +12 -227
  3. package/assets/global.css +769 -53
  4. package/dist/bin.js +61 -16
  5. package/dist/client/issues.js +5 -0
  6. package/dist/client/pages/_common.js +258 -86
  7. package/dist/client/pages/_module.js +347 -0
  8. package/dist/client/pages/default.js +145 -62
  9. package/dist/client/pages/module.js +2 -309
  10. package/dist/client/pages/moduleDiff.js +76 -0
  11. package/dist/client/pages/package.js +172 -154
  12. package/dist/client/prepare.js +15 -0
  13. package/dist/client/queryHelpers.js +445 -81
  14. package/dist/client/views/{_theme.js → _colorScheme.js} +2 -9
  15. package/dist/client/views/_html.js +52 -0
  16. package/dist/client/views/codeDiff.js +66 -0
  17. package/dist/client/views/compare.js +237 -0
  18. package/dist/client/views/importGraph.js +255 -0
  19. package/dist/client/views/insights.js +80 -0
  20. package/dist/client/views/moduleOverview.js +134 -0
  21. package/dist/client/views/packageOverview.js +114 -0
  22. package/dist/client/views/persistedFilterInput.js +27 -0
  23. package/dist/client/views/reportBar.js +89 -0
  24. package/dist/client/views/treemap.js +598 -0
  25. package/dist/discoveryrc.js +26 -7
  26. package/dist/rsdoctor.js +5 -101
  27. package/dist/src-Bp8pPQrQ.js +1545 -0
  28. package/package.json +5 -3
  29. package/assets/highcharts.css +0 -1
  30. package/dist/client/_virtual/_rolldown/runtime.js +0 -37
  31. package/dist/client/views/foamtree.js +0 -103
  32. package/dist/client/views/highcharts.js +0 -33
  33. package/dist/src-BSLX-He2.js +0 -36
  34. package/vendors/foamtree.js +0 -9008
  35. package/vendors/high-contrast-dark.js +0 -1
  36. package/vendors/highcharts-exporting.js +0 -13
  37. package/vendors/highcharts-networkgraph.js +0 -1
  38. package/vendors/highcharts.js +0 -10
package/dist/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.js");
3
- const require_src = require("./src-BSLX-He2.js");
3
+ const require_src = require("./src-Bp8pPQrQ.js");
4
4
  const require_discoveryrc = require("./discoveryrc.js");
5
5
  let minimist = require("minimist");
6
6
  minimist = require_rolldown_runtime.__toESM(minimist);
@@ -12,8 +12,23 @@ let _discoveryjs_cli = require("@discoveryjs/cli");
12
12
  let chalk = require("chalk");
13
13
  chalk = require_rolldown_runtime.__toESM(chalk);
14
14
  let _discoveryjs_cli_lib_shared_utils_js = require("@discoveryjs/cli/lib/shared/utils.js");
15
+ //#region src/withCompare.ts
16
+ /** Wraps a report expression of the generated discovery.js config with a "before" report diff */
17
+ function withCompare(reportExpression, beforeJsonPath) {
18
+ return beforeJsonPath
19
+ ? `withComparison(${reportExpression}, require("${beforeJsonPath}"), "${beforeJsonPath}")`
20
+ : reportExpression;
21
+ }
22
+ //#endregion
15
23
  //#region src/build.ts
16
- function buildHtmlPage(filePath, output, clean, singleFile, verbose) {
24
+ function buildHtmlPage(
25
+ filePath,
26
+ output,
27
+ clean,
28
+ singleFile,
29
+ verbose,
30
+ compareFilePath,
31
+ ) {
17
32
  if (verbose)
18
33
  console.info(
19
34
  `Building HTML page from file: ${chalk.default.green(filePath)} options: ${JSON.stringify(
@@ -47,6 +62,22 @@ function buildHtmlPage(filePath, output, clean, singleFile, verbose) {
47
62
  console.error(err.message);
48
63
  process.exit(1);
49
64
  }
65
+ let fullCompareJsonPath = null;
66
+ if (compareFilePath) {
67
+ const compareJsonPath = node_path.default.resolve(
68
+ process.cwd(),
69
+ compareFilePath,
70
+ );
71
+ try {
72
+ fullCompareJsonPath = require.resolve(compareJsonPath);
73
+ } catch (err) {
74
+ console.error(
75
+ `❌Error loading file: ${chalk.default.red(compareJsonPath)}\n\n`,
76
+ );
77
+ console.error(err.message);
78
+ process.exit(1);
79
+ }
80
+ }
50
81
  const configFile = node_path.default.resolve(__dirname, "./.tmp.js");
51
82
  if (verbose)
52
83
  console.info(
@@ -55,7 +86,7 @@ function buildHtmlPage(filePath, output, clean, singleFile, verbose) {
55
86
  const transformRcdoctor = node_path.default.join(__dirname, "rsdoctor.js");
56
87
  node_fs.default.writeFileSync(
57
88
  configFile,
58
- `const {transformRSDoctorData} = require("${transformRcdoctor}");
89
+ `const {transformRSDoctorData, transformEsbuildMetafile, withComparison, withRecommendations} = require("${transformRcdoctor}");
59
90
  module.exports = ${JSON.stringify(
60
91
  {
61
92
  ...require_discoveryrc,
@@ -65,9 +96,7 @@ module.exports = ${JSON.stringify(
65
96
  1,
66
97
  ).replace(
67
98
  `"<tmp>"`,
68
- require_src.isRsdoctorReportPath(fullJsonPath)
69
- ? `() => transformRSDoctorData(require("${fullJsonPath}"))`
70
- : `() => require("${fullJsonPath}")`,
99
+ `() => ${withCompare(require_src.isRsdoctorReportPath(fullJsonPath) ? `withRecommendations(transformRSDoctorData(require("${fullJsonPath}")))` : require_src.isEsbuildMetafilePath(fullJsonPath) ? `withRecommendations(transformEsbuildMetafile(require("${fullJsonPath}"), "${fullJsonPath}"))` : `withRecommendations(require("${fullJsonPath}"))`, fullCompareJsonPath)}`,
71
100
  )};`,
72
101
  );
73
102
  if (verbose)
@@ -120,7 +149,7 @@ module.exports = ${JSON.stringify(
120
149
  }
121
150
  //#endregion
122
151
  //#region src/server.ts
123
- function serve(filePath, port, verbose) {
152
+ function serve(filePath, port, verbose, compareFilePath) {
124
153
  const PORT = process.env.PORT || port;
125
154
  if (verbose)
126
155
  console.info(
@@ -147,6 +176,22 @@ function serve(filePath, port, verbose) {
147
176
  console.error(err.message);
148
177
  process.exit(1);
149
178
  }
179
+ let fullCompareJsonPath = null;
180
+ if (compareFilePath) {
181
+ const compareJsonPath = node_path.default.resolve(
182
+ process.cwd(),
183
+ compareFilePath,
184
+ );
185
+ try {
186
+ fullCompareJsonPath = require.resolve(compareJsonPath);
187
+ } catch (err) {
188
+ console.error(
189
+ `❌Error loading file: ${chalk.default.red(compareJsonPath)}\n\n`,
190
+ );
191
+ console.error(err.message);
192
+ process.exit(1);
193
+ }
194
+ }
150
195
  const configFile = node_path.default.resolve(__dirname, "./.tmp.js");
151
196
  if (verbose)
152
197
  console.info(
@@ -155,7 +200,7 @@ function serve(filePath, port, verbose) {
155
200
  const transformRcdoctor = node_path.default.join(__dirname, "rsdoctor.js");
156
201
  node_fs.default.writeFileSync(
157
202
  configFile,
158
- `const {transformRSDoctorData} = require("${transformRcdoctor}");
203
+ `const {transformRSDoctorData, transformEsbuildMetafile, withComparison, withRecommendations} = require("${transformRcdoctor}");
159
204
  module.exports = ${JSON.stringify(
160
205
  {
161
206
  ...require_discoveryrc,
@@ -165,9 +210,7 @@ module.exports = ${JSON.stringify(
165
210
  1,
166
211
  ).replace(
167
212
  `"<tmp>"`,
168
- require_src.isRsdoctorReportPath(fullJsonPath)
169
- ? `() => transformRSDoctorData(require("${fullJsonPath}"))`
170
- : `() => require("${fullJsonPath}")`,
213
+ `() => ${withCompare(require_src.isRsdoctorReportPath(fullJsonPath) ? `withRecommendations(transformRSDoctorData(require("${fullJsonPath}")))` : require_src.isEsbuildMetafilePath(fullJsonPath) ? `withRecommendations(transformEsbuildMetafile(require("${fullJsonPath}"), "${fullJsonPath}"))` : `withRecommendations(require("${fullJsonPath}"))`, fullCompareJsonPath)}`,
171
214
  )};`,
172
215
  );
173
216
  if (verbose)
@@ -196,10 +239,10 @@ function printHelp() {
196
239
  console.log(`react-native-bundle-discovery-ui
197
240
 
198
241
  Usage:
199
- react-native-bundle-discovery-ui <file> [port] [--verbose] # <- server can be omitted
200
- react-native-bundle-discovery-ui server <file> [port] [--verbose]
242
+ react-native-bundle-discovery-ui <file> [port] [--compare <file>] [--verbose] # <- server can be omitted
243
+ react-native-bundle-discovery-ui server <file> [port] [--compare <file>] [--verbose]
201
244
 
202
- react-native-bundle-discovery-ui build <file> [--output <path>] [--clean] [--single-file] [--verbose]
245
+ react-native-bundle-discovery-ui build <file> [--compare <file>] [--output <path>] [--clean] [--single-file] [--verbose]
203
246
 
204
247
  Commands:
205
248
  server <file> [port] Run a web server to show a Metro bundler stat report
@@ -211,6 +254,7 @@ Options:
211
254
  -c, --clean Clean output directory before writing build files (default: true)
212
255
  -s, --single-file Output report build as a single HTML file (default: true)
213
256
  -p, --port <port> Port for server command (same as [port], default: 8079)
257
+ --compare <file> A "before" report to compare <file> with (adds the Compare tab)
214
258
  -h, --help Show help
215
259
  `);
216
260
  }
@@ -230,7 +274,7 @@ function main() {
230
274
  p: "port",
231
275
  },
232
276
  boolean: ["verbose", "help", "clean", "single-file"],
233
- string: ["output"],
277
+ string: ["output", "compare"],
234
278
  default: {
235
279
  clean: true,
236
280
  "single-file": true,
@@ -252,7 +296,7 @@ function main() {
252
296
  argv.port ?? (isImplicitServerCommand ? argv._[1] : argv._[2]) ?? 8079;
253
297
  const port = Number(rawPort);
254
298
  if (!Number.isFinite(port)) fail(`Invalid port: ${rawPort}`);
255
- return serve(file, port, Boolean(argv.verbose));
299
+ return serve(file, port, Boolean(argv.verbose), argv.compare);
256
300
  }
257
301
  if (isBuildCommand) {
258
302
  const file = argv._[1];
@@ -263,6 +307,7 @@ function main() {
263
307
  argv.clean,
264
308
  argv["single-file"],
265
309
  Boolean(argv.verbose),
310
+ argv.compare,
266
311
  );
267
312
  }
268
313
  fail(`Unknown command: ${command}`);
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/client/issues.ts
3
+ const DUPLICATE_ISSUE = "Duplicate module";
4
+ //#endregion
5
+ exports.DUPLICATE_ISSUE = DUPLICATE_ISSUE;
@@ -1,6 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region src/client/pages/_common.ts
3
- const platformColor = `transformOptions.platform = 'android' ? 'rgba(194, 239, 116, .4)' : 'rgba(119, 31, 218, .4)'`;
4
3
  function getPackage(entry) {
5
4
  return `
6
5
  $packages: $.packages;
@@ -30,6 +29,86 @@ function getPackage(entry) {
30
29
  }),
31
30
  }))`;
32
31
  }
32
+ function renderSortToggleGroup(el, config, options) {
33
+ let current = null;
34
+ const label = document.createElement("div");
35
+ label.className = "view-toggle-group-before";
36
+ label.textContent = "Sort by:";
37
+ el.classList.add("view-toggle-group", "sort-toggle-group");
38
+ el.append(label);
39
+ const toggles = options.map(({ value, text }) => {
40
+ const toggle = document.createElement("div");
41
+ toggle.className = "view-toggle onclick";
42
+ toggle.textContent = text;
43
+ toggle.addEventListener("click", () => {
44
+ current = current === value ? null : value;
45
+ toggles.forEach((t, i) => {
46
+ t.classList.toggle("checked", options[i].value === current);
47
+ });
48
+ config.onChange?.(current, config.name);
49
+ });
50
+ return el.appendChild(toggle);
51
+ });
52
+ config.onInit?.(current, config.name);
53
+ }
54
+ function getSortableContentFilter({
55
+ data,
56
+ className,
57
+ nameField,
58
+ sizeField,
59
+ duplicatesCount,
60
+ defaultSort,
61
+ content,
62
+ }) {
63
+ const filtered = `.[${nameField} ~= #.filterByPathStr]`;
64
+ return {
65
+ view: "block",
66
+ data,
67
+ className: `view-content-filter sortable-content-filter ${className ?? ""}`,
68
+ content: {
69
+ view: "context",
70
+ modifiers: [
71
+ {
72
+ view: renderSortToggleGroup,
73
+ name: "sortBy",
74
+ data: `
75
+ $hasDuplicates: .[${duplicatesCount} > 0];
76
+ [
77
+ { value: 'size', text: 'Size' },
78
+ { value: 'name', text: 'Name' },
79
+ { value: 'duplicates', text: 'Duplicates' },
80
+ ].[value != 'duplicates' or $hasDuplicates]
81
+ `,
82
+ },
83
+ {
84
+ view: "persisted-filter-input",
85
+ persistKey: `${className}:${nameField}`,
86
+ name: "filterByPathStr",
87
+ placeholder: "Filter",
88
+ },
89
+ ],
90
+ content: [
91
+ {
92
+ view: "badge",
93
+ when: `#.filterByPathStr and ${filtered}`,
94
+ className: "filtered-size",
95
+ data: `{ text: 'Filtered size: ' + ${filtered}.sum(=> ${sizeField}).formatBytes(), color: 'rgba(120, 177, 9, 0.35)' }`,
96
+ },
97
+ {
98
+ view: "block",
99
+ className: "content",
100
+ content: content(`
101
+ $filtered: ${filtered};
102
+ #.sortBy = 'name' ? $filtered.sort(${nameField} asc)
103
+ : #.sortBy = 'duplicates' ? $filtered.sort(${duplicatesCount} desc, ${sizeField} desc)
104
+ : #.sortBy = 'size' ? $filtered.sort(${sizeField} desc)
105
+ : ${defaultSort ? `$filtered.sort(${defaultSort})` : "$filtered"}
106
+ `),
107
+ },
108
+ ],
109
+ },
110
+ };
111
+ }
33
112
  const metadataTooltip = {
34
113
  view: "text",
35
114
  when: "metadata.createdAt",
@@ -170,23 +249,19 @@ function getTreeModule({ hasTextMatch = false, hasPercent = false } = {}) {
170
249
  : null,
171
250
  ].filter(Boolean);
172
251
  }
173
- function getModulesTree({ data, limit }) {
252
+ function getModulesTree({ data, limit, sortable }) {
174
253
  if (!data) throw new Error("[getModulesTree]: data is required");
175
- return {
176
- view: "content-filter",
177
- data,
178
- name: "filterByPathStr",
179
- content: {
180
- view: "list",
181
- limit,
182
- data: ".[name ~= #.filterByPathStr]",
183
- emptyText: "⚠️ No modules found",
184
- item: {
185
- view: "tree",
186
- expanded: false,
187
- itemConfig: {
188
- content: getTreeModule({ hasTextMatch: true }),
189
- children: `
254
+ const getList = (listData) => ({
255
+ view: "list",
256
+ limit,
257
+ data: listData,
258
+ emptyText: "⚠️ No modules found",
259
+ item: {
260
+ view: "tree",
261
+ expanded: false,
262
+ itemConfig: {
263
+ content: getTreeModule({ hasTextMatch: true }),
264
+ children: `
190
265
  [
191
266
  {
192
267
  title:'Imported by modules',
@@ -200,86 +275,183 @@ function getModulesTree({ data, limit }) {
200
275
  }
201
276
  ].filter(=> $.data.size() > 0)
202
277
  `,
203
- itemConfig: {
204
- view: "switch",
205
- content: [
206
- {
207
- when: 'type="reasons"',
208
- content: {
278
+ itemConfig: {
279
+ view: "switch",
280
+ content: [
281
+ {
282
+ when: 'type="reasons"',
283
+ content: {
284
+ view: "tree-leaf",
285
+ content: [
286
+ "text:title",
287
+ "text:' '",
288
+ "badge:{ text: $.data.size() }",
289
+ ],
290
+ children: `$.data`,
291
+ itemConfig: {
209
292
  view: "tree-leaf",
210
- content: [
211
- "text:title",
212
- "text:' '",
213
- "badge:{ text: $.data.size() }",
214
- ],
215
- children: `$.data`,
216
- itemConfig: {
217
- view: "tree-leaf",
218
- content: getTreeModule(),
219
- },
293
+ content: getTreeModule(),
220
294
  },
221
295
  },
222
- {
223
- when: 'type="duplicates"',
224
- content: {
296
+ },
297
+ {
298
+ when: 'type="duplicates"',
299
+ content: {
300
+ view: "tree-leaf",
301
+ content: [
302
+ "text:title",
303
+ "text:' '",
304
+ "badge:{ text: $.data.size() }",
305
+ ],
306
+ children: `$.data`,
307
+ itemConfig: {
225
308
  view: "tree-leaf",
226
- content: [
227
- "text:title",
228
- "text:' '",
229
- "badge:{ text: $.data.size() }",
230
- ],
231
- children: `$.data`,
232
- itemConfig: {
233
- view: "tree-leaf",
234
- content: getTreeModule(),
235
- },
309
+ content: getTreeModule(),
236
310
  },
237
311
  },
238
- ],
239
- },
312
+ },
313
+ ],
240
314
  },
241
315
  },
242
316
  },
317
+ });
318
+ if (sortable)
319
+ return getSortableContentFilter({
320
+ data,
321
+ nameField: "name",
322
+ sizeField: "sizeInBytes",
323
+ duplicatesCount: "duplicates.size()",
324
+ content: getList,
325
+ });
326
+ return {
327
+ view: "content-filter",
328
+ data,
329
+ name: "filterByPathStr",
330
+ content: getList(".[name ~= #.filterByPathStr]"),
243
331
  };
244
332
  }
245
- const metadata = {
246
- platform: {
247
- when: "transformOptions.platform",
248
- view: "badge",
249
- data: `{ prefix: 'Platform: ', text: transformOptions.platform, color: ${platformColor} }`,
250
- },
251
- size: {
252
- when: "modules.filter(=> $.path has 'node_modules').size()",
253
- view: "badge",
254
- data: `{ prefix: 'Size: ', text: modules.sum(=>output.sizeInBytes).formatBytes(), color: ${platformColor} }`,
255
- },
256
- node_modules_size: {
257
- when: "modules.filter(=> $.path has 'node_modules').size()",
258
- view: "badge",
259
- data: "{ prefix: 'node_modules: ', text: modules.filter(=> $.path has 'node_modules').sum(=>output.sizeInBytes).formatBytes(), color: 'rgba(255, 0, 0, 0.35)' }",
260
- },
261
- source_code_size: {
262
- when: "modules.filter(=> $.path has 'node_modules').size()",
263
- view: "badge",
264
- data: `
265
- // vars
266
- $totalSize: modules.sum(=>output.sizeInBytes);
267
- $thirdPartySize: modules.filter(=> $.path has 'node_modules').sum(=>output.sizeInBytes);
268
- // return data
269
- { prefix: 'Source code: ', text: ($totalSize - $thirdPartySize).formatBytes(), color: 'rgba(148, 111, 234, 0.5)' }`,
270
- },
271
- is_dev: {
272
- when: "transformOptions.dev != null",
273
- view: "badge",
274
- data: "{ prefix: '__DEV__: ', text: transformOptions.dev }",
275
- },
276
- is_minified: {
277
- when: "transformOptions.minify != null",
278
- view: "badge",
279
- data: "{ prefix: 'Minify: ', text: transformOptions.minify }",
333
+ const findingItem = {
334
+ view: "expand",
335
+ className: "='in-finding in-finding-' + severity",
336
+ header: [
337
+ {
338
+ view: "pill-badge",
339
+ className: "in-severity",
340
+ data: `{
341
+ text: severity = 'high' ? 'High impact' : severity = 'medium' ? 'Saves size' : 'Advice',
342
+ color: severity = 'high' ? 'rgba(220, 50, 60, 0.3)' : severity = 'medium' ? 'rgba(255, 165, 0, 0.35)' : 'rgba(0, 170, 255, 0.25)',
343
+ }`,
344
+ },
345
+ "text: title",
346
+ {
347
+ view: "pill-badge",
348
+ when: "savings",
349
+ data: "{ text: '~' + savings.formatBytes(), postfix: savingsShare.percent(2), color: 'rgba(120, 177, 9, 0.35)' }",
350
+ tooltip: "text: 'Estimated savings: size and share of the bundle'",
351
+ },
352
+ {
353
+ view: "inline-list",
354
+ className: "in-packages",
355
+ when: "packageNames",
356
+ data: "packageNames",
357
+ item: {
358
+ view: "link",
359
+ className: "mo-chip mo-chip-pkg",
360
+ data: `{ href: $.pageLink("package", {}), text: $ }`,
361
+ },
362
+ },
363
+ ],
364
+ content: {
365
+ view: "block",
366
+ className: "in-finding-content",
367
+ content: [
368
+ {
369
+ view: "markdown",
370
+ data: "message",
371
+ },
372
+ {
373
+ view: "block",
374
+ when: "packagesText",
375
+ className: "in-pre",
376
+ content: "text: packagesText",
377
+ },
378
+ {
379
+ view: "expand",
380
+ when: "affected",
381
+ className: "in-affected",
382
+ header: [
383
+ "text: 'Affected modules '",
384
+ "pill-badge: affected.size()",
385
+ "pill-badge: { text: affectedSize.formatBytes(), color: 'rgba(120, 177, 9, 0.35)' }",
386
+ ],
387
+ content: {
388
+ view: "list",
389
+ data: "affected",
390
+ limit: 20,
391
+ item: {
392
+ view: "block",
393
+ content: getTreeModule(),
394
+ },
395
+ },
396
+ },
397
+ {
398
+ view: "block",
399
+ when: "docsUrls",
400
+ className: "in-docs",
401
+ content: [
402
+ "text: 'Learn more: '",
403
+ {
404
+ view: "inline-list",
405
+ data: "docsUrls",
406
+ item: {
407
+ view: "link",
408
+ external: true,
409
+ data: "{ href: $, text: $ }",
410
+ },
411
+ },
412
+ ],
413
+ },
414
+ ],
280
415
  },
281
416
  };
282
- const externalLinkHtml = `<svg class="my-icon my-icon-link" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14 21 3"></path></svg>`;
417
+ function getInsightsTab() {
418
+ return {
419
+ view: "block",
420
+ className: "tab-scroll insights",
421
+ data: "$.bundleInsights()",
422
+ content: [
423
+ { view: "bundle-insights" },
424
+ {
425
+ view: "h3",
426
+ className: "in-title",
427
+ content: [
428
+ "text: 'Recommendations '",
429
+ {
430
+ view: "pill-badge",
431
+ when: "hasRecommendations",
432
+ data: "findings.size()",
433
+ },
434
+ ],
435
+ },
436
+ {
437
+ view: "alert-warning",
438
+ when: "not hasRecommendations",
439
+ content:
440
+ "html: 'Recommendations are only available for production bundles. Generate the report from a bundle built with <code>--dev false</code> to see them.'",
441
+ },
442
+ {
443
+ view: "alert-success",
444
+ when: "hasRecommendations and not findings",
445
+ content: "text: '✅ No recommendations: the bundle looks good!'",
446
+ },
447
+ {
448
+ view: "list",
449
+ data: "findings",
450
+ item: findingItem,
451
+ },
452
+ ],
453
+ };
454
+ }
283
455
  function getCopyToClipboardButton({ textToCopy, className, text }) {
284
456
  return {
285
457
  view: "button",
@@ -302,10 +474,10 @@ function getCopyToClipboardButton({ textToCopy, className, text }) {
302
474
  };
303
475
  }
304
476
  //#endregion
305
- exports.externalLinkHtml = externalLinkHtml;
306
477
  exports.getCopyToClipboardButton = getCopyToClipboardButton;
478
+ exports.getInsightsTab = getInsightsTab;
307
479
  exports.getModulesTree = getModulesTree;
308
480
  exports.getPackage = getPackage;
309
481
  exports.getPackageList = getPackageList;
482
+ exports.getSortableContentFilter = getSortableContentFilter;
310
483
  exports.getTreeModule = getTreeModule;
311
- exports.metadata = metadata;