Merge branch 'master' into mobile-sidebar

This commit is contained in:
Philipp Hugenroth
2022-01-07 16:41:44 +01:00
53 changed files with 2016 additions and 298 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-todo-backend': patch
---
Properly exported all referenced types
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Add catalog permission rules.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Switch Webpack minification to use `esbuild` instead of `terser`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Add apply-conditions endpoint for evaluating conditional permissions in catalog backend.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Fix an issue where the TechDocs sidebar is hidden when the Backstage sidebar is pinned at smaller screen sizes
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes-backend': patch
---
Exclude the AWS session token from credential validation, because it's not necessary in this context.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-tech-insights': patch
---
fix React warning because of missing `key` prop
@@ -4,6 +4,14 @@ on:
paths:
- '.github/workflows/chromatic-storybook-test.yml'
- 'packages/storybook/**'
- 'packages/config/src/**'
- 'packages/theme/src/**'
- 'packages/types/src/**'
- 'packages/errors/src/**'
- 'packages/version-bridge/src/**'
- 'packages/test-utils/src/**'
- 'packages/core-app-api/src/**'
- 'packages/core-plugin-api/src/**'
- 'packages/core-components/src/**'
- '**/*.stories.tsx'
@@ -1 +0,0 @@
<mxfile host="Chrome" modified="2021-12-30T23:10:43.723Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36" etag="XmQE_awyVPeS9B3bWnM_" version="15.5.6" type="device"><diagram id="M4OCM2KiCGRnt6vHj1W_" name="Page-1">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</diagram></mxfile>
@@ -0,0 +1,746 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1261px" height="642px" viewBox="-0.5 -0.5 1261 642" content="&lt;mxfile&gt;&lt;diagram id=&quot;M4OCM2KiCGRnt6vHj1W_&quot; name=&quot;Page-1&quot;&gt;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&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(255, 255, 255);">
<defs/>
<g>
<rect x="0" y="0" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="all"/>
<rect x="180" y="500" width="100" height="130" fill="none" stroke="#000000" pointer-events="all"/>
<path d="M 160 245 L 253.88 245" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 258.88 245 L 253.88 246.67 L 253.88 243.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 120 230 Q 120 230 120 136.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 120 131.12 L 121.67 136.12 L 118.33 136.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 120 260 Q 120 305 143.88 305" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 148.88 305 L 143.88 306.67 L 143.88 303.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 140 230 Q 140 137.5 243.88 137.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 248.88 137.5 L 243.88 139.17 L 243.88 135.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="80" y="230" width="80" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 70px; height: 1px; padding-top: 245px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
app
</div>
</div>
</div>
</foreignObject>
<text x="90" y="249" fill="#000000" font-family="Helvetica" font-size="12px">
app
</text>
</switch>
</g>
<path d="M 120 390 Q 120 345 143.88 345" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 148.88 345 L 143.88 346.67 L 143.88 343.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 160 405 L 253.88 405" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 258.88 405 L 253.88 406.67 L 253.88 403.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 140 420 Q 140 450 347.5 450 Q 555 450 555 473.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 555 478.88 L 553.33 473.88 L 556.67 473.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="80" y="390" width="80" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 70px; height: 1px; padding-top: 405px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
backend
</div>
</div>
</div>
</foreignObject>
<text x="90" y="409" fill="#000000" font-family="Helvetica" font-size="12px">
backend
</text>
</switch>
</g>
<path d="M 387.5 230 Q 387.5 205 378.75 205 Q 370 205 370 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 370 181.12 L 371.67 186.12 L 368.33 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 430 245 Q 475 245 475 266.25 Q 475 287.5 513.88 287.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 518.88 287.5 L 513.88 289.17 L 513.88 285.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 430 237.5 Q 585 237.5 585 233.75 Q 585 230 690 230 Q 795 230 795 243.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 795 248.88 L 793.33 243.88 L 796.67 243.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="260" y="230" width="170" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 160px; height: 1px; padding-top: 245px; margin-left: 270px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;
</div>
</div>
</div>
</foreignObject>
<text x="270" y="249" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;
</text>
</switch>
</g>
<path d="M 387.5 420 Q 387.5 555 493.88 555" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 498.88 555 L 493.88 556.67 L 493.88 553.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 430 412.5 Q 585 412.5 585 416.25 Q 585 420 690 420 Q 795 420 795 406.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 795 401.12 L 796.67 406.12 L 793.33 406.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 430 405 Q 475 405 475 383.75 Q 475 362.5 513.88 362.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 518.88 362.5 L 513.88 364.17 L 513.88 360.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<rect x="260" y="390" width="170" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 160px; height: 1px; padding-top: 405px; margin-left: 270px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-backend
</div>
</div>
</div>
</foreignObject>
<text x="270" y="409" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-backend
</text>
</switch>
</g>
<path d="M 500 480 L 500 480 L 720 480 L 720 480" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 500 480 L 500 630 L 720 630 L 720 480" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 500 480 L 720 480" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="609.5" y="497.5">
Backend Libraries
</text>
</g>
<rect x="520" y="510" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 525px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/backend-common
</div>
</div>
</div>
</foreignObject>
<text x="530" y="529" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/backend-common
</text>
</switch>
</g>
<rect x="520" y="550" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 565px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/backend-test-utils
</div>
</div>
</div>
</foreignObject>
<text x="530" y="569" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/backend-test-utils
</text>
</switch>
</g>
<rect x="520" y="590" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 605px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/backend-tasks
</div>
</div>
</div>
</foreignObject>
<text x="530" y="609" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/backend-tasks
</text>
</switch>
</g>
<path d="M 1030 250 L 1030 250 L 1250 250 L 1250 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1030 250 L 1030 520 L 1250 520 L 1250 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1030 250 L 1250 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="1139.5" y="267.5">
Common Libraries
</text>
</g>
<rect x="1050" y="440" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 455px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/catalog-client
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="459" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/catalog-client
</text>
</switch>
</g>
<rect x="1050" y="280" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 295px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/types
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="299" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/types
</text>
</switch>
</g>
<rect x="1050" y="320" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 335px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/config
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="339" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/config
</text>
</switch>
</g>
<rect x="1050" y="360" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 375px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/errors
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="379" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/errors
</text>
</switch>
</g>
<rect x="1050" y="400" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 415px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/catalog-model
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="419" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/catalog-model
</text>
</switch>
</g>
<rect x="1050" y="480" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 495px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/integration
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="499" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/integration
</text>
</switch>
</g>
<path d="M 10 20 L 10 20 L 230 20 L 230 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 10 20 L 10 130 L 230 130 L 230 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 10 20 L 230 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="119.5" y="37.5">
Frontend App Core
</text>
</g>
<rect x="30" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 40px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/core-app-api
</div>
</div>
</div>
</foreignObject>
<text x="40" y="69" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/core-app-api
</text>
</switch>
</g>
<rect x="30" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 40px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/app-defaults
</div>
</div>
</div>
</foreignObject>
<text x="40" y="109" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/app-defaults
</text>
</switch>
</g>
<path d="M 988 305 Q 1010 305 1010 345 Q 1010 385 1023.88 385" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1028.88 385 L 1023.88 386.67 L 1023.88 383.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 988 305 Q 1010 305 1010 250 Q 1010 195 1023.88 195" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1028.88 195 L 1023.88 196.67 L 1023.88 193.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 990 10 L 985 10 Q 980 10 980 20 L 980 295 Q 980 305 975 305 L 972.5 305 Q 970 305 975 305 L 977.5 305 Q 980 305 980 315 L 980 590 Q 980 600 985 600 L 990 600" fill="none" stroke="#000000" stroke-miterlimit="10" transform="translate(980,0)scale(-1,1)translate(-980,0)" pointer-events="none"/>
<path d="M 430 345 Q 475 345 475 353.75 Q 475 362.5 513.88 362.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 518.88 362.5 L 513.88 364.17 L 513.88 360.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 430 352.5 Q 465 352.5 465 435 Q 465 517.5 493.88 517.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 498.88 517.5 L 493.88 519.17 L 493.88 515.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="150" y="330" width="280" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 270px; height: 1px; padding-top: 345px; margin-left: 160px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-backend-module-&lt;module-id&gt;
</div>
</div>
</div>
</foreignObject>
<text x="160" y="349" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-backend-module-&lt;module-id&gt;
</text>
</switch>
</g>
<path d="M 430 305 Q 475 305 475 296.25 Q 475 287.5 513.88 287.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 518.88 287.5 L 513.88 289.17 L 513.88 285.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 430 297.5 Q 464.08 297.5 464.08 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 464.08 181.12 L 465.75 186.12 L 462.41 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="150" y="290" width="280" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 270px; height: 1px; padding-top: 305px; margin-left: 160px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-module-&lt;module-id&gt;
</div>
</div>
</div>
</foreignObject>
<text x="160" y="309" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-module-&lt;module-id&gt;
</text>
</switch>
</g>
<path d="M 1030 160 L 1030 160 L 1250 160 L 1250 160" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1030 160 L 1030 230 L 1250 230 L 1250 160" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1030 160 L 1250 160" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="1139.5" y="177.5">
Common Tooling
</text>
</g>
<rect x="1050" y="190" width="180" height="30" fill="#e1d5e7" stroke="#9673a6" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 205px; margin-left: 1060px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/cli
</div>
</div>
</div>
</foreignObject>
<text x="1060" y="209" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/cli
</text>
</switch>
</g>
<path d="M 850 400 Q 850 555 726.12 555" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 721.12 555 L 726.12 553.33 L 726.12 556.67 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 850 250 Q 850 95 736.12 95" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 731.12 95 L 736.12 93.33 L 736.12 96.67 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 740 250 L 740 250 L 960 250 L 960 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 740 250 L 740 400 L 960 400 L 960 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 740 250 L 960 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="849.5" y="267.5">
External Plugin Libraries
</text>
</g>
<rect x="750" y="280" width="200" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 295px; margin-left: 760px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
plugin-&lt;other-plugin-id&gt;-react
</div>
</div>
</div>
</foreignObject>
<text x="760" y="299" fill="#000000" font-family="Helvetica" font-size="12px">
plugin-&lt;other-plugin-id&gt;-react
</text>
</switch>
</g>
<rect x="750" y="320" width="200" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 335px; margin-left: 760px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
plugin-&lt;other-plugin-id&gt;-common
</div>
</div>
</div>
</foreignObject>
<text x="760" y="339" fill="#000000" font-family="Helvetica" font-size="12px">
plugin-&lt;other-plugin-id&gt;-common
</text>
</switch>
</g>
<rect x="750" y="360" width="200" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 375px; margin-left: 760px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
plugin-&lt;other-plugin-id&gt;-node
</div>
</div>
</div>
</foreignObject>
<text x="760" y="379" fill="#000000" font-family="Helvetica" font-size="12px">
plugin-&lt;other-plugin-id&gt;-node
</text>
</switch>
</g>
<path d="M 700 325 Q 700 325 733.88 325" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 738.88 325 L 733.88 326.67 L 733.88 323.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 610 400 Q 610 400 610 473.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 610 478.88 L 608.33 473.88 L 611.67 473.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 610 250 Q 610 250 610 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 610 181.12 L 611.67 186.12 L 608.33 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 520 250 L 520 250 L 700 250 L 700 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 520 250 L 520 400 L 700 400 L 700 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 520 250 L 700 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="609.5" y="267.5">
Plugin Libraries
</text>
</g>
<rect x="530" y="280" width="160" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 295px; margin-left: 540px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-react
</div>
</div>
</div>
</foreignObject>
<text x="540" y="299" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-react
</text>
</switch>
</g>
<rect x="530" y="320" width="160" height="30" fill="#d5e8d4" stroke="#82b366" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 335px; margin-left: 540px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-common
</div>
</div>
</div>
</foreignObject>
<text x="540" y="339" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-common
</text>
</switch>
</g>
<rect x="530" y="360" width="160" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 375px; margin-left: 540px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; ">
plugin-&lt;plugin-id&gt;-node
</div>
</div>
</div>
</foreignObject>
<text x="540" y="379" fill="#000000" font-family="Helvetica" font-size="12px" font-style="italic">
plugin-&lt;plugin-id&gt;-node
</text>
</switch>
</g>
<path d="M 250 10 L 250 10 L 730 10 L 730 10" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/>
<path d="M 250 10 L 250 180 L 730 180 L 730 10" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/>
<path d="M 250 10 L 730 10" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/>
<path d="M 260 20 L 260 20 L 480 20 L 480 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 260 20 L 260 170 L 480 170 L 480 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 260 20 L 480 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="369.5" y="37.5">
Frontend Plugin Core
</text>
</g>
<rect x="280" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 290px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/core-plugin-api
</div>
</div>
</div>
</foreignObject>
<text x="290" y="69" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/core-plugin-api
</text>
</switch>
</g>
<rect x="280" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 290px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/test-utils
</div>
</div>
</div>
</foreignObject>
<text x="290" y="109" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/test-utils
</text>
</switch>
</g>
<rect x="280" y="130" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 145px; margin-left: 290px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/dev-utils
</div>
</div>
</div>
</foreignObject>
<text x="290" y="149" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/dev-utils
</text>
</switch>
</g>
<path d="M 500 20 L 500 20 L 720 20 L 720 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 500 20 L 500 170 L 720 170 L 720 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 500 20 L 720 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px">
<text x="609.5" y="37.5">
Frontend Libraries
</text>
</g>
<rect x="520" y="130" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 145px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/integration-react
</div>
</div>
</div>
</foreignObject>
<text x="530" y="149" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/integration-react
</text>
</switch>
</g>
<rect x="520" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/core-components
</div>
</div>
</div>
</foreignObject>
<text x="530" y="69" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/core-components
</text>
</switch>
</g>
<rect x="520" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 530px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
@backstage/theme
</div>
</div>
</div>
</foreignObject>
<text x="530" y="109" fill="#000000" font-family="Helvetica" font-size="12px">
@backstage/theme
</text>
</switch>
</g>
<rect x="10" y="500" width="160" height="130" fill="none" stroke="#000000" pointer-events="none"/>
<rect x="20" y="510" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<rect x="20" y="570" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<rect x="20" y="540" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 520px; margin-left: 50px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
Frontend Package
</div>
</div>
</div>
</foreignObject>
<text x="50" y="524" fill="#000000" font-family="Helvetica" font-size="12px">
Frontend Package
</text>
</switch>
</g>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 550px; margin-left: 50px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
Isomorphic Package
</div>
</div>
</div>
</foreignObject>
<text x="50" y="554" fill="#000000" font-family="Helvetica" font-size="12px">
Isomorphic Package
</text>
</switch>
</g>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 580px; margin-left: 50px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
Backend Package
</div>
</div>
</div>
</foreignObject>
<text x="50" y="584" fill="#000000" font-family="Helvetica" font-size="12px">
Backend Package
</text>
</switch>
</g>
<rect x="20" y="600" width="20" height="20" fill="#e1d5e7" stroke="#9673a6" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 610px; margin-left: 50px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
CLI Package
</div>
</div>
</div>
</foreignObject>
<text x="50" y="614" fill="#000000" font-family="Helvetica" font-size="12px">
CLI Package
</text>
</switch>
</g>
<rect x="190" y="540" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<rect x="190" y="600" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<rect x="190" y="570" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<rect x="250" y="540" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/>
<path d="M 210 550 Q 210 550 243.88 550" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 550 L 243.88 551.67 L 243.88 548.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="250" y="600" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/>
<path d="M 210 610 Q 210 610 243.88 610" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 610 L 243.88 611.67 L 243.88 608.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<rect x="250" y="570" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/>
<path d="M 210 550 Q 230 550 230 562.5 Q 230 575 243.88 575" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 575 L 243.88 576.67 L 243.88 573.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 210 605 Q 230 605 230 595 Q 230 585 243.88 585" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 585 L 243.88 586.67 L 243.88 583.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 210 580 Q 210 580 243.88 580" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 248.88 580 L 243.88 581.67 L 243.88 578.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 90px; height: 1px; padding-top: 520px; margin-left: 190px;">
<div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; ">
Compatibility
</div>
</div>
</div>
</foreignObject>
<text x="190" y="524" fill="#000000" font-family="Helvetica" font-size="12px">
Compatibility
</text>
</switch>
</g>
<path d="M 251 589 L 251 570 L 270 589 Z" fill="#e1d5e7" stroke="#9673a6" stroke-miterlimit="10" transform="rotate(-180,260.5,579.5)" pointer-events="none"/>
<rect x="0" y="630" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/>
<rect x="1250" y="630" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/>
<rect x="1250" y="0" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Viewer does not support full SVG 1.1
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

+38
View File
@@ -50,3 +50,41 @@ The Bitbucket provider is a structure with two configuration keys:
To add the provider to the frontend, add the `bitbucketAuthApi` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
## Using Bitbucket for sign-in
In order to use the Bitbucket provider for sign-in, you must configure it with a
`signIn.resolver`. See the
[Sign-In Resolver documentation](../identity-resolver.md) for more details on
how this is done. Note that for the Bitbucket provider, you'll want to use
`bitbucket` as the provider ID, and `createBitbucketProvider` for the provider
factory.
The `@backstage/plugin-auth-backend` plugin also comes with two built-in
resolves that can be used if desired. The first one is the
`bitbucketUsernameSignInResolver`, which identifies users by matching their
Bitbucket username to `bitbucket.org/username` annotations of `User` entities in
the catalog. Note that you must populate your catalog with matching entities or
users will not be able to sign in.
The second resolver is the `bitbucketUsernameSignInResolver`, which works the
same way, but uses the Bitbucket user ID instead, and matches on the
`bitbucket.org/user-id` annotation.
The following is an example of how to use one of the built-in resolvers:
```ts
import {
createBitbucketProvider,
bitbucketUsernameSignInResolver,
} from '@backstage/plugin-auth-backend';
// ...
providerFactories: {
bitbucket: createBitbucketProvider({
signIn: {
resolver: bitbucketUsernameSignInResolver,
},
}),
},
```
+1 -1
View File
@@ -230,7 +230,7 @@ You can do so by including the following lines in the last step of your
```Dockerfile
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install mkdocs-techdocs-core==0.0.16
RUN pip3 install mkdocs-techdocs-core==0.2.2
```
Please be aware that the version requirement could change, you need to check our
+1 -7
View File
@@ -179,13 +179,7 @@ Surrounding the plugin are different package groups which are the different
possible interface points of the plugin. Note that not all library package lists
are complete as packages have been omitted for brevity.
![Package architecture](../assets/architecture-overview/package-architecture.png)
<!--
NOTE: The diagram is exported from /docs/assets/architecture-overview/package-architecture.drawio
It can be edited using https://www.diagrams.net/
Export it as a PNG with 10px margin without transparent background
-->
![Package architecture](../assets/architecture-overview/package-architecture.drawio.svg)
### Overview
+2 -1
View File
@@ -61,7 +61,8 @@
"commander": "^6.1.0",
"css-loader": "^6.5.1",
"diff": "^5.0.0",
"esbuild": "^0.14.1",
"esbuild": "^0.14.10",
"esbuild-loader": "^2.18.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-formatter-friendly": "^7.0.0",
+8 -13
View File
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import { WebpackOptionsNormalized, WebpackPluginInstance } from 'webpack';
import TerserPlugin from 'terser-webpack-plugin';
import { WebpackOptionsNormalized } from 'webpack';
import { BundlingOptions } from './types';
import { isParallelDefault } from '../parallel';
const { ESBuildMinifyPlugin } = require('esbuild-loader');
export const optimization = (
options: BundlingOptions,
@@ -26,16 +26,11 @@ export const optimization = (
return {
minimize: !isDev,
// Only configure when parallel is explicitly overridden from the default
...(!isParallelDefault(options.parallel)
? {
minimizer: [
new TerserPlugin({
parallel: options.parallel,
}) as unknown as WebpackPluginInstance,
],
}
: {}),
minimizer: [
new ESBuildMinifyPlugin({
target: 'es2019',
}),
],
runtimeChunk: 'single',
splitChunks: {
automaticNameDelimiter: '-',
+3
View File
@@ -9,6 +9,9 @@
},
"dependencies": {
"@backstage/theme": "^0.2.0",
"@backstage/test-utils": "^0.2.1",
"@backstage/core-app-api": "^0.3.1",
"@backstage/core-plugin-api": "^0.4.1",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
@@ -37,75 +37,75 @@ import { ConfigApi } from '@backstage/core-plugin-api';
* ```
*/
export class MockConfigApi implements ConfigApi {
readonly #config: ConfigReader;
private readonly config: ConfigReader;
// NOTE: not extending in order to avoid inheriting the static `.fromConfigs`
constructor(data: JsonObject) {
this.#config = new ConfigReader(data);
this.config = new ConfigReader(data);
}
/** {@inheritdoc @backstage/config#Config.has} */
has(key: string): boolean {
return this.#config.has(key);
return this.config.has(key);
}
/** {@inheritdoc @backstage/config#Config.keys} */
keys(): string[] {
return this.#config.keys();
return this.config.keys();
}
/** {@inheritdoc @backstage/config#Config.get} */
get<T = JsonValue>(key?: string): T {
return this.#config.get(key);
return this.config.get(key);
}
/** {@inheritdoc @backstage/config#Config.getOptional} */
getOptional<T = JsonValue>(key?: string): T | undefined {
return this.#config.getOptional(key);
return this.config.getOptional(key);
}
/** {@inheritdoc @backstage/config#Config.getConfig} */
getConfig(key: string): Config {
return this.#config.getConfig(key);
return this.config.getConfig(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalConfig} */
getOptionalConfig(key: string): Config | undefined {
return this.#config.getOptionalConfig(key);
return this.config.getOptionalConfig(key);
}
/** {@inheritdoc @backstage/config#Config.getConfigArray} */
getConfigArray(key: string): Config[] {
return this.#config.getConfigArray(key);
return this.config.getConfigArray(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */
getOptionalConfigArray(key: string): Config[] | undefined {
return this.#config.getOptionalConfigArray(key);
return this.config.getOptionalConfigArray(key);
}
/** {@inheritdoc @backstage/config#Config.getNumber} */
getNumber(key: string): number {
return this.#config.getNumber(key);
return this.config.getNumber(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalNumber} */
getOptionalNumber(key: string): number | undefined {
return this.#config.getOptionalNumber(key);
return this.config.getOptionalNumber(key);
}
/** {@inheritdoc @backstage/config#Config.getBoolean} */
getBoolean(key: string): boolean {
return this.#config.getBoolean(key);
return this.config.getBoolean(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */
getOptionalBoolean(key: string): boolean | undefined {
return this.#config.getOptionalBoolean(key);
return this.config.getOptionalBoolean(key);
}
/** {@inheritdoc @backstage/config#Config.getString} */
getString(key: string): string {
return this.#config.getString(key);
return this.config.getString(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalString} */
getOptionalString(key: string): string | undefined {
return this.#config.getOptionalString(key);
return this.config.getOptionalString(key);
}
/** {@inheritdoc @backstage/config#Config.getStringArray} */
getStringArray(key: string): string[] {
return this.#config.getStringArray(key);
return this.config.getStringArray(key);
}
/** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */
getOptionalStringArray(key: string): string[] | undefined {
return this.#config.getOptionalStringArray(key);
return this.config.getOptionalStringArray(key);
}
}
+20
View File
@@ -25,6 +25,7 @@ import { Location as Location_2 } from '@backstage/catalog-model';
import { LocationSpec } from '@backstage/catalog-model';
import { Logger as Logger_2 } from 'winston';
import { Organizations } from 'aws-sdk';
import { PermissionRule } from '@backstage/plugin-permission-node';
import { PluginDatabaseManager } from '@backstage/backend-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { ResourceEntityV1alpha1 } from '@backstage/catalog-model';
@@ -303,6 +304,12 @@ export type CatalogEnvironment = {
reader: UrlReader;
};
// @public
export type CatalogPermissionRule = PermissionRule<
Entity,
EntitiesSearchFilter
>;
// Warning: (ae-missing-release-tag) "CatalogProcessingEngine" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -1302,6 +1309,7 @@ export class NextCatalogBuilder {
addEntityPolicy(...policies: EntityPolicy[]): NextCatalogBuilder;
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
addEntityProvider(...providers: EntityProvider[]): NextCatalogBuilder;
addPermissionRules(...permissionRules: CatalogPermissionRule[]): void;
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
addProcessor(...processors: CatalogProcessor[]): NextCatalogBuilder;
build(): Promise<{
@@ -1349,6 +1357,8 @@ export interface NextRouterOptions {
// (undocumented)
logger: Logger_2;
// (undocumented)
permissionRules?: CatalogPermissionRule[];
// (undocumented)
refreshService?: RefreshService;
}
@@ -1380,6 +1390,16 @@ export function parseEntityYaml(
location: LocationSpec,
): Iterable<CatalogProcessorResult>;
// @public
export const permissionRules: {
hasAnnotation: CatalogPermissionRule;
hasLabel: CatalogPermissionRule;
hasMetadata: CatalogPermissionRule;
hasSpec: CatalogPermissionRule;
isEntityKind: CatalogPermissionRule;
isEntityOwner: CatalogPermissionRule;
};
// Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
+3
View File
@@ -32,10 +32,12 @@
"dependencies": {
"@backstage/backend-common": "^0.10.1",
"@backstage/catalog-client": "^0.5.3",
"@backstage/plugin-catalog-common": "^0.1.0",
"@backstage/catalog-model": "^0.9.8",
"@backstage/config": "^0.1.11",
"@backstage/errors": "^0.1.5",
"@backstage/integration": "^0.7.0",
"@backstage/plugin-permission-node": "^0.2.3",
"@backstage/search-common": "^0.2.1",
"@backstage/types": "^0.1.1",
"@octokit/graphql": "^4.5.8",
@@ -64,6 +66,7 @@
"devDependencies": {
"@backstage/backend-test-utils": "^0.1.12",
"@backstage/cli": "^0.10.4",
"@backstage/plugin-permission-common": "^0.3.0",
"@backstage/test-utils": "^0.2.1",
"@types/core-js": "^2.5.4",
"@types/git-url-parse": "^9.0.0",
+1
View File
@@ -28,3 +28,4 @@ export * from './util';
export * from './processing';
export * from './providers';
export * from './service';
export * from './permissions';
@@ -0,0 +1,18 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './rules';
export type { CatalogPermissionRule } from './types';
@@ -0,0 +1,132 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createPropertyRule } from './createPropertyRule';
describe('createPropertyRule', () => {
const { name, description, apply, toQuery } = createPropertyRule('metadata');
it('formats the rule name correctly', () => {
expect(name).toBe('HAS_METADATA');
});
it('formats the rule description correctly', () => {
expect(description).toBe(
'Allow entities which have the specified metadata subfield.',
);
});
describe('apply', () => {
describe('key only', () => {
it('returns false when specified key is not present', () => {
expect(
apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
},
},
'org.name',
),
).toBe(false);
});
it('returns true when specified key is present', () => {
expect(
apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
org: {
name: 'test-org',
},
},
},
'org.name',
),
).toBe(true);
});
});
describe('key and value', () => {
it('returns false when specified key is not present', () => {
expect(
apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
},
},
'org.name',
'test-org',
),
).toBe(false);
});
it('returns false when specified value is not present', () => {
expect(
apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
org: {
name: 'another-org',
},
},
},
'org.name',
'test-org',
),
).toBe(false);
});
it('returns true when specified key and value is present', () => {
expect(
apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
org: {
name: 'test-org',
},
},
},
'org.name',
'test-org',
),
).toBe(true);
});
});
});
describe('toQuery', () => {
it('returns an appropriate catalog-backend filter', () => {
expect(toQuery('backstage.io/test-component')).toEqual({
key: 'metadata.backstage.io/test-component',
});
});
});
});
@@ -0,0 +1,40 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { EntitiesSearchFilter } from '../../catalog/types';
import { CatalogPermissionRule } from '../types';
import { get } from 'lodash';
export function createPropertyRule(
propertyType: 'metadata' | 'spec',
): CatalogPermissionRule {
return {
name: `HAS_${propertyType.toUpperCase()}`,
description: `Allow entities which have the specified ${propertyType} subfield.`,
apply: (resource: Entity, key: string, value?: string) => {
const foundValue = get(resource[propertyType], key);
if (value !== undefined) {
return value === foundValue;
}
return !!foundValue;
},
toQuery: (key: string, value?: string): EntitiesSearchFilter => ({
key: `${propertyType}.${key}`,
...(value !== undefined && { values: [value] }),
}),
};
}
@@ -0,0 +1,81 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { hasAnnotation } from './hasAnnotation';
describe('hasAnnotation permission rule', () => {
describe('apply', () => {
it('returns false when specified annotation is not present', () => {
expect(
hasAnnotation.apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
annotations: {
'other-annotation': 'foo',
},
},
},
'backstage.io/test-annotation',
),
).toEqual(false);
});
it('returns false when no annotations are present', () => {
expect(
hasAnnotation.apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
},
},
'backstage.io/test-annotation',
),
).toEqual(false);
});
it('returns true when specified annotation is present', () => {
expect(
hasAnnotation.apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
annotations: {
'other-annotation': 'foo',
'backstage.io/test-annotation': 'bar',
},
},
},
'backstage.io/test-annotation',
),
).toEqual(true);
});
});
describe('toQuery', () => {
it('returns an appropriate catalog-backend filter', () => {
expect(hasAnnotation.toQuery('backstage.io/test-annotation')).toEqual({
key: 'metadata.annotations.backstage.io/test-annotation',
});
});
});
});
@@ -0,0 +1,35 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { EntitiesSearchFilter } from '../../catalog/types';
import { CatalogPermissionRule } from '../types';
/**
* A {@link CatalogPermissionRule} which filters for the presence of an
* annotation on a given entity.
* @public
*/
export const hasAnnotation: CatalogPermissionRule = {
name: 'HAS_ANNOTATION',
description:
'Allow entities which are annotated with the specified annotation',
apply: (resource: Entity, annotation: string) =>
!!resource.metadata.annotations?.hasOwnProperty(annotation),
toQuery: (annotation: string): EntitiesSearchFilter => ({
key: `metadata.annotations.${annotation}`,
}),
};
@@ -0,0 +1,81 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { hasLabel } from './hasLabel';
describe('hasLabel permission rule', () => {
describe('apply', () => {
it('returns false when specified label is not present', () => {
expect(
hasLabel.apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
labels: {
somelabel: 'foo',
},
},
},
'backstage.io/testlabel',
),
).toEqual(false);
});
it('returns false when no annotations are present', () => {
expect(
hasLabel.apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
},
},
'backstage.io/testlabel',
),
).toEqual(false);
});
it('returns true when specified annotation is present', () => {
expect(
hasLabel.apply(
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-component',
labels: {
somelabel: 'foo',
'backstage.io/testlabel': 'bar',
},
},
},
'backstage.io/testlabel',
),
).toEqual(true);
});
});
describe('toQuery', () => {
it('returns an appropriate catalog-backend filter', () => {
expect(hasLabel.toQuery('backstage.io/testlabel')).toEqual({
key: 'metadata.labels.backstage.io/testlabel',
});
});
});
});
@@ -0,0 +1,34 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { EntitiesSearchFilter } from '../../catalog/types';
import { CatalogPermissionRule } from '../types';
/**
* A {@link CatalogPermissionRule} which filters for entities with a specified
* label in its metadata.
* @public
*/
export const hasLabel: CatalogPermissionRule = {
name: 'HAS_LABEL',
description: 'Allow entities which have the specified label metadata.',
apply: (resource: Entity, label: string) =>
!!resource.metadata.labels?.hasOwnProperty(label),
toQuery: (label: string): EntitiesSearchFilter => ({
key: `metadata.labels.${label}`,
}),
};
@@ -0,0 +1,27 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createPropertyRule } from './createPropertyRule';
/**
* A {@link CatalogPermissionRule} which filters for entities with the specified
* metadata subfield. Also matches on values if value is provided.
*
* The key argument to the `apply` and `toQuery` methods can be nested, such as
* 'field.nestedfield'.
* @public
*/
export const hasMetadata = createPropertyRule('metadata');
@@ -0,0 +1,27 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createPropertyRule } from './createPropertyRule';
/**
* A {@link CatalogPermissionRule} which filters for entities with the specified
* spec subfield. Also matches on values if value is provided.
*
* The key argument to the `apply` and `toQuery` methods can be nested, such as
* 'field.nestedfield'.
* @public
*/
export const hasSpec = createPropertyRule('spec');
@@ -0,0 +1,36 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { hasAnnotation } from './hasAnnotation';
import { isEntityKind } from './isEntityKind';
import { isEntityOwner } from './isEntityOwner';
import { hasLabel } from './hasLabel';
import { hasMetadata } from './hasMetadata';
import { hasSpec } from './hasSpec';
/**
* These permission rules can be used to conditionally filter catalog entities
* or describe a user's access to the entities.
* @public
*/
export const permissionRules = {
hasAnnotation,
hasLabel,
hasMetadata,
hasSpec,
isEntityKind,
isEntityOwner,
};
@@ -0,0 +1,53 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { isEntityKind } from './isEntityKind';
describe('isEntityKind', () => {
describe('apply', () => {
it('returns true when entity is the correct kind', () => {
const component: Entity = {
apiVersion: 'a',
kind: 'b',
metadata: {
name: 'some-component',
},
};
expect(isEntityKind.apply(component, ['b'])).toBe(true);
});
it('returns false when entity is not the correct kind', () => {
const component: Entity = {
apiVersion: 'a',
kind: 'b',
metadata: {
name: 'some-component',
},
};
expect(isEntityKind.apply(component, ['c'])).toBe(false);
});
});
describe('toQuery', () => {
it('returns an appropriate catalog-backend filter', () => {
expect(isEntityKind.toQuery(['b'])).toEqual({
key: 'kind',
values: ['b'],
});
});
});
});
@@ -0,0 +1,38 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { EntitiesSearchFilter } from '../../catalog/types';
import { CatalogPermissionRule } from '../types';
/**
* A {@link CatalogPermissionRule} which filters for entities with a specified
* kind.
* @public
*/
export const isEntityKind: CatalogPermissionRule = {
name: 'IS_ENTITY_KIND',
description: 'Allow entities with the specified kind',
apply(resource: Entity, kinds: string[]) {
const resourceKind = resource.kind.toLocaleLowerCase('en-US');
return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind);
},
toQuery(kinds: string[]): EntitiesSearchFilter {
return {
key: 'kind',
values: kinds.map(kind => kind.toLocaleLowerCase('en-US')),
};
},
};
@@ -0,0 +1,90 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { isEntityOwner } from './isEntityOwner';
describe('isEntityOwner', () => {
describe('apply', () => {
it('returns true when entity is owned by the given user', () => {
const component: Entity = {
apiVersion: 'a',
kind: 'b',
metadata: {
name: 'some-component',
},
relations: [
{
type: 'ownedBy',
target: {
kind: 'user',
namespace: 'default',
name: 'spiderman',
},
},
],
};
expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe(
true,
);
});
it('returns false when entity is not owned by the given user', () => {
const component: Entity = {
apiVersion: 'a',
kind: 'b',
metadata: {
name: 'some-component',
},
relations: [
{
type: 'ownedBy',
target: {
kind: 'user',
namespace: 'default',
name: 'green-goblin',
},
},
],
};
expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe(
false,
);
});
it('returns false when entity does not have an owner', () => {
const component: Entity = {
apiVersion: 'a',
kind: 'b',
metadata: {
name: 'some-component',
},
};
expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe(
false,
);
});
});
describe('toQuery', () => {
it('returns an appropriate catalog-backend filter', () => {
expect(isEntityOwner.toQuery(['user:default/spiderman'])).toEqual({
key: 'relations.ownedBy',
values: ['user:default/spiderman'],
});
});
});
});
@@ -0,0 +1,46 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Entity,
RELATION_OWNED_BY,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { EntitiesSearchFilter } from '../../catalog/types';
import { CatalogPermissionRule } from '../types';
/**
* A {@link CatalogPermissionRule} which filters for entities with a specified
* owner.
* @public
*/
export const isEntityOwner: CatalogPermissionRule = {
name: 'IS_ENTITY_OWNER',
description: 'Allow entities owned by the current user',
apply: (resource: Entity, claims: string[]) => {
if (!resource.relations) {
return false;
}
return resource.relations
.filter(relation => relation.type === RELATION_OWNED_BY)
.some(relation => claims.includes(stringifyEntityRef(relation.target)));
},
toQuery: (claims: string[]): EntitiesSearchFilter => ({
key: 'relations.ownedBy',
values: claims,
}),
};
@@ -0,0 +1,30 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { PermissionRule } from '@backstage/plugin-permission-node';
import { EntitiesSearchFilter } from '../catalog/types';
/**
* A conditional rule that can be used to filter catalog entities for an
* authorization request. See
* {@link @backstage/plugin-permission-node#PermissionRule} for more details.
*
* @public
*/
export type CatalogPermissionRule = PermissionRule<
Entity,
EntitiesSearchFilter
>;
@@ -82,6 +82,8 @@ import { Config } from '@backstage/config';
import { Logger } from 'winston';
import { LocationService } from './types';
import { connectEntityProviders } from '../processing/connectEntityProviders';
import { CatalogPermissionRule } from '../permissions/types';
import { permissionRules as catalogPermissionRules } from '../permissions/rules';
export type CatalogEnvironment = {
logger: Logger;
@@ -125,6 +127,7 @@ export class NextCatalogBuilder {
maxSeconds: 150,
});
private locationAnalyzer: LocationAnalyzer | undefined = undefined;
private permissionRules: CatalogPermissionRule[];
constructor(env: CatalogEnvironment) {
this.env = env;
@@ -136,6 +139,7 @@ export class NextCatalogBuilder {
this.processors = [];
this.processorsReplace = false;
this.parser = undefined;
this.permissionRules = Object.values(catalogPermissionRules);
}
/**
@@ -317,6 +321,17 @@ export class NextCatalogBuilder {
return this;
}
/**
* Adds additional permission rules. Permission rules are used to evaluate
* catalog resources against queries. See
* {@link @backstage/plugin-permission-node#PermissionRule}.
*
* @param permissionRules - Additional permission rules
*/
addPermissionRules(...permissionRules: CatalogPermissionRule[]) {
this.permissionRules.push(...permissionRules);
}
/**
* Wires up and returns all of the component parts of the catalog
*/
@@ -393,6 +408,7 @@ export class NextCatalogBuilder {
refreshService,
logger,
config,
permissionRules: this.permissionRules,
});
await connectEntityProviders(processingDatabase, entityProviders);
@@ -24,6 +24,7 @@ import { EntitiesCatalog } from '../catalog';
import { LocationService, RefreshService } from './types';
import { basicEntityFilter } from './request';
import { createNextRouter } from './NextRouter';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
describe('createNextRouter readonly disabled', () => {
let entitiesCatalog: jest.Mocked<EntitiesCatalog>;
@@ -51,6 +52,7 @@ describe('createNextRouter readonly disabled', () => {
logger: getVoidLogger(),
refreshService,
config: new ConfigReader(undefined),
permissionRules: [],
});
app = express().use(router);
});
@@ -336,6 +338,7 @@ describe('createNextRouter readonly enabled', () => {
readonly: true,
},
}),
permissionRules: [],
});
app = express().use(router);
});
@@ -429,3 +432,72 @@ describe('createNextRouter readonly enabled', () => {
});
});
});
describe('NextRouter permissioning', () => {
let entitiesCatalog: jest.Mocked<EntitiesCatalog>;
let locationService: jest.Mocked<LocationService>;
let app: express.Express;
let refreshService: RefreshService;
const fakeRule = {
name: 'FAKE_RULE',
description: 'fake rule',
apply: () => true,
toQuery: () => ({ key: '', values: [] }),
};
beforeAll(async () => {
entitiesCatalog = {
entities: jest.fn(),
removeEntityByUid: jest.fn(),
batchAddOrUpdateEntities: jest.fn(),
entityAncestry: jest.fn(),
};
locationService = {
getLocation: jest.fn(),
createLocation: jest.fn(),
listLocations: jest.fn(),
deleteLocation: jest.fn(),
};
refreshService = { refresh: jest.fn() };
const router = await createNextRouter({
entitiesCatalog,
locationService,
logger: getVoidLogger(),
refreshService,
config: new ConfigReader(undefined),
permissionRules: [fakeRule],
});
app = express().use(router);
});
afterEach(() => {
jest.resetAllMocks();
});
it('accepts and evaluates conditions at the apply-conditions endpoint', async () => {
const spideySense: Entity = {
apiVersion: 'a',
kind: 'b',
metadata: {
name: 'spidey-sense',
},
};
entitiesCatalog.entities.mockResolvedValueOnce({
entities: [spideySense],
pageInfo: { hasNextPage: false },
});
const requestBody = {
resourceType: 'catalog-entity',
resourceRef: 'component:default/spidey-sense',
conditions: { rule: 'FAKE_RULE', params: ['user:default/spiderman'] },
};
const response = await request(app)
.post('/.well-known/backstage/permissions/apply-conditions')
.send(requestBody);
expect(response.status).toBe(200);
expect(response.body).toEqual({ result: AuthorizeResult.ALLOW });
});
});
@@ -17,17 +17,22 @@
import { errorHandler } from '@backstage/backend-common';
import {
analyzeLocationSchema,
Entity,
locationSpecSchema,
parseEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { NotFoundError } from '@backstage/errors';
import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common';
import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import yn from 'yn';
import { EntitiesCatalog } from '../catalog';
import { LocationAnalyzer } from '../ingestion/types';
import { CatalogPermissionRule } from '../permissions/types';
import {
basicEntityFilter,
parseEntityFilterParams,
@@ -44,6 +49,7 @@ export interface NextRouterOptions {
refreshService?: RefreshService;
logger: Logger;
config: Config;
permissionRules?: CatalogPermissionRule[];
}
export async function createNextRouter(
@@ -56,6 +62,7 @@ export async function createNextRouter(
refreshService,
config,
logger,
permissionRules,
} = options;
const router = Router();
@@ -77,6 +84,14 @@ export async function createNextRouter(
if (entitiesCatalog) {
router
.use(
createPermissionIntegrationRouter({
resourceType: RESOURCE_TYPE_CATALOG_ENTITY,
getResource: resourceRef =>
getEntityResource(resourceRef, entitiesCatalog),
rules: permissionRules ?? [],
}),
)
.get('/entities', async (req, res) => {
const { entities, pageInfo } = await entitiesCatalog.entities({
filter: parseEntityFilterParams(req.query),
@@ -182,3 +197,20 @@ export async function createNextRouter(
router.use(errorHandler());
return router;
}
async function getEntityResource(
resourceRef: string,
entitiesCatalog: EntitiesCatalog,
): Promise<Entity | undefined> {
const parsed = parseEntityRef(resourceRef);
const { entities } = await entitiesCatalog.entities({
filter: basicEntityFilter({
kind: parsed.kind,
'metadata.namespace': parsed.namespace,
'metadata.name': parsed.name,
}),
});
return entities[0];
}
@@ -34,7 +34,9 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
},
};
let credentialsResponse: any = new AWS.Credentials(credentials);
let mockedCredentials: any = undefined;
AWS.config.credentials = new AWS.Credentials(credentials);
AWSMock.setSDKInstance(AWS);
@@ -53,19 +55,25 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
const authTranslator = new AwsIamKubernetesAuthTranslator();
jest
.spyOn(authTranslator, 'awsGetCredentials')
.mockImplementation(async () => credentialsResponse);
if (mockedCredentials) {
jest
.spyOn(authTranslator, 'awsGetCredentials')
.mockImplementation(async () => mockedCredentials);
}
return authTranslator.decorateClusterDetailsWithAuth({
const response = authTranslator.decorateClusterDetailsWithAuth({
assumeRole: role,
name: 'test-cluster',
url: '',
authProvider: 'aws',
});
mockedCredentials = undefined;
return response;
});
it('returns a signed url for aws credentials', async () => {
it('returns a signed url for AWS credentials', async () => {
// These credentials are not real.
// Pulled from example in docs: https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
AWS.config.credentials = new AWS.Credentials(
@@ -87,7 +95,7 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
role = 'SomeRole';
describe('When the role is valid', () => {
it('returns a signed url for aws credentials', async () => {
it('returns a signed url for AWS credentials', async () => {
const subject = await get('subject');
expect(subject.serviceAccountToken).toBeDefined();
});
@@ -101,16 +109,20 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
});
});
describe('When no creds are returned from AWS', () => {
it('throws unable to get aws credentials', async () => {
credentialsResponse = new Error();
describe('When no AWS creds are available', () => {
it('throws unable to get AWS credentials', async () => {
mockedCredentials = new Error();
await expect(get('subject')).rejects.toThrow('No AWS credentials found.');
});
});
describe('When invalid creds are returned from AWS', () => {
it('throws credentials are invalid to get aws credentials', async () => {
credentialsResponse = new AWS.Credentials(credentialsResponse);
describe('When invalid AWS creds are available', () => {
it('throws credentials are invalid to get AWS credentials', async () => {
const undefinedSecret: any = undefined;
AWS.config.credentials = new AWS.Credentials(
'AKIAIOSFODNN7EXAMPLE',
undefinedSecret,
);
await expect(get('subject')).rejects.toThrow(
'Invalid AWS credentials found.',
);
@@ -41,9 +41,7 @@ export class AwsIamKubernetesAuthTranslator
implements KubernetesAuthTranslator
{
validCredentials(creds: SigningCreds): boolean {
return (creds?.accessKeyId &&
creds?.secretAccessKey &&
creds?.sessionToken) as unknown as boolean;
return (creds?.accessKeyId && creds?.secretAccessKey) as unknown as boolean;
}
awsGetCredentials = async (): Promise<Credentials> => {
@@ -41,7 +41,7 @@ export const BooleanCheck = ({ checkResult }: Prop) => {
return (
<List>
{checkResult.map((check, index) => (
<ListItem>
<ListItem key={check.check.id}>
<ListItemText
key={index}
primary={check.check.name}
@@ -31,6 +31,7 @@ import { EntityName } from '@backstage/catalog-model';
import { useApi, configApiRef } from '@backstage/core-plugin-api';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { BackstageTheme } from '@backstage/theme';
import { SidebarPinStateContext } from '@backstage/core-components';
import { techdocsStorageApiRef } from '../../api';
@@ -144,6 +145,9 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => {
const [sidebars, setSidebars] = useState<HTMLElement[]>();
const [dom, setDom] = useState<HTMLElement | null>(null);
// sidebar pinned status to be used in computing CSS style injections
const { isPinned } = useContext(SidebarPinStateContext);
const updateSidebarPosition = useCallback(() => {
if (!dom || !sidebars) return;
// set sidebar height so they don't initially render in wrong position
@@ -252,7 +256,9 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => {
transition: none !important
}
.md-sidebar--secondary { display: none; }
.md-sidebar--primary { left: 72px; width: 10rem }
.md-sidebar--primary { left: ${
isPinned ? '242px' : '72px'
}; width: 10rem }
.md-content { margin-left: 10rem; max-width: calc(100% - 10rem); }
.md-content__inner { font-size: 0.9rem }
.md-footer {
@@ -348,6 +354,7 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => {
theme.palette.success.main,
theme.palette.text.primary,
theme.typography.fontFamily,
isPinned,
],
);
+49 -37
View File
@@ -11,38 +11,27 @@ import { Logger as Logger_2 } from 'winston';
import { ScmIntegrations } from '@backstage/integration';
import { UrlReader } from '@backstage/backend-common';
// Warning: (ae-forgotten-export) The symbol "RouterOptions" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export function createRouter(options: RouterOptions): Promise<express.Router>;
// Warning: (ae-forgotten-export) The symbol "TodoParserOptions" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "TodoParser" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "createTodoParser" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export function createTodoParser(options?: TodoParserOptions): TodoParser;
// Warning: (ae-missing-release-tag) "ListTodosRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type ListTodosRequest = {
entity?: EntityName;
offset?: number;
limit?: number;
orderBy?: {
field: Fields;
field: 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath';
direction: 'asc' | 'desc';
};
filters?: {
field: Fields;
field: 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath';
value: string;
}[];
};
// Warning: (ae-missing-release-tag) "ListTodosResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type ListTodosResponse = {
items: TodoItem[];
@@ -51,22 +40,22 @@ export type ListTodosResponse = {
limit: number;
};
// Warning: (ae-missing-release-tag) "ReadTodosOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type ReadTodosOptions = {
url: string;
};
// Warning: (ae-missing-release-tag) "ReadTodosResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type ReadTodosResult = {
items: TodoItem[];
};
// Warning: (ae-missing-release-tag) "TodoItem" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface RouterOptions {
// (undocumented)
todoService: TodoService;
}
// @public (undocumented)
export type TodoItem = {
text: string;
@@ -77,19 +66,36 @@ export type TodoItem = {
repoFilePath?: string;
};
// Warning: (ae-missing-release-tag) "TodoReader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type TodoParser = (ctx: TodoParserContext) => TodoParserResult[];
// @public (undocumented)
export type TodoParserContext = {
content: string;
path: string;
};
// @public (undocumented)
export type TodoParserOptions = {
additionalTags?: string[];
};
// @public (undocumented)
export type TodoParserResult = {
text: string;
tag: string;
author?: string;
lineNumber: number;
};
// @public (undocumented)
export interface TodoReader {
readTodos(options: ReadTodosOptions): Promise<ReadTodosResult>;
}
// Warning: (ae-missing-release-tag) "TodoReaderService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export class TodoReaderService implements TodoService {
// Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts
constructor(options: Options_2);
constructor(options: TodoReaderServiceOptions);
// (undocumented)
listTodos(
req: ListTodosRequest,
@@ -99,24 +105,34 @@ export class TodoReaderService implements TodoService {
): Promise<ListTodosResponse>;
}
// Warning: (ae-missing-release-tag) "TodoScmReader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type TodoReaderServiceOptions = {
todoReader: TodoReader;
catalogClient: CatalogApi;
maxPageSize?: number;
defaultPageSize?: number;
};
// @public (undocumented)
export class TodoScmReader implements TodoReader {
constructor(options: Options);
// Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts
//
constructor(options: TodoScmReaderOptions);
// (undocumented)
static fromConfig(
config: Config,
options: Omit<Options, 'integrations'>,
options: Omit<TodoScmReaderOptions, 'integrations'>,
): TodoScmReader;
// (undocumented)
readTodos(options: ReadTodosOptions): Promise<ReadTodosResult>;
}
// Warning: (ae-missing-release-tag) "TodoService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type TodoScmReaderOptions = {
logger: Logger_2;
reader: UrlReader;
integrations: ScmIntegrations;
parser?: TodoParser;
};
// @public (undocumented)
export interface TodoService {
// (undocumented)
@@ -127,8 +143,4 @@ export interface TodoService {
},
): Promise<ListTodosResponse>;
}
// Warnings were encountered during analysis:
//
// src/service/types.d.ts:9:9 - (ae-forgotten-export) The symbol "Fields" needs to be exported by the entry point index.d.ts
```
@@ -41,7 +41,8 @@ const excludedExtensions = [
];
const MAX_FILE_SIZE = 200000;
type Options = {
/** @public */
export type TodoScmReaderOptions = {
logger: Logger;
reader: UrlReader;
integrations: ScmIntegrations;
@@ -53,6 +54,7 @@ type CacheItem = {
result: ReadTodosResult;
};
/** @public */
export class TodoScmReader implements TodoReader {
private readonly logger: Logger;
private readonly reader: UrlReader;
@@ -62,14 +64,17 @@ export class TodoScmReader implements TodoReader {
private readonly cache = new Map<string, CacheItem>();
private readonly inFlightReads = new Map<string, Promise<CacheItem>>();
static fromConfig(config: Config, options: Omit<Options, 'integrations'>) {
static fromConfig(
config: Config,
options: Omit<TodoScmReaderOptions, 'integrations'>,
) {
return new TodoScmReader({
...options,
integrations: ScmIntegrations.fromConfig(config),
});
}
constructor(options: Options) {
constructor(options: TodoScmReaderOptions) {
this.logger = options.logger;
this.reader = options.reader;
this.parser = options.parser ?? createTodoParser();
@@ -18,11 +18,13 @@ import { extname } from 'path';
import { parse } from 'leasot';
import { TodoParser } from './types';
/** @public */
export type TodoParserOptions = {
/** Custom tags to support in addition to TODO and FIXME */
additionalTags?: string[];
};
/** @public */
export function createTodoParser(options: TodoParserOptions = {}): TodoParser {
return ({ content, path }) => {
try {
@@ -15,10 +15,15 @@
*/
export { TodoScmReader } from './TodoScmReader';
export type { TodoScmReaderOptions } from './TodoScmReader';
export { createTodoParser } from './createTodoParser';
export type { TodoParserOptions } from './createTodoParser';
export type {
TodoItem,
TodoReader,
ReadTodosOptions,
ReadTodosResult,
TodoParser,
TodoParserContext,
TodoParserResult,
} from './types';
@@ -14,6 +14,7 @@
* limitations under the License.
*/
/** @public */
export type TodoItem = {
/** The contents of the TODO comment */
text: string;
@@ -34,6 +35,7 @@ export type TodoItem = {
repoFilePath?: string;
};
/** @public */
export type ReadTodosOptions = {
/**
* Base URLs defining the root at which to search for TODOs
@@ -41,6 +43,7 @@ export type ReadTodosOptions = {
url: string;
};
/** @public */
export type ReadTodosResult = {
/**
* TODO items found at the given locations
@@ -48,6 +51,7 @@ export type ReadTodosResult = {
items: TodoItem[];
};
/** @public */
export interface TodoReader {
/**
* Searches for TODO items in code at a given location
@@ -55,16 +59,19 @@ export interface TodoReader {
readTodos(options: ReadTodosOptions): Promise<ReadTodosResult>;
}
type TodoParserContext = {
/** @public */
export type TodoParserContext = {
content: string;
path: string;
};
type TodoParserResult = {
/** @public */
export type TodoParserResult = {
text: string;
tag: string;
author?: string;
lineNumber: number;
};
/** @public */
export type TodoParser = (ctx: TodoParserContext) => TodoParserResult[];
@@ -29,7 +29,8 @@ import { ListTodosRequest, ListTodosResponse, TodoService } from './types';
const DEFAULT_DEFAULT_PAGE_SIZE = 10;
const DEFAULT_MAX_PAGE_SIZE = 50;
type Options = {
/** @public */
export type TodoReaderServiceOptions = {
todoReader: TodoReader;
catalogClient: CatalogApi;
maxPageSize?: number;
@@ -43,13 +44,14 @@ function wildcardRegex(str: string): RegExp {
return new RegExp(`^${pattern}$`, 'i');
}
/** @public */
export class TodoReaderService implements TodoService {
private readonly todoReader: TodoReader;
private readonly catalogClient: CatalogApi;
private readonly maxPageSize: number;
private readonly defaultPageSize: number;
constructor(options: Options) {
constructor(options: TodoReaderServiceOptions) {
this.todoReader = options.todoReader;
this.catalogClient = options.catalogClient;
this.maxPageSize = options.maxPageSize ?? DEFAULT_MAX_PAGE_SIZE;
@@ -15,5 +15,7 @@
*/
export { createRouter } from './router';
export type { RouterOptions } from './router';
export type { TodoService, ListTodosRequest, ListTodosResponse } from './types';
export { TodoReaderService } from './TodoReaderService';
export type { TodoReaderServiceOptions } from './TodoReaderService';
@@ -28,10 +28,12 @@ const TODO_FIELDS = [
'repoFilePath',
] as const;
/** @public */
export interface RouterOptions {
todoService: TodoService;
}
/** @public */
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
+5 -4
View File
@@ -17,23 +17,23 @@
import { EntityName } from '@backstage/catalog-model';
import { TodoItem } from '../lib';
type Fields = 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath';
/** @public */
export type ListTodosRequest = {
entity?: EntityName;
offset?: number;
limit?: number;
orderBy?: {
field: Fields;
field: 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath';
direction: 'asc' | 'desc';
};
filters?: {
field: Fields;
field: 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath';
/** Value to filter by, with '*' used as wildcard */
value: string;
}[];
};
/** @public */
export type ListTodosResponse = {
items: TodoItem[];
totalCount: number;
@@ -41,6 +41,7 @@ export type ListTodosResponse = {
limit: number;
};
/** @public */
export interface TodoService {
listTodos(
req: ListTodosRequest,
+123 -191
View File
@@ -7569,9 +7569,9 @@
"@types/istanbul-lib-report" "*"
"@types/jenkins@^0.23.1":
version "0.23.2"
resolved "https://registry.npmjs.org/@types/jenkins/-/jenkins-0.23.2.tgz#96736a2be4904efdfe7fe2650569479fd77dc48e"
integrity sha512-BELmIZ6brxwGFqBHfLjLTjYRWAUqcT1d2BydH1CcRTZEjCYw3DRVfZkXU7BVlyIsKXm2ZMIKVkEjKKtVDvPiXA==
version "0.23.3"
resolved "https://registry.npmjs.org/@types/jenkins/-/jenkins-0.23.3.tgz#bca00a395147c8d448cfba6bb36241a56862f820"
integrity sha512-/uNktGT4d/NNIBZTur4cO4YU00fQ+iEe0Arm0l5v9Ixv6zwZR9u0V7XSuhWbWJkzKsh8aIeK66QxYqJvYF4yzA==
dependencies:
"@types/node" "*"
@@ -10600,18 +10600,7 @@ browserslist@4.14.2:
escalade "^3.0.2"
node-releases "^1.1.61"
browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.5, browserslist@^4.16.6:
version "4.18.1"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f"
integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==
dependencies:
caniuse-lite "^1.0.30001280"
electron-to-chromium "^1.3.896"
escalade "^3.1.1"
node-releases "^2.0.1"
picocolors "^1.0.0"
browserslist@^4.17.5:
browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1:
version "4.19.1"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3"
integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==
@@ -10664,11 +10653,6 @@ buffer-indexof@^1.0.0:
resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==
buffer-writer@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08"
integrity sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=
buffer-writer@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04"
@@ -10981,7 +10965,7 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001280:
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125:
version "1.0.30001282"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz#38c781ee0a90ccfe1fe7fefd00e43f5ffdcb96fd"
integrity sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg==
@@ -11052,7 +11036,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@4.1.1, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
chalk@4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
@@ -11079,6 +11063,14 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
change-case-all@1.0.14:
version "1.0.14"
resolved "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1"
@@ -13521,6 +13513,11 @@ duplexer@^0.1.1, duplexer@~0.1.1:
resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
duplexer@^0.1.2:
version "0.1.2"
resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
duplexify@^3.4.2, duplexify@^3.6.0:
version "3.7.1"
resolved "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
@@ -13583,7 +13580,7 @@ elastic-builder@^2.16.0:
lodash.isstring "^4.0.1"
lodash.omit "^4.5.0"
electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.896:
electron-to-chromium@^1.3.564:
version "1.3.900"
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5"
integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ==
@@ -13956,6 +13953,18 @@ esbuild-linux-s390x@0.14.10:
resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.10.tgz#cc4228ac842febc48b84757814bed964a619be62"
integrity sha512-knArKKZm0ypIYWOWyOT7+accVwbVV1LZnl2FWWy05u9Tyv5oqJ2F5+X2Vqe/gqd61enJXQWqoufXopvG3zULOg==
esbuild-loader@^2.18.0:
version "2.18.0"
resolved "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-2.18.0.tgz#7b9548578ab954574fd94655693d22aa5ec74120"
integrity sha512-AKqxM3bI+gvGPV8o6NAhR+cBxVO8+dh+O0OXBHIXXwuSGumckbPWHzZ17subjBGI2YEGyJ1STH7Haj8aCrwL/w==
dependencies:
esbuild "^0.14.6"
joycon "^3.0.1"
json5 "^2.2.0"
loader-utils "^2.0.0"
tapable "^2.2.0"
webpack-sources "^2.2.0"
esbuild-netbsd-64@0.14.10:
version "0.14.10"
resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.10.tgz#6ec50d9e4547a7579f447307b19f66bbedfd868b"
@@ -13986,7 +13995,7 @@ esbuild-windows-arm64@0.14.10:
resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.10.tgz#50ab9a83f6ccf71c272e58489ecc4d7375075f32"
integrity sha512-OJOyxDtabvcUYTc+O4dR0JMzLBz6G9+gXIHA7Oc5d5Fv1xiYa0nUeo8+W5s2e6ZkPRdIwOseYoL70rZz80S5BA==
esbuild@^0.14.1:
esbuild@^0.14.1, esbuild@^0.14.10, esbuild@^0.14.6:
version "0.14.10"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.10.tgz#10268d2b576b25ed6f8554553413988628a7767b"
integrity sha512-ibZb+NwFqBwHHJlpnFMtg4aNmVK+LUtYMFC9CuKs6lDCBEvCHpqCFZFEirpqt1jOugwKGx8gALNGvX56lQyfew==
@@ -14976,10 +14985,10 @@ filesize@6.1.0:
resolved "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00"
integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==
filesize@^6.1.0:
version "6.4.0"
resolved "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz#914f50471dd66fdca3cefe628bd0cde4ef769bcd"
integrity sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==
filesize@^8.0.6:
version "8.0.6"
resolved "https://registry.npmjs.org/filesize/-/filesize-8.0.6.tgz#5f0c27aa1b507fa7d9f72c912a774ca6a44111b1"
integrity sha512-sHvRqTiwdmcuzqet7iVwsbwF6UrV3wIgDf2SHNdY1Hgl8PC45HZg/0xtdw6U2izIV4lccnrY9ftl6wZFNdjYMg==
fill-range@^4.0.0:
version "4.0.0"
@@ -15191,29 +15200,10 @@ fork-ts-checker-webpack-plugin@4.1.6, fork-ts-checker-webpack-plugin@^4.0.5, for
tapable "^1.0.0"
worker-rpc "^0.1.0"
fork-ts-checker-webpack-plugin@^6.0.4:
version "6.3.1"
resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.3.1.tgz#938535f844cdddf6796211e5761da27341b9fcd3"
integrity sha512-uxqlKTEeSJ5/JRr0zaCiw2U+kOV8F4/MhCnnRf6vbxj4ZU3Or0DLl/0CNtXro7uLWDssnuR7wUN7fU9w1I0REA==
dependencies:
"@babel/code-frame" "^7.8.3"
"@types/json-schema" "^7.0.5"
chalk "^4.1.0"
chokidar "^3.4.2"
cosmiconfig "^6.0.0"
deepmerge "^4.2.2"
fs-extra "^9.0.0"
glob "^7.1.6"
memfs "^3.1.2"
minimatch "^3.0.4"
schema-utils "2.7.0"
semver "^7.3.2"
tapable "^1.0.0"
fork-ts-checker-webpack-plugin@^6.0.5:
version "6.4.2"
resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.4.2.tgz#6d307fb4072ce4abe4d56a89c8ef060066f33d81"
integrity sha512-EqtzzRdx2mldr0KEydSN9jaNrf419gMpwkloumG6K/S7jtJc9Fl7wMJ+y+o7DLLGMMU/kouYr06agTD/YkxzIQ==
fork-ts-checker-webpack-plugin@^6.0.4, fork-ts-checker-webpack-plugin@^6.5.0:
version "6.5.0"
resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e"
integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw==
dependencies:
"@babel/code-frame" "^7.8.3"
"@types/json-schema" "^7.0.5"
@@ -15535,11 +15525,6 @@ generic-names@^2.0.1:
dependencies:
loader-utils "^1.1.0"
generic-pool@2.4.3:
version "2.4.3"
resolved "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff"
integrity sha1-eAw29p360FpaBF3Te+etyhGk9v8=
gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -16215,7 +16200,7 @@ gud@^1.0.0:
resolved "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==
gzip-size@5.1.1, gzip-size@^5.1.1:
gzip-size@5.1.1:
version "5.1.1"
resolved "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==
@@ -16223,6 +16208,13 @@ gzip-size@5.1.1, gzip-size@^5.1.1:
duplexer "^0.1.1"
pify "^4.0.1"
gzip-size@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==
dependencies:
duplexer "^0.1.2"
handle-thing@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754"
@@ -16902,7 +16894,7 @@ immer@8.0.1:
resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656"
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
immer@^9.0.1, immer@^9.0.6:
immer@^9.0.1, immer@^9.0.7:
version "9.0.7"
resolved "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075"
integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA==
@@ -18552,7 +18544,7 @@ js-levenshtein@^1.1.6:
resolved "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
js-string-escape@1.0.1, js-string-escape@^1.0.1:
js-string-escape@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=
@@ -18843,7 +18835,7 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
json5@^2.1.2, json5@^2.1.3:
json5@^2.1.2, json5@^2.1.3, json5@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
@@ -19530,6 +19522,11 @@ loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
emojis-list "^3.0.0"
json5 "^1.0.1"
loader-utils@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f"
integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@@ -20727,15 +20724,10 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
mime-db@1.49.0:
version "1.49.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==
"mime-db@>= 1.43.0 < 2":
version "1.48.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d"
integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==
mime-db@1.51.0, "mime-db@>= 1.43.0 < 2":
version "1.51.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
mime-db@~1.33.0:
version "1.33.0"
@@ -20750,11 +20742,11 @@ mime-types@2.1.18:
mime-db "~1.33.0"
mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
version "2.1.32"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==
version "2.1.34"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
dependencies:
mime-db "1.49.0"
mime-db "1.51.0"
mime@1.6.0:
version "1.6.0"
@@ -21784,11 +21776,6 @@ oauth@0.9.x:
resolved "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
object-assign@4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
integrity sha1-ejs9DpgGPUP0wD8uiubNUahog6A=
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -21975,7 +21962,7 @@ open@^7.0.2, open@^7.0.3:
is-docker "^2.0.0"
is-wsl "^2.1.1"
open@^8.0.0, open@^8.0.9:
open@^8.0.0, open@^8.0.9, open@^8.4.0:
version "8.4.0"
resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
@@ -22281,11 +22268,6 @@ package-json@^6.3.0:
registry-url "^5.0.0"
semver "^6.2.0"
packet-reader@0.3.1:
version "0.3.1"
resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz#cd62e60af8d7fea8a705ec4ff990871c46871f27"
integrity sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=
packet-reader@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74"
@@ -22781,17 +22763,12 @@ performance-now@^2.1.0:
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
pg-connection-string@0.1.3, pg-connection-string@^0.1.3:
version "0.1.3"
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7"
integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=
pg-connection-string@2.4.0:
version "2.4.0"
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz#c979922eb47832999a204da5dbe1ebf2341b6a10"
integrity sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==
pg-connection-string@^2.3.0, pg-connection-string@^2.5.0:
pg-connection-string@^2.3.0, pg-connection-string@^2.4.0, pg-connection-string@^2.5.0:
version "2.5.0"
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
@@ -22801,35 +22778,16 @@ pg-int8@1.0.1:
resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
pg-pool@1.*:
version "1.8.0"
resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-1.8.0.tgz#f7ec73824c37a03f076f51bfdf70e340147c4f37"
integrity sha1-9+xzgkw3oD8Hb1G/33DjQBR8Tzc=
dependencies:
generic-pool "2.4.3"
object-assign "4.1.0"
pg-pool@^3.3.0:
version "3.3.0"
resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz#12d5c7f65ea18a6e99ca9811bd18129071e562fc"
integrity sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg==
pg-pool@^3.4.1:
version "3.4.1"
resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz#0e71ce2c67b442a5e862a9c182172c37eda71e9c"
integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==
pg-protocol@^1.5.0:
version "1.5.0"
resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0"
integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==
pg-types@1.*:
version "1.13.0"
resolved "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz#75f490b8a8abf75f1386ef5ec4455ecf6b345c63"
integrity sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ==
dependencies:
pg-int8 "1.0.1"
postgres-array "~1.0.0"
postgres-bytea "~1.0.0"
postgres-date "~1.0.0"
postgres-interval "^1.1.0"
pg-types@^2.1.0:
version "2.2.0"
resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
@@ -22841,34 +22799,20 @@ pg-types@^2.1.0:
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
pg@^6.1.0:
version "6.4.2"
resolved "https://registry.npmjs.org/pg/-/pg-6.4.2.tgz#c364011060eac7a507a2ae063eb857ece910e27f"
integrity sha1-w2QBEGDqx6UHoq4GPrhX7OkQ4n8=
dependencies:
buffer-writer "1.0.1"
js-string-escape "1.0.1"
packet-reader "0.3.1"
pg-connection-string "0.1.3"
pg-pool "1.*"
pg-types "1.*"
pgpass "1.*"
semver "4.3.2"
pg@^8.3.0:
version "8.6.0"
resolved "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz#e222296b0b079b280cce106ea991703335487db2"
integrity sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==
pg@^8.3.0, pg@^8.4.0:
version "8.7.1"
resolved "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz#9ea9d1ec225980c36f94e181d009ab9f4ce4c471"
integrity sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==
dependencies:
buffer-writer "2.0.0"
packet-reader "1.0.0"
pg-connection-string "^2.5.0"
pg-pool "^3.3.0"
pg-pool "^3.4.1"
pg-protocol "^1.5.0"
pg-types "^2.1.0"
pgpass "1.x"
pgpass@1.*, pgpass@1.x:
pgpass@1.x:
version "1.0.2"
resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306"
integrity sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=
@@ -22876,13 +22820,13 @@ pgpass@1.*, pgpass@1.x:
split "^1.0.0"
pgtools@^0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/pgtools/-/pgtools-0.3.0.tgz#ee7decf4183ada28299c63df71e1e73c95b5bc53"
integrity sha512-8NxDCJ8xJ6hOp9hVNZqxi+TZl7hM1Jc8pQyj8DlAbyaWnk5OsGwf3gB/UyDODdOguiim9QzbzPsslp//apO+Uw==
version "0.3.2"
resolved "https://registry.npmjs.org/pgtools/-/pgtools-0.3.2.tgz#df11d54057c889e27ba891664efda69de1b7a0fe"
integrity sha512-o9iI8CrJohpjt3hgoJuEC18oYrt/iLsc3BYtW6kP/0T7EyQ9T/WlnuzyKcC2GtfutREfXCmwaUcbqPrLw8sjng==
dependencies:
bluebird "^3.3.5"
pg "^6.1.0"
pg-connection-string "^0.1.3"
pg "^8.4.0"
pg-connection-string "^2.4.0"
yargs "^5.0.0"
picocolors@^1.0.0:
@@ -23397,11 +23341,6 @@ postcss@^8.1.0, postcss@^8.2.15:
nanoid "^3.1.23"
source-map-js "^0.6.2"
postgres-array@~1.0.0:
version "1.0.3"
resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz#c561fc3b266b21451fc6555384f4986d78ec80f5"
integrity sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ==
postgres-array@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
@@ -23412,7 +23351,7 @@ postgres-bytea@~1.0.0:
resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=
postgres-date@~1.0.0, postgres-date@~1.0.4:
postgres-date@~1.0.4:
version "1.0.5"
resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz#710b27de5f27d550f6e80b5d34f7ba189213c2ee"
integrity sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==
@@ -23627,7 +23566,7 @@ prompts@2.4.0:
kleur "^3.0.3"
sisteransi "^1.0.5"
prompts@^2.0.1, prompts@^2.4.0:
prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.2:
version "2.4.2"
resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
@@ -24072,33 +24011,33 @@ react-dev-utils@^11.0.3:
text-table "0.2.0"
react-dev-utils@^12.0.0-next.47:
version "12.0.0-next.47"
resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0-next.47.tgz#e55c31a05eb30cfd69ca516e8b87d61724e880fb"
integrity sha512-PsE71vP15TZMmp/RZKOJC4fYD5Pvt0+wCoyG3QHclto0d4FyIJI78xGRICOOThZFROqgXYlZP6ddmeybm+jO4w==
version "12.0.0"
resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526"
integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/code-frame" "^7.16.0"
address "^1.1.2"
browserslist "^4.16.5"
chalk "^2.4.2"
browserslist "^4.18.1"
chalk "^4.1.2"
cross-spawn "^7.0.3"
detect-port-alt "^1.1.6"
escape-string-regexp "^2.0.0"
filesize "^6.1.0"
find-up "^4.1.0"
fork-ts-checker-webpack-plugin "^6.0.5"
escape-string-regexp "^4.0.0"
filesize "^8.0.6"
find-up "^5.0.0"
fork-ts-checker-webpack-plugin "^6.5.0"
global-modules "^2.0.0"
globby "^11.0.1"
gzip-size "^5.1.1"
immer "^9.0.6"
globby "^11.0.4"
gzip-size "^6.0.0"
immer "^9.0.7"
is-root "^2.1.0"
loader-utils "^2.0.0"
open "^7.0.2"
loader-utils "^3.2.0"
open "^8.4.0"
pkg-up "^3.1.0"
prompts "^2.4.0"
react-error-overlay "7.0.0-next.54+1465357b"
prompts "^2.4.2"
react-error-overlay "^6.0.10"
recursive-readdir "^2.2.2"
shell-quote "^1.7.2"
strip-ansi "^6.0.0"
shell-quote "^1.7.3"
strip-ansi "^6.0.1"
text-table "^0.2.0"
react-docgen-typescript@^2.0.0:
@@ -24150,15 +24089,10 @@ react-error-boundary@^3.1.0:
dependencies:
"@babel/runtime" "^7.12.5"
react-error-overlay@7.0.0-next.54+1465357b:
version "7.0.0-next.54"
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-7.0.0-next.54.tgz#c1eb5ab86aee15e9552e6d97897b08f2bd06d140"
integrity sha512-b96CiTnZahXPDNH9MKplvt5+jD+BkxDw7q5R3jnkUXze/ux1pLv32BBZmlj0OfCUeMqyz4sAmF+0ccJGVMlpXw==
react-error-overlay@^6.0.9:
version "6.0.9"
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
react-error-overlay@^6.0.10, react-error-overlay@^6.0.9:
version "6.0.10"
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
react-fast-compare@^3.0.1, react-fast-compare@^3.1.1, react-fast-compare@^3.2.0:
version "3.2.0"
@@ -25654,11 +25588,6 @@ semver-store@^0.3.0:
resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@4.3.2:
version "4.3.2"
resolved "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
semver@7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
@@ -25894,7 +25823,7 @@ shell-quote@1.7.2:
resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
shell-quote@^1.7.2:
shell-quote@^1.7.3:
version "1.7.3"
resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123"
integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==
@@ -26145,7 +26074,7 @@ sort-keys@^4.0.0:
dependencies:
is-plain-obj "^2.0.0"
source-list-map@^2.0.0:
source-list-map@^2.0.0, source-list-map@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
@@ -26735,7 +26664,7 @@ strip-ansi@5.2.0, strip-ansi@^5.1.0:
dependencies:
ansi-regex "^4.1.0"
strip-ansi@6.0.0, strip-ansi@^6.0.0:
strip-ansi@6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
@@ -26756,7 +26685,7 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^6.0.1:
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -29035,6 +28964,14 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
source-list-map "^2.0.0"
source-map "~0.6.1"
webpack-sources@^2.2.0:
version "2.3.1"
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd"
integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==
dependencies:
source-list-map "^2.0.1"
source-map "^0.6.1"
webpack-sources@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.0.tgz#b16973bcf844ebcdb3afde32eda1c04d0b90f89d"
@@ -29409,21 +29346,16 @@ ws@7.4.6:
resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
ws@8.3.0:
version "8.3.0"
resolved "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz#7185e252c8973a60d57170175ff55fdbd116070d"
integrity sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==
ws@8.3.0, ws@^7.4.6, ws@^8.3.0:
version "7.5.6"
resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
"ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.2.3, ws@^7.3.1:
version "7.5.0"
resolved "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691"
integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==
ws@^7.4.6, ws@^8.3.0:
version "7.5.6"
resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
ws@^8.1.0:
version "8.2.3"
resolved "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"