@pipeline-builder/pipeline-data 4.1.24 → 4.1.25

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 (64) hide show
  1. package/lib/api/alert-destinations.d.ts +17 -0
  2. package/lib/api/alert-destinations.js +36 -0
  3. package/lib/api/reporting/analytics-queries.d.ts +274 -0
  4. package/lib/api/reporting/analytics-queries.js +598 -0
  5. package/lib/api/reporting/daily-rollup.d.ts +41 -0
  6. package/lib/api/reporting/daily-rollup.js +173 -0
  7. package/lib/api/reporting/deploy-rollup.d.ts +78 -0
  8. package/lib/api/reporting/deploy-rollup.js +203 -0
  9. package/lib/api/reporting/dora-scoring.d.ts +14 -0
  10. package/lib/api/reporting/dora-scoring.js +25 -1
  11. package/lib/api/reporting/dora.js +30 -6
  12. package/lib/api/reporting/failure-classifier.d.ts +54 -0
  13. package/lib/api/reporting/failure-classifier.js +179 -0
  14. package/lib/api/reporting/ingest.d.ts +2 -2
  15. package/lib/api/reporting/ingest.js +129 -4
  16. package/lib/api/reporting/last-deploy.d.ts +31 -0
  17. package/lib/api/reporting/last-deploy.js +93 -0
  18. package/lib/api/reporting/needs-attention.d.ts +93 -0
  19. package/lib/api/reporting/needs-attention.js +152 -0
  20. package/lib/api/reporting/plugin-exposure.d.ts +129 -0
  21. package/lib/api/reporting/plugin-exposure.js +312 -0
  22. package/lib/api/reporting/retention-sweep.js +9 -1
  23. package/lib/api/reporting/sql-helpers.d.ts +35 -2
  24. package/lib/api/reporting/sql-helpers.js +39 -1
  25. package/lib/api/reporting/stakeholder/compose.d.ts +115 -0
  26. package/lib/api/reporting/stakeholder/compose.js +234 -0
  27. package/lib/api/reporting/stakeholder/index.d.ts +14 -0
  28. package/lib/api/reporting/stakeholder/index.js +17 -0
  29. package/lib/api/reporting/stakeholder/period.d.ts +102 -0
  30. package/lib/api/reporting/stakeholder/period.js +273 -0
  31. package/lib/api/reporting/stakeholder/report-html.d.ts +93 -0
  32. package/lib/api/reporting/stakeholder/report-html.js +208 -0
  33. package/lib/api/reporting/stakeholder/sections.d.ts +117 -0
  34. package/lib/api/reporting/stakeholder/sections.js +249 -0
  35. package/lib/api/reporting/stakeholder/store.d.ts +450 -0
  36. package/lib/api/reporting/stakeholder/store.js +981 -0
  37. package/lib/api/reporting/stakeholder/templates.d.ts +21 -0
  38. package/lib/api/reporting/stakeholder/templates.js +104 -0
  39. package/lib/api/reporting/types.d.ts +73 -0
  40. package/lib/api/reporting/types.js +1 -1
  41. package/lib/api/reporting-service.d.ts +94 -5
  42. package/lib/api/reporting-service.js +169 -10
  43. package/lib/api/soft-delete-sweep.d.ts +11 -2
  44. package/lib/api/soft-delete-sweep.js +17 -6
  45. package/lib/database/drizzle-schema.d.ts +2263 -385
  46. package/lib/database/drizzle-schema.js +18 -7
  47. package/lib/database/index.d.ts +1 -1
  48. package/lib/database/index.js +4 -2
  49. package/lib/database/schema/compliance.d.ts +0 -682
  50. package/lib/database/schema/compliance.js +6 -66
  51. package/lib/database/schema/ecosystem.d.ts +20 -157
  52. package/lib/database/schema/ecosystem.js +23 -16
  53. package/lib/database/schema/pipeline.d.ts +200 -0
  54. package/lib/database/schema/pipeline.js +100 -1
  55. package/lib/database/schema/plugin.d.ts +20 -1
  56. package/lib/database/schema/plugin.js +13 -1
  57. package/lib/database/schema/reporting-analytics.d.ts +1212 -0
  58. package/lib/database/schema/reporting-analytics.js +250 -0
  59. package/lib/database/schema/reporting-stakeholder.d.ts +1405 -0
  60. package/lib/database/schema/reporting-stakeholder.js +251 -0
  61. package/lib/index.d.ts +10 -1
  62. package/lib/index.js +15 -1
  63. package/lib/testing/exports.json +67 -4
  64. package/package.json +4 -3
@@ -0,0 +1,208 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /** HTML-escape. Applied at every insertion point; see the file header. */
4
+ function esc(value) {
5
+ return String(value ?? '')
6
+ .replace(/&/g, '&')
7
+ .replace(/</g, '&lt;')
8
+ .replace(/>/g, '&gt;')
9
+ .replace(/"/g, '&quot;')
10
+ .replace(/'/g, '&#39;');
11
+ }
12
+ /** A date as a plain calendar day, in UTC, or `—` when absent. */
13
+ function day(iso) {
14
+ if (!iso)
15
+ return '—';
16
+ const d = new Date(iso);
17
+ if (Number.isNaN(d.getTime()))
18
+ return '—';
19
+ return d.toISOString().slice(0, 10);
20
+ }
21
+ /**
22
+ * The trend, IN WORDS.
23
+ *
24
+ * "down 3 points" rather than a red arrow, because the arrow is the whole fact for a
25
+ * section whose headline barely moved, and an arrow is invisible to a screen reader and
26
+ * ambiguous in greyscale. `unknown` renders nothing at all rather than "flat" — a section
27
+ * with no comparable previous period has not held steady, we just cannot say.
28
+ */
29
+ export function trendSentence(section) {
30
+ const { change, headline } = section;
31
+ if (!change || change.direction === 'unknown')
32
+ return null;
33
+ const unit = headline?.unit === '%' ? 'points' : null;
34
+ const magnitude = Math.abs(change.absolute);
35
+ if (change.direction === 'flat')
36
+ return 'unchanged from the previous period';
37
+ const word = change.direction === 'up' ? 'up' : 'down';
38
+ const pct = change.percent !== null && change.percent !== undefined
39
+ ? ` (${change.percent > 0 ? '+' : ''}${change.percent}%)`
40
+ : '';
41
+ return `${word} ${magnitude}${unit ? ` ${unit}` : ''}${pct} from the previous period`;
42
+ }
43
+ /**
44
+ * One section as a table row.
45
+ *
46
+ * A TABLE, not a grid of cards, because that is what makes the PDF's structure tree
47
+ * navigable: a screen reader announces "Build success rate, 91%, down 3 points from the
48
+ * previous period" from the row's association with its headers. Cards would print the
49
+ * same ink and carry none of that.
50
+ */
51
+ function sectionRow(section) {
52
+ const title = esc(section.title);
53
+ if (section.state === 'locked') {
54
+ const feature = section.requiresFeature ? esc(section.requiresFeature) : 'an add-on';
55
+ return `<tr><th scope="row">${title}</th>`
56
+ + '<td class="muted">Not included</td>'
57
+ + `<td class="muted">Not on your plan — needs ${feature}. The rest of this report is unaffected.</td></tr>`;
58
+ }
59
+ if (section.state === 'failed') {
60
+ return `<tr><th scope="row">${title}</th>`
61
+ + '<td class="muted">Unavailable</td>'
62
+ + '<td class="muted">Could not be computed for this period.</td></tr>';
63
+ }
64
+ const { headline } = section;
65
+ // A section with no single number is not an error and must not read like one: several
66
+ // legitimately have none (a breakdown, a list of offenders).
67
+ const value = headline
68
+ ? `${esc(headline.value)}${esc(headline.unit ?? '')}`
69
+ : '—';
70
+ const label = headline?.label ? esc(headline.label) : 'No single headline for this section';
71
+ const trend = trendSentence(section);
72
+ const note = trend ? `${label} · ${esc(trend)}` : label;
73
+ return `<tr><th scope="row">${title}</th><td class="value">${value}</td><td>${note}</td></tr>`;
74
+ }
75
+ /** The data-quality notes, which are the difference between "0 deploys" and "we cannot see your deploys". */
76
+ function notesList(notes) {
77
+ if (notes.length === 0)
78
+ return '';
79
+ const items = notes.map((n) => `<li>${esc(n.message)}</li>`).join('');
80
+ return '<section class="block"><h2>What to know about these numbers</h2>'
81
+ + `<ul class="notes">${items}</ul></section>`;
82
+ }
83
+ /**
84
+ * Paragraphs from free text.
85
+ *
86
+ * Blank-line separated, and NOT a single `<p>` with newlines: a lead who wrote three
87
+ * paragraphs gets three, and the PDF's structure tree gets three, instead of one wall
88
+ * that a screen reader reads as a single utterance.
89
+ */
90
+ function paragraphs(text) {
91
+ return text
92
+ .split(/\n{2,}/)
93
+ .map((p) => p.trim())
94
+ .filter(Boolean)
95
+ .map((p) => `<p>${esc(p).replace(/\n/g, '<br />')}</p>`)
96
+ .join('');
97
+ }
98
+ /**
99
+ * The print stylesheet.
100
+ *
101
+ * Deliberately small and deliberately not the product's design system: this document is
102
+ * printed once, to paper size, by one engine. Importing the app's CSS would drag in a web
103
+ * font (a network fetch the renderer forbids) and a dark-mode block that would render a
104
+ * manager's PDF black.
105
+ *
106
+ * `@page` owns the margins so Chromium's own `margin` option does not have to, which keeps
107
+ * the page geometry in the document rather than split across two places.
108
+ */
109
+ const STYLE = `
110
+ :root { color-scheme: only light; }
111
+ * { box-sizing: border-box; }
112
+ @page { size: A4; margin: 18mm 16mm 20mm; }
113
+ body {
114
+ margin: 0; background: #ffffff; color: #111827;
115
+ font: 11pt/1.45 -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
116
+ }
117
+ h1 { font-size: 20pt; margin: 0 0 2mm; }
118
+ h2 { font-size: 12pt; margin: 0 0 2mm; }
119
+ p { margin: 0 0 2.5mm; }
120
+ .sub { color: #4b5563; font-size: 9.5pt; margin: 0 0 6mm; }
121
+ .block { margin: 0 0 7mm; page-break-inside: avoid; }
122
+ table { width: 100%; border-collapse: collapse; font-size: 10pt; }
123
+ caption { text-align: left; font-weight: 600; font-size: 12pt; padding: 0 0 2mm; }
124
+ th, td { text-align: left; vertical-align: top; padding: 2mm 2mm 2mm 0; border-bottom: 1px solid #e5e7eb; }
125
+ thead th { border-bottom: 1.5px solid #9ca3af; font-size: 9pt; text-transform: uppercase; letter-spacing: .04em; }
126
+ tbody th { font-weight: 600; width: 42%; }
127
+ td.value { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; width: 18%; }
128
+ .muted { color: #4b5563; }
129
+ .notes { margin: 0; padding-left: 5mm; }
130
+ .notes li { margin: 0 0 1.5mm; }
131
+ .footer { margin-top: 8mm; padding-top: 3mm; border-top: 1px solid #e5e7eb; color: #4b5563; font-size: 8.5pt; }
132
+ .footer p { margin: 0 0 1mm; }
133
+ tr { page-break-inside: avoid; }
134
+ `;
135
+ /**
136
+ * Render one report as a complete HTML document.
137
+ *
138
+ * ORDER IS MEANING, and it is the same order the shared web page uses: the lead's words,
139
+ * then the executive summary, then the numbers. A reader who meets the table first has
140
+ * already formed a view of the dip by the time they reach the sentence explaining it, so
141
+ * the context the data cannot supply has to come first. `a11y-stakeholder-reports.test.tsx`
142
+ * pins that ordering for the page; `report-html.test.ts` pins it here.
143
+ *
144
+ * A NULL SNAPSHOT STILL RENDERS. A run can be read before or without a snapshot (a failed
145
+ * compose, a draft), and answering an empty file would look like a broken download. The
146
+ * document says there is nothing to show instead.
147
+ */
148
+ export function renderReportHtml(input) {
149
+ const snapshot = input.snapshot;
150
+ const sections = snapshot?.sections ?? [];
151
+ const lang = 'en';
152
+ const head = [
153
+ '<!DOCTYPE html>',
154
+ `<html lang="${lang}"><head><meta charset="utf-8" />`,
155
+ // No external anything: the one directive that makes the promise in the file header
156
+ // enforceable by the engine rather than by review.
157
+ '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; style-src \'unsafe-inline\'" />',
158
+ `<title>${esc(input.title)} — ${esc(input.periodLabel)}</title>`,
159
+ `<style>${STYLE}</style>`,
160
+ '</head><body>',
161
+ ].join('');
162
+ const version = input.version > 1 ? ` · revision ${esc(input.version)}` : '';
163
+ const published = input.publishedAt ? ` · published ${esc(day(input.publishedAt))}` : '';
164
+ const header = `<h1>${esc(input.title)}</h1>`
165
+ + `<p class="sub">${esc(input.periodLabel)} · ${esc(day(input.periodStart))} to `
166
+ + `${esc(day(input.periodEnd))}${version}${published}</p>`;
167
+ const summary = input.executiveSummary
168
+ ? `<section class="block"><h2>Executive summary</h2>${paragraphs(input.executiveSummary)}</section>`
169
+ : '';
170
+ const notes = input.leadNotes
171
+ ? `<section class="block"><h2>Notes from the team</h2>${paragraphs(input.leadNotes)}</section>`
172
+ : '';
173
+ const body = sections.length > 0
174
+ ? '<section class="block"><table>'
175
+ + `<caption>Delivery measures for ${esc(input.periodLabel)}</caption>`
176
+ + '<thead><tr><th scope="col">Measure</th><th scope="col">Value</th><th scope="col">Detail</th></tr></thead>'
177
+ + `<tbody>${sections.map(sectionRow).join('')}</tbody></table></section>`
178
+ : '<section class="block"><h2>No measures</h2>'
179
+ + '<p class="muted">This report has no computed sections. Nothing was measured for this period.</p></section>';
180
+ const footerLines = [];
181
+ if (input.namesRedacted) {
182
+ footerLines.push('Project and pipeline names have been replaced in this copy.');
183
+ }
184
+ if (snapshot?.methodology)
185
+ footerLines.push(snapshot.methodology);
186
+ if (snapshot?.generatedAt) {
187
+ footerLines.push(`Figures computed ${day(snapshot.generatedAt)} and frozen for the period shown.`);
188
+ }
189
+ if (input.expiresAt) {
190
+ footerLines.push(`The link this copy came from expires on ${day(input.expiresAt)}.`);
191
+ }
192
+ footerLines.push(`This file was created ${day(input.renderedAt)}.`);
193
+ const footer = `<footer class="footer">${footerLines.map((l) => `<p>${esc(l)}</p>`).join('')}</footer>`;
194
+ return `${head}${header}${notes}${summary}${body}${notesList(snapshot?.notes ?? [])}${footer}</body></html>`;
195
+ }
196
+ /**
197
+ * The download's file name.
198
+ *
199
+ * Built from the period and the version rather than the run id, because the person saving
200
+ * it is filing it next to last month's, and `report-2026-W38-v2.pdf` sorts and reads while
201
+ * a uuid does not. Sanitizing is the ROUTE's job (`attachmentDisposition`) — this only
202
+ * chooses the words.
203
+ */
204
+ export function reportFileName(title, periodLabel, version) {
205
+ const base = `${title} ${periodLabel}${version > 1 ? ` v${version}` : ''}`;
206
+ return `${base.trim()}.pdf`;
207
+ }
208
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwb3J0LWh0bWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBpL3JlcG9ydGluZy9zdGFrZWhvbGRlci9yZXBvcnQtaHRtbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBa0V0QywwRUFBMEU7QUFDMUUsU0FBUyxHQUFHLENBQUMsS0FBYztJQUN6QixPQUFPLE1BQU0sQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDO1NBQ3ZCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDO1NBQ3RCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDO1NBQ3JCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDO1NBQ3JCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDO1NBQ3ZCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDNUIsQ0FBQztBQUVELGtFQUFrRTtBQUNsRSxTQUFTLEdBQUcsQ0FBQyxHQUE4QjtJQUN6QyxJQUFJLENBQUMsR0FBRztRQUFFLE9BQU8sR0FBRyxDQUFDO0lBQ3JCLE1BQU0sQ0FBQyxHQUFHLElBQUksSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ3hCLElBQUksTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7UUFBRSxPQUFPLEdBQUcsQ0FBQztJQUMxQyxPQUFPLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ3RDLENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGFBQWEsQ0FBQyxPQUF3QjtJQUNwRCxNQUFNLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxHQUFHLE9BQU8sQ0FBQztJQUNyQyxJQUFJLENBQUMsTUFBTSxJQUFJLE1BQU0sQ0FBQyxTQUFTLEtBQUssU0FBUztRQUFFLE9BQU8sSUFB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SUFBSSxFQUFFLENBQUMsR0FBRyxNQUFNLGdCQUFnQixDQUFDO0FBQy9HLENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGNBQWMsQ0FBQyxLQUFhLEVBQUUsV0FBbUIsRUFBRSxPQUFlO0lBQ2hGLE1BQU0sSUFBSSxHQUFHLEdBQUcsS0FBSyxJQUFJLFdBQVcsR0FBRyxPQUFPLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQztJQUMzRSxPQUFPLEdBQUcsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUM7QUFDOUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFRoZSBzbmFwc2hvdCwgYXMgb25lIHNlbGYtY29udGFpbmVkIEhUTUwgZG9jdW1lbnQg4oCUIHRoZSBpbnB1dCB0aGUgUERGIHJlbmRlcmVyIHByaW50cy5cbiAqXG4gKiBXSFkgSFRNTCBBTkQgTk9UIEEgUERGIExJQlJBUlkuIEEgbGF5b3V0IGVuZ2luZSB3ZSB3b3VsZCBoYXZlIHRvIHRlYWNoIGFib3V0IHBhZ2VcbiAqIGJyZWFrcywgdGFibGUgd2lkdGhzIGFuZCBmb250IG1ldHJpY3MgaXMgYSBzZWNvbmQgcmVuZGVyZXIgdG8gbWFpbnRhaW47IENocm9taXVtXG4gKiBhbHJlYWR5IGhhcyBvbmUuIFNvIHRoZSBwaXBlbGluZSBpcyBzbmFwc2hvdCAtPiBIVE1MIC0+IGBQYWdlLnByaW50VG9QREZgLCBhbmQgdGhpc1xuICogZmlsZSBvd25zIHRoZSBmaXJzdCBhcnJvdy4gSXQgaXMgcHVyZTogYSBzdHJpbmcgaW4sIGEgc3RyaW5nIG91dCwgbm8gSS9PLCBubyBjbG9jaywgbm9cbiAqIGRhdGFiYXNlLiBUaGF0IGlzIHdoYXQgbWFrZXMgdGhlIFBERidzIGNvbnRlbnQgdGVzdGFibGUgd2l0aG91dCBhIGJyb3dzZXIuXG4gKlxuICogU0VMRi1DT05UQUlORUQsIEFORCBUSEFUIElTIEEgU0VDVVJJVFkgUFJPUEVSVFksIG5vdCB0aWRpbmVzcy4gTm8gPHNjcmlwdD4sIG5vXG4gKiBzdHlsZXNoZWV0IGxpbmssIG5vIHdlYiBmb250LCBubyBpbWFnZSBVUkwg4oCUIGV2ZXJ5dGhpbmcgaXMgaW5saW5lLiBUaGUgcmVuZGVyZXIgbG9hZHNcbiAqIHRoaXMgdGhyb3VnaCBgc2V0Q29udGVudGAgd2l0aCB0aGUgcGFnZSBvZmZsaW5lLCBzbyBhIGRvY3VtZW50IHRoYXQgcmVmZXJlbmNlZCBhbnl0aGluZ1xuICogZXh0ZXJuYWwgd291bGQgZWl0aGVyIGhhbmcgb3IgcXVpZXRseSByZW5kZXIgd2l0aG91dCBpdC4gU3RhdGVkIGhlcmUgYmVjYXVzZSB0aGVcbiAqIHRlbXB0YXRpb24gdG8gYWRkIGEgbG9nbyBieSBVUkwgd2lsbCBjb21lIHVwLCBhbmQgaXQgd291bGQgYmUgdGhlIG9uZSBjaGFuZ2UgdGhhdCBnaXZlc1xuICogdGhlIHJlbmRlcmVyIGEgcmVhc29uIHRvIHRhbGsgdG8gdGhlIG5ldHdvcmsuXG4gKlxuICogRVZFUlkgSU5URVJQT0xBVElPTiBJUyBFU0NBUEVELiBUaGUgc25hcHNob3QgY2FycmllcyBvcmctY29udHJvbGxlZCB0ZXh0IOKAlCBwaXBlbGluZVxuICogbmFtZXMsIGEgbGVhZCdzIG5vdGVzLCBhIHNlY3Rpb24gdGl0bGUg4oCUIGludG8gbWFya3VwIHRoYXQgYSBicm93c2VyIHRoZW4gcGFyc2VzLiBUaGF0XG4gKiBpcyB0aGUgc2FtZSBib3VuZGFyeSBhcyBhbnkgWFNTIHNpbmssIGFuZCBpdCBkb2VzIG5vdCBzdG9wIG1hdHRlcmluZyBiZWNhdXNlIHRoZSBvdXRwdXRcbiAqIGlzIGEgUERGOiB1bmVzY2FwZWQgbm90ZXMgY291bGQgY2xvc2UgYSB0YWcgYW5kIHJld3JpdGUgdGhlIHJlc3Qgb2YgdGhlIHJlcG9ydCwgd2hpY2ggaXNcbiAqIGEgZG9jdW1lbnQtZm9yZ2VyeSBidWcgaW4gYSBkb2N1bWVudCBwZW9wbGUgbWFrZSBkZWNpc2lvbnMgZnJvbS4gYGVzYygpYCBpcyBhcHBsaWVkIGF0XG4gKiBldmVyeSBzaW5nbGUgaW5zZXJ0aW9uIHBvaW50IGFuZCB0aGVyZSBpcyBubyBcInRydXN0ZWRcIiBjYWxsZXIuXG4gKlxuICogTk8gQ09MT1VSLU9OTFkgTUVBTklORywgYW5kIG5vIGJhcmUgZ2x5cGggY2FycnlpbmcgaW5mb3JtYXRpb24gKFdDQUcgMS40LjEgLyAxLjEuMSkuIEFcbiAqIGxvY2tlZCBwYW5lbCBzYXlzIFwiTm90IG9uIHlvdXIgcGxhblwiIGluIHdvcmRzOyBhIHRyZW5kIHNheXMgXCJkb3duIDMgcG9pbnRzXCIgaW4gd29yZHNcbiAqIHJhdGhlciB0aGFuIGxlYW5pbmcgb24gYW4gYXJyb3cuIFRoZXJlIGFyZSBubyBjaGFydHMgaW4gYSBzbmFwc2hvdCB0b2RheSDigJQgZXZlcnkgc2VjdGlvblxuICogcmVuZGVycyBhcyBhIG51bWJlciwgYSB3b3JkIGFuZCBhIHRhYmxlIHJvdyDigJQgc28gdGhlIHBsYW4ncyBcImFsdCB0ZXh0IG9uIGNoYXJ0c1wiIGhhc1xuICogbm90aGluZyB0byBhdHRhY2ggdG8geWV0LiBUaGUgcnVsZSB0aGF0IHJlcGxhY2VzIGl0IGlzIHN0cmljdGVyIGFuZCBzdXJ2aXZlcyBhIGNoYXJ0XG4gKiBiZWluZyBhZGRlZCBsYXRlcjogaWYgYSByZWFkZXIgd2l0aCBubyBjb2xvdXIgdmlzaW9uLCBubyBpbWFnZXMgYW5kIGEgc2NyZWVuIHJlYWRlclxuICogY2Fubm90IGdldCBhIGZhY3Qgb3V0IG9mIHRoaXMgZG9jdW1lbnQsIHRoZSBmYWN0IGlzIG5vdCBpbiBpdC5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IENvbXBvc2VkU2VjdGlvbiwgRGF0YVF1YWxpdHlOb3RlLCBSZXBvcnRTbmFwc2hvdCB9IGZyb20gJy4vY29tcG9zZS5qcyc7XG5cbi8qKiBXaGF0IHRoZSByZW5kZXJlciBuZWVkcyB0byBwcm9kdWNlIG9uZSByZXBvcnQgZG9jdW1lbnQuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlcG9ydEh0bWxJbnB1dCB7XG4gIC8qKiBUaGUgZGVmaW5pdGlvbidzIG5hbWUuIEhlYWRzIHRoZSBkb2N1bWVudC4gKi9cbiAgdGl0bGU6IHN0cmluZztcbiAgcGVyaW9kTGFiZWw6IHN0cmluZztcbiAgcGVyaW9kU3RhcnQ6IHN0cmluZztcbiAgcGVyaW9kRW5kOiBzdHJpbmc7XG4gIC8qKiBSZWdlbmVyYXRpb25zIGFyZSBub3JtYWw7IGEgcmVhZGVyIG11c3QgYmUgYWJsZSB0byB0ZWxsIHdoaWNoIG9uZSB0aGV5IGhvbGQuICovXG4gIHZlcnNpb246IG51bWJlcjtcbiAgcHVibGlzaGVkQXQ/OiBzdHJpbmcgfCBudWxsO1xuICAvKiogVGhlIGxlYWQncyBvd24gd29yZHMuIFJlbmRlcmVkIEFCT1ZFIHRoZSBudW1iZXJzIOKAlCBzZWUgdGhlIG9yZGVyaW5nIG5vdGUgYmVsb3cuICovXG4gIGxlYWROb3Rlcz86IHN0cmluZyB8IG51bGw7XG4gIC8qKlxuICAgKiBUaGUgQUktZHJhZnRlZCBleGVjdXRpdmUgc3VtbWFyeSwgd2hlbiB0aGUgcnVuIGhhcyBvbmUgYW5kIHRoZSBjYWxsZXIgaXMgYWxsb3dlZCB0b1xuICAgKiBzZWUgaXQuIE5ldmVyIGZldGNoZWQgaGVyZTogYSBjYWxsZXIgdGhhdCBzaG91bGQgbm90IGRpc2Nsb3NlIGl0IHNpbXBseSBvbWl0cyBpdC5cbiAgICovXG4gIGV4ZWN1dGl2ZVN1bW1hcnk/OiBzdHJpbmcgfCBudWxsO1xuICBzbmFwc2hvdDogUmVwb3J0U25hcHNob3QgfCBudWxsO1xuICAvKiogVHJ1ZSB3aGVuIHBpcGVsaW5lL3Byb2plY3QgbmFtZXMgd2VyZSByZXBsYWNlZCDigJQgc2FpZCBpbiB0aGUgZG9jdW1lbnQsIG5vdCBpbXBsaWVkLiAqL1xuICBuYW1lc1JlZGFjdGVkPzogYm9vbGVhbjtcbiAgLyoqIFNob3duIGluIHRoZSBmb290ZXIgb2YgYSBzaGFyZWQgY29weSwgc28gYSBib29rbWFya2VkIGxpbmsncyBkZWF0aCBpcyBub3QgYSBzdXJwcmlzZS4gKi9cbiAgZXhwaXJlc0F0Pzogc3RyaW5nIHwgbnVsbDtcbiAgLyoqXG4gICAqIFN0YW1wZWQgaW4gdGhlIGZvb3RlciBhcyB0aGUgbW9tZW50IHRoZSBGSUxFIHdhcyBtYWRlLCBkaXN0aW5jdCBmcm9tIHRoZSBzbmFwc2hvdCdzXG4gICAqIG93biBgZ2VuZXJhdGVkQXRgLiBUd28gZGF0ZXMgYmVjYXVzZSB0aGV5IGFuc3dlciBkaWZmZXJlbnQgcXVlc3Rpb25zOiBcImlzIHRoaXMgcmVwb3J0XG4gICAqIGN1cnJlbnQ/XCIgYW5kIFwiaXMgdGhpcyB0aGUgY29weSBJIHdhcyBzZW50P1wiLlxuICAgKi9cbiAgcmVuZGVyZWRBdDogc3RyaW5nO1xufVxuXG4vKiogSFRNTC1lc2NhcGUuIEFwcGxpZWQgYXQgZXZlcnkgaW5zZXJ0aW9uIHBvaW50OyBzZWUgdGhlIGZpbGUgaGVhZGVyLiAqL1xuZnVuY3Rpb24gZXNjKHZhbHVlOiB1bmtub3duKTogc3RyaW5nIHtcbiAgcmV0dXJuIFN0cmluZyh2YWx1ZSA/PyAnJylcbiAgICAucmVwbGFjZSgvJi9nLCAnJmFtcDsnKVxuICAgIC5yZXBsYWNlKC88L2csICcmbHQ7JylcbiAgICAucmVwbGFjZSgvPi9nLCAnJmd0OycpXG4gICAgLnJlcGxhY2UoL1wiL2csICcmcXVvdDsnKVxuICAgIC5yZXBsYWNlKC8nL2csICcmIzM5OycpO1xufVxuXG4vKiogQSBkYXRlIGFzIGEgcGxhaW4gY2FsZW5kYXIgZGF5LCBpbiBVVEMsIG9yIGDigJRgIHdoZW4gYWJzZW50LiAqL1xuZnVuY3Rpb24gZGF5KGlzbzogc3RyaW5nIHwgbnVsbCB8IHVuZGVmaW5lZCk6IHN0cmluZyB7XG4gIGlmICghaXNvKSByZXR1cm4gJ+KAlCc7XG4gIGNvbnN0IGQgPSBuZXcgRGF0ZShpc28pO1xuICBpZiAoTnVtYmVyLmlzTmFOKGQuZ2V0VGltZSgpKSkgcmV0dXJuICfigJQnO1xuICByZXR1cm4gZC50b0lTT1N0cmluZygpLnNsaWNlKDAsIDEwKTtcbn1cblxuLyoqXG4gKiBUaGUgdHJlbmQsIElOIFdPUkRTLlxuICpcbiAqIFwiZG93biAzIHBvaW50c1wiIHJhdGhlciB0aGFuIGEgcmVkIGFycm93LCBiZWNhdXNlIHRoZSBhcnJvdyBpcyB0aGUgd2hvbGUgZmFjdCBmb3IgYVxuICogc2VjdGlvbiB3aG9zZSBoZWFkbGluZSBiYXJlbHkgbW92ZWQsIGFuZCBhbiBhcnJvdyBpcyBpbnZpc2libGUgdG8gYSBzY3JlZW4gcmVhZGVyIGFuZFxuICogYW1iaWd1b3VzIGluIGdyZXlzY2FsZS4gYHVua25vd25gIHJlbmRlcnMgbm90aGluZyBhdCBhbGwgcmF0aGVyIHRoYW4gXCJmbGF0XCIg4oCUIGEgc2VjdGlvblxuICogd2l0aCBubyBjb21wYXJhYmxlIHByZXZpb3VzIHBlcmlvZCBoYXMgbm90IGhlbGQgc3RlYWR5LCB3ZSBqdXN0IGNhbm5vdCBzYXkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0cmVuZFNlbnRlbmNlKHNlY3Rpb246IENvbXBvc2VkU2VjdGlvbik6IHN0cmluZyB8IG51bGwge1xuICBjb25zdCB7IGNoYW5nZSwgaGVhZGxpbmUgfSA9IHNlY3Rpb247XG4gIGlmICghY2hhbmdlIHx8IGNoYW5nZS5kaXJlY3Rpb24gPT09ICd1bmtub3duJykgcmV0dXJuIG51bGw7XG4gIGNvbnN0IHVuaXQgPSBoZWFkbGluZT8udW5pdCA9PT0gJyUnID8gJ3BvaW50cycgOiBudWxsO1xuICBjb25zdCBtYWduaXR1ZGUgPSBNYXRoLmFicyhjaGFuZ2UuYWJzb2x1dGUpO1xuICBpZiAoY2hhbmdlLmRpcmVjdGlvbiA9PT0gJ2ZsYXQnKSByZXR1cm4gJ3VuY2hhbmdlZCBmcm9tIHRoZSBwcmV2aW91cyBwZXJpb2QnO1xuICBjb25zdCB3b3JkID0gY2hhbmdlLmRpcmVjdGlvbiA9PT0gJ3VwJyA/ICd1cCcgOiAnZG93bic7XG4gIGNvbnN0IHBjdCA9IGNoYW5nZS5wZXJjZW50ICE9PSBudWxsICYmIGNoYW5nZS5wZXJjZW50ICE9PSB1bmRlZmluZWRcbiAgICA/IGAgKCR7Y2hhbmdlLnBlcmNlbnQgPiAwID8gJysnIDogJyd9JHtjaGFuZ2UucGVyY2VudH0lKWBcbiAgICA6ICcnO1xuICByZXR1cm4gYCR7d29yZH0gJHttYWduaXR1ZGV9JHt1bml0ID8gYCAke3VuaXR9YCA6ICcnfSR7cGN0fSBmcm9tIHRoZSBwcmV2aW91cyBwZXJpb2RgO1xufVxuXG4vKipcbiAqIE9uZSBzZWN0aW9uIGFzIGEgdGFibGUgcm93LlxuICpcbiAqIEEgVEFCTEUsIG5vdCBhIGdyaWQgb2YgY2FyZHMsIGJlY2F1c2UgdGhhdCBpcyB3aGF0IG1ha2VzIHRoZSBQREYncyBzdHJ1Y3R1cmUgdHJlZVxuICogbmF2aWdhYmxlOiBhIHNjcmVlbiByZWFkZXIgYW5ub3VuY2VzIFwiQnVpbGQgc3VjY2VzcyByYXRlLCA5MSUsIGRvd24gMyBwb2ludHMgZnJvbSB0aGVcbiAqIHByZXZpb3VzIHBlcmlvZFwiIGZyb20gdGhlIHJvdydzIGFzc29jaWF0aW9uIHdpdGggaXRzIGhlYWRlcnMuIENhcmRzIHdvdWxkIHByaW50IHRoZVxuICogc2FtZSBpbmsgYW5kIGNhcnJ5IG5vbmUgb2YgdGhhdC5cbiAqL1xuZnVuY3Rpb24gc2VjdGlvblJvdyhzZWN0aW9uOiBDb21wb3NlZFNlY3Rpb24pOiBzdHJpbmcge1xuICBjb25zdCB0aXRsZSA9IGVzYyhzZWN0aW9uLnRpdGxlKTtcbiAgaWYgKHNlY3Rpb24uc3RhdGUgPT09ICdsb2NrZWQnKSB7XG4gICAgY29uc3QgZmVhdHVyZSA9IHNlY3Rpb24ucmVxdWlyZXNGZWF0dXJlID8gZXNjKHNlY3Rpb24ucmVxdWlyZXNGZWF0dXJlKSA6ICdhbiBhZGQtb24nO1xuICAgIHJldHVybiBgPHRyPjx0aCBzY29wZT1cInJvd1wiPiR7dGl0bGV9PC90aD5gXG4gICAgICArICc8dGQgY2xhc3M9XCJtdXRlZFwiPk5vdCBpbmNsdWRlZDwvdGQ+J1xuICAgICAgKyBgPHRkIGNsYXNzPVwibXV0ZWRcIj5Ob3Qgb24geW91ciBwbGFuIOKAlCBuZWVkcyAke2ZlYXR1cmV9LiBUaGUgcmVzdCBvZiB0aGlzIHJlcG9ydCBpcyB1bmFmZmVjdGVkLjwvdGQ+PC90cj5gO1xuICB9XG4gIGlmIChzZWN0aW9uLnN0YXRlID09PSAnZmFpbGVkJykge1xuICAgIHJldHVybiBgPHRyPjx0aCBzY29wZT1cInJvd1wiPiR7dGl0bGV9PC90aD5gXG4gICAgICArICc8dGQgY2xhc3M9XCJtdXRlZFwiPlVuYXZhaWxhYmxlPC90ZD4nXG4gICAgICArICc8dGQgY2xhc3M9XCJtdXRlZFwiPkNvdWxkIG5vdCBiZSBjb21wdXRlZCBmb3IgdGhpcyBwZXJpb2QuPC90ZD48L3RyPic7XG4gIH1cbiAgY29uc3QgeyBoZWFkbGluZSB9ID0gc2VjdGlvbjtcbiAgLy8gQSBzZWN0aW9uIHdpdGggbm8gc2luZ2xlIG51bWJlciBpcyBub3QgYW4gZXJyb3IgYW5kIG11c3Qgbm90IHJlYWQgbGlrZSBvbmU6IHNldmVyYWxcbiAgLy8gbGVnaXRpbWF0ZWx5IGhhdmUgbm9uZSAoYSBicmVha2Rvd24sIGEgbGlzdCBvZiBvZmZlbmRlcnMpLlxuICBjb25zdCB2YWx1ZSA9IGhlYWRsaW5lXG4gICAgPyBgJHtlc2MoaGVhZGxpbmUudmFsdWUpfSR7ZXNjKGhlYWRsaW5lLnVuaXQgPz8gJycpfWBcbiAgICA6ICfigJQnO1xuICBjb25zdCBsYWJlbCA9IGhlYWRsaW5lPy5sYWJlbCA/IGVzYyhoZWFkbGluZS5sYWJlbCkgOiAnTm8gc2luZ2xlIGhlYWRsaW5lIGZvciB0aGlzIHNlY3Rpb24nO1xuICBjb25zdCB0cmVuZCA9IHRyZW5kU2VudGVuY2Uoc2VjdGlvbik7XG4gIGNvbnN0IG5vdGUgPSB0cmVuZCA/IGAke2xhYmVsfSDCtyAke2VzYyh0cmVuZCl9YCA6IGxhYmVsO1xuICByZXR1cm4gYDx0cj48dGggc2NvcGU9XCJyb3dcIj4ke3RpdGxlfTwvdGg+PHRkIGNsYXNzPVwidmFsdWVcIj4ke3ZhbHVlfTwvdGQ+PHRkPiR7bm90ZX08L3RkPjwvdHI+YDtcbn1cblxuLyoqIFRoZSBkYXRhLXF1YWxpdHkgbm90ZXMsIHdoaWNoIGFyZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIFwiMCBkZXBsb3lzXCIgYW5kIFwid2UgY2Fubm90IHNlZSB5b3VyIGRlcGxveXNcIi4gKi9cbmZ1bmN0aW9uIG5vdGVzTGlzdChub3RlczogcmVhZG9ubHkgRGF0YVF1YWxpdHlOb3RlW10pOiBzdHJpbmcge1xuICBpZiAobm90ZXMubGVuZ3RoID09PSAwKSByZXR1cm4gJyc7XG4gIGNvbnN0IGl0ZW1zID0gbm90ZXMubWFwKChuKSA9PiBgPGxpPiR7ZXNjKG4ubWVzc2FnZSl9PC9saT5gKS5qb2luKCcnKTtcbiAgcmV0dXJuICc8c2VjdGlvbiBjbGFzcz1cImJsb2NrXCI+PGgyPldoYXQgdG8ga25vdyBhYm91dCB0aGVzZSBudW1iZXJzPC9oMj4nXG4gICAgKyBgPHVsIGNsYXNzPVwibm90ZXNcIj4ke2l0ZW1zfTwvdWw+PC9zZWN0aW9uPmA7XG59XG5cbi8qKlxuICogUGFyYWdyYXBocyBmcm9tIGZyZWUgdGV4dC5cbiAqXG4gKiBCbGFuay1saW5lIHNlcGFyYXRlZCwgYW5kIE5PVCBhIHNpbmdsZSBgPHA+YCB3aXRoIG5ld2xpbmVzOiBhIGxlYWQgd2hvIHdyb3RlIHRocmVlXG4gKiBwYXJhZ3JhcGhzIGdldHMgdGhyZWUsIGFuZCB0aGUgUERGJ3Mgc3RydWN0dXJlIHRyZWUgZ2V0cyB0aHJlZSwgaW5zdGVhZCBvZiBvbmUgd2FsbFxuICogdGhhdCBhIHNjcmVlbiByZWFkZXIgcmVhZHMgYXMgYSBzaW5nbGUgdXR0ZXJhbmNlLlxuICovXG5mdW5jdGlvbiBwYXJhZ3JhcGhzKHRleHQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiB0ZXh0XG4gICAgLnNwbGl0KC9cXG57Mix9LylcbiAgICAubWFwKChwKSA9PiBwLnRyaW0oKSlcbiAgICAuZmlsdGVyKEJvb2xlYW4pXG4gICAgLm1hcCgocCkgPT4gYDxwPiR7ZXNjKHApLnJlcGxhY2UoL1xcbi9nLCAnPGJyIC8+Jyl9PC9wPmApXG4gICAgLmpvaW4oJycpO1xufVxuXG4vKipcbiAqIFRoZSBwcmludCBzdHlsZXNoZWV0LlxuICpcbiAqIERlbGliZXJhdGVseSBzbWFsbCBhbmQgZGVsaWJlcmF0ZWx5IG5vdCB0aGUgcHJvZHVjdCdzIGRlc2lnbiBzeXN0ZW06IHRoaXMgZG9jdW1lbnQgaXNcbiAqIHByaW50ZWQgb25jZSwgdG8gcGFwZXIgc2l6ZSwgYnkgb25lIGVuZ2luZS4gSW1wb3J0aW5nIHRoZSBhcHAncyBDU1Mgd291bGQgZHJhZyBpbiBhIHdlYlxuICogZm9udCAoYSBuZXR3b3JrIGZldGNoIHRoZSByZW5kZXJlciBmb3JiaWRzKSBhbmQgYSBkYXJrLW1vZGUgYmxvY2sgdGhhdCB3b3VsZCByZW5kZXIgYVxuICogbWFuYWdlcidzIFBERiBibGFjay5cbiAqXG4gKiBgQHBhZ2VgIG93bnMgdGhlIG1hcmdpbnMgc28gQ2hyb21pdW0ncyBvd24gYG1hcmdpbmAgb3B0aW9uIGRvZXMgbm90IGhhdmUgdG8sIHdoaWNoIGtlZXBzXG4gKiB0aGUgcGFnZSBnZW9tZXRyeSBpbiB0aGUgZG9jdW1lbnQgcmF0aGVyIHRoYW4gc3BsaXQgYWNyb3NzIHR3byBwbGFjZXMuXG4gKi9cbmNvbnN0IFNUWUxFID0gYFxuOnJvb3QgeyBjb2xvci1zY2hlbWU6IG9ubHkgbGlnaHQ7IH1cbiogeyBib3gtc2l6aW5nOiBib3JkZXItYm94OyB9XG5AcGFnZSB7IHNpemU6IEE0OyBtYXJnaW46IDE4bW0gMTZtbSAyMG1tOyB9XG5ib2R5IHtcbiAgbWFyZ2luOiAwOyBiYWNrZ3JvdW5kOiAjZmZmZmZmOyBjb2xvcjogIzExMTgyNztcbiAgZm9udDogMTFwdC8xLjQ1IC1hcHBsZS1zeXN0ZW0sIFwiU2Vnb2UgVUlcIiwgUm9ib3RvLCBcIkhlbHZldGljYSBOZXVlXCIsIEFyaWFsLCBzYW5zLXNlcmlmO1xufVxuaDEgeyBmb250LXNpemU6IDIwcHQ7IG1hcmdpbjogMCAwIDJtbTsgfVxuaDIgeyBmb250LXNpemU6IDEycHQ7IG1hcmdpbjogMCAwIDJtbTsgfVxucCB7IG1hcmdpbjogMCAwIDIuNW1tOyB9XG4uc3ViIHsgY29sb3I6ICM0YjU1NjM7IGZvbnQtc2l6ZTogOS41cHQ7IG1hcmdpbjogMCAwIDZtbTsgfVxuLmJsb2NrIHsgbWFyZ2luOiAwIDAgN21tOyBwYWdlLWJyZWFrLWluc2lkZTogYXZvaWQ7IH1cbnRhYmxlIHsgd2lkdGg6IDEwMCU7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IGZvbnQtc2l6ZTogMTBwdDsgfVxuY2FwdGlvbiB7IHRleHQtYWxpZ246IGxlZnQ7IGZvbnQtd2VpZ2h0OiA2MDA7IGZvbnQtc2l6ZTogMTJwdDsgcGFkZGluZzogMCAwIDJtbTsgfVxudGgsIHRkIHsgdGV4dC1hbGlnbjogbGVmdDsgdmVydGljYWwtYWxpZ246IHRvcDsgcGFkZGluZzogMm1tIDJtbSAybW0gMDsgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNlNWU3ZWI7IH1cbnRoZWFkIHRoIHsgYm9yZGVyLWJvdHRvbTogMS41cHggc29saWQgIzljYTNhZjsgZm9udC1zaXplOiA5cHQ7IHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7IGxldHRlci1zcGFjaW5nOiAuMDRlbTsgfVxudGJvZHkgdGggeyBmb250LXdlaWdodDogNjAwOyB3aWR0aDogNDIlOyB9XG50ZC52YWx1ZSB7IGZvbnQtdmFyaWFudC1udW1lcmljOiB0YWJ1bGFyLW51bXM7IGZvbnQtd2VpZ2h0OiA2MDA7IHdoaXRlLXNwYWNlOiBub3dyYXA7IHdpZHRoOiAxOCU7IH1cbi5tdXRlZCB7IGNvbG9yOiAjNGI1NTYzOyB9XG4ubm90ZXMgeyBtYXJnaW46IDA7IHBhZGRpbmctbGVmdDogNW1tOyB9XG4ubm90ZXMgbGkgeyBtYXJnaW46IDAgMCAxLjVtbTsgfVxuLmZvb3RlciB7IG1hcmdpbi10b3A6IDhtbTsgcGFkZGluZy10b3A6IDNtbTsgYm9yZGVyLXRvcDogMXB4IHNvbGlkICNlNWU3ZWI7IGNvbG9yOiAjNGI1NTYzOyBmb250LXNpemU6IDguNXB0OyB9XG4uZm9vdGVyIHAgeyBtYXJnaW46IDAgMCAxbW07IH1cbnRyIHsgcGFnZS1icmVhay1pbnNpZGU6IGF2b2lkOyB9XG5gO1xuXG4vKipcbiAqIFJlbmRlciBvbmUgcmVwb3J0IGFzIGEgY29tcGxldGUgSFRNTCBkb2N1bWVudC5cbiAqXG4gKiBPUkRFUiBJUyBNRUFOSU5HLCBhbmQgaXQgaXMgdGhlIHNhbWUgb3JkZXIgdGhlIHNoYXJlZCB3ZWIgcGFnZSB1c2VzOiB0aGUgbGVhZCdzIHdvcmRzLFxuICogdGhlbiB0aGUgZXhlY3V0aXZlIHN1bW1hcnksIHRoZW4gdGhlIG51bWJlcnMuIEEgcmVhZGVyIHdobyBtZWV0cyB0aGUgdGFibGUgZmlyc3QgaGFzXG4gKiBhbHJlYWR5IGZvcm1lZCBhIHZpZXcgb2YgdGhlIGRpcCBieSB0aGUgdGltZSB0aGV5IHJlYWNoIHRoZSBzZW50ZW5jZSBleHBsYWluaW5nIGl0LCBzb1xuICogdGhlIGNvbnRleHQgdGhlIGRhdGEgY2Fubm90IHN1cHBseSBoYXMgdG8gY29tZSBmaXJzdC4gYGExMXktc3Rha2Vob2xkZXItcmVwb3J0cy50ZXN0LnRzeGBcbiAqIHBpbnMgdGhhdCBvcmRlcmluZyBmb3IgdGhlIHBhZ2U7IGByZXBvcnQtaHRtbC50ZXN0LnRzYCBwaW5zIGl0IGhlcmUuXG4gKlxuICogQSBOVUxMIFNOQVBTSE9UIFNUSUxMIFJFTkRFUlMuIEEgcnVuIGNhbiBiZSByZWFkIGJlZm9yZSBvciB3aXRob3V0IGEgc25hcHNob3QgKGEgZmFpbGVkXG4gKiBjb21wb3NlLCBhIGRyYWZ0KSwgYW5kIGFuc3dlcmluZyBhbiBlbXB0eSBmaWxlIHdvdWxkIGxvb2sgbGlrZSBhIGJyb2tlbiBkb3dubG9hZC4gVGhlXG4gKiBkb2N1bWVudCBzYXlzIHRoZXJlIGlzIG5vdGhpbmcgdG8gc2hvdyBpbnN0ZWFkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVuZGVyUmVwb3J0SHRtbChpbnB1dDogUmVwb3J0SHRtbElucHV0KTogc3RyaW5nIHtcbiAgY29uc3Qgc25hcHNob3QgPSBpbnB1dC5zbmFwc2hvdDtcbiAgY29uc3Qgc2VjdGlvbnMgPSBzbmFwc2hvdD8uc2VjdGlvbnMgPz8gW107XG4gIGNvbnN0IGxhbmcgPSAnZW4nO1xuXG4gIGNvbnN0IGhlYWQgPSBbXG4gICAgJzwhRE9DVFlQRSBodG1sPicsXG4gICAgYDxodG1sIGxhbmc9XCIke2xhbmd9XCI+PGhlYWQ+PG1ldGEgY2hhcnNldD1cInV0Zi04XCIgLz5gLFxuICAgIC8vIE5vIGV4dGVybmFsIGFueXRoaW5nOiB0aGUgb25lIGRpcmVjdGl2ZSB0aGF0IG1ha2VzIHRoZSBwcm9taXNlIGluIHRoZSBmaWxlIGhlYWRlclxuICAgIC8vIGVuZm9yY2VhYmxlIGJ5IHRoZSBlbmdpbmUgcmF0aGVyIHRoYW4gYnkgcmV2aWV3LlxuICAgICc8bWV0YSBodHRwLWVxdWl2PVwiQ29udGVudC1TZWN1cml0eS1Qb2xpY3lcIiBjb250ZW50PVwiZGVmYXVsdC1zcmMgXFwnbm9uZVxcJzsgc3R5bGUtc3JjIFxcJ3Vuc2FmZS1pbmxpbmVcXCdcIiAvPicsXG4gICAgYDx0aXRsZT4ke2VzYyhpbnB1dC50aXRsZSl9IOKAlCAke2VzYyhpbnB1dC5wZXJpb2RMYWJlbCl9PC90aXRsZT5gLFxuICAgIGA8c3R5bGU+JHtTVFlMRX08L3N0eWxlPmAsXG4gICAgJzwvaGVhZD48Ym9keT4nLFxuICBdLmpvaW4oJycpO1xuXG4gIGNvbnN0IHZlcnNpb24gPSBpbnB1dC52ZXJzaW9uID4gMSA/IGAgwrcgcmV2aXNpb24gJHtlc2MoaW5wdXQudmVyc2lvbil9YCA6ICcnO1xuICBjb25zdCBwdWJsaXNoZWQgPSBpbnB1dC5wdWJsaXNoZWRBdCA/IGAgwrcgcHVibGlzaGVkICR7ZXNjKGRheShpbnB1dC5wdWJsaXNoZWRBdCkpfWAgOiAnJztcbiAgY29uc3QgaGVhZGVyID0gYDxoMT4ke2VzYyhpbnB1dC50aXRsZSl9PC9oMT5gXG4gICAgKyBgPHAgY2xhc3M9XCJzdWJcIj4ke2VzYyhpbnB1dC5wZXJpb2RMYWJlbCl9IMK3ICR7ZXNjKGRheShpbnB1dC5wZXJpb2RTdGFydCkpfSB0byBgXG4gICAgKyBgJHtlc2MoZGF5KGlucHV0LnBlcmlvZEVuZCkpfSR7dmVyc2lvbn0ke3B1Ymxpc2hlZH08L3A+YDtcblxuICBjb25zdCBzdW1tYXJ5ID0gaW5wdXQuZXhlY3V0aXZlU3VtbWFyeVxuICAgID8gYDxzZWN0aW9uIGNsYXNzPVwiYmxvY2tcIj48aDI+RXhlY3V0aXZlIHN1bW1hcnk8L2gyPiR7cGFyYWdyYXBocyhpbnB1dC5leGVjdXRpdmVTdW1tYXJ5KX08L3NlY3Rpb24+YFxuICAgIDogJyc7XG5cbiAgY29uc3Qgbm90ZXMgPSBpbnB1dC5sZWFkTm90ZXNcbiAgICA/IGA8c2VjdGlvbiBjbGFzcz1cImJsb2NrXCI+PGgyPk5vdGVzIGZyb20gdGhlIHRlYW08L2gyPiR7cGFyYWdyYXBocyhpbnB1dC5sZWFkTm90ZXMpfTwvc2VjdGlvbj5gXG4gICAgOiAnJztcblxuICBjb25zdCBib2R5ID0gc2VjdGlvbnMubGVuZ3RoID4gMFxuICAgID8gJzxzZWN0aW9uIGNsYXNzPVwiYmxvY2tcIj48dGFibGU+J1xuICAgICAgKyBgPGNhcHRpb24+RGVsaXZlcnkgbWVhc3VyZXMgZm9yICR7ZXNjKGlucHV0LnBlcmlvZExhYmVsKX08L2NhcHRpb24+YFxuICAgICAgKyAnPHRoZWFkPjx0cj48dGggc2NvcGU9XCJjb2xcIj5NZWFzdXJlPC90aD48dGggc2NvcGU9XCJjb2xcIj5WYWx1ZTwvdGg+PHRoIHNjb3BlPVwiY29sXCI+RGV0YWlsPC90aD48L3RyPjwvdGhlYWQ+J1xuICAgICAgKyBgPHRib2R5PiR7c2VjdGlvbnMubWFwKHNlY3Rpb25Sb3cpLmpvaW4oJycpfTwvdGJvZHk+PC90YWJsZT48L3NlY3Rpb24+YFxuICAgIDogJzxzZWN0aW9uIGNsYXNzPVwiYmxvY2tcIj48aDI+Tm8gbWVhc3VyZXM8L2gyPidcbiAgICAgICsgJzxwIGNsYXNzPVwibXV0ZWRcIj5UaGlzIHJlcG9ydCBoYXMgbm8gY29tcHV0ZWQgc2VjdGlvbnMuIE5vdGhpbmcgd2FzIG1lYXN1cmVkIGZvciB0aGlzIHBlcmlvZC48L3A+PC9zZWN0aW9uPic7XG5cbiAgY29uc3QgZm9vdGVyTGluZXM6IHN0cmluZ1tdID0gW107XG4gIGlmIChpbnB1dC5uYW1lc1JlZGFjdGVkKSB7XG4gICAgZm9vdGVyTGluZXMucHVzaCgnUHJvamVjdCBhbmQgcGlwZWxpbmUgbmFtZXMgaGF2ZSBiZWVuIHJlcGxhY2VkIGluIHRoaXMgY29weS4nKTtcbiAgfVxuICBpZiAoc25hcHNob3Q/Lm1ldGhvZG9sb2d5KSBmb290ZXJMaW5lcy5wdXNoKHNuYXBzaG90Lm1ldGhvZG9sb2d5KTtcbiAgaWYgKHNuYXBzaG90Py5nZW5lcmF0ZWRBdCkge1xuICAgIGZvb3RlckxpbmVzLnB1c2goYEZpZ3VyZXMgY29tcHV0ZWQgJHtkYXkoc25hcHNob3QuZ2VuZXJhdGVkQXQpfSBhbmQgZnJvemVuIGZvciB0aGUgcGVyaW9kIHNob3duLmApO1xuICB9XG4gIGlmIChpbnB1dC5leHBpcmVzQXQpIHtcbiAgICBmb290ZXJMaW5lcy5wdXNoKGBUaGUgbGluayB0aGlzIGNvcHkgY2FtZSBmcm9tIGV4cGlyZXMgb24gJHtkYXkoaW5wdXQuZXhwaXJlc0F0KX0uYCk7XG4gIH1cbiAgZm9vdGVyTGluZXMucHVzaChgVGhpcyBmaWxlIHdhcyBjcmVhdGVkICR7ZGF5KGlucHV0LnJlbmRlcmVkQXQpfS5gKTtcbiAgY29uc3QgZm9vdGVyID0gYDxmb290ZXIgY2xhc3M9XCJmb290ZXJcIj4ke2Zvb3RlckxpbmVzLm1hcCgobCkgPT4gYDxwPiR7ZXNjKGwpfTwvcD5gKS5qb2luKCcnKX08L2Zvb3Rlcj5gO1xuXG4gIHJldHVybiBgJHtoZWFkfSR7aGVhZGVyfSR7bm90ZXN9JHtzdW1tYXJ5fSR7Ym9keX0ke25vdGVzTGlzdChzbmFwc2hvdD8ubm90ZXMgPz8gW10pfSR7Zm9vdGVyfTwvYm9keT48L2h0bWw+YDtcbn1cblxuLyoqXG4gKiBUaGUgZG93bmxvYWQncyBmaWxlIG5hbWUuXG4gKlxuICogQnVpbHQgZnJvbSB0aGUgcGVyaW9kIGFuZCB0aGUgdmVyc2lvbiByYXRoZXIgdGhhbiB0aGUgcnVuIGlkLCBiZWNhdXNlIHRoZSBwZXJzb24gc2F2aW5nXG4gKiBpdCBpcyBmaWxpbmcgaXQgbmV4dCB0byBsYXN0IG1vbnRoJ3MsIGFuZCBgcmVwb3J0LTIwMjYtVzM4LXYyLnBkZmAgc29ydHMgYW5kIHJlYWRzIHdoaWxlXG4gKiBhIHV1aWQgZG9lcyBub3QuIFNhbml0aXppbmcgaXMgdGhlIFJPVVRFJ3Mgam9iIChgYXR0YWNobWVudERpc3Bvc2l0aW9uYCkg4oCUIHRoaXMgb25seVxuICogY2hvb3NlcyB0aGUgd29yZHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXBvcnRGaWxlTmFtZSh0aXRsZTogc3RyaW5nLCBwZXJpb2RMYWJlbDogc3RyaW5nLCB2ZXJzaW9uOiBudW1iZXIpOiBzdHJpbmcge1xuICBjb25zdCBiYXNlID0gYCR7dGl0bGV9ICR7cGVyaW9kTGFiZWx9JHt2ZXJzaW9uID4gMSA/IGAgdiR7dmVyc2lvbn1gIDogJyd9YDtcbiAgcmV0dXJuIGAke2Jhc2UudHJpbSgpfS5wZGZgO1xufVxuIl19
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The section registry: the one place that maps a report section id to the
3
+ * ReportingService method that computes it.
4
+ *
5
+ * Why a registry rather than each template calling the service directly:
6
+ *
7
+ * - The numbers in a report MUST match the dashboard. A manager who compares the
8
+ * two and finds a difference stops trusting both, so both read the same query —
9
+ * there is no separate "report" implementation of success rate.
10
+ * - Feature gating belongs with the section, not the route. DORA sections need
11
+ * `advanced_reporting`; a report that includes one without the add-on renders a
12
+ * LOCKED section and still delivers, rather than failing the whole run. A run
13
+ * that dies because of one gated section is a support ticket; a locked panel is
14
+ * an upsell.
15
+ * - Per-person metrics are refused HERE, at registration. Ranking individual
16
+ * developers destroys trust in the whole report, and a guideline in a document
17
+ * does not stop someone adding `topCommitters` later — a throwing registry does.
18
+ */
19
+ import type { FeatureFlag } from '@pipeline-builder/api-core';
20
+ import type { AnalyticsScope } from '../analytics-queries.js';
21
+ /** What a section needs to run one period's query. */
22
+ export interface SectionContext {
23
+ orgId: string;
24
+ /** Rollup org ids, when the definition's scope is a team rollup. */
25
+ orgIds?: string[];
26
+ /** Period bounds as ISO strings, which is what every service method takes. */
27
+ from: string;
28
+ to: string;
29
+ /** The report's bucketing settings, for the time-series sections. */
30
+ tz: string;
31
+ weekStart: 'monday' | 'sunday';
32
+ /**
33
+ * The PREVIOUS period's bounds, for the sections that compute their own trend
34
+ * internally rather than being run twice.
35
+ *
36
+ * Most sections are simply re-run over the previous window by the composer and
37
+ * compared on a single headline. The analytics sections cannot be: their trend is
38
+ * per category, per stage or per pipeline, and comparing two whole result sets
39
+ * afterwards would mean re-implementing the join in the composer.
40
+ */
41
+ previousFrom?: string;
42
+ previousTo?: string;
43
+ }
44
+ /** The service surface a section is allowed to call. Structural on purpose: the
45
+ * registry never imports the service, so it stays testable with a stub. */
46
+ export interface SectionDataSource {
47
+ getExecutionCount(orgId: string, orgIds?: string[], range?: {
48
+ from?: string;
49
+ to?: string;
50
+ }): Promise<unknown>;
51
+ getSuccessRate(orgId: string, interval: string, from: string, to: string, orgIds?: string[], bucket?: {
52
+ tz?: string;
53
+ weekStart?: 'monday' | 'sunday';
54
+ }): Promise<unknown>;
55
+ getAverageDuration(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
56
+ getStageFailures(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
57
+ getStageBottlenecks(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
58
+ getActionFailures(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
59
+ getErrors(orgId: string, from: string, to: string, limit?: number, orgIds?: string[]): Promise<unknown>;
60
+ getPluginSummary(orgId: string): Promise<unknown>;
61
+ getPluginDistribution(orgId: string): Promise<unknown>;
62
+ getPluginVersions(orgId: string): Promise<unknown>;
63
+ getBuildSuccessRate(orgId: string, interval: string, from: string, to: string, orgIds?: string[], bucket?: {
64
+ tz?: string;
65
+ weekStart?: 'monday' | 'sunday';
66
+ }): Promise<unknown>;
67
+ getBuildDuration(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
68
+ getBuildFailures(orgId: string, from: string, to: string, limit?: number, orgIds?: string[]): Promise<unknown>;
69
+ getDoraMetrics(orgId: string, from: string, to: string, orgIds?: string[], opts?: Record<string, unknown>): Promise<unknown>;
70
+ getDoraTrend(orgId: string, interval: string, from: string, to: string, orgIds?: string[], opts?: Record<string, unknown>): Promise<unknown>;
71
+ getBuildHealth(orgId: string, pipelineId: string, from: string, to: string): Promise<unknown>;
72
+ getReportEnvironments(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
73
+ getPipelineBreakdown(scope: AnalyticsScope, limit?: number): Promise<unknown>;
74
+ getFailureAnalysis(scope: AnalyticsScope, previous?: {
75
+ from: string;
76
+ to: string;
77
+ }): Promise<unknown>;
78
+ getStagePerformance(scope: AnalyticsScope, pipelineId: string, previous?: {
79
+ from: string;
80
+ to: string;
81
+ }): Promise<unknown>;
82
+ getResourceConsumption(scope: AnalyticsScope, limit?: number): Promise<unknown>;
83
+ getPromotionView(scope: AnalyticsScope, productionEnv?: string, limit?: number): Promise<unknown>;
84
+ getOutdatedPlugins(scope: AnalyticsScope, limit?: number): Promise<unknown>;
85
+ getPluginVulnerabilities(scope: AnalyticsScope, limit?: number): Promise<unknown>;
86
+ getAdoption(scope: AnalyticsScope, timeSavedMinutes?: number | null): Promise<unknown>;
87
+ /** Compliance + access posture, over a service-to-service read. Degrades. */
88
+ getCompliancePosture?(scope: AnalyticsScope): Promise<unknown>;
89
+ /** The evaluated needs-attention findings for the period. */
90
+ getNeedsAttention?(scope: AnalyticsScope, previous?: {
91
+ from: string;
92
+ to: string;
93
+ }): Promise<unknown>;
94
+ }
95
+ /** One registered section. */
96
+ export interface SectionSpec {
97
+ id: string;
98
+ /** Manager-facing title. Not a metric name — "Deploys", not "deployment_frequency". */
99
+ title: string;
100
+ /** Feature the org must hold, or undefined for a free section. */
101
+ requiresFeature?: FeatureFlag;
102
+ /**
103
+ * True when this section's value is a single comparable number, so the composer
104
+ * can compute period-over-period change. A list section (top failures) has no
105
+ * meaningful "change", and pretending otherwise produces nonsense arrows.
106
+ */
107
+ comparable: boolean;
108
+ run(source: SectionDataSource, ctx: SectionContext): Promise<unknown>;
109
+ }
110
+ /** Register a section. Throws on a duplicate id or a per-person one. */
111
+ export declare function registerSection(spec: SectionSpec): SectionSpec;
112
+ /** Look a section up; undefined when the id is unknown. */
113
+ export declare function getSection(id: string): SectionSpec | undefined;
114
+ /** Every registered section, in registration order. */
115
+ export declare function allSections(): SectionSpec[];
116
+ /** The ids a caller may put in a definition. */
117
+ export declare function sectionIds(): string[];