Merge branch 'master' into canon-field
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch
|
||||
---
|
||||
|
||||
Use the email from `cfIdentity` instead of `claims` when constructing user profile in order to support Cloudflare Service Tokens.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-proxy-backend': patch
|
||||
'@backstage/plugin-proxy-node': minor
|
||||
---
|
||||
|
||||
Added `proxyEndpointsExtensionPoint` to allow addition of proxy configuration through an extension point in the new backend system.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
The `GerritUrlReader` can now read content from a commit and not only from the top of a branch. The
|
||||
Gitiles URL must contain the full commit `SHA` hash like: `https://gerrit.com/gitiles/repo/+/2846e8dc327ae2f60249983b1c3b96f42f205bae/catalog-info.yaml`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Added `reviewerIds` to the Gitlab Merge Request action.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
A new Gerrit helper function (`buildGerritGitilesArchiveUrlFromLocation`) has been added. This
|
||||
constructs a Gitiles URL to download an archive. It is similar to the existing
|
||||
`buildGerritGitilesArchiveUrl` but also support content referenced by a full commit `SHA`.
|
||||
|
||||
**DEPRECATIONS**: The function `buildGerritGitilesArchiveUrl` is deprecated, use the
|
||||
`buildGerritGitilesArchiveUrlFromLocation` function instead.
|
||||
|
||||
**DEPRECATIONS**: The function `parseGerritGitilesUrl` is deprecated, use the
|
||||
`parseGitilesUrlRef` function instead.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"mode": "exit",
|
||||
"tag": "next",
|
||||
"initialVersions": {
|
||||
"example-app": "0.2.104",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Added action `gitlab:group:migrate` to migrate projects using `bulk_import`
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Added support for `FormDecoratorBlueprint` to create form decorators in the Scaffolder plugin
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Removed the `EXPERIMENTAL_VITE` flag for using Vite as a dev server. If you were using this feature, we recommend switching to Rspack via the `EXPERIMENTAL_RSPACK` flag.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-backstage-openapi': patch
|
||||
---
|
||||
|
||||
docs: Use a valid configuration as example
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-bitbucket-server': patch
|
||||
---
|
||||
|
||||
Updated the `getFile` method in `BitbucketServerClient` to use `this.config.apiBaseUrl` directly for constructing the API request URL, removing the creation of an unnecessary `URL` object. The method now relies on REST API paths for accessing resources instead of direct access, ensuring better alignment with Bitbucket's API conventions.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Updated the path field in the `gitlab:group:ensureExists` action to accept an array of either strings or objects with name and slug fields.
|
||||
@@ -132,6 +132,7 @@ Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugi
|
||||
| -------------------- | ------------ | ------------------------------------------- | ------------ |
|
||||
| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
|
||||
| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` |
|
||||
| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` |
|
||||
| Nick Boldt | Red Hat | [nickboldt](https://github.com/nickboldt) | `nboldt` |
|
||||
| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` |
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ body {
|
||||
background-color: var(--canon-background);
|
||||
color: var(--canon-text-primary);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
--docs-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
|
||||
iframe {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Sidebar } from '../components/Sidebar';
|
||||
import { Toolbar } from '@/components/Toolbar';
|
||||
import { Providers } from './providers';
|
||||
|
||||
import styles from './page.module.css';
|
||||
import { Providers } from './providers';
|
||||
|
||||
import './globals.css';
|
||||
import '/public/core.css';
|
||||
@@ -19,10 +20,6 @@ export default function RootLayout({
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
// const cookieStore = await cookies();
|
||||
// const theme = cookieStore.get('theme')?.value || 'light';
|
||||
// const themeName = cookieStore.get('theme-name')?.value || 'default';
|
||||
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
@@ -34,7 +31,10 @@ export default function RootLayout({
|
||||
<Providers>
|
||||
<div className={styles.global}>
|
||||
<Sidebar />
|
||||
<div className={styles.container}>{children}</div>
|
||||
<div className={styles.container}>
|
||||
<Toolbar />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</Providers>
|
||||
</body>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 350 KiB |
@@ -3,10 +3,15 @@
|
||||
}
|
||||
|
||||
.container {
|
||||
padding-left: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding-left: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.page {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 300px;
|
||||
height: 100vh;
|
||||
color: var(--canon-text-primary);
|
||||
background-color: var(--canon-surface-1);
|
||||
border-right: 1px solid var(--canon-border-base);
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 300px;
|
||||
height: 100vh;
|
||||
color: var(--canon-text-primary);
|
||||
background-color: var(--canon-surface-1);
|
||||
border-right: 1px solid var(--canon-border-base);
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
padding-left: 6px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.logo path {
|
||||
fill: var(--canon-text-primary);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -46,6 +46,13 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-family: var(--docs-font);
|
||||
font-size: var(--canon-font-size-body);
|
||||
font-weight: var(--canon-font-weight-bold);
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -65,3 +72,16 @@
|
||||
.line.active {
|
||||
background-color: var(--canon-surface-2);
|
||||
}
|
||||
|
||||
.lineTitle {
|
||||
font-family: var(--docs-font);
|
||||
font-size: var(--canon-font-size-body);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-text-primary);
|
||||
}
|
||||
|
||||
.lineStatus {
|
||||
font-family: var(--docs-font);
|
||||
font-size: var(--canon-font-size-body);
|
||||
color: var(--canon-text-secondary);
|
||||
}
|
||||
|
||||
@@ -55,9 +55,7 @@ export const Docs = () => {
|
||||
return (
|
||||
<Fragment key={section.title}>
|
||||
<Box marginTop="lg" marginBottom="2xs">
|
||||
<Text variant="body" weight="bold">
|
||||
{section.title}
|
||||
</Text>
|
||||
<div className={styles.sectionTitle}>{section.title}</div>
|
||||
</Box>
|
||||
{section.content.map(item => {
|
||||
const isActive = pathname === `${section.url}/${item.slug}`;
|
||||
@@ -68,17 +66,14 @@ export const Docs = () => {
|
||||
key={item.slug}
|
||||
className={`${styles.line} ${isActive ? styles.active : ''}`}
|
||||
>
|
||||
<Text variant="body">{item.title}</Text>
|
||||
<Text
|
||||
variant="body"
|
||||
style={{ color: 'var(--canon-text-secondary)' }}
|
||||
>
|
||||
<div className={styles.lineTitle}>{item.title}</div>
|
||||
<div className={styles.lineStatus}>
|
||||
{item.status === 'alpha' && 'Alpha'}
|
||||
{item.status === 'beta' && 'Beta'}
|
||||
{item.status === 'inProgress' && 'In Progress'}
|
||||
{item.status === 'stable' && 'Stable'}
|
||||
{item.status === 'deprecated' && 'Deprecated'}
|
||||
</Text>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import styles from './Sidebar.module.css';
|
||||
import { TabsVersion, TabsPages, TabsTheme } from './tabs';
|
||||
import { Docs } from './docs';
|
||||
import { Playground } from './playground';
|
||||
import Link from 'next/link';
|
||||
@@ -7,7 +6,7 @@ import Link from 'next/link';
|
||||
export const Sidebar = () => {
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.logoContainer}>
|
||||
<Link href="/">
|
||||
<svg
|
||||
width="89"
|
||||
@@ -20,15 +19,10 @@ export const Sidebar = () => {
|
||||
<path d="M14.037 16.729a4.237 4.237 0 0 0 4.234-4.24 4.237 4.237 0 0 0-4.234-4.242 4.237 4.237 0 0 0-4.234 4.241 4.237 4.237 0 0 0 4.234 4.24Z" />
|
||||
</svg>
|
||||
</Link>
|
||||
<div className={styles.actions}>
|
||||
<TabsVersion />
|
||||
<TabsTheme />
|
||||
</div>
|
||||
<TabsPages />
|
||||
<div className={styles.menu}>
|
||||
<Docs />
|
||||
<Playground />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.menu}>
|
||||
<Docs />
|
||||
<Playground />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import styles from './Tabs.module.css';
|
||||
import { Tabs } from '@base-ui-components/react/tabs';
|
||||
import { Icon } from '../../../../packages/canon/src/components/Icon';
|
||||
import { Text } from '../../../../packages/canon/src/components/Text';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { usePlayground } from '@/utils/playground-context';
|
||||
|
||||
export const TabsVersion = () => {
|
||||
const { selectedThemeName, setSelectedThemeName } = usePlayground();
|
||||
return (
|
||||
<Tabs.Root
|
||||
className={styles.tabs}
|
||||
onValueChange={setSelectedThemeName}
|
||||
value={selectedThemeName}
|
||||
>
|
||||
<Tabs.List className={styles.list}>
|
||||
<Tabs.Tab className={styles.tab} value="legacy">
|
||||
<Text variant="caption" weight="bold">
|
||||
Theme 1
|
||||
</Text>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab className={styles.tab} value="default">
|
||||
<Text variant="caption" weight="bold">
|
||||
Theme 2
|
||||
</Text>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Indicator className={styles.indicator} />
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsTheme = () => {
|
||||
const { selectedTheme, setSelectedTheme } = usePlayground();
|
||||
|
||||
return (
|
||||
<Tabs.Root
|
||||
className={styles.tabsTheme}
|
||||
onValueChange={setSelectedTheme}
|
||||
value={selectedTheme}
|
||||
>
|
||||
<Tabs.List className={styles.list}>
|
||||
<Tabs.Tab className={styles.tab} value="light">
|
||||
<Icon name="sun" />
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab className={styles.tab} value="dark">
|
||||
<Icon name="moon" />
|
||||
</Tabs.Tab>
|
||||
<Tabs.Indicator className={styles.indicator} />
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsPages = () => {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
const onValueChange = (value: string) => {
|
||||
if (value === 'docs') {
|
||||
router.push('/');
|
||||
} else {
|
||||
router.push('/playground');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs.Root
|
||||
className={styles.tabs}
|
||||
value={pathname.includes('playground') ? 'playground' : 'docs'}
|
||||
onValueChange={onValueChange}
|
||||
>
|
||||
<Tabs.List className={styles.list}>
|
||||
<Tabs.Tab
|
||||
className={styles.tab}
|
||||
value="docs"
|
||||
onClick={() => {
|
||||
router.push('/');
|
||||
}}
|
||||
>
|
||||
<Text variant="caption" weight="bold">
|
||||
Documentation
|
||||
</Text>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
className={styles.tab}
|
||||
value="playground"
|
||||
onClick={() => {
|
||||
router.push('/playground');
|
||||
}}
|
||||
>
|
||||
<Text variant="caption" weight="bold">
|
||||
Playground
|
||||
</Text>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Indicator className={styles.indicator} />
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ThemeSelector } from './theme';
|
||||
import { ThemeNameSelector } from './theme-name';
|
||||
import styles from './styles.module.css';
|
||||
import { Nav } from './nav';
|
||||
|
||||
export const Toolbar = () => {
|
||||
return (
|
||||
<div className={styles.toolbar}>
|
||||
<div>
|
||||
<Nav />
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<ThemeNameSelector />
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
.tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.tabs {
|
||||
display: block;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabsTheme {
|
||||
width: 142px;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--canon-surface-2);
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
gap: var(--canon-spacing-lg);
|
||||
}
|
||||
|
||||
.tab {
|
||||
all: unset;
|
||||
height: 60px;
|
||||
font-family: var(--docs-font);
|
||||
color: var(--canon-text-secondary);
|
||||
font-size: var(--canon-font-size-body);
|
||||
font-weight: var(--canon-font-weight-bold);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: var(--canon-text-primary);
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--canon-text-primary);
|
||||
|
||||
& p {
|
||||
color: var(--canon-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0.25rem 0;
|
||||
border-radius: 0.25rem;
|
||||
outline: 2px solid var(--canon-surface-1);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab p {
|
||||
color: var(--canon-text-secondary) !important;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
left: 0;
|
||||
bottom: -1px;
|
||||
translate: var(--active-tab-left) -50%;
|
||||
width: var(--active-tab-width);
|
||||
height: 1px;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--canon-text-primary);
|
||||
transition-property: translate, width, background-color;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
'use client';
|
||||
|
||||
import { Tabs } from '@base-ui-components/react/tabs';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import styles from './nav.module.css';
|
||||
|
||||
export const Nav = () => {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
const onValueChange = (value: string) => {
|
||||
if (value === 'docs') {
|
||||
router.push('/');
|
||||
} else {
|
||||
router.push('/playground');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs.Root
|
||||
className={styles.tabs}
|
||||
value={pathname.includes('playground') ? 'playground' : 'docs'}
|
||||
onValueChange={onValueChange}
|
||||
>
|
||||
<Tabs.List className={styles.list}>
|
||||
<Tabs.Tab
|
||||
className={styles.tab}
|
||||
value="docs"
|
||||
onClick={() => {
|
||||
router.push('/');
|
||||
}}
|
||||
>
|
||||
Documentation
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
className={styles.tab}
|
||||
value="playground"
|
||||
onClick={() => {
|
||||
router.push('/playground');
|
||||
}}
|
||||
>
|
||||
Playground
|
||||
</Tabs.Tab>
|
||||
<Tabs.Indicator className={styles.indicator} />
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
.toolbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
background-color: var(--canon-surface-1);
|
||||
border-bottom: 1px solid var(--canon-border-base);
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--canon-spacing-md);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--canon-spacing-sm);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
.Select {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
height: 2rem;
|
||||
padding-left: 0.875rem;
|
||||
padding-right: 0.75rem;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: 0.375rem;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
color: var(--color-gray-900);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background-color: var(--canon-surface-2);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--color-blue);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.SelectIcon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.SelectValue {
|
||||
font-size: var(--canon-font-size-body);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
}
|
||||
|
||||
.Positioner {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.Popup {
|
||||
box-sizing: border-box;
|
||||
padding-block: 0.25rem;
|
||||
border-radius: 0.375rem;
|
||||
background-color: canvas;
|
||||
color: var(--color-gray-900);
|
||||
border: 1px solid var(--canon-border-base);
|
||||
padding-inline: 0.25rem;
|
||||
transform-origin: var(--transform-origin);
|
||||
transition: transform 150ms, opacity 150ms;
|
||||
|
||||
&[data-starting-style],
|
||||
&[data-ending-style] {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
&[data-side='none'] {
|
||||
transition: none;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
outline: 1px solid var(--color-gray-200);
|
||||
box-shadow: 0px 10px 15px -3px var(--color-gray-200),
|
||||
0px 4px 6px -4px var(--color-gray-200);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
outline: 1px solid var(--color-gray-300);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.Item {
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1rem;
|
||||
padding-block: 0.5rem;
|
||||
padding-left: 0.625rem;
|
||||
padding-right: 1rem;
|
||||
font-size: var(--canon-font-size-body);
|
||||
font-weight: var(--canon-font-weight-bold);
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
grid-template-columns: 0.75rem 1fr;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
|
||||
[data-side='none'] & {
|
||||
font-size: 1rem;
|
||||
padding-right: 3rem;
|
||||
min-width: calc(var(--anchor-width) + 1rem);
|
||||
}
|
||||
|
||||
&[data-highlighted] {
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
color: var(--color-gray-50);
|
||||
background-color: var(--canon-surface-2);
|
||||
}
|
||||
|
||||
&[data-highlighted]::before {
|
||||
content: '';
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--color-gray-900);
|
||||
}
|
||||
}
|
||||
|
||||
.ItemIndicator {
|
||||
grid-column-start: 1;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ItemIndicatorIcon {
|
||||
display: block;
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.ItemText {
|
||||
grid-column-start: 2;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { Select } from '@base-ui-components/react/select';
|
||||
import styles from './theme-name.module.css';
|
||||
import { Icon } from '@backstage/canon';
|
||||
import { usePlayground } from '@/utils/playground-context';
|
||||
|
||||
const themes = [
|
||||
{ name: 'Backstage Legacy', value: 'legacy' },
|
||||
{ name: 'Backstage Default', value: 'default' },
|
||||
];
|
||||
|
||||
export const ThemeNameSelector = () => {
|
||||
const { selectedThemeName, setSelectedThemeName } = usePlayground();
|
||||
|
||||
return (
|
||||
<Select.Root
|
||||
value={selectedThemeName || 'default'}
|
||||
onValueChange={setSelectedThemeName}
|
||||
>
|
||||
<Select.Trigger className={styles.Select}>
|
||||
<Select.Value
|
||||
className={styles.SelectValue}
|
||||
placeholder="Select a theme"
|
||||
/>
|
||||
<Select.Icon className={styles.SelectIcon}>
|
||||
<Icon name="chevronDown" />
|
||||
</Select.Icon>
|
||||
</Select.Trigger>
|
||||
<Select.Portal>
|
||||
<Select.Positioner className={styles.Positioner} sideOffset={8}>
|
||||
<Select.Popup className={styles.Popup}>
|
||||
{themes.map(({ name, value }) => (
|
||||
<Select.Item className={styles.Item} value={value}>
|
||||
<Select.ItemIndicator className={styles.ItemIndicator}>
|
||||
<Icon name="check" />
|
||||
</Select.ItemIndicator>
|
||||
<Select.ItemText className={styles.ItemText}>
|
||||
{name}
|
||||
</Select.ItemText>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Popup>
|
||||
</Select.Positioner>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
);
|
||||
};
|
||||
+6
-10
@@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
.tabsTheme {
|
||||
width: 142px;
|
||||
width: 100px;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--canon-surface-2);
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
@@ -29,21 +29,17 @@
|
||||
outline: 0;
|
||||
background: none;
|
||||
appearance: none;
|
||||
color: var(--canon-text-secondary) !important;
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.25rem;
|
||||
font-weight: 500;
|
||||
color: var(--canon-text-secondary);
|
||||
user-select: none;
|
||||
height: 2.25rem;
|
||||
height: 2rem;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--canon-text-primary) !important;
|
||||
color: var(--canon-text-primary);
|
||||
|
||||
& p {
|
||||
color: var(--canon-text-primary) !important;
|
||||
color: var(--canon-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +74,7 @@
|
||||
top: 50%;
|
||||
translate: var(--active-tab-left) -50%;
|
||||
width: var(--active-tab-width);
|
||||
height: 1.8rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--canon-surface-1);
|
||||
transition-property: translate, width, background-color;
|
||||
@@ -0,0 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { Tabs } from '@base-ui-components/react/tabs';
|
||||
import { Icon } from '@backstage/canon';
|
||||
import { usePlayground } from '@/utils/playground-context';
|
||||
import styles from './theme.module.css';
|
||||
|
||||
export const ThemeSelector = () => {
|
||||
const { selectedTheme, setSelectedTheme } = usePlayground();
|
||||
|
||||
return (
|
||||
<Tabs.Root
|
||||
className={styles.tabsTheme}
|
||||
onValueChange={setSelectedTheme}
|
||||
value={selectedTheme}
|
||||
>
|
||||
<Tabs.List className={styles.list}>
|
||||
<Tabs.Tab className={styles.tab} value="light">
|
||||
<Icon name="sun" />
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab className={styles.tab} value="dark">
|
||||
<Icon name="moon" />
|
||||
</Tabs.Tab>
|
||||
<Tabs.Indicator className={styles.indicator} />
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +1,64 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import type { MDXComponents } from 'mdx/types';
|
||||
import Image, { ImageProps } from 'next/image';
|
||||
import { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Heading } from '../../packages/canon/src/components/Heading';
|
||||
import { Text } from '../../packages/canon/src/components/Text';
|
||||
import { Box } from '../../packages/canon/src/components/Box';
|
||||
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
// Allows customizing built-in components, e.g. to add styling.
|
||||
h1: ({ children }) => (
|
||||
<Box marginBottom="md" style={{ marginTop: '4rem' }}>
|
||||
<Heading variant="title2">{children as ReactNode}</Heading>
|
||||
<Box style={{ marginTop: '4rem' }}>
|
||||
<h1
|
||||
style={{
|
||||
fontFamily: 'var(--docs-font)',
|
||||
fontSize: '3rem',
|
||||
fontWeight: 'var(--canon-font-weight-bold)',
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
{children as ReactNode}
|
||||
</h1>
|
||||
</Box>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<Box marginTop="2xl" marginBottom="md">
|
||||
<Heading variant="title4">{children as ReactNode}</Heading>
|
||||
<h2
|
||||
style={{
|
||||
fontFamily: 'var(--docs-font)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'var(--canon-font-weight-bold)',
|
||||
}}
|
||||
>
|
||||
{children as ReactNode}
|
||||
</h2>
|
||||
</Box>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<Box marginTop="xl" marginBottom="xs">
|
||||
<Heading variant="title5">{children as ReactNode}</Heading>
|
||||
<h3
|
||||
style={{
|
||||
fontFamily: 'var(--docs-font)',
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 'var(--canon-font-weight-bold)',
|
||||
}}
|
||||
>
|
||||
{children as ReactNode}
|
||||
</h3>
|
||||
</Box>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<Box marginBottom="sm">
|
||||
<Text variant="subtitle">{children as ReactNode}</Text>
|
||||
<p
|
||||
style={{
|
||||
fontFamily: 'var(--docs-font)',
|
||||
fontSize: '1rem',
|
||||
lineHeight: '1.5',
|
||||
}}
|
||||
>
|
||||
{children as ReactNode}
|
||||
</p>
|
||||
</Box>
|
||||
),
|
||||
a: ({ children, href }) => (
|
||||
@@ -42,6 +73,21 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
|
||||
return <CodeBlock lang="tsx" code={codeContent} />;
|
||||
},
|
||||
code: ({ children }) => (
|
||||
<code
|
||||
style={{
|
||||
fontFamily: 'var(--canon-font-monospace)',
|
||||
backgroundColor: 'var(--canon-surface-1)',
|
||||
padding: '0.2rem 0.375rem',
|
||||
borderRadius: '0.25rem',
|
||||
color: 'var(--canon-text-secondary)',
|
||||
border: '1px solid var(--canon-border-base)',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
img: props => (
|
||||
<Image
|
||||
sizes="100vw"
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ More details are provided in dedicated sections of the documentation.
|
||||
|
||||
- [Reading Configuration](./reading.md): How to read configuration in your
|
||||
plugin.
|
||||
- [Writing Configuration](./writing.md): How to provide configuration for your
|
||||
- [Writing Configuration](./writing.md): How to write configuration for your
|
||||
Backstage deployment.
|
||||
- [Defining Configuration](./defining.md): How to define a configuration schema
|
||||
for users of your plugin or package.
|
||||
|
||||
@@ -169,7 +169,7 @@ Kubernetes definitions in a single file and apply them at the same time.
|
||||
Note the volume `type: local`; this creates a volume using local disk on
|
||||
Kubernetes nodes. More likely in a production scenario, you'd want to use a more
|
||||
highly available
|
||||
[type of PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes).
|
||||
[types of PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes).
|
||||
|
||||
Apply the storage volume and claim to the Kubernetes cluster:
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ The following versioning policy applies to all packages:
|
||||
- Breaking changes are noted in the changelog, and documentation is updated.
|
||||
- Breaking changes are prefixed with `**BREAKING**: ` in the changelog.
|
||||
- All public exports are considered stable and will have an entry in the
|
||||
changelog
|
||||
changelog.
|
||||
- Breaking changes are recommended to document a clear upgrade path in the
|
||||
changelog. This may be omitted for newly introduced or unstable packages.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Backstage integrator.
|
||||
## Datadog RUM Events
|
||||
|
||||
See how to install Datadog Events in your app
|
||||
[here](../integrations/datadog-rum/installation.md)
|
||||
[here](../integrations/datadog-rum/installation.md).
|
||||
|
||||
## Logging
|
||||
|
||||
|
||||
@@ -120,3 +120,42 @@ third-parties.
|
||||
|
||||
The same logic applies to headers that are sent from the target back to the
|
||||
frontend.
|
||||
|
||||
### Proxy Extension Endpoint
|
||||
|
||||
The proxy plugin additionally supports a `proxyExtensionEndpoint` which a proxy
|
||||
plugin module can utilize in order to programmatically register additional
|
||||
endpoints, whose payloads are specified exactly as in app-config (described
|
||||
above). Note that endpoints configured in app-config will always override those
|
||||
registered in this manner.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
backend.add(
|
||||
createBackendModule({
|
||||
pluginId: 'proxy',
|
||||
moduleId: 'demo-additional-endpoints',
|
||||
register: reg => {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
proxyEndpoints: proxyEndpointsExtensionPoint,
|
||||
},
|
||||
init: async ({ proxyEndpoints }) => {
|
||||
let largerExampleAuth: string = /* exercise for the reader */;
|
||||
proxyEndpoints.addProxyEndpoints({
|
||||
"/simple-example": "http://simple.example.com:8080",
|
||||
"/larger-example/v1": {
|
||||
target: "http://larger.example.com:8080/svc.v1",
|
||||
credentials: "require",
|
||||
headers: {
|
||||
Authorization: largerExampleAuth
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
@@ -48,9 +48,9 @@ If you are encountering errors related to React, it is likely that the versions
|
||||
|
||||
## Generating temporary patches
|
||||
|
||||
!!!info
|
||||
:::info
|
||||
This feature is experimental and currently only supports Yarn workspaces.
|
||||
!!!
|
||||
:::
|
||||
|
||||
After making local changes to a package in an external workspace you might often want to merge and deploy these changes in your own project. You can use Yarn patches for this purpose, but it can be quite cumbersome to create these patches manually. To make this process easier, you can use `yarn backstage-repo-tools generate-patch` command from the `@backstage/repo-tools` package to generate a patch and resolution entries in the external workspace.
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
title: Crossplane
|
||||
author: terasky.com
|
||||
authorUrl: https://github.com/terasky-oss
|
||||
category: Kubernetes
|
||||
description: View Crossplane Resources in Backstage.
|
||||
documentation: https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugins/crossplane-resources
|
||||
iconUrl: https://github.com/cncf/artwork/blob/main/projects/crossplane/icon/color/crossplane-icon-color.png?raw=true
|
||||
npmPackageName: '@terasky/backstage-plugin-crossplane-resources-frontend'
|
||||
addedDate: '2024-12-30'
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Glean
|
||||
author: Mark Shields
|
||||
authorUrl: https://github.com/beejiujitsu
|
||||
category: Search
|
||||
description: Index Backstage TechDocs-enabled Entities into Glean, the AI-powered work assistant
|
||||
documentation: https://github.com/beejiujitsu/wealthsimple-backstage-plugins/tree/master/plugins/glean-backend
|
||||
iconUrl: /img/glean-logo-circular-white.png
|
||||
npmPackageName: '@beejiujitsu/backstage-plugin-glean-backend'
|
||||
addedDate: '2024-12-30'
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Jaeger Tracing
|
||||
author: Deepankumar
|
||||
authorUrl: https://github.com/deepan10
|
||||
category: Observability
|
||||
description: View Jaeger traces and trace details in Backstage components.
|
||||
documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/jaeger/plugins/jaeger/README.md
|
||||
iconUrl: https://avatars.githubusercontent.com/u/28545596?s=200&v=4
|
||||
npmPackageName: '@backstage-community/plugin-jaeger'
|
||||
tags:
|
||||
- monitoring
|
||||
- tracing
|
||||
- observability
|
||||
addedDate: '2024-12-21'
|
||||
@@ -0,0 +1,9 @@
|
||||
title: Kubernetes Ingestor
|
||||
author: terasky.com
|
||||
authorUrl: https://github.com/terasky-oss
|
||||
category: Kubernetes
|
||||
description: Automatically Ingest Kubernetes Workloads and Crossplane Claims as Components, and XRDs as Templates.
|
||||
documentation: https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugins/kubernetes-ingestor
|
||||
iconUrl: https://avatars.githubusercontent.com/u/13629408
|
||||
npmPackageName: '@terasky/backstage-plugin-kubernetes-ingestor'
|
||||
addedDate: '2024-12-30'
|
||||
@@ -0,0 +1,9 @@
|
||||
title: Kyverno Policy Reports
|
||||
author: terasky.com
|
||||
authorUrl: https://github.com/terasky-oss
|
||||
category: Kubernetes
|
||||
description: View Kyverno Policy reports for Kubernetes resources related to components in Backstage.
|
||||
documentation: https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugins/kyverno-policy-reports
|
||||
iconUrl: https://github.com/cncf/artwork/blob/main/projects/kyverno/icon/color/kyverno-icon-color.png?raw=true
|
||||
npmPackageName: '@terasky/backstage-plugin-kyverno-policy-reports'
|
||||
addedDate: '2025-01-05'
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -73,18 +73,13 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
||||
"@octokit/rest": "^19.0.3",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"history": "^5.0.0",
|
||||
"react": "^18.0.2",
|
||||
"react-dom": "^18.0.2",
|
||||
"react-router": "^6.3.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-use": "^17.2.4",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"zen-observable": "^0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
registerMswTestHooks,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { NotModifiedError, NotFoundError } from '@backstage/errors';
|
||||
import { NotModifiedError } from '@backstage/errors';
|
||||
import {
|
||||
GerritIntegration,
|
||||
readGerritIntegrationConfig,
|
||||
@@ -36,22 +36,11 @@ import { GerritUrlReader } from './GerritUrlReader';
|
||||
import getRawBody from 'raw-body';
|
||||
|
||||
const mockDir = createMockDirectory({ mockOsTmpDir: true });
|
||||
const env = process.env;
|
||||
process.env = { ...env, DISABLE_GERRIT_GITILES_REQUIREMENT: '1' };
|
||||
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
const cloneMock = jest.fn(() => Promise.resolve());
|
||||
jest.mock('./git', () => ({
|
||||
Git: {
|
||||
fromAuth: () => ({
|
||||
clone: cloneMock,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
// Gerrit processor without a gitilesBaseUrl configured
|
||||
const gerritProcessor = new GerritUrlReader(
|
||||
new GerritIntegration(
|
||||
@@ -96,12 +85,10 @@ describe.skip('GerritUrlReader', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockDir.clear();
|
||||
process.env = { ...env, DISABLE_GERRIT_GITILES_REQUIREMENT: '1' };
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = env;
|
||||
});
|
||||
|
||||
describe('reader factory', () => {
|
||||
@@ -186,7 +173,24 @@ describe.skip('GerritUrlReader', () => {
|
||||
const buffer = await result.buffer();
|
||||
expect(buffer.toString()).toBe(responseBuffer.toString());
|
||||
});
|
||||
|
||||
it('should be able to read file contents of a commit as buffer', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://gerrit.com/projects/web%2Fproject/commits/f775f9119c313c7ffc890d7908a45997273434d5/files/LICENSE/content',
|
||||
(_, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body(responseBuffer.toString('base64')),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
const result = await gerritProcessor.readUrl(
|
||||
'https://gerrit.com/web/project/+/f775f9119c313c7ffc890d7908a45997273434d5/LICENSE',
|
||||
);
|
||||
const buffer = await result.buffer();
|
||||
expect(buffer.toString()).toBe(responseBuffer.toString());
|
||||
});
|
||||
it('should be able to read file contents as stream', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
@@ -258,6 +262,7 @@ describe.skip('GerritUrlReader', () => {
|
||||
const treeUrlGitiles =
|
||||
'https://gerrit.com/gitiles/app/web/+/refs/heads/master/';
|
||||
const etag = '52432507a70b677b5674b019c9a46b2e9f29d0a1';
|
||||
const sha = 'f775f9119c313c7ffc890d7908a45997273434d5';
|
||||
const mkdocsContent = 'a repo fetched using git clone';
|
||||
const mdContent = 'doc';
|
||||
const repoArchiveBuffer = fs.readFileSync(
|
||||
@@ -302,6 +307,19 @@ describe.skip('GerritUrlReader', () => {
|
||||
ctx.body(repoArchiveDocsBuffer),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
`https://gerrit.com/gitiles/app/web/\\+archive/${sha}.tar.gz`,
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/x-gzip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=web-refs/heads/master.tar.gz',
|
||||
),
|
||||
ctx.body(repoArchiveBuffer),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -330,8 +348,6 @@ describe.skip('GerritUrlReader', () => {
|
||||
|
||||
const mdFile = await files[1].content();
|
||||
expect(mdFile.toString()).toBe('site_name: Test\n');
|
||||
|
||||
expect(cloneMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws NotModifiedError for matching etags.', async () => {
|
||||
@@ -346,15 +362,17 @@ describe.skip('GerritUrlReader', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws NotFoundError if branch info not found.', async () => {
|
||||
it('throws ResponseError if branch info not found.', async () => {
|
||||
worker.use(
|
||||
rest.get(branchAPIUrl, (_, res, ctx) => {
|
||||
return res(ctx.status(404, 'Not found.'));
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(gerritProcessor.readTree(treeUrl)).rejects.toThrow(
|
||||
NotFoundError,
|
||||
await expect(
|
||||
gerritProcessor.readTree(treeUrl),
|
||||
).rejects.toMatchInlineSnapshot(
|
||||
`[ResponseError: Request failed with 404 Not found.]`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -386,8 +404,20 @@ describe.skip('GerritUrlReader', () => {
|
||||
|
||||
const mdFile = await files[0].content();
|
||||
expect(mdFile.toString()).toBe('# Test\n');
|
||||
});
|
||||
it('throws NotModifiedError for a known commit.', async () => {
|
||||
const shaTreeUrl = `https://gerrit.com/app/web/+/${sha}/`;
|
||||
|
||||
expect(cloneMock).not.toHaveBeenCalled();
|
||||
await expect(
|
||||
gerritProcessor.readTree(shaTreeUrl, { etag: sha }),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
it('can fetch files for a specifc sha.', async () => {
|
||||
const response = await gerritProcessorWithGitiles.readTree(
|
||||
`https://gerrit.com/gitiles/app/web/+/${sha}/`,
|
||||
);
|
||||
|
||||
expect(response.etag).toBe(sha);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,14 +28,18 @@ import { Readable } from 'stream';
|
||||
import {
|
||||
GerritIntegration,
|
||||
ScmIntegrations,
|
||||
buildGerritGitilesArchiveUrl,
|
||||
buildGerritGitilesArchiveUrlFromLocation,
|
||||
getGerritBranchApiUrl,
|
||||
getGerritFileContentsApiUrl,
|
||||
getGerritRequestOptions,
|
||||
parseGerritGitilesUrl,
|
||||
parseGerritJsonResponse,
|
||||
parseGitilesUrlRef,
|
||||
} from '@backstage/integration';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import {
|
||||
NotFoundError,
|
||||
NotModifiedError,
|
||||
ResponseError,
|
||||
} from '@backstage/errors';
|
||||
import { ReadTreeResponseFactory, ReaderFactory } from './types';
|
||||
|
||||
/**
|
||||
@@ -135,34 +139,9 @@ export class GerritUrlReader implements UrlReaderService {
|
||||
url: string,
|
||||
options?: UrlReaderServiceReadTreeOptions,
|
||||
): Promise<UrlReaderServiceReadTreeResponse> {
|
||||
const apiUrl = getGerritBranchApiUrl(this.integration.config, url);
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(apiUrl, {
|
||||
method: 'GET',
|
||||
...getGerritRequestOptions(this.integration.config),
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to read branch state ${url}, ${e}`);
|
||||
}
|
||||
const urlRevision = await this.getRevisionForUrl(url, options);
|
||||
|
||||
if (response.status === 404) {
|
||||
throw new NotFoundError(`Not found: ${url}`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`${url} could not be read as ${apiUrl}, ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
const branchInfo = (await parseGerritJsonResponse(response as any)) as {
|
||||
revision: string;
|
||||
};
|
||||
if (options?.etag === branchInfo.revision) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
return this.readTreeFromGitiles(url, branchInfo.revision, options);
|
||||
return this.readTreeFromGitiles(url, urlRevision, options);
|
||||
}
|
||||
|
||||
async search(): Promise<UrlReaderServiceSearchResponse> {
|
||||
@@ -179,16 +158,10 @@ export class GerritUrlReader implements UrlReaderService {
|
||||
revision: string,
|
||||
options?: UrlReaderServiceReadTreeOptions,
|
||||
) {
|
||||
const { branch, filePath, project } = parseGerritGitilesUrl(
|
||||
const archiveUrl = buildGerritGitilesArchiveUrlFromLocation(
|
||||
this.integration.config,
|
||||
url,
|
||||
);
|
||||
const archiveUrl = buildGerritGitilesArchiveUrl(
|
||||
this.integration.config,
|
||||
project,
|
||||
branch,
|
||||
filePath,
|
||||
);
|
||||
const archiveResponse = await fetch(archiveUrl, {
|
||||
...getGerritRequestOptions(this.integration.config),
|
||||
// TODO(freben): The signal cast is there because pre-3.x versions of
|
||||
@@ -217,4 +190,41 @@ export class GerritUrlReader implements UrlReaderService {
|
||||
stripFirstDirectory: false,
|
||||
});
|
||||
}
|
||||
|
||||
private async getRevisionForUrl(
|
||||
url: string,
|
||||
options?: UrlReaderServiceReadTreeOptions,
|
||||
): Promise<string> {
|
||||
const { ref, refType } = parseGitilesUrlRef(this.integration.config, url);
|
||||
// The url points to a static revision.
|
||||
if (refType === 'sha') {
|
||||
if (options?.etag === ref) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
const apiUrl = getGerritBranchApiUrl(this.integration.config, url);
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(apiUrl, {
|
||||
method: 'GET',
|
||||
...getGerritRequestOptions(this.integration.config),
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to read branch state ${url}, ${e}`);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
const branchInfo = (await parseGerritJsonResponse(response as any)) as {
|
||||
revision: string;
|
||||
};
|
||||
if (options?.etag === branchInfo.revision) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
return branchInfo.revision;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,28 +192,16 @@
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/webpack-sources": "^3.2.3",
|
||||
"@types/yarnpkg__lockfile": "^1.1.4",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"del": "^8.0.0",
|
||||
"msw": "^1.0.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-node-polyfills": "^0.22.0"
|
||||
"nodemon": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
||||
"@rspack/core": "^1.0.10",
|
||||
"@rspack/dev-server": "^1.0.9",
|
||||
"@rspack/plugin-react-refresh": "^1.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.4",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0"
|
||||
"@rspack/plugin-react-refresh": "^1.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@modyfi/vite-plugin-yaml": {
|
||||
"optional": true
|
||||
},
|
||||
"@rspack/core": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -222,18 +210,6 @@
|
||||
},
|
||||
"@rspack/plugin-react-refresh": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitejs/plugin-react": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
},
|
||||
"vite-plugin-html": {
|
||||
"optional": true
|
||||
},
|
||||
"vite-plugin-node-polyfills": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"configSchema": {
|
||||
|
||||
@@ -27,7 +27,6 @@ import { createConfig, resolveBaseUrl, resolveEndpoint } from './config';
|
||||
import { createDetectedModulesEntryPoint } from './packageDetection';
|
||||
import { resolveBundlingPaths, resolveOptionalBundlingPaths } from './paths';
|
||||
import { ServeOptions } from './types';
|
||||
import { hasReactDomClient } from './hasReactDomClient';
|
||||
|
||||
export async function serveBundle(options: ServeOptions) {
|
||||
const paths = resolveBundlingPaths(options);
|
||||
@@ -54,17 +53,11 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
|
||||
const { name } = await fs.readJson(libPaths.resolveTarget('package.json'));
|
||||
|
||||
let webpackServer: WebpackDevServer | undefined = undefined;
|
||||
// @ts-ignore
|
||||
let viteServer: import('vite').ViteDevServer | undefined = undefined;
|
||||
|
||||
let latestFrontendAppConfigs: AppConfig[] = [];
|
||||
|
||||
/** Triggers a full reload of all clients */
|
||||
const triggerReload = () => {
|
||||
if (viteServer) {
|
||||
viteServer.restart();
|
||||
}
|
||||
|
||||
if (webpackServer) {
|
||||
webpackServer.invalidate();
|
||||
|
||||
@@ -147,167 +140,84 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
|
||||
moduleFederation: options.moduleFederation,
|
||||
});
|
||||
|
||||
if (process.env.EXPERIMENTAL_VITE) {
|
||||
const vite = require('vite') as typeof import('vite');
|
||||
const { default: viteReact } =
|
||||
require('@vitejs/plugin-react') as typeof import('@vitejs/plugin-react');
|
||||
const { default: viteYaml } =
|
||||
require('@modyfi/vite-plugin-yaml') as typeof import('@modyfi/vite-plugin-yaml');
|
||||
const { nodePolyfills: viteNodePolyfills } =
|
||||
require('vite-plugin-node-polyfills') as typeof import('vite-plugin-node-polyfills');
|
||||
const { createHtmlPlugin: viteHtml } =
|
||||
require('vite-plugin-html') as typeof import('vite-plugin-html');
|
||||
const bundler = (rspack ?? webpack) as typeof webpack;
|
||||
const DevServer: typeof WebpackDevServer = rspack
|
||||
? require('@rspack/dev-server').RspackDevServer
|
||||
: WebpackDevServer;
|
||||
|
||||
viteServer = await vite.createServer({
|
||||
define: {
|
||||
'process.argv': JSON.stringify(process.argv),
|
||||
'process.env.APP_CONFIG': JSON.stringify(cliConfig.frontendAppConfigs),
|
||||
// This allows for conditional imports of react-dom/client, since there's no way
|
||||
// to check for presence of it in source code without module resolution errors.
|
||||
'process.env.HAS_REACT_DOM_CLIENT': JSON.stringify(hasReactDomClient()),
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
plugins: [
|
||||
{
|
||||
name: 'custom-define',
|
||||
setup(build) {
|
||||
const define = (build.initialOptions.define ||= {});
|
||||
define['process.env.HAS_REACT_DOM_CLIENT'] = JSON.stringify(
|
||||
hasReactDomClient(),
|
||||
);
|
||||
define['process.env.NODE_ENV'] = JSON.stringify('development');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
viteReact(),
|
||||
viteNodePolyfills({
|
||||
include: [
|
||||
'buffer',
|
||||
'events',
|
||||
'fs',
|
||||
'http',
|
||||
'https',
|
||||
'os',
|
||||
'path',
|
||||
'process',
|
||||
'querystring',
|
||||
'stream',
|
||||
'url',
|
||||
'util',
|
||||
'zlib',
|
||||
],
|
||||
globals: {
|
||||
global: true,
|
||||
Buffer: true,
|
||||
process: true,
|
||||
},
|
||||
}),
|
||||
viteYaml(),
|
||||
viteHtml({
|
||||
entry: paths.targetEntry,
|
||||
// todo(blam): we should look at contributing to thPe plugin here
|
||||
// to support absolute paths, but works in the interim at least.
|
||||
template: 'public/index.html',
|
||||
inject: {
|
||||
data: {
|
||||
config: frontendConfig,
|
||||
publicPath: config.output?.publicPath,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
host,
|
||||
port,
|
||||
},
|
||||
publicDir: paths.targetPublic,
|
||||
root: paths.targetPath,
|
||||
});
|
||||
} else {
|
||||
const bundler = (rspack ?? webpack) as typeof webpack;
|
||||
const DevServer: typeof WebpackDevServer = rspack
|
||||
? require('@rspack/dev-server').RspackDevServer
|
||||
: WebpackDevServer;
|
||||
|
||||
if (rspack) {
|
||||
console.log(
|
||||
chalk.yellow(`⚠️ WARNING: Using experimental RSPack dev server.`),
|
||||
);
|
||||
}
|
||||
|
||||
const publicPaths = await resolveOptionalBundlingPaths({
|
||||
entry: 'src/index-public-experimental',
|
||||
dist: 'dist/public',
|
||||
});
|
||||
if (publicPaths) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`⚠️ WARNING: The app /public entry point is an experimental feature that may receive immediate breaking changes.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
const compiler = publicPaths
|
||||
? bundler([config, await createConfig(publicPaths, commonConfigOptions)])
|
||||
: bundler(config);
|
||||
|
||||
webpackServer = new DevServer(
|
||||
{
|
||||
hot: !process.env.CI,
|
||||
devMiddleware: {
|
||||
publicPath: config.output?.publicPath as string,
|
||||
stats: 'errors-warnings',
|
||||
},
|
||||
static: paths.targetPublic
|
||||
? {
|
||||
publicPath: config.output?.publicPath as string,
|
||||
directory: paths.targetPublic,
|
||||
}
|
||||
: undefined,
|
||||
historyApiFallback:
|
||||
options.moduleFederation?.mode === 'remote'
|
||||
? false
|
||||
: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
|
||||
// The index needs to be rewritten relative to the new public path, including subroutes.
|
||||
index: `${config.output?.publicPath}index.html`,
|
||||
},
|
||||
server:
|
||||
url.protocol === 'https:'
|
||||
? {
|
||||
type: 'https',
|
||||
options: {
|
||||
cert: fullConfig.getString('app.https.certificate.cert'),
|
||||
key: fullConfig.getString('app.https.certificate.key'),
|
||||
},
|
||||
}
|
||||
: {},
|
||||
host,
|
||||
port,
|
||||
proxy: targetPkg.proxy,
|
||||
// When the dev server is behind a proxy, the host and public hostname differ
|
||||
allowedHosts: [url.hostname],
|
||||
client: {
|
||||
webSocketURL: { hostname: host, port },
|
||||
},
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers':
|
||||
'X-Requested-With, content-type, Authorization',
|
||||
},
|
||||
},
|
||||
compiler,
|
||||
if (rspack) {
|
||||
console.log(
|
||||
chalk.yellow(`⚠️ WARNING: Using experimental RSPack dev server.`),
|
||||
);
|
||||
}
|
||||
|
||||
await viteServer?.listen();
|
||||
const publicPaths = await resolveOptionalBundlingPaths({
|
||||
entry: 'src/index-public-experimental',
|
||||
dist: 'dist/public',
|
||||
});
|
||||
if (publicPaths) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`⚠️ WARNING: The app /public entry point is an experimental feature that may receive immediate breaking changes.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
const compiler = publicPaths
|
||||
? bundler([config, await createConfig(publicPaths, commonConfigOptions)])
|
||||
: bundler(config);
|
||||
|
||||
webpackServer = new DevServer(
|
||||
{
|
||||
hot: !process.env.CI,
|
||||
devMiddleware: {
|
||||
publicPath: config.output?.publicPath as string,
|
||||
stats: 'errors-warnings',
|
||||
},
|
||||
static: paths.targetPublic
|
||||
? {
|
||||
publicPath: config.output?.publicPath as string,
|
||||
directory: paths.targetPublic,
|
||||
}
|
||||
: undefined,
|
||||
historyApiFallback:
|
||||
options.moduleFederation?.mode === 'remote'
|
||||
? false
|
||||
: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
|
||||
// The index needs to be rewritten relative to the new public path, including subroutes.
|
||||
index: `${config.output?.publicPath}index.html`,
|
||||
},
|
||||
server:
|
||||
url.protocol === 'https:'
|
||||
? {
|
||||
type: 'https',
|
||||
options: {
|
||||
cert: fullConfig.getString('app.https.certificate.cert'),
|
||||
key: fullConfig.getString('app.https.certificate.key'),
|
||||
},
|
||||
}
|
||||
: {},
|
||||
host,
|
||||
port,
|
||||
proxy: targetPkg.proxy,
|
||||
// When the dev server is behind a proxy, the host and public hostname differ
|
||||
allowedHosts: [url.hostname],
|
||||
client: {
|
||||
webSocketURL: { hostname: host, port },
|
||||
},
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers':
|
||||
'X-Requested-With, content-type, Authorization',
|
||||
},
|
||||
},
|
||||
compiler,
|
||||
);
|
||||
|
||||
await new Promise<void>(async (resolve, reject) => {
|
||||
if (webpackServer) {
|
||||
webpackServer.startCallback((err?: Error) => {
|
||||
@@ -330,7 +240,6 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
|
||||
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
||||
process.on(signal, () => {
|
||||
webpackServer?.stop();
|
||||
viteServer?.close();
|
||||
// exit instead of resolve. The process is shutting down and resolving a promise here logs an error
|
||||
process.exit();
|
||||
});
|
||||
|
||||
@@ -299,7 +299,7 @@ export type BitbucketServerIntegrationConfig = {
|
||||
password?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export function buildGerritGitilesArchiveUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
project: string,
|
||||
@@ -307,6 +307,12 @@ export function buildGerritGitilesArchiveUrl(
|
||||
filePath: string,
|
||||
): string;
|
||||
|
||||
// @public
|
||||
export function buildGerritGitilesArchiveUrlFromLocation(
|
||||
config: GerritIntegrationConfig,
|
||||
url: string,
|
||||
): string;
|
||||
|
||||
// @public
|
||||
export class DefaultAzureCredentialsManager implements AzureCredentialsManager {
|
||||
static fromIntegrations(
|
||||
@@ -802,7 +808,7 @@ export interface IntegrationsByType {
|
||||
harness: ScmIntegrationsGroup<HarnessIntegration>;
|
||||
}
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export function parseGerritGitilesUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
url: string,
|
||||
|
||||
@@ -21,6 +21,7 @@ import { registerMswTestHooks } from '../helpers';
|
||||
import { GerritIntegrationConfig } from './config';
|
||||
import {
|
||||
buildGerritGitilesArchiveUrl,
|
||||
buildGerritGitilesArchiveUrlFromLocation,
|
||||
buildGerritGitilesUrl,
|
||||
getGerritBranchApiUrl,
|
||||
getGerritCloneRepoUrl,
|
||||
@@ -115,6 +116,94 @@ describe('gerrit core', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildGerritGitilesArchiveUrlFromLocation', () => {
|
||||
const config: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
baseUrl: 'https://gerrit.com',
|
||||
gitilesBaseUrl: 'https://gerrit.com/gitiles',
|
||||
};
|
||||
const configWithPath: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
baseUrl: 'https://gerrit.com/gerrit',
|
||||
gitilesBaseUrl: 'https://gerrit.com/gerrit/plugins/gitiles',
|
||||
};
|
||||
const configWithDedicatedGitiles: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
baseUrl: 'https://gerrit.com/gerrit',
|
||||
gitilesBaseUrl: 'https://dedicated-gitiles-server.com/gerrit/gitiles',
|
||||
};
|
||||
it('can create an archive url for a branch', () => {
|
||||
expect(
|
||||
buildGerritGitilesArchiveUrlFromLocation(
|
||||
config,
|
||||
'https://gerrit.com/gitiles/repo/+/refs/heads/dev/',
|
||||
),
|
||||
).toEqual(
|
||||
'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev.tar.gz',
|
||||
);
|
||||
});
|
||||
it('can create an archive url for a sha', () => {
|
||||
expect(
|
||||
buildGerritGitilesArchiveUrlFromLocation(
|
||||
config,
|
||||
'https://gerrit.com/gitiles/repo/+/2846e8dc327ae2f60249983b1c3b96f42f205bae/',
|
||||
),
|
||||
).toEqual(
|
||||
'https://gerrit.com/gitiles/repo/+archive/2846e8dc327ae2f60249983b1c3b96f42f205bae.tar.gz',
|
||||
);
|
||||
});
|
||||
it('can create an archive url for a sha with a specific directory', () => {
|
||||
expect(
|
||||
buildGerritGitilesArchiveUrlFromLocation(
|
||||
config,
|
||||
'https://gerrit.com/gitiles/repo/+/2846e8dc327ae2f60249983b1c3b96f42f205bae/docs',
|
||||
),
|
||||
).toEqual(
|
||||
'https://gerrit.com/gitiles/repo/+archive/2846e8dc327ae2f60249983b1c3b96f42f205bae/docs.tar.gz',
|
||||
);
|
||||
});
|
||||
it('can create an archive url for a specific directory', () => {
|
||||
expect(
|
||||
buildGerritGitilesArchiveUrlFromLocation(
|
||||
config,
|
||||
'https://gerrit.com/gitiles/repo/+/refs/heads/dev/docs/',
|
||||
),
|
||||
).toEqual(
|
||||
'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz',
|
||||
);
|
||||
});
|
||||
it('can create an authenticated url when auth is enabled and an url-path is used', () => {
|
||||
const authConfig = {
|
||||
...configWithPath,
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
};
|
||||
expect(
|
||||
buildGerritGitilesArchiveUrlFromLocation(
|
||||
authConfig,
|
||||
'https://gerrit.com/gerrit/plugins/gitiles/repo/+/refs/heads/dev/docs/',
|
||||
),
|
||||
).toEqual(
|
||||
'https://gerrit.com/gerrit/a/plugins/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz',
|
||||
);
|
||||
});
|
||||
it('Cannot build an authenticated url when a dedicated Gitiles server is used', () => {
|
||||
const authConfig = {
|
||||
...configWithDedicatedGitiles,
|
||||
username: 'username',
|
||||
password: 'password',
|
||||
};
|
||||
expect(() =>
|
||||
buildGerritGitilesArchiveUrlFromLocation(
|
||||
authConfig,
|
||||
'https://gerrit.com/gitiles/repo/+/refs/heads/dev/',
|
||||
),
|
||||
).toThrow(
|
||||
'Since the baseUrl (Gerrit) is not part of the gitilesBaseUrl, an authentication URL could not be constructed.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildGerritGitilesUrl', () => {
|
||||
it('can create an url from arguments', () => {
|
||||
const config: GerritIntegrationConfig = {
|
||||
@@ -426,6 +515,39 @@ describe('gerrit core', () => {
|
||||
'https://gerrit.com/a/projects/web%2Fproject/branches/master/files/README.md/content',
|
||||
);
|
||||
});
|
||||
it('can create an authenticated url for a commit.', () => {
|
||||
const authConfig: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
baseUrl: 'https://gerrit.com',
|
||||
gitilesBaseUrl: 'https://gerrit.com',
|
||||
username: 'u',
|
||||
password: 'u',
|
||||
};
|
||||
|
||||
const authFileContentUrl = getGerritFileContentsApiUrl(
|
||||
authConfig,
|
||||
'https://gerrit.com/web/project/+/157f862803d45b9d269f0e390f88aece1ded51e8/README.md',
|
||||
);
|
||||
expect(authFileContentUrl).toEqual(
|
||||
'https://gerrit.com/a/projects/web%2Fproject/commits/157f862803d45b9d269f0e390f88aece1ded51e8/files/README.md/content',
|
||||
);
|
||||
});
|
||||
it('will throw for unsupported ref types (tag).', () => {
|
||||
const authConfig: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
baseUrl: 'https://gerrit.com',
|
||||
gitilesBaseUrl: 'https://gerrit.com',
|
||||
username: 'u',
|
||||
password: 'u',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
getGerritFileContentsApiUrl(
|
||||
authConfig,
|
||||
'https://gerrit.com/modules/events-broker/+/refs/tags/v3.5.6/src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java',
|
||||
),
|
||||
).toThrow(/gitiles ref type/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseGerritJsonResponse', () => {
|
||||
|
||||
@@ -41,8 +41,9 @@ const GERRIT_BODY_PREFIX = ")]}'";
|
||||
*
|
||||
* @param url - An URL pointing to a file stored in git.
|
||||
* @public
|
||||
* @deprecated `parseGerritGitilesUrl` is deprecated. Use
|
||||
* {@link parseGitilesUrlRef} instead.
|
||||
*/
|
||||
|
||||
export function parseGerritGitilesUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
url: string,
|
||||
@@ -215,6 +216,8 @@ export function buildGerritGitilesUrl(
|
||||
* @param branch - The branch we will target.
|
||||
* @param filePath - The absolute file path.
|
||||
* @public
|
||||
* @deprecated `buildGerritGitilesArchiveUrl` is deprecated. Use
|
||||
* {@link buildGerritGitilesArchiveUrlFromLocation} instead.
|
||||
*/
|
||||
export function buildGerritGitilesArchiveUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
@@ -229,6 +232,38 @@ export function buildGerritGitilesArchiveUrl(
|
||||
)}/${project}/+archive/refs/heads/${branch}${archiveName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Gerrit Gitiles archive url from a Gitiles url.
|
||||
*
|
||||
* @param config - A Gerrit provider config.
|
||||
* @param url - The gitiles url
|
||||
* @public
|
||||
*/
|
||||
export function buildGerritGitilesArchiveUrlFromLocation(
|
||||
config: GerritIntegrationConfig,
|
||||
url: string,
|
||||
): string {
|
||||
const {
|
||||
path: filePath,
|
||||
ref,
|
||||
project,
|
||||
refType,
|
||||
} = parseGitilesUrlRef(config, url);
|
||||
const archiveName =
|
||||
filePath === '/' || filePath === '' ? '.tar.gz' : `/${filePath}.tar.gz`;
|
||||
if (refType === 'branch') {
|
||||
return `${getGitilesAuthenticationUrl(
|
||||
config,
|
||||
)}/${project}/+archive/refs/heads/${ref}${archiveName}`;
|
||||
}
|
||||
if (refType === 'sha') {
|
||||
return `${getGitilesAuthenticationUrl(
|
||||
config,
|
||||
)}/${project}/+archive/${ref}${archiveName}`;
|
||||
}
|
||||
throw new Error(`Unsupported gitiles ref type: ${refType}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication prefix.
|
||||
*
|
||||
@@ -324,13 +359,25 @@ export function getGerritFileContentsApiUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
url: string,
|
||||
) {
|
||||
const { branch, filePath, project } = parseGerritGitilesUrl(config, url);
|
||||
const { ref, refType, path, project } = parseGitilesUrlRef(config, url);
|
||||
|
||||
return `${config.baseUrl}${getAuthenticationPrefix(
|
||||
config,
|
||||
)}projects/${encodeURIComponent(
|
||||
project,
|
||||
)}/branches/${branch}/files/${encodeURIComponent(filePath)}/content`;
|
||||
// https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-content
|
||||
if (refType === 'branch') {
|
||||
return `${config.baseUrl}${getAuthenticationPrefix(
|
||||
config,
|
||||
)}projects/${encodeURIComponent(
|
||||
project,
|
||||
)}/branches/${ref}/files/${encodeURIComponent(path)}/content`;
|
||||
}
|
||||
// https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-content-from-commit
|
||||
if (refType === 'sha') {
|
||||
return `${config.baseUrl}${getAuthenticationPrefix(
|
||||
config,
|
||||
)}projects/${encodeURIComponent(
|
||||
project,
|
||||
)}/commits/${ref}/files/${encodeURIComponent(path)}/content`;
|
||||
}
|
||||
throw new Error(`Unsupported gitiles ref type: ${refType}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
} from './config';
|
||||
export {
|
||||
buildGerritGitilesArchiveUrl,
|
||||
buildGerritGitilesArchiveUrlFromLocation,
|
||||
getGerritBranchApiUrl,
|
||||
getGerritCloneRepoUrl,
|
||||
getGerritFileContentsApiUrl,
|
||||
|
||||
@@ -18,14 +18,14 @@ import { mockServices } from '@backstage/backend-test-utils';
|
||||
import { createCloudflareAccessAuthenticator } from './authenticator';
|
||||
|
||||
describe('authenticator', () => {
|
||||
it('createCloudflareAccessAuthenticator works', async () => {
|
||||
it('works for normal users', async () => {
|
||||
const auth = createCloudflareAccessAuthenticator({
|
||||
cache: mockServices.cache.mock(),
|
||||
});
|
||||
|
||||
const profile = await auth.defaultProfileTransform(
|
||||
{
|
||||
cfIdentity: { name: 'Name' } as any,
|
||||
cfIdentity: { name: 'Name', email: 'hello@example.com' } as any,
|
||||
claims: { email: 'hello@example.com' } as any,
|
||||
token: 'fake',
|
||||
},
|
||||
@@ -38,4 +38,25 @@ describe('authenticator', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('works for service tokens', async () => {
|
||||
const auth = createCloudflareAccessAuthenticator({
|
||||
cache: mockServices.cache.mock(),
|
||||
});
|
||||
|
||||
const profile = await auth.defaultProfileTransform(
|
||||
{
|
||||
cfIdentity: { name: 'Name', email: 'hello@example.com' } as any,
|
||||
claims: {} as any,
|
||||
token: 'fake',
|
||||
},
|
||||
{} as any,
|
||||
);
|
||||
expect(profile).toEqual({
|
||||
profile: {
|
||||
displayName: 'Name',
|
||||
email: 'hello@example.com',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ export function createCloudflareAccessAuthenticator(options?: {
|
||||
async defaultProfileTransform(result: CloudflareAccessResult) {
|
||||
return {
|
||||
profile: {
|
||||
email: result.claims.email,
|
||||
email: result.cfIdentity.email,
|
||||
displayName: result.cfIdentity.name,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,14 +26,14 @@ catalog:
|
||||
backstageOpenapi:
|
||||
plugins:
|
||||
- catalog
|
||||
- todo
|
||||
- events
|
||||
- search
|
||||
entityOverrides: # All optional
|
||||
metadata:
|
||||
name: 'my name'
|
||||
name: 'my-name'
|
||||
title: 'my title'
|
||||
spec:
|
||||
owner: 'my team'
|
||||
owner: 'my-team'
|
||||
```
|
||||
|
||||
We will attempt to load each plugin's OpenAPI spec hosted at `${pluginRoute}/openapi.json`. These are automatically added if you are using `@backstage/backend-openapi-utils`'s `createValidatedOpenApiRouter`.
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ describe('BitbucketServerClient', () => {
|
||||
it('getFile', async () => {
|
||||
server.use(
|
||||
rest.get(
|
||||
`https://${config.host}/projects/test-project/repos/test-repo/raw/catalog-info.yaml`,
|
||||
`${config.apiBaseUrl}/projects/test-project/repos/test-repo/raw/catalog-info.yaml`,
|
||||
(req, res, ctx) => {
|
||||
if (
|
||||
req.headers.get('authorization') !==
|
||||
|
||||
@@ -77,9 +77,8 @@ export class BitbucketServerClient {
|
||||
repo: string;
|
||||
path: string;
|
||||
}): Promise<Response> {
|
||||
const base = new URL(this.config.apiBaseUrl);
|
||||
return throttledFetch(
|
||||
`${base.protocol}//${base.host}/projects/${options.projectKey}/repos/${options.repo}/raw/${options.path}`,
|
||||
`${this.config.apiBaseUrl}/projects/${options.projectKey}/repos/${options.repo}/raw/${options.path}`,
|
||||
getBitbucketServerRequestOptions(this.config),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,35 @@
|
||||
*/
|
||||
|
||||
import { createBackend } from '@backstage/backend-defaults';
|
||||
import { createBackendModule } from '@backstage/backend-plugin-api';
|
||||
import { proxyEndpointsExtensionPoint } from '@backstage/plugin-proxy-node/alpha';
|
||||
|
||||
const backend = createBackend();
|
||||
backend.add(import('../src/alpha'));
|
||||
|
||||
backend.add(
|
||||
createBackendModule({
|
||||
pluginId: 'proxy',
|
||||
moduleId: 'demo-additional-endpoints',
|
||||
register: reg => {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
proxyEndpoints: proxyEndpointsExtensionPoint,
|
||||
},
|
||||
init: async ({ proxyEndpoints }) => {
|
||||
proxyEndpoints.addProxyEndpoints({
|
||||
...Object.fromEntries(
|
||||
['foo', 'bar', 'baz'].map(msv => [
|
||||
`/${msv}`,
|
||||
{ target: `http://${msv}.org` },
|
||||
]),
|
||||
),
|
||||
'/gocd': 'http://cannot-override-config.no',
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
backend.start();
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"@backstage/backend-common": "^0.25.0",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-proxy-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { DiscoveryService } from '@backstage/backend-plugin-api';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
import { ProxyConfig } from '@backstage/plugin-proxy-node/alpha';
|
||||
import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public @deprecated
|
||||
@@ -18,6 +19,8 @@ export default proxyPlugin;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
additionalEndpoints?: ProxyConfig;
|
||||
// (undocumented)
|
||||
config: RootConfigService;
|
||||
// (undocumented)
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createRouterInternal } from './service/router';
|
||||
import { proxyEndpointsExtensionPoint } from '@backstage/plugin-proxy-node/alpha';
|
||||
|
||||
/**
|
||||
* The proxy backend plugin.
|
||||
@@ -29,6 +30,13 @@ import { createRouterInternal } from './service/router';
|
||||
export const proxyPlugin = createBackendPlugin({
|
||||
pluginId: 'proxy',
|
||||
register(env) {
|
||||
const additionalEndpoints = {};
|
||||
|
||||
env.registerExtensionPoint(proxyEndpointsExtensionPoint, {
|
||||
addProxyEndpoints(endpoints) {
|
||||
Object.assign(additionalEndpoints, endpoints);
|
||||
},
|
||||
});
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
@@ -42,6 +50,7 @@ export const proxyPlugin = createBackendPlugin({
|
||||
discovery,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
httpRouterService: httpRouter,
|
||||
additionalEndpoints,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,7 +20,6 @@ import Router from 'express-promise-router';
|
||||
import {
|
||||
createProxyMiddleware,
|
||||
fixRequestBody,
|
||||
Options,
|
||||
RequestHandler,
|
||||
} from 'http-proxy-middleware';
|
||||
import { Logger } from 'winston';
|
||||
@@ -31,6 +30,7 @@ import {
|
||||
HttpRouterService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { ProxyConfig } from '@backstage/plugin-proxy-node/alpha';
|
||||
|
||||
// A list of headers that are always forwarded to the proxy targets.
|
||||
const safeForwardHeaders = [
|
||||
@@ -63,12 +63,7 @@ export interface RouterOptions {
|
||||
discovery: DiscoveryService;
|
||||
skipInvalidProxies?: boolean;
|
||||
reviveConsumedRequestBodies?: boolean;
|
||||
}
|
||||
|
||||
export interface ProxyConfig extends Options {
|
||||
allowedMethods?: string[];
|
||||
allowedHeaders?: string[];
|
||||
reviveRequestBody?: boolean;
|
||||
additionalEndpoints?: ProxyConfig;
|
||||
}
|
||||
|
||||
// Creates a proxy middleware, possibly with defaults added on top of the
|
||||
@@ -87,7 +82,7 @@ export function buildMiddleware(
|
||||
fullConfig = { target: config };
|
||||
credentialsPolicy = 'require';
|
||||
} else {
|
||||
const { credentials, ...rest } = config as any;
|
||||
const { credentials, ...rest } = config;
|
||||
fullConfig = rest;
|
||||
credentialsPolicy = credentials ?? 'require';
|
||||
}
|
||||
@@ -315,7 +310,11 @@ export async function createRouterInternal(
|
||||
const externalUrl = await options.discovery.getExternalBaseUrl('proxy');
|
||||
const { pathname: pathPrefix } = new URL(externalUrl);
|
||||
|
||||
const proxyConfig = readProxyConfig(options.config, options.logger);
|
||||
const proxyConfig: ProxyConfig = {
|
||||
...(options.additionalEndpoints ?? {}),
|
||||
...readProxyConfig(options.config, options.logger),
|
||||
};
|
||||
|
||||
configureMiddlewares(
|
||||
proxyOptions,
|
||||
currentRouter,
|
||||
@@ -358,10 +357,10 @@ function configureMiddlewares(
|
||||
},
|
||||
router: express.Router,
|
||||
pathPrefix: string,
|
||||
proxyConfig: any,
|
||||
proxyConfig: ProxyConfig,
|
||||
httpRouterService?: HttpRouterService,
|
||||
) {
|
||||
Object.entries<any>(proxyConfig).forEach(([route, proxyRouteConfig]) => {
|
||||
Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => {
|
||||
try {
|
||||
router.use(
|
||||
route,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,5 @@
|
||||
# backstage-plugin-proxy-node-node
|
||||
|
||||
Welcome to the Node.js library package for the proxy-node plugin!
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: backstage-plugin-proxy-node
|
||||
title: '@backstage/plugin-proxy-node'
|
||||
description: The plugin-proxy-node module for @backstage/plugin-proxy-backend
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-node-library
|
||||
owner: maintainers
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "@backstage/plugin-proxy-node",
|
||||
"version": "0.0.0",
|
||||
"description": "The plugin-proxy-node module for @backstage/plugin-proxy-backend",
|
||||
"backstage": {
|
||||
"role": "node-library",
|
||||
"pluginId": "proxy-backend",
|
||||
"pluginPackages": [
|
||||
"@backstage/plugin-proxy-node"
|
||||
]
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/proxy-node"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
"./alpha": "./src/alpha/index.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "src/alpha/index.ts",
|
||||
"types": "src/alpha/index.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"alpha": [
|
||||
"src/alpha/index.ts"
|
||||
],
|
||||
"package.json": [
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"start": "backstage-cli package start",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"http-proxy-middleware": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@types/http-proxy-middleware": "^1.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
## API Report File for "@backstage/plugin-proxy-node"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { ExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { Options } from 'http-proxy-middleware';
|
||||
|
||||
// @alpha
|
||||
export interface ProxyConfig extends Options {
|
||||
// (undocumented)
|
||||
allowedHeaders?: string[];
|
||||
// (undocumented)
|
||||
allowedMethods?: string[];
|
||||
// (undocumented)
|
||||
credentials?: 'require' | 'forward' | 'dangerously-allow-unauthenticated';
|
||||
// (undocumented)
|
||||
reviveRequestBody?: boolean;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export interface ProxyEndpointsExtensionPoint {
|
||||
// (undocumented)
|
||||
addProxyEndpoints(endpoints: Record<string, string | ProxyConfig>): void;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const proxyEndpointsExtensionPoint: ExtensionPoint<ProxyEndpointsExtensionPoint>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2024 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 { createExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { Options } from 'http-proxy-middleware';
|
||||
|
||||
/**
|
||||
* http-proxy-middleware proxy config interface.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface ProxyConfig extends Options {
|
||||
allowedMethods?: string[];
|
||||
allowedHeaders?: string[];
|
||||
reviveRequestBody?: boolean;
|
||||
credentials?: 'require' | 'forward' | 'dangerously-allow-unauthenticated';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point interface for managing proxy endpoints.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface ProxyEndpointsExtensionPoint {
|
||||
addProxyEndpoints(endpoints: Record<string, string | ProxyConfig>): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for managing proxy endpoints.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const proxyEndpointsExtensionPoint =
|
||||
createExtensionPoint<ProxyEndpointsExtensionPoint>({
|
||||
id: 'proxy.endpoints',
|
||||
});
|
||||
@@ -14,7 +14,13 @@ export const createGitlabGroupEnsureExistsAction: (options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) => TemplateAction<
|
||||
{
|
||||
path: string[];
|
||||
path: (
|
||||
| string
|
||||
| {
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
)[];
|
||||
repoUrl: string;
|
||||
token?: string | undefined;
|
||||
},
|
||||
@@ -202,6 +208,7 @@ export const createPublishGitlabMergeRequestAction: (options: {
|
||||
projectid?: string | undefined;
|
||||
removeSourceBranch?: boolean | undefined;
|
||||
assignee?: string | undefined;
|
||||
reviewers?: string[] | undefined;
|
||||
},
|
||||
JsonObject
|
||||
>;
|
||||
|
||||
+47
@@ -196,4 +196,51 @@ describe('gitlab:group:ensureExists', () => {
|
||||
|
||||
expect(mockContext.output).toHaveBeenCalledWith('groupId', 42);
|
||||
});
|
||||
|
||||
it(`Should ${examples[4].description}`, async () => {
|
||||
mockGitlabClient.Groups.search.mockResolvedValue([
|
||||
{
|
||||
id: 1,
|
||||
full_path: 'group1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
full_path: 'group1/group2',
|
||||
},
|
||||
]);
|
||||
mockGitlabClient.Groups.create.mockResolvedValue({
|
||||
id: 3,
|
||||
full_path: 'group1/group2/group3',
|
||||
});
|
||||
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
token: 'tokenlols',
|
||||
apiBaseUrl: 'https://api.gitlab.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
|
||||
const action = createGitlabGroupEnsureExistsAction({ integrations });
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: yaml.parse(examples[4].example).steps[0].input,
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.Groups.create).toHaveBeenCalledWith(
|
||||
'Group 3',
|
||||
'group3',
|
||||
{
|
||||
parentId: 2,
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockContext.output).toHaveBeenCalledWith('groupId', 3);
|
||||
});
|
||||
});
|
||||
|
||||
+20
@@ -82,4 +82,24 @@ export const examples: TemplateExample[] = [
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Create a group nested within another group using objects',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'gitlabGroup',
|
||||
name: 'Group',
|
||||
action: 'gitlab:group:ensureExists',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com',
|
||||
path: [
|
||||
{ name: 'Group 1', slug: 'group1' },
|
||||
{ name: 'Group 2', slug: 'group2' },
|
||||
{ name: 'Group 3', slug: 'group3' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
+40
-1
@@ -62,7 +62,7 @@ describe('gitlab:group:ensureExists', () => {
|
||||
|
||||
const mockContext = createMockActionContext();
|
||||
|
||||
it('should create a new group if it does not exists', async () => {
|
||||
it('should create a new group from string if it does not exists', async () => {
|
||||
mockGitlabClient.Groups.search.mockResolvedValue([
|
||||
{
|
||||
id: 1,
|
||||
@@ -94,6 +94,45 @@ describe('gitlab:group:ensureExists', () => {
|
||||
expect(mockContext.output).toHaveBeenCalledWith('groupId', 3);
|
||||
});
|
||||
|
||||
it('should create a new group from object if it does not exists', async () => {
|
||||
mockGitlabClient.Groups.search.mockResolvedValue([
|
||||
{
|
||||
id: 1,
|
||||
full_path: 'bar',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
full_path: 'foo',
|
||||
},
|
||||
]);
|
||||
|
||||
mockGitlabClient.Groups.create.mockResolvedValue({
|
||||
id: 3,
|
||||
full_path: 'foo/bar',
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
path: [
|
||||
{ name: 'Foo', slug: 'foo' },
|
||||
{ name: 'Bar is a nice name', slug: 'bar' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.Groups.create).toHaveBeenCalledWith(
|
||||
'Bar is a nice name',
|
||||
'bar',
|
||||
{
|
||||
parentId: 2,
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockContext.output).toHaveBeenCalledWith('groupId', 3);
|
||||
});
|
||||
|
||||
it('should return existing group if it does exists', async () => {
|
||||
mockGitlabClient.Groups.search.mockResolvedValue([
|
||||
{
|
||||
|
||||
@@ -41,9 +41,18 @@ export const createGitlabGroupEnsureExistsAction = (options: {
|
||||
input: commonGitlabConfig.merge(
|
||||
z.object({
|
||||
path: z
|
||||
.array(z.string(), {
|
||||
description: 'A path of group names that is ensured to exist',
|
||||
})
|
||||
.array(
|
||||
z.string().or(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
}),
|
||||
),
|
||||
{
|
||||
description:
|
||||
'A path of group names or objects (name and slug) that is ensured to exist',
|
||||
},
|
||||
)
|
||||
.min(1),
|
||||
}),
|
||||
),
|
||||
@@ -68,9 +77,11 @@ export const createGitlabGroupEnsureExistsAction = (options: {
|
||||
let currentPath: string | null = null;
|
||||
let parentId: number | null = null;
|
||||
for (const pathElement of path) {
|
||||
const fullPath: string = currentPath
|
||||
? `${currentPath}/${pathElement}`
|
||||
: pathElement;
|
||||
const slug =
|
||||
typeof pathElement === 'string' ? pathElement : pathElement.slug;
|
||||
const name =
|
||||
typeof pathElement === 'string' ? pathElement : pathElement.name;
|
||||
const fullPath: string = currentPath ? `${currentPath}/${slug}` : slug;
|
||||
const result = (await api.Groups.search(
|
||||
fullPath,
|
||||
)) as unknown as Array<GroupSchema>; // recast since the return type for search is wrong in the gitbeaker typings
|
||||
@@ -81,8 +92,8 @@ export const createGitlabGroupEnsureExistsAction = (options: {
|
||||
ctx.logger.info(`creating missing group ${fullPath}`);
|
||||
parentId = (
|
||||
await api.Groups.create(
|
||||
pathElement,
|
||||
pathElement,
|
||||
name,
|
||||
slug,
|
||||
parentId
|
||||
? {
|
||||
parentId: parentId,
|
||||
|
||||
+52
@@ -42,6 +42,58 @@ const mockGitlabClient = {
|
||||
default_branch: 'main',
|
||||
};
|
||||
}),
|
||||
show: jest.fn(async (_: any) => {
|
||||
return {
|
||||
default_branch: 'main',
|
||||
};
|
||||
}),
|
||||
edit: jest.fn(async (_: any) => {
|
||||
return {
|
||||
default_branch: 'main',
|
||||
};
|
||||
}),
|
||||
},
|
||||
MergeRequestApprovals: {
|
||||
allApprovalRules: jest.fn(async (_: any) => {
|
||||
return [
|
||||
{
|
||||
id: 123,
|
||||
name: 'rule1',
|
||||
rule_type: 'regular',
|
||||
eligible_approvers: [
|
||||
{
|
||||
id: 123,
|
||||
username: 'John Smith',
|
||||
},
|
||||
{
|
||||
id: 456,
|
||||
username: 'Jane Doe',
|
||||
},
|
||||
],
|
||||
approvals_required: 1,
|
||||
users: [],
|
||||
contains_hidden_groups: false,
|
||||
report_type: null,
|
||||
section: null,
|
||||
source_rule: { approvals_required: 1 },
|
||||
overridden: false,
|
||||
},
|
||||
{
|
||||
id: 456,
|
||||
name: 'All Members',
|
||||
rule_type: 'any_approver',
|
||||
eligible_approvers: [],
|
||||
approvals_required: 1,
|
||||
users: [],
|
||||
groups: [],
|
||||
contains_hidden_groups: false,
|
||||
report_type: null,
|
||||
section: null,
|
||||
source_rule: { approvals_required: 1 },
|
||||
overridden: false,
|
||||
},
|
||||
];
|
||||
}),
|
||||
},
|
||||
Projects: {
|
||||
create: jest.fn(),
|
||||
|
||||
@@ -51,11 +51,86 @@ const mockGitlabClient = {
|
||||
create: jest.fn(),
|
||||
},
|
||||
MergeRequests: {
|
||||
create: jest.fn(async (_: any) => {
|
||||
create: jest.fn(async (repoId: string) => {
|
||||
if (repoId === 'owner/repo-without-approvals') {
|
||||
return {
|
||||
iid: 5,
|
||||
};
|
||||
}
|
||||
return {
|
||||
default_branch: 'main',
|
||||
iid: 4,
|
||||
};
|
||||
}),
|
||||
show: jest.fn(async (repoId: string, iid: number) => {
|
||||
if (repoId === 'owner/repo' && iid === 4) {
|
||||
return {
|
||||
iid: 4,
|
||||
};
|
||||
} else if (repoId === 'owner/repo-without-approvals' && iid === 5) {
|
||||
return {
|
||||
iid: 5,
|
||||
};
|
||||
}
|
||||
return {
|
||||
default_branch: 'main',
|
||||
};
|
||||
}),
|
||||
edit: jest.fn(async (_: any) => {
|
||||
return {
|
||||
default_branch: 'main',
|
||||
};
|
||||
}),
|
||||
},
|
||||
MergeRequestApprovals: {
|
||||
allApprovalRules: jest.fn(
|
||||
async (repoId: string, options: { mergerequestIId: number }) => {
|
||||
if (
|
||||
repoId === 'owner/repo-without-approvals' &&
|
||||
options.mergerequestIId === 5
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: 123,
|
||||
name: 'rule1',
|
||||
rule_type: 'regular',
|
||||
eligible_approvers: [
|
||||
{
|
||||
id: 234,
|
||||
username: 'Bob Vance',
|
||||
},
|
||||
{
|
||||
id: 345,
|
||||
username: 'Dina Fox',
|
||||
},
|
||||
],
|
||||
approvals_required: 1,
|
||||
users: [],
|
||||
contains_hidden_groups: false,
|
||||
report_type: null,
|
||||
section: null,
|
||||
source_rule: { approvals_required: 1 },
|
||||
overridden: false,
|
||||
},
|
||||
{
|
||||
id: 456,
|
||||
name: 'All Members',
|
||||
rule_type: 'any_approver',
|
||||
eligible_approvers: [],
|
||||
approvals_required: 1,
|
||||
users: [],
|
||||
groups: [],
|
||||
contains_hidden_groups: false,
|
||||
report_type: null,
|
||||
section: null,
|
||||
source_rule: { approvals_required: 1 },
|
||||
overridden: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
),
|
||||
},
|
||||
Projects: {
|
||||
create: jest.fn(),
|
||||
@@ -67,13 +142,28 @@ const mockGitlabClient = {
|
||||
},
|
||||
Users: {
|
||||
all: jest.fn(async (userOptions: { username: string }) => {
|
||||
if (userOptions.username !== 'John Smith')
|
||||
throw new Error('user does not exist');
|
||||
return [
|
||||
{
|
||||
id: 123,
|
||||
},
|
||||
];
|
||||
switch (userOptions.username) {
|
||||
case 'John Smith':
|
||||
return [
|
||||
{
|
||||
id: 123,
|
||||
},
|
||||
];
|
||||
case 'Bob Vance':
|
||||
return [
|
||||
{
|
||||
id: 234,
|
||||
},
|
||||
];
|
||||
case 'Jane Doe':
|
||||
return [
|
||||
{
|
||||
id: 456,
|
||||
},
|
||||
];
|
||||
default:
|
||||
throw new Error('user does not exist');
|
||||
}
|
||||
}),
|
||||
},
|
||||
Repositories: {
|
||||
@@ -517,6 +607,236 @@ describe('createGitLabMergeRequest', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGitlabMergeRequestWithReviewers', () => {
|
||||
it('no reviewers are set when a no reviewer are passed in options', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
title: 'Create my new MR',
|
||||
branchName: 'new-mr',
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
targetPath: 'Subdirectory',
|
||||
assignee: 'John Smith',
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
source: { 'foo.txt': 'Hello there!' },
|
||||
irrelevant: { 'bar.txt': 'Nothing to see here' },
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createMockActionContext({ input, workspacePath });
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'new-mr',
|
||||
'main',
|
||||
);
|
||||
expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'new-mr',
|
||||
'main',
|
||||
'Create my new MR',
|
||||
{
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
assigneeId: 123,
|
||||
},
|
||||
);
|
||||
expect(
|
||||
mockGitlabClient.MergeRequestApprovals.allApprovalRules,
|
||||
).toHaveBeenCalled();
|
||||
expect(mockGitlabClient.MergeRequests.edit).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
4,
|
||||
{
|
||||
reviewerIds: [234, 345], // Approval Rule Members
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('reviewer is set correcly when a valid reviewer username is passed in options in combination with MR approval rules', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
title: 'Create my new MR',
|
||||
branchName: 'new-mr',
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
targetPath: 'Subdirectory',
|
||||
assignee: 'John Smith',
|
||||
reviewers: ['Jane Doe', 'Bob Vance'],
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
source: { 'foo.txt': 'Hello there!' },
|
||||
irrelevant: { 'bar.txt': 'Nothing to see here' },
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createMockActionContext({ input, workspacePath });
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'new-mr',
|
||||
'main',
|
||||
);
|
||||
expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'new-mr',
|
||||
'main',
|
||||
'Create my new MR',
|
||||
{
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
assigneeId: 123,
|
||||
reviewerIds: [456, 234], // Jane Doe and Bob Vance
|
||||
},
|
||||
);
|
||||
expect(
|
||||
mockGitlabClient.MergeRequestApprovals.allApprovalRules,
|
||||
).toHaveBeenCalled();
|
||||
expect(mockGitlabClient.MergeRequests.edit).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
4,
|
||||
{
|
||||
reviewerIds: [234, 345, 456], // Approval Rule Members + Jane Doe (individual reviewer) but no duplicates (Bob Vance)
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('reviewer is set correcly when a valid reviewer username is passed in options and no MR rules exist', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo-without-approvals&owner=owner',
|
||||
title: 'Create my new MR',
|
||||
branchName: 'new-mr',
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
targetPath: 'Subdirectory',
|
||||
assignee: 'John Smith',
|
||||
reviewers: ['Jane Doe', 'Bob Vance'],
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
source: { 'foo.txt': 'Hello there!' },
|
||||
irrelevant: { 'bar.txt': 'Nothing to see here' },
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createMockActionContext({ input, workspacePath });
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith(
|
||||
'owner/repo-without-approvals',
|
||||
'new-mr',
|
||||
'main',
|
||||
);
|
||||
expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith(
|
||||
'owner/repo-without-approvals',
|
||||
'new-mr',
|
||||
'main',
|
||||
'Create my new MR',
|
||||
{
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
assigneeId: 123,
|
||||
reviewerIds: [456, 234], // Jane Doe and Bob Vance
|
||||
},
|
||||
);
|
||||
expect(
|
||||
mockGitlabClient.MergeRequestApprovals.allApprovalRules,
|
||||
).toHaveBeenCalledWith('owner/repo-without-approvals', {
|
||||
mergerequestIId: 5,
|
||||
});
|
||||
expect(mockGitlabClient.MergeRequests.edit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('reviewers are set correcly when valid reviewers username are passed in options', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
title: 'Create my new MR',
|
||||
branchName: 'new-mr',
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
targetPath: 'Subdirectory',
|
||||
assignee: 'John Smith',
|
||||
reviewers: ['Jane Doe', 'John Smith'],
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
source: { 'foo.txt': 'Hello there!' },
|
||||
irrelevant: { 'bar.txt': 'Nothing to see here' },
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createMockActionContext({ input, workspacePath });
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'new-mr',
|
||||
'main',
|
||||
);
|
||||
expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'new-mr',
|
||||
'main',
|
||||
'Create my new MR',
|
||||
{
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
assigneeId: 123,
|
||||
reviewerIds: [456, 123],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('assignee is not set when a valid assignee username is not passed in options', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
title: 'Create my new MR',
|
||||
branchName: 'new-mr',
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
targetPath: 'Subdirectory',
|
||||
reviewers: ['John Doe'],
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
source: { 'foo.txt': 'Hello there!' },
|
||||
irrelevant: { 'bar.txt': 'Nothing to see here' },
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = createMockActionContext({ input, workspacePath });
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'new-mr',
|
||||
'main',
|
||||
);
|
||||
expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'new-mr',
|
||||
'main',
|
||||
'Create my new MR',
|
||||
{
|
||||
description: 'This is an important change',
|
||||
removeSourceBranch: false,
|
||||
assigneeId: undefined,
|
||||
reviewerIds: [],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGitLabMergeRequestWithoutCommitAction', () => {
|
||||
it('default commitAction is auto', async () => {
|
||||
const input = {
|
||||
|
||||
@@ -20,7 +20,12 @@ import {
|
||||
SerializedFile,
|
||||
serializeDirectoryContents,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { Gitlab, RepositoryTreeSchema, CommitAction } from '@gitbeaker/rest';
|
||||
import {
|
||||
Gitlab,
|
||||
RepositoryTreeSchema,
|
||||
CommitAction,
|
||||
SimpleUserSchema,
|
||||
} from '@gitbeaker/rest';
|
||||
import path from 'path';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { InputError } from '@backstage/errors';
|
||||
@@ -100,6 +105,7 @@ export const createPublishGitlabMergeRequestAction = (options: {
|
||||
projectid?: string;
|
||||
removeSourceBranch?: boolean;
|
||||
assignee?: string;
|
||||
reviewers?: string[];
|
||||
}>({
|
||||
id: 'publish:gitlab:merge-request',
|
||||
examples,
|
||||
@@ -179,6 +185,14 @@ which uses additional API calls in order to detect whether to 'create', 'update'
|
||||
type: 'string',
|
||||
description: 'User this merge request will be assigned to',
|
||||
},
|
||||
reviewers: {
|
||||
title: 'Merge Request Reviewers',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
description: 'Users that will be assigned as reviewers',
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
@@ -207,6 +221,7 @@ which uses additional API calls in order to detect whether to 'create', 'update'
|
||||
async handler(ctx) {
|
||||
const {
|
||||
assignee,
|
||||
reviewers,
|
||||
branchName,
|
||||
targetBranchName,
|
||||
description,
|
||||
@@ -242,6 +257,27 @@ which uses additional API calls in order to detect whether to 'create', 'update'
|
||||
}
|
||||
}
|
||||
|
||||
let reviewerIds: number[] | undefined = undefined; // Explicitly set to undefined. Strangely, passing an empty array to the API will result the other options being undefined also being explicity passed to the Gitlab API call (e.g. assigneeId)
|
||||
if (reviewers !== undefined) {
|
||||
reviewerIds = (
|
||||
await Promise.all(
|
||||
reviewers.map(async reviewer => {
|
||||
try {
|
||||
const reviewerUser = await api.Users.all({
|
||||
username: reviewer,
|
||||
});
|
||||
return reviewerUser[0].id;
|
||||
} catch (e) {
|
||||
ctx.logger.warn(
|
||||
`Failed to find gitlab user id for ${reviewer}: ${e}. Proceeding with MR creation without reviewer.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
)
|
||||
).filter(id => id !== undefined) as number[];
|
||||
}
|
||||
|
||||
let fileRoot: string;
|
||||
if (sourcePath) {
|
||||
fileRoot = resolveSafeChildPath(ctx.workspacePath, sourcePath);
|
||||
@@ -355,7 +391,7 @@ which uses additional API calls in order to detect whether to 'create', 'update'
|
||||
}
|
||||
}
|
||||
try {
|
||||
const mergeRequestUrl = await api.MergeRequests.create(
|
||||
let mergeRequest = await api.MergeRequests.create(
|
||||
repoID,
|
||||
branchName,
|
||||
String(targetBranch),
|
||||
@@ -364,12 +400,59 @@ which uses additional API calls in order to detect whether to 'create', 'update'
|
||||
description,
|
||||
removeSourceBranch: removeSourceBranch ? removeSourceBranch : false,
|
||||
assigneeId,
|
||||
reviewerIds,
|
||||
},
|
||||
).then(mergeRequest => mergeRequest.web_url ?? mergeRequest.webUrl);
|
||||
);
|
||||
|
||||
// Because we don't know the code owners before the MR is created, we can't check the approval rules beforehand.
|
||||
// Getting the approval rules beforehand is very difficult, especially, because of the inheritance rules for groups.
|
||||
// Code owners take a moment to be processed and added to the approval rules after the MR is created.
|
||||
|
||||
while (
|
||||
mergeRequest.detailed_merge_status === 'preparing' ||
|
||||
mergeRequest.detailed_merge_status === 'approvals_syncing' ||
|
||||
mergeRequest.detailed_merge_status === 'checking'
|
||||
) {
|
||||
mergeRequest = await api.MergeRequests.show(repoID, mergeRequest.iid);
|
||||
ctx.logger.info(`${mergeRequest.detailed_merge_status}`);
|
||||
}
|
||||
|
||||
const approvalRules = await api.MergeRequestApprovals.allApprovalRules(
|
||||
repoID,
|
||||
{
|
||||
mergerequestIId: mergeRequest.iid,
|
||||
},
|
||||
);
|
||||
|
||||
if (approvalRules.length !== 0) {
|
||||
const eligibleApprovers = approvalRules
|
||||
.filter(rule => rule.eligible_approvers !== undefined)
|
||||
.map(rule => {
|
||||
return rule.eligible_approvers as SimpleUserSchema[];
|
||||
})
|
||||
.flat();
|
||||
|
||||
const eligibleUserIds = new Set([
|
||||
...eligibleApprovers.map(user => user.id),
|
||||
...(reviewerIds ?? []),
|
||||
]);
|
||||
|
||||
mergeRequest = await api.MergeRequests.edit(
|
||||
repoID,
|
||||
mergeRequest.iid,
|
||||
{
|
||||
reviewerIds: Array.from(eligibleUserIds),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ctx.output('projectid', repoID);
|
||||
ctx.output('targetBranchName', targetBranch);
|
||||
ctx.output('projectPath', repoID);
|
||||
ctx.output('mergeRequestUrl', mergeRequestUrl);
|
||||
ctx.output(
|
||||
'mergeRequestUrl',
|
||||
mergeRequest.web_url ?? mergeRequest.webUrl,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new InputError(
|
||||
`Merge request creation failed. ${getErrorMessage(e)}`,
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2023 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 { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { createGitlabProjectMigrateAction } from './gitlabProjectMigrate';
|
||||
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { examples } from './gitlabProjectMigrate.examples';
|
||||
import yaml from 'yaml';
|
||||
|
||||
const mockGitlabClient = {
|
||||
Migrations: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@gitbeaker/rest', () => ({
|
||||
Gitlab: class {
|
||||
constructor() {
|
||||
return mockGitlabClient;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('gitlab:group:migrate', () => {
|
||||
let instance: TemplateAction<any>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.local.com',
|
||||
token: 'token',
|
||||
apiBaseUrl: 'https://api.gitlab.local.com',
|
||||
},
|
||||
{
|
||||
host: 'hosted.gitlab.com',
|
||||
apiBaseUrl: 'https://api.hosted.gitlab.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
instance = createGitlabProjectMigrateAction({ integrations });
|
||||
});
|
||||
|
||||
describe('Migrate a gitlab project to a different gitlab instance in another', () => {
|
||||
const workspacePath = createMockDirectory().resolve('workspace');
|
||||
|
||||
it(`Execute example ${examples[0].description}`, async () => {
|
||||
const input = yaml.parse(examples[0].example).steps[0].input;
|
||||
const ctx = createMockActionContext({ input, workspacePath });
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Migrations.create).toHaveBeenCalledWith(
|
||||
{
|
||||
url: 'https://gitlab.remote.com',
|
||||
access_token: 'lolstoken',
|
||||
},
|
||||
[
|
||||
{
|
||||
sourceType: 'project_entity',
|
||||
sourceFullPath: 'my/wonderful-repo',
|
||||
destinationSlug: 'precious-repo',
|
||||
destinationNamespace: 'my/local/site',
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2023 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 { TemplateExample } from '@backstage/plugin-scaffolder-node';
|
||||
import yaml from 'yaml';
|
||||
|
||||
export const examples: TemplateExample[] = [
|
||||
{
|
||||
description: 'Import repo from remote GitLab instance',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'importGitLabRepo',
|
||||
action: 'gitlab:repo:push',
|
||||
name: 'Push changes to gitlab repository',
|
||||
input: {
|
||||
destinationAccessToken: 'tokenslols',
|
||||
destinationUrl:
|
||||
'gitlab.local.com?repo=precious-repo&owner=my%2Flocal%2Fsite',
|
||||
sourceAccessToken: 'lolstoken',
|
||||
sourceFullPath: 'my/wonderful-repo',
|
||||
sourceUrl: 'https://gitlab.remote.com',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2023 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 { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { createGitlabProjectMigrateAction } from './gitlabProjectMigrate';
|
||||
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
const mockGitlabClient = {
|
||||
Migrations: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@gitbeaker/rest', () => ({
|
||||
Gitlab: class {
|
||||
constructor() {
|
||||
return mockGitlabClient;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('createGitlabRepoMigrate', () => {
|
||||
let instance: TemplateAction<any>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
token: 'token',
|
||||
apiBaseUrl: 'https://api.gitlab.com',
|
||||
},
|
||||
{
|
||||
host: 'hosted.gitlab.com',
|
||||
apiBaseUrl: 'https://api.hosted.gitlab.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
instance = createGitlabProjectMigrateAction({ integrations });
|
||||
});
|
||||
|
||||
describe('createGitlabRepoImportAction', () => {
|
||||
const workspacePath = createMockDirectory().resolve('workspace');
|
||||
|
||||
it('default repo import action is created', async () => {
|
||||
const input = {
|
||||
destinationAccessToken: 'moreLOLsToken',
|
||||
destinationUrl:
|
||||
'gitlab.com?owner=migrated%2Ffoo%2Fbar&repo=migrated-go-lang',
|
||||
sourceUrl: 'https://gitlab.remote.com',
|
||||
sourceAccessToken: 'lolstoken',
|
||||
sourceFullPath: 'foo/bar/go-lang',
|
||||
};
|
||||
const ctx = createMockActionContext({ input, workspacePath });
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Migrations.create).toHaveBeenCalledWith(
|
||||
{
|
||||
url: 'https://gitlab.remote.com',
|
||||
access_token: 'lolstoken',
|
||||
},
|
||||
[
|
||||
{
|
||||
sourceType: 'project_entity',
|
||||
sourceFullPath: 'foo/bar/go-lang',
|
||||
destinationSlug: 'migrated-go-lang',
|
||||
destinationNamespace: 'migrated/foo/bar',
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
createTemplateAction,
|
||||
parseRepoUrl,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { createGitlabApi } from './helpers';
|
||||
import { examples } from './gitlabRepoPush.examples';
|
||||
import { MigrationEntityOptions } from '@gitbeaker/rest';
|
||||
|
||||
/**
|
||||
* Create a new action that imports a gitlab project into another gitlab project (potentially from another gitlab instance).
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const createGitlabProjectMigrateAction = (options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) => {
|
||||
const { integrations } = options;
|
||||
|
||||
return createTemplateAction<{
|
||||
destinationAccessToken: string;
|
||||
destinationUrl: string;
|
||||
sourceAccessToken: string;
|
||||
sourceFullPath: string;
|
||||
sourceUrl: string;
|
||||
}>({
|
||||
id: 'gitlab:group:migrate',
|
||||
examples,
|
||||
schema: {
|
||||
input: {
|
||||
required: [
|
||||
'destinationAccessToken',
|
||||
'destinationUrl',
|
||||
'sourceAccessToken',
|
||||
'sourceFullPath',
|
||||
'sourceUrl',
|
||||
],
|
||||
type: 'object',
|
||||
properties: {
|
||||
destinationAccessToken: {
|
||||
type: 'string',
|
||||
title: 'Target Repository Access Token',
|
||||
description: `The token to use for authorization to the target GitLab'`,
|
||||
},
|
||||
destinationUrl: {
|
||||
type: 'string',
|
||||
title: 'Target Project Location',
|
||||
description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`,
|
||||
},
|
||||
sourceAccessToken: {
|
||||
type: 'string',
|
||||
title: 'Source Group Access Token',
|
||||
description: `The token to use for authorization to the source GitLab'`,
|
||||
},
|
||||
sourceFullPath: {
|
||||
type: 'string',
|
||||
title: 'Group Full Path',
|
||||
description:
|
||||
'Full path to the project in the source Gitlab instance',
|
||||
},
|
||||
sourceUrl: {
|
||||
type: 'string',
|
||||
title: 'Source URL Location',
|
||||
description: `Accepts the format 'https://gitlab.com/'`,
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
importedRepoUrl: {
|
||||
title: 'URL to the newly imported repo',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
async handler(ctx) {
|
||||
const {
|
||||
destinationAccessToken,
|
||||
destinationUrl,
|
||||
sourceAccessToken,
|
||||
sourceFullPath,
|
||||
sourceUrl,
|
||||
} = ctx.input;
|
||||
|
||||
const {
|
||||
host: destinationHost,
|
||||
repo: destinationSlug,
|
||||
owner: destinationNamespace,
|
||||
} = parseRepoUrl(destinationUrl, integrations);
|
||||
|
||||
if (!destinationNamespace) {
|
||||
throw new InputError(
|
||||
`Failed to determine target repository to migrate to. Make sure destinationUrl matches the format 'gitlab.myorg.com?repo=project_name&owner=group_name'`,
|
||||
);
|
||||
}
|
||||
|
||||
const api = createGitlabApi({
|
||||
integrations,
|
||||
token: destinationAccessToken,
|
||||
repoUrl: destinationUrl,
|
||||
});
|
||||
|
||||
const migrationEntity: MigrationEntityOptions[] = [
|
||||
{
|
||||
sourceType: 'project_entity',
|
||||
sourceFullPath: sourceFullPath,
|
||||
destinationSlug: destinationSlug,
|
||||
destinationNamespace: destinationNamespace,
|
||||
},
|
||||
];
|
||||
|
||||
const sourceConfig = {
|
||||
url: sourceUrl,
|
||||
access_token: sourceAccessToken,
|
||||
};
|
||||
|
||||
try {
|
||||
await api.Migrations.create(sourceConfig, migrationEntity);
|
||||
} catch (e: any) {
|
||||
throw new InputError(
|
||||
`Failed to transfer repo ${sourceFullPath}. Make sure that ${sourceFullPath} exists in ${sourceUrl}, and token has enough rights.\nError: ${e}`,
|
||||
);
|
||||
}
|
||||
|
||||
ctx.output(
|
||||
'importedRepoUrl',
|
||||
`${destinationHost}/${destinationNamespace}/${destinationSlug}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
createTriggerGitlabPipelineAction,
|
||||
editGitlabIssueAction,
|
||||
} from './actions';
|
||||
import { createGitlabProjectMigrateAction } from './actions/gitlabProjectMigrate';
|
||||
import { createHandleAutocompleteRequest } from './autocomplete/autocomplete';
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,7 @@ export const gitlabModule = createBackendModule({
|
||||
|
||||
scaffolder.addActions(
|
||||
createGitlabGroupEnsureExistsAction({ integrations }),
|
||||
createGitlabProjectMigrateAction({ integrations }),
|
||||
createGitlabIssueAction({ integrations }),
|
||||
createGitlabProjectAccessTokenAction({ integrations }),
|
||||
createGitlabProjectDeployTokenAction({ integrations }),
|
||||
|
||||
@@ -358,6 +358,7 @@ export const createPublishGitlabMergeRequestAction: (options: {
|
||||
projectid?: string | undefined;
|
||||
removeSourceBranch?: boolean | undefined;
|
||||
assignee?: string | undefined;
|
||||
reviewers?: string[] | undefined;
|
||||
},
|
||||
JsonObject
|
||||
>;
|
||||
|
||||
@@ -132,6 +132,30 @@ export const Form: (
|
||||
props: PropsWithChildren<ScaffolderRJSFFormProps>,
|
||||
) => React_2.JSX.Element;
|
||||
|
||||
// @alpha
|
||||
export const FormDecoratorBlueprint: ExtensionBlueprint<{
|
||||
kind: 'scaffolder-form-decorator';
|
||||
name: undefined;
|
||||
params: {
|
||||
decorator: ScaffolderFormDecorator;
|
||||
};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
ScaffolderFormDecorator,
|
||||
'scaffolder.form-decorator-loader',
|
||||
{}
|
||||
>;
|
||||
inputs: {};
|
||||
config: {};
|
||||
configInput: {};
|
||||
dataRefs: {
|
||||
formDecoratorLoader: ConfigurableExtensionDataRef<
|
||||
ScaffolderFormDecorator,
|
||||
'scaffolder.form-decorator-loader',
|
||||
{}
|
||||
>;
|
||||
};
|
||||
}>;
|
||||
|
||||
// @alpha
|
||||
export const FormFieldBlueprint: ExtensionBlueprint<{
|
||||
kind: 'scaffolder-form-field';
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
createExtensionBlueprint,
|
||||
createExtensionDataRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { ScaffolderFormDecorator } from '../extensions';
|
||||
|
||||
const formDecoratorExtensionDataRef =
|
||||
createExtensionDataRef<ScaffolderFormDecorator>().with({
|
||||
id: 'scaffolder.form-decorator-loader',
|
||||
});
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* Creates extensions that are Field Extensions for the Scaffolder
|
||||
* */
|
||||
export const FormDecoratorBlueprint = createExtensionBlueprint({
|
||||
kind: 'scaffolder-form-decorator',
|
||||
attachTo: { id: 'api:scaffolder/form-decorators', input: 'formDecorators' },
|
||||
dataRefs: {
|
||||
formDecoratorLoader: formDecoratorExtensionDataRef,
|
||||
},
|
||||
output: [formDecoratorExtensionDataRef],
|
||||
*factory(params: { decorator: ScaffolderFormDecorator }) {
|
||||
yield formDecoratorExtensionDataRef(params.decorator);
|
||||
},
|
||||
});
|
||||
@@ -14,5 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './FormDecoratorBlueprint';
|
||||
export * from './FormFieldBlueprint';
|
||||
export * from './types';
|
||||
|
||||
@@ -80,6 +80,33 @@ const _default: FrontendPlugin<
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
'api:scaffolder/form-decorators': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<
|
||||
AnyApiFactory,
|
||||
'core.api.factory',
|
||||
{}
|
||||
>;
|
||||
inputs: {
|
||||
formDecorators: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
ScaffolderFormDecorator,
|
||||
'scaffolder.form-decorator-loader',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'api';
|
||||
name: 'form-decorators';
|
||||
params: {
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
'page:scaffolder': ExtensionDefinition<{
|
||||
kind: 'page';
|
||||
name: undefined;
|
||||
@@ -173,6 +200,31 @@ export class DefaultScaffolderFormDecoratorsApi
|
||||
getFormDecorators(): Promise<ScaffolderFormDecorator[]>;
|
||||
}
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const formDecoratorsApi: ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
|
||||
inputs: {
|
||||
formDecorators: ExtensionInput<
|
||||
ConfigurableExtensionDataRef<
|
||||
ScaffolderFormDecorator,
|
||||
'scaffolder.form-decorator-loader',
|
||||
{}
|
||||
>,
|
||||
{
|
||||
singleton: false;
|
||||
optional: false;
|
||||
}
|
||||
>;
|
||||
};
|
||||
kind: 'api';
|
||||
name: 'form-decorators';
|
||||
params: {
|
||||
factory: AnyApiFactory;
|
||||
};
|
||||
}>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const formDecoratorsApiRef: ApiRef<ScaffolderFormDecoratorsApi>;
|
||||
|
||||
|
||||
@@ -14,8 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ApiBlueprint,
|
||||
createApiFactory,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { ScaffolderFormDecoratorsApi } from './types';
|
||||
import { ScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { formDecoratorsApiRef } from './ref';
|
||||
import { FormDecoratorBlueprint } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
/** @alpha */
|
||||
export class DefaultScaffolderFormDecoratorsApi
|
||||
@@ -37,3 +44,29 @@ export class DefaultScaffolderFormDecoratorsApi
|
||||
return this.options.decorators;
|
||||
}
|
||||
}
|
||||
|
||||
/** @alpha */
|
||||
export const formDecoratorsApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'form-decorators',
|
||||
inputs: {
|
||||
formDecorators: createExtensionInput([
|
||||
FormDecoratorBlueprint.dataRefs.formDecoratorLoader,
|
||||
]),
|
||||
},
|
||||
factory(originalFactory, { inputs }) {
|
||||
const formDecorators = inputs.formDecorators.map(e =>
|
||||
e.get(FormDecoratorBlueprint.dataRefs.formDecoratorLoader),
|
||||
);
|
||||
|
||||
return originalFactory({
|
||||
factory: createApiFactory({
|
||||
api: formDecoratorsApiRef,
|
||||
deps: {},
|
||||
factory: () =>
|
||||
DefaultScaffolderFormDecoratorsApi.create({
|
||||
decorators: formDecorators,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
|
||||
export { formDecoratorsApiRef } from './ref';
|
||||
export type { ScaffolderFormDecoratorsApi } from './types';
|
||||
export { DefaultScaffolderFormDecoratorsApi } from './FormDecoratorsApi';
|
||||
export * from './FormDecoratorsApi';
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
scaffolderApi,
|
||||
} from './extensions';
|
||||
import { formFieldsApi } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { formDecoratorsApi } from './api';
|
||||
|
||||
/** @alpha */
|
||||
export default createFrontendPlugin({
|
||||
@@ -53,6 +54,7 @@ export default createFrontendPlugin({
|
||||
scaffolderApi,
|
||||
scaffolderPage,
|
||||
scaffolderNavItem,
|
||||
formDecoratorsApi,
|
||||
formFieldsApi,
|
||||
repoUrlPickerFormField,
|
||||
],
|
||||
|
||||
+4
-4
@@ -2689,15 +2689,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-storybook@npm:^0.11.0":
|
||||
version: 0.11.1
|
||||
resolution: "eslint-plugin-storybook@npm:0.11.1"
|
||||
version: 0.11.2
|
||||
resolution: "eslint-plugin-storybook@npm:0.11.2"
|
||||
dependencies:
|
||||
"@storybook/csf": ^0.1.11
|
||||
"@typescript-eslint/utils": ^8.8.1
|
||||
ts-dedent: ^2.2.0
|
||||
peerDependencies:
|
||||
eslint: ">=6"
|
||||
checksum: 8b8eb30b598f3c44c2bbf921e318215338f1159c1fba2d2f6cd5bc0b2ec14515655cf1760b5e11355baddabc2ac8446d4dc18ee6df6d2252555b126ff649a421
|
||||
eslint: ">=8"
|
||||
checksum: 04dab47b676db57aa6c2325d4ab66283f5970133a2615cc68a97e42cb4ae1c79317e6db73bcfc24468e146bdff4c9ffe90a368bf2bcb67593954485f058b33ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -1872,7 +1872,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.19.6, @babel/core@npm:^7.24.7, @babel/core@npm:^7.26.0":
|
||||
"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.19.6, @babel/core@npm:^7.24.7":
|
||||
version: 7.26.0
|
||||
resolution: "@babel/core@npm:7.26.0"
|
||||
dependencies:
|
||||
@@ -2956,28 +2956,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self@npm:^7.25.9":
|
||||
version: 7.25.9
|
||||
resolution: "@babel/plugin-transform-react-jsx-self@npm:7.25.9"
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils": ^7.25.9
|
||||
peerDependencies:
|
||||
"@babel/core": ^7.0.0-0
|
||||
checksum: 41c833cd7f91b1432710f91b1325706e57979b2e8da44e83d86312c78bbe96cd9ef778b4e79e4e17ab25fa32c72b909f2be7f28e876779ede28e27506c41f4ae
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source@npm:^7.25.9":
|
||||
version: 7.25.9
|
||||
resolution: "@babel/plugin-transform-react-jsx-source@npm:7.25.9"
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils": ^7.25.9
|
||||
peerDependencies:
|
||||
"@babel/core": ^7.0.0-0
|
||||
checksum: a3e0e5672e344e9d01fb20b504fe29a84918eaa70cec512c4d4b1b035f72803261257343d8e93673365b72c371f35cf34bb0d129720bf178a4c87812c8b9c662
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-transform-react-jsx@npm:^7.22.5":
|
||||
version: 7.22.5
|
||||
resolution: "@babel/plugin-transform-react-jsx@npm:7.22.5"
|
||||
@@ -4013,7 +3991,6 @@ __metadata:
|
||||
"@types/yarnpkg__lockfile": ^1.1.4
|
||||
"@typescript-eslint/eslint-plugin": ^8.17.0
|
||||
"@typescript-eslint/parser": ^8.16.0
|
||||
"@vitejs/plugin-react": ^4.3.1
|
||||
"@yarnpkg/lockfile": ^1.1.0
|
||||
"@yarnpkg/parsers": ^3.0.0
|
||||
bfj: ^8.0.0
|
||||
@@ -4086,9 +4063,6 @@ __metadata:
|
||||
terser-webpack-plugin: ^5.1.3
|
||||
ts-morph: ^24.0.0
|
||||
util: ^0.12.3
|
||||
vite: ^5.0.0
|
||||
vite-plugin-html: ^3.2.2
|
||||
vite-plugin-node-polyfills: ^0.22.0
|
||||
webpack: ^5.94.0
|
||||
webpack-dev-server: ^5.0.0
|
||||
yaml: ^2.0.0
|
||||
@@ -4097,31 +4071,16 @@ __metadata:
|
||||
yn: ^4.0.0
|
||||
zod: ^3.22.4
|
||||
peerDependencies:
|
||||
"@modyfi/vite-plugin-yaml": ^1.1.0
|
||||
"@rspack/core": ^1.0.10
|
||||
"@rspack/dev-server": ^1.0.9
|
||||
"@rspack/plugin-react-refresh": ^1.0.0
|
||||
"@vitejs/plugin-react": ^4.0.4
|
||||
vite: ^5.0.0
|
||||
vite-plugin-html: ^3.2.0
|
||||
vite-plugin-node-polyfills: ^0.22.0
|
||||
peerDependenciesMeta:
|
||||
"@modyfi/vite-plugin-yaml":
|
||||
optional: true
|
||||
"@rspack/core":
|
||||
optional: true
|
||||
"@rspack/dev-server":
|
||||
optional: true
|
||||
"@rspack/plugin-react-refresh":
|
||||
optional: true
|
||||
"@vitejs/plugin-react":
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
vite-plugin-html:
|
||||
optional: true
|
||||
vite-plugin-node-polyfills:
|
||||
optional: true
|
||||
bin:
|
||||
backstage-cli: bin/backstage-cli
|
||||
languageName: unknown
|
||||
@@ -7511,6 +7470,7 @@ __metadata:
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/config-loader": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-proxy-node": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
"@types/express": ^4.17.6
|
||||
"@types/http-proxy-middleware": ^1.0.0
|
||||
@@ -7528,6 +7488,19 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-proxy-node@workspace:^, @backstage/plugin-proxy-node@workspace:plugins/proxy-node":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-proxy-node@workspace:plugins/proxy-node"
|
||||
dependencies:
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@types/http-proxy-middleware": ^1.0.0
|
||||
http-proxy-middleware: ^2.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-scaffolder-backend-module-azure@workspace:^, @backstage/plugin-scaffolder-backend-module-azure@workspace:plugins/scaffolder-backend-module-azure":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-scaffolder-backend-module-azure@workspace:plugins/scaffolder-backend-module-azure"
|
||||
@@ -12174,19 +12147,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@modyfi/vite-plugin-yaml@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "@modyfi/vite-plugin-yaml@npm:1.1.0"
|
||||
dependencies:
|
||||
"@rollup/pluginutils": 5.1.0
|
||||
js-yaml: 4.1.0
|
||||
tosource: 2.0.0-alpha.3
|
||||
peerDependencies:
|
||||
vite: ^3.2.7 || ^4.0.5 || ^5.0.5
|
||||
checksum: 6989cde89323321b714b69d11b9125c8d3483a8c6ace536df313edd653180e85e6013effbc6cb8b89e6f9d6e107a10f7a82fadf6a44e81255b437702d5dd1db0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@motionone/animation@npm:^10.12.0":
|
||||
version: 10.16.3
|
||||
resolution: "@motionone/animation@npm:10.16.3"
|
||||
@@ -15828,22 +15788,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/plugin-inject@npm:^5.0.5":
|
||||
version: 5.0.5
|
||||
resolution: "@rollup/plugin-inject@npm:5.0.5"
|
||||
dependencies:
|
||||
"@rollup/pluginutils": ^5.0.1
|
||||
estree-walker: ^2.0.2
|
||||
magic-string: ^0.30.3
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
checksum: 22cb772fd6f7178308b2ece95cdde5f8615f6257197832166294552a7e4c0d3976dc996cbfa6470af3151d8b86c00091aa93da5f4db6ec563f11b6db29fd1b63
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/plugin-json@npm:^6.0.0":
|
||||
version: 6.1.0
|
||||
resolution: "@rollup/plugin-json@npm:6.1.0"
|
||||
@@ -15892,7 +15836,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/pluginutils@npm:5.1.0, @rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.5, @rollup/pluginutils@npm:^5.1.0":
|
||||
"@rollup/pluginutils@npm:^4.2.1":
|
||||
version: 4.2.1
|
||||
resolution: "@rollup/pluginutils@npm:4.2.1"
|
||||
dependencies:
|
||||
estree-walker: ^2.0.1
|
||||
picomatch: ^2.2.2
|
||||
checksum: 6bc41f22b1a0f1efec3043899e4d3b6b1497b3dea4d94292d8f83b4cf07a1073ecbaedd562a22d11913ff7659f459677b01b09e9598a98936e746780ecc93a12
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.5, @rollup/pluginutils@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "@rollup/pluginutils@npm:5.1.0"
|
||||
dependencies:
|
||||
@@ -15908,16 +15862,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/pluginutils@npm:^4.2.0, @rollup/pluginutils@npm:^4.2.1":
|
||||
version: 4.2.1
|
||||
resolution: "@rollup/pluginutils@npm:4.2.1"
|
||||
dependencies:
|
||||
estree-walker: ^2.0.1
|
||||
picomatch: ^2.2.2
|
||||
checksum: 6bc41f22b1a0f1efec3043899e4d3b6b1497b3dea4d94292d8f83b4cf07a1073ecbaedd562a22d11913ff7659f459677b01b09e9598a98936e746780ecc93a12
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@npm:4.27.4":
|
||||
version: 4.27.4
|
||||
resolution: "@rollup/rollup-android-arm-eabi@npm:4.27.4"
|
||||
@@ -19037,7 +18981,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.18.0, @types/babel__core@npm:^7.20.5":
|
||||
"@types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.18.0":
|
||||
version: 7.20.5
|
||||
resolution: "@types/babel__core@npm:7.20.5"
|
||||
dependencies:
|
||||
@@ -21379,21 +21323,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitejs/plugin-react@npm:^4.3.1":
|
||||
version: 4.3.4
|
||||
resolution: "@vitejs/plugin-react@npm:4.3.4"
|
||||
dependencies:
|
||||
"@babel/core": ^7.26.0
|
||||
"@babel/plugin-transform-react-jsx-self": ^7.25.9
|
||||
"@babel/plugin-transform-react-jsx-source": ^7.25.9
|
||||
"@types/babel__core": ^7.20.5
|
||||
react-refresh: ^0.14.2
|
||||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0
|
||||
checksum: d417f40d9259a1d5193152f7d9fee081d5bf41cbeef9662ae1123ccc1e26aa4b6b04bc82ebb8c4fbfde9516a746fb3af7da19fdd449819c30f0631daaa10a44b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/expect@npm:2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "@vitest/expect@npm:2.0.5"
|
||||
@@ -23101,19 +23030,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"assert@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "assert@npm:2.1.0"
|
||||
dependencies:
|
||||
call-bind: ^1.0.2
|
||||
is-nan: ^1.3.2
|
||||
object-is: ^1.1.5
|
||||
object.assign: ^4.1.4
|
||||
util: ^0.12.5
|
||||
checksum: 1ed1cabba9abe55f4109b3f7292b4e4f3cf2953aad8dc148c0b3c3bd676675c31b1abb32ef563b7d5a19d1715bf90d1e5f09fad2a4ee655199468902da80f7c2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"assertion-error@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "assertion-error@npm:2.0.1"
|
||||
@@ -24000,15 +23916,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"browser-resolve@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "browser-resolve@npm:2.0.0"
|
||||
dependencies:
|
||||
resolve: ^1.17.0
|
||||
checksum: 69225e73b555bd6d2a08fb93c7342cfcf3b5058b975099c52649cd5c3cec84c2066c5385084d190faedfb849684d9dabe10129f0cd401d1883572f2e6650f440
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4":
|
||||
version: 1.2.0
|
||||
resolution: "browserify-aes@npm:1.2.0"
|
||||
@@ -24177,7 +24084,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer@npm:^5.5.0, buffer@npm:^5.7.1":
|
||||
"buffer@npm:^5.5.0":
|
||||
version: 5.7.1
|
||||
resolution: "buffer@npm:5.7.1"
|
||||
dependencies:
|
||||
@@ -24428,7 +24335,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8":
|
||||
"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8":
|
||||
version: 1.0.8
|
||||
resolution: "call-bind@npm:1.0.8"
|
||||
dependencies:
|
||||
@@ -25204,7 +25111,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"colorette@npm:^2.0.10, colorette@npm:^2.0.16, colorette@npm:^2.0.20":
|
||||
"colorette@npm:^2.0.10, colorette@npm:^2.0.20":
|
||||
version: 2.0.20
|
||||
resolution: "colorette@npm:2.0.20"
|
||||
checksum: 0c016fea2b91b733eb9f4bcdb580018f52c0bc0979443dad930e5037a968237ac53d9beb98e218d2e9235834f8eebce7f8e080422d6194e957454255bde71d3d
|
||||
@@ -25506,13 +25413,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"connect-history-api-fallback@npm:^1.6.0":
|
||||
version: 1.6.0
|
||||
resolution: "connect-history-api-fallback@npm:1.6.0"
|
||||
checksum: 804ca2be28c999032ecd37a9f71405e5d7b7a4b3defcebbe41077bb8c5a0a150d7b59f51dcc33b2de30bc7e217a31d10f8cfad27e8e74c2fc7655eeba82d6e7e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"connect-history-api-fallback@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "connect-history-api-fallback@npm:2.0.0"
|
||||
@@ -25542,7 +25442,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"consola@npm:^2.15.0, consola@npm:^2.15.3":
|
||||
"consola@npm:^2.15.0":
|
||||
version: 2.15.3
|
||||
resolution: "consola@npm:2.15.3"
|
||||
checksum: 8ef7a09b703ec67ac5c389a372a33b6dc97eda6c9876443a60d76a3076eea0259e7f67a4e54fd5a52f97df73690822d090cf8b7e102b5761348afef7c6d03e28
|
||||
@@ -25878,7 +25778,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"create-require@npm:^1.1.0, create-require@npm:^1.1.1":
|
||||
"create-require@npm:^1.1.0":
|
||||
version: 1.1.1
|
||||
resolution: "create-require@npm:1.1.1"
|
||||
checksum: a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff
|
||||
@@ -26089,7 +25989,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-select@npm:^4.1.3, css-select@npm:^4.2.1":
|
||||
"css-select@npm:^4.1.3":
|
||||
version: 4.3.0
|
||||
resolution: "css-select@npm:4.3.0"
|
||||
dependencies:
|
||||
@@ -27162,13 +27062,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domain-browser@npm:^4.22.0":
|
||||
version: 4.23.0
|
||||
resolution: "domain-browser@npm:4.23.0"
|
||||
checksum: 95b772f5fa88300240694380e06e03868573acdf86ca392a58c78602d6536dca2097ad2469a1500bd23a1329b09992de846e0b66c364cbf5711a7fee3ee5dac9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0":
|
||||
version: 2.3.0
|
||||
resolution: "domelementtype@npm:2.3.0"
|
||||
@@ -27267,14 +27160,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dotenv-expand@npm:^8.0.2":
|
||||
version: 8.0.3
|
||||
resolution: "dotenv-expand@npm:8.0.3"
|
||||
checksum: 128ce90ac825b543de3ece0154a51b056ab0dc36bb26d97a68cd0b8707327ecd3c182fb6ac63b26a0fcdfa85064419906a1065cb634f1f9dc08ad311375f1fc0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dotenv@npm:^16.0.0, dotenv@npm:^16.3.1":
|
||||
"dotenv@npm:^16.3.1":
|
||||
version: 16.4.5
|
||||
resolution: "dotenv@npm:16.4.5"
|
||||
checksum: 301a12c3d44fd49888b74eb9ccf9f07a1f5df43f489e7fcb89647a2edcd84c42d6bc349dc8df099cd18f07c35c7b04685c1a4f3e6a6a9e6b30f8d48c15b7f49c
|
||||
@@ -27984,7 +27870,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.21.0, esbuild@npm:^0.21.3":
|
||||
"esbuild@npm:^0.21.0":
|
||||
version: 0.21.5
|
||||
resolution: "esbuild@npm:0.21.5"
|
||||
dependencies:
|
||||
@@ -28343,15 +28229,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-storybook@npm:^0.11.1":
|
||||
version: 0.11.1
|
||||
resolution: "eslint-plugin-storybook@npm:0.11.1"
|
||||
version: 0.11.2
|
||||
resolution: "eslint-plugin-storybook@npm:0.11.2"
|
||||
dependencies:
|
||||
"@storybook/csf": ^0.1.11
|
||||
"@typescript-eslint/utils": ^8.8.1
|
||||
ts-dedent: ^2.2.0
|
||||
peerDependencies:
|
||||
eslint: ">=6"
|
||||
checksum: 8b8eb30b598f3c44c2bbf921e318215338f1159c1fba2d2f6cd5bc0b2ec14515655cf1760b5e11355baddabc2ac8446d4dc18ee6df6d2252555b126ff649a421
|
||||
eslint: ">=8"
|
||||
checksum: 04dab47b676db57aa6c2325d4ab66283f5970133a2615cc68a97e42cb4ae1c79317e6db73bcfc24468e146bdff4c9ffe90a368bf2bcb67593954485f058b33ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -28774,7 +28660,6 @@ __metadata:
|
||||
"@material-ui/core": ^4.12.2
|
||||
"@material-ui/icons": ^4.9.1
|
||||
"@material-ui/lab": 4.0.0-alpha.61
|
||||
"@modyfi/vite-plugin-yaml": ^1.1.0
|
||||
"@octokit/rest": ^19.0.3
|
||||
"@playwright/test": ^1.32.3
|
||||
"@testing-library/dom": ^10.0.0
|
||||
@@ -28785,7 +28670,6 @@ __metadata:
|
||||
"@types/react": "*"
|
||||
"@types/react-dom": "*"
|
||||
"@types/zen-observable": ^0.8.0
|
||||
"@vitejs/plugin-react": ^4.3.1
|
||||
axios: ^1.7.7
|
||||
cross-env: ^7.0.0
|
||||
history: ^5.0.0
|
||||
@@ -28795,9 +28679,6 @@ __metadata:
|
||||
react-router: ^6.3.0
|
||||
react-router-dom: ^6.3.0
|
||||
react-use: ^17.2.4
|
||||
vite: ^5.0.0
|
||||
vite-plugin-html: ^3.2.2
|
||||
vite-plugin-node-polyfills: ^0.22.0
|
||||
zen-observable: ^0.10.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -29215,7 +29096,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2":
|
||||
"fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2":
|
||||
version: 3.3.2
|
||||
resolution: "fast-glob@npm:3.3.2"
|
||||
dependencies:
|
||||
@@ -29976,7 +29857,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-extra@npm:10.1.0, fs-extra@npm:^10.0.0, fs-extra@npm:^10.0.1":
|
||||
"fs-extra@npm:10.1.0, fs-extra@npm:^10.0.0":
|
||||
version: 10.1.0
|
||||
resolution: "fs-extra@npm:10.1.0"
|
||||
dependencies:
|
||||
@@ -30074,7 +29955,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3":
|
||||
"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2":
|
||||
version: 2.3.3
|
||||
resolution: "fsevents@npm:2.3.3"
|
||||
dependencies:
|
||||
@@ -30093,7 +29974,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fsevents@patch:fsevents@^2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.3#~builtin<compat/fsevents>":
|
||||
"fsevents@patch:fsevents@^2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>":
|
||||
version: 2.3.3
|
||||
resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin<compat/fsevents>::version=2.3.3&hash=df0bf1"
|
||||
dependencies:
|
||||
@@ -31292,7 +31173,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-minifier-terser@npm:^6.0.2, html-minifier-terser@npm:^6.1.0":
|
||||
"html-minifier-terser@npm:^6.0.2":
|
||||
version: 6.1.0
|
||||
resolution: "html-minifier-terser@npm:6.1.0"
|
||||
dependencies:
|
||||
@@ -32422,16 +32303,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-nan@npm:^1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "is-nan@npm:1.3.2"
|
||||
dependencies:
|
||||
call-bind: ^1.0.0
|
||||
define-properties: ^1.1.3
|
||||
checksum: 5dfadcef6ad12d3029d43643d9800adbba21cf3ce2ec849f734b0e14ee8da4070d82b15fdb35138716d02587c6578225b9a22779cab34888a139cc43e4e3610a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-network-error@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "is-network-error@npm:1.0.1"
|
||||
@@ -32893,13 +32764,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isomorphic-timers-promises@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "isomorphic-timers-promises@npm:1.0.1"
|
||||
checksum: 16ef59f0fbcceba1a037c74b5f7195d252ae058724ccd3e53b37ad034e8498f5532084e8ab18e7940ba3fa8fca2f21403d00eed15802ab1f7cab7c099cba62a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isomorphic-ws@npm:5.0.0, isomorphic-ws@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "isomorphic-ws@npm:5.0.0"
|
||||
@@ -33646,7 +33510,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-yaml@npm:4.1.0, js-yaml@npm:=4.1.0, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0":
|
||||
"js-yaml@npm:=4.1.0, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "js-yaml@npm:4.1.0"
|
||||
dependencies:
|
||||
@@ -37359,16 +37223,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-html-parser@npm:^5.3.3":
|
||||
version: 5.4.2
|
||||
resolution: "node-html-parser@npm:5.4.2"
|
||||
dependencies:
|
||||
css-select: ^4.2.1
|
||||
he: 1.2.0
|
||||
checksum: 2d2391147c83b402786eeab95d23ea4e24ca8608e0e70a2823bfd4f2a248be13a8cc31acfd55a0109e051131e4f0c17d7ada8d999ce70ff2e342ab0110f5da59
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-html-parser@npm:^6.1.1":
|
||||
version: 6.1.5
|
||||
resolution: "node-html-parser@npm:6.1.5"
|
||||
@@ -37478,41 +37332,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-stdlib-browser@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "node-stdlib-browser@npm:1.2.0"
|
||||
dependencies:
|
||||
assert: ^2.0.0
|
||||
browser-resolve: ^2.0.0
|
||||
browserify-zlib: ^0.2.0
|
||||
buffer: ^5.7.1
|
||||
console-browserify: ^1.1.0
|
||||
constants-browserify: ^1.0.0
|
||||
create-require: ^1.1.1
|
||||
crypto-browserify: ^3.11.0
|
||||
domain-browser: ^4.22.0
|
||||
events: ^3.0.0
|
||||
https-browserify: ^1.0.0
|
||||
isomorphic-timers-promises: ^1.0.1
|
||||
os-browserify: ^0.3.0
|
||||
path-browserify: ^1.0.1
|
||||
pkg-dir: ^5.0.0
|
||||
process: ^0.11.10
|
||||
punycode: ^1.4.1
|
||||
querystring-es3: ^0.2.1
|
||||
readable-stream: ^3.6.0
|
||||
stream-browserify: ^3.0.0
|
||||
stream-http: ^3.2.0
|
||||
string_decoder: ^1.0.0
|
||||
timers-browserify: ^2.0.4
|
||||
tty-browserify: 0.0.1
|
||||
url: ^0.11.0
|
||||
util: ^0.12.4
|
||||
vm-browserify: ^1.0.1
|
||||
checksum: fe491f0839319fd9bb95964c6f7da81fc7fde4c3ac9062aa367f19bc5a6060d0d9e423d3de4196cb51f8259d6aaf6cf380048c48a86eb3721c6223dd0dcc5bfd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-watch@npm:0.7.3":
|
||||
version: 0.7.3
|
||||
resolution: "node-watch@npm:0.7.3"
|
||||
@@ -37888,16 +37707,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-is@npm:^1.1.5":
|
||||
version: 1.1.6
|
||||
resolution: "object-is@npm:1.1.6"
|
||||
dependencies:
|
||||
call-bind: ^1.0.7
|
||||
define-properties: ^1.2.1
|
||||
checksum: 3ea22759967e6f2380a2cbbd0f737b42dc9ddb2dfefdb159a1b927fea57335e1b058b564bfa94417db8ad58cddab33621a035de6f5e5ad56d89f2dd03e66c6a1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-keys@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "object-keys@npm:1.1.1"
|
||||
@@ -38977,13 +38786,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pathe@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "pathe@npm:0.2.0"
|
||||
checksum: 9a8149ce152088f30d15b0b03a7c128ba21f16b4dc1f3f90fe38eee9f6d0f1d6da8e4e47bd2a4f9e14aaac7c30ed01cfc86216479011de2bdc598b65e6f19f41
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pathval@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "pathval@npm:2.0.0"
|
||||
@@ -39309,15 +39111,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pkg-dir@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "pkg-dir@npm:5.0.0"
|
||||
dependencies:
|
||||
find-up: ^5.0.0
|
||||
checksum: b167bb8dac7bbf22b1d5e30ec223e6b064b84b63010c9d49384619a36734caf95ed23ad23d4f9bd975e8e8082b60a83395f43a89bb192df53a7c25a38ecb57d9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pkg-up@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "pkg-up@npm:3.1.0"
|
||||
@@ -39837,7 +39630,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postcss@npm:^8.1.0, postcss@npm:^8.4.33, postcss@npm:^8.4.43":
|
||||
"postcss@npm:^8.1.0, postcss@npm:^8.4.33":
|
||||
version: 8.4.49
|
||||
resolution: "postcss@npm:8.4.49"
|
||||
dependencies:
|
||||
@@ -40355,7 +40148,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"punycode@npm:^1.2.4, punycode@npm:^1.3.2, punycode@npm:^1.4.1":
|
||||
"punycode@npm:^1.2.4, punycode@npm:^1.3.2":
|
||||
version: 1.4.1
|
||||
resolution: "punycode@npm:1.4.1"
|
||||
checksum: fa6e698cb53db45e4628559e557ddaf554103d2a96a1d62892c8f4032cd3bc8871796cae9eabc1bc700e2b6677611521ce5bb1d9a27700086039965d0cf34518
|
||||
@@ -40429,7 +40222,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"querystring-es3@npm:^0.2.0, querystring-es3@npm:^0.2.1":
|
||||
"querystring-es3@npm:^0.2.0":
|
||||
version: 0.2.1
|
||||
resolution: "querystring-es3@npm:0.2.1"
|
||||
checksum: 691e8d6b8b157e7cd49ae8e83fcf86de39ab3ba948c25abaa94fba84c0986c641aa2f597770848c64abce290ed17a39c9df6df737dfa7e87c3b63acc7d225d61
|
||||
@@ -41105,7 +40898,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-refresh@npm:^0.14.0, react-refresh@npm:^0.14.2":
|
||||
"react-refresh@npm:^0.14.0":
|
||||
version: 0.14.2
|
||||
resolution: "react-refresh@npm:0.14.2"
|
||||
checksum: d80db4bd40a36dab79010dc8aa317a5b931f960c0d83c4f3b81f0552cbcf7f29e115b84bb7908ec6a1eb67720fff7023084eff73ece8a7ddc694882478464382
|
||||
@@ -42032,7 +41825,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@npm:1.22.8, resolve@npm:^1.1.6, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2":
|
||||
"resolve@npm:1.22.8, resolve@npm:^1.1.6, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:~1.22.1, resolve@npm:~1.22.2":
|
||||
version: 1.22.8
|
||||
resolution: "resolve@npm:1.22.8"
|
||||
dependencies:
|
||||
@@ -42058,7 +41851,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@patch:resolve@1.22.8#~builtin<compat/resolve>, resolve@patch:resolve@^1.1.6#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.17.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.1#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.4#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.8#~builtin<compat/resolve>, resolve@patch:resolve@~1.22.1#~builtin<compat/resolve>, resolve@patch:resolve@~1.22.2#~builtin<compat/resolve>":
|
||||
"resolve@patch:resolve@1.22.8#~builtin<compat/resolve>, resolve@patch:resolve@^1.1.6#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.1#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.4#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.8#~builtin<compat/resolve>, resolve@patch:resolve@~1.22.1#~builtin<compat/resolve>, resolve@patch:resolve@~1.22.2#~builtin<compat/resolve>":
|
||||
version: 1.22.8
|
||||
resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin<compat/resolve>::version=1.22.8&hash=c3c19d"
|
||||
dependencies:
|
||||
@@ -42369,7 +42162,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rollup@npm:^4.20.0, rollup@npm:^4.27.3":
|
||||
"rollup@npm:^4.27.3":
|
||||
version: 4.27.4
|
||||
resolution: "rollup@npm:4.27.4"
|
||||
dependencies:
|
||||
@@ -43827,7 +43620,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stream-browserify@npm:3.0.0, stream-browserify@npm:^3.0.0":
|
||||
"stream-browserify@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "stream-browserify@npm:3.0.0"
|
||||
dependencies:
|
||||
@@ -43883,18 +43676,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stream-http@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "stream-http@npm:3.2.0"
|
||||
dependencies:
|
||||
builtin-status-codes: ^3.0.0
|
||||
inherits: ^2.0.4
|
||||
readable-stream: ^3.6.0
|
||||
xtend: ^4.0.2
|
||||
checksum: c9b78453aeb0c84fcc59555518ac62bacab9fa98e323e7b7666e5f9f58af8f3155e34481078509b02929bd1268427f664d186604cdccee95abc446099b339f83
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stream-json@npm:^1.7.4":
|
||||
version: 1.8.0
|
||||
resolution: "stream-json@npm:1.8.0"
|
||||
@@ -45161,7 +44942,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tosource@npm:2.0.0-alpha.3, tosource@npm:^2.0.0-alpha.3":
|
||||
"tosource@npm:^2.0.0-alpha.3":
|
||||
version: 2.0.0-alpha.3
|
||||
resolution: "tosource@npm:2.0.0-alpha.3"
|
||||
checksum: bc03a7571de8ed4306e6721283fa891f2adcab9dd80c46f6f177d4259b34bb192fe3a2cb3e1e2ce16f9db0bc7e534acfcb5478ab094b0ba255f98abfce6dab46
|
||||
@@ -45501,13 +45282,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tty-browserify@npm:0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "tty-browserify@npm:0.0.1"
|
||||
checksum: 93b745d43fa5a7d2b948fa23be8d313576d1d884b48acd957c07710bac1c0d8ac34c0556ad4c57c73d36e11741763ef66b3fb4fb97b06b7e4d525315a3cd45f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tunnel-agent@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "tunnel-agent@npm:0.6.0"
|
||||
@@ -46739,83 +46513,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-plugin-html@npm:^3.2.2":
|
||||
version: 3.2.2
|
||||
resolution: "vite-plugin-html@npm:3.2.2"
|
||||
dependencies:
|
||||
"@rollup/pluginutils": ^4.2.0
|
||||
colorette: ^2.0.16
|
||||
connect-history-api-fallback: ^1.6.0
|
||||
consola: ^2.15.3
|
||||
dotenv: ^16.0.0
|
||||
dotenv-expand: ^8.0.2
|
||||
ejs: ^3.1.6
|
||||
fast-glob: ^3.2.11
|
||||
fs-extra: ^10.0.1
|
||||
html-minifier-terser: ^6.1.0
|
||||
node-html-parser: ^5.3.3
|
||||
pathe: ^0.2.0
|
||||
peerDependencies:
|
||||
vite: ">=2.0.0"
|
||||
checksum: 2fd6e1f91f74a4432222ed28e68d5f27e58ccbc9ad44e71ff9d02b684b358b0c634bdb4dd32e9d93d09e88d83c3b7b74b89698e25510bc5b94173cdc067b3ac2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-plugin-node-polyfills@npm:^0.22.0":
|
||||
version: 0.22.0
|
||||
resolution: "vite-plugin-node-polyfills@npm:0.22.0"
|
||||
dependencies:
|
||||
"@rollup/plugin-inject": ^5.0.5
|
||||
node-stdlib-browser: ^1.2.0
|
||||
peerDependencies:
|
||||
vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
checksum: c08d3df0d5cc3102280483d3f7b216f92a18e0708fcb9f67f78f01ab865474254756bacee17caa90b3f46afe5834cb9d8de0dd0e58c1bbbdae1b949edc1e6b57
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:^5.0.0":
|
||||
version: 5.4.11
|
||||
resolution: "vite@npm:5.4.11"
|
||||
dependencies:
|
||||
esbuild: ^0.21.3
|
||||
fsevents: ~2.3.3
|
||||
postcss: ^8.4.43
|
||||
rollup: ^4.20.0
|
||||
peerDependencies:
|
||||
"@types/node": ^18.0.0 || >=20.0.0
|
||||
less: "*"
|
||||
lightningcss: ^1.21.0
|
||||
sass: "*"
|
||||
sass-embedded: "*"
|
||||
stylus: "*"
|
||||
sugarss: "*"
|
||||
terser: ^5.4.0
|
||||
dependenciesMeta:
|
||||
fsevents:
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
"@types/node":
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
bin:
|
||||
vite: bin/vite.js
|
||||
checksum: 8c5b31d17487b69c40a30419dc0ade9f33360eb6893dbfa33a90980271bd74d35ae550b5cbb2a9e640f0df41ea36fd1bb4f222c98f6d02e607080f20832e69e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vm-browserify@npm:^1.0.1":
|
||||
version: 1.1.2
|
||||
resolution: "vm-browserify@npm:1.1.2"
|
||||
@@ -47650,7 +47347,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xtend@npm:^4.0.0, xtend@npm:^4.0.2":
|
||||
"xtend@npm:^4.0.0":
|
||||
version: 4.0.2
|
||||
resolution: "xtend@npm:4.0.2"
|
||||
checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a
|
||||
|
||||
Reference in New Issue
Block a user