@quotivity/cpq-inventory-core 0.1.0 → 0.2.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.
- package/assets/report/assets/index-BaGavxLi.js +9 -0
- package/assets/report/assets/index-ktVHVKok.css +1 -0
- package/assets/report/favicon.svg +6 -0
- package/assets/report/index.html +3 -2
- package/lib/banner.js +1 -1
- package/lib/classify/rules.js +22 -22
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/mapping.js +12 -12
- package/lib/report/assemble.js +3 -3
- package/lib/server.d.ts +1 -0
- package/lib/server.js +14 -1
- package/lib/summary.js +2 -2
- package/lib/types.d.ts +1 -1
- package/lib/waitForExit.d.ts +19 -0
- package/lib/waitForExit.js +43 -0
- package/package.json +1 -1
- package/assets/report/assets/index-CKPi--yh.css +0 -1
- package/assets/report/assets/index-CqUjsqpS.js +0 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:Lexend Deca;src:url(../fonts/LexendDeca-VariableFont_wght.ttf)format("truetype-variations");font-weight:100 900;font-style:normal;font-display:swap}:root{--font-sans:"Lexend Deca", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono:"Andale Mono", ui-monospace, "SF Mono", Menlo, monospace;--page:#fff9f4;--ink:#1c2a23;--ink-strong:#0f2e22;--ink-body:#3c4a42;--ink-soft:#5c6b62;--ink-muted:#6e7c73;--ink-faint:#8a9490;--ink-ghost:#a79c8e;--line:#e6ded4;--line-strong:#ded5c9;--line-soft:#efe8df;--line-faint:#f3ede5;--field-border:#c9bfb2;--dot:#d8cec1;--track:#e9e0d6;--white:#fff;--brand:#047251;--brand-accent:#07ae77;--brand-electric:#00db9a;--brand-tint:#f2faf5;--brand-tint-border:#cfe3d8;--banner-fg:#dcefe5;--clear-fg:#047251;--clear-accent:#07ae77;--partial-path-fg:#a9611c;--partial-path-accent:#d97230;--notarget-fg:#b23a2e;--notarget-accent:#c0392b;--review-fg:#1f6fa8;--review-accent:#3e8dc4;--review-border:#c5d9e8;--review-tint:#f4f8fb;--warn-fg:#8a4a17;--warn-body:#5c4a38;--warn-border:#e8c9a6;--warn-tint:#fdf3e8;--partial:#e0a11b}html,body{background:var(--page);margin:0}body{font-family:var(--font-sans);color:var(--ink);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:300}a{color:var(--brand);text-underline-offset:2px;text-decoration:underline}a:hover{color:var(--ink-strong)}button{font-family:var(--font-sans)}*{box-sizing:border-box}.page{background:var(--page);min-height:100vh}.wrap{max-width:920px;margin:0 auto;padding-inline:32px}.banner{background:var(--ink-strong);color:var(--banner-fg)}.banner .wrap{flex-wrap:wrap;align-items:center;gap:10px;padding-block:7px 8px;display:flex}.banner-text{letter-spacing:.01em;font-size:12px;line-height:1.5}.banner-host{font-family:var(--font-mono);color:var(--white);font-weight:600}.header{z-index:5;background:var(--page);border-bottom:1px solid var(--line);position:sticky;top:0}.header-row{flex-wrap:wrap;align-items:center;gap:20px;padding-block:13px;display:flex}.brand{align-items:center;gap:9px;display:flex}.brand img{width:auto;height:22px;display:block}.brand-sub{color:var(--ink-muted);font-size:13px}.spacer{flex:1;min-width:24px}.learn{color:var(--brand);align-items:center;gap:5px;font-size:14px;font-weight:600;text-decoration:none;display:inline-flex}.rail{flex-wrap:wrap;gap:24px;display:flex}.rail button{cursor:pointer;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);background:0 0;border:0;border-bottom:2px solid #0000;margin:0;padding:0 0 9px;font-size:12px;font-weight:600}.rail button.active{color:var(--ink-strong);border-bottom-color:var(--brand-accent)}.eyebrow{letter-spacing:.1em;text-transform:uppercase;color:var(--brand);margin-bottom:18px;font-size:12px;font-weight:600}.eyebrow.tight{margin-bottom:14px}h1{color:var(--ink-strong);letter-spacing:-.01em;margin:0 0 18px;font-size:32px;font-weight:700;line-height:1.12}h1.hero{letter-spacing:-.015em;text-wrap:pretty;margin-bottom:24px;font-size:40px;line-height:1.08}h1.h28{letter-spacing:0;margin-bottom:14px;font-size:28px;line-height:1.15}h1.h32-14{margin-bottom:14px}h1.h32-20{margin-bottom:20px}.lede{color:var(--ink-body);font-size:16px;line-height:1.6}.lede-17{color:var(--ink-body);gap:13px;font-size:17px;line-height:1.55;display:grid}.lede-grid{color:var(--ink-body);gap:11px;font-size:16px;line-height:1.6;display:grid}.muted-13{color:var(--ink-muted);font-size:13px;line-height:1.65}.muted-14{color:var(--ink-muted);font-size:14px;line-height:1.6}.soft-14{color:var(--ink-soft);font-size:14px;line-height:1.65}.mono{font-family:var(--font-mono)}.btn{background:var(--ink-strong);color:var(--page);cursor:pointer;border:0;border-radius:3px;padding:14px 26px;font-size:15px;font-weight:600;transition:background .12s ease-out}.btn:hover{background:#1a4634}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-outline{border:1px solid var(--field-border);color:var(--ink-strong);cursor:pointer;background:0 0;border-radius:3px;padding:13px 22px;font-size:15px;font-weight:600}.btn-chip{border:1px solid var(--field-border);cursor:pointer;color:var(--ink-strong);background:0 0;border-radius:3px;padding:9px 14px;font-size:13px;font-weight:600}.btn-text{cursor:pointer;color:var(--brand);text-align:left;background:0 0;border:0;margin:0;padding:0;font-size:13px;font-weight:600}.btn-back{cursor:pointer;color:var(--ink-muted);background:0 0;border:0;margin:0;padding:0;font-size:14px;font-weight:600}.box-brand{border:1px solid var(--brand-tint-border);border-left:3px solid var(--brand-accent);background:var(--brand-tint);border-radius:3px;padding:18px 20px}.box-brand.lg{padding:20px 22px}.box-warn{border:1px solid var(--warn-border);border-left:3px solid var(--partial-path-accent);background:var(--warn-tint);border-radius:3px;padding:16px 18px}.box-plain{border:1px solid var(--line);background:var(--white);border-radius:3px;padding:16px 18px}.box-plain.lg{padding:18px 20px}.box-plain.xl{padding:20px 22px}.box-error{border:1px solid #e8b4ae;border-left:3px solid var(--notarget-accent);background:#fdf0ee;border-radius:3px;padding:16px 18px}.checklist{gap:8px;display:grid}.checklist>div{color:var(--ink-body);align-items:baseline;gap:10px;font-size:14px;line-height:1.55;display:flex}.checklist .mark{width:12px;font-family:var(--font-mono);flex:none;font-weight:700}.mark.yes{color:var(--brand)}.mark.no{color:var(--notarget-accent)}.mark.partial{color:var(--partial)}.landing{padding-block:56px 96px}.landing-inner{flex-direction:column;min-height:540px;display:flex}.slide{flex:1}.cmp{border-top:1px solid var(--line-strong)}.cmp-head,.cmp-row{align-items:baseline;gap:16px;padding:9px 0;display:flex}.cmp-head{border-bottom:1px solid var(--line-strong);letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);font-size:11px;font-weight:600}.cmp-row{border-bottom:1px solid var(--line-faint)}.cmp-label{flex:1;min-width:170px}.cmp-row .cmp-label{color:var(--ink-body);font-size:14px;line-height:1.45}.cmp-group{letter-spacing:.08em;text-transform:uppercase;color:var(--ink-strong);padding:15px 0 7px;font-size:11px;font-weight:600}.cmp-hs{text-align:center;flex:none;width:104px;font-size:16px;font-weight:700}.cmp-head .cmp-hs{font-size:11px;font-weight:600}.cmp-q{text-align:center;width:118px;color:var(--brand);background:var(--brand-tint);flex:none;font-size:16px;font-weight:700}.cmp-head .cmp-q{background:0 0;font-size:11px;font-weight:600}.landing-nav{flex-wrap:wrap;align-items:center;gap:20px;margin-top:44px;display:flex}.dots{align-items:center;gap:9px;margin-right:4px;display:flex}.dots button{cursor:pointer;background:var(--dot);border:0;border-radius:4px;flex:none;width:8px;height:8px;margin:0;padding:0;transition:width .2s ease-out,background .2s ease-out}.dots button.active{background:var(--ink-strong);width:26px}.gate{padding-block:76px 104px}.label{color:var(--ink-strong);margin-bottom:6px;font-size:13px;font-weight:600;display:block}.input{border:1px solid var(--field-border);background:var(--white);width:100%;max-width:440px;font-family:var(--font-sans);color:var(--ink);border-radius:3px;outline:none;padding:12px;font-size:15px;font-weight:300}.input:focus{border-color:var(--brand);box-shadow:0 0 0 3px #07ae7726}.input.invalid{border-color:var(--notarget-accent)}.error-13{color:var(--notarget-accent);margin-top:7px;font-size:13px}.gate-foot{border-top:1px solid var(--line);margin-top:34px;padding-top:20px}.name-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;max-width:440px;display:grid}.name-row .input{max-width:none}@media (max-width:520px){.name-row{grid-template-columns:1fr}}.modal-overlay{z-index:20;background:#0f2e2273;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.modal{background:var(--page);border:1px solid var(--line);border-radius:3px;width:100%;max-width:560px;max-height:calc(100vh - 48px);padding:24px 26px 22px;overflow:auto;box-shadow:0 12px 32px #0f2e2233}.modal .input{max-width:none}.modal-title{color:var(--ink-strong);font-size:20px;font-weight:700}.modal-actions{align-items:center;gap:16px;margin-top:22px;display:flex}.input.textarea{resize:vertical;min-height:120px;line-height:1.55}.input.select{appearance:auto;padding:11px 12px}.scanning{padding-block:72px 104px}.progress-track{background:var(--track);border-radius:3px;height:4px;overflow:hidden}.progress-bar{background:var(--brand-accent);height:4px;transition:width .24s ease-out}.scan-log{gap:10px;margin-top:28px;display:grid}.scan-row{align-items:baseline;gap:12px;font-size:14px;display:flex}.scan-row .mark{width:12px;font-family:var(--font-mono);flex:none;font-size:13px}.scan-row .detail{font-family:var(--font-mono);color:var(--ink-faint);font-size:12px}.scan-row.done{color:var(--ink-body)}.scan-row.now{color:var(--ink-strong)}.scan-row.queued{color:var(--ink-ghost)}.stage{padding-block:52px 104px}.chips{flex-wrap:wrap;align-items:center;gap:10px;margin-top:24px;display:flex}.chip{border:1px solid var(--line);background:var(--white);color:var(--ink-body);border-radius:3px;padding:9px 14px;font-size:13px}.chip b{color:var(--ink-strong);font-weight:600}.phases{gap:34px;margin-top:34px;display:grid}.phase-head{border-bottom:1px solid var(--line-strong);align-items:baseline;gap:12px;padding-bottom:9px;display:flex}.phase-name{letter-spacing:.08em;text-transform:uppercase;color:var(--ink-strong);font-size:13px;font-weight:600}.phase-note{color:var(--ink-faint);font-size:13px}.cards{grid-template-columns:repeat(auto-fill,minmax(316px,1fr));align-items:start;gap:14px;margin-top:14px;display:grid}.card{border:1px solid var(--line);background:var(--white);border-radius:3px;flex-direction:column;gap:14px;padding:18px 20px;display:flex}.card-name{color:var(--ink-strong);font-size:16px;font-weight:600}.card-q{color:var(--ink-faint);margin-top:4px;font-size:13px}.metrics{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 22px;display:grid}.metric-v{font-family:var(--font-mono);color:var(--ink-strong);font-size:19px;font-weight:600;line-height:1.1}.metric-v.attention{color:var(--notarget-fg)}.metric-v.zero{color:var(--ink-faint)}.metric-v.review{color:var(--review-fg)}.metric-v.unreliable{color:var(--partial-path-fg);font-size:13px}.metric-k{letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);margin-top:3px;font-size:11px;font-weight:600}.summary{color:var(--ink-body);gap:4px;font-size:14px;line-height:1.55;display:grid}.breakdown{border-top:1px solid var(--line-soft);gap:9px;padding-top:12px;display:grid}.brow{align-items:baseline;gap:10px;display:flex}.brow-label{color:var(--ink-body);font-size:13px}.brow-lands{color:var(--ink-faint);margin-top:2px;font-size:12px}.brow-cross{color:var(--ink-ghost);margin-top:2px;font-size:11px;font-style:italic}.brow-right{text-align:right;flex:none}.brow-count{font-family:var(--font-mono);color:var(--ink-strong);font-size:13px}.brow-count.zero{color:var(--ink-ghost)}.brow-tags{justify-content:flex-end;align-items:baseline;gap:6px;margin-top:2px;display:flex}.shape{letter-spacing:.04em;text-transform:uppercase;color:var(--ink-faint);font-size:10px;font-weight:600}.verdict{font-size:11px;font-weight:600}.v-clear{color:var(--clear-fg)}.v-partial-path{color:var(--partial-path-fg)}.v-notarget{color:var(--notarget-fg)}.v-review{color:var(--review-fg)}.bnote{color:var(--ink-faint);margin-top:2px;font-size:12px;line-height:1.55}.partial-flag{color:var(--partial-path-fg);font-size:11px;font-weight:600}.dense-row{border-bottom:1px solid var(--line-soft);padding:16px 0}.dense-head{flex-wrap:wrap;align-items:baseline;gap:24px;display:flex}.dense-name{flex:none;width:196px}.dense-name b{color:var(--ink-strong);font-size:15px;font-weight:600;display:block}.dense-name span{color:var(--ink-faint);margin-top:3px;font-size:12px;display:block}.dense-summary{min-width:240px;color:var(--ink-body);flex:1;gap:3px;font-size:14px;line-height:1.5;display:grid}.dense-metrics{flex-wrap:wrap;flex:none;gap:16px;width:210px;display:flex}.dense-metrics .metric-v{font-size:15px}.dense-metrics .metric-k{margin-top:2px;font-size:10px}.dense-rows{gap:8px;margin-top:14px;margin-left:220px;display:grid}.drow{align-items:baseline;gap:14px;display:flex}.drow-label{color:var(--ink-body);flex:1;font-size:13px}.drow-label span{color:var(--ink-faint)}.drow-count{text-align:right;width:72px;font-family:var(--font-mono);color:var(--ink-strong);flex:none;font-size:13px}.drow-shape{text-align:right;flex:none;width:58px}.drow-verdict{text-align:right;flex:none;width:98px;font-size:12px;font-weight:600}.tiles{grid-template-columns:repeat(auto-fit,minmax(212px,1fr));gap:12px;margin-top:30px;display:grid}.tile{border:1px solid var(--line);border-left:3px solid var(--ink-strong);background:var(--white);border-radius:3px;padding:14px 16px}.tile-top{align-items:baseline;gap:8px;display:flex}.tile-count{color:var(--ink-strong);font-size:22px;font-weight:700;line-height:1.1}.tile-pct{font-family:var(--font-mono);color:var(--ink-faint);font-size:12px}.tile-label{margin-top:5px;font-size:13px;font-weight:600}.tile-meaning{color:var(--ink-muted);margin-top:3px;font-size:12px;line-height:1.5}.legend{border:1px solid var(--line);background:var(--white);border-radius:3px;flex-wrap:wrap;align-items:baseline;gap:26px;margin-top:12px;padding:14px 18px;display:flex}.legend-title{letter-spacing:.06em;text-transform:uppercase;color:var(--ink-strong);font-size:11px;font-weight:600}.legend-item{align-items:baseline;gap:7px;display:flex}.legend-count{font-family:var(--font-mono);color:var(--ink-strong);font-size:13px;font-weight:600}.legend-label{letter-spacing:.04em;text-transform:uppercase;color:var(--ink-body);font-size:12px;font-weight:600}.legend-meaning{color:var(--ink-faint);font-size:12px}.map{border-top:1px solid var(--line-strong);margin-top:36px}.map-head,.map-row{align-items:baseline;gap:20px;display:flex}.map-head{border-bottom:1px solid var(--line-strong);letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);padding:10px 0;font-size:11px;font-weight:600}.map-row{border-bottom:1px solid var(--line-soft);flex-wrap:wrap;padding:13px 0}.map-bucket{width:150px;color:var(--ink-faint);flex:none;font-size:12px;font-weight:600}.map-from{min-width:170px;color:var(--ink-strong);flex:1.1;font-size:14px;line-height:1.5}.map-count{font-family:var(--font-mono);color:var(--ink-faint);margin-left:6px;font-size:12px}.map-to{min-width:190px;color:var(--ink-body);flex:1.25;font-size:14px;line-height:1.5}.map-shape{text-align:right;flex:none;width:62px}.map-verdict{text-align:right;flex:none;width:104px;font-size:13px;font-weight:600}.panel{border:1px solid var(--line-strong);background:var(--white);border-radius:3px;margin-top:36px;overflow:hidden}.panel.review{border-color:var(--review-border)}.panel-head{border-bottom:1px solid var(--line-soft);padding:18px 22px}.panel.review .panel-head{background:var(--review-tint)}.panel-title{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}.panel-title b{color:var(--ink-strong);font-size:16px;font-weight:700}.panel-count{font-family:var(--font-mono);color:var(--review-fg);font-size:13px;font-weight:600}.panel-sub{color:var(--ink-soft);margin-top:5px;font-size:13px;line-height:1.6}.panel-body{padding:4px 22px 14px}.review-row{border-bottom:1px solid var(--line-faint);padding:16px 0}.review-row:last-child{border-bottom:0}.review-top{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}.reason{letter-spacing:.06em;text-transform:uppercase;color:var(--review-fg);border:1px solid var(--review-border);border-radius:3px;padding:2px 6px;font-size:10px;font-weight:600}.review-subject{color:var(--ink-strong);font-size:14px;font-weight:600}.review-q{color:var(--ink-body);margin-top:9px;font-size:14px;line-height:1.55}.outcomes{gap:5px;margin-top:9px;display:grid}.outcome{color:var(--ink-soft);align-items:baseline;gap:9px;font-size:13px;line-height:1.55;display:flex}.outcome .arrow{font-weight:700}.outcome.a .arrow{color:var(--brand)}.outcome.b .arrow{color:var(--partial-path-fg)}.review-names{font-family:var(--font-mono);color:var(--ink-faint);margin-top:8px;font-size:12px;line-height:1.6}.notes{gap:14px;margin-top:28px;display:grid}.note-head{color:var(--ink-strong);margin-bottom:5px;font-size:14px;font-weight:600}.note-body{color:var(--ink-body);font-size:14px;line-height:1.65}.prereq-row{border-bottom:1px solid var(--line-faint);flex-wrap:wrap;align-items:baseline;gap:16px;padding:11px 0;display:flex}.prereq-field{min-width:200px;font-family:var(--font-mono);color:var(--ink-strong);flex:1;font-size:13px}.prereq-need{min-width:180px;color:var(--ink-soft);flex:1;font-size:13px}.prereq-flag{color:var(--review-fg);flex:none;font-size:11px;font-weight:600}.close{padding-block:76px 120px}.send-title{letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);margin-bottom:12px;font-size:12px;font-weight:600}.actions{flex-wrap:wrap;align-items:center;gap:14px;margin-top:30px;display:flex}.filename{font-family:var(--font-mono);color:var(--ink-faint);margin-top:12px;font-size:12px}.footer{border-top:1px solid var(--line);font-family:var(--font-mono);color:var(--ink-faint);margin-top:48px;padding-top:22px;font-size:12px;line-height:1.7}.confirm-title{color:var(--ink-strong);margin-bottom:5px;font-size:14px;font-weight:600}.confirm-body{color:var(--ink-body);font-size:14px;line-height:1.65}.print-doc .stage{padding-block:32px 40px}.print-ident{font-family:var(--font-mono);color:var(--ink-faint);border-bottom:1px solid var(--line);flex-wrap:wrap;gap:18px;margin-bottom:24px;padding:10px 0;font-size:11px;display:flex}.print-ident b{color:var(--ink-strong);font-weight:600}@page{size:A4;margin:16mm 14mm 18mm}@media print{html,body{background:#fff}*,:before,:after{-webkit-print-color-adjust:exact;-moz-print-color-adjust:exact;print-color-adjust:exact;transition:none!important}.banner,.header,.rail,.no-print,.btn,.btn-outline,.btn-chip,.btn-text,.btn-back,.actions,.dots,.landing-nav{display:none!important}.page{min-height:0}.wrap{max-width:none;padding-inline:0}.stage,.close{padding-block:0 8mm}.breakdown,.dense-rows{display:grid!important}.card,.dense-row,.map-row,.brow,.drow,.review-row,.tile,.box-brand,.box-plain,.note,.prereq-row,.phase-head{break-inside:avoid;page-break-inside:avoid}.phase{break-inside:auto}.cards{grid-template-columns:1fr 1fr}thead{display:table-header-group}tr{break-inside:avoid}h1{break-after:avoid}.stage-break{break-before:page;page-break-before:always}.print-ident{display:flex}a{color:inherit;text-decoration:none}.learn:after{content:""}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="324" height="324" viewBox="0 0 324 324" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<title>Quotivity</title>
|
|
3
|
+
<circle cx="162" cy="162" r="162" fill="#07AE77"/>
|
|
4
|
+
<path d="M70.8835 119.414C91.2643 76.2336 137.49 53.5985 184.442 63.8082C229.519 73.6105 263.011 114.401 262.631 160.909C262.527 173.518 259.56 186.487 255.727 198.6C252.06 210.187 251.023 221.131 253.152 231.653C228.843 202.908 221.016 194.278 207.703 177.81C209.387 177.642 211.146 177.186 212.983 176.418C220.856 173.126 222.767 166.285 222.125 158.421C220.183 134.618 208.508 117.19 186.824 107.389C164.325 97.2181 142.751 100.216 123.76 115.939C105.024 131.451 98.5569 152.025 103.638 175.564C110.534 207.519 142.059 227.612 174.224 220.747C180.858 219.33 185.295 220.542 190.079 224.922C198.064 232.235 206.571 238.978 215.555 246.549C183.676 269.796 133.313 267.224 100.295 241.638C63.273 212.947 50.9347 161.675 70.8835 119.414Z" fill="white"/>
|
|
5
|
+
<path d="M263.123 254.163C264.97 256.79 264.435 261.093 265.002 264.62C261.25 264 256.444 264.657 253.909 262.563C219.275 233.967 184.872 205.089 150.613 176.042C140.788 167.712 139.979 156.235 147.793 148.267C155.783 140.119 167.892 141.047 176.363 150.655C182.04 157.097 187.636 163.625 192.957 170.361C216.193 199.775 219.91 202.649 263.123 254.163Z" fill="white"/>
|
|
6
|
+
</svg>
|
package/assets/report/index.html
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<meta name="referrer" content="no-referrer" />
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
|
|
7
8
|
<title>Quotivity CPQ Inventory</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-BaGavxLi.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="./assets/index-ktVHVKok.css">
|
|
10
11
|
</head>
|
|
11
12
|
<body>
|
|
12
13
|
<div id="root"></div>
|
package/lib/banner.js
CHANGED
|
@@ -8,6 +8,6 @@ export function launchBanner(org) {
|
|
|
8
8
|
].join('\n');
|
|
9
9
|
}
|
|
10
10
|
export function readyLine(url) {
|
|
11
|
-
return `Report served at ${url} —
|
|
11
|
+
return `Report served at ${url} — loopback only, nothing is reachable off this machine.`;
|
|
12
12
|
}
|
|
13
13
|
//# sourceMappingURL=banner.js.map
|
package/lib/classify/rules.js
CHANGED
|
@@ -53,7 +53,7 @@ export const RULE_ROWS = [
|
|
|
53
53
|
discriminator: 'multiple maintained rate periods per currency',
|
|
54
54
|
bucket: 'catalog',
|
|
55
55
|
lands: 'HubSpot holds one current rate per currency. There is no way to author a rate for a named past or future period',
|
|
56
|
-
verdict: '
|
|
56
|
+
verdict: 'Partial path',
|
|
57
57
|
unit: 'currencies',
|
|
58
58
|
mapping: 'catalog.dcrTable',
|
|
59
59
|
}),
|
|
@@ -98,7 +98,7 @@ export const RULE_ROWS = [
|
|
|
98
98
|
discriminator: 'MinOptionCount > 1, or MaxOptionCount is a finite number > 1',
|
|
99
99
|
bucket: 'configuration',
|
|
100
100
|
lands: 'Allow-multiple, plus roll-up + Block to enforce the count',
|
|
101
|
-
verdict: '
|
|
101
|
+
verdict: 'Partial path',
|
|
102
102
|
mapping: 'configuration.minMax',
|
|
103
103
|
}),
|
|
104
104
|
row({
|
|
@@ -119,7 +119,7 @@ export const RULE_ROWS = [
|
|
|
119
119
|
discriminator: 'OptionalSKU is itself configurable',
|
|
120
120
|
bucket: 'configuration',
|
|
121
121
|
lands: '—',
|
|
122
|
-
verdict: 'No
|
|
122
|
+
verdict: 'No path',
|
|
123
123
|
mapping: 'configuration.nested',
|
|
124
124
|
}),
|
|
125
125
|
row({
|
|
@@ -162,7 +162,7 @@ export const RULE_ROWS = [
|
|
|
162
162
|
discriminator: 'Type = Dependency, any other shape',
|
|
163
163
|
bucket: 'configuration',
|
|
164
164
|
lands: 'Add add-on bundle product, or Block',
|
|
165
|
-
verdict: '
|
|
165
|
+
verdict: 'Partial path',
|
|
166
166
|
mapping: 'configuration.dependency',
|
|
167
167
|
}),
|
|
168
168
|
row({
|
|
@@ -183,7 +183,7 @@ export const RULE_ROWS = [
|
|
|
183
183
|
discriminator: 'Type = Filter',
|
|
184
184
|
bucket: 'configuration',
|
|
185
185
|
lands: '—',
|
|
186
|
-
verdict: 'No
|
|
186
|
+
verdict: 'No path',
|
|
187
187
|
mapping: 'configuration.filter',
|
|
188
188
|
}),
|
|
189
189
|
row({
|
|
@@ -204,7 +204,7 @@ export const RULE_ROWS = [
|
|
|
204
204
|
discriminator: 'Type contains Remove or Disable, no replacement',
|
|
205
205
|
bucket: 'configuration',
|
|
206
206
|
lands: '—',
|
|
207
|
-
verdict: 'No
|
|
207
|
+
verdict: 'No path',
|
|
208
208
|
mapping: 'configuration.nested',
|
|
209
209
|
}),
|
|
210
210
|
row({
|
|
@@ -226,7 +226,7 @@ export const RULE_ROWS = [
|
|
|
226
226
|
discriminator: 'TargetObject = Configurator',
|
|
227
227
|
bucket: 'configuration',
|
|
228
228
|
lands: 'Compatibility Rules and Dynamic Property Sets evaluate in the configurator; pricing does not resolve until the bundle lands on the quote',
|
|
229
|
-
verdict: '
|
|
229
|
+
verdict: 'Partial path',
|
|
230
230
|
mapping: 'configuration.dependency',
|
|
231
231
|
}),
|
|
232
232
|
// ── Discovery & capture ─────────────────────────────────────────────────
|
|
@@ -354,7 +354,7 @@ export const RULE_ROWS = [
|
|
|
354
354
|
discriminator: 'formula outside the supported grammar, or over the IF / length / nesting limits',
|
|
355
355
|
bucket: 'price',
|
|
356
356
|
lands: '—',
|
|
357
|
-
verdict: 'No
|
|
357
|
+
verdict: 'No path',
|
|
358
358
|
mapping: 'price.term',
|
|
359
359
|
}),
|
|
360
360
|
row({
|
|
@@ -375,7 +375,7 @@ export const RULE_ROWS = [
|
|
|
375
375
|
discriminator: 'TargetObject = Quote, SourceVariable empty',
|
|
376
376
|
bucket: 'price',
|
|
377
377
|
lands: '— no outcome writes an arbitrary quote property',
|
|
378
|
-
verdict: 'No
|
|
378
|
+
verdict: 'No path',
|
|
379
379
|
mapping: 'price.term',
|
|
380
380
|
}),
|
|
381
381
|
row({
|
|
@@ -386,7 +386,7 @@ export const RULE_ROWS = [
|
|
|
386
386
|
bucket: 'price',
|
|
387
387
|
crossBucket: 'lifecycle',
|
|
388
388
|
lands: 'Ramp pricing',
|
|
389
|
-
verdict: '
|
|
389
|
+
verdict: 'Partial path',
|
|
390
390
|
unit: 'products',
|
|
391
391
|
mapping: 'price.mdq',
|
|
392
392
|
}),
|
|
@@ -398,7 +398,7 @@ export const RULE_ROWS = [
|
|
|
398
398
|
bucket: 'price',
|
|
399
399
|
crossBucket: 'lifecycle',
|
|
400
400
|
lands: 'Stepped periods built with Update Bundle Members rules — the Ramp Pricing feature itself cannot be applied to a bundle',
|
|
401
|
-
verdict: '
|
|
401
|
+
verdict: 'Partial path',
|
|
402
402
|
unit: 'products',
|
|
403
403
|
mapping: 'price.mdq',
|
|
404
404
|
}),
|
|
@@ -409,7 +409,7 @@ export const RULE_ROWS = [
|
|
|
409
409
|
discriminator: 'ChargeType = Usage',
|
|
410
410
|
bucket: 'price',
|
|
411
411
|
lands: '—',
|
|
412
|
-
verdict: 'No
|
|
412
|
+
verdict: 'No path',
|
|
413
413
|
unit: 'products',
|
|
414
414
|
mapping: 'price.term',
|
|
415
415
|
}),
|
|
@@ -432,7 +432,7 @@ export const RULE_ROWS = [
|
|
|
432
432
|
discriminator: 'MDQ segments with differing quantities',
|
|
433
433
|
bucket: 'price',
|
|
434
434
|
lands: 'Quantity is locked across ramp periods — stepped quantity does not carry',
|
|
435
|
-
verdict: '
|
|
435
|
+
verdict: 'Partial path',
|
|
436
436
|
unit: 'dimensions',
|
|
437
437
|
mapping: 'price.mdq',
|
|
438
438
|
}),
|
|
@@ -467,7 +467,7 @@ export const RULE_ROWS = [
|
|
|
467
467
|
discriminator: 'ConstraintField populated',
|
|
468
468
|
bucket: 'follows',
|
|
469
469
|
lands: 'Roll-up conditions compare a line property to a static value, not to a quote property. Needs Update Line Items to stamp the quote value onto lines first, then filter on it',
|
|
470
|
-
verdict: '
|
|
470
|
+
verdict: 'Partial path',
|
|
471
471
|
mapping: 'price.mdq',
|
|
472
472
|
}),
|
|
473
473
|
row({
|
|
@@ -488,7 +488,7 @@ export const RULE_ROWS = [
|
|
|
488
488
|
discriminator: 'TargetObject = Product Option',
|
|
489
489
|
bucket: 'follows',
|
|
490
490
|
lands: 'Aggregates catalog records at configuration time; roll-ups aggregate quote lines',
|
|
491
|
-
verdict: '
|
|
491
|
+
verdict: 'Partial path',
|
|
492
492
|
mapping: 'configuration.dependency',
|
|
493
493
|
}),
|
|
494
494
|
row({
|
|
@@ -498,7 +498,7 @@ export const RULE_ROWS = [
|
|
|
498
498
|
discriminator: 'TargetObject = Asset or Subscription, or Scope = Assets',
|
|
499
499
|
bucket: 'follows',
|
|
500
500
|
lands: '— no asset model',
|
|
501
|
-
verdict: 'No
|
|
501
|
+
verdict: 'No path',
|
|
502
502
|
mapping: 'lifecycle.orders',
|
|
503
503
|
}),
|
|
504
504
|
// ── Discounting ─────────────────────────────────────────────────────────
|
|
@@ -531,7 +531,7 @@ export const RULE_ROWS = [
|
|
|
531
531
|
discriminator: 'AggregationScope = Group',
|
|
532
532
|
bucket: 'discounting',
|
|
533
533
|
lands: 'Roll-ups aggregate quote-wide or per bundle. Line item grouping is a template display feature and carries no functional aggregate',
|
|
534
|
-
verdict: '
|
|
534
|
+
verdict: 'Partial path',
|
|
535
535
|
mapping: 'discounting.group',
|
|
536
536
|
}),
|
|
537
537
|
row({
|
|
@@ -541,7 +541,7 @@ export const RULE_ROWS = [
|
|
|
541
541
|
discriminator: 'Type = Slab',
|
|
542
542
|
bucket: 'discounting',
|
|
543
543
|
lands: 'Volume tiers, converted to per-unit',
|
|
544
|
-
verdict: '
|
|
544
|
+
verdict: 'Partial path',
|
|
545
545
|
mapping: 'discounting.tiers',
|
|
546
546
|
}),
|
|
547
547
|
row({
|
|
@@ -653,7 +653,7 @@ export const RULE_ROWS = [
|
|
|
653
653
|
discriminator: 'referenced by a discount schedule, rule or approval',
|
|
654
654
|
bucket: 'guardrails',
|
|
655
655
|
lands: 'Grouping is display-only; anything that aggregates or gates by group has to be rebuilt on a quote-wide or per-bundle roll-up',
|
|
656
|
-
verdict: '
|
|
656
|
+
verdict: 'Partial path',
|
|
657
657
|
mapping: 'discounting.group',
|
|
658
658
|
}),
|
|
659
659
|
// ── Approvals ───────────────────────────────────────────────────────────
|
|
@@ -675,7 +675,7 @@ export const RULE_ROWS = [
|
|
|
675
675
|
discriminator: 'approver derived from a field',
|
|
676
676
|
bucket: 'approvals',
|
|
677
677
|
lands: 'Workflow routing',
|
|
678
|
-
verdict: '
|
|
678
|
+
verdict: 'Partial path',
|
|
679
679
|
mapping: 'approvals.derived',
|
|
680
680
|
}),
|
|
681
681
|
row({
|
|
@@ -696,7 +696,7 @@ export const RULE_ROWS = [
|
|
|
696
696
|
discriminator: '—',
|
|
697
697
|
bucket: 'approvals',
|
|
698
698
|
lands: 'Rule conditions + roll-ups',
|
|
699
|
-
verdict: '
|
|
699
|
+
verdict: 'Partial path',
|
|
700
700
|
mapping: 'approvals.derived',
|
|
701
701
|
}),
|
|
702
702
|
row({
|
|
@@ -807,7 +807,7 @@ export const RULE_ROWS = [
|
|
|
807
807
|
discriminator: '—',
|
|
808
808
|
bucket: 'lifecycle',
|
|
809
809
|
lands: '—',
|
|
810
|
-
verdict: 'No
|
|
810
|
+
verdict: 'No path',
|
|
811
811
|
mapping: 'lifecycle.orders',
|
|
812
812
|
}),
|
|
813
813
|
// ── Custom code & UI ────────────────────────────────────────────────────
|
package/lib/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from './report/types.js';
|
|
|
19
19
|
export { type RunningServer, type RunStatus, type ServerOptions, startServer } from './server.js';
|
|
20
20
|
export * from './soql.js';
|
|
21
21
|
export * from './types.js';
|
|
22
|
+
export * from './waitForExit.js';
|
|
22
23
|
export interface RunOptions extends Omit<ExtractOptions, 'onProgress'> {
|
|
23
24
|
version: string;
|
|
24
25
|
runId?: string;
|
package/lib/index.js
CHANGED
package/lib/mapping.js
CHANGED
|
@@ -28,7 +28,7 @@ export const MAPPING_ROWS = [
|
|
|
28
28
|
bucket: 'catalog',
|
|
29
29
|
from: 'Dated conversion rates, a maintained rate table',
|
|
30
30
|
to: 'HubSpot holds one current rate per currency — a rate cannot be authored against a period',
|
|
31
|
-
verdict: '
|
|
31
|
+
verdict: 'Partial path',
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
id: 'configuration.bundles',
|
|
@@ -51,28 +51,28 @@ export const MAPPING_ROWS = [
|
|
|
51
51
|
bucket: 'configuration',
|
|
52
52
|
from: 'Option constraints — dependency ("requires")',
|
|
53
53
|
to: 'Already satisfied when the required option is included with a default; otherwise Add add-on or Block',
|
|
54
|
-
verdict: '
|
|
54
|
+
verdict: 'Partial path',
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
id: 'configuration.minMax',
|
|
58
58
|
bucket: 'configuration',
|
|
59
59
|
from: 'Feature min/max option counts',
|
|
60
60
|
to: '"Pick one" and "Pick one or more" are native; a numeric cap needs a roll-up + Block',
|
|
61
|
-
verdict: '
|
|
61
|
+
verdict: 'Partial path',
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
id: 'configuration.nested',
|
|
65
65
|
bucket: 'configuration',
|
|
66
66
|
from: 'Nested bundles',
|
|
67
67
|
to: '—',
|
|
68
|
-
verdict: 'No
|
|
68
|
+
verdict: 'No path',
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
id: 'configuration.filter',
|
|
72
72
|
bucket: 'configuration',
|
|
73
73
|
from: 'Dynamic option sets (filter rules)',
|
|
74
74
|
to: '—',
|
|
75
|
-
verdict: 'No
|
|
75
|
+
verdict: 'No path',
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
id: 'configuration.lookup',
|
|
@@ -118,14 +118,14 @@ export const MAPPING_ROWS = [
|
|
|
118
118
|
bucket: 'price',
|
|
119
119
|
from: 'MDQ / segmented subscription pricing',
|
|
120
120
|
to: 'Ramp pricing; Update Bundle Members rules when bundled. Stepped quantity does not carry',
|
|
121
|
-
verdict: '
|
|
121
|
+
verdict: 'Partial path',
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
id: 'price.term',
|
|
125
125
|
bucket: 'price',
|
|
126
126
|
from: 'Subscription term and proration maths',
|
|
127
127
|
to: '—',
|
|
128
|
-
verdict: 'No
|
|
128
|
+
verdict: 'No path',
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
id: 'price.lookup',
|
|
@@ -155,7 +155,7 @@ export const MAPPING_ROWS = [
|
|
|
155
155
|
bucket: 'discounting',
|
|
156
156
|
from: 'Schedules aggregating across a quote line group',
|
|
157
157
|
to: 'Roll-up quote-wide or per bundle — grouping itself carries no functional aggregate',
|
|
158
|
-
verdict: '
|
|
158
|
+
verdict: 'Partial path',
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
id: 'discounting.contracted',
|
|
@@ -218,7 +218,7 @@ export const MAPPING_ROWS = [
|
|
|
218
218
|
bucket: 'approvals',
|
|
219
219
|
from: 'Derived approvers, escalation',
|
|
220
220
|
to: 'Workflow routing',
|
|
221
|
-
verdict: '
|
|
221
|
+
verdict: 'Partial path',
|
|
222
222
|
},
|
|
223
223
|
{
|
|
224
224
|
id: 'output.groups',
|
|
@@ -249,7 +249,7 @@ export const MAPPING_ROWS = [
|
|
|
249
249
|
bucket: 'lifecycle',
|
|
250
250
|
from: 'Orders, assets',
|
|
251
251
|
to: '—',
|
|
252
|
-
verdict: 'No
|
|
252
|
+
verdict: 'No path',
|
|
253
253
|
},
|
|
254
254
|
{
|
|
255
255
|
id: 'code.custom',
|
|
@@ -261,8 +261,8 @@ export const MAPPING_ROWS = [
|
|
|
261
261
|
];
|
|
262
262
|
export const VERDICT_MEANINGS = {
|
|
263
263
|
'Clear path': 'The behaviour survives and the route to it is known',
|
|
264
|
-
|
|
265
|
-
'No
|
|
264
|
+
'Partial path': 'The behaviour survives in part. The report names what is lost',
|
|
265
|
+
'No path': 'The behaviour does not survive. Needs a redesign or a product change',
|
|
266
266
|
};
|
|
267
267
|
export const SHAPE_MEANINGS = {
|
|
268
268
|
'1:1': 'One record becomes one record',
|
package/lib/report/assemble.js
CHANGED
|
@@ -3,7 +3,7 @@ import { outboundConfig, SOURCE_URL } from '../config/outbound.js';
|
|
|
3
3
|
import { MAPPING_ROWS, SHAPE_MEANINGS, VERDICT_MEANINGS } from '../mapping.js';
|
|
4
4
|
import { bucketNote, summaryLines } from '../summary.js';
|
|
5
5
|
import { BUCKETS, bucketDef, PHASES } from '../types.js';
|
|
6
|
-
const ATTENTION = new Set(['
|
|
6
|
+
const ATTENTION = new Set(['Partial path', 'No path']);
|
|
7
7
|
/** Objects that feed each bucket, for unread and bulk-touch reporting. */
|
|
8
8
|
const OBJECT_BUCKETS = {
|
|
9
9
|
Product2: ['catalog', 'price', 'guardrails'],
|
|
@@ -249,7 +249,7 @@ export function assembleReport(ex, cl, opts) {
|
|
|
249
249
|
count: mappingCounts.get(m.id) ?? 0,
|
|
250
250
|
}));
|
|
251
251
|
const verdicted = mapping.filter((m) => m.verdict !== 'Further review').length;
|
|
252
|
-
const verdictScale = ['Clear path', '
|
|
252
|
+
const verdictScale = ['Clear path', 'Partial path', 'No path'].map((label) => {
|
|
253
253
|
const count = mapping.filter((m) => m.verdict === label).length;
|
|
254
254
|
return {
|
|
255
255
|
label,
|
|
@@ -450,7 +450,7 @@ function buildNotes(ex, cl, rowCount) {
|
|
|
450
450
|
},
|
|
451
451
|
{
|
|
452
452
|
head: 'Ramp pricing has hard edges, and a route around them',
|
|
453
|
-
body: 'A ramp needs at least two periods, applies only to recurring lines, and cannot be applied to a bundle header or an option product. Stepped periods on a bundled product are still buildable with Update Bundle Members rules — what is lost is the native grouping, period badges and MRR handling. Quantity is also locked across ramp periods, which is the most common reason a segmented subscription lands on
|
|
453
|
+
body: 'A ramp needs at least two periods, applies only to recurring lines, and cannot be applied to a bundle header or an option product. Stepped periods on a bundled product are still buildable with Update Bundle Members rules — what is lost is the native grouping, period badges and MRR handling. Quantity is also locked across ramp periods, which is the most common reason a segmented subscription lands on Partial path.',
|
|
454
454
|
},
|
|
455
455
|
{
|
|
456
456
|
head: 'Dated conversion rates: quoting carries, authoring does not',
|
package/lib/server.d.ts
CHANGED
package/lib/server.js
CHANGED
|
@@ -144,7 +144,10 @@ export async function startServer(opts) {
|
|
|
144
144
|
const server = createServer(handler);
|
|
145
145
|
const port = await listen(server, host, opts.port ?? DEFAULT_PORT);
|
|
146
146
|
const address = server.address();
|
|
147
|
-
|
|
147
|
+
// Browsers resolve "localhost" to ::1 or 127.0.0.1 depending on the machine. Bind the IPv6
|
|
148
|
+
// loopback as well, best effort, so http://localhost works either way. Still loopback only.
|
|
149
|
+
const v6 = host === HOST ? await listenLoopbackV6(handler, address.port) : null;
|
|
150
|
+
const url = host === HOST ? `http://localhost:${address.port}/` : `http://${host}:${address.port}/`;
|
|
148
151
|
return {
|
|
149
152
|
server,
|
|
150
153
|
url,
|
|
@@ -153,11 +156,21 @@ export async function startServer(opts) {
|
|
|
153
156
|
for (const s of subscribers)
|
|
154
157
|
s.end();
|
|
155
158
|
subscribers.clear();
|
|
159
|
+
v6?.close();
|
|
156
160
|
server.close(() => resolveClose());
|
|
157
161
|
}),
|
|
158
162
|
state: () => ({ status, error }),
|
|
159
163
|
};
|
|
160
164
|
}
|
|
165
|
+
/** Best-effort second listener on ::1 for the same port; null when IPv6 loopback is unavailable. */
|
|
166
|
+
function listenLoopbackV6(handler, port) {
|
|
167
|
+
return new Promise((resolveV6) => {
|
|
168
|
+
const s = createServer(handler);
|
|
169
|
+
s.once('error', () => resolveV6(null));
|
|
170
|
+
s.once('listening', () => resolveV6(s));
|
|
171
|
+
s.listen(port, '::1');
|
|
172
|
+
});
|
|
173
|
+
}
|
|
161
174
|
/** Binds 127.0.0.1 only. Detects a taken port and increments, up to 20 tries. */
|
|
162
175
|
async function listen(server, host, start) {
|
|
163
176
|
for (let port = start; port < start + 20; port++) {
|
package/lib/summary.js
CHANGED
|
@@ -104,7 +104,7 @@ export function bucketNote(id, s) {
|
|
|
104
104
|
case 'configuration': {
|
|
105
105
|
const nested = s.rowCount('cfg.optionsNested');
|
|
106
106
|
return nested
|
|
107
|
-
? `Nesting has no
|
|
107
|
+
? `Nesting has no path — Quotivity bundles are one level deep, so ${n(nested)} option product${nested === 1 ? '' : 's'} ha${nested === 1 ? 's' : 've'} to be flattened or split.`
|
|
108
108
|
: 'No nested bundles. Every option product is a leaf, which is the shape Quotivity bundles expect.';
|
|
109
109
|
}
|
|
110
110
|
case 'discovery':
|
|
@@ -126,7 +126,7 @@ export function bucketNote(id, s) {
|
|
|
126
126
|
case 'lifecycle':
|
|
127
127
|
return f.amendRenewShareLastYear != null && f.amendRenewShareLastYear >= 0.5
|
|
128
128
|
? 'Renewals and amendments are the majority of quoting volume here, so this bucket carries more weight than its record count suggests.'
|
|
129
|
-
: 'Contracts and subscriptions carry to HubSpot Contracts; orders and assets have no
|
|
129
|
+
: 'Contracts and subscriptions carry to HubSpot Contracts; orders and assets have no path.';
|
|
130
130
|
case 'code': {
|
|
131
131
|
const seeded = Object.entries(ex.seeded)
|
|
132
132
|
.filter(([o]) => /CustomAction|CustomScript/.test(o))
|
package/lib/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Verdict scale from the spec. "Further review" is not a verdict but travels in the same field. */
|
|
2
|
-
export type Verdict = 'Clear path' | '
|
|
2
|
+
export type Verdict = 'Clear path' | 'Partial path' | 'No path' | 'Further review';
|
|
3
3
|
export type Shape = '1:1' | 'Fan-out' | 'Rebuild';
|
|
4
4
|
export type ReviewReason = 'Unread' | 'Intent';
|
|
5
5
|
export type BucketId = 'catalog' | 'configuration' | 'discovery' | 'price' | 'discounting' | 'guardrails' | 'approvals' | 'output' | 'lifecycle' | 'code';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface WaitForExitOptions {
|
|
2
|
+
/** Input stream to watch for Enter. Defaults to process.stdin. */
|
|
3
|
+
input?: NodeJS.ReadableStream & {
|
|
4
|
+
isTTY?: boolean;
|
|
5
|
+
};
|
|
6
|
+
/** Force Enter handling on even when the input is not a TTY (tests). */
|
|
7
|
+
interactive?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export type ExitReason = 'enter' | 'signal';
|
|
10
|
+
/**
|
|
11
|
+
* Resolves when the user presses Enter, or on SIGINT / SIGTERM. Both paths resolve normally so the
|
|
12
|
+
* caller can close the server and return, which lets the process exit with status 0 instead of a
|
|
13
|
+
* thrown exit error and a stack trace.
|
|
14
|
+
*/
|
|
15
|
+
export declare function waitForExit(opts?: WaitForExitOptions): Promise<ExitReason>;
|
|
16
|
+
/** The line printed once the server is up. */
|
|
17
|
+
export declare const EXIT_HINT = "Press ENTER to exit.";
|
|
18
|
+
export declare const STOPPED_LINE = "Stopped. The query results on this machine were never transmitted.";
|
|
19
|
+
//# sourceMappingURL=waitForExit.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const CTRL_C = String.fromCharCode(3);
|
|
2
|
+
/**
|
|
3
|
+
* Resolves when the user presses Enter, or on SIGINT / SIGTERM. Both paths resolve normally so the
|
|
4
|
+
* caller can close the server and return, which lets the process exit with status 0 instead of a
|
|
5
|
+
* thrown exit error and a stack trace.
|
|
6
|
+
*/
|
|
7
|
+
export function waitForExit(opts = {}) {
|
|
8
|
+
const input = opts.input ?? process.stdin;
|
|
9
|
+
const interactive = opts.interactive ?? !!input.isTTY;
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
let done = false;
|
|
12
|
+
const finish = (reason) => {
|
|
13
|
+
if (done)
|
|
14
|
+
return;
|
|
15
|
+
done = true;
|
|
16
|
+
process.removeListener('SIGINT', onSignal);
|
|
17
|
+
process.removeListener('SIGTERM', onSignal);
|
|
18
|
+
if (interactive) {
|
|
19
|
+
input.removeListener('data', onData);
|
|
20
|
+
input.pause();
|
|
21
|
+
}
|
|
22
|
+
resolve(reason);
|
|
23
|
+
};
|
|
24
|
+
const onSignal = () => finish('signal');
|
|
25
|
+
const onData = (chunk) => {
|
|
26
|
+
const text = chunk.toString();
|
|
27
|
+
if (text.includes(CTRL_C))
|
|
28
|
+
finish('signal');
|
|
29
|
+
else if (text.includes('\n') || text.includes('\r'))
|
|
30
|
+
finish('enter');
|
|
31
|
+
};
|
|
32
|
+
process.on('SIGINT', onSignal);
|
|
33
|
+
process.on('SIGTERM', onSignal);
|
|
34
|
+
if (interactive) {
|
|
35
|
+
input.on('data', onData);
|
|
36
|
+
input.resume();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** The line printed once the server is up. */
|
|
41
|
+
export const EXIT_HINT = 'Press ENTER to exit.';
|
|
42
|
+
export const STOPPED_LINE = 'Stopped. The query results on this machine were never transmitted.';
|
|
43
|
+
//# sourceMappingURL=waitForExit.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quotivity/cpq-inventory-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Analysis core for the Quotivity CPQ Inventory & Migration Analysis: extraction, classification, mapping, report assembly and the local report server. Runs entirely on the customer's machine.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@font-face{font-family:Lexend Deca;src:url(../fonts/LexendDeca-VariableFont_wght.ttf)format("truetype-variations");font-weight:100 900;font-style:normal;font-display:swap}:root{--font-sans:"Lexend Deca", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono:"Andale Mono", ui-monospace, "SF Mono", Menlo, monospace;--page:#fff9f4;--ink:#1c2a23;--ink-strong:#0f2e22;--ink-body:#3c4a42;--ink-soft:#5c6b62;--ink-muted:#6e7c73;--ink-faint:#8a9490;--ink-ghost:#a79c8e;--line:#e6ded4;--line-strong:#ded5c9;--line-soft:#efe8df;--line-faint:#f3ede5;--field-border:#c9bfb2;--dot:#d8cec1;--track:#e9e0d6;--white:#fff;--brand:#047251;--brand-accent:#07ae77;--brand-electric:#00db9a;--brand-tint:#f2faf5;--brand-tint-border:#cfe3d8;--banner-fg:#dcefe5;--clear-fg:#047251;--clear-accent:#07ae77;--degraded-fg:#a9611c;--degraded-accent:#d97230;--notarget-fg:#b23a2e;--notarget-accent:#c0392b;--review-fg:#1f6fa8;--review-accent:#3e8dc4;--review-border:#c5d9e8;--review-tint:#f4f8fb;--warn-fg:#8a4a17;--warn-body:#5c4a38;--warn-border:#e8c9a6;--warn-tint:#fdf3e8;--partial:#e0a11b}html,body{background:var(--page);margin:0}body{font-family:var(--font-sans);color:var(--ink);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:300}a{color:var(--brand);text-underline-offset:2px;text-decoration:underline}a:hover{color:var(--ink-strong)}button{font-family:var(--font-sans)}*{box-sizing:border-box}.page{background:var(--page);min-height:100vh}.wrap{max-width:920px;margin:0 auto;padding-inline:32px}.banner{background:var(--ink-strong);color:var(--banner-fg)}.banner .wrap{flex-wrap:wrap;align-items:center;gap:10px;padding-block:7px 8px;display:flex}.banner-text{letter-spacing:.01em;font-size:12px;line-height:1.5}.banner-host{font-family:var(--font-mono);color:var(--white);font-weight:600}.header{z-index:5;background:var(--page);border-bottom:1px solid var(--line);position:sticky;top:0}.header-row{flex-wrap:wrap;align-items:center;gap:20px;padding-block:13px;display:flex}.brand{align-items:center;gap:9px;display:flex}.brand img{width:auto;height:22px;display:block}.brand-sub{color:var(--ink-muted);font-size:13px}.spacer{flex:1;min-width:24px}.learn{color:var(--brand);align-items:center;gap:5px;font-size:14px;font-weight:600;text-decoration:none;display:inline-flex}.rail{flex-wrap:wrap;gap:24px;display:flex}.rail button{cursor:pointer;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);background:0 0;border:0;border-bottom:2px solid #0000;margin:0;padding:0 0 9px;font-size:12px;font-weight:600}.rail button.active{color:var(--ink-strong);border-bottom-color:var(--brand-accent)}.eyebrow{letter-spacing:.1em;text-transform:uppercase;color:var(--brand);margin-bottom:18px;font-size:12px;font-weight:600}.eyebrow.tight{margin-bottom:14px}h1{color:var(--ink-strong);letter-spacing:-.01em;margin:0 0 18px;font-size:32px;font-weight:700;line-height:1.12}h1.hero{letter-spacing:-.015em;text-wrap:pretty;margin-bottom:24px;font-size:40px;line-height:1.08}h1.h28{letter-spacing:0;margin-bottom:14px;font-size:28px;line-height:1.15}h1.h32-14{margin-bottom:14px}h1.h32-20{margin-bottom:20px}.lede{color:var(--ink-body);font-size:16px;line-height:1.6}.lede-17{color:var(--ink-body);gap:13px;font-size:17px;line-height:1.55;display:grid}.lede-grid{color:var(--ink-body);gap:11px;font-size:16px;line-height:1.6;display:grid}.muted-13{color:var(--ink-muted);font-size:13px;line-height:1.65}.muted-14{color:var(--ink-muted);font-size:14px;line-height:1.6}.soft-14{color:var(--ink-soft);font-size:14px;line-height:1.65}.mono{font-family:var(--font-mono)}.btn{background:var(--ink-strong);color:var(--page);cursor:pointer;border:0;border-radius:3px;padding:14px 26px;font-size:15px;font-weight:600;transition:background .12s ease-out}.btn:hover{background:#1a4634}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-outline{border:1px solid var(--field-border);color:var(--ink-strong);cursor:pointer;background:0 0;border-radius:3px;padding:13px 22px;font-size:15px;font-weight:600}.btn-chip{border:1px solid var(--field-border);cursor:pointer;color:var(--ink-strong);background:0 0;border-radius:3px;padding:9px 14px;font-size:13px;font-weight:600}.btn-text{cursor:pointer;color:var(--brand);text-align:left;background:0 0;border:0;margin:0;padding:0;font-size:13px;font-weight:600}.btn-back{cursor:pointer;color:var(--ink-muted);background:0 0;border:0;margin:0;padding:0;font-size:14px;font-weight:600}.box-brand{border:1px solid var(--brand-tint-border);border-left:3px solid var(--brand-accent);background:var(--brand-tint);border-radius:3px;padding:18px 20px}.box-brand.lg{padding:20px 22px}.box-warn{border:1px solid var(--warn-border);border-left:3px solid var(--degraded-accent);background:var(--warn-tint);border-radius:3px;padding:16px 18px}.box-plain{border:1px solid var(--line);background:var(--white);border-radius:3px;padding:16px 18px}.box-plain.lg{padding:18px 20px}.box-plain.xl{padding:20px 22px}.box-error{border:1px solid #e8b4ae;border-left:3px solid var(--notarget-accent);background:#fdf0ee;border-radius:3px;padding:16px 18px}.checklist{gap:8px;display:grid}.checklist>div{color:var(--ink-body);align-items:baseline;gap:10px;font-size:14px;line-height:1.55;display:flex}.checklist .mark{width:12px;font-family:var(--font-mono);flex:none;font-weight:700}.mark.yes{color:var(--brand)}.mark.no{color:var(--notarget-accent)}.mark.partial{color:var(--partial)}.landing{padding-block:56px 96px}.landing-inner{flex-direction:column;min-height:540px;display:flex}.slide{flex:1}.cmp{border-top:1px solid var(--line-strong)}.cmp-head,.cmp-row{align-items:baseline;gap:16px;padding:9px 0;display:flex}.cmp-head{border-bottom:1px solid var(--line-strong);letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);font-size:11px;font-weight:600}.cmp-row{border-bottom:1px solid var(--line-faint)}.cmp-label{flex:1;min-width:170px}.cmp-row .cmp-label{color:var(--ink-body);font-size:14px;line-height:1.45}.cmp-group{letter-spacing:.08em;text-transform:uppercase;color:var(--ink-strong);padding:15px 0 7px;font-size:11px;font-weight:600}.cmp-hs{text-align:center;flex:none;width:104px;font-size:16px;font-weight:700}.cmp-head .cmp-hs{font-size:11px;font-weight:600}.cmp-q{text-align:center;width:118px;color:var(--brand);background:var(--brand-tint);flex:none;font-size:16px;font-weight:700}.cmp-head .cmp-q{background:0 0;font-size:11px;font-weight:600}.landing-nav{flex-wrap:wrap;align-items:center;gap:20px;margin-top:44px;display:flex}.dots{align-items:center;gap:9px;margin-right:4px;display:flex}.dots button{cursor:pointer;background:var(--dot);border:0;border-radius:4px;flex:none;width:8px;height:8px;margin:0;padding:0;transition:width .2s ease-out,background .2s ease-out}.dots button.active{background:var(--ink-strong);width:26px}.gate{padding-block:76px 104px}.label{color:var(--ink-strong);margin-bottom:6px;font-size:13px;font-weight:600;display:block}.input{border:1px solid var(--field-border);background:var(--white);width:100%;max-width:440px;font-family:var(--font-sans);color:var(--ink);border-radius:3px;outline:none;padding:12px;font-size:15px;font-weight:300}.input:focus{border-color:var(--brand);box-shadow:0 0 0 3px #07ae7726}.input.invalid{border-color:var(--notarget-accent)}.error-13{color:var(--notarget-accent);margin-top:7px;font-size:13px}.gate-foot{border-top:1px solid var(--line);margin-top:34px;padding-top:20px}.name-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;max-width:440px;display:grid}.name-row .input{max-width:none}@media (max-width:520px){.name-row{grid-template-columns:1fr}}.scanning{padding-block:72px 104px}.progress-track{background:var(--track);border-radius:3px;height:4px;overflow:hidden}.progress-bar{background:var(--brand-accent);height:4px;transition:width .24s ease-out}.scan-log{gap:10px;margin-top:28px;display:grid}.scan-row{align-items:baseline;gap:12px;font-size:14px;display:flex}.scan-row .mark{width:12px;font-family:var(--font-mono);flex:none;font-size:13px}.scan-row .detail{font-family:var(--font-mono);color:var(--ink-faint);font-size:12px}.scan-row.done{color:var(--ink-body)}.scan-row.now{color:var(--ink-strong)}.scan-row.queued{color:var(--ink-ghost)}.stage{padding-block:52px 104px}.chips{flex-wrap:wrap;align-items:center;gap:10px;margin-top:24px;display:flex}.chip{border:1px solid var(--line);background:var(--white);color:var(--ink-body);border-radius:3px;padding:9px 14px;font-size:13px}.chip b{color:var(--ink-strong);font-weight:600}.phases{gap:34px;margin-top:34px;display:grid}.phase-head{border-bottom:1px solid var(--line-strong);align-items:baseline;gap:12px;padding-bottom:9px;display:flex}.phase-name{letter-spacing:.08em;text-transform:uppercase;color:var(--ink-strong);font-size:13px;font-weight:600}.phase-note{color:var(--ink-faint);font-size:13px}.cards{grid-template-columns:repeat(auto-fill,minmax(316px,1fr));align-items:start;gap:14px;margin-top:14px;display:grid}.card{border:1px solid var(--line);background:var(--white);border-radius:3px;flex-direction:column;gap:14px;padding:18px 20px;display:flex}.card-name{color:var(--ink-strong);font-size:16px;font-weight:600}.card-q{color:var(--ink-faint);margin-top:4px;font-size:13px}.metrics{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 22px;display:grid}.metric-v{font-family:var(--font-mono);color:var(--ink-strong);font-size:19px;font-weight:600;line-height:1.1}.metric-v.attention{color:var(--notarget-fg)}.metric-v.zero{color:var(--ink-faint)}.metric-v.review{color:var(--review-fg)}.metric-v.unreliable{color:var(--degraded-fg);font-size:13px}.metric-k{letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);margin-top:3px;font-size:11px;font-weight:600}.summary{color:var(--ink-body);gap:4px;font-size:14px;line-height:1.55;display:grid}.breakdown{border-top:1px solid var(--line-soft);gap:9px;padding-top:12px;display:grid}.brow{align-items:baseline;gap:10px;display:flex}.brow-label{color:var(--ink-body);font-size:13px}.brow-lands{color:var(--ink-faint);margin-top:2px;font-size:12px}.brow-cross{color:var(--ink-ghost);margin-top:2px;font-size:11px;font-style:italic}.brow-right{text-align:right;flex:none}.brow-count{font-family:var(--font-mono);color:var(--ink-strong);font-size:13px}.brow-count.zero{color:var(--ink-ghost)}.brow-tags{justify-content:flex-end;align-items:baseline;gap:6px;margin-top:2px;display:flex}.shape{letter-spacing:.04em;text-transform:uppercase;color:var(--ink-faint);font-size:10px;font-weight:600}.verdict{font-size:11px;font-weight:600}.v-clear{color:var(--clear-fg)}.v-degraded{color:var(--degraded-fg)}.v-notarget{color:var(--notarget-fg)}.v-review{color:var(--review-fg)}.bnote{color:var(--ink-faint);margin-top:2px;font-size:12px;line-height:1.55}.partial-flag{color:var(--degraded-fg);font-size:11px;font-weight:600}.dense-row{border-bottom:1px solid var(--line-soft);padding:16px 0}.dense-head{flex-wrap:wrap;align-items:baseline;gap:24px;display:flex}.dense-name{flex:none;width:196px}.dense-name b{color:var(--ink-strong);font-size:15px;font-weight:600;display:block}.dense-name span{color:var(--ink-faint);margin-top:3px;font-size:12px;display:block}.dense-summary{min-width:240px;color:var(--ink-body);flex:1;gap:3px;font-size:14px;line-height:1.5;display:grid}.dense-metrics{flex-wrap:wrap;flex:none;gap:16px;width:210px;display:flex}.dense-metrics .metric-v{font-size:15px}.dense-metrics .metric-k{margin-top:2px;font-size:10px}.dense-rows{gap:8px;margin-top:14px;margin-left:220px;display:grid}.drow{align-items:baseline;gap:14px;display:flex}.drow-label{color:var(--ink-body);flex:1;font-size:13px}.drow-label span{color:var(--ink-faint)}.drow-count{text-align:right;width:72px;font-family:var(--font-mono);color:var(--ink-strong);flex:none;font-size:13px}.drow-shape{text-align:right;flex:none;width:58px}.drow-verdict{text-align:right;flex:none;width:98px;font-size:12px;font-weight:600}.tiles{grid-template-columns:repeat(auto-fit,minmax(212px,1fr));gap:12px;margin-top:30px;display:grid}.tile{border:1px solid var(--line);border-left:3px solid var(--ink-strong);background:var(--white);border-radius:3px;padding:14px 16px}.tile-top{align-items:baseline;gap:8px;display:flex}.tile-count{color:var(--ink-strong);font-size:22px;font-weight:700;line-height:1.1}.tile-pct{font-family:var(--font-mono);color:var(--ink-faint);font-size:12px}.tile-label{margin-top:5px;font-size:13px;font-weight:600}.tile-meaning{color:var(--ink-muted);margin-top:3px;font-size:12px;line-height:1.5}.legend{border:1px solid var(--line);background:var(--white);border-radius:3px;flex-wrap:wrap;align-items:baseline;gap:26px;margin-top:12px;padding:14px 18px;display:flex}.legend-title{letter-spacing:.06em;text-transform:uppercase;color:var(--ink-strong);font-size:11px;font-weight:600}.legend-item{align-items:baseline;gap:7px;display:flex}.legend-count{font-family:var(--font-mono);color:var(--ink-strong);font-size:13px;font-weight:600}.legend-label{letter-spacing:.04em;text-transform:uppercase;color:var(--ink-body);font-size:12px;font-weight:600}.legend-meaning{color:var(--ink-faint);font-size:12px}.map{border-top:1px solid var(--line-strong);margin-top:36px}.map-head,.map-row{align-items:baseline;gap:20px;display:flex}.map-head{border-bottom:1px solid var(--line-strong);letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);padding:10px 0;font-size:11px;font-weight:600}.map-row{border-bottom:1px solid var(--line-soft);flex-wrap:wrap;padding:13px 0}.map-bucket{width:150px;color:var(--ink-faint);flex:none;font-size:12px;font-weight:600}.map-from{min-width:170px;color:var(--ink-strong);flex:1.1;font-size:14px;line-height:1.5}.map-count{font-family:var(--font-mono);color:var(--ink-faint);margin-left:6px;font-size:12px}.map-to{min-width:190px;color:var(--ink-body);flex:1.25;font-size:14px;line-height:1.5}.map-shape{text-align:right;flex:none;width:62px}.map-verdict{text-align:right;flex:none;width:104px;font-size:13px;font-weight:600}.panel{border:1px solid var(--line-strong);background:var(--white);border-radius:3px;margin-top:36px;overflow:hidden}.panel.review{border-color:var(--review-border)}.panel-head{border-bottom:1px solid var(--line-soft);padding:18px 22px}.panel.review .panel-head{background:var(--review-tint)}.panel-title{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}.panel-title b{color:var(--ink-strong);font-size:16px;font-weight:700}.panel-count{font-family:var(--font-mono);color:var(--review-fg);font-size:13px;font-weight:600}.panel-sub{color:var(--ink-soft);margin-top:5px;font-size:13px;line-height:1.6}.panel-body{padding:4px 22px 14px}.review-row{border-bottom:1px solid var(--line-faint);padding:16px 0}.review-row:last-child{border-bottom:0}.review-top{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}.reason{letter-spacing:.06em;text-transform:uppercase;color:var(--review-fg);border:1px solid var(--review-border);border-radius:3px;padding:2px 6px;font-size:10px;font-weight:600}.review-subject{color:var(--ink-strong);font-size:14px;font-weight:600}.review-q{color:var(--ink-body);margin-top:9px;font-size:14px;line-height:1.55}.outcomes{gap:5px;margin-top:9px;display:grid}.outcome{color:var(--ink-soft);align-items:baseline;gap:9px;font-size:13px;line-height:1.55;display:flex}.outcome .arrow{font-weight:700}.outcome.a .arrow{color:var(--brand)}.outcome.b .arrow{color:var(--degraded-fg)}.review-names{font-family:var(--font-mono);color:var(--ink-faint);margin-top:8px;font-size:12px;line-height:1.6}.notes{gap:14px;margin-top:28px;display:grid}.note-head{color:var(--ink-strong);margin-bottom:5px;font-size:14px;font-weight:600}.note-body{color:var(--ink-body);font-size:14px;line-height:1.65}.prereq-row{border-bottom:1px solid var(--line-faint);flex-wrap:wrap;align-items:baseline;gap:16px;padding:11px 0;display:flex}.prereq-field{min-width:200px;font-family:var(--font-mono);color:var(--ink-strong);flex:1;font-size:13px}.prereq-need{min-width:180px;color:var(--ink-soft);flex:1;font-size:13px}.prereq-flag{color:var(--review-fg);flex:none;font-size:11px;font-weight:600}.close{padding-block:76px 120px}.send-title{letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);margin-bottom:12px;font-size:12px;font-weight:600}.actions{flex-wrap:wrap;align-items:center;gap:14px;margin-top:30px;display:flex}.filename{font-family:var(--font-mono);color:var(--ink-faint);margin-top:12px;font-size:12px}.footer{border-top:1px solid var(--line);font-family:var(--font-mono);color:var(--ink-faint);margin-top:48px;padding-top:22px;font-size:12px;line-height:1.7}.confirm-title{color:var(--ink-strong);margin-bottom:5px;font-size:14px;font-weight:600}.confirm-body{color:var(--ink-body);font-size:14px;line-height:1.65}.print-doc .stage{padding-block:32px 40px}.print-ident{font-family:var(--font-mono);color:var(--ink-faint);border-bottom:1px solid var(--line);flex-wrap:wrap;gap:18px;margin-bottom:24px;padding:10px 0;font-size:11px;display:flex}.print-ident b{color:var(--ink-strong);font-weight:600}@page{size:A4;margin:16mm 14mm 18mm}@media print{html,body{background:#fff}*,:before,:after{-webkit-print-color-adjust:exact;-moz-print-color-adjust:exact;print-color-adjust:exact;transition:none!important}.banner,.header,.rail,.no-print,.btn,.btn-outline,.btn-chip,.btn-text,.btn-back,.actions,.dots,.landing-nav{display:none!important}.page{min-height:0}.wrap{max-width:none;padding-inline:0}.stage,.close{padding-block:0 8mm}.breakdown,.dense-rows{display:grid!important}.card,.dense-row,.map-row,.brow,.drow,.review-row,.tile,.box-brand,.box-plain,.note,.prereq-row,.phase-head{break-inside:avoid;page-break-inside:avoid}.phase{break-inside:auto}.cards{grid-template-columns:1fr 1fr}thead{display:table-header-group}tr{break-inside:avoid}h1{break-after:avoid}.stage-break{break-before:page;page-break-before:always}.print-ident{display:flex}a{color:inherit;text-decoration:none}.learn:after{content:""}}
|