Merge branch 'master' of github.com:backstage/backstage into readersprocessorstasks
This commit is contained in:
@@ -1,5 +1,49 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/cli@0.10.5
|
||||
- @backstage/plugin-search@0.5.4
|
||||
- @backstage/plugin-techdocs@0.12.13
|
||||
- @backstage/core-plugin-api@0.4.1
|
||||
- @backstage/plugin-catalog-react@0.6.10
|
||||
- @backstage/plugin-kubernetes@0.5.3
|
||||
- @backstage/core-app-api@0.3.1
|
||||
- @backstage/core-components@0.8.3
|
||||
- @backstage/plugin-apache-airflow@0.1.2
|
||||
- @backstage/plugin-azure-devops@0.1.9
|
||||
- @backstage/plugin-badges@0.2.18
|
||||
- @backstage/plugin-catalog@0.7.7
|
||||
- @backstage/plugin-catalog-graph@0.2.5
|
||||
- @backstage/plugin-catalog-import@0.7.8
|
||||
- @backstage/plugin-circleci@0.2.33
|
||||
- @backstage/plugin-cloudbuild@0.2.31
|
||||
- @backstage/plugin-code-coverage@0.1.21
|
||||
- @backstage/plugin-cost-insights@0.11.16
|
||||
- @backstage/plugin-explore@0.3.24
|
||||
- @backstage/plugin-gcp-projects@0.3.12
|
||||
- @backstage/plugin-github-actions@0.4.30
|
||||
- @backstage/plugin-graphiql@0.2.26
|
||||
- @backstage/plugin-home@0.4.9
|
||||
- @backstage/plugin-jenkins@0.5.16
|
||||
- @backstage/plugin-kafka@0.2.24
|
||||
- @backstage/plugin-lighthouse@0.2.33
|
||||
- @backstage/plugin-newrelic@0.3.12
|
||||
- @backstage/plugin-newrelic-dashboard@0.1.2
|
||||
- @backstage/plugin-org@0.3.33
|
||||
- @backstage/plugin-pagerduty@0.3.21
|
||||
- @backstage/plugin-rollbar@0.3.22
|
||||
- @backstage/plugin-scaffolder@0.11.17
|
||||
- @backstage/plugin-sentry@0.3.32
|
||||
- @backstage/plugin-shortcuts@0.1.18
|
||||
- @backstage/plugin-tech-insights@0.1.4
|
||||
- @backstage/plugin-tech-radar@0.5.1
|
||||
- @backstage/plugin-user-settings@0.3.15
|
||||
- @backstage/plugin-api-docs@0.6.21
|
||||
|
||||
## 0.2.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+43
-42
@@ -1,60 +1,61 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.58",
|
||||
"version": "0.2.59",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/app-defaults": "^0.1.3",
|
||||
"@backstage/catalog-model": "^0.9.8",
|
||||
"@backstage/cli": "^0.10.3",
|
||||
"@backstage/core-app-api": "^0.3.0",
|
||||
"@backstage/core-components": "^0.8.2",
|
||||
"@backstage/core-plugin-api": "^0.4.0",
|
||||
"@backstage/cli": "^0.10.5",
|
||||
"@backstage/core-app-api": "^0.3.1",
|
||||
"@backstage/core-components": "^0.8.3",
|
||||
"@backstage/core-plugin-api": "^0.4.1",
|
||||
"@backstage/integration-react": "^0.1.16",
|
||||
"@backstage/plugin-api-docs": "^0.6.19",
|
||||
"@backstage/plugin-azure-devops": "^0.1.8",
|
||||
"@backstage/plugin-apache-airflow": "^0.1.1",
|
||||
"@backstage/plugin-badges": "^0.2.17",
|
||||
"@backstage/plugin-catalog": "^0.7.5",
|
||||
"@backstage/plugin-catalog-graph": "^0.2.4",
|
||||
"@backstage/plugin-catalog-import": "^0.7.6",
|
||||
"@backstage/plugin-catalog-react": "^0.6.8",
|
||||
"@backstage/plugin-circleci": "^0.2.32",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.30",
|
||||
"@backstage/plugin-code-coverage": "^0.1.20",
|
||||
"@backstage/plugin-cost-insights": "^0.11.14",
|
||||
"@backstage/plugin-explore": "^0.3.23",
|
||||
"@backstage/plugin-gcp-projects": "^0.3.11",
|
||||
"@backstage/plugin-github-actions": "^0.4.28",
|
||||
"@backstage/plugin-graphiql": "^0.2.25",
|
||||
"@backstage/plugin-home": "^0.4.8",
|
||||
"@backstage/plugin-jenkins": "^0.5.15",
|
||||
"@backstage/plugin-kafka": "^0.2.23",
|
||||
"@backstage/plugin-kubernetes": "^0.5.2",
|
||||
"@backstage/plugin-lighthouse": "^0.2.32",
|
||||
"@backstage/plugin-newrelic": "^0.3.11",
|
||||
"@backstage/plugin-newrelic-dashboard": "^0.1.1",
|
||||
"@backstage/plugin-org": "^0.3.32",
|
||||
"@backstage/plugin-pagerduty": "0.3.20",
|
||||
"@backstage/plugin-rollbar": "^0.3.21",
|
||||
"@backstage/plugin-scaffolder": "^0.11.15",
|
||||
"@backstage/plugin-search": "^0.5.2",
|
||||
"@backstage/plugin-sentry": "^0.3.31",
|
||||
"@backstage/plugin-shortcuts": "^0.1.16",
|
||||
"@backstage/plugin-tech-radar": "^0.5.0",
|
||||
"@backstage/plugin-techdocs": "^0.12.11",
|
||||
"@backstage/plugin-airbrake": "^0.0.0",
|
||||
"@backstage/plugin-api-docs": "^0.6.21",
|
||||
"@backstage/plugin-azure-devops": "^0.1.9",
|
||||
"@backstage/plugin-apache-airflow": "^0.1.2",
|
||||
"@backstage/plugin-badges": "^0.2.18",
|
||||
"@backstage/plugin-catalog": "^0.7.7",
|
||||
"@backstage/plugin-catalog-graph": "^0.2.5",
|
||||
"@backstage/plugin-catalog-import": "^0.7.8",
|
||||
"@backstage/plugin-catalog-react": "^0.6.10",
|
||||
"@backstage/plugin-circleci": "^0.2.33",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.31",
|
||||
"@backstage/plugin-code-coverage": "^0.1.21",
|
||||
"@backstage/plugin-cost-insights": "^0.11.16",
|
||||
"@backstage/plugin-explore": "^0.3.24",
|
||||
"@backstage/plugin-gcp-projects": "^0.3.12",
|
||||
"@backstage/plugin-github-actions": "^0.4.30",
|
||||
"@backstage/plugin-graphiql": "^0.2.26",
|
||||
"@backstage/plugin-home": "^0.4.9",
|
||||
"@backstage/plugin-jenkins": "^0.5.16",
|
||||
"@backstage/plugin-kafka": "^0.2.24",
|
||||
"@backstage/plugin-kubernetes": "^0.5.3",
|
||||
"@backstage/plugin-lighthouse": "^0.2.33",
|
||||
"@backstage/plugin-newrelic": "^0.3.12",
|
||||
"@backstage/plugin-newrelic-dashboard": "^0.1.2",
|
||||
"@backstage/plugin-org": "^0.3.33",
|
||||
"@backstage/plugin-pagerduty": "0.3.21",
|
||||
"@backstage/plugin-rollbar": "^0.3.22",
|
||||
"@backstage/plugin-scaffolder": "^0.11.17",
|
||||
"@backstage/plugin-search": "^0.5.4",
|
||||
"@backstage/plugin-sentry": "^0.3.32",
|
||||
"@backstage/plugin-shortcuts": "^0.1.18",
|
||||
"@backstage/plugin-tech-radar": "^0.5.1",
|
||||
"@backstage/plugin-techdocs": "^0.12.13",
|
||||
"@backstage/plugin-todo": "^0.1.17",
|
||||
"@backstage/plugin-user-settings": "^0.3.14",
|
||||
"@backstage/plugin-user-settings": "^0.3.15",
|
||||
"@backstage/search-common": "^0.2.0",
|
||||
"@backstage/plugin-tech-insights": "^0.1.2",
|
||||
"@backstage/plugin-tech-insights": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@roadiehq/backstage-plugin-github-insights": "^1.2.2",
|
||||
"@roadiehq/backstage-plugin-github-pull-requests": "^1.0.13",
|
||||
"@roadiehq/backstage-plugin-travis-ci": "^1.0.11",
|
||||
"@roadiehq/backstage-plugin-github-insights": "^1.4.2",
|
||||
"@roadiehq/backstage-plugin-github-pull-requests": "^1.3.2",
|
||||
"@roadiehq/backstage-plugin-travis-ci": "^1.3.2",
|
||||
"history": "^5.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.13.1",
|
||||
|
||||
@@ -42,11 +42,6 @@
|
||||
href="<%= publicPath %>/safari-pinned-tab.svg"
|
||||
color="#5bbad5"
|
||||
/>
|
||||
<style>
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
<title><%= config.getString('app.title') %></title>
|
||||
|
||||
<% if (config.has('app.googleAnalyticsTrackingId')) { %>
|
||||
@@ -103,7 +98,7 @@
|
||||
<% } %>
|
||||
</head>
|
||||
|
||||
<body style="margin: 0">
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
|
||||
@@ -14,7 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Content, Header, Lifecycle, Page } from '@backstage/core-components';
|
||||
import {
|
||||
CatalogIcon,
|
||||
Content,
|
||||
DocsIcon,
|
||||
Header,
|
||||
Lifecycle,
|
||||
Page,
|
||||
} from '@backstage/core-components';
|
||||
import { CatalogResultListItem } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
DefaultResultListItem,
|
||||
@@ -39,6 +46,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||
},
|
||||
filters: {
|
||||
padding: theme.spacing(2),
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -55,12 +63,23 @@ const SearchPage = () => {
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<SearchType.Accordion
|
||||
name="Result Type"
|
||||
defaultValue="software-catalog"
|
||||
types={[
|
||||
{
|
||||
value: 'software-catalog',
|
||||
name: 'Software Catalog',
|
||||
icon: <CatalogIcon />,
|
||||
},
|
||||
{
|
||||
value: 'techdocs',
|
||||
name: 'Documentation',
|
||||
icon: <DocsIcon />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Paper className={classes.filters}>
|
||||
<SearchType
|
||||
values={['techdocs', 'software-catalog']}
|
||||
name="type"
|
||||
defaultValue="software-catalog"
|
||||
/>
|
||||
<SearchFilter.Select
|
||||
className={classes.filter}
|
||||
name="kind"
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 21ae56168e: Updated the Git class with the following:
|
||||
|
||||
- Added `depth` and `noCheckout` options to Git clone, using these you can create a bare clone that includes just the git history
|
||||
- New `log` function which you can use to view the commit history of a git repo
|
||||
|
||||
- eacc582473: Reverted the default CSP configuration to include `'unsafe-eval'` again, which was mistakenly removed in the previous version.
|
||||
|
||||
## 0.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 94cdf5d1bd: In-memory cache clients instantiated from the same cache manager now share the same memory space.
|
||||
- 916b2f1f3e: Use the default CSP policy provided by `helmet` directly rather than a copy.
|
||||
- 7d4b4e937c: Uptake changes to the GitHub Credentials Provider interface.
|
||||
- 995e4c7d9d: Added support for non-"amazonaws.com" hosts (for example when testing with LocalStack) in AwsS3UrlReader.
|
||||
- Updated dependencies
|
||||
- @backstage/integration@0.7.0
|
||||
- @backstage/config-loader@0.9.1
|
||||
|
||||
## 0.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -34,9 +34,7 @@ import { Server } from 'http';
|
||||
import * as winston from 'winston';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AwsS3UrlReader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export class AwsS3UrlReader implements UrlReader {
|
||||
constructor(
|
||||
integration: AwsS3Integration,
|
||||
@@ -59,7 +57,7 @@ export class AwsS3UrlReader implements UrlReader {
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export class AzureUrlReader implements UrlReader {
|
||||
constructor(
|
||||
integration: AzureIntegration,
|
||||
@@ -114,12 +112,12 @@ export interface CacheClient {
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type CacheClientOptions = {
|
||||
defaultTtl?: number;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type CacheClientSetOptions = {
|
||||
ttl?: number;
|
||||
};
|
||||
@@ -133,18 +131,17 @@ export class CacheManager {
|
||||
): CacheManager;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type CacheManagerOptions = {
|
||||
logger?: Logger_2;
|
||||
onError?: (err: Error) => void;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export const coloredFormat: winston.Logform.Format;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export interface ContainerRunner {
|
||||
// (undocumented)
|
||||
runContainer(opts: RunContainerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -157,7 +154,7 @@ export function createDatabaseClient(
|
||||
overrides?: Partial<Knex.Config>,
|
||||
): Knex<any, unknown[]>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export function createRootLogger(
|
||||
options?: winston.LoggerOptions,
|
||||
env?: NodeJS.ProcessEnv,
|
||||
@@ -166,14 +163,14 @@ export function createRootLogger(
|
||||
// @public
|
||||
export function createServiceBuilder(_module: NodeModule): ServiceBuilder;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export function createStatusCheckRouter(options: {
|
||||
logger: Logger_2;
|
||||
path?: string;
|
||||
statusCheck?: StatusCheck;
|
||||
}): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export class DatabaseManager {
|
||||
forPlugin(pluginId: string): PluginDatabaseManager;
|
||||
static fromConfig(
|
||||
@@ -187,7 +184,7 @@ export type DatabaseManagerOptions = {
|
||||
migrations?: PluginDatabaseManager['migrations'];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export class DockerContainerRunner implements ContainerRunner {
|
||||
constructor(options: { dockerClient: Docker });
|
||||
// (undocumented)
|
||||
@@ -205,7 +202,7 @@ export function errorHandler(
|
||||
options?: ErrorHandlerOptions,
|
||||
): ErrorRequestHandler;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type ErrorHandlerOptions = {
|
||||
showStackTraces?: boolean;
|
||||
logger?: Logger_2;
|
||||
@@ -218,13 +215,13 @@ export type FromReadableArrayOptions = Array<{
|
||||
path: string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export function getRootLogger(): winston.Logger;
|
||||
|
||||
// @public
|
||||
export function getVoidLogger(): winston.Logger;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export class Git {
|
||||
// (undocumented)
|
||||
add(options: { dir: string; filepath: string }): Promise<void>;
|
||||
@@ -234,8 +231,13 @@ export class Git {
|
||||
remote: string;
|
||||
url: string;
|
||||
}): Promise<void>;
|
||||
// (undocumented)
|
||||
clone(options: { url: string; dir: string; ref?: string }): Promise<void>;
|
||||
clone(options: {
|
||||
url: string;
|
||||
dir: string;
|
||||
ref?: string;
|
||||
depth?: number;
|
||||
noCheckout?: boolean;
|
||||
}): Promise<void>;
|
||||
// (undocumented)
|
||||
commit(options: {
|
||||
dir: string;
|
||||
@@ -249,12 +251,10 @@ export class Git {
|
||||
email: string;
|
||||
};
|
||||
}): Promise<string>;
|
||||
// (undocumented)
|
||||
currentBranch(options: {
|
||||
dir: string;
|
||||
fullName?: boolean;
|
||||
}): Promise<string | undefined>;
|
||||
// (undocumented)
|
||||
fetch(options: { dir: string; remote?: string }): Promise<void>;
|
||||
// (undocumented)
|
||||
static fromAuth: (options: {
|
||||
@@ -264,7 +264,7 @@ export class Git {
|
||||
}) => Git;
|
||||
// (undocumented)
|
||||
init(options: { dir: string; defaultBranch?: string }): Promise<void>;
|
||||
// (undocumented)
|
||||
log(options: { dir: string; ref?: string }): Promise<ReadCommitResult[]>;
|
||||
merge(options: {
|
||||
dir: string;
|
||||
theirs: string;
|
||||
@@ -280,9 +280,7 @@ export class Git {
|
||||
}): Promise<MergeResult>;
|
||||
// (undocumented)
|
||||
push(options: { dir: string; remote: string }): Promise<PushResult>;
|
||||
// (undocumented)
|
||||
readCommit(options: { dir: string; sha: string }): Promise<ReadCommitResult>;
|
||||
// (undocumented)
|
||||
resolveRef(options: { dir: string; ref: string }): Promise<string>;
|
||||
}
|
||||
|
||||
@@ -309,7 +307,7 @@ export class GithubUrlReader implements UrlReader {
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export class GitlabUrlReader implements UrlReader {
|
||||
constructor(
|
||||
integration: GitLabIntegration,
|
||||
@@ -397,7 +395,7 @@ export type ReadTreeResponseDirOptions = {
|
||||
targetDir?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export interface ReadTreeResponseFactory {
|
||||
// (undocumented)
|
||||
fromReadableArray(
|
||||
@@ -447,7 +445,7 @@ export type ReadUrlResponse = {
|
||||
// @public
|
||||
export function requestLoggingHandler(logger?: Logger_2): RequestHandler;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type RequestLoggingHandlerFactory = (
|
||||
logger?: Logger_2,
|
||||
) => RequestHandler;
|
||||
@@ -458,7 +456,7 @@ export function resolvePackagePath(name: string, ...paths: string[]): string;
|
||||
// @public
|
||||
export function resolveSafeChildPath(base: string, path: string): string;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type RunContainerOptions = {
|
||||
imageName: string;
|
||||
command?: string | string[];
|
||||
@@ -507,7 +505,7 @@ export class ServerTokenManager implements TokenManager {
|
||||
static noop(): TokenManager;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type ServiceBuilder = {
|
||||
loadConfig(config: Config): ServiceBuilder;
|
||||
setPort(port: number): ServiceBuilder;
|
||||
@@ -533,7 +531,7 @@ export type ServiceBuilder = {
|
||||
start(): Promise<Server>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export function setRootLogger(newLogger: winston.Logger): void;
|
||||
|
||||
// @public @deprecated
|
||||
@@ -553,7 +551,7 @@ export class SingleHostDiscovery implements PluginEndpointDiscovery {
|
||||
getExternalBaseUrl(pluginId: string): Promise<string>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type StatusCheck = () => Promise<any>;
|
||||
|
||||
// @public
|
||||
@@ -561,7 +559,7 @@ export function statusCheckHandler(
|
||||
options?: StatusCheckHandlerOptions,
|
||||
): Promise<RequestHandler>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export interface StatusCheckHandlerOptions {
|
||||
statusCheck?: StatusCheck;
|
||||
}
|
||||
@@ -596,7 +594,7 @@ export class UrlReaders {
|
||||
static default(options: UrlReadersOptions): UrlReader;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type UrlReadersOptions = {
|
||||
config: Config;
|
||||
logger: Logger_2;
|
||||
@@ -611,8 +609,4 @@ export function useHotCleanup(
|
||||
|
||||
// @public
|
||||
export function useHotMemoize<T>(_module: NodeModule, valueFactory: () => T): T;
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/database/types.d.ts:23:12 - (tsdoc-undefined-tag) The TSDoc tag "@default" is not defined in this configuration
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -31,12 +31,12 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.6",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/config-loader": "^0.9.0",
|
||||
"@backstage/config-loader": "^0.9.1",
|
||||
"@backstage/errors": "^0.1.5",
|
||||
"@backstage/integration": "^0.6.10",
|
||||
"@backstage/integration": "^0.7.0",
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@google-cloud/storage": "^5.8.0",
|
||||
"@lerna/project": "^4.0.0",
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/dockerode": "^3.3.0",
|
||||
@@ -58,7 +58,7 @@
|
||||
"keyv-memcache": "^1.2.5",
|
||||
"knex": "^0.95.1",
|
||||
"lodash": "^4.17.21",
|
||||
"logform": "^2.1.1",
|
||||
"logform": "^2.3.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.5",
|
||||
"morgan": "^1.10.0",
|
||||
@@ -81,8 +81,8 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.3",
|
||||
"@backstage/test-utils": "^0.2.0",
|
||||
"@backstage/cli": "^0.10.5",
|
||||
"@backstage/test-utils": "^0.2.1",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/concat-stream": "^1.6.0",
|
||||
|
||||
+5
-1
@@ -22,7 +22,11 @@ type CacheClientArgs = {
|
||||
client: Keyv;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Options passed to {@link CacheClient.set}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CacheClientSetOptions = {
|
||||
/**
|
||||
* Optional TTL in milliseconds. Defaults to the TTL provided when the client
|
||||
|
||||
@@ -147,6 +147,22 @@ describe('CacheManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('shares memory across multiple instances of the memory client', () => {
|
||||
const manager = CacheManager.fromConfig(defaultConfig());
|
||||
const plugin = 'test-plugin';
|
||||
|
||||
// Instantiate two in-memory clients.
|
||||
manager.forPlugin(plugin).getClient({ defaultTtl: 10 });
|
||||
manager.forPlugin(plugin).getClient({ defaultTtl: 10 });
|
||||
|
||||
const cache = Keyv as unknown as jest.Mock;
|
||||
const mockCall2 = cache.mock.calls.splice(-1)[0][0];
|
||||
const mockCall1 = cache.mock.calls.splice(-1)[0][0];
|
||||
|
||||
// Note: .toBe() checks referential identity of object instances.
|
||||
expect(mockCall1.store).toBe(mockCall2.store);
|
||||
});
|
||||
|
||||
it('returns a memcache client when configured', () => {
|
||||
const expectedHost = '127.0.0.1:11211';
|
||||
const manager = CacheManager.fromConfig(
|
||||
|
||||
+12
-3
@@ -42,14 +42,21 @@ export class CacheManager {
|
||||
none: this.getNoneClient,
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared memory store for the in-memory cache client. Sharing the same Map
|
||||
* instance ensures get/set/delete operations hit the same store, regardless
|
||||
* of where/when a client is instantiated.
|
||||
*/
|
||||
private readonly memoryStore = new Map();
|
||||
|
||||
private readonly logger: Logger;
|
||||
private readonly store: keyof CacheManager['storeFactories'];
|
||||
private readonly connection: string;
|
||||
private readonly errorHandler: CacheManagerOptions['onError'];
|
||||
|
||||
/**
|
||||
* Creates a new CacheManager instance by reading from the `backend` config
|
||||
* section, specifically the `.cache` key.
|
||||
* Creates a new {@link CacheManager} instance by reading from the `backend`
|
||||
* config section, specifically the `.cache` key.
|
||||
*
|
||||
* @param config - The loaded application configuration.
|
||||
*/
|
||||
@@ -86,7 +93,8 @@ export class CacheManager {
|
||||
/**
|
||||
* Generates a PluginCacheManager for consumption by plugins.
|
||||
*
|
||||
* @param pluginId - The plugin that the cache manager should be created for. Plugin names should be unique.
|
||||
* @param pluginId - The plugin that the cache manager should be created for.
|
||||
* Plugin names should be unique.
|
||||
*/
|
||||
forPlugin(pluginId: string): PluginCacheManager {
|
||||
return {
|
||||
@@ -133,6 +141,7 @@ export class CacheManager {
|
||||
return new Keyv({
|
||||
namespace: pluginId,
|
||||
ttl: defaultTtl,
|
||||
store: this.memoryStore,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+17
-7
@@ -17,7 +17,11 @@
|
||||
import { Logger } from 'winston';
|
||||
import { CacheClient } from './CacheClient';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Options given when constructing a {@link CacheClient}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CacheClientOptions = {
|
||||
/**
|
||||
* An optional default TTL (in milliseconds) to be set when getting a client
|
||||
@@ -27,7 +31,11 @@ export type CacheClientOptions = {
|
||||
defaultTtl?: number;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Options given when constructing a {@link CacheManager}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CacheManagerOptions = {
|
||||
/**
|
||||
* An optional logger for use by the PluginCacheManager.
|
||||
@@ -42,17 +50,19 @@ export type CacheManagerOptions = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The PluginCacheManager manages access to cache stores that Plugins get.
|
||||
* Manages access to cache stores that plugins get.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type PluginCacheManager = {
|
||||
/**
|
||||
* getClient provides backend plugins cache connections for itself.
|
||||
* Provides backend plugins cache connections for themselves.
|
||||
*
|
||||
* The purpose of this method is to allow plugins to get isolated data
|
||||
* stores so that plugins are discouraged from cache-level integration
|
||||
* and/or cache key collisions.
|
||||
* @remarks
|
||||
*
|
||||
* The purpose of this method is to allow plugins to get isolated data stores
|
||||
* so that plugins are discouraged from cache-level integration and/or cache
|
||||
* key collisions.
|
||||
*/
|
||||
getClient: (options?: CacheClientOptions) => CacheClient;
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from '@backstage/config-loader';
|
||||
import { AppConfig, Config, ConfigReader } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
|
||||
import { isValidUrl } from './urls';
|
||||
|
||||
@@ -194,11 +195,9 @@ export async function loadBackendConfig(options: {
|
||||
// TODO(hhogg): This is fetching _all_ of the packages of the monorepo
|
||||
// in order to find the secrets for redactions, however we only care about
|
||||
// the backend ones, we need to find a way to exclude the frontend packages.
|
||||
const { Project } = require('@lerna/project');
|
||||
const project = new Project(paths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
const { packages } = await getPackages(paths.targetDir);
|
||||
const schema = await loadConfigSchema({
|
||||
dependencies: packages.map((p: any) => p.name),
|
||||
dependencies: packages.map(p => p.packageJson.name),
|
||||
});
|
||||
|
||||
const config = new ObservableConfigProxy(options.logger);
|
||||
|
||||
@@ -38,7 +38,7 @@ function pluginPath(pluginId: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options object.
|
||||
* Creation options for {@link DatabaseManager}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -46,15 +46,20 @@ export type DatabaseManagerOptions = {
|
||||
migrations?: PluginDatabaseManager['migrations'];
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Manages database connections for Backstage backend plugins.
|
||||
*
|
||||
* The database manager allows the user to set connection and client settings on
|
||||
* a per pluginId basis by defining a database config block under
|
||||
* `plugin.<pluginId>` in addition to top level defaults. Optionally, a user may
|
||||
* set `prefix` which is used to prefix generated database names if config is
|
||||
* not provided.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class DatabaseManager {
|
||||
/**
|
||||
* Creates a DatabaseManager from `backend.database` config.
|
||||
*
|
||||
* The database manager allows the user to set connection and client settings on a per pluginId
|
||||
* basis by defining a database config block under `plugin.<pluginId>` in addition to top level
|
||||
* defaults. Optionally, a user may set `prefix` which is used to prefix generated database
|
||||
* names if config is not provided.
|
||||
* Creates a {@link DatabaseManager} from `backend.database` config.
|
||||
*
|
||||
* @param config - The loaded application configuration.
|
||||
* @param options - An optional configuration object.
|
||||
@@ -108,7 +113,7 @@ export class DatabaseManager {
|
||||
* which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is
|
||||
* `schema`, it will fallback to using the default database for the knex instance.
|
||||
*
|
||||
* @param pluginId Lookup the database name for given plugin
|
||||
* @param pluginId - Lookup the database name for given plugin
|
||||
* @returns String representing the plugin's database name
|
||||
*/
|
||||
private getDatabaseName(pluginId: string): string | undefined {
|
||||
@@ -143,12 +148,13 @@ export class DatabaseManager {
|
||||
/**
|
||||
* Provides the client type which should be used for a given plugin.
|
||||
*
|
||||
* The client type is determined by plugin specific config if present. Otherwise the base
|
||||
* client is used as the fallback.
|
||||
* The client type is determined by plugin specific config if present.
|
||||
* Otherwise the base client is used as the fallback.
|
||||
*
|
||||
* @param pluginId Plugin to get the client type for
|
||||
* @returns Object with client type returned as `client` and boolean representing whether
|
||||
* or not the client was overridden as `overridden`
|
||||
* @param pluginId - Plugin to get the client type for
|
||||
* @returns Object with client type returned as `client` and boolean
|
||||
* representing whether or not the client was overridden as
|
||||
* `overridden`
|
||||
*/
|
||||
private getClientType(pluginId: string): {
|
||||
client: string;
|
||||
@@ -169,8 +175,8 @@ export class DatabaseManager {
|
||||
/**
|
||||
* Provides the knexConfig which should be used for a given plugin.
|
||||
*
|
||||
* @param pluginId Plugin to get the knexConfig for
|
||||
* @returns the merged kexConfig value or undefined if it isn't specified
|
||||
* @param pluginId - Plugin to get the knexConfig for
|
||||
* @returns The merged knexConfig value or undefined if it isn't specified
|
||||
*/
|
||||
private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {
|
||||
const pluginConfig = this.config
|
||||
@@ -197,13 +203,15 @@ export class DatabaseManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Knex connection plugin config by combining base and plugin config.
|
||||
* Provides a Knex connection plugin config by combining base and plugin
|
||||
* config.
|
||||
*
|
||||
* This method provides a baseConfig for a plugin database connector. If the client type
|
||||
* has not been overridden, the global connection config will be included with plugin
|
||||
* specific config as the base. Values from the plugin connection take precedence over the
|
||||
* base. Base database name is omitted for all supported databases excluding SQLite unless
|
||||
* `pluginDivisionMode` is set to `schema`.
|
||||
* This method provides a baseConfig for a plugin database connector. If the
|
||||
* client type has not been overridden, the global connection config will be
|
||||
* included with plugin specific config as the base. Values from the plugin
|
||||
* connection take precedence over the base. Base database name is omitted for
|
||||
* all supported databases excluding SQLite unless `pluginDivisionMode` is set
|
||||
* to `schema`.
|
||||
*/
|
||||
private getConnectionConfig(
|
||||
pluginId: string,
|
||||
@@ -249,9 +257,10 @@ export class DatabaseManager {
|
||||
/**
|
||||
* Provides a Knex database config for a given plugin.
|
||||
*
|
||||
* This method provides a Knex configuration object along with the plugin's client type.
|
||||
* This method provides a Knex configuration object along with the plugin's
|
||||
* client type.
|
||||
*
|
||||
* @param pluginId The plugin that the database config should correspond with
|
||||
* @param pluginId - The plugin that the database config should correspond with
|
||||
*/
|
||||
private getConfigForPlugin(pluginId: string): Knex.Config {
|
||||
const { client } = this.getClientType(pluginId);
|
||||
@@ -264,20 +273,21 @@ export class DatabaseManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a partial Knex.Config database schema override for a given plugin.
|
||||
* Provides a partial `Knex.Config` database schema override for a given
|
||||
* plugin.
|
||||
*
|
||||
* @param pluginId Target plugin to get database schema override
|
||||
* @returns Partial Knex.Config with database schema override
|
||||
* @param pluginId - Target plugin to get database schema override
|
||||
* @returns Partial `Knex.Config` with database schema override
|
||||
*/
|
||||
private getSchemaOverrides(pluginId: string): Knex.Config | undefined {
|
||||
return createSchemaOverride(this.getClientType(pluginId).client, pluginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a partial Knex.Config database name override for a given plugin.
|
||||
* Provides a partial `Knex.Config`• database name override for a given plugin.
|
||||
*
|
||||
* @param pluginId Target plugin to get database name override
|
||||
* @returns Partial Knex.Config with database name override
|
||||
* @param pluginId - Target plugin to get database name override
|
||||
* @returns Partial `Knex.Config` with database name override
|
||||
*/
|
||||
private getDatabaseOverrides(pluginId: string): Knex.Config {
|
||||
const databaseName = this.getDatabaseName(pluginId);
|
||||
@@ -289,8 +299,9 @@ export class DatabaseManager {
|
||||
/**
|
||||
* Provides a scoped Knex client for a plugin as per application config.
|
||||
*
|
||||
* @param pluginId Plugin to get a Knex client for
|
||||
* @returns Promise which resolves to a scoped Knex database client for a plugin
|
||||
* @param pluginId - Plugin to get a Knex client for
|
||||
* @returns Promise which resolves to a scoped Knex database client for a
|
||||
* plugin
|
||||
*/
|
||||
private async getDatabase(pluginId: string): Promise<Knex> {
|
||||
const pluginConfig = new ConfigReader(
|
||||
|
||||
@@ -20,8 +20,8 @@ import { merge } from 'lodash';
|
||||
* Merges database objects together
|
||||
*
|
||||
* @public
|
||||
* @param config The base config. The input is not modified
|
||||
* @param overrides Any additional overrides
|
||||
* @param config - The base config. The input is not modified
|
||||
* @param overrides - Any additional overrides
|
||||
*/
|
||||
export function mergeDatabaseConfig(config: any, ...overrides: any[]) {
|
||||
return merge({}, config, ...overrides);
|
||||
|
||||
@@ -58,7 +58,7 @@ export function createDatabaseClient(
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for createDatabaseClient
|
||||
* Alias for {@link createDatabaseClient}
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use createDatabaseClient instead
|
||||
@@ -100,7 +100,8 @@ export async function ensureSchemaExists(
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Knex.Config object with the provided database name for a given client.
|
||||
* Provides a `Knex.Config` object with the provided database name for a given
|
||||
* client.
|
||||
*/
|
||||
export function createNameOverride(
|
||||
client: string,
|
||||
@@ -117,7 +118,8 @@ export function createNameOverride(
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a Knex.Config object with the provided database schema for a given client. Currently only supported by `pg`.
|
||||
* Provides a `Knex.Config` object with the provided database schema for a given
|
||||
* client. Currently only supported by `pg`.
|
||||
*/
|
||||
export function createSchemaOverride(
|
||||
client: string,
|
||||
@@ -156,7 +158,8 @@ export function parseConnectionString(
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a connection config or string into an object which can be passed to Knex.
|
||||
* Normalizes a connection config or string into an object which can be passed
|
||||
* to Knex.
|
||||
*/
|
||||
export function normalizeConnection(
|
||||
connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Knex } from 'knex';
|
||||
* Default override for knex database drivers which accept ConnectionConfig
|
||||
* with `connection.database` as the database name field.
|
||||
*
|
||||
* @param name database name to get config override for
|
||||
* @param name - database name to get config override for
|
||||
*/
|
||||
export default function defaultNameOverride(
|
||||
name: string,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Knex } from 'knex';
|
||||
/**
|
||||
* Provides a partial knex config with schema name override.
|
||||
*
|
||||
* @param name schema name to get config override for
|
||||
* @param name - schema name to get config override for
|
||||
*/
|
||||
export default function defaultSchemaOverride(
|
||||
name: string,
|
||||
|
||||
@@ -26,8 +26,8 @@ import defaultNameOverride from './defaultNameOverride';
|
||||
/**
|
||||
* Creates a knex mysql database connection
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
* @param dbConfig - The database config
|
||||
* @param overrides - Additional options to merge with the config
|
||||
*/
|
||||
export function createMysqlDatabaseClient(
|
||||
dbConfig: Config,
|
||||
@@ -41,8 +41,8 @@ export function createMysqlDatabaseClient(
|
||||
/**
|
||||
* Builds a knex mysql database connection
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
* @param dbConfig - The database config
|
||||
* @param overrides - Additional options to merge with the config
|
||||
*/
|
||||
export function buildMysqlDatabaseConfig(
|
||||
dbConfig: Config,
|
||||
@@ -61,8 +61,8 @@ export function buildMysqlDatabaseConfig(
|
||||
/**
|
||||
* Gets the mysql connection config
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param parseConnectionString Flag to explicitly control connection string parsing
|
||||
* @param dbConfig - The database config
|
||||
* @param parseConnectionString - Flag to explicitly control connection string parsing
|
||||
*/
|
||||
export function getMysqlConnectionConfig(
|
||||
dbConfig: Config,
|
||||
@@ -86,7 +86,7 @@ export function getMysqlConnectionConfig(
|
||||
* Parses a mysql connection string.
|
||||
*
|
||||
* e.g. mysql://examplename:somepassword@examplehost:3306/dbname
|
||||
* @param connectionString The mysql connection string
|
||||
* @param connectionString - The mysql connection string
|
||||
*/
|
||||
export function parseMysqlConnectionString(
|
||||
connectionString: string,
|
||||
@@ -140,8 +140,8 @@ export function parseMysqlConnectionString(
|
||||
/**
|
||||
* Creates the missing mysql database if it does not exist
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param databases The names of the databases to create
|
||||
* @param dbConfig - The database config
|
||||
* @param databases - The names of the databases to create
|
||||
*/
|
||||
export async function ensureMysqlDatabaseExists(
|
||||
dbConfig: Config,
|
||||
|
||||
@@ -26,8 +26,8 @@ import defaultSchemaOverride from './defaultSchemaOverride';
|
||||
/**
|
||||
* Creates a knex postgres database connection
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
* @param dbConfig - The database config
|
||||
* @param overrides - Additional options to merge with the config
|
||||
*/
|
||||
export function createPgDatabaseClient(
|
||||
dbConfig: Config,
|
||||
@@ -41,8 +41,8 @@ export function createPgDatabaseClient(
|
||||
/**
|
||||
* Builds a knex postgres database connection
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
* @param dbConfig - The database config
|
||||
* @param overrides - Additional options to merge with the config
|
||||
*/
|
||||
export function buildPgDatabaseConfig(
|
||||
dbConfig: Config,
|
||||
@@ -61,8 +61,8 @@ export function buildPgDatabaseConfig(
|
||||
/**
|
||||
* Gets the postgres connection config
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param parseConnectionString Flag to explicitly control connection string parsing
|
||||
* @param dbConfig - The database config
|
||||
* @param parseConnectionString - Flag to explicitly control connection string parsing
|
||||
*/
|
||||
export function getPgConnectionConfig(
|
||||
dbConfig: Config,
|
||||
@@ -85,7 +85,7 @@ export function getPgConnectionConfig(
|
||||
/**
|
||||
* Parses a connection string using pg-connection-string
|
||||
*
|
||||
* @param connectionString The postgres connection string
|
||||
* @param connectionString - The postgres connection string
|
||||
*/
|
||||
export function parsePgConnectionString(connectionString: string) {
|
||||
const parse = requirePgConnectionString();
|
||||
@@ -103,8 +103,8 @@ function requirePgConnectionString() {
|
||||
/**
|
||||
* Creates the missing Postgres database if it does not exist
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param databases The name of the databases to create
|
||||
* @param dbConfig - The database config
|
||||
* @param databases - The name of the databases to create
|
||||
*/
|
||||
export async function ensurePgDatabaseExists(
|
||||
dbConfig: Config,
|
||||
@@ -139,8 +139,8 @@ export async function ensurePgDatabaseExists(
|
||||
/**
|
||||
* Creates the missing Postgres schema if it does not exist
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param schemas The name of the schemas to create
|
||||
* @param dbConfig - The database config
|
||||
* @param schemas - The name of the schemas to create
|
||||
*/
|
||||
export async function ensurePgSchemaExists(
|
||||
dbConfig: Config,
|
||||
|
||||
@@ -25,8 +25,8 @@ import { DatabaseConnector } from '../types';
|
||||
/**
|
||||
* Creates a knex SQLite3 database connection
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
* @param dbConfig - The database config
|
||||
* @param overrides - Additional options to merge with the config
|
||||
*/
|
||||
export function createSqliteDatabaseClient(
|
||||
dbConfig: Config,
|
||||
@@ -58,8 +58,8 @@ export function createSqliteDatabaseClient(
|
||||
/**
|
||||
* Builds a knex SQLite3 connection config
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
* @param dbConfig - The database config
|
||||
* @param overrides - Additional options to merge with the config
|
||||
*/
|
||||
export function buildSqliteDatabaseConfig(
|
||||
dbConfig: Config,
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface PluginDatabaseManager {
|
||||
/**
|
||||
* skip database migrations. Useful if connecting to a read-only database.
|
||||
*
|
||||
* @default false
|
||||
* @defaultValue false
|
||||
*/
|
||||
skip?: boolean;
|
||||
};
|
||||
|
||||
@@ -31,7 +31,11 @@ const coloredTemplate = (info: TransformableInfo) => {
|
||||
return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* A logging format that adds coloring to console output.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const coloredFormat = winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.colorize({
|
||||
|
||||
@@ -23,12 +23,29 @@ import { escapeRegExp } from '../util/escapeRegExp';
|
||||
let rootLogger: winston.Logger;
|
||||
let redactionRegExp: RegExp | undefined;
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Gets the current root logger.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function getRootLogger(): winston.Logger {
|
||||
return rootLogger;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Sets a completely custom default "root" logger.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This is the logger instance that will be the foundation for all other logger
|
||||
* instances passed to plugins etc, in a given backend.
|
||||
*
|
||||
* Only use this if you absolutely need to make a completely custom logger.
|
||||
* Normally if you want to make light adaptations to the default logger
|
||||
* behavior, you would instead call {@link createRootLogger}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function setRootLogger(newLogger: winston.Logger) {
|
||||
rootLogger = newLogger;
|
||||
}
|
||||
@@ -67,7 +84,17 @@ function redactLogLine(info: winston.Logform.TransformableInfo) {
|
||||
return info;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Creates a default "root" logger. This also calls {@link setRootLogger} under
|
||||
* the hood.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This is the logger instance that will be the foundation for all other logger
|
||||
* instances passed to plugins etc, in a given backend.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createRootLogger(
|
||||
options: winston.LoggerOptions = {},
|
||||
env = process.env,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
AuthenticationError,
|
||||
ConflictError,
|
||||
ErrorResponse,
|
||||
ErrorResponseBody,
|
||||
InputError,
|
||||
NotAllowedError,
|
||||
NotFoundError,
|
||||
@@ -28,7 +28,11 @@ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express';
|
||||
import { Logger } from 'winston';
|
||||
import { getRootLogger } from '../logging';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Options passed to the {@link errorHandler} middleware.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ErrorHandlerOptions = {
|
||||
/**
|
||||
* Whether error response bodies should show error stack traces or not.
|
||||
@@ -89,7 +93,7 @@ export function errorHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
const body: ErrorResponse = {
|
||||
const body: ErrorResponseBody = {
|
||||
error: serializeError(error, { includeStack: showStackTraces }),
|
||||
request: { method: req.method, url: req.url },
|
||||
response: { statusCode },
|
||||
|
||||
@@ -16,10 +16,19 @@
|
||||
|
||||
import { NextFunction, Request, Response, RequestHandler } from 'express';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* A custom status checking function, passed to {@link statusCheckHandler} and
|
||||
* {@link createStatusCheckRouter}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type StatusCheck = () => Promise<any>;
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Options passed to {@link statusCheckHandler}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface StatusCheckHandlerOptions {
|
||||
/**
|
||||
* Optional status function which returns a message.
|
||||
|
||||
@@ -178,7 +178,7 @@ describe('AwsS3UrlReader', () => {
|
||||
),
|
||||
).rejects.toThrow(
|
||||
Error(
|
||||
`Could not retrieve file from S3; caused by Error: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
|
||||
`Could not retrieve file from S3; caused by Error: invalid AWS S3 URL, cannot parse region from host in https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -234,7 +234,7 @@ describe('AwsS3UrlReader', () => {
|
||||
),
|
||||
).rejects.toThrow(
|
||||
Error(
|
||||
`Could not retrieve file from S3; caused by Error: not a valid AWS S3 URL: https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
|
||||
`Could not retrieve file from S3; caused by Error: invalid AWS S3 URL, cannot parse region from host in https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml`,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -332,4 +332,47 @@ describe('AwsS3UrlReader', () => {
|
||||
expect(body.toString().trim()).toBe('site_name: Test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readNonAwsHost', () => {
|
||||
let awsS3UrlReader: AwsS3UrlReader;
|
||||
|
||||
beforeAll(() => {
|
||||
AWSMock.setSDKInstance(aws);
|
||||
AWSMock.mock(
|
||||
'S3',
|
||||
'getObject',
|
||||
Buffer.from(
|
||||
require('fs').readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/awsS3/awsS3-mock-object.yaml',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const s3 = new aws.S3();
|
||||
awsS3UrlReader = new AwsS3UrlReader(
|
||||
new AwsS3Integration(
|
||||
readAwsS3IntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'localhost:4566',
|
||||
accessKeyId: 'fake-access-key',
|
||||
secretAccessKey: 'fake-secret-key',
|
||||
endpoint: 'http://localhost:4566',
|
||||
s3ForcePathStyle: true,
|
||||
}),
|
||||
),
|
||||
),
|
||||
{ s3, treeResponseFactory },
|
||||
);
|
||||
});
|
||||
|
||||
it('returns contents of an object in a bucket', async () => {
|
||||
const response = await awsS3UrlReader.read(
|
||||
'http://localhost:4566/test-bucket/awsS3-mock-object.yaml',
|
||||
);
|
||||
expect(response.toString().trim()).toBe('site_name: Test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,12 +27,17 @@ import {
|
||||
UrlReader,
|
||||
} from './types';
|
||||
import getRawBody from 'raw-body';
|
||||
import { AwsS3Integration, ScmIntegrations } from '@backstage/integration';
|
||||
import {
|
||||
AwsS3Integration,
|
||||
ScmIntegrations,
|
||||
AwsS3IntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
import { ForwardedError, NotModifiedError } from '@backstage/errors';
|
||||
import { ListObjectsV2Output, ObjectList } from 'aws-sdk/clients/s3';
|
||||
|
||||
const parseURL = (
|
||||
url: string,
|
||||
config: AwsS3IntegrationConfig,
|
||||
): { path: string; bucket: string; region: string } => {
|
||||
let { host, pathname } = new URL(url);
|
||||
|
||||
@@ -42,20 +47,45 @@ const parseURL = (
|
||||
*/
|
||||
pathname = pathname.substr(1);
|
||||
|
||||
let bucket;
|
||||
let region;
|
||||
|
||||
/**
|
||||
* Checks that the given URL is a valid S3 object url.
|
||||
* Format of a Valid S3 URL: https://bucket-name.s3.Region.amazonaws.com/keyname
|
||||
* Path style URLs: https://s3.Region.amazonaws.com/bucket-name/key-name
|
||||
* Virtual hosted style URLs: https://bucket-name.s3.Region.amazonaws.com/key-name
|
||||
* See https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#path-style-access
|
||||
*/
|
||||
const validHost = new RegExp(
|
||||
/^[a-z\d][a-z\d\.-]{1,61}[a-z\d]\.s3\.[a-z\d-]+\.amazonaws.com$/,
|
||||
);
|
||||
if (!validHost.test(host)) {
|
||||
throw new Error(`not a valid AWS S3 URL: ${url}`);
|
||||
if (config.s3ForcePathStyle) {
|
||||
if (pathname.indexOf('/') < 0) {
|
||||
throw new Error(
|
||||
`invalid path-style AWS S3 URL, ${url} does not contain bucket in the path`,
|
||||
);
|
||||
}
|
||||
[bucket] = pathname.split('/');
|
||||
pathname = pathname.substr(bucket.length + 1);
|
||||
} else {
|
||||
if (host.indexOf('.') < 0) {
|
||||
throw new Error(
|
||||
`invalid virtual hosted-style AWS S3 URL, ${url} does not contain bucket prefix in the host`,
|
||||
);
|
||||
}
|
||||
[bucket] = host.split('.');
|
||||
host = host.substr(bucket.length + 1);
|
||||
}
|
||||
|
||||
const [bucket] = host.split(/\.s3\.[a-z\d-]+\.amazonaws.com/);
|
||||
host = host.substring(bucket.length);
|
||||
const [, , region, ,] = host.split('.');
|
||||
// Only extract region from *.amazonaws.com hosts
|
||||
if (config.host === 'amazonaws.com') {
|
||||
// At this point bucket prefix is removed from host for virtual hosted URLs
|
||||
const match = host.match(/^s3\.([a-z\d-]+)\.amazonaws\.com$/);
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`invalid AWS S3 URL, cannot parse region from host in ${url}`,
|
||||
);
|
||||
}
|
||||
region = match[1];
|
||||
} else {
|
||||
region = '';
|
||||
}
|
||||
|
||||
return {
|
||||
path: pathname,
|
||||
@@ -64,15 +94,23 @@ const parseURL = (
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a {@link UrlReader} for AWS S3 buckets.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class AwsS3UrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
|
||||
return integrations.awsS3.list().map(integration => {
|
||||
const creds = AwsS3UrlReader.buildCredentials(integration);
|
||||
|
||||
const s3 = new S3({
|
||||
apiVersion: '2006-03-01',
|
||||
credentials: creds,
|
||||
endpoint: integration.config.endpoint,
|
||||
s3ForcePathStyle: integration.config.s3ForcePathStyle,
|
||||
});
|
||||
const reader = new AwsS3UrlReader(integration, {
|
||||
s3,
|
||||
@@ -138,7 +176,7 @@ export class AwsS3UrlReader implements UrlReader {
|
||||
options?: ReadUrlOptions,
|
||||
): Promise<ReadUrlResponse> {
|
||||
try {
|
||||
const { path, bucket, region } = parseURL(url);
|
||||
const { path, bucket, region } = parseURL(url, this.integration.config);
|
||||
aws.config.update({ region: region });
|
||||
|
||||
let params;
|
||||
@@ -178,7 +216,7 @@ export class AwsS3UrlReader implements UrlReader {
|
||||
options?: ReadTreeOptions,
|
||||
): Promise<ReadTreeResponse> {
|
||||
try {
|
||||
const { path, bucket, region } = parseURL(url);
|
||||
const { path, bucket, region } = parseURL(url, this.integration.config);
|
||||
const allObjects: ObjectList = [];
|
||||
const responses = [];
|
||||
let continuationToken: string | undefined;
|
||||
|
||||
@@ -38,7 +38,11 @@ import {
|
||||
ReadUrlResponse,
|
||||
} from './types';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Implements a {@link UrlReader} for Azure repos.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class AzureUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
|
||||
@@ -41,8 +41,8 @@ import {
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* A processor that adds the ability to read files from Bitbucket v1 and v2 APIs, such as
|
||||
* the one exposed by Bitbucket Cloud itself.
|
||||
* Implements a {@link UrlReader} for files from Bitbucket v1 and v2 APIs, such
|
||||
* as the one exposed by Bitbucket Cloud itself.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* A UrlReader that does a plain fetch of the URL.
|
||||
* A {@link UrlReader} that does a plain fetch of the URL.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -50,7 +50,7 @@ export type GhBlobResponse =
|
||||
RestEndpointMethodTypes['git']['getBlob']['response']['data'];
|
||||
|
||||
/**
|
||||
* A processor that adds the ability to read files from GitHub v3 APIs, such as
|
||||
* Implements a {@link UrlReader} for files through the GitHub v3 APIs, such as
|
||||
* the one exposed by GitHub itself.
|
||||
*
|
||||
* @public
|
||||
|
||||
@@ -39,7 +39,11 @@ import {
|
||||
} from './types';
|
||||
import { trimEnd } from 'lodash';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Implements a {@link UrlReader} for files on GitLab.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GitlabUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
|
||||
@@ -48,7 +48,11 @@ const parseURL = (
|
||||
};
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Implements a {@link UrlReader} for files on Google GCS.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GoogleGcsUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config, logger }) => {
|
||||
if (!config.has('integrations.googleGcs')) {
|
||||
|
||||
@@ -27,7 +27,11 @@ import { FetchUrlReader } from './FetchUrlReader';
|
||||
import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
|
||||
import { AwsS3UrlReader } from './AwsS3UrlReader';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Creation options for {@link UrlReaders}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UrlReadersOptions = {
|
||||
/** Root config object */
|
||||
config: Config;
|
||||
@@ -38,13 +42,13 @@ export type UrlReadersOptions = {
|
||||
};
|
||||
|
||||
/**
|
||||
* UrlReaders provide various utilities related to the UrlReader interface.
|
||||
* Helps construct {@link UrlReader}s.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class UrlReaders {
|
||||
/**
|
||||
* Creates a UrlReader without any known types.
|
||||
* Creates a custom {@link UrlReader} wrapper for your own set of factories.
|
||||
*/
|
||||
static create(options: UrlReadersOptions): UrlReader {
|
||||
const { logger, config, factories } = options;
|
||||
@@ -65,7 +69,8 @@ export class UrlReaders {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a UrlReader that includes all the default factories from this package.
|
||||
* Creates a {@link UrlReader} wrapper that includes all the default factories
|
||||
* from this package.
|
||||
*
|
||||
* Any additional factories passed will be loaded before the default ones.
|
||||
*/
|
||||
|
||||
@@ -281,7 +281,12 @@ export type FromReadableArrayOptions = Array<{
|
||||
path: string;
|
||||
}>;
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* A factory for response factories that handle the unpacking and inspection of
|
||||
* complex responses such as archive data.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ReadTreeResponseFactory {
|
||||
fromTarArchive(
|
||||
options: ReadTreeResponseFactoryOptions,
|
||||
|
||||
@@ -320,4 +320,21 @@ describe('Git', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('log', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const ref = 'as43bd7';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.log({ dir, ref });
|
||||
|
||||
expect(isomorphic.log).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
ref,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,11 @@ From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub
|
||||
Azure 'notempty' token
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* A convenience wrapper around the `isomorphic-git` library.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class Git {
|
||||
private constructor(
|
||||
private readonly config: {
|
||||
@@ -76,12 +80,15 @@ export class Git {
|
||||
return git.commit({ fs, dir, message, author, committer });
|
||||
}
|
||||
|
||||
/** https://isomorphic-git.org/docs/en/clone */
|
||||
async clone(options: {
|
||||
url: string;
|
||||
dir: string;
|
||||
ref?: string;
|
||||
depth?: number;
|
||||
noCheckout?: boolean;
|
||||
}): Promise<void> {
|
||||
const { url, dir, ref } = options;
|
||||
const { url, dir, ref, depth, noCheckout } = options;
|
||||
this.config.logger?.info(`Cloning repo {dir=${dir},url=${url}}`);
|
||||
return git.clone({
|
||||
fs,
|
||||
@@ -90,7 +97,8 @@ export class Git {
|
||||
dir,
|
||||
ref,
|
||||
singleBranch: true,
|
||||
depth: 1,
|
||||
depth: depth ?? 1,
|
||||
noCheckout,
|
||||
onProgress: this.onProgressHandler(),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
@@ -99,7 +107,7 @@ export class Git {
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/currentBranch
|
||||
/** https://isomorphic-git.org/docs/en/currentBranch */
|
||||
async currentBranch(options: {
|
||||
dir: string;
|
||||
fullName?: boolean;
|
||||
@@ -110,7 +118,7 @@ export class Git {
|
||||
>;
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/fetch
|
||||
/** https://isomorphic-git.org/docs/en/fetch */
|
||||
async fetch(options: { dir: string; remote?: string }): Promise<void> {
|
||||
const { dir, remote = 'origin' } = options;
|
||||
this.config.logger?.info(
|
||||
@@ -138,7 +146,7 @@ export class Git {
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/merge
|
||||
/** https://isomorphic-git.org/docs/en/merge */
|
||||
async merge(options: {
|
||||
dir: string;
|
||||
theirs: string;
|
||||
@@ -180,7 +188,7 @@ export class Git {
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/readCommit
|
||||
/** https://isomorphic-git.org/docs/en/readCommit */
|
||||
async readCommit(options: {
|
||||
dir: string;
|
||||
sha: string;
|
||||
@@ -189,12 +197,25 @@ export class Git {
|
||||
return git.readCommit({ fs, dir, oid: sha });
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/resolveRef
|
||||
/** https://isomorphic-git.org/docs/en/resolveRef */
|
||||
async resolveRef(options: { dir: string; ref: string }): Promise<string> {
|
||||
const { dir, ref } = options;
|
||||
return git.resolveRef({ fs, dir, ref });
|
||||
}
|
||||
|
||||
/** https://isomorphic-git.org/docs/en/log */
|
||||
async log(options: {
|
||||
dir: string;
|
||||
ref?: string;
|
||||
}): Promise<ReadCommitResult[]> {
|
||||
const { dir, ref } = options;
|
||||
return git.log({
|
||||
fs,
|
||||
dir,
|
||||
ref: ref ?? 'HEAD',
|
||||
});
|
||||
}
|
||||
|
||||
private onAuth = () => ({
|
||||
username: this.config.username,
|
||||
password: this.config.password,
|
||||
|
||||
@@ -19,9 +19,26 @@ import Router from 'express-promise-router';
|
||||
import express from 'express';
|
||||
import { errorHandler, statusCheckHandler, StatusCheck } from '../middleware';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Creates a default status checking router, that you can add to your express
|
||||
* app.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* This adds a `/healthcheck` route (or any other path, if given as an
|
||||
* argument), which your infra can call to see if the service is ready to serve
|
||||
* requests.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export async function createStatusCheckRouter(options: {
|
||||
logger: Logger;
|
||||
/**
|
||||
* The path (including a leading slash) that the health check should be
|
||||
* mounted on.
|
||||
*
|
||||
* @defaultValue '/healthcheck'
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* If not implemented, the default express middleware always returns 200.
|
||||
|
||||
@@ -19,6 +19,7 @@ import compression from 'compression';
|
||||
import cors from 'cors';
|
||||
import express, { Router, ErrorRequestHandler } from 'express';
|
||||
import helmet from 'helmet';
|
||||
import { ContentSecurityPolicyOptions } from 'helmet/dist/middlewares/content-security-policy';
|
||||
import * as http from 'http';
|
||||
import stoppable from 'stoppable';
|
||||
import { Logger } from 'winston';
|
||||
@@ -43,19 +44,6 @@ import { createHttpServer, createHttpsServer } from './hostFactory';
|
||||
export const DEFAULT_PORT = 7007;
|
||||
// '' is express default, which listens to all interfaces
|
||||
const DEFAULT_HOST = '';
|
||||
// taken from the helmet source code - don't seem to be exported
|
||||
const DEFAULT_CSP = {
|
||||
'default-src': ["'self'"],
|
||||
'base-uri': ["'self'"],
|
||||
'block-all-mixed-content': [],
|
||||
'font-src': ["'self'", 'https:', 'data:'],
|
||||
'frame-ancestors': ["'self'"],
|
||||
'img-src': ["'self'", 'data:'],
|
||||
'object-src': ["'none'"],
|
||||
'script-src': ["'self'", "'unsafe-eval'"],
|
||||
'script-src-attr': ["'none'"],
|
||||
'style-src': ["'self'", 'https:', "'unsafe-inline'"],
|
||||
};
|
||||
|
||||
export class ServiceBuilderImpl implements ServiceBuilder {
|
||||
private port: number | undefined;
|
||||
@@ -236,8 +224,13 @@ export class ServiceBuilderImpl implements ServiceBuilder {
|
||||
|
||||
export function applyCspDirectives(
|
||||
directives: Record<string, string[] | false> | undefined,
|
||||
): CspOptions | undefined {
|
||||
const result: CspOptions = { ...DEFAULT_CSP };
|
||||
): ContentSecurityPolicyOptions['directives'] {
|
||||
const result: ContentSecurityPolicyOptions['directives'] =
|
||||
helmet.contentSecurityPolicy.getDefaultDirectives();
|
||||
|
||||
// TODO(Rugvip): We currently use non-precompiled AJV for validation in the frontend, which uses eval.
|
||||
// It should be replaced by any other solution that doesn't require unsafe-eval.
|
||||
result['script-src'] = ["'self'", "'unsafe-eval'"];
|
||||
|
||||
if (directives) {
|
||||
for (const [key, value] of Object.entries(directives)) {
|
||||
|
||||
@@ -42,8 +42,6 @@ export type CertificateAttributes = {
|
||||
|
||||
/**
|
||||
* A map from CSP directive names to their values.
|
||||
*
|
||||
* Added here since helmet doesn't export this type publicly.
|
||||
*/
|
||||
export type CspOptions = Record<string, string[]>;
|
||||
|
||||
@@ -57,7 +55,7 @@ type CustomOrigin = (
|
||||
/**
|
||||
* Reads some base options out of a config object.
|
||||
*
|
||||
* @param config The root of a backend config object
|
||||
* @param config - The root of a backend config object
|
||||
* @returns A base options object
|
||||
*
|
||||
* @example
|
||||
@@ -100,7 +98,7 @@ export function readBaseOptions(config: Config): BaseOptions {
|
||||
/**
|
||||
* Attempts to read a CORS options object from the root of a config object.
|
||||
*
|
||||
* @param config The root of a backend config object
|
||||
* @param config - The root of a backend config object
|
||||
* @returns A CORS options object, or undefined if not specified
|
||||
*
|
||||
* @example
|
||||
@@ -134,7 +132,7 @@ export function readCorsOptions(config: Config): CorsOptions | undefined {
|
||||
/**
|
||||
* Attempts to read a CSP options object from the root of a config object.
|
||||
*
|
||||
* @param config The root of a backend config object
|
||||
* @param config - The root of a backend config object
|
||||
* @returns A CSP options object, or undefined if not specified. Values can be
|
||||
* false as well, which means to remove the default behavior for that
|
||||
* key.
|
||||
@@ -170,7 +168,7 @@ export function readCspOptions(
|
||||
/**
|
||||
* Attempts to read a https settings object from the root of a config object.
|
||||
*
|
||||
* @param config The root of a backend config object
|
||||
* @param config - The root of a backend config object
|
||||
* @returns A https settings object, or undefined if not specified
|
||||
*
|
||||
* @example
|
||||
|
||||
@@ -29,8 +29,8 @@ const IP_HOSTNAME_REGEX = /:|^\d+\.\d+\.\d+\.\d+$/;
|
||||
/**
|
||||
* Creates a Http server instance based on an Express application.
|
||||
*
|
||||
* @param app The Express application object
|
||||
* @param logger Optional Winston logger object
|
||||
* @param app - The Express application object
|
||||
* @param logger - Optional Winston logger object
|
||||
* @returns A Http server instance
|
||||
*
|
||||
*/
|
||||
@@ -46,9 +46,9 @@ export function createHttpServer(
|
||||
/**
|
||||
* Creates a Https server instance based on an Express application.
|
||||
*
|
||||
* @param app The Express application object
|
||||
* @param httpsSettings HttpsSettings for self-signed certificate generation
|
||||
* @param logger Optional Winston logger object
|
||||
* @param app - The Express application object
|
||||
* @param httpsSettings - HttpsSettings for self-signed certificate generation
|
||||
* @param logger - Optional Winston logger object
|
||||
* @returns A Https server instance
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,11 @@ import { Router, RequestHandler, ErrorRequestHandler } from 'express';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* A helper for building backend service instances.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ServiceBuilder = {
|
||||
/**
|
||||
* Sets the service parameters based on configuration.
|
||||
@@ -119,5 +123,9 @@ export type ServiceBuilder = {
|
||||
start(): Promise<Server>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* A factory for request loggers.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type RequestLoggingHandlerFactory = (logger?: Logger) => RequestHandler;
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
import { Writable } from 'stream';
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Options passed to the {@link ContainerRunner.runContainer} method.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type RunContainerOptions = {
|
||||
imageName: string;
|
||||
command?: string | string[];
|
||||
@@ -28,7 +32,14 @@ export type RunContainerOptions = {
|
||||
pullImage?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* Handles the running of containers, on behalf of others.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ContainerRunner {
|
||||
/**
|
||||
* Runs a container image to completion.
|
||||
*/
|
||||
runContainer(opts: RunContainerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,11 @@ export type UserOptions = {
|
||||
User?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* A {@link ContainerRunner} for Docker containers.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class DockerContainerRunner implements ContainerRunner {
|
||||
private readonly dockerClient: Docker;
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @backstage/backend-tasks
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e188b37024: Updated README to clarify the following:
|
||||
|
||||
- Dashes cannot be used in the `id`, changed to an underscore in the example
|
||||
- The `timeout` is required, this was also added to the example
|
||||
- Added a note about tasks not running as expected for local development using persistent database
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.10.2
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -21,14 +21,19 @@ import { Duration } from 'luxon';
|
||||
const scheduler = TaskScheduler.fromConfig(rootConfig).forPlugin('my-plugin');
|
||||
|
||||
await scheduler.scheduleTask({
|
||||
id: 'refresh-things',
|
||||
id: 'refresh_things',
|
||||
frequency: Duration.fromObject({ minutes: 10 }),
|
||||
timeout: Duration.fromObject({ minutes: 15 }),
|
||||
fn: async () => {
|
||||
await entityProvider.run();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Local Development
|
||||
|
||||
When working with the `@backstage/backend-tasks` library you may run into your task not running immediately at startup as expected if you are using a persistent database. This is by design - the library respects the previous state and does not run the task sooner than the specified frequency. If you want to get around this, there is a table called `backstage_backend_tasks__tasks` in the applicable plugin's database which will contain a record with the next run date and time. You can delete this record to get things back to what you expect.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-tasks",
|
||||
"description": "Common distributed task management library for Backstage backends",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -29,7 +29,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.10.0",
|
||||
"@backstage/backend-common": "^0.10.2",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/errors": "^0.1.4",
|
||||
"@backstage/types": "^0.1.1",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^0.1.11",
|
||||
"@backstage/cli": "^0.10.3",
|
||||
"@backstage/cli": "^0.10.5",
|
||||
"jest": "^26.0.1",
|
||||
"wait-for-expect": "^3.0.2"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/backend-test-utils
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 130b7aadf2: Lazy-load `testcontainers` module in order to avoid side-effects.
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.10.1
|
||||
- @backstage/cli@0.10.4
|
||||
|
||||
## 0.1.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-test-utils",
|
||||
"description": "Test helpers library for Backstage backends",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.12",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -30,18 +30,18 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.10.0",
|
||||
"@backstage/cli": "^0.10.3",
|
||||
"@backstage/backend-common": "^0.10.1",
|
||||
"@backstage/cli": "^0.10.4",
|
||||
"@backstage/config": "^0.1.9",
|
||||
"knex": "^0.95.1",
|
||||
"mysql2": "^2.2.5",
|
||||
"pg": "^8.3.0",
|
||||
"sqlite3": "^5.0.1",
|
||||
"testcontainers": "^7.23.0",
|
||||
"testcontainers": "^8.1.2",
|
||||
"uuid": "^8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.3",
|
||||
"@backstage/cli": "^0.10.4",
|
||||
"jest": "^26.0.1"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import createConnection, { Knex } from 'knex';
|
||||
import { GenericContainer } from 'testcontainers';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
async function waitForMysqlReady(
|
||||
@@ -50,6 +49,9 @@ export async function startMysqlContainer(image: string) {
|
||||
const user = 'root';
|
||||
const password = uuid();
|
||||
|
||||
// Lazy-load to avoid side-effect of importing testcontainers
|
||||
const { GenericContainer } = await import('testcontainers');
|
||||
|
||||
const container = await new GenericContainer(image)
|
||||
.withExposedPorts(3306)
|
||||
.withEnv('MYSQL_ROOT_PASSWORD', password)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import createConnection, { Knex } from 'knex';
|
||||
import { GenericContainer } from 'testcontainers';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
async function waitForPostgresReady(
|
||||
@@ -50,6 +49,9 @@ export async function startPostgresContainer(image: string) {
|
||||
const user = 'postgres';
|
||||
const password = uuid();
|
||||
|
||||
// Lazy-load to avoid side-effect of importing testcontainers
|
||||
const { GenericContainer } = await import('testcontainers');
|
||||
|
||||
const container = await new GenericContainer(image)
|
||||
.withExposedPorts(5432)
|
||||
.withEnv('POSTGRES_PASSWORD', password)
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# example-backend
|
||||
|
||||
## 0.2.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-rollbar-backend@0.1.18
|
||||
- @backstage/plugin-auth-backend@0.6.0
|
||||
- @backstage/backend-common@0.10.1
|
||||
- @backstage/plugin-app-backend@0.3.21
|
||||
- @backstage/plugin-catalog-backend@0.19.4
|
||||
- @backstage/plugin-scaffolder-backend@0.15.19
|
||||
- @backstage/integration@0.7.0
|
||||
- @backstage/plugin-techdocs-backend@0.12.2
|
||||
- @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.4
|
||||
- @backstage/plugin-permission-backend@0.2.3
|
||||
- @backstage/plugin-permission-node@0.2.3
|
||||
- @backstage/plugin-code-coverage-backend@0.1.18
|
||||
- @backstage/plugin-scaffolder-backend-module-rails@0.2.2
|
||||
- @backstage/plugin-todo-backend@0.1.17
|
||||
|
||||
## 0.2.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.2.58",
|
||||
"version": "0.2.59",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -24,37 +24,38 @@
|
||||
"migrate:create": "knex migrate:make -x ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.10.0",
|
||||
"@backstage/backend-common": "^0.10.1",
|
||||
"@backstage/backend-tasks": "^0.1.0",
|
||||
"@backstage/catalog-client": "^0.5.3",
|
||||
"@backstage/catalog-model": "^0.9.8",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/integration": "^0.6.10",
|
||||
"@backstage/plugin-app-backend": "^0.3.20",
|
||||
"@backstage/plugin-auth-backend": "^0.5.2",
|
||||
"@backstage/integration": "^0.7.0",
|
||||
"@backstage/plugin-app-backend": "^0.3.21",
|
||||
"@backstage/plugin-auth-backend": "^0.6.0",
|
||||
"@backstage/plugin-azure-devops-backend": "^0.2.6",
|
||||
"@backstage/plugin-badges-backend": "^0.1.14",
|
||||
"@backstage/plugin-catalog-backend": "^0.19.3",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.1.17",
|
||||
"@backstage/plugin-catalog-backend": "^0.19.4",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.1.18",
|
||||
"@backstage/plugin-graphql-backend": "^0.1.10",
|
||||
"@backstage/plugin-jenkins-backend": "^0.1.10",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.4.1",
|
||||
"@backstage/plugin-kafka-backend": "^0.2.13",
|
||||
"@backstage/plugin-permission-backend": "^0.2.2",
|
||||
"@backstage/plugin-permission-backend": "^0.2.3",
|
||||
"@backstage/plugin-permission-common": "^0.3.0",
|
||||
"@backstage/plugin-permission-node": "^0.2.2",
|
||||
"@backstage/plugin-permission-node": "^0.2.3",
|
||||
"@backstage/plugin-proxy-backend": "^0.2.15",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.17",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.15.18",
|
||||
"@backstage/plugin-scaffolder-backend-module-rails": "^0.2.1",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.18",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.15.19",
|
||||
"@backstage/plugin-scaffolder-backend-module-rails": "^0.2.2",
|
||||
"@backstage/plugin-search-backend": "^0.3.0",
|
||||
"@backstage/plugin-search-backend-node": "^0.4.2",
|
||||
"@backstage/plugin-search-backend-module-elasticsearch": "^0.0.7",
|
||||
"@backstage/plugin-search-backend-module-pg": "^0.2.3",
|
||||
"@backstage/plugin-techdocs-backend": "^0.12.1",
|
||||
"@backstage/plugin-techdocs-backend": "^0.12.2",
|
||||
"@backstage/plugin-tech-insights-backend": "^0.1.4",
|
||||
"@backstage/plugin-tech-insights-node": "^0.1.2",
|
||||
"@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.3",
|
||||
"@backstage/plugin-todo-backend": "^0.1.16",
|
||||
"@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.4",
|
||||
"@backstage/plugin-todo-backend": "^0.1.17",
|
||||
"@gitbeaker/node": "^34.6.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
@@ -71,7 +72,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.3",
|
||||
"@backstage/cli": "^0.10.4",
|
||||
"@types/dockerode": "^3.3.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express-serve-static-core": "^4.17.5"
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
useHotMemoize,
|
||||
ServerTokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { TaskScheduler } from '@backstage/backend-tasks';
|
||||
import { Config } from '@backstage/config';
|
||||
import healthcheck from './plugins/healthcheck';
|
||||
import { metricsInit, metricsHandler } from './metrics';
|
||||
@@ -68,16 +69,17 @@ function makeCreateEnv(config: Config) {
|
||||
discovery,
|
||||
tokenManager,
|
||||
});
|
||||
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
const databaseManager = DatabaseManager.fromConfig(config);
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
const taskScheduler = TaskScheduler.fromConfig(config);
|
||||
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
return (plugin: string): PluginEnvironment => {
|
||||
const logger = root.child({ type: 'plugin', plugin });
|
||||
const database = databaseManager.forPlugin(plugin);
|
||||
const cache = cacheManager.forPlugin(plugin);
|
||||
const scheduler = taskScheduler.forPlugin(plugin);
|
||||
return {
|
||||
logger,
|
||||
cache,
|
||||
@@ -87,6 +89,7 @@ function makeCreateEnv(config: Config) {
|
||||
discovery,
|
||||
tokenManager,
|
||||
permissions,
|
||||
scheduler,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
* Copyright 2020 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.
|
||||
|
||||
@@ -41,11 +41,11 @@ export default async function createPlugin({
|
||||
discovery,
|
||||
factRetrievers: [
|
||||
createFactRetrieverRegistration(
|
||||
'* * * * *', // Example cron, every minute
|
||||
'1 1 1 * *', // Example cron, At 01:01 on day-of-month 1.
|
||||
entityOwnershipFactRetriever,
|
||||
),
|
||||
createFactRetrieverRegistration('* * * * *', entityMetadataFactRetriever),
|
||||
createFactRetrieverRegistration('* * * * *', techdocsFactRetriever),
|
||||
createFactRetrieverRegistration('1 1 1 * *', entityMetadataFactRetriever),
|
||||
createFactRetrieverRegistration('1 1 1 * *', techdocsFactRetriever),
|
||||
],
|
||||
factCheckerFactory: new JsonRulesEngineFactCheckerFactory({
|
||||
checks: [
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
UrlReader,
|
||||
} from '@backstage/backend-common';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
|
||||
export type PluginEnvironment = {
|
||||
logger: Logger;
|
||||
@@ -34,4 +35,5 @@ export type PluginEnvironment = {
|
||||
discovery: PluginEndpointDiscovery;
|
||||
tokenManager: TokenManager;
|
||||
permissions: ServerPermissionClient;
|
||||
scheduler: PluginTaskScheduler;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.10.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 37123b4851: Bump `css-loader` from `5.2.6` to `6.5.1`
|
||||
- 9534391ae4: Add --pattern option to override matching glob patterns for backstage versioning
|
||||
- 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead.
|
||||
|
||||
## 0.10.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1d26017090: Fix issue with plugin:serve for Plugins not using Lerna monorepo.
|
||||
- 9c3aaf3512: Bump `@typescript-eslint/eslint-plugin` to `4.33.0`
|
||||
- 808828e4fa: remove inline CSS from serve_index.html
|
||||
- 6e4080d31b: Add option to build command for minifying the generated code
|
||||
- Updated dependencies
|
||||
- @backstage/config-loader@0.9.1
|
||||
|
||||
## 0.10.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -80,6 +80,10 @@ module.exports = {
|
||||
name: '@material-ui/icons',
|
||||
message: "Please import '@material-ui/icons/<Icon>' instead.",
|
||||
},
|
||||
{
|
||||
name: '@material-ui/icons/', // because this is possible too ._.
|
||||
message: "Please import '@material-ui/icons/<Icon>' instead.",
|
||||
},
|
||||
...require('module').builtinModules,
|
||||
],
|
||||
// Avoid cross-package imports
|
||||
|
||||
+22
-20
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.10.3",
|
||||
"version": "0.10.5",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -30,12 +30,11 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.6",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/config-loader": "^0.9.0",
|
||||
"@backstage/config-loader": "^0.9.1",
|
||||
"@backstage/errors": "^0.1.5",
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^4.0.0",
|
||||
"@lerna/project": "^4.0.0",
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@octokit/request": "^5.4.12",
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
@@ -51,26 +50,27 @@
|
||||
"@svgr/rollup": "5.5.x",
|
||||
"@svgr/webpack": "5.5.x",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@typescript-eslint/eslint-plugin": "^v4.30.0",
|
||||
"@typescript-eslint/parser": "^v4.28.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
||||
"@typescript-eslint/parser": "^5.9.0",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"bfj": "^7.0.2",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.0.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"commander": "^6.1.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"css-loader": "^6.5.1",
|
||||
"diff": "^5.0.0",
|
||||
"esbuild": "^0.14.1",
|
||||
"eslint": "^7.30.0",
|
||||
"esbuild": "^0.14.10",
|
||||
"esbuild-loader": "^2.18.0",
|
||||
"eslint": "^8.6.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-jest": "^24.1.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.1",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^25.3.4",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-monorepo": "^0.3.2",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"express": "^4.17.1",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.5",
|
||||
"fs-extra": "9.1.0",
|
||||
@@ -83,6 +83,7 @@
|
||||
"json-schema": "^0.4.0",
|
||||
"jest-transform-yaml": "^0.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "3.0.4",
|
||||
"mini-css-extract-plugin": "^2.4.2",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"ora": "^5.3.0",
|
||||
@@ -114,19 +115,20 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.10.0",
|
||||
"@backstage/backend-common": "^0.10.2",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/core-components": "^0.8.2",
|
||||
"@backstage/core-plugin-api": "^0.4.0",
|
||||
"@backstage/core-app-api": "^0.3.0",
|
||||
"@backstage/dev-utils": "^0.2.15",
|
||||
"@backstage/test-utils": "^0.2.0",
|
||||
"@backstage/core-components": "^0.8.3",
|
||||
"@backstage/core-plugin-api": "^0.4.1",
|
||||
"@backstage/core-app-api": "^0.3.1",
|
||||
"@backstage/dev-utils": "^0.2.16",
|
||||
"@backstage/test-utils": "^0.2.1",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
"@types/diff": "^5.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/inquirer": "^8.1.3",
|
||||
"@types/minimatch": "^3.0.5",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
|
||||
@@ -33,5 +33,5 @@ export default async (cmd: Command) => {
|
||||
outputs = new Set([Output.types, Output.esm, Output.cjs]);
|
||||
}
|
||||
|
||||
await buildPackage({ outputs });
|
||||
await buildPackage({ outputs, minify: cmd.minify });
|
||||
};
|
||||
|
||||
@@ -138,6 +138,7 @@ export function registerCommands(program: CommanderStatic) {
|
||||
.command('build')
|
||||
.description('Build a package for publishing')
|
||||
.option('--outputs <formats>', 'List of formats to output [types,cjs,esm]')
|
||||
.option('--minify', 'Minify the generated code')
|
||||
.action(lazy(() => import('./build').then(m => m.default)));
|
||||
|
||||
program
|
||||
@@ -213,6 +214,10 @@ export function registerCommands(program: CommanderStatic) {
|
||||
|
||||
program
|
||||
.command('versions:bump')
|
||||
.option(
|
||||
'--pattern <glob>',
|
||||
'Override glob for matching packages to upgrade',
|
||||
)
|
||||
.description('Bump Backstage packages to the latest versions')
|
||||
.action(lazy(() => import('./versions/bump').then(m => m.default)));
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { Command } from 'commander';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { mapDependencies } from '../../lib/versioning';
|
||||
import * as runObj from '../../lib/run';
|
||||
import bump, { bumpBackstageJsonVersion } from './bump';
|
||||
import { withLogCollector } from '@backstage/test-utils';
|
||||
@@ -36,6 +36,8 @@ const REGISTRY_VERSIONS: { [name: string]: string } = {
|
||||
'@backstage/core': '1.0.6',
|
||||
'@backstage/core-api': '1.0.7',
|
||||
'@backstage/theme': '2.0.0',
|
||||
'@backstage-extra/custom': '1.1.0',
|
||||
'@backstage-extra/custom-two': '2.0.0',
|
||||
};
|
||||
|
||||
const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
@@ -82,13 +84,12 @@ describe('bump', () => {
|
||||
});
|
||||
|
||||
it('should bump backstage dependencies', async () => {
|
||||
// Make sure all modules involved in package discovery are in the module cache before we mock fs
|
||||
await mapDependencies(paths.targetDir);
|
||||
|
||||
mockFs({
|
||||
'/yarn.lock': lockfileMock,
|
||||
'/lerna.json': JSON.stringify({
|
||||
packages: ['packages/*'],
|
||||
'/package.json': JSON.stringify({
|
||||
workspaces: {
|
||||
packages: ['packages/*'],
|
||||
},
|
||||
}),
|
||||
'/packages/a/package.json': JSON.stringify({
|
||||
name: 'a',
|
||||
@@ -105,7 +106,6 @@ describe('bump', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
paths.targetDir = '/';
|
||||
jest
|
||||
.spyOn(paths, 'resolveTargetRoot')
|
||||
.mockImplementation((...path) => resolvePath('/', ...path));
|
||||
@@ -123,9 +123,10 @@ describe('bump', () => {
|
||||
jest.spyOn(runObj, 'run').mockResolvedValue(undefined);
|
||||
|
||||
const { log: logs } = await withLogCollector(['log'], async () => {
|
||||
await bump();
|
||||
await bump({ pattern: null } as unknown as Command);
|
||||
});
|
||||
expect(logs.filter(Boolean)).toEqual([
|
||||
'Using default pattern glob @backstage/*',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Checking for updates of @backstage/core-api',
|
||||
@@ -178,13 +179,148 @@ describe('bump', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should bump backstage dependencies and dependencies matching pattern glob', async () => {
|
||||
const customLockfileMock = `${lockfileMock}
|
||||
"@backstage-extra/custom@^1.1.0":
|
||||
version "1.1.0"
|
||||
|
||||
"@backstage-extra/custom@^1.0.1":
|
||||
version "1.0.1"
|
||||
|
||||
"@backstage-extra/custom-two@^1.0.0":
|
||||
version "1.0.0"
|
||||
`;
|
||||
const customLockfileMockResult = `${HEADER}
|
||||
"@backstage-extra/custom-two@^1.0.0":
|
||||
version "1.0.0"
|
||||
|
||||
"@backstage-extra/custom@^1.1.0":
|
||||
version "1.1.0"
|
||||
|
||||
"@backstage/core@^1.0.5":
|
||||
version "1.0.6"
|
||||
dependencies:
|
||||
"@backstage/core-api" "^1.0.6"
|
||||
|
||||
"@backstage/theme@^1.0.0":
|
||||
version "1.0.0"
|
||||
`;
|
||||
mockFs({
|
||||
'/yarn.lock': customLockfileMock,
|
||||
'/package.json': JSON.stringify({
|
||||
workspaces: {
|
||||
packages: ['packages/*'],
|
||||
},
|
||||
}),
|
||||
'/packages/a/package.json': JSON.stringify({
|
||||
name: 'a',
|
||||
dependencies: {
|
||||
'@backstage/core': '^1.0.5',
|
||||
'@backstage-extra/custom': '^1.0.1',
|
||||
'@backstage-extra/custom-two': '^1.0.0',
|
||||
},
|
||||
}),
|
||||
'/packages/b/package.json': JSON.stringify({
|
||||
name: 'b',
|
||||
dependencies: {
|
||||
'@backstage/core': '^1.0.3',
|
||||
'@backstage/theme': '^1.0.0',
|
||||
'@backstage-extra/custom': '^1.1.0',
|
||||
'@backstage-extra/custom-two': '^1.0.0',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
jest
|
||||
.spyOn(paths, 'resolveTargetRoot')
|
||||
.mockImplementation((...path) => resolvePath('/', ...path));
|
||||
jest.spyOn(runObj, 'runPlain').mockImplementation(async (...[, , , name]) =>
|
||||
JSON.stringify({
|
||||
type: 'inspect',
|
||||
data: {
|
||||
name: name,
|
||||
'dist-tags': {
|
||||
latest: REGISTRY_VERSIONS[name],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
jest.spyOn(runObj, 'run').mockResolvedValue(undefined);
|
||||
|
||||
const { log: logs } = await withLogCollector(['log'], async () => {
|
||||
await bump({ pattern: '@{backstage,backstage-extra}/*' } as any);
|
||||
});
|
||||
expect(logs.filter(Boolean)).toEqual([
|
||||
'Using custom pattern glob @{backstage,backstage-extra}/*',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage-extra/custom-two',
|
||||
'Checking for updates of @backstage-extra/custom',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Checking for updates of @backstage/core-api',
|
||||
'Some packages are outdated, updating',
|
||||
'unlocking @backstage-extra/custom@^1.0.1 ~> 1.1.0',
|
||||
'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
|
||||
'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
|
||||
'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
|
||||
'bumping @backstage-extra/custom-two in a to ^2.0.0',
|
||||
'bumping @backstage/theme in b to ^2.0.0',
|
||||
'bumping @backstage-extra/custom-two in b to ^2.0.0',
|
||||
'Running yarn install to install new versions',
|
||||
'⚠️ The following packages may have breaking changes:',
|
||||
' @backstage-extra/custom-two : 1.0.0 ~> 2.0.0',
|
||||
' @backstage/theme : 1.0.0 ~> 2.0.0',
|
||||
' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md',
|
||||
'Version bump complete!',
|
||||
]);
|
||||
|
||||
expect(runObj.runPlain).toHaveBeenCalledTimes(6);
|
||||
expect(runObj.runPlain).toHaveBeenCalledWith(
|
||||
'yarn',
|
||||
'info',
|
||||
'--json',
|
||||
'@backstage/core',
|
||||
);
|
||||
expect(runObj.runPlain).toHaveBeenCalledWith(
|
||||
'yarn',
|
||||
'info',
|
||||
'--json',
|
||||
'@backstage/theme',
|
||||
);
|
||||
|
||||
expect(runObj.run).toHaveBeenCalledTimes(1);
|
||||
expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']);
|
||||
|
||||
const lockfileContents = await fs.readFile('/yarn.lock', 'utf8');
|
||||
expect(lockfileContents).toEqual(customLockfileMockResult);
|
||||
|
||||
const packageA = await fs.readJson('/packages/a/package.json');
|
||||
expect(packageA).toEqual({
|
||||
name: 'a',
|
||||
dependencies: {
|
||||
'@backstage-extra/custom': '^1.0.1',
|
||||
'@backstage-extra/custom-two': '^2.0.0',
|
||||
'@backstage/core': '^1.0.5', // not bumped since new version is within range
|
||||
},
|
||||
});
|
||||
const packageB = await fs.readJson('/packages/b/package.json');
|
||||
expect(packageB).toEqual({
|
||||
name: 'b',
|
||||
dependencies: {
|
||||
'@backstage-extra/custom': '^1.1.0',
|
||||
'@backstage-extra/custom-two': '^2.0.0',
|
||||
'@backstage/core': '^1.0.3', // not bumped
|
||||
'@backstage/theme': '^2.0.0', // bumped since newer
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore not found packages', async () => {
|
||||
// Make sure all modules involved in package discovery are in the module cache before we mock fs
|
||||
await mapDependencies(paths.targetDir);
|
||||
mockFs({
|
||||
'/yarn.lock': lockfileMockResult,
|
||||
'/lerna.json': JSON.stringify({
|
||||
packages: ['packages/*'],
|
||||
'/package.json': JSON.stringify({
|
||||
workspaces: {
|
||||
packages: ['packages/*'],
|
||||
},
|
||||
}),
|
||||
'/packages/a/package.json': JSON.stringify({
|
||||
name: 'a',
|
||||
@@ -201,7 +337,6 @@ describe('bump', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
paths.targetDir = '/';
|
||||
jest
|
||||
.spyOn(paths, 'resolveTargetRoot')
|
||||
.mockImplementation((...path) => resolvePath('/', ...path));
|
||||
@@ -209,9 +344,10 @@ describe('bump', () => {
|
||||
jest.spyOn(runObj, 'run').mockResolvedValue(undefined);
|
||||
|
||||
const { log: logs } = await withLogCollector(['log'], async () => {
|
||||
await bump();
|
||||
await bump({ pattern: null } as unknown as Command);
|
||||
});
|
||||
expect(logs.filter(Boolean)).toEqual([
|
||||
'Using default pattern glob @backstage/*',
|
||||
'Checking for updates of @backstage/theme',
|
||||
'Checking for updates of @backstage/core',
|
||||
'Package info not found, ignoring package @backstage/theme',
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import semver from 'semver';
|
||||
import minimatch from 'minimatch';
|
||||
import { Command } from 'commander';
|
||||
import { isError } from '@backstage/errors';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { run } from '../../lib/run';
|
||||
@@ -26,7 +28,7 @@ import {
|
||||
fetchPackageInfo,
|
||||
Lockfile,
|
||||
} from '../../lib/versioning';
|
||||
import { includedFilter, forbiddenDuplicatesFilter } from './lint';
|
||||
import { forbiddenDuplicatesFilter } from './lint';
|
||||
import { BACKSTAGE_JSON } from '@backstage/cli-common';
|
||||
|
||||
const DEP_TYPES = [
|
||||
@@ -36,6 +38,8 @@ const DEP_TYPES = [
|
||||
'optionalDependencies',
|
||||
];
|
||||
|
||||
const DEFAULT_PATTERN_GLOB = '@backstage/*';
|
||||
|
||||
type PkgVersionInfo = {
|
||||
range: string;
|
||||
target: string;
|
||||
@@ -43,14 +47,22 @@ type PkgVersionInfo = {
|
||||
location: string;
|
||||
};
|
||||
|
||||
export default async () => {
|
||||
export default async (cmd: Command) => {
|
||||
const lockfilePath = paths.resolveTargetRoot('yarn.lock');
|
||||
const lockfile = await Lockfile.load(lockfilePath);
|
||||
let pattern = cmd.pattern;
|
||||
|
||||
if (!pattern) {
|
||||
console.log(`Using default pattern glob ${DEFAULT_PATTERN_GLOB}`);
|
||||
pattern = DEFAULT_PATTERN_GLOB;
|
||||
} else {
|
||||
console.log(`Using custom pattern glob ${pattern}`);
|
||||
}
|
||||
|
||||
const findTargetVersion = createVersionFinder();
|
||||
|
||||
// First we discover all Backstage dependencies within our own repo
|
||||
const dependencyMap = await mapDependencies(paths.targetDir);
|
||||
const dependencyMap = await mapDependencies(paths.targetDir, pattern);
|
||||
|
||||
// Next check with the package registry to see which dependency ranges we need to bump
|
||||
const versionBumps = new Map<string, PkgVersionInfo[]>();
|
||||
@@ -88,10 +100,12 @@ export default async () => {
|
||||
}
|
||||
});
|
||||
|
||||
const filter = (name: string) => minimatch(name, pattern);
|
||||
|
||||
// Check for updates of transitive backstage dependencies
|
||||
await workerThreads(16, lockfile.keys(), async name => {
|
||||
// Only check @backstage packages and friends, we don't want this to do a full update of all deps
|
||||
if (!includedFilter(name)) {
|
||||
if (!filter(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -237,7 +251,7 @@ export default async () => {
|
||||
// Finally we make sure the new lockfile doesn't have any duplicates
|
||||
const dedupLockfile = await Lockfile.load(lockfilePath);
|
||||
const result = dedupLockfile.analyze({
|
||||
filter: includedFilter,
|
||||
filter,
|
||||
});
|
||||
|
||||
if (result.newVersions.length > 0) {
|
||||
|
||||
@@ -26,12 +26,18 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
path.resolve(root, 'repo/node_modules'),
|
||||
[
|
||||
{
|
||||
name: 'a',
|
||||
location: path.resolve(root, 'external-a'),
|
||||
dir: path.resolve(root, 'external-a'),
|
||||
packageJson: {
|
||||
name: 'a',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '@s/b',
|
||||
location: path.resolve(root, 'external-b'),
|
||||
dir: path.resolve(root, 'external-b'),
|
||||
packageJson: {
|
||||
name: '@s/b',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { WebpackPluginInstance } from 'webpack';
|
||||
import { isChildPath } from '@backstage/cli-common';
|
||||
import { LernaPackage } from './types';
|
||||
import { Package } from '@manypkg/get-packages';
|
||||
|
||||
// Enables proper resolution of packages when linking in external packages.
|
||||
// Without this the packages would depend on dependencies in the node_modules
|
||||
@@ -25,7 +25,7 @@ import { LernaPackage } from './types';
|
||||
export class LinkedPackageResolvePlugin implements WebpackPluginInstance {
|
||||
constructor(
|
||||
private readonly targetModules: string,
|
||||
private readonly packages: LernaPackage[],
|
||||
private readonly packages: Package[],
|
||||
) {}
|
||||
|
||||
apply(resolver: any) {
|
||||
@@ -41,7 +41,7 @@ export class LinkedPackageResolvePlugin implements WebpackPluginInstance {
|
||||
callback: () => void,
|
||||
) => {
|
||||
const pkg = this.packages.find(
|
||||
pkge => data.path && isChildPath(pkge.location, data.path),
|
||||
pkge => data.path && isChildPath(pkge.dir, data.path),
|
||||
);
|
||||
if (!pkg) {
|
||||
callback();
|
||||
@@ -51,14 +51,14 @@ export class LinkedPackageResolvePlugin implements WebpackPluginInstance {
|
||||
// pkg here is an external package. We rewrite the context of any imports to resolve
|
||||
// from the location of the package within the node_modules of the target root rather
|
||||
// than the real location of the external package.
|
||||
const modulesLocation = resolvePath(this.targetModules, pkg.name);
|
||||
const modulesLocation = resolvePath(
|
||||
this.targetModules,
|
||||
pkg.packageJson.name,
|
||||
);
|
||||
const newContext = data.context?.issuer
|
||||
? {
|
||||
...data.context,
|
||||
issuer: data.context.issuer.replace(
|
||||
pkg.location,
|
||||
modulesLocation,
|
||||
),
|
||||
issuer: data.context.issuer.replace(pkg.dir, modulesLocation),
|
||||
}
|
||||
: data.context;
|
||||
|
||||
@@ -70,7 +70,7 @@ export class LinkedPackageResolvePlugin implements WebpackPluginInstance {
|
||||
{
|
||||
...data,
|
||||
context: newContext,
|
||||
path: data.path && data.path.replace(pkg.location, modulesLocation),
|
||||
path: data.path && data.path.replace(pkg.dir, modulesLocation),
|
||||
},
|
||||
`resolve ${data.request} in ${modulesLocation}`,
|
||||
context,
|
||||
|
||||
@@ -24,12 +24,13 @@ 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 { getPackages } from '@manypkg/get-packages';
|
||||
import { optimization } from './optimization';
|
||||
import { Config } from '@backstage/config';
|
||||
import { BundlingPaths } from './paths';
|
||||
import { transforms } from './transforms';
|
||||
import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin';
|
||||
import { BundlingOptions, BackendBundlingOptions, LernaPackage } from './types';
|
||||
import { BundlingOptions, BackendBundlingOptions } from './types';
|
||||
import { version } from '../../lib/version';
|
||||
import { paths as cliPaths } from '../../lib/paths';
|
||||
import { runPlain } from '../run';
|
||||
@@ -74,25 +75,17 @@ async function readBuildInfo() {
|
||||
};
|
||||
}
|
||||
|
||||
async function loadLernaPackages(): Promise<LernaPackage[]> {
|
||||
const { Project } = require('@lerna/project');
|
||||
const project = new Project(cliPaths.targetDir);
|
||||
return project.getPackages();
|
||||
}
|
||||
|
||||
export async function createConfig(
|
||||
paths: BundlingPaths,
|
||||
options: BundlingOptions,
|
||||
): Promise<webpack.Configuration> {
|
||||
const { checksEnabled, isDev, frontendConfig } = options;
|
||||
|
||||
const packages = await loadLernaPackages();
|
||||
const { plugins, loaders } = transforms(options);
|
||||
// Any package that is part of the monorepo but outside the monorepo root dir need
|
||||
// separate resolution logic.
|
||||
const externalPkgs = packages.filter(
|
||||
p => !isChildPath(paths.root, p.location),
|
||||
);
|
||||
const { packages } = await getPackages(cliPaths.targetDir);
|
||||
const externalPkgs = packages.filter(p => !isChildPath(paths.root, p.dir));
|
||||
|
||||
const baseUrl = frontendConfig.getString('app.baseUrl');
|
||||
const validBaseUrl = new URL(baseUrl);
|
||||
@@ -253,14 +246,11 @@ export async function createBackendConfig(
|
||||
const { checksEnabled, isDev } = options;
|
||||
|
||||
// Find all local monorepo packages and their node_modules, and mark them as external.
|
||||
const packages = await await loadLernaPackages();
|
||||
const localPackageNames = packages.map((p: any) => p.name);
|
||||
const moduleDirs = packages.map((p: any) =>
|
||||
resolvePath(p.location, 'node_modules'),
|
||||
);
|
||||
const externalPkgs = packages.filter(
|
||||
p => !isChildPath(paths.root, p.location),
|
||||
); // See frontend config
|
||||
const { packages } = await getPackages(cliPaths.targetDir);
|
||||
const localPackageNames = packages.map(p => p.packageJson.name);
|
||||
const moduleDirs = packages.map(p => resolvePath(p.dir, 'node_modules'));
|
||||
// See frontend config
|
||||
const externalPkgs = packages.filter(p => !isChildPath(paths.root, p.dir));
|
||||
|
||||
const { loaders } = transforms(options);
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { WebpackOptionsNormalized, WebpackPluginInstance } from 'webpack';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import { WebpackOptionsNormalized } from 'webpack';
|
||||
import { BundlingOptions } from './types';
|
||||
import { isParallelDefault } from '../parallel';
|
||||
|
||||
const { ESBuildMinifyPlugin } = require('esbuild-loader');
|
||||
|
||||
export const optimization = (
|
||||
options: BundlingOptions,
|
||||
@@ -26,16 +26,11 @@ export const optimization = (
|
||||
|
||||
return {
|
||||
minimize: !isDev,
|
||||
// Only configure when parallel is explicitly overridden from the default
|
||||
...(!isParallelDefault(options.parallel)
|
||||
? {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: options.parallel,
|
||||
}) as unknown as WebpackPluginInstance,
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
minimizer: [
|
||||
new ESBuildMinifyPlugin({
|
||||
target: 'es2019',
|
||||
}),
|
||||
],
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
automaticNameDelimiter: '-',
|
||||
|
||||
@@ -55,8 +55,3 @@ export type BackendServeOptions = BundlingPathsOptions & {
|
||||
inspectEnabled: boolean;
|
||||
inspectBrkEnabled: boolean;
|
||||
};
|
||||
|
||||
export type LernaPackage = {
|
||||
name: string;
|
||||
location: string;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { paths } from './paths';
|
||||
import { isValidUrl } from './urls';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { PackageGraph } from './monorepo';
|
||||
|
||||
type Options = {
|
||||
args: string[];
|
||||
@@ -40,13 +42,28 @@ export async function loadCliConfig(options: Options) {
|
||||
});
|
||||
|
||||
// Consider all packages in the monorepo when loading in config
|
||||
const { Project } = require('@lerna/project');
|
||||
const project = new Project(paths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
const { packages } = await getPackages(paths.targetDir);
|
||||
|
||||
const localPackageNames = options.fromPackage
|
||||
? findPackages(packages, options.fromPackage)
|
||||
: packages.map((p: any) => p.name);
|
||||
let localPackageNames;
|
||||
if (options.fromPackage) {
|
||||
if (packages.length) {
|
||||
const graph = PackageGraph.fromPackages(packages);
|
||||
localPackageNames = Array.from(
|
||||
graph.collectPackageNames([options.fromPackage], node => {
|
||||
// Workaround for Backstage main repo only, since the CLI has some artificial devDependencies
|
||||
if (node.name === '@backstage/cli') {
|
||||
return undefined;
|
||||
}
|
||||
return node.localDependencies.keys();
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// No packages: it means that it's not a monorepo (e.g. standalone plugin)
|
||||
localPackageNames = [options.fromPackage];
|
||||
}
|
||||
} else {
|
||||
localPackageNames = packages.map(p => p.packageJson.name);
|
||||
}
|
||||
|
||||
const schema = await loadConfigSchema({
|
||||
dependencies: localPackageNames,
|
||||
@@ -92,34 +109,3 @@ export async function loadCliConfig(options: Options) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function findPackages(packages: any[], fromPackage: string): string[] {
|
||||
const { PackageGraph } = require('@lerna/package-graph');
|
||||
|
||||
const graph = new PackageGraph(packages);
|
||||
|
||||
const targets = new Set<string>();
|
||||
const searchNames = [fromPackage];
|
||||
|
||||
while (searchNames.length) {
|
||||
const name = searchNames.pop()!;
|
||||
|
||||
if (targets.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = graph.get(name);
|
||||
if (!node) {
|
||||
throw new Error(`Package '${name}' not found`);
|
||||
}
|
||||
|
||||
targets.add(name);
|
||||
|
||||
// Workaround for Backstage main repo only, since the CLI has some artificial devDependencies
|
||||
if (name !== '@backstage/cli') {
|
||||
searchNames.push(...node.localDependencies.keys());
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(targets);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2020 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 { getPackages } from '@manypkg/get-packages';
|
||||
import { paths } from '../paths';
|
||||
import { PackageGraph } from './PackageGraph';
|
||||
|
||||
const testPackages = [
|
||||
{
|
||||
dir: '/packages/a',
|
||||
packageJson: {
|
||||
name: 'a',
|
||||
version: '1.0.0',
|
||||
dependencies: {
|
||||
b: '1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
c: '1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dir: '/packages/b',
|
||||
packageJson: {
|
||||
name: 'b',
|
||||
version: '1.0.0',
|
||||
devDependencies: {
|
||||
c: '1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dir: '/packages/c',
|
||||
packageJson: {
|
||||
name: 'c',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('PackageGraph', () => {
|
||||
it('is able to construct a graph from this repo', async () => {
|
||||
const { packages } = await getPackages(paths.ownDir);
|
||||
const graph = PackageGraph.fromPackages(packages);
|
||||
expect(graph.has('@backstage/cli')).toBe(true);
|
||||
});
|
||||
|
||||
it('creates a graph', () => {
|
||||
const graph = PackageGraph.fromPackages(testPackages);
|
||||
const a = graph.get('a');
|
||||
const b = graph.get('b');
|
||||
const c = graph.get('c');
|
||||
|
||||
expect(a).toMatchObject({
|
||||
name: 'a',
|
||||
dir: '/packages/a',
|
||||
allLocalDependencies: new Map([
|
||||
['b', b],
|
||||
['c', c],
|
||||
]),
|
||||
publishedLocalDependencies: new Map([['b', b]]),
|
||||
localDependencies: new Map([['b', b]]),
|
||||
localDevDependencies: new Map([['c', c]]),
|
||||
localOptionalDependencies: new Map(),
|
||||
});
|
||||
expect(b).toMatchObject({
|
||||
name: 'b',
|
||||
dir: '/packages/b',
|
||||
allLocalDependencies: new Map([['c', c]]),
|
||||
publishedLocalDependencies: new Map(),
|
||||
localDependencies: new Map(),
|
||||
localDevDependencies: new Map([['c', c]]),
|
||||
localOptionalDependencies: new Map(),
|
||||
});
|
||||
expect(c).toMatchObject({
|
||||
name: 'c',
|
||||
dir: '/packages/c',
|
||||
allLocalDependencies: new Map(),
|
||||
publishedLocalDependencies: new Map(),
|
||||
localDependencies: new Map(),
|
||||
localDevDependencies: new Map(),
|
||||
localOptionalDependencies: new Map(),
|
||||
});
|
||||
});
|
||||
|
||||
it('collects package names', () => {
|
||||
const graph = PackageGraph.fromPackages(testPackages);
|
||||
|
||||
expect(graph.collectPackageNames(['a'], () => undefined)).toEqual(
|
||||
new Set(['a']),
|
||||
);
|
||||
expect(
|
||||
graph.collectPackageNames(['a'], pkg => pkg.localDependencies.keys()),
|
||||
).toEqual(new Set(['a', 'b']));
|
||||
expect(
|
||||
graph.collectPackageNames(['a'], pkg => pkg.localDevDependencies.keys()),
|
||||
).toEqual(new Set(['a', 'c']));
|
||||
expect(
|
||||
graph.collectPackageNames(['b', 'a'], pkg =>
|
||||
pkg.localDevDependencies.keys(),
|
||||
),
|
||||
).toEqual(new Set(['b', 'a', 'c']));
|
||||
|
||||
// Should not get stuck in cycles
|
||||
expect(graph.collectPackageNames(['a'], () => ['a', 'b', 'c'])).toEqual(
|
||||
new Set(['b', 'a', 'c']),
|
||||
);
|
||||
|
||||
// Throws on unknown packages
|
||||
expect(() => graph.collectPackageNames(['a'], () => ['unknown'])).toThrow(
|
||||
`Package 'unknown' not found`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2020 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 { Package } from '@manypkg/get-packages';
|
||||
|
||||
type PackageJSON = Package['packageJson'];
|
||||
|
||||
export interface ExtendedPackageJSON extends PackageJSON {
|
||||
scripts?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
// The `bundled` field is a field known within Backstage, it means
|
||||
// that the package bundles all of its dependencies in its build output.
|
||||
bundled?: boolean;
|
||||
}
|
||||
|
||||
export type PackageGraphNode = {
|
||||
/** The name of the package */
|
||||
name: string;
|
||||
/** The directory of the package */
|
||||
dir: string;
|
||||
/** The package data of the package itself */
|
||||
packageJson: ExtendedPackageJSON;
|
||||
/** All direct local dependencies of the package */
|
||||
allLocalDependencies: Map<string, PackageGraphNode>;
|
||||
/** All direct local dependencies that will be present in the published package */
|
||||
publishedLocalDependencies: Map<string, PackageGraphNode>;
|
||||
/** Local dependencies */
|
||||
localDependencies: Map<string, PackageGraphNode>;
|
||||
/** Local devDependencies */
|
||||
localDevDependencies: Map<string, PackageGraphNode>;
|
||||
/** Local optionalDependencies */
|
||||
localOptionalDependencies: Map<string, PackageGraphNode>;
|
||||
};
|
||||
|
||||
export class PackageGraph extends Map<string, PackageGraphNode> {
|
||||
static fromPackages(packages: Package[]): PackageGraph {
|
||||
const graph = new PackageGraph();
|
||||
|
||||
// Add all local packages to the graph
|
||||
for (const pkg of packages) {
|
||||
const name = pkg.packageJson.name;
|
||||
const existingPkg = graph.get(name);
|
||||
if (existingPkg) {
|
||||
throw new Error(
|
||||
`Duplicate package name '${name}' at ${pkg.dir} and ${existingPkg.dir}`,
|
||||
);
|
||||
}
|
||||
|
||||
graph.set(name, {
|
||||
name,
|
||||
dir: pkg.dir,
|
||||
packageJson: pkg.packageJson as ExtendedPackageJSON,
|
||||
allLocalDependencies: new Map(),
|
||||
publishedLocalDependencies: new Map(),
|
||||
localDependencies: new Map(),
|
||||
localDevDependencies: new Map(),
|
||||
localOptionalDependencies: new Map(),
|
||||
});
|
||||
}
|
||||
|
||||
// Populate the local dependency structure
|
||||
for (const node of graph.values()) {
|
||||
for (const depName of Object.keys(node.packageJson.dependencies || {})) {
|
||||
const depPkg = graph.get(depName);
|
||||
if (depPkg) {
|
||||
node.allLocalDependencies.set(depName, depPkg);
|
||||
node.publishedLocalDependencies.set(depName, depPkg);
|
||||
node.localDependencies.set(depName, depPkg);
|
||||
}
|
||||
}
|
||||
for (const depName of Object.keys(
|
||||
node.packageJson.devDependencies || {},
|
||||
)) {
|
||||
const depPkg = graph.get(depName);
|
||||
if (depPkg) {
|
||||
node.allLocalDependencies.set(depName, depPkg);
|
||||
node.localDevDependencies.set(depName, depPkg);
|
||||
}
|
||||
}
|
||||
for (const depName of Object.keys(
|
||||
node.packageJson.optionalDependencies || {},
|
||||
)) {
|
||||
const depPkg = graph.get(depName);
|
||||
if (depPkg) {
|
||||
node.allLocalDependencies.set(depName, depPkg);
|
||||
node.publishedLocalDependencies.set(depName, depPkg);
|
||||
node.localOptionalDependencies.set(depName, depPkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the package graph and collects a set of package names.
|
||||
*
|
||||
* The traversal starts at the provided list names, and continues
|
||||
* throughout all the names returned by the `collectFn`, which is
|
||||
* called once for each seen package.
|
||||
*/
|
||||
collectPackageNames(
|
||||
startingPackageNames: string[],
|
||||
collectFn: (pkg: PackageGraphNode) => Iterable<string> | undefined,
|
||||
): Set<string> {
|
||||
const targets = new Set<string>();
|
||||
const searchNames = startingPackageNames.slice();
|
||||
|
||||
while (searchNames.length) {
|
||||
const name = searchNames.pop()!;
|
||||
|
||||
if (targets.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = this.get(name);
|
||||
if (!node) {
|
||||
throw new Error(`Package '${name}' not found`);
|
||||
}
|
||||
|
||||
targets.add(name);
|
||||
|
||||
const collected = collectFn(node);
|
||||
if (collected) {
|
||||
searchNames.push(...collected);
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { PackageGraph } from './PackageGraph';
|
||||
export type { PackageGraphNode } from './PackageGraph';
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
dependencies as cliDependencies,
|
||||
devDependencies as cliDevDependencies,
|
||||
} from '../../../package.json';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { PackageGraph, PackageGraphNode } from '../monorepo';
|
||||
|
||||
// These packages aren't safe to pack in parallel since the CLI depends on them
|
||||
const UNSAFE_PACKAGES = [
|
||||
@@ -36,14 +38,6 @@ const UNSAFE_PACKAGES = [
|
||||
...Object.keys(cliDevDependencies),
|
||||
];
|
||||
|
||||
type LernaPackage = {
|
||||
name: string;
|
||||
private: boolean;
|
||||
location: string;
|
||||
scripts: Record<string, string>;
|
||||
get(key: string): any;
|
||||
};
|
||||
|
||||
type FileEntry =
|
||||
| string
|
||||
| {
|
||||
@@ -102,7 +96,17 @@ export async function createDistWorkspace(
|
||||
options.targetDir ??
|
||||
(await fs.mkdtemp(resolvePath(tmpdir(), 'dist-workspace')));
|
||||
|
||||
const targets = await findTargetPackages(packageNames);
|
||||
const { packages } = await getPackages(paths.targetDir);
|
||||
const packageGraph = PackageGraph.fromPackages(packages);
|
||||
const targetNames = packageGraph.collectPackageNames(packageNames, node => {
|
||||
// Don't include dependencies of packages that are marked as bundled
|
||||
if (node.packageJson.bundled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return node.publishedLocalDependencies.keys();
|
||||
});
|
||||
const targets = Array.from(targetNames).map(name => packageGraph.get(name)!);
|
||||
|
||||
if (options.buildDependencies) {
|
||||
const exclude = options.buildExcludes ?? [];
|
||||
@@ -133,7 +137,7 @@ export async function createDistWorkspace(
|
||||
|
||||
if (options.skeleton) {
|
||||
const skeletonFiles = targets.map(target => {
|
||||
const dir = relativePath(paths.targetRoot, target.location);
|
||||
const dir = relativePath(paths.targetRoot, target.dir);
|
||||
return joinPath(dir, 'package.json');
|
||||
});
|
||||
|
||||
@@ -154,21 +158,21 @@ export async function createDistWorkspace(
|
||||
|
||||
async function moveToDistWorkspace(
|
||||
workspaceDir: string,
|
||||
localPackages: LernaPackage[],
|
||||
localPackages: PackageGraphNode[],
|
||||
): Promise<void> {
|
||||
async function pack(target: LernaPackage, archive: string) {
|
||||
async function pack(target: PackageGraphNode, archive: string) {
|
||||
console.log(`Repacking ${target.name} into dist workspace`);
|
||||
const archivePath = resolvePath(workspaceDir, archive);
|
||||
|
||||
await run('yarn', ['pack', '--filename', archivePath], {
|
||||
cwd: target.location,
|
||||
cwd: target.dir,
|
||||
});
|
||||
// TODO(Rugvip): yarn pack doesn't call postpack, once the bug is fixed this can be removed
|
||||
if (target.scripts.postpack) {
|
||||
await run('yarn', ['postpack'], { cwd: target.location });
|
||||
if (target.packageJson?.scripts?.postpack) {
|
||||
await run('yarn', ['postpack'], { cwd: target.dir });
|
||||
}
|
||||
|
||||
const outputDir = relativePath(paths.targetRoot, target.location);
|
||||
const outputDir = relativePath(paths.targetRoot, target.dir);
|
||||
const absoluteOutputPath = resolvePath(workspaceDir, outputDir);
|
||||
await fs.ensureDir(absoluteOutputPath);
|
||||
|
||||
@@ -181,7 +185,7 @@ async function moveToDistWorkspace(
|
||||
|
||||
// We remove the dependencies from package.json of packages that are marked
|
||||
// as bundled, so that yarn doesn't try to install them.
|
||||
if (target.get('bundled')) {
|
||||
if (target.packageJson.bundled) {
|
||||
const pkgJson = await fs.readJson(
|
||||
resolvePath(absoluteOutputPath, 'package.json'),
|
||||
);
|
||||
@@ -220,41 +224,3 @@ async function moveToDistWorkspace(
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async function findTargetPackages(pkgNames: string[]): Promise<LernaPackage[]> {
|
||||
const { Project } = require('@lerna/project');
|
||||
const { PackageGraph } = require('@lerna/package-graph');
|
||||
|
||||
const project = new Project(paths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
const graph = new PackageGraph(packages);
|
||||
|
||||
const targets = new Map<string, any>();
|
||||
const searchNames = pkgNames.slice();
|
||||
|
||||
while (searchNames.length) {
|
||||
const name = searchNames.pop()!;
|
||||
|
||||
if (targets.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = graph.get(name);
|
||||
if (!node) {
|
||||
throw new Error(`Package '${name}' not found`);
|
||||
}
|
||||
|
||||
// Don't include dependencies of packages that are marked as bundled
|
||||
if (!node.pkg.get('bundled')) {
|
||||
const pkgDeps = Object.keys(node.pkg.dependencies ?? {});
|
||||
const localDeps: string[] = Array.from(node.localDependencies.keys());
|
||||
const filteredDeps = localDeps.filter(dep => pkgDeps.includes(dep));
|
||||
|
||||
searchNames.push(...filteredDeps);
|
||||
}
|
||||
|
||||
targets.set(name, node.pkg);
|
||||
}
|
||||
|
||||
return Array.from(targets.values());
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import mockFs from 'mock-fs';
|
||||
import path from 'path';
|
||||
import * as runObj from '../run';
|
||||
import { paths } from '../paths';
|
||||
import { fetchPackageInfo, mapDependencies } from './packages';
|
||||
import { NotFoundError } from '../errors';
|
||||
|
||||
@@ -58,22 +57,19 @@ describe('mapDependencies', () => {
|
||||
});
|
||||
|
||||
it('should read dependencies', async () => {
|
||||
// Make sure all modules involved in package discovery are in the module cache before we mock fs
|
||||
const { Project } = require('@lerna/project');
|
||||
const project = new Project(paths.targetDir);
|
||||
await project.getPackages();
|
||||
|
||||
mockFs({
|
||||
'lerna.json': JSON.stringify({
|
||||
packages: ['pkgs/*'],
|
||||
'/root/package.json': JSON.stringify({
|
||||
workspaces: {
|
||||
packages: ['pkgs/*'],
|
||||
},
|
||||
}),
|
||||
'pkgs/a/package.json': JSON.stringify({
|
||||
'/root/pkgs/a/package.json': JSON.stringify({
|
||||
name: 'a',
|
||||
dependencies: {
|
||||
'@backstage/core': '1 || 2',
|
||||
},
|
||||
}),
|
||||
'pkgs/b/package.json': JSON.stringify({
|
||||
'/root/pkgs/b/package.json': JSON.stringify({
|
||||
name: 'b',
|
||||
dependencies: {
|
||||
'@backstage/core': '3',
|
||||
@@ -82,7 +78,7 @@ describe('mapDependencies', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const dependencyMap = await mapDependencies(paths.targetDir);
|
||||
const dependencyMap = await mapDependencies('/root', '@backstage/*');
|
||||
expect(Array.from(dependencyMap)).toEqual([
|
||||
[
|
||||
'@backstage/core',
|
||||
@@ -90,12 +86,12 @@ describe('mapDependencies', () => {
|
||||
{
|
||||
name: 'a',
|
||||
range: '1 || 2',
|
||||
location: path.resolve('pkgs', 'a'),
|
||||
location: path.resolve('/root/pkgs/a'),
|
||||
},
|
||||
{
|
||||
name: 'b',
|
||||
range: '3',
|
||||
location: path.resolve('pkgs', 'b'),
|
||||
location: path.resolve('/root/pkgs/b'),
|
||||
},
|
||||
],
|
||||
],
|
||||
@@ -105,7 +101,7 @@ describe('mapDependencies', () => {
|
||||
{
|
||||
name: 'b',
|
||||
range: '^0',
|
||||
location: path.resolve('pkgs', 'b'),
|
||||
location: path.resolve('/root/pkgs/b'),
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
@@ -13,18 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import minimatch from 'minimatch';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { runPlain } from '../../lib/run';
|
||||
import { NotFoundError } from '../errors';
|
||||
|
||||
const PREFIX = '@backstage';
|
||||
|
||||
const DEP_TYPES = [
|
||||
'dependencies',
|
||||
'devDependencies',
|
||||
'peerDependencies',
|
||||
'optionalDependencies',
|
||||
];
|
||||
] as const;
|
||||
|
||||
// Package data as returned by `yarn info`
|
||||
export type YarnInfoInspectData = {
|
||||
@@ -66,25 +65,24 @@ export async function fetchPackageInfo(
|
||||
/** Map all dependencies in the repo as dependency => dependents */
|
||||
export async function mapDependencies(
|
||||
targetDir: string,
|
||||
pattern: string,
|
||||
): Promise<Map<string, PkgVersionInfo[]>> {
|
||||
const { Project } = require('@lerna/project');
|
||||
const project = new Project(targetDir);
|
||||
const packages = await project.getPackages();
|
||||
const { packages } = await getPackages(targetDir);
|
||||
|
||||
const dependencyMap = new Map<string, PkgVersionInfo[]>();
|
||||
for (const pkg of packages) {
|
||||
const deps = DEP_TYPES.flatMap(
|
||||
t => Object.entries(pkg.get(t) ?? {}) as [string, string][],
|
||||
t => Object.entries(pkg.packageJson[t] ?? {}) as [string, string][],
|
||||
);
|
||||
|
||||
for (const [name, range] of deps) {
|
||||
if (name.startsWith(PREFIX)) {
|
||||
if (minimatch(name, pattern)) {
|
||||
dependencyMap.set(
|
||||
name,
|
||||
(dependencyMap.get(name) ?? []).concat({
|
||||
range,
|
||||
name: pkg.name,
|
||||
location: pkg.location,
|
||||
name: pkg.packageJson.name,
|
||||
location: pkg.dir,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Table, TableColumn, Progress } from '@backstage/core-components';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { useAsync } from 'react-use';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
avatar: {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/>
|
||||
<title>Backstage</title>
|
||||
</head>
|
||||
<body style="margin: 0">
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/codemods
|
||||
|
||||
## 0.1.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@0.4.1
|
||||
- @backstage/core-app-api@0.3.1
|
||||
- @backstage/core-components@0.8.3
|
||||
|
||||
## 0.1.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/codemods",
|
||||
"description": "A collection of codemods for Backstage projects",
|
||||
"version": "0.1.27",
|
||||
"version": "0.1.28",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/config-loader
|
||||
|
||||
## 0.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 84663d59a3: Bump `typescript-json-schema` from `^0.51.0` to `^0.52.0`.
|
||||
|
||||
## 0.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config-loader",
|
||||
"description": "Config loading functionality used by Backstage backend, and CLI",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -42,7 +42,7 @@
|
||||
"json-schema-merge-allof": "^0.8.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"typescript-json-schema": "^0.51.0",
|
||||
"typescript-json-schema": "^0.52.0",
|
||||
"yaml": "^1.9.2",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
|
||||
@@ -265,6 +265,19 @@ describe('ConfigReader', () => {
|
||||
withLogCollector(() => config.getOptionalConfigArray('b')),
|
||||
).toMatchObject({ warn: [] });
|
||||
});
|
||||
|
||||
it('should coerce number strings to numbers', () => {
|
||||
const config = ConfigReader.fromConfigs([
|
||||
{
|
||||
data: {
|
||||
port: '123',
|
||||
},
|
||||
context: '1',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(config.getNumber('port')).toEqual(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ConfigReader with fallback', () => {
|
||||
@@ -661,16 +674,50 @@ describe('ConfigReader.get()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('coerces number strings to numbers', () => {
|
||||
const config = ConfigReader.fromConfigs([
|
||||
{
|
||||
data: {
|
||||
port: '123',
|
||||
},
|
||||
context: '1',
|
||||
it('should return deep clones of the backing data', () => {
|
||||
const data1 = {
|
||||
foo: {
|
||||
bar: [],
|
||||
baz: {},
|
||||
},
|
||||
};
|
||||
const data2 = {
|
||||
x: {
|
||||
y: {
|
||||
z: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const reader = ConfigReader.fromConfigs([
|
||||
{ data: data1, context: '1' },
|
||||
{ data: data2, context: '2' },
|
||||
]);
|
||||
|
||||
expect(config.getNumber('port')).toEqual(123);
|
||||
reader.get<any>().foo.bar.push(1);
|
||||
reader.get<any>('foo').bar.push(1);
|
||||
reader.get<any>('foo.bar').push(1);
|
||||
reader.get<any>().foo.baz.x = 1;
|
||||
reader.get<any>('foo').baz.x = 1;
|
||||
reader.get<any>('foo.baz').x = 1;
|
||||
reader.get<any>().x.y.z.w = 1;
|
||||
reader.get<any>('x').y.z.w = 1;
|
||||
reader.get<any>('x.y').z.w = 1;
|
||||
reader.get<any>('x.y.z').w = 1;
|
||||
|
||||
const readerSingle = ConfigReader.fromConfigs([
|
||||
{ data: data1, context: '1' },
|
||||
]);
|
||||
|
||||
readerSingle.get<any>().foo.bar.push(1);
|
||||
readerSingle.get<any>('foo').bar.push(1);
|
||||
readerSingle.get<any>('foo.bar').push(1);
|
||||
readerSingle.get<any>().foo.baz.x = 1;
|
||||
readerSingle.get<any>('foo').baz.x = 1;
|
||||
readerSingle.get<any>('foo.baz').x = 1;
|
||||
|
||||
expect(data1.foo.bar).toEqual([]);
|
||||
expect(data1.foo.baz).toEqual({});
|
||||
expect(data2.x.y.z).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -126,7 +126,7 @@ export class ConfigReader implements Config {
|
||||
|
||||
/** {@inheritdoc Config.getOptional} */
|
||||
getOptional<T = JsonValue>(key?: string): T | undefined {
|
||||
const value = this.readValue(key);
|
||||
const value = cloneDeep(this.readValue(key));
|
||||
const fallbackValue = this.fallback?.getOptional<T>(key);
|
||||
|
||||
if (value === undefined) {
|
||||
@@ -153,11 +153,8 @@ export class ConfigReader implements Config {
|
||||
|
||||
// Avoid merging arrays and primitive values, since that's how merging works for other
|
||||
// methods for reading config.
|
||||
return mergeWith(
|
||||
{},
|
||||
{ value: cloneDeep(fallbackValue) },
|
||||
{ value },
|
||||
(into, from) => (!isObject(from) || !isObject(into) ? from : undefined),
|
||||
return mergeWith({}, { value: fallbackValue }, { value }, (into, from) =>
|
||||
!isObject(from) || !isObject(into) ? from : undefined,
|
||||
).value as T;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/core-app-api
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead.
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@0.4.1
|
||||
- @backstage/core-components@0.8.3
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -50,7 +50,6 @@ import { oktaAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { oneloginAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { OpenIdConnectApi } from '@backstage/core-plugin-api';
|
||||
import { PendingOAuthRequest } from '@backstage/core-plugin-api';
|
||||
import { PluginOutput } from '@backstage/core-plugin-api';
|
||||
import { ProfileInfo } from '@backstage/core-plugin-api';
|
||||
import { ProfileInfoApi } from '@backstage/core-plugin-api';
|
||||
import { PropsWithChildren } from 'react';
|
||||
@@ -129,21 +128,6 @@ export type ApiProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export class ApiRegistry implements ApiHolder {
|
||||
constructor(apis: Map<string, unknown>);
|
||||
// Warning: (ae-forgotten-export) The symbol "ApiRegistryBuilder" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
static builder(): ApiRegistryBuilder;
|
||||
// Warning: (ae-forgotten-export) The symbol "ApiImpl" needs to be exported by the entry point index.d.ts
|
||||
static from(apis: ApiImpl[]): ApiRegistry;
|
||||
// (undocumented)
|
||||
get<T>(api: ApiRef<T>): T | undefined;
|
||||
static with<T>(api: ApiRef<T>, impl: T): ApiRegistry;
|
||||
with<T>(api: ApiRef<T>, impl: T): ApiRegistry;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class ApiResolver implements ApiHolder {
|
||||
constructor(factories: ApiFactoryHolder);
|
||||
@@ -208,14 +192,19 @@ export type AppOptions = {
|
||||
icons: AppIcons & {
|
||||
[key in string]: IconComponent;
|
||||
};
|
||||
plugins?: (Omit<BackstagePlugin<any, any>, 'output'> & {
|
||||
output(): (
|
||||
| PluginOutput
|
||||
| {
|
||||
type: string;
|
||||
}
|
||||
)[];
|
||||
})[];
|
||||
plugins?: Array<
|
||||
BackstagePlugin<any, any> & {
|
||||
output?(): Array<
|
||||
| {
|
||||
type: 'feature-flag';
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
type: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
>;
|
||||
components: AppComponents;
|
||||
themes: (Partial<AppTheme> & Omit<AppTheme, 'theme'>)[];
|
||||
configLoader?: AppConfigLoader;
|
||||
@@ -410,7 +399,7 @@ export class GithubAuth implements OAuthApi, SessionApi {
|
||||
signOut(): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export type GithubSession = {
|
||||
providerInfo: {
|
||||
accessToken: string;
|
||||
@@ -562,7 +551,7 @@ export class SamlAuth
|
||||
signOut(): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export type SamlSession = {
|
||||
userId: string;
|
||||
profile: ProfileInfo;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-app-api",
|
||||
"description": "Core app API used by Backstage apps",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,15 +29,10 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/app-defaults": "^0.1.3",
|
||||
"@backstage/core-components": "^0.8.2",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/core-plugin-api": "^0.4.0",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
"@backstage/core-plugin-api": "^0.4.1",
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@backstage/version-bridge": "^0.1.1",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
@@ -50,7 +45,7 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.3",
|
||||
"@backstage/cli": "^0.10.5",
|
||||
"@backstage/test-utils": "^0.2.0",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
ErrorApi,
|
||||
ErrorApiError,
|
||||
ErrorApiErrorContext,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
* Copyright 2020 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.
|
||||
@@ -20,6 +14,12 @@ import {
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ErrorApi,
|
||||
ErrorApiError,
|
||||
ErrorApiErrorContext,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* Utility class that helps with error forwarding.
|
||||
*
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
import { ProfileInfo, BackstageIdentity } from '@backstage/core-plugin-api';
|
||||
import { z } from 'zod';
|
||||
|
||||
// TODO(Rugvip): Make GithubSession internal
|
||||
|
||||
/**
|
||||
* Session information for GitHub auth.
|
||||
*
|
||||
* @public
|
||||
* @deprecated This type is internal and will be removed
|
||||
*/
|
||||
export type GithubSession = {
|
||||
providerInfo: {
|
||||
@@ -29,6 +32,7 @@ export type GithubSession = {
|
||||
expiresAt?: Date;
|
||||
};
|
||||
profile: ProfileInfo;
|
||||
// TODO(Rugvip): This should be made optional once the type is no longer public
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { default as SamlAuth } from './SamlAuth';
|
||||
export type { SamlSession } from './types';
|
||||
export type { ExportedSamlSession as SamlSession } from './types';
|
||||
|
||||
@@ -21,15 +21,22 @@ import { z } from 'zod';
|
||||
* Session information for SAML auth.
|
||||
*
|
||||
* @public
|
||||
* @deprecated This type is internal and will be removed
|
||||
*/
|
||||
export type SamlSession = {
|
||||
export type ExportedSamlSession = {
|
||||
userId: string;
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export type SamlSession = {
|
||||
profile: ProfileInfo;
|
||||
backstageIdentity: BackstageIdentity;
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export const samlSessionSchema: z.ZodSchema<SamlSession> = z.object({
|
||||
userId: z.string(),
|
||||
profile: z.object({
|
||||
email: z.string().optional(),
|
||||
displayName: z.string().optional(),
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ApiRef, ApiHolder } from '@backstage/core-plugin-api';
|
||||
|
||||
type ApiImpl<T = unknown> = readonly [ApiRef<T>, T];
|
||||
|
||||
/** @internal */
|
||||
class ApiRegistryBuilder {
|
||||
private apis: [string, unknown][] = [];
|
||||
|
||||
@@ -35,8 +36,7 @@ class ApiRegistryBuilder {
|
||||
/**
|
||||
* A registry for utility APIs.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Will be removed, use {@link @backstage/test-utils#TestApiProvider} or {@link @backstage/test-utils#TestApiRegistry} instead.
|
||||
* @internal
|
||||
*/
|
||||
export class ApiRegistry implements ApiHolder {
|
||||
static builder() {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
export { ApiProvider } from './ApiProvider';
|
||||
export type { ApiProviderProps } from './ApiProvider';
|
||||
export { ApiRegistry } from './ApiRegistry';
|
||||
export { ApiResolver } from './ApiResolver';
|
||||
export { ApiFactoryRegistry } from './ApiFactoryRegistry';
|
||||
export type { ApiFactoryScope } from './ApiFactoryRegistry';
|
||||
|
||||
@@ -316,7 +316,7 @@ describe('Integration Test', () => {
|
||||
plugins: [
|
||||
createPlugin({
|
||||
id: 'test',
|
||||
register: p => p.featureFlags.register('name'),
|
||||
featureFlags: [{ name: 'name' }],
|
||||
}),
|
||||
],
|
||||
components,
|
||||
@@ -384,8 +384,24 @@ describe('Integration Test', () => {
|
||||
name: 'foo',
|
||||
},
|
||||
],
|
||||
register: p => p.featureFlags.register('name'),
|
||||
}),
|
||||
// We still support consuming the old feature flag API for a little while longer
|
||||
{
|
||||
getId() {
|
||||
return 'old-test';
|
||||
},
|
||||
getApis() {
|
||||
return [];
|
||||
},
|
||||
output() {
|
||||
return [
|
||||
{
|
||||
type: 'feature-flag',
|
||||
name: 'old-feature-flag',
|
||||
},
|
||||
];
|
||||
},
|
||||
} as any,
|
||||
],
|
||||
components,
|
||||
configLoader: async () => [],
|
||||
@@ -412,12 +428,12 @@ describe('Integration Test', () => {
|
||||
);
|
||||
|
||||
expect(storageFlags.registerFlag).toHaveBeenCalledWith({
|
||||
name: 'name',
|
||||
name: 'foo',
|
||||
pluginId: 'test',
|
||||
});
|
||||
expect(storageFlags.registerFlag).toHaveBeenCalledWith({
|
||||
name: 'foo',
|
||||
pluginId: 'test',
|
||||
name: 'old-feature-flag',
|
||||
pluginId: 'old-test',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -24,10 +24,9 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
AppThemeSelector,
|
||||
ConfigReader,
|
||||
LocalStorageFeatureFlags,
|
||||
@@ -50,7 +49,6 @@ import {
|
||||
SubRouteRef,
|
||||
ExternalRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { UserIdentity } from '@backstage/core-components';
|
||||
import { ApiFactoryRegistry, ApiResolver } from '../apis/system';
|
||||
import {
|
||||
childDiscoverer,
|
||||
@@ -80,6 +78,13 @@ import {
|
||||
} from './types';
|
||||
import { AppThemeProvider } from './AppThemeProvider';
|
||||
import { defaultConfigLoader } from './defaultConfigLoader';
|
||||
import { ApiRegistry } from '../apis/system/ApiRegistry';
|
||||
|
||||
type CompatiblePlugin =
|
||||
| BackstagePlugin<any, any>
|
||||
| (Omit<BackstagePlugin<any, any>, 'getFeatureFlags'> & {
|
||||
output(): Array<{ type: 'feature-flag'; name: string }>;
|
||||
});
|
||||
|
||||
export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) {
|
||||
const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();
|
||||
@@ -149,7 +154,7 @@ function useConfigLoader(
|
||||
if (noConfigNode) {
|
||||
return {
|
||||
node: (
|
||||
<ApiProvider apis={ApiRegistry.from([[appThemeApiRef, appThemeApi]])}>
|
||||
<ApiProvider apis={ApiRegistry.with(appThemeApiRef, appThemeApi)}>
|
||||
<ThemeProvider>{noConfigNode}</ThemeProvider>
|
||||
</ApiProvider>
|
||||
),
|
||||
@@ -183,7 +188,7 @@ export class AppManager implements BackstageApp {
|
||||
|
||||
private readonly apis: Iterable<AnyApiFactory>;
|
||||
private readonly icons: NonNullable<AppOptions['icons']>;
|
||||
private readonly plugins: Set<BackstagePlugin<any, any>>;
|
||||
private readonly plugins: Set<CompatiblePlugin>;
|
||||
private readonly components: AppComponents;
|
||||
private readonly themes: AppTheme[];
|
||||
private readonly configLoader?: AppConfigLoader;
|
||||
@@ -196,9 +201,7 @@ export class AppManager implements BackstageApp {
|
||||
constructor(options: AppOptions) {
|
||||
this.apis = options.apis ?? [];
|
||||
this.icons = options.icons;
|
||||
this.plugins = new Set(
|
||||
(options.plugins as BackstagePlugin<any, any>[]) ?? [],
|
||||
);
|
||||
this.plugins = new Set((options.plugins as CompatiblePlugin[]) ?? []);
|
||||
this.components = options.components;
|
||||
this.themes = options.themes as AppTheme[];
|
||||
this.configLoader = options.configLoader ?? defaultConfigLoader;
|
||||
@@ -208,7 +211,7 @@ export class AppManager implements BackstageApp {
|
||||
}
|
||||
|
||||
getPlugins(): BackstagePlugin<any, any>[] {
|
||||
return Array.from(this.plugins);
|
||||
return Array.from(this.plugins) as BackstagePlugin<any, any>[];
|
||||
}
|
||||
|
||||
getSystemIcon(key: string): IconComponent | undefined {
|
||||
@@ -282,16 +285,11 @@ export class AppManager implements BackstageApp {
|
||||
}
|
||||
} else {
|
||||
for (const output of plugin.output()) {
|
||||
switch (output.type) {
|
||||
case 'feature-flag': {
|
||||
featureFlagsApi.registerFlag({
|
||||
name: output.name,
|
||||
pluginId: plugin.getId(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
if (output.type === 'feature-flag') {
|
||||
featureFlagsApi.registerFlag({
|
||||
name: output.name,
|
||||
pluginId: plugin.getId(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,7 +359,25 @@ export class AppManager implements BackstageApp {
|
||||
|
||||
// If the app hasn't configured a sign-in page, we just continue as guest.
|
||||
if (!SignInPageComponent) {
|
||||
this.appIdentityProxy.setTarget(UserIdentity.createGuest());
|
||||
this.appIdentityProxy.setTarget({
|
||||
getUserId: () => 'guest',
|
||||
getIdToken: async () => undefined,
|
||||
getProfile: () => ({
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
}),
|
||||
getProfileInfo: async () => ({
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
}),
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: ['user:default/guest'],
|
||||
}),
|
||||
getCredentials: async () => ({}),
|
||||
signOut: async () => {},
|
||||
});
|
||||
|
||||
return (
|
||||
<RouterComponent>
|
||||
@@ -468,7 +484,7 @@ export class AppManager implements BackstageApp {
|
||||
return this.apiHolder;
|
||||
}
|
||||
|
||||
private verifyPlugins(plugins: Iterable<BackstagePlugin>) {
|
||||
private verifyPlugins(plugins: Iterable<CompatiblePlugin>) {
|
||||
const pluginIds = new Set<string>();
|
||||
|
||||
for (const plugin of plugins) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React, { useMemo, useEffect, useState, PropsWithChildren } from 'react';
|
||||
import { useApi, appThemeApiRef, AppTheme } from '@backstage/core-plugin-api';
|
||||
import { useObservable } from 'react-use';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
|
||||
// This tries to find the most accurate match, but also falls back to less
|
||||
// accurate results in order to avoid errors.
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
RouteRef,
|
||||
SubRouteRef,
|
||||
ExternalRouteRef,
|
||||
PluginOutput,
|
||||
IdentityApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { AppConfig } from '@backstage/config';
|
||||
@@ -232,9 +231,13 @@ export type AppOptions = {
|
||||
/**
|
||||
* A list of all plugins to include in the app.
|
||||
*/
|
||||
plugins?: (Omit<BackstagePlugin<any, any>, 'output'> & {
|
||||
output(): (PluginOutput | { type: string })[];
|
||||
})[];
|
||||
plugins?: Array<
|
||||
BackstagePlugin<any, any> & {
|
||||
output?(): Array<
|
||||
{ type: 'feature-flag'; name: string } | { type: string }
|
||||
>; // support for old plugins
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Supply components to the app to override the default ones.
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/core-components
|
||||
|
||||
## 0.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4ce51ab0f1: Internal refactor of the `react-use` imports to use `react-use/lib/*` instead.
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@0.4.1
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -151,7 +151,6 @@ export function CodeSnippet(props: CodeSnippetProps): JSX.Element;
|
||||
export interface CodeSnippetProps {
|
||||
customStyle?: any;
|
||||
highlightedNumbers?: number[];
|
||||
// Warning: (tsdoc-reference-missing-identifier) Syntax error in declaration reference: expecting a member identifier
|
||||
language: string;
|
||||
showCopyCodeButton?: boolean;
|
||||
showLineNumbers?: boolean;
|
||||
@@ -527,8 +526,6 @@ export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem';
|
||||
// @public
|
||||
export function IntroCard(props: IntroCardProps): JSX.Element;
|
||||
|
||||
// Warning: (tsdoc-malformed-html-name) Invalid HTML element: Expecting an HTML name
|
||||
// Warning: (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
|
||||
// Warning: (ae-forgotten-export) The symbol "ItemCardProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ItemCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -2415,18 +2412,6 @@ export function useSupportConfig(): SupportConfig;
|
||||
// @public (undocumented)
|
||||
export function WarningIcon(props: IconComponentProps): JSX.Element;
|
||||
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets.
|
||||
// Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}'
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets.
|
||||
// Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}'
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets.
|
||||
// Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}'
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets.
|
||||
// Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}'
|
||||
// Warning: (ae-forgotten-export) The symbol "WarningProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "WarningPanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user