Merge branch 'master' of github.com:backstage/backstage into github-installations-limit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes-backend': patch
|
||||
---
|
||||
|
||||
Adds ability to send an ExternalId with the assume role request to AWS
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
- Adds a new field `EntityNamePicker` that can be used in scaffolder templates to accept and validate an entity name. This field is registered by default, and can be used in templates by setting the `ui:field` property to `EntityNamePicker`. If you've customized your scaffolder field extensions, you can include this one by adding it when registering the scaffolder route:
|
||||
|
||||
```diff
|
||||
import {
|
||||
ScaffolderFieldExtensions,
|
||||
+ EntityNamePickerFieldExtension,
|
||||
} from '@backstage/plugin-scaffolder';
|
||||
|
||||
<Route path="/create" element={<ScaffolderPage />}>
|
||||
<ScaffolderFieldExtensions>
|
||||
{/* ...custom field extensions... */}
|
||||
|
||||
+ <EntityNamePickerFieldExtension />
|
||||
</ScaffolderFieldExtensions>
|
||||
</Route>;
|
||||
```
|
||||
|
||||
- Adds a new generic field `TextValuePicker` to be used when writing custom field extensions that use a standard UI with custom validation. An example of doing this can be found in `packages/app/src/components/scaffolder/customScaffolderExtensions.tsx`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-app-api': patch
|
||||
---
|
||||
|
||||
Add support for serving the app with a base path other than `/`, which is enabled by including the path in `app.baseUrl`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-sonarqube': minor
|
||||
---
|
||||
|
||||
Use IdentityApi to provide Auth Token for SonarQubeClient Api calls
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Updated the index page redirect to work with apps served on a different base path than `/`.
|
||||
|
||||
To apply this change to an existing app, remove the `/` prefix from the target route in the `Navigate` element in `packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
-<Navigate key="/" to="/catalog" />
|
||||
+<Navigate key="/" to="catalog" />
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Remove repo restriction from GitHub credentials provider
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Migrate to using `webpack@5` 🎉
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Removed the `/` prefix in the catalog `SidebarItem` element, as it is no longer needed.
|
||||
|
||||
To apply this change to an existing app, remove the `/` prefix from the catalog and any other sidebar items in `packages/app/src/components/Root/Root.ts`:
|
||||
|
||||
```diff
|
||||
-<SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
|
||||
+<SidebarItem icon={HomeIcon} to="catalog" text="Home" />
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Fix for `SidebarItem` matching the active route too broadly.
|
||||
@@ -50,4 +50,5 @@ jobs:
|
||||
# projetToken intentionally shared to allow collaborators to run Chromatic on forks
|
||||
# https://www.chromatic.com/docs/custom-ci-provider#run-chromatic-on-external-forks-of-open-source-projects
|
||||
projectToken: 9tzak77m9nj
|
||||
storybookBuildDir: 'packages/storybook/dist'
|
||||
workingDir: 'packages/storybook'
|
||||
storybookBuildDir: 'dist'
|
||||
|
||||
+1
-3
@@ -1,5 +1,3 @@
|
||||
{
|
||||
"ignoredFiles": [
|
||||
"docs/assets/**/*.svg"
|
||||
]
|
||||
"ignoredFiles": ["docs/assets/**/*.svg"]
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
# [Backstage](https://backstage.io)
|
||||
|
||||
_During the month of July the majority of the maintainers will be on summer vacation 🏖️ Development will continue as usual, but expect a slower pace for discussions and PR reviews. Why not take this opportunity to [build a plugin](https://backstage.io/docs/plugins/)?_
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://www.cncf.io/projects)
|
||||
[](https://github.com/backstage/backstage/actions?query=workflow%3A%22Main+Master+Build%22)
|
||||
|
||||
@@ -244,9 +244,7 @@ export interface OAuthProviderHandlers {
|
||||
req: express.Request,
|
||||
options: Record<string, string>,
|
||||
): Promise<RedirectInfo>;
|
||||
handler(
|
||||
req: express.Request,
|
||||
): Promise<{
|
||||
handler(req: express.Request): Promise<{
|
||||
response: AuthResponse<OAuthProviderInfo>;
|
||||
refreshToken?: string;
|
||||
}>;
|
||||
|
||||
@@ -187,7 +187,7 @@ new `CustomCatalogIndexPage`.
|
||||
# packages/app/src/App.tsx
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Navigate key="/" to="/catalog" />
|
||||
<Navigate key="/" to="catalog" />
|
||||
- <Route path="/catalog" element={<CatalogIndexPage />} />
|
||||
+ <Route path="/catalog" element={<CustomCatalogIndexPage />} />
|
||||
```
|
||||
|
||||
@@ -56,13 +56,11 @@ matching repository is processed.
|
||||
repository.
|
||||
|
||||
```typescript
|
||||
const customRepositoryParser: BitbucketRepositoryParser = async function* customRepositoryParser({
|
||||
client,
|
||||
repository,
|
||||
}) {
|
||||
// Custom logic for interpret the matching repository.
|
||||
// See defaultRepositoryParser for an example
|
||||
};
|
||||
const customRepositoryParser: BitbucketRepositoryParser =
|
||||
async function* customRepositoryParser({ client, repository }) {
|
||||
// Custom logic for interpret the matching repository.
|
||||
// See defaultRepositoryParser for an example
|
||||
};
|
||||
|
||||
const processor = BitbucketDiscoveryProcessor.fromConfig(env.config, {
|
||||
parser: customRepositoryParser,
|
||||
|
||||
@@ -312,14 +312,6 @@ configuration.
|
||||
|
||||
Stability: `1`
|
||||
|
||||
### `register-component` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/register-component/)
|
||||
|
||||
A frontend plugin that allows the user to register entity locations in the
|
||||
catalog.
|
||||
|
||||
Stability: `0`. This plugin is likely to be replaced by a generic entity import
|
||||
plugin instead.
|
||||
|
||||
### `scaffolder` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/scaffolder/)
|
||||
|
||||
The frontend scaffolder plugin where one can browse templates and initiate
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
"packages": ["packages/*", "plugins/*"],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.1.1"
|
||||
"version": "0.1.0"
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"plugins/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/@storybook/**",
|
||||
"**/@storybook"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
@@ -55,6 +59,7 @@
|
||||
"@microsoft/api-extractor-model": "^7.13.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@changesets/cli": "^2.14.0",
|
||||
"@octokit/openapi-types": "^2.2.0",
|
||||
"@spotify/prettier-config": "^10.0.0",
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^0.1.16",
|
||||
"@rjsf/core": "^3.0.0",
|
||||
"@testing-library/cypress": "^7.0.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -41,7 +41,15 @@ import { GcpProjectsPage } from '@backstage/plugin-gcp-projects';
|
||||
import { GraphiQLPage } from '@backstage/plugin-graphiql';
|
||||
import { LighthousePage } from '@backstage/plugin-lighthouse';
|
||||
import { NewRelicPage } from '@backstage/plugin-newrelic';
|
||||
import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
|
||||
import {
|
||||
ScaffolderPage,
|
||||
scaffolderPlugin,
|
||||
ScaffolderFieldExtensions,
|
||||
RepoUrlPickerFieldExtension,
|
||||
OwnerPickerFieldExtension,
|
||||
EntityPickerFieldExtension,
|
||||
EntityNamePickerFieldExtension,
|
||||
} from '@backstage/plugin-scaffolder';
|
||||
import { SearchPage } from '@backstage/plugin-search';
|
||||
import { TechRadarPage } from '@backstage/plugin-tech-radar';
|
||||
import { TechdocsPage } from '@backstage/plugin-techdocs';
|
||||
@@ -54,6 +62,7 @@ import { apis } from './apis';
|
||||
import { Root } from './components/Root';
|
||||
import { entityPage } from './components/catalog/EntityPage';
|
||||
import { searchPage } from './components/search/SearchPage';
|
||||
import { LowerCaseValuePickerFieldExtension } from './components/scaffolder/customScaffolderExtensions';
|
||||
import { providers } from './identityProviders';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
@@ -98,7 +107,7 @@ const AppRouter = app.getRouter();
|
||||
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Navigate key="/" to="/catalog" />
|
||||
<Navigate key="/" to="catalog" />
|
||||
<Route path="/catalog" element={<CatalogIndexPage />} />
|
||||
<Route
|
||||
path="/catalog/:namespace/:kind/:name"
|
||||
@@ -108,7 +117,15 @@ const routes = (
|
||||
</Route>
|
||||
<Route path="/catalog-import" element={<CatalogImportPage />} />
|
||||
<Route path="/docs" element={<TechdocsPage />} />
|
||||
<Route path="/create" element={<ScaffolderPage />} />
|
||||
<Route path="/create" element={<ScaffolderPage />}>
|
||||
<ScaffolderFieldExtensions>
|
||||
<EntityPickerFieldExtension />
|
||||
<EntityNamePickerFieldExtension />
|
||||
<RepoUrlPickerFieldExtension />
|
||||
<OwnerPickerFieldExtension />
|
||||
<LowerCaseValuePickerFieldExtension />
|
||||
</ScaffolderFieldExtensions>
|
||||
</Route>
|
||||
<Route path="/explore" element={<ExplorePage />} />
|
||||
<Route
|
||||
path="/tech-radar"
|
||||
@@ -116,7 +133,6 @@ const routes = (
|
||||
/>
|
||||
<Route path="/graphiql" element={<GraphiQLPage />} />
|
||||
<Route path="/lighthouse" element={<LighthousePage />} />
|
||||
|
||||
<Route path="/api-docs" element={<ApiExplorerPage />} />
|
||||
<Route path="/gcp-projects" element={<GcpProjectsPage />} />
|
||||
<Route path="/newrelic" element={<NewRelicPage />} />
|
||||
|
||||
@@ -82,7 +82,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
|
||||
<SidebarSearch />
|
||||
<SidebarDivider />
|
||||
{/* Global nav, not org-specific */}
|
||||
<SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
|
||||
<SidebarItem icon={HomeIcon} to="catalog" text="Home" />
|
||||
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
|
||||
<SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
|
||||
<SidebarItem icon={LayersIcon} to="explore" text="Explore" />
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { FieldValidation } from '@rjsf/core';
|
||||
import {
|
||||
createScaffolderFieldExtension,
|
||||
TextValuePicker,
|
||||
scaffolderPlugin,
|
||||
} from '@backstage/plugin-scaffolder';
|
||||
|
||||
export const LowerCaseValuePickerFieldExtension = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
name: 'LowerCaseValuePicker',
|
||||
component: TextValuePicker,
|
||||
validation: (value: string, validation: FieldValidation) => {
|
||||
if (value.toLowerCase() !== value) {
|
||||
validation.addError('Only lowercase values are allowed.');
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
+4
-4
@@ -104,7 +104,7 @@ describe('CacheManager', () => {
|
||||
manager.forPlugin(plugin2Id).getClient({ defaultTtl: expectedTtl });
|
||||
|
||||
const client = DefaultCacheClient as jest.Mock;
|
||||
const cache = (Keyv as unknown) as jest.Mock;
|
||||
const cache = Keyv as unknown as jest.Mock;
|
||||
expect(cache).toHaveBeenCalledTimes(2);
|
||||
expect(client).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -124,7 +124,7 @@ describe('CacheManager', () => {
|
||||
const expectedNamespace = 'test-plugin';
|
||||
manager.forPlugin(expectedNamespace).getClient();
|
||||
|
||||
const cache = (Keyv as unknown) as jest.Mock;
|
||||
const cache = Keyv as unknown as jest.Mock;
|
||||
const mockCalls = cache.mock.calls.splice(-1);
|
||||
const callArgs = mockCalls[0];
|
||||
expect(callArgs[0].store).toBeInstanceOf(NoStore);
|
||||
@@ -138,7 +138,7 @@ describe('CacheManager', () => {
|
||||
.forPlugin(expectedNamespace)
|
||||
.getClient({ defaultTtl: expectedTtl });
|
||||
|
||||
const cache = (Keyv as unknown) as jest.Mock;
|
||||
const cache = Keyv as unknown as jest.Mock;
|
||||
const mockCalls = cache.mock.calls.splice(-1);
|
||||
const callArgs = mockCalls[0];
|
||||
expect(callArgs[0]).toMatchObject({
|
||||
@@ -162,7 +162,7 @@ describe('CacheManager', () => {
|
||||
const expectedTtl = 3600;
|
||||
manager.forPlugin('test').getClient({ defaultTtl: expectedTtl });
|
||||
|
||||
const cache = (Keyv as unknown) as jest.Mock;
|
||||
const cache = Keyv as unknown as jest.Mock;
|
||||
const mockCacheCalls = cache.mock.calls.splice(-1);
|
||||
expect(mockCacheCalls[0][0]).toMatchObject({
|
||||
ttl: expectedTtl,
|
||||
|
||||
@@ -110,9 +110,7 @@ export class DatabaseManager {
|
||||
* @returns Object with client type returned as `client` and boolean representing whether
|
||||
* or not the client was overridden as `overridden`
|
||||
*/
|
||||
private getClientType(
|
||||
pluginId: string,
|
||||
): {
|
||||
private getClientType(pluginId: string): {
|
||||
client: string;
|
||||
overridden: boolean;
|
||||
} {
|
||||
|
||||
@@ -149,7 +149,7 @@ export async function ensureMysqlDatabaseExists(
|
||||
) {
|
||||
const admin = createMysqlDatabaseClient(dbConfig, {
|
||||
connection: {
|
||||
database: (null as unknown) as string,
|
||||
database: null as unknown as string,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -78,21 +78,17 @@ describe('AzureUrlReader', () => {
|
||||
|
||||
it.each([
|
||||
{
|
||||
url:
|
||||
'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster',
|
||||
url: 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster',
|
||||
config: createConfig(),
|
||||
response: expect.objectContaining({
|
||||
url:
|
||||
'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master',
|
||||
url: 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master',
|
||||
}),
|
||||
},
|
||||
{
|
||||
url:
|
||||
'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml',
|
||||
url: 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml',
|
||||
config: createConfig(),
|
||||
response: expect.objectContaining({
|
||||
url:
|
||||
'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml',
|
||||
url: 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml',
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -129,7 +129,7 @@ export class AzureUrlReader implements UrlReader {
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromZipArchive({
|
||||
stream: (archiveAzureResponse.body as unknown) as Readable,
|
||||
stream: archiveAzureResponse.body as unknown as Readable,
|
||||
etag: commitSha,
|
||||
filter: options?.filter,
|
||||
});
|
||||
|
||||
@@ -60,13 +60,8 @@ export class BitbucketUrlReader implements UrlReader {
|
||||
private readonly integration: BitbucketIntegration,
|
||||
private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },
|
||||
) {
|
||||
const {
|
||||
host,
|
||||
apiBaseUrl,
|
||||
token,
|
||||
username,
|
||||
appPassword,
|
||||
} = integration.config;
|
||||
const { host, apiBaseUrl, token, username, appPassword } =
|
||||
integration.config;
|
||||
|
||||
if (!apiBaseUrl) {
|
||||
throw new Error(
|
||||
@@ -138,7 +133,7 @@ export class BitbucketUrlReader implements UrlReader {
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
stream: (archiveBitbucketResponse.body as unknown) as Readable,
|
||||
stream: archiveBitbucketResponse.body as unknown as Readable,
|
||||
subpath: filepath,
|
||||
etag: lastCommitShortHash,
|
||||
filter: options?.filter,
|
||||
|
||||
@@ -41,9 +41,9 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
const mockCredentialsProvider = ({
|
||||
const mockCredentialsProvider = {
|
||||
getCredentials: jest.fn().mockResolvedValue({ headers: {} }),
|
||||
} as unknown) as GithubCredentialsProvider;
|
||||
} as unknown as GithubCredentialsProvider;
|
||||
|
||||
const githubProcessor = new GithubUrlReader(
|
||||
new GitHubIntegration(
|
||||
|
||||
@@ -39,10 +39,14 @@ import {
|
||||
ReadUrlResponse,
|
||||
} from './types';
|
||||
|
||||
export type GhRepoResponse = RestEndpointMethodTypes['repos']['get']['response']['data'];
|
||||
export type GhBranchResponse = RestEndpointMethodTypes['repos']['getBranch']['response']['data'];
|
||||
export type GhTreeResponse = RestEndpointMethodTypes['git']['getTree']['response']['data'];
|
||||
export type GhBlobResponse = RestEndpointMethodTypes['git']['getBlob']['response']['data'];
|
||||
export type GhRepoResponse =
|
||||
RestEndpointMethodTypes['repos']['get']['response']['data'];
|
||||
export type GhBranchResponse =
|
||||
RestEndpointMethodTypes['repos']['getBranch']['response']['data'];
|
||||
export type GhTreeResponse =
|
||||
RestEndpointMethodTypes['git']['getTree']['response']['data'];
|
||||
export type GhBlobResponse =
|
||||
RestEndpointMethodTypes['git']['getBlob']['response']['data'];
|
||||
|
||||
/**
|
||||
* A processor that adds the ability to read files from GitHub v3 APIs, such as
|
||||
@@ -195,7 +199,7 @@ export class GithubUrlReader implements UrlReader {
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
// TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want
|
||||
// to stick to using that in exclusively backend code.
|
||||
stream: (archive.body as unknown) as Readable,
|
||||
stream: archive.body as unknown as Readable,
|
||||
subpath,
|
||||
etag: sha,
|
||||
filter: options?.filter,
|
||||
@@ -258,9 +262,7 @@ export class GithubUrlReader implements UrlReader {
|
||||
}));
|
||||
}
|
||||
|
||||
private async getRepoDetails(
|
||||
url: string,
|
||||
): Promise<{
|
||||
private async getRepoDetails(url: string): Promise<{
|
||||
repo: GhRepoResponse;
|
||||
branch: GhBranchResponse;
|
||||
}> {
|
||||
|
||||
@@ -108,36 +108,30 @@ describe('GitlabUrlReader', () => {
|
||||
it.each([
|
||||
// Project URLs
|
||||
{
|
||||
url:
|
||||
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
|
||||
config: createConfig(),
|
||||
response: expect.objectContaining({
|
||||
url:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
url: 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
headers: expect.objectContaining({
|
||||
'private-token': '',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
url:
|
||||
'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
|
||||
url: 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
|
||||
config: createConfig('0123456789'),
|
||||
response: expect.objectContaining({
|
||||
url:
|
||||
'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
url: 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
headers: expect.objectContaining({
|
||||
'private-token': '0123456789',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
url:
|
||||
'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/to/file.yaml', // Repo not in subgroup
|
||||
url: 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/to/file.yaml', // Repo not in subgroup
|
||||
config: createConfig(),
|
||||
response: expect.objectContaining({
|
||||
url:
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
url: 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
}),
|
||||
},
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ export class GitlabUrlReader implements UrlReader {
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
stream: (archiveGitLabResponse.body as unknown) as Readable,
|
||||
stream: archiveGitLabResponse.body as unknown as Readable,
|
||||
subpath: filepath,
|
||||
etag: commitSha,
|
||||
filter: options?.filter,
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
import { stripFirstDirectoryFromPath } from './util';
|
||||
|
||||
// Tar types for `Parse` is not a proper constructor, but it should be
|
||||
const TarParseStream = (Parse as unknown) as { new (): ParseStream };
|
||||
const TarParseStream = Parse as unknown as { new (): ParseStream };
|
||||
|
||||
const pipeline = promisify(pipelineCb);
|
||||
|
||||
|
||||
@@ -123,9 +123,9 @@ describe('Git', () => {
|
||||
|
||||
await git.clone({ url, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.clone as unknown) as jest.Mock<
|
||||
typeof isomorphic['clone']
|
||||
>).mock.calls[0][0]!;
|
||||
const { onAuth } = (
|
||||
isomorphic.clone as unknown as jest.Mock<typeof isomorphic['clone']>
|
||||
).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
@@ -190,9 +190,9 @@ describe('Git', () => {
|
||||
|
||||
await git.fetch({ remote, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.fetch as unknown) as jest.Mock<
|
||||
typeof isomorphic['fetch']
|
||||
>).mock.calls[0][0]!;
|
||||
const { onAuth } = (
|
||||
isomorphic.fetch as unknown as jest.Mock<typeof isomorphic['fetch']>
|
||||
).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
@@ -279,9 +279,9 @@ describe('Git', () => {
|
||||
|
||||
await git.push({ remote, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.push as unknown) as jest.Mock<
|
||||
typeof isomorphic['push']
|
||||
>).mock.calls[0][0]!;
|
||||
const { onAuth } = (
|
||||
isomorphic.push as unknown as jest.Mock<typeof isomorphic['push']>
|
||||
).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
|
||||
@@ -154,14 +154,8 @@ export class ServiceBuilderImpl implements ServiceBuilder {
|
||||
|
||||
async start(): Promise<http.Server> {
|
||||
const app = express();
|
||||
const {
|
||||
port,
|
||||
host,
|
||||
logger,
|
||||
corsOptions,
|
||||
httpsSettings,
|
||||
helmetOptions,
|
||||
} = this.getOptions();
|
||||
const { port, host, logger, corsOptions, httpsSettings, helmetOptions } =
|
||||
this.getOptions();
|
||||
|
||||
app.use(helmet(helmetOptions));
|
||||
if (corsOptions) {
|
||||
|
||||
@@ -92,18 +92,17 @@ export class DockerContainerRunner implements ContainerRunner {
|
||||
Env.push(`${key}=${value}`);
|
||||
}
|
||||
|
||||
const [
|
||||
{ Error: error, StatusCode: statusCode },
|
||||
] = await this.dockerClient.run(imageName, args, logStream, {
|
||||
Volumes,
|
||||
HostConfig: {
|
||||
Binds,
|
||||
},
|
||||
...(workingDir ? { WorkingDir: workingDir } : {}),
|
||||
Entrypoint: command,
|
||||
Env,
|
||||
...userOptions,
|
||||
} as Docker.ContainerCreateOptions);
|
||||
const [{ Error: error, StatusCode: statusCode }] =
|
||||
await this.dockerClient.run(imageName, args, logStream, {
|
||||
Volumes,
|
||||
HostConfig: {
|
||||
Binds,
|
||||
},
|
||||
...(workingDir ? { WorkingDir: workingDir } : {}),
|
||||
Entrypoint: command,
|
||||
Env,
|
||||
...userOptions,
|
||||
} as Docker.ContainerCreateOptions);
|
||||
|
||||
if (error) {
|
||||
throw new Error(
|
||||
|
||||
@@ -38,33 +38,31 @@ export type Instance = {
|
||||
databaseManager: DatabaseManager;
|
||||
connections: Array<Knex>;
|
||||
};
|
||||
export const allDatabases: Record<
|
||||
TestDatabaseId,
|
||||
TestDatabaseProperties
|
||||
> = Object.freeze({
|
||||
POSTGRES_13: {
|
||||
name: 'Postgres 13.x',
|
||||
driver: 'pg',
|
||||
dockerImageName: 'postgres:13',
|
||||
connectionStringEnvironmentVariableName:
|
||||
'BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING',
|
||||
},
|
||||
POSTGRES_9: {
|
||||
name: 'Postgres 9.x',
|
||||
driver: 'pg',
|
||||
dockerImageName: 'postgres:9',
|
||||
connectionStringEnvironmentVariableName:
|
||||
'BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING',
|
||||
},
|
||||
MYSQL_8: {
|
||||
name: 'MySQL 8.x',
|
||||
driver: 'mysql2',
|
||||
dockerImageName: 'mysql:8',
|
||||
connectionStringEnvironmentVariableName:
|
||||
'BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING',
|
||||
},
|
||||
SQLITE_3: {
|
||||
name: 'SQLite 3.x',
|
||||
driver: 'sqlite3',
|
||||
},
|
||||
});
|
||||
export const allDatabases: Record<TestDatabaseId, TestDatabaseProperties> =
|
||||
Object.freeze({
|
||||
POSTGRES_13: {
|
||||
name: 'Postgres 13.x',
|
||||
driver: 'pg',
|
||||
dockerImageName: 'postgres:13',
|
||||
connectionStringEnvironmentVariableName:
|
||||
'BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING',
|
||||
},
|
||||
POSTGRES_9: {
|
||||
name: 'Postgres 9.x',
|
||||
driver: 'pg',
|
||||
dockerImageName: 'postgres:9',
|
||||
connectionStringEnvironmentVariableName:
|
||||
'BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING',
|
||||
},
|
||||
MYSQL_8: {
|
||||
name: 'MySQL 8.x',
|
||||
driver: 'mysql2',
|
||||
dockerImageName: 'mysql:8',
|
||||
connectionStringEnvironmentVariableName:
|
||||
'BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING',
|
||||
},
|
||||
SQLITE_3: {
|
||||
name: 'SQLite 3.x',
|
||||
driver: 'sqlite3',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -190,7 +190,7 @@ export type EntityEnvelope = {
|
||||
//
|
||||
// @public
|
||||
export function entityEnvelopeSchemaValidator<
|
||||
T extends EntityEnvelope = EntityEnvelope
|
||||
T extends EntityEnvelope = EntityEnvelope,
|
||||
>(schema?: unknown): (data: unknown) => T;
|
||||
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
@@ -333,9 +333,7 @@ export function getEntityName(entity: Entity): EntityName;
|
||||
// Warning: (ae-missing-release-tag) "getEntitySourceLocation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export function getEntitySourceLocation(
|
||||
entity: Entity,
|
||||
): {
|
||||
export function getEntitySourceLocation(entity: Entity): {
|
||||
type: string;
|
||||
target: string;
|
||||
};
|
||||
@@ -542,9 +540,7 @@ export function parseEntityRef(
|
||||
// Warning: (ae-missing-release-tag) "parseLocationReference" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export function parseLocationReference(
|
||||
ref: string,
|
||||
): {
|
||||
export function parseLocationReference(ref: string): {
|
||||
type: string;
|
||||
target: string;
|
||||
};
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('SchemaValidEntityPolicy', () => {
|
||||
//
|
||||
|
||||
it('rejects wrong root type', async () => {
|
||||
await expect(policy.enforce((7 as unknown) as Entity)).rejects.toThrow(
|
||||
await expect(policy.enforce(7 as unknown as Entity)).rejects.toThrow(
|
||||
/object/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -18,9 +18,7 @@ import { EntityName, EntityRef } from '../types';
|
||||
import { ENTITY_DEFAULT_NAMESPACE } from './constants';
|
||||
import { Entity } from './Entity';
|
||||
|
||||
function parseRefString(
|
||||
ref: string,
|
||||
): {
|
||||
function parseRefString(ref: string): {
|
||||
kind?: string;
|
||||
namespace?: string;
|
||||
name: string;
|
||||
|
||||
@@ -30,6 +30,5 @@ export interface ApiEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const apiEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const apiEntityV1alpha1Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -33,6 +33,5 @@ export interface ComponentEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const componentEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const componentEntityV1alpha1Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -26,6 +26,5 @@ export interface DomainEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const domainEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const domainEntityV1alpha1Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -34,6 +34,5 @@ export interface GroupEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const groupEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const groupEntityV1alpha1Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -28,6 +28,5 @@ export interface LocationEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const locationEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const locationEntityV1alpha1Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -29,6 +29,5 @@ export interface ResourceEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const resourceEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const resourceEntityV1alpha1Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -27,6 +27,5 @@ export interface SystemEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const systemEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const systemEntityV1alpha1Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -40,6 +40,5 @@ export interface TemplateEntityV1beta2 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const templateEntityV1beta2Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const templateEntityV1beta2Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -31,6 +31,5 @@ export interface UserEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const userEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
schema,
|
||||
);
|
||||
export const userEntityV1alpha1Validator =
|
||||
ajvCompiledJsonSchemaValidator(schema);
|
||||
|
||||
@@ -26,9 +26,10 @@ import { LOCATION_ANNOTATION, SOURCE_LOCATION_ANNOTATION } from './annotation';
|
||||
* @param ref A string-form location reference, e.g. 'url:https://host'
|
||||
* @returns A location reference, e.g. { type: 'url', target: 'https://host' }
|
||||
*/
|
||||
export function parseLocationReference(
|
||||
ref: string,
|
||||
): { type: string; target: string } {
|
||||
export function parseLocationReference(ref: string): {
|
||||
type: string;
|
||||
target: string;
|
||||
} {
|
||||
if (typeof ref !== 'string') {
|
||||
throw new TypeError(
|
||||
`Unable to parse location reference '${ref}', unexpected argument ${typeof ref}`,
|
||||
@@ -91,9 +92,10 @@ export function stringifyLocationReference(ref: {
|
||||
* using the UrlReader from @backstage/backend-common. If it is not of type 'url', the caller
|
||||
* needs to have explicit handling of each location type or signal that it is not supported.
|
||||
*/
|
||||
export function getEntitySourceLocation(
|
||||
entity: Entity,
|
||||
): { type: string; target: string } {
|
||||
export function getEntitySourceLocation(entity: Entity): {
|
||||
type: string;
|
||||
target: string;
|
||||
} {
|
||||
const locationRef =
|
||||
entity.metadata?.annotations?.[SOURCE_LOCATION_ANNOTATION] ??
|
||||
entity.metadata?.annotations?.[LOCATION_ANNOTATION];
|
||||
|
||||
@@ -41,7 +41,7 @@ import { compileAjvSchema, throwAjvError } from './ajv';
|
||||
* @see https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema
|
||||
*/
|
||||
export function entityEnvelopeSchemaValidator<
|
||||
T extends EntityEnvelope = EntityEnvelope
|
||||
T extends EntityEnvelope = EntityEnvelope,
|
||||
>(schema?: unknown): (data: unknown) => T {
|
||||
const validate = compileAjvSchema(
|
||||
schema ? (schema as Schema) : entityEnvelopeSchema,
|
||||
|
||||
+15
-14
@@ -50,9 +50,7 @@
|
||||
"@svgr/plugin-svgo": "5.4.x",
|
||||
"@svgr/rollup": "5.5.x",
|
||||
"@svgr/webpack": "5.5.x",
|
||||
"@types/start-server-webpack-plugin": "^2.2.0",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@types/webpack-node-externals": "^2.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^v4.28.3",
|
||||
"@typescript-eslint/parser": "^v4.28.3",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
@@ -76,18 +74,19 @@
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"express": "^4.17.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^6.2.9",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.5",
|
||||
"fs-extra": "9.1.0",
|
||||
"handlebars": "^4.7.3",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"inquirer": "^7.0.4",
|
||||
"jest": "^26.0.1",
|
||||
"jest-css-modules": "^2.1.0",
|
||||
"json-schema": "^0.3.0",
|
||||
"lodash": "^4.17.19",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"mini-css-extract-plugin": "^1.4.1",
|
||||
"ora": "^5.3.0",
|
||||
"postcss": "^8.1.0",
|
||||
"process": "^0.11.10",
|
||||
"raw-loader": "^4.0.1",
|
||||
"react": "^16.0.0",
|
||||
"react-dev-utils": "^11.0.4",
|
||||
@@ -100,17 +99,18 @@
|
||||
"rollup-plugin-peer-deps-external": "^2.2.2",
|
||||
"rollup-plugin-postcss": "^4.0.0",
|
||||
"rollup-pluginutils": "^2.8.2",
|
||||
"run-script-webpack-plugin": "^0.0.11",
|
||||
"semver": "^7.3.2",
|
||||
"start-server-webpack-plugin": "^2.2.5",
|
||||
"style-loader": "^1.2.1",
|
||||
"sucrase": "^3.18.2",
|
||||
"tar": "^6.1.2",
|
||||
"terser-webpack-plugin": "^1.4.3",
|
||||
"terser-webpack-plugin": "^5.1.3",
|
||||
"ts-loader": "^8.0.17",
|
||||
"typescript": "^4.0.3",
|
||||
"url-loader": "^4.1.0",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-dev-server": "3.11.0",
|
||||
"util": "^0.12.3",
|
||||
"webpack": "^5.48.0",
|
||||
"webpack-dev-server": "4.0.0-rc.0",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"yaml": "^1.10.0",
|
||||
"yaml-jest": "^1.0.5",
|
||||
@@ -129,25 +129,26 @@
|
||||
"@types/diff": "^5.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/html-webpack-plugin": "^3.2.2",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/inquirer": "^7.3.1",
|
||||
"@types/mini-css-extract-plugin": "^1.2.2",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/react-dev-utils": "^9.0.4",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"@types/rollup-plugin-peer-deps-external": "^2.2.0",
|
||||
"@types/rollup-plugin-postcss": "^2.0.0",
|
||||
"@types/tar": "^4.0.3",
|
||||
"@types/webpack": "^4.41.7",
|
||||
"@types/webpack-dev-server": "^3.11.0",
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-dev-server": "^3.11.5",
|
||||
"@types/yarnpkg__lockfile": "^1.1.4",
|
||||
"del": "^6.0.0",
|
||||
"mock-fs": "^4.13.0",
|
||||
"nodemon": "^2.0.2",
|
||||
"ts-node": "^10.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/webpack-dev-server/@types/webpack": "^5.28.0"
|
||||
},
|
||||
"files": [
|
||||
"asset-types",
|
||||
"templates",
|
||||
|
||||
@@ -55,11 +55,9 @@ export default async (cmd: Command) => {
|
||||
lockfile.replaceVersions(result.newVersions);
|
||||
await lockfile.save();
|
||||
} else {
|
||||
const [
|
||||
newVersionsForbidden,
|
||||
newVersionsAllowed,
|
||||
] = partition(result.newVersions, ({ name }) =>
|
||||
forbiddenDuplicatesFilter(name),
|
||||
const [newVersionsForbidden, newVersionsAllowed] = partition(
|
||||
result.newVersions,
|
||||
({ name }) => forbiddenDuplicatesFilter(name),
|
||||
);
|
||||
if (newVersionsForbidden.length && !fix) {
|
||||
success = false;
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { ResolvePlugin } from 'webpack';
|
||||
import { WebpackPluginInstance } from 'webpack';
|
||||
import { isChildPath } from '@backstage/cli-common';
|
||||
import { LernaPackage } from './types';
|
||||
|
||||
// Enables proper resolution of packages when linking in external packages.
|
||||
// Without this the packages would depend on dependencies in the node_modules
|
||||
// of the external packages themselves, leading to module duplication
|
||||
export class LinkedPackageResolvePlugin implements ResolvePlugin {
|
||||
export class LinkedPackageResolvePlugin implements WebpackPluginInstance {
|
||||
constructor(
|
||||
private readonly targetModules: string,
|
||||
private readonly packages: LernaPackage[],
|
||||
|
||||
@@ -26,23 +26,17 @@ export async function serveBackend(options: BackendServeOptions) {
|
||||
isDev: true,
|
||||
});
|
||||
|
||||
const compiler = webpack(config);
|
||||
|
||||
const watcher = compiler.watch(
|
||||
{
|
||||
poll: true,
|
||||
},
|
||||
(err: Error) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else console.log('Build succeeded');
|
||||
},
|
||||
);
|
||||
const compiler = webpack(config, (err: Error | undefined) => {
|
||||
if (err) {
|
||||
console.log('here');
|
||||
console.error(err);
|
||||
} else console.log('Build succeeded');
|
||||
});
|
||||
|
||||
const waitForExit = async () => {
|
||||
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
||||
process.on(signal, () => {
|
||||
watcher.close(() => console.log('Stopped watcher'));
|
||||
compiler.close(() => console.log('Stopped watcher'));
|
||||
// exit instead of resolve. The process is shutting down and resolving a promise here logs an error
|
||||
process.exit();
|
||||
});
|
||||
|
||||
@@ -69,6 +69,10 @@ export async function buildBundle(options: BuildOptions) {
|
||||
throw new Error(`Failed to compile.\n${error.message || error}`);
|
||||
});
|
||||
|
||||
if (!stats) {
|
||||
throw new Error('No stats returned');
|
||||
}
|
||||
|
||||
if (statsJsonEnabled) {
|
||||
// No @types/bfj
|
||||
await require('bfj').write(
|
||||
@@ -87,25 +91,32 @@ export async function buildBundle(options: BuildOptions) {
|
||||
}
|
||||
|
||||
async function build(compiler: webpack.Compiler, isCi: boolean) {
|
||||
const stats = await new Promise<webpack.Stats>((resolve, reject) => {
|
||||
compiler.run((err, buildStats) => {
|
||||
if (err) {
|
||||
if (err.message) {
|
||||
const { errors } = formatWebpackMessages({
|
||||
errors: [err.message],
|
||||
warnings: new Array<string>(),
|
||||
} as webpack.Stats.ToJsonOutput);
|
||||
const stats = await new Promise<webpack.Stats | undefined>(
|
||||
(resolve, reject) => {
|
||||
compiler.run((err, buildStats) => {
|
||||
if (err) {
|
||||
if (err.message) {
|
||||
const { errors } = formatWebpackMessages({
|
||||
errors: [err.message],
|
||||
warnings: new Array<string>(),
|
||||
_showErrors: true,
|
||||
_showWarnings: true,
|
||||
});
|
||||
|
||||
throw new Error(errors[0]);
|
||||
throw new Error(errors[0]);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
reject(err);
|
||||
resolve(buildStats);
|
||||
}
|
||||
} else {
|
||||
resolve(buildStats);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (!stats) {
|
||||
throw new Error('No stats provided');
|
||||
}
|
||||
const { errors, warnings } = formatWebpackMessages(
|
||||
stats.toJson({ all: false, warnings: true, errors: true }),
|
||||
);
|
||||
|
||||
@@ -19,8 +19,8 @@ import { resolve as resolvePath } from 'path';
|
||||
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin';
|
||||
import StartServerPlugin from 'start-server-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin';
|
||||
import webpack, { ProvidePlugin } from 'webpack';
|
||||
import nodeExternals from 'webpack-node-externals';
|
||||
import { isChildPath } from '@backstage/cli-common';
|
||||
import { optimization } from './optimization';
|
||||
@@ -98,10 +98,9 @@ export async function createConfig(
|
||||
if (checksEnabled) {
|
||||
plugins.push(
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
configFile: paths.targetTsConfig,
|
||||
},
|
||||
eslint: {
|
||||
typescript: paths.targetTsConfig,
|
||||
eslint: true,
|
||||
eslintOptions: {
|
||||
files: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
|
||||
options: {
|
||||
parserOptions: {
|
||||
@@ -114,6 +113,15 @@ export async function createConfig(
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(blam): process is no longer auto polyfilled by webpack in v5.
|
||||
// we use the provide plugin to provide this polyfill, but lets look
|
||||
// to remove this eventually!
|
||||
plugins.push(
|
||||
new ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
);
|
||||
|
||||
plugins.push(
|
||||
new webpack.EnvironmentPlugin({
|
||||
APP_CONFIG: options.frontendAppConfigs,
|
||||
@@ -156,27 +164,33 @@ export async function createConfig(
|
||||
return {
|
||||
mode: isDev ? 'development' : 'production',
|
||||
profile: false,
|
||||
node: {
|
||||
module: 'empty',
|
||||
dgram: 'empty',
|
||||
dns: 'mock',
|
||||
fs: 'empty',
|
||||
http2: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
child_process: 'empty',
|
||||
},
|
||||
optimization: optimization(options),
|
||||
bail: false,
|
||||
performance: {
|
||||
hints: false, // we check the gzip size instead
|
||||
},
|
||||
devtool: isDev ? 'cheap-module-eval-source-map' : 'source-map',
|
||||
devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map',
|
||||
context: paths.targetPath,
|
||||
entry: [require.resolve('react-hot-loader/patch'), paths.targetEntry],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'],
|
||||
mainFields: ['browser', 'module', 'main'],
|
||||
fallback: {
|
||||
module: false,
|
||||
dgram: false,
|
||||
dns: false,
|
||||
fs: false,
|
||||
http2: false,
|
||||
net: false,
|
||||
tls: false,
|
||||
child_process: false,
|
||||
|
||||
/* new ignores */
|
||||
path: false,
|
||||
https: false,
|
||||
http: false,
|
||||
util: require.resolve('util/'),
|
||||
},
|
||||
plugins: [
|
||||
new LinkedPackageResolvePlugin(paths.rootNodeModules, externalPkgs),
|
||||
new ModuleScopePlugin(
|
||||
@@ -200,7 +214,7 @@ export async function createConfig(
|
||||
: 'static/[name].[chunkhash:8].chunk.js',
|
||||
...(isDev
|
||||
? {
|
||||
devtoolModuleFilenameTemplate: info =>
|
||||
devtoolModuleFilenameTemplate: (info: any) =>
|
||||
`file:///${resolvePath(info.absoluteResourcePath).replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
@@ -237,7 +251,7 @@ export async function createBackendConfig(
|
||||
? {
|
||||
watch: true,
|
||||
watchOptions: {
|
||||
ignored: [/node_modules\/(?!\@backstage)/],
|
||||
ignored: /node_modules\/(?!\@backstage)/,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
@@ -259,7 +273,7 @@ export async function createBackendConfig(
|
||||
performance: {
|
||||
hints: false, // we check the gzip size instead
|
||||
},
|
||||
devtool: isDev ? 'cheap-module-eval-source-map' : 'source-map',
|
||||
devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map',
|
||||
context: paths.targetPath,
|
||||
entry: [
|
||||
'webpack/hot/poll?100',
|
||||
@@ -291,7 +305,7 @@ export async function createBackendConfig(
|
||||
: '[name].[chunkhash:8].chunk.js',
|
||||
...(isDev
|
||||
? {
|
||||
devtoolModuleFilenameTemplate: info =>
|
||||
devtoolModuleFilenameTemplate: (info: any) =>
|
||||
`file:///${resolvePath(info.absoluteResourcePath).replace(
|
||||
/\\/g,
|
||||
'/',
|
||||
@@ -300,7 +314,7 @@ export async function createBackendConfig(
|
||||
: {}),
|
||||
},
|
||||
plugins: [
|
||||
new StartServerPlugin({
|
||||
new RunScriptWebpackPlugin({
|
||||
name: 'main.js',
|
||||
nodeArgs: options.inspectEnabled ? ['--inspect'] : undefined,
|
||||
}),
|
||||
@@ -308,10 +322,9 @@ export async function createBackendConfig(
|
||||
...(checksEnabled
|
||||
? [
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
configFile: paths.targetTsConfig,
|
||||
},
|
||||
eslint: {
|
||||
typescript: paths.targetTsConfig,
|
||||
eslint: true,
|
||||
eslintOptions: {
|
||||
files: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
|
||||
options: {
|
||||
parserOptions: {
|
||||
@@ -347,11 +360,10 @@ function nodeExternalsWithResolve(
|
||||
});
|
||||
|
||||
return (
|
||||
context: string,
|
||||
request: string,
|
||||
callback: webpack.ExternalsFunctionCallback,
|
||||
{ context, request }: { context?: string; request?: string },
|
||||
callback: any,
|
||||
) => {
|
||||
currentContext = context;
|
||||
currentContext = context!;
|
||||
return externals(context, request, callback);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Options } from 'webpack';
|
||||
import { WebpackOptionsNormalized, WebpackPluginInstance } from 'webpack';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import { BundlingOptions } from './types';
|
||||
import { isParallelDefault } from '../parallel';
|
||||
|
||||
export const optimization = (
|
||||
options: BundlingOptions,
|
||||
): Options.Optimization => {
|
||||
): WebpackOptionsNormalized['optimization'] => {
|
||||
const { isDev } = options;
|
||||
|
||||
return {
|
||||
@@ -32,7 +32,7 @@ export const optimization = (
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: options.parallel,
|
||||
}),
|
||||
}) as unknown as WebpackPluginInstance,
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
|
||||
@@ -42,25 +42,31 @@ export async function serveBundle(options: ServeOptions) {
|
||||
});
|
||||
const compiler = webpack(config);
|
||||
|
||||
const server = new WebpackDevServer(compiler, {
|
||||
hot: !process.env.CI,
|
||||
contentBase: paths.targetPublic,
|
||||
contentBasePublicPath: config.output?.publicPath,
|
||||
publicPath: config.output?.publicPath,
|
||||
historyApiFallback: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
},
|
||||
clientLogLevel: 'warning',
|
||||
stats: 'errors-warnings',
|
||||
https: url.protocol === 'https:',
|
||||
host,
|
||||
port,
|
||||
proxy: pkg.proxy,
|
||||
// When the dev server is behind a proxy, the host and public hostname differ
|
||||
allowedHosts: [url.hostname],
|
||||
});
|
||||
const server = new WebpackDevServer(
|
||||
compiler as any,
|
||||
{
|
||||
hot: !process.env.CI,
|
||||
devMiddleware: {
|
||||
publicPath: config.output?.publicPath as string,
|
||||
stats: 'errors-warnings',
|
||||
},
|
||||
static: {
|
||||
publicPath: config.output?.publicPath as string,
|
||||
directory: paths.targetPublic,
|
||||
},
|
||||
historyApiFallback: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
},
|
||||
https: url.protocol === 'https:',
|
||||
host,
|
||||
port,
|
||||
proxy: pkg.proxy,
|
||||
// When the dev server is behind a proxy, the host and public hostname differ
|
||||
allowedHosts: [url.hostname],
|
||||
} as any,
|
||||
);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.listen(port, host, (err?: Error) => {
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import webpack, { Module, Plugin } from 'webpack';
|
||||
import webpack, { ModuleOptions, WebpackPluginInstance } from 'webpack';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import { svgrTemplate } from '../svgrTemplate';
|
||||
|
||||
type Transforms = {
|
||||
loaders: Module['rules'];
|
||||
plugins: Plugin[];
|
||||
loaders: ModuleOptions['rules'];
|
||||
plugins: WebpackPluginInstance[];
|
||||
};
|
||||
|
||||
type TransformOptions = {
|
||||
@@ -51,6 +51,12 @@ export const transforms = (options: TransformOptions): Transforms => {
|
||||
production: !isDev,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.m?js/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: [/\.icon\.svg$/],
|
||||
use: [
|
||||
@@ -105,7 +111,7 @@ export const transforms = (options: TransformOptions): Transforms => {
|
||||
},
|
||||
];
|
||||
|
||||
const plugins = new Array<Plugin>();
|
||||
const plugins = new Array<WebpackPluginInstance>();
|
||||
|
||||
if (isDev) {
|
||||
plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
|
||||
@@ -137,9 +137,9 @@ export class Lockfile {
|
||||
}
|
||||
|
||||
// Find all versions currently in use
|
||||
const versions = Array.from(
|
||||
new Set(entries.map(e => e.version)),
|
||||
).sort((v1, v2) => semver.rcompare(v1, v2));
|
||||
const versions = Array.from(new Set(entries.map(e => e.version))).sort(
|
||||
(v1, v2) => semver.rcompare(v1, v2),
|
||||
);
|
||||
|
||||
// If we're not using at least 2 different versions we're done
|
||||
if (versions.length < 2) {
|
||||
|
||||
Vendored
+226
-1
@@ -29,5 +29,230 @@ declare module '@svgr/rollup' {
|
||||
}
|
||||
|
||||
declare module '@rollup/plugin-yaml';
|
||||
declare module 'react-dev-utils/formatWebpackMessages' {
|
||||
export default function (stats: any): {
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
};
|
||||
}
|
||||
|
||||
declare module 'terser-webpack-plugin';
|
||||
declare module 'react-dev-utils/openBrowser' {
|
||||
export default function (url: string): boolean;
|
||||
}
|
||||
|
||||
declare module 'react-dev-utils/ModuleScopePlugin' {
|
||||
import webpack = require('webpack');
|
||||
|
||||
export default class ModuleScopePlugin
|
||||
implements webpack.WebpackPluginInstance
|
||||
{
|
||||
constructor(
|
||||
appSrc: string | ReadonlyArray<string>,
|
||||
allowedFiles?: ReadonlyArray<string>,
|
||||
);
|
||||
apply: (resolver: any) => void;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'react-dev-utils/FileSizeReporter' {
|
||||
import webpack = require('webpack');
|
||||
|
||||
export interface OpaqueFileSizes {
|
||||
root: string;
|
||||
sizes: Record<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures JS and CSS asset sizes inside the passed `buildFolder`. Save the
|
||||
* result value to compare it after the build.
|
||||
*/
|
||||
export function measureFileSizesBeforeBuild(
|
||||
buildFolder: string,
|
||||
): Promise<OpaqueFileSizes>;
|
||||
|
||||
/**
|
||||
* Prints the JS and CSS asset sizes after the build, and includes a size
|
||||
* comparison with `previousFileSizes` that were captured earlier using
|
||||
* `measureFileSizesBeforeBuild()`. `maxBundleGzipSize` and
|
||||
* `maxChunkGzipSizemay` may optionally be specified to display a warning when
|
||||
* the main bundle or a chunk exceeds the specified size (in bytes).
|
||||
*/
|
||||
export function printFileSizesAfterBuild(
|
||||
webpackStats: webpack.Stats,
|
||||
previousFileSizes: OpaqueFileSizes,
|
||||
buildFolder: string,
|
||||
maxBundleGzipSize?: number,
|
||||
maxChunkGzipSize?: number,
|
||||
): void;
|
||||
}
|
||||
|
||||
declare module 'mini-css-extract-plugin' {
|
||||
import webpack = require('webpack');
|
||||
|
||||
/**
|
||||
* Lightweight CSS extraction webpack plugin.
|
||||
*
|
||||
* This plugin extracts CSS into separate files. It creates a CSS file per JS file which
|
||||
* contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.
|
||||
*
|
||||
* Configuration Detail: https://github.com/webpack-contrib/mini-css-extract-plugin#configuration
|
||||
*/
|
||||
export default class MiniCssExtractPlugin {
|
||||
/**
|
||||
* Webpack loader always used at the end of loaders list (ie. array index zero).
|
||||
*/
|
||||
static loader: string;
|
||||
|
||||
constructor(options?: MiniCssExtractPlugin.PluginOptions);
|
||||
|
||||
/**
|
||||
* Apply the plugin
|
||||
*/
|
||||
apply(compiler: webpack.Compiler): void;
|
||||
}
|
||||
|
||||
namespace MiniCssExtractPlugin {
|
||||
interface PluginOptions {
|
||||
/**
|
||||
* Works like [`output.filename`](https://webpack.js.org/configuration/output/#outputfilename).
|
||||
*/
|
||||
filename?: Required<webpack.Configuration>['output']['filename'];
|
||||
/**
|
||||
* Works like [`output.chunkFilename`](https://webpack.js.org/configuration/output/#outputchunkfilename).
|
||||
*/
|
||||
chunkFilename?: string;
|
||||
/**
|
||||
* For projects where CSS ordering has been mitigated through consistent
|
||||
* use of scoping or naming conventions, the CSS order warnings can be
|
||||
* disabled by setting this flag to true for the plugin.
|
||||
*/
|
||||
ignoreOrder?: boolean;
|
||||
/**
|
||||
* Specify where to insert the link tag.
|
||||
*
|
||||
* A string value specifies a DOM query for a parent element to attach to.
|
||||
*
|
||||
* A function allows to override default behavior for non-entry CSS chunks.
|
||||
* This code will run in the browser alongside your application. It is recommend
|
||||
* to only use ECMA 5 features and syntax. The function won't have access to the
|
||||
* scope of the webpack configuration module.
|
||||
*
|
||||
* @default function() { document.head.appendChild(linkTag); }
|
||||
*/
|
||||
insert?: string | ((linkTag: any) => void);
|
||||
/**
|
||||
* Specify additional html attributes to add to the link tag.
|
||||
*
|
||||
* Note: These are only applied to dynamically loaded css chunks. To modify link
|
||||
* attributes for entry CSS chunks, please use html-webpack-plugin.
|
||||
*/
|
||||
attributes?: Record<string, string>;
|
||||
/**
|
||||
* This option allows loading asynchronous chunks with a custom link type, such as
|
||||
* `<link type="text/css" ...>`.
|
||||
*
|
||||
* `false` disables the link `type` attribute.
|
||||
*
|
||||
* @default 'text/css'
|
||||
*/
|
||||
linkType?: string | false | 'text/css';
|
||||
}
|
||||
interface LoaderOptions {
|
||||
/**
|
||||
* Overrides [`output.publicPath`](https://webpack.js.org/configuration/output/#outputpublicpath).
|
||||
* @default output.publicPath
|
||||
*/
|
||||
publicPath?: string | ((resourcePath: string, context: string) => string);
|
||||
/**
|
||||
* If false, the plugin will extract the CSS but **will not** emit the file
|
||||
* @default true
|
||||
*/
|
||||
emit?: boolean;
|
||||
/**
|
||||
* By default, `mini-css-extract-plugin` generates JS modules that use the ES modules syntax.
|
||||
* There are some cases in which using ES modules is beneficial,
|
||||
* like in the case of module concatenation and tree shaking.
|
||||
* @default true
|
||||
*/
|
||||
esModule?: boolean;
|
||||
modules?: {
|
||||
/**
|
||||
* Enables/disables ES modules named export for locals.
|
||||
*
|
||||
* Names of locals are converted to camelCase. It is not allowed to use
|
||||
* JavaScript reserved words in CSS class names. Options `esModule` and
|
||||
* `modules.namedExport` in css-loader and MiniCssExtractPlugin.loader
|
||||
* must be enabled.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
namedExport?: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'fork-ts-checker-webpack-plugin/lib/ForkTsCheckerWebpackPlugin' {}
|
||||
declare module 'webpack-node-externals' {
|
||||
export default function webpackNodeExternals(
|
||||
options?: webpackNodeExternals.Options,
|
||||
): any;
|
||||
|
||||
namespace webpackNodeExternals {
|
||||
type AllowlistOption = string | RegExp | AllowlistFunctionType;
|
||||
type ImportTypeCallback = (moduleName: string) => string;
|
||||
/** a function that accepts the module name and returns whether it should be included */
|
||||
type AllowlistFunctionType = (moduleName: string) => boolean;
|
||||
interface ModulesFromFileType {
|
||||
exclude?: string | string[];
|
||||
include?: string | string[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* An array for the externals to allow, so they will be included in the bundle.
|
||||
* Can accept exact strings ('module_name'), regex patterns (/^module_name/), or a
|
||||
* function that accepts the module name and returns whether it should be included.
|
||||
* Important - if you have set aliases in your webpack config with the exact
|
||||
* same names as modules in node_modules, you need to allowlist them so Webpack will know
|
||||
* they should be bundled.
|
||||
* @default []
|
||||
*/
|
||||
allowlist?: AllowlistOption[] | AllowlistOption;
|
||||
/**
|
||||
* @default ['.bin']
|
||||
*/
|
||||
binaryDirs?: string[];
|
||||
/**
|
||||
* The method in which unbundled modules will be required in the code. Best to leave as
|
||||
* 'commonjs' for node modules.
|
||||
* @default 'commonjs'
|
||||
*/
|
||||
importType?:
|
||||
| 'var'
|
||||
| 'this'
|
||||
| 'commonjs'
|
||||
| 'amd'
|
||||
| 'umd'
|
||||
| ImportTypeCallback;
|
||||
/**
|
||||
* The folder in which to search for the node modules.
|
||||
* @default 'node_modules'
|
||||
*/
|
||||
modulesDir?: string;
|
||||
/**
|
||||
* Additional folders to look for node modules.
|
||||
*/
|
||||
additionalModuleDirs?: string[];
|
||||
/**
|
||||
* Read the modules from the package.json file instead of the node_modules folder.
|
||||
* @default false
|
||||
*/
|
||||
modulesFromFile?: boolean | ModulesFromFileType;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
includeAbsolutePaths?: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,11 @@ const substituteMe = '${MY_SUBSTITUTION}';
|
||||
const mySubstitution = 'fooSubstitution';
|
||||
|
||||
const env = jest.fn(async (name: string) => {
|
||||
return ({
|
||||
SECRET: 'my-secret',
|
||||
} as { [name: string]: string })[name];
|
||||
return (
|
||||
{
|
||||
SECRET: 'my-secret',
|
||||
} as { [name: string]: string }
|
||||
)[name];
|
||||
});
|
||||
|
||||
const substitute: TransformFunc = async value => {
|
||||
@@ -44,14 +46,16 @@ const substitute: TransformFunc = async value => {
|
||||
};
|
||||
|
||||
const readFile = jest.fn(async (path: string) => {
|
||||
const content = ({
|
||||
[resolvePath(root, 'my-secret')]: 'secret',
|
||||
[resolvePath(root, 'my-data.json')]: '{"a":{"b":{"c":42}}}',
|
||||
[resolvePath(root, 'my-data.yaml')]: 'some:\n yaml:\n key: 7',
|
||||
[resolvePath(root, 'my-data.yml')]: 'different: { key: hello }',
|
||||
[resolvePath(root, 'invalid.yaml')]: 'foo: [}',
|
||||
[resolvePath(root, `${mySubstitution}/my-data.json`)]: '{"foo":"bar"}',
|
||||
} as { [key: string]: string })[path];
|
||||
const content = (
|
||||
{
|
||||
[resolvePath(root, 'my-secret')]: 'secret',
|
||||
[resolvePath(root, 'my-data.json')]: '{"a":{"b":{"c":42}}}',
|
||||
[resolvePath(root, 'my-data.yaml')]: 'some:\n yaml:\n key: 7',
|
||||
[resolvePath(root, 'my-data.yml')]: 'different: { key: hello }',
|
||||
[resolvePath(root, 'invalid.yaml')]: 'foo: [}',
|
||||
[resolvePath(root, `${mySubstitution}/my-data.json`)]: '{"foo":"bar"}',
|
||||
} as { [key: string]: string }
|
||||
)[path];
|
||||
|
||||
if (!content) {
|
||||
throw new Error('File not found!');
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
import { createSubstitutionTransform } from './substitution';
|
||||
|
||||
const env = jest.fn(async (name: string) => {
|
||||
return ({
|
||||
SECRET: 'my-secret',
|
||||
TOKEN: 'my-token',
|
||||
} as { [name: string]: string })[name];
|
||||
return (
|
||||
{
|
||||
SECRET: 'my-secret',
|
||||
TOKEN: 'my-token',
|
||||
} as { [name: string]: string }
|
||||
)[name];
|
||||
});
|
||||
|
||||
const substituteTransform = createSubstitutionTransform(env);
|
||||
|
||||
@@ -64,9 +64,9 @@ function expectValidValues(config: ConfigReader) {
|
||||
strings: ['string1', 'string2'],
|
||||
});
|
||||
expect(config.getConfig('nested').getString('string')).toBe('string');
|
||||
expect(
|
||||
config.getOptionalConfig('nested')!.getStringArray('strings'),
|
||||
).toEqual(['string1', 'string2']);
|
||||
expect(config.getOptionalConfig('nested')!.getStringArray('strings')).toEqual(
|
||||
['string1', 'string2'],
|
||||
);
|
||||
expect(config.getOptional('missing')).toBe(undefined);
|
||||
expect(config.getOptionalConfig('missing')).toBe(undefined);
|
||||
expect(config.getOptionalConfigArray('missing')).toBe(undefined);
|
||||
@@ -234,9 +234,9 @@ describe('ConfigReader', () => {
|
||||
expect(withLogCollector(() => config.getOptional('a'))).toMatchObject({
|
||||
warn: [],
|
||||
});
|
||||
expect(
|
||||
withLogCollector(() => config.getOptionalString('a')),
|
||||
).toMatchObject({ warn: [] });
|
||||
expect(withLogCollector(() => config.getOptionalString('a'))).toMatchObject(
|
||||
{ warn: [] },
|
||||
);
|
||||
expect(
|
||||
withLogCollector(() => config.getOptionalConfigArray('b')),
|
||||
).toMatchObject({ warn: [] });
|
||||
|
||||
@@ -68,9 +68,7 @@ export class AlertApiForwarder implements AlertApi {
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type ApiFactoryHolder = {
|
||||
get<T>(
|
||||
api: ApiRef<T>,
|
||||
):
|
||||
get<T>(api: ApiRef<T>):
|
||||
| ApiFactory<
|
||||
T,
|
||||
T,
|
||||
@@ -86,9 +84,7 @@ export type ApiFactoryHolder = {
|
||||
// @public
|
||||
export class ApiFactoryRegistry implements ApiFactoryHolder {
|
||||
// (undocumented)
|
||||
get<T>(
|
||||
api: ApiRef<T>,
|
||||
):
|
||||
get<T>(api: ApiRef<T>):
|
||||
| ApiFactory<
|
||||
T,
|
||||
T,
|
||||
@@ -105,7 +101,7 @@ export class ApiFactoryRegistry implements ApiFactoryHolder {
|
||||
Impl extends Api,
|
||||
Deps extends {
|
||||
[name in string]: unknown;
|
||||
}
|
||||
},
|
||||
>(scope: ApiFactoryScope, factory: ApiFactory<Api, Impl, Deps>): boolean;
|
||||
}
|
||||
|
||||
@@ -211,7 +207,7 @@ export type AppOptions = {
|
||||
export type AppRouteBinder = <
|
||||
ExternalRoutes extends {
|
||||
[name: string]: ExternalRouteRef;
|
||||
}
|
||||
},
|
||||
>(
|
||||
externalRoutes: ExternalRoutes,
|
||||
targetRoutes: PartialKeys<
|
||||
@@ -469,7 +465,8 @@ export class OAuth2
|
||||
OpenIdConnectApi,
|
||||
ProfileInfoApi,
|
||||
BackstageIdentityApi,
|
||||
SessionApi {
|
||||
SessionApi
|
||||
{
|
||||
// Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts
|
||||
constructor(options: Options);
|
||||
// Warning: (ae-forgotten-export) The symbol "CreateOptions" needs to be exported by the entry point index.d.ts
|
||||
@@ -561,7 +558,8 @@ export class OneLoginAuth {
|
||||
//
|
||||
// @public (undocumented)
|
||||
export class SamlAuth
|
||||
implements ProfileInfoApi, BackstageIdentityApi, SessionApi {
|
||||
implements ProfileInfoApi, BackstageIdentityApi, SessionApi
|
||||
{
|
||||
// Warning: (ae-forgotten-export) The symbol "SamlSession" needs to be exported by the entry point index.d.ts
|
||||
constructor(sessionManager: SessionManager<SamlSession>);
|
||||
// Warning: (ae-forgotten-export) The symbol "AuthApiCreateOptions" needs to be exported by the entry point index.d.ts
|
||||
|
||||
+1
-2
@@ -194,8 +194,7 @@ describe('FeatureFlags', () => {
|
||||
it('throws an error if length is greater than 150 characters', () => {
|
||||
expect(() =>
|
||||
featureFlags.registerFlag({
|
||||
name:
|
||||
'loremipsumdolorsitametconsecteturadipiscingelitnuncvitaeportaexaullamcorperturpismaurisutmattisnequemorbisediaculisauguevivamuspulvinarcursuseratblandithendreritquisqueuttinciduntmagnavestibulumblanditaugueat',
|
||||
name: 'loremipsumdolorsitametconsecteturadipiscingelitnuncvitaeportaexaullamcorperturpismaurisutmattisnequemorbisediaculisauguevivamuspulvinarcursuseratblandithendreritquisqueuttinciduntmagnavestibulumblanditaugueat',
|
||||
pluginId: 'plugin-three',
|
||||
}),
|
||||
).toThrow(/not exceed 150 characters/i);
|
||||
|
||||
@@ -65,7 +65,8 @@ class OAuth2
|
||||
OpenIdConnectApi,
|
||||
ProfileInfoApi,
|
||||
BackstageIdentityApi,
|
||||
SessionApi {
|
||||
SessionApi
|
||||
{
|
||||
static create({
|
||||
discoveryApi,
|
||||
environment = 'development',
|
||||
|
||||
@@ -185,9 +185,10 @@ describe('ApiProvider', () => {
|
||||
});
|
||||
|
||||
describe('v1 consumer', () => {
|
||||
const ApiContext = getGlobalSingleton<
|
||||
Context<VersionedValue<{ 1: ApiHolder }>>
|
||||
>('api-context');
|
||||
const ApiContext =
|
||||
getGlobalSingleton<Context<VersionedValue<{ 1: ApiHolder }>>>(
|
||||
'api-context',
|
||||
);
|
||||
|
||||
function useMockApiV1<T>(apiRef: ApiRef<T>): T {
|
||||
const impl = useContext(ApiContext)?.atVersion(1)?.get(apiRef);
|
||||
|
||||
@@ -107,6 +107,20 @@ export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app base path from the configured app baseUrl.
|
||||
*
|
||||
* The returned path does not have a trailing slash.
|
||||
*/
|
||||
function getBasePath(configApi: Config) {
|
||||
let { pathname } = new URL(
|
||||
configApi.getOptionalString('app.baseUrl') ?? '/',
|
||||
'http://dummy.dev', // baseUrl can be specified as just a path
|
||||
);
|
||||
pathname = pathname.replace(/\/*$/, '');
|
||||
return pathname;
|
||||
}
|
||||
|
||||
type FullAppOptions = {
|
||||
apis: Iterable<AnyApiFactory>;
|
||||
icons: NonNullable<AppOptions['icons']>;
|
||||
@@ -216,37 +230,33 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
[],
|
||||
);
|
||||
|
||||
const {
|
||||
routePaths,
|
||||
routeParents,
|
||||
routeObjects,
|
||||
featureFlags,
|
||||
} = useMemo(() => {
|
||||
const result = traverseElementTree({
|
||||
root: children,
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
collectors: {
|
||||
routePaths: routePathCollector,
|
||||
routeParents: routeParentCollector,
|
||||
routeObjects: routeObjectCollector,
|
||||
collectedPlugins: pluginCollector,
|
||||
featureFlags: featureFlagCollector,
|
||||
},
|
||||
});
|
||||
const { routePaths, routeParents, routeObjects, featureFlags } =
|
||||
useMemo(() => {
|
||||
const result = traverseElementTree({
|
||||
root: children,
|
||||
discoverers: [childDiscoverer, routeElementDiscoverer],
|
||||
collectors: {
|
||||
routePaths: routePathCollector,
|
||||
routeParents: routeParentCollector,
|
||||
routeObjects: routeObjectCollector,
|
||||
collectedPlugins: pluginCollector,
|
||||
featureFlags: featureFlagCollector,
|
||||
},
|
||||
});
|
||||
|
||||
validateRoutes(result.routePaths, result.routeParents);
|
||||
validateRoutes(result.routePaths, result.routeParents);
|
||||
|
||||
// TODO(Rugvip): Restructure the public API so that we can get an immediate view of
|
||||
// the app, rather than having to wait for the provider to render.
|
||||
// For now we need to push the additional plugins we find during
|
||||
// collection and then make sure we initialize things afterwards.
|
||||
result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));
|
||||
this.verifyPlugins(this.plugins);
|
||||
// TODO(Rugvip): Restructure the public API so that we can get an immediate view of
|
||||
// the app, rather than having to wait for the provider to render.
|
||||
// For now we need to push the additional plugins we find during
|
||||
// collection and then make sure we initialize things afterwards.
|
||||
result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));
|
||||
this.verifyPlugins(this.plugins);
|
||||
|
||||
// Initialize APIs once all plugins are available
|
||||
this.getApiHolder();
|
||||
return result;
|
||||
}, [children]);
|
||||
// Initialize APIs once all plugins are available
|
||||
this.getApiHolder();
|
||||
return result;
|
||||
}, [children]);
|
||||
|
||||
const loadedConfig = useConfigLoader(
|
||||
this.configLoader,
|
||||
@@ -302,6 +312,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
routeParents={routeParents}
|
||||
routeObjects={routeObjects}
|
||||
routeBindings={generateBoundRoutes(this.bindRoutes)}
|
||||
basePath={getBasePath(loadedConfig.api)}
|
||||
>
|
||||
{children}
|
||||
</RoutingProvider>
|
||||
@@ -314,10 +325,8 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
}
|
||||
|
||||
getRouter(): ComponentType<{}> {
|
||||
const {
|
||||
Router: RouterComponent,
|
||||
SignInPage: SignInPageComponent,
|
||||
} = this.components;
|
||||
const { Router: RouterComponent, SignInPage: SignInPageComponent } =
|
||||
this.components;
|
||||
|
||||
// This wraps the sign-in page and waits for sign-in to be completed before rendering the app
|
||||
const SignInPageWrapper = ({
|
||||
@@ -339,14 +348,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
|
||||
const AppRouter = ({ children }: PropsWithChildren<{}>) => {
|
||||
const configApi = useApi(configApiRef);
|
||||
|
||||
let { pathname } = new URL(
|
||||
configApi.getOptionalString('app.baseUrl') ?? '/',
|
||||
'http://dummy.dev', // baseUrl can be specified as just a path
|
||||
);
|
||||
if (pathname.endsWith('/')) {
|
||||
pathname = pathname.replace(/\/$/, '');
|
||||
}
|
||||
const mountPath = `${getBasePath(configApi)}/*`;
|
||||
|
||||
// If the app hasn't configured a sign-in page, we just continue as guest.
|
||||
if (!SignInPageComponent) {
|
||||
@@ -361,7 +363,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
return (
|
||||
<RouterComponent>
|
||||
<Routes>
|
||||
<Route path={`${pathname}/*`} element={<>{children}</>} />
|
||||
<Route path={mountPath} element={<>{children}</>} />
|
||||
</Routes>
|
||||
</RouterComponent>
|
||||
);
|
||||
@@ -371,7 +373,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
<RouterComponent>
|
||||
<SignInPageWrapper component={SignInPageComponent}>
|
||||
<Routes>
|
||||
<Route path={`${pathname}/*`} element={<>{children}</>} />
|
||||
<Route path={mountPath} element={<>{children}</>} />
|
||||
</Routes>
|
||||
</SignInPageWrapper>
|
||||
</RouterComponent>
|
||||
|
||||
@@ -22,9 +22,10 @@ import { AppContext as AppContextV1 } from './types';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
|
||||
describe('v1 consumer', () => {
|
||||
const AppContext = getGlobalSingleton<
|
||||
Context<VersionedValue<{ 1: AppContextV1 }>>
|
||||
>('app-context');
|
||||
const AppContext =
|
||||
getGlobalSingleton<Context<VersionedValue<{ 1: AppContextV1 }>>>(
|
||||
'app-context',
|
||||
);
|
||||
|
||||
function useMockAppV1(): AppContextV1 {
|
||||
const impl = useContext(AppContext)?.atVersion(1);
|
||||
|
||||
@@ -60,7 +60,7 @@ export const defaultConfigLoader: AppConfigLoader = async (
|
||||
if (!Array.isArray(appConfig)) {
|
||||
throw new Error('Static configuration has invalid format');
|
||||
}
|
||||
const configs = (appConfig.slice() as unknown) as AppConfig[];
|
||||
const configs = appConfig.slice() as unknown as AppConfig[];
|
||||
|
||||
// Avoiding this string also being replaced at runtime
|
||||
if (
|
||||
|
||||
@@ -105,14 +105,14 @@ type KeysWithType<Obj extends { [key in string]: any }, Type> = {
|
||||
*/
|
||||
type PartialKeys<
|
||||
Map extends { [name in string]: any },
|
||||
Keys extends keyof Map
|
||||
Keys extends keyof Map,
|
||||
> = Partial<Pick<Map, Keys>> & Required<Omit<Map, Keys>>;
|
||||
|
||||
/**
|
||||
* Creates a map of target routes with matching parameters based on a map of external routes.
|
||||
*/
|
||||
type TargetRouteMap<
|
||||
ExternalRoutes extends { [name: string]: ExternalRouteRef }
|
||||
ExternalRoutes extends { [name: string]: ExternalRouteRef },
|
||||
> = {
|
||||
[name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<
|
||||
infer Params,
|
||||
@@ -123,7 +123,7 @@ type TargetRouteMap<
|
||||
};
|
||||
|
||||
export type AppRouteBinder = <
|
||||
ExternalRoutes extends { [name: string]: ExternalRouteRef }
|
||||
ExternalRoutes extends { [name: string]: ExternalRouteRef },
|
||||
>(
|
||||
externalRoutes: ExternalRoutes,
|
||||
targetRoutes: PartialKeys<
|
||||
|
||||
@@ -28,7 +28,7 @@ const ERROR_MESSAGE = 'Import this from @backstage/core-plugin-api';
|
||||
|
||||
/** @deprecated Import from @backstage/core-plugin-api instead */
|
||||
export function createRoutableExtension<
|
||||
T extends (props: any) => JSX.Element | null
|
||||
T extends (props: any) => JSX.Element | null,
|
||||
>(_options: {
|
||||
component: () => Promise<T>;
|
||||
mountPoint: RouteRef;
|
||||
@@ -38,14 +38,14 @@ export function createRoutableExtension<
|
||||
|
||||
/** @deprecated Import from @backstage/core-plugin-api instead */
|
||||
export function createComponentExtension<
|
||||
T extends (props: any) => JSX.Element | null
|
||||
T extends (props: any) => JSX.Element | null,
|
||||
>(_options: { component: ComponentLoader<T> }): Extension<T> {
|
||||
throw new Error(ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
/** @deprecated Import from @backstage/core-plugin-api instead */
|
||||
export function createReactExtension<
|
||||
T extends (props: any) => JSX.Element | null
|
||||
T extends (props: any) => JSX.Element | null,
|
||||
>(_options: {
|
||||
component: ComponentLoader<T>;
|
||||
data?: Record<string, unknown>;
|
||||
|
||||
@@ -126,8 +126,7 @@ describe('DefaultAuthConnector', () => {
|
||||
|
||||
expect(popupSpy).toBeCalledTimes(1);
|
||||
expect(popupSpy.mock.calls[0][0]).toMatchObject({
|
||||
url:
|
||||
'http://my-host/api/auth/my-provider/start?scope=a%20b&env=production',
|
||||
url: 'http://my-host/api/auth/my-provider/start?scope=a%20b&env=production',
|
||||
});
|
||||
|
||||
await expect(sessionPromise).resolves.toEqual({
|
||||
@@ -175,8 +174,7 @@ describe('DefaultAuthConnector', () => {
|
||||
|
||||
expect(popupSpy).toBeCalledTimes(1);
|
||||
expect(popupSpy.mock.calls[0][0]).toMatchObject({
|
||||
url:
|
||||
'http://my-host/api/auth/my-provider/start?scope=-ab-&env=production',
|
||||
url: 'http://my-host/api/auth/my-provider/start?scope=-ab-&env=production',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,7 +61,8 @@ function defaultJoinScopes(scopes: Set<string>) {
|
||||
* via the OAuthRequestApi.
|
||||
*/
|
||||
export class DefaultAuthConnector<AuthSession>
|
||||
implements AuthConnector<AuthSession> {
|
||||
implements AuthConnector<AuthSession>
|
||||
{
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly environment: string;
|
||||
private readonly provider: AuthProvider & { id: string };
|
||||
|
||||
@@ -30,8 +30,7 @@ describe('showLoginPopup', () => {
|
||||
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
|
||||
|
||||
const payloadPromise = showLoginPopup({
|
||||
url:
|
||||
'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb',
|
||||
url: 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb',
|
||||
name: 'test-popup',
|
||||
origin: 'my-origin',
|
||||
});
|
||||
|
||||
@@ -29,7 +29,8 @@ import ObservableImpl from 'zen-observable';
|
||||
* See http://reactivex.io/documentation/subject.html
|
||||
*/
|
||||
export class PublishSubject<T>
|
||||
implements Observable<T>, ZenObservable.SubscriptionObserver<T> {
|
||||
implements Observable<T>, ZenObservable.SubscriptionObserver<T>
|
||||
{
|
||||
private isClosed = false;
|
||||
private terminatingError?: Error;
|
||||
|
||||
@@ -121,7 +122,8 @@ export class PublishSubject<T>
|
||||
* See http://reactivex.io/documentation/subject.html
|
||||
*/
|
||||
export class BehaviorSubject<T>
|
||||
implements Observable<T>, ZenObservable.SubscriptionObserver<T> {
|
||||
implements Observable<T>, ZenObservable.SubscriptionObserver<T>
|
||||
{
|
||||
private isClosed = false;
|
||||
private currentValue: T;
|
||||
private terminatingError?: Error;
|
||||
|
||||
@@ -29,7 +29,7 @@ export type VersionedValue<Versions extends { [version: number]: any }> = {
|
||||
* Creates a container for a map of versioned values that implements VersionedValue.
|
||||
*/
|
||||
export function createVersionedValueMap<
|
||||
Versions extends { [version: number]: any }
|
||||
Versions extends { [version: number]: any },
|
||||
>(versions: Versions): VersionedValue<Versions> {
|
||||
Object.freeze(versions);
|
||||
return {
|
||||
|
||||
@@ -37,11 +37,11 @@ function makeRouteRenderer(node: ReactNode) {
|
||||
<Wrapper>
|
||||
<AppContextProvider
|
||||
appContext={
|
||||
({
|
||||
{
|
||||
getComponents: () => ({
|
||||
NotFoundErrorPage: () => <>Not Found</>,
|
||||
}),
|
||||
} as unknown) as AppContext
|
||||
} as unknown as AppContext
|
||||
}
|
||||
>
|
||||
<MemoryRouter initialEntries={[path]} children={node} />
|
||||
|
||||
@@ -65,7 +65,7 @@ const externalRef4 = createExternalRouteRef({
|
||||
|
||||
describe('RouteResolver', () => {
|
||||
it('should not resolve anything with an empty resolver', () => {
|
||||
const r = new RouteResolver(new Map(), new Map(), [], new Map());
|
||||
const r = new RouteResolver(new Map(), new Map(), [], new Map(), '');
|
||||
|
||||
expect(r.resolve(ref1, '/')?.()).toBe(undefined);
|
||||
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined);
|
||||
@@ -85,6 +85,7 @@ describe('RouteResolver', () => {
|
||||
new Map(),
|
||||
[{ routeRefs: new Set([ref1]), path: '/my-route', ...rest }],
|
||||
new Map(),
|
||||
'',
|
||||
);
|
||||
|
||||
expect(r.resolve(ref1, '/')?.()).toBe('/my-route');
|
||||
@@ -99,6 +100,29 @@ describe('RouteResolver', () => {
|
||||
expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should resolve an absolute route and an app base path', () => {
|
||||
const r = new RouteResolver(
|
||||
new Map([[ref1, '/my-route']]),
|
||||
new Map(),
|
||||
[{ routeRefs: new Set([ref1]), path: '/my-route', ...rest }],
|
||||
new Map(),
|
||||
'/base',
|
||||
);
|
||||
|
||||
expect(r.resolve(ref1, '/')?.()).toBe('/base/my-route');
|
||||
expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined);
|
||||
expect(r.resolve(subRef1, '/')?.()).toBe('/base/my-route/foo');
|
||||
expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe(
|
||||
'/base/my-route/foo/2a',
|
||||
);
|
||||
expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined);
|
||||
expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined);
|
||||
expect(r.resolve(externalRef1, '/')?.()).toBe(undefined);
|
||||
expect(r.resolve(externalRef2, '/')?.()).toBe(undefined);
|
||||
expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined);
|
||||
expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should resolve an absolute route with a param and with a parent', () => {
|
||||
const r = new RouteResolver(
|
||||
new Map<RouteRef, string>([
|
||||
@@ -122,6 +146,7 @@ describe('RouteResolver', () => {
|
||||
[externalRef3, ref2],
|
||||
[externalRef4, subRef3],
|
||||
]),
|
||||
'',
|
||||
);
|
||||
|
||||
expect(r.resolve(ref1, '/')?.()).toBe('/my-route');
|
||||
@@ -179,6 +204,7 @@ describe('RouteResolver', () => {
|
||||
},
|
||||
],
|
||||
new Map<ExternalRouteRef, RouteRef | SubRouteRef>(),
|
||||
'',
|
||||
);
|
||||
|
||||
expect(r.resolve(ref2, '/')?.({ x: 'x' })).toBe('/root/x');
|
||||
@@ -232,6 +258,7 @@ describe('RouteResolver', () => {
|
||||
[externalRef3, ref2],
|
||||
[externalRef4, subRef3],
|
||||
]),
|
||||
'',
|
||||
);
|
||||
|
||||
const l = '/my-grandparent/my-y/my-parent/my-x';
|
||||
|
||||
@@ -187,6 +187,7 @@ export class RouteResolver {
|
||||
ExternalRouteRef,
|
||||
RouteRef | SubRouteRef
|
||||
>,
|
||||
private readonly appBasePath: string, // base path without a trailing slash
|
||||
) {}
|
||||
|
||||
resolve<Params extends AnyParams>(
|
||||
@@ -209,13 +210,15 @@ export class RouteResolver {
|
||||
// Next we figure out the base path, which is the combination of the common parent path
|
||||
// between our current location and our target location, as well as the additional path
|
||||
// that is the difference between the parent path and the base of our target location.
|
||||
const basePath = resolveBasePath(
|
||||
targetRef,
|
||||
sourceLocation,
|
||||
this.routePaths,
|
||||
this.routeParents,
|
||||
this.routeObjects,
|
||||
);
|
||||
const basePath =
|
||||
this.appBasePath +
|
||||
resolveBasePath(
|
||||
targetRef,
|
||||
sourceLocation,
|
||||
this.routePaths,
|
||||
this.routeParents,
|
||||
this.routeObjects,
|
||||
);
|
||||
|
||||
const routeFunc: RouteFunc<Params> = (...[params]) => {
|
||||
return basePath + generatePath(targetPath, params);
|
||||
|
||||
@@ -152,6 +152,7 @@ function withRoutingProvider(
|
||||
routeParents={routeParents}
|
||||
routeObjects={routeObjects}
|
||||
routeBindings={new Map(routeBindings)}
|
||||
basePath=""
|
||||
>
|
||||
{root}
|
||||
</RoutingProvider>
|
||||
@@ -330,9 +331,10 @@ describe('discovery', () => {
|
||||
});
|
||||
|
||||
describe('v1 consumer', () => {
|
||||
const RoutingContext = getGlobalSingleton<
|
||||
Context<VersionedValue<{ 1: RouteResolver }>>
|
||||
>('routing-context');
|
||||
const RoutingContext =
|
||||
getGlobalSingleton<Context<VersionedValue<{ 1: RouteResolver }>>>(
|
||||
'routing-context',
|
||||
);
|
||||
|
||||
function useMockRouteRefV1(
|
||||
routeRef: AnyRouteRef,
|
||||
@@ -367,6 +369,7 @@ describe('v1 consumer', () => {
|
||||
routeParents={new Map()}
|
||||
routeObjects={[]}
|
||||
routeBindings={new Map()}
|
||||
basePath="/base"
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
@@ -375,8 +378,8 @@ describe('v1 consumer', () => {
|
||||
|
||||
expect(renderedHook.result.current).toBe(undefined);
|
||||
renderedHook.rerender({ routeRef: routeRef2 });
|
||||
expect(renderedHook.result.current?.()).toBe('/foo');
|
||||
expect(renderedHook.result.current?.()).toBe('/base/foo');
|
||||
renderedHook.rerender({ routeRef: routeRef3 });
|
||||
expect(renderedHook.result.current?.({ x: 'my-x' })).toBe('/bar/my-x');
|
||||
expect(renderedHook.result.current?.({ x: 'my-x' })).toBe('/base/bar/my-x');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,6 +38,7 @@ type ProviderProps = {
|
||||
routeParents: Map<RouteRef, RouteRef | undefined>;
|
||||
routeObjects: BackstageRouteObject[];
|
||||
routeBindings: Map<ExternalRouteRef, RouteRef | SubRouteRef>;
|
||||
basePath?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
@@ -46,6 +47,7 @@ export const RoutingProvider = ({
|
||||
routeParents,
|
||||
routeObjects,
|
||||
routeBindings,
|
||||
basePath = '',
|
||||
children,
|
||||
}: ProviderProps) => {
|
||||
const resolver = new RouteResolver(
|
||||
@@ -53,6 +55,7 @@ export const RoutingProvider = ({
|
||||
routeParents,
|
||||
routeObjects,
|
||||
routeBindings,
|
||||
basePath,
|
||||
);
|
||||
|
||||
const versionedValue = createVersionedValueMap({ 1: resolver });
|
||||
|
||||
@@ -75,7 +75,7 @@ export function isSubRouteRef<Params extends AnyParams>(
|
||||
|
||||
export function isExternalRouteRef<
|
||||
Params extends AnyParams,
|
||||
Optional extends boolean
|
||||
Optional extends boolean,
|
||||
>(
|
||||
routeRef:
|
||||
| RouteRef<Params>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@backstage/core-plugin-api": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-table/core": "^3.1.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -50,7 +51,6 @@
|
||||
"dagre": "^0.8.5",
|
||||
"immer": "^9.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"@material-table/core": "^3.1.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"qs": "^6.9.4",
|
||||
@@ -84,6 +84,7 @@
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@types/react-syntax-highlighter": "^13.5.2",
|
||||
"@types/zen-observable": "^0.8.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -38,10 +38,8 @@ export function DefaultNode({ node: { id } }: RenderNodeProps) {
|
||||
React.useLayoutEffect(() => {
|
||||
// set the width to the length of the ID
|
||||
if (idRef.current) {
|
||||
let {
|
||||
height: renderedHeight,
|
||||
width: renderedWidth,
|
||||
} = idRef.current.getBBox();
|
||||
let { height: renderedHeight, width: renderedWidth } =
|
||||
idRef.current.getBBox();
|
||||
renderedHeight = Math.round(renderedHeight);
|
||||
renderedWidth = Math.round(renderedWidth);
|
||||
|
||||
|
||||
@@ -132,10 +132,8 @@ export function DependencyGraph({
|
||||
|
||||
container.call(zoom);
|
||||
|
||||
const {
|
||||
width: newContainerWidth,
|
||||
height: newContainerHeight,
|
||||
} = node.getBoundingClientRect();
|
||||
const { width: newContainerWidth, height: newContainerHeight } =
|
||||
node.getBoundingClientRect();
|
||||
if (containerWidth !== newContainerWidth) {
|
||||
setContainerWidth(newContainerWidth);
|
||||
}
|
||||
|
||||
@@ -71,10 +71,8 @@ export function Edge({
|
||||
React.useLayoutEffect(() => {
|
||||
// set the label width to the actual rendered width to properly layout graph
|
||||
if (labelRef.current) {
|
||||
let {
|
||||
height: renderedHeight,
|
||||
width: renderedWidth,
|
||||
} = labelRef.current.getBBox();
|
||||
let { height: renderedHeight, width: renderedWidth } =
|
||||
labelRef.current.getBBox();
|
||||
renderedHeight = Math.round(renderedHeight);
|
||||
renderedWidth = Math.round(renderedWidth);
|
||||
|
||||
|
||||
@@ -46,10 +46,8 @@ export function Node({
|
||||
React.useLayoutEffect(() => {
|
||||
// set the node width to the actual rendered width to properly layout graph
|
||||
if (nodeRef.current) {
|
||||
let {
|
||||
height: renderedHeight,
|
||||
width: renderedWidth,
|
||||
} = nodeRef.current.getBBox();
|
||||
let { height: renderedHeight, width: renderedWidth } =
|
||||
nodeRef.current.getBBox();
|
||||
renderedHeight = Math.round(renderedHeight);
|
||||
renderedWidth = Math.round(renderedWidth);
|
||||
|
||||
|
||||
@@ -38,9 +38,10 @@ function useCalloutStates(): {
|
||||
return { states, setState };
|
||||
}
|
||||
|
||||
function useCalloutHasBeenSeen(
|
||||
featureId: string,
|
||||
): { seen: boolean | undefined; markSeen: () => void } {
|
||||
function useCalloutHasBeenSeen(featureId: string): {
|
||||
seen: boolean | undefined;
|
||||
markSeen: () => void;
|
||||
} {
|
||||
const { states, setState } = useCalloutStates();
|
||||
|
||||
const markSeen = useCallback(() => {
|
||||
@@ -50,9 +51,10 @@ function useCalloutHasBeenSeen(
|
||||
return { seen: states[featureId] === true, markSeen };
|
||||
}
|
||||
|
||||
export function useShowCallout(
|
||||
featureId: string,
|
||||
): { show: boolean; hide: () => void } {
|
||||
export function useShowCallout(featureId: string): {
|
||||
show: boolean;
|
||||
hide: () => void;
|
||||
} {
|
||||
const { seen, markSeen } = useCalloutHasBeenSeen(featureId);
|
||||
return { show: seen === false, hide: markSeen };
|
||||
}
|
||||
|
||||
@@ -96,14 +96,14 @@ interface StyleProps extends WithStyles {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const MetadataList = withStyles(
|
||||
listStyles,
|
||||
)(({ classes, children }: StyleProps) => (
|
||||
<ul className={classes.root}>{children}</ul>
|
||||
));
|
||||
export const MetadataList = withStyles(listStyles)(
|
||||
({ classes, children }: StyleProps) => (
|
||||
<ul className={classes.root}>{children}</ul>
|
||||
),
|
||||
);
|
||||
|
||||
export const MetadataListItem = withStyles(
|
||||
listItemStyles,
|
||||
)(({ classes, children }: StyleProps) => (
|
||||
<li className={classes.root}>{children}</li>
|
||||
));
|
||||
export const MetadataListItem = withStyles(listItemStyles)(
|
||||
({ classes, children }: StyleProps) => (
|
||||
<li className={classes.root}>{children}</li>
|
||||
),
|
||||
);
|
||||
|
||||
+10
-10
@@ -44,16 +44,16 @@ interface StyleProps extends WithStyles {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
// Sub Components
|
||||
const StyledList = withStyles(
|
||||
listStyle,
|
||||
)(({ classes, children }: StyleProps) => (
|
||||
<MetadataList classes={classes}>{children}</MetadataList>
|
||||
));
|
||||
const StyledNestedList = withStyles(
|
||||
nestedListStyle,
|
||||
)(({ classes, children }: StyleProps) => (
|
||||
<MetadataList classes={classes}>{children}</MetadataList>
|
||||
));
|
||||
const StyledList = withStyles(listStyle)(
|
||||
({ classes, children }: StyleProps) => (
|
||||
<MetadataList classes={classes}>{children}</MetadataList>
|
||||
),
|
||||
);
|
||||
const StyledNestedList = withStyles(nestedListStyle)(
|
||||
({ classes, children }: StyleProps) => (
|
||||
<MetadataList classes={classes}>{children}</MetadataList>
|
||||
),
|
||||
);
|
||||
|
||||
function renderList(list: Array<any>, nested?: boolean) {
|
||||
const values = list.map((item: any, index: number) => (
|
||||
|
||||
@@ -20,9 +20,11 @@ import { Content } from '../../layout/Content';
|
||||
import { HeaderTabs } from '../../layout/HeaderTabs';
|
||||
import { SubRoute } from './types';
|
||||
|
||||
export function useSelectedSubRoute(
|
||||
subRoutes: SubRoute[],
|
||||
): { index: number; route: SubRoute; element: JSX.Element } {
|
||||
export function useSelectedSubRoute(subRoutes: SubRoute[]): {
|
||||
index: number;
|
||||
route: SubRoute;
|
||||
element: JSX.Element;
|
||||
} {
|
||||
const params = useParams();
|
||||
|
||||
const routes = subRoutes.map(({ path, children }) => ({
|
||||
|
||||
@@ -285,9 +285,10 @@ export function Table<T extends object = {}>({
|
||||
const [filtersOpen, setFiltersOpen] = useState(
|
||||
calculatedInitialState.filtersOpen,
|
||||
);
|
||||
const toggleFilters = useCallback(() => setFiltersOpen(v => !v), [
|
||||
setFiltersOpen,
|
||||
]);
|
||||
const toggleFilters = useCallback(
|
||||
() => setFiltersOpen(v => !v),
|
||||
[setFiltersOpen],
|
||||
);
|
||||
const [selectedFiltersLength, setSelectedFiltersLength] = useState(0);
|
||||
const [tableData, setTableData] = useState(data as any[]);
|
||||
const [selectedFilters, setSelectedFilters] = useState(
|
||||
|
||||
@@ -42,8 +42,7 @@ const DEFAULT_SUPPORT_CONFIG: SupportConfig = {
|
||||
{
|
||||
// TODO: Update to dedicated support page on backstage.io/docs
|
||||
title: 'Add `app.support` config key',
|
||||
url:
|
||||
'https://github.com/andrewthauer/backstage/blob/master/app-config.yaml',
|
||||
url: 'https://github.com/andrewthauer/backstage/blob/master/app-config.yaml',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -42,18 +42,14 @@ describe('<ErrorBoundary/>', () => {
|
||||
it('should render error boundary with and without error', async () => {
|
||||
const { error } = await withLogCollector(['error'], async () => {
|
||||
const apis = ApiRegistry.with(errorApiRef, new MockErrorApi());
|
||||
const {
|
||||
rerender,
|
||||
queryByRole,
|
||||
getByRole,
|
||||
getByText,
|
||||
} = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ErrorBoundary>
|
||||
<Bomb />
|
||||
</ErrorBoundary>
|
||||
</ApiProvider>,
|
||||
);
|
||||
const { rerender, queryByRole, getByRole, getByText } =
|
||||
await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ErrorBoundary>
|
||||
<Bomb />
|
||||
</ErrorBoundary>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(queryByRole('alert')).not.toBeInTheDocument();
|
||||
expect(getByText(/working component/i)).toBeInTheDocument();
|
||||
|
||||
@@ -133,10 +133,8 @@ export const SidebarIntro = () => {
|
||||
starredItemsDismissed: false,
|
||||
recentlyViewedItemsDismissed: false,
|
||||
};
|
||||
const [
|
||||
dismissedIntro,
|
||||
setDismissedIntro,
|
||||
] = useLocalStorage<SidebarIntroLocalStorage>(SIDEBAR_INTRO_LOCAL_STORAGE);
|
||||
const [dismissedIntro, setDismissedIntro] =
|
||||
useLocalStorage<SidebarIntroLocalStorage>(SIDEBAR_INTRO_LOCAL_STORAGE);
|
||||
|
||||
const { starredItemsDismissed, recentlyViewedItemsDismissed } =
|
||||
dismissedIntro ?? {};
|
||||
|
||||
@@ -34,7 +34,12 @@ import React, {
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom';
|
||||
import {
|
||||
Link,
|
||||
NavLinkProps,
|
||||
useLocation,
|
||||
useResolvedPath,
|
||||
} from 'react-router-dom';
|
||||
import { sidebarConfig, SidebarContext } from './config';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => {
|
||||
@@ -147,6 +152,54 @@ function isButtonItem(
|
||||
return (props as SidebarItemLinkProps).to === undefined;
|
||||
}
|
||||
|
||||
// TODO(Rugvip): Remove this once NavLink is updated in react-router-dom.
|
||||
// This is needed because react-router doesn't handle the path comparison
|
||||
// properly yet, matching for example /foobar with /foo.
|
||||
export const WorkaroundNavLink = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
NavLinkProps
|
||||
>(function WorkaroundNavLinkWithRef(
|
||||
{
|
||||
to,
|
||||
end,
|
||||
style,
|
||||
className,
|
||||
activeStyle,
|
||||
caseSensitive,
|
||||
activeClassName = 'active',
|
||||
'aria-current': ariaCurrentProp = 'page',
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
let { pathname: locationPathname } = useLocation();
|
||||
let { pathname: toPathname } = useResolvedPath(to);
|
||||
|
||||
if (!caseSensitive) {
|
||||
locationPathname = locationPathname.toLowerCase();
|
||||
toPathname = toPathname.toLowerCase();
|
||||
}
|
||||
|
||||
let isActive = locationPathname === toPathname;
|
||||
if (!isActive && !end) {
|
||||
// This is the behavior that is different from the original NavLink
|
||||
isActive = locationPathname.startsWith(`${toPathname}/`);
|
||||
}
|
||||
|
||||
const ariaCurrent = isActive ? ariaCurrentProp : undefined;
|
||||
|
||||
return (
|
||||
<Link
|
||||
{...rest}
|
||||
to={to}
|
||||
ref={ref}
|
||||
aria-current={ariaCurrent}
|
||||
style={{ ...style, ...(isActive ? activeStyle : undefined) }}
|
||||
className={clsx([className, isActive ? activeClassName : undefined])}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const SidebarItem = forwardRef<any, SidebarItemProps>((props, ref) => {
|
||||
const {
|
||||
icon: Icon,
|
||||
@@ -211,7 +264,7 @@ export const SidebarItem = forwardRef<any, SidebarItemProps>((props, ref) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
<WorkaroundNavLink
|
||||
{...childProps}
|
||||
activeClassName={classes.selected}
|
||||
to={props.to}
|
||||
@@ -220,7 +273,7 @@ export const SidebarItem = forwardRef<any, SidebarItemProps>((props, ref) => {
|
||||
{...navLinkProps}
|
||||
>
|
||||
{content}
|
||||
</NavLink>
|
||||
</WorkaroundNavLink>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ export type ApiFactory<
|
||||
Impl extends Api,
|
||||
Deps extends {
|
||||
[name in string]: unknown;
|
||||
}
|
||||
},
|
||||
> = {
|
||||
api: ApiRef<Api>;
|
||||
deps: TypesToApiRefs<Deps>;
|
||||
@@ -86,7 +86,7 @@ export type ApiRef<T> = {
|
||||
export type ApiRefsToTypes<
|
||||
T extends {
|
||||
[key in string]: ApiRef<unknown>;
|
||||
}
|
||||
},
|
||||
> = {
|
||||
[key in keyof T]: ApiRefType<T[key]>;
|
||||
};
|
||||
@@ -216,7 +216,7 @@ export type BackstageIdentityApi = {
|
||||
// @public (undocumented)
|
||||
export type BackstagePlugin<
|
||||
Routes extends AnyRoutes = {},
|
||||
ExternalRoutes extends AnyExternalRoutes = {}
|
||||
ExternalRoutes extends AnyExternalRoutes = {},
|
||||
> = {
|
||||
getId(): string;
|
||||
output(): PluginOutput[];
|
||||
@@ -253,7 +253,7 @@ export function createApiFactory<
|
||||
Impl extends Api,
|
||||
Deps extends {
|
||||
[name in string]: unknown;
|
||||
}
|
||||
},
|
||||
>(factory: ApiFactory<Api, Impl, Deps>): ApiFactory<Api, Impl, Deps>;
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -272,7 +272,7 @@ export function createApiRef<T>(config: ApiRefConfig): ApiRef<T>;
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function createComponentExtension<
|
||||
T extends (props: any) => JSX.Element | null
|
||||
T extends (props: any) => JSX.Element | null,
|
||||
>(options: { component: ComponentLoader<T> }): Extension<T>;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "OptionalParams" needs to be exported by the entry point index.d.ts
|
||||
@@ -284,7 +284,7 @@ export function createExternalRouteRef<
|
||||
[param in ParamKey]: string;
|
||||
},
|
||||
Optional extends boolean = false,
|
||||
ParamKey extends string = never
|
||||
ParamKey extends string = never,
|
||||
>(options: {
|
||||
id: string;
|
||||
params?: ParamKey[];
|
||||
@@ -296,7 +296,7 @@ export function createExternalRouteRef<
|
||||
// @public (undocumented)
|
||||
export function createPlugin<
|
||||
Routes extends AnyRoutes = {},
|
||||
ExternalRoutes extends AnyExternalRoutes = {}
|
||||
ExternalRoutes extends AnyExternalRoutes = {},
|
||||
>(
|
||||
config: PluginConfig<Routes, ExternalRoutes>,
|
||||
): BackstagePlugin<Routes, ExternalRoutes>;
|
||||
@@ -305,7 +305,7 @@ export function createPlugin<
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function createReactExtension<
|
||||
T extends (props: any) => JSX.Element | null
|
||||
T extends (props: any) => JSX.Element | null,
|
||||
>(options: {
|
||||
component: ComponentLoader<T>;
|
||||
data?: Record<string, unknown>;
|
||||
@@ -315,7 +315,7 @@ export function createReactExtension<
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function createRoutableExtension<
|
||||
T extends (props: any) => JSX.Element | null
|
||||
T extends (props: any) => JSX.Element | null,
|
||||
>(options: { component: () => Promise<T>; mountPoint: RouteRef }): Extension<T>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -325,7 +325,7 @@ export function createRouteRef<
|
||||
Params extends {
|
||||
[param in ParamKey]: string;
|
||||
},
|
||||
ParamKey extends string = never
|
||||
ParamKey extends string = never,
|
||||
>(config: {
|
||||
id?: string;
|
||||
params?: ParamKey[];
|
||||
@@ -342,7 +342,7 @@ export function createRouteRef<
|
||||
// @public (undocumented)
|
||||
export function createSubRouteRef<
|
||||
Path extends string,
|
||||
ParentParams extends AnyParams = never
|
||||
ParentParams extends AnyParams = never,
|
||||
>(config: {
|
||||
id: string;
|
||||
path: Path;
|
||||
@@ -369,7 +369,7 @@ export interface ElementCollection {
|
||||
getElements<
|
||||
Props extends {
|
||||
[name: string]: unknown;
|
||||
}
|
||||
},
|
||||
>(): Array<ReactElement<Props>>;
|
||||
selectByComponentData(query: {
|
||||
key: string;
|
||||
@@ -421,7 +421,7 @@ export type Extension<T> = {
|
||||
// @public (undocumented)
|
||||
export type ExternalRouteRef<
|
||||
Params extends AnyParams = any,
|
||||
Optional extends boolean = any
|
||||
Optional extends boolean = any,
|
||||
> = {
|
||||
readonly [routeRefType]: 'external';
|
||||
params: ParamKeys<Params>;
|
||||
@@ -667,7 +667,7 @@ export type PendingAuthRequest = {
|
||||
// @public (undocumented)
|
||||
export type PluginConfig<
|
||||
Routes extends AnyRoutes,
|
||||
ExternalRoutes extends AnyExternalRoutes
|
||||
ExternalRoutes extends AnyExternalRoutes,
|
||||
> = {
|
||||
id: string;
|
||||
apis?: Iterable<AnyApiFactory>;
|
||||
@@ -885,9 +885,7 @@ export function useRouteRefParams<Params extends AnyParams>(
|
||||
// Warning: (ae-missing-release-tag) "withApis" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function withApis<T>(
|
||||
apis: TypesToApiRefs<T>,
|
||||
): <P extends T>(
|
||||
export function withApis<T>(apis: TypesToApiRefs<T>): <P extends T>(
|
||||
WrappedComponent: React_2.ComponentType<P>,
|
||||
) => {
|
||||
(props: React_2.PropsWithChildren<Omit<P, keyof T>>): JSX.Element;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user