Merge branch 'master' into feat/search-facet-by-type
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-rollbar-backend': patch
|
||||
---
|
||||
|
||||
Moved `@backstage/test-utils` to `devDependencies`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Replaces the usage of `got` with `node-fetch` in the `getUserPhoto` method of the Microsoft provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-cost-insights': patch
|
||||
---
|
||||
|
||||
Fixed an accidental re-export of `@backstage/test-utils` that broke this plugin in the most recent release.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
---
|
||||
|
||||
Bump `@azure/identity` from `^1.5.0` to `^2.0.1`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
In-memory cache clients instantiated from the same cache manager now share the same memory space.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Update `auth0` and `onelogin` providers to allow for `authHandler` and `signIn.resolver` configuration.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Fixed bug on refresh token on Okta provider, now it gets the refresh token and it sends it into providerInfo
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': minor
|
||||
---
|
||||
|
||||
Avoid ever returning OAuth refresh tokens back to the client, and always exchange refresh tokens for a new one when available for all providers.
|
||||
|
||||
This comes with a breaking change to the TypeScript API for custom auth providers. The `refresh` method of `OAuthHandlers` implementation must now return a `{ response, refreshToken }` object rather than a direct response. Existing `refresh` implementations are typically migrated by changing an existing return expression that looks like this:
|
||||
|
||||
```ts
|
||||
return await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
});
|
||||
```
|
||||
|
||||
Into the following:
|
||||
|
||||
```ts
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search': patch
|
||||
---
|
||||
|
||||
Internal cleanup of the exports structure
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Fix issue with plugin:serve for Plugins not using Lerna monorepo.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
When a user has zero owned entities when viewing an entity kind in the catalog
|
||||
page, it will be automatically redirected to see all the entities. Furthermore,
|
||||
for the kind User and Group there are no longer the owned selector.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Add a comment to the default backend about the fallback 404 handler.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-rollbar-backend': patch
|
||||
---
|
||||
|
||||
Replace the usage of `axios` with `node-fetch` in the Rollbar API
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
'@backstage/plugin-scaffolder-common': patch
|
||||
---
|
||||
|
||||
Support navigating back to pre-filled templates to update inputs of scaffolder tasks for resubmission
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
---
|
||||
|
||||
Add support for API auth in DefaultTechDocsCollator
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-tech-insights': patch
|
||||
---
|
||||
|
||||
Export `techInsightsApiRef` and associated types.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
Bump `typescript-json-schema` from `^0.51.0` to `^0.52.0`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-tech-insights-backend-module-jsonfc': patch
|
||||
---
|
||||
|
||||
ability to add custom operators
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Add option to build command for minifying the generated code
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Enforce cookie SSL protection when in production for auth-backend sessions
|
||||
@@ -43,3 +43,4 @@
|
||||
/.changeset/techdocs-* @backstage/techdocs-core
|
||||
/cypress/src/integration/plugins/techdocs.spec.ts @backstage/techdocs-core
|
||||
/plugins/apache-airflow @backstage/reviewers @cmpadden
|
||||
/plugins/newrelic-dashboard @backstage/reviewers @mufaddal7
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
id: adrs-adr013
|
||||
title: 'ADR013: Proper use of HTTP fetching libraries'
|
||||
# prettier-ignore
|
||||
description: Architecture Decision Record (ADR) for the proper use of fetchApiRef, node-fetch, and cross-fetch for data fetching.
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Using multiple HTTP packages for data fetching increases the complexity and the
|
||||
support burden of keeping said package up to date.
|
||||
|
||||
## Decision
|
||||
|
||||
Backend (node) packages should use the `node-fetch` package for HTTP data
|
||||
fetching. Example:
|
||||
|
||||
```ts
|
||||
import fetch from 'node-fetch';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
const response = await fetch('https://example.com/api/v1/users.json');
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
const users = await response.json();
|
||||
```
|
||||
|
||||
Frontend plugins and packages should prefer to use the
|
||||
[`fetchApiRef`](https://backstage.io/docs/reference/core-plugin-api.fetchapiref).
|
||||
It uses `cross-fetch` internally. Example:
|
||||
|
||||
```ts
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
const { fetch } = useApi(fetchApiRef);
|
||||
|
||||
const response = await fetch('https://example.com/api/v1/users.json');
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
const users = await response.json();
|
||||
```
|
||||
|
||||
Isomorphic packages should have a dependency on the `cross-fetch` package for
|
||||
mocking and type definitions. Preferably, classes and functions in isomorphic
|
||||
packages should accept an argument of type `typeof fetch` to let callers supply
|
||||
their preferred implementation of `fetch`. This lets them adorn the calls with
|
||||
auth or other information, and track metrics etc, in a cross-platform way.
|
||||
Example:
|
||||
|
||||
```ts
|
||||
import crossFetch from 'cross-fetch';
|
||||
|
||||
export class MyClient {
|
||||
private readonly fetch: typeof crossFetch;
|
||||
|
||||
constructor(options: { fetch?: typeof crossFetch }) {
|
||||
this.fetch = options.fetch || crossFetch;
|
||||
}
|
||||
|
||||
async users() {
|
||||
return await this.fetch('https://example.com/api/v1/users.json');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
We will gradually transition away from third party packages such as `axios`,
|
||||
`got` and others. Once we have transitioned to `node-fetch` we will add lint
|
||||
rules to enforce this decision.
|
||||
@@ -10,7 +10,7 @@ TechDocs reads the static generated documentation files from a cloud storage
|
||||
bucket (GCS, AWS S3, etc.). The documentation site is generated on the CI/CD
|
||||
workflow associated with the repository containing the documentation files. This
|
||||
document explains the steps needed to generate docs on CI and publish to a cloud
|
||||
storage using [`techdocs-cli`](https://github.com/backstage/techdocs-cli).
|
||||
storage using [`techdocs-cli`](./cli.md).
|
||||
|
||||
The steps here target all kinds of CI providers (GitHub Actions, CircleCI,
|
||||
Jenkins, etc.). Specific tools for individual providers will also be made
|
||||
@@ -40,9 +40,8 @@ techdocs-cli publish --publisher-type awsS3 --storage-name <bucket/container> --
|
||||
|
||||
That's it!
|
||||
|
||||
Take a look at
|
||||
[`techdocs-cli` README](https://github.com/backstage/techdocs-cli) for the
|
||||
complete command reference, details, and options.
|
||||
Take a look at [`techdocs-cli`](./cli.md) for the complete command reference,
|
||||
details, and options.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -74,7 +73,7 @@ Install [`npx`](https://www.npmjs.com/package/npx) to use it for running
|
||||
`techdocs-cli`. Or you can install using `npm install -g @techdocs/cli`.
|
||||
|
||||
We are going to use the
|
||||
[`techdocs-cli generate`](https://github.com/backstage/techdocs-cli#generate-techdocs-site-from-a-documentation-project)
|
||||
[`techdocs-cli generate`](./cli.md#generate-techdocs-site-from-a-documentation-project)
|
||||
command in this step.
|
||||
|
||||
```sh
|
||||
@@ -93,8 +92,7 @@ necessary authentication environment variables.
|
||||
- [AWS authentication](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html)
|
||||
|
||||
And then run the
|
||||
[`techdocs-cli publish`](https://github.com/backstage/techdocs-cli#publish-generated-techdocs-sites)
|
||||
command.
|
||||
[`techdocs-cli publish`](./cli.md#publish-generated-techdocs-sites) command.
|
||||
|
||||
```sh
|
||||
npx @techdocs/cli publish --publisher-type <awsS3|googleGcs> --storage-name <bucket/container> --entity <namespace/kind/name> --directory ./site
|
||||
|
||||
@@ -434,7 +434,8 @@ Usage: backstage-cli build [options]
|
||||
|
||||
Options:
|
||||
--outputs <formats> List of formats to output [types,cjs,esm]
|
||||
-h, --help display help for command
|
||||
--minify Minify the generated code
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
## lint
|
||||
|
||||
@@ -287,7 +287,8 @@
|
||||
"architecture-decisions/adrs-adr009",
|
||||
"architecture-decisions/adrs-adr010",
|
||||
"architecture-decisions/adrs-adr011",
|
||||
"architecture-decisions/adrs-adr012"
|
||||
"architecture-decisions/adrs-adr012",
|
||||
"architecture-decisions/adrs-adr013"
|
||||
],
|
||||
"FAQ": ["FAQ"]
|
||||
}
|
||||
|
||||
@@ -186,6 +186,8 @@ nav:
|
||||
- ADR009 - Entity References: 'architecture-decisions/adr009-entity-references.md'
|
||||
- ADR010 - Luxon Date Library: 'architecture-decisions/adr010-luxon-date-library.md'
|
||||
- ADR011 - Plugin Package Structure: 'architecture-decisions/adr011-plugin-package-structure.md'
|
||||
- ADR012 - Plugin Package Structure: 'architecture-decisions/adr012-use-luxon-locale-and-date-presets.md'
|
||||
- ADR013 - Plugin Package Structure: 'architecture-decisions/adr013-use-node-fetch.md'
|
||||
- Support:
|
||||
- Backstage Project Structure: 'support/project-structure.md'
|
||||
- Glossary: glossary.md
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -42,6 +42,13 @@ 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;
|
||||
@@ -133,6 +140,7 @@ export class CacheManager {
|
||||
return new Keyv({
|
||||
namespace: pluginId,
|
||||
ttl: defaultTtl,
|
||||
store: this.memoryStore,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,9 +44,17 @@ export async function loadCliConfig(options: Options) {
|
||||
const project = new Project(paths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
|
||||
const localPackageNames = options.fromPackage
|
||||
? findPackages(packages, options.fromPackage)
|
||||
: packages.map((p: any) => p.name);
|
||||
let localPackageNames;
|
||||
if (options.fromPackage) {
|
||||
if (packages.length) {
|
||||
localPackageNames = findPackages(packages, options.fromPackage);
|
||||
} else {
|
||||
// No packages: it means that it's not a monorepo (e.g. standalone plugin)
|
||||
localPackageNames = [options.fromPackage];
|
||||
}
|
||||
} else {
|
||||
localPackageNames = packages.map((p: any) => p.name);
|
||||
}
|
||||
|
||||
const schema = await loadConfigSchema({
|
||||
dependencies: localPackageNames,
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -81,6 +81,8 @@ async function main() {
|
||||
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
|
||||
apiRouter.use('/proxy', await proxy(proxyEnv));
|
||||
apiRouter.use('/search', await search(searchEnv));
|
||||
|
||||
// Add backends ABOVE this line; this 404 handler is the catch-all fallback
|
||||
apiRouter.use(notFoundHandler());
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"url": "https://github.com/backstage/backstage/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/identity": "^1.5.0",
|
||||
"@azure/identity": "^2.0.1",
|
||||
"@azure/storage-blob": "^12.5.0",
|
||||
"@backstage/backend-common": "^0.10.0",
|
||||
"@backstage/catalog-model": "^0.9.7",
|
||||
|
||||
@@ -27,10 +27,13 @@ export class AtlassianAuthProvider implements OAuthHandlers {
|
||||
// (undocumented)
|
||||
handler(req: express.Request): Promise<{
|
||||
response: OAuthResponse;
|
||||
refreshToken: string;
|
||||
refreshToken: string | undefined;
|
||||
}>;
|
||||
// (undocumented)
|
||||
refresh(req: OAuthRefreshRequest): Promise<OAuthResponse>;
|
||||
refresh(req: OAuthRefreshRequest): Promise<{
|
||||
response: OAuthResponse;
|
||||
refreshToken: string | undefined;
|
||||
}>;
|
||||
// Warning: (ae-forgotten-export) The symbol "RedirectInfo" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
@@ -47,6 +50,14 @@ export type AtlassianProviderOptions = {
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type Auth0ProviderOptions = {
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
};
|
||||
|
||||
// @public
|
||||
export type AuthHandler<AuthResult> = (
|
||||
input: AuthResult,
|
||||
@@ -219,6 +230,11 @@ export const createAtlassianProvider: (
|
||||
options?: AtlassianProviderOptions | undefined,
|
||||
) => AuthProviderFactory;
|
||||
|
||||
// @public (undocumented)
|
||||
export const createAuth0Provider: (
|
||||
options?: Auth0ProviderOptions | undefined,
|
||||
) => AuthProviderFactory;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createAwsAlbProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -282,6 +298,11 @@ export const createOktaProvider: (
|
||||
_options?: OktaProviderOptions | undefined,
|
||||
) => AuthProviderFactory;
|
||||
|
||||
// @public (undocumented)
|
||||
export const createOneLoginProvider: (
|
||||
options?: OneLoginProviderOptions | undefined,
|
||||
) => AuthProviderFactory;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createOriginFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -470,7 +491,10 @@ export interface OAuthHandlers {
|
||||
// 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-type) The @param block should not include a JSDoc-style '{type}'
|
||||
refresh?(req: OAuthRefreshRequest): Promise<OAuthResponse>;
|
||||
refresh?(req: OAuthRefreshRequest): Promise<{
|
||||
response: OAuthResponse;
|
||||
refreshToken?: string;
|
||||
}>;
|
||||
// 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-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
|
||||
@@ -485,7 +509,6 @@ export type OAuthProviderInfo = {
|
||||
idToken?: string;
|
||||
expiresInSeconds?: number;
|
||||
scope: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "OAuthProviderOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -572,6 +595,14 @@ export type OktaProviderOptions = {
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type OneLoginProviderOptions = {
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
signIn?: {
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "postMessageResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -677,11 +708,11 @@ export type WebMessageResponse =
|
||||
//
|
||||
// src/identity/types.d.ts:31:9 - (ae-forgotten-export) The symbol "AnyJWK" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/aws-alb/provider.d.ts:77:5 - (ae-forgotten-export) The symbol "AwsAlbResult" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/github/provider.d.ts:71:58 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
|
||||
// src/providers/github/provider.d.ts:71:90 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
|
||||
// src/providers/github/provider.d.ts:71:89 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
|
||||
// src/providers/github/provider.d.ts:71:67 - (tsdoc-malformed-html-name) Invalid HTML element: Expecting an HTML name
|
||||
// src/providers/github/provider.d.ts:71:68 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
|
||||
// src/providers/github/provider.d.ts:78:5 - (ae-forgotten-export) The symbol "StateEncoder" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/github/provider.d.ts:74:58 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
|
||||
// src/providers/github/provider.d.ts:74:90 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
|
||||
// src/providers/github/provider.d.ts:74:89 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag
|
||||
// src/providers/github/provider.d.ts:74:67 - (tsdoc-malformed-html-name) Invalid HTML element: Expecting an HTML name
|
||||
// src/providers/github/provider.d.ts:74:68 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@"
|
||||
// src/providers/github/provider.d.ts:81:5 - (ae-forgotten-export) The symbol "StateEncoder" needs to be exported by the entry point index.d.ts
|
||||
// src/providers/types.d.ts:100:5 - (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point index.d.ts
|
||||
```
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"express-promise-router": "^4.1.0",
|
||||
"express-session": "^1.17.1",
|
||||
"fs-extra": "9.1.0",
|
||||
"got": "^11.5.2",
|
||||
"helmet": "^4.0.0",
|
||||
"jose": "^1.27.1",
|
||||
"jwt-decode": "^3.1.0",
|
||||
|
||||
@@ -57,7 +57,10 @@ describe('OAuthAdapter', () => {
|
||||
};
|
||||
}
|
||||
async refresh() {
|
||||
return mockResponseData;
|
||||
return {
|
||||
response: mockResponseData,
|
||||
refreshToken: 'token',
|
||||
};
|
||||
}
|
||||
}
|
||||
const providerInstance = new MyAuthProvider();
|
||||
@@ -257,7 +260,10 @@ describe('OAuthAdapter', () => {
|
||||
});
|
||||
|
||||
it('correctly populates incomplete identities', async () => {
|
||||
const mockRefresh = jest.fn<Promise<OAuthResponse>, [express.Request]>();
|
||||
const mockRefresh = jest.fn<
|
||||
Promise<{ response: OAuthResponse }>,
|
||||
[express.Request]
|
||||
>();
|
||||
|
||||
const oauthProvider = new OAuthAdapter(
|
||||
{
|
||||
@@ -291,10 +297,12 @@ describe('OAuthAdapter', () => {
|
||||
|
||||
// Without a token
|
||||
mockRefresh.mockResolvedValueOnce({
|
||||
...mockResponseData,
|
||||
backstageIdentity: {
|
||||
id: 'foo',
|
||||
token: '',
|
||||
response: {
|
||||
...mockResponseData,
|
||||
backstageIdentity: {
|
||||
id: 'foo',
|
||||
token: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
await oauthProvider.refresh(mockRequest, mockResponse);
|
||||
@@ -315,10 +323,12 @@ describe('OAuthAdapter', () => {
|
||||
|
||||
// With a token
|
||||
mockRefresh.mockResolvedValueOnce({
|
||||
...mockResponseData,
|
||||
backstageIdentity: {
|
||||
id: 'foo',
|
||||
token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`,
|
||||
response: {
|
||||
...mockResponseData,
|
||||
backstageIdentity: {
|
||||
id: 'foo',
|
||||
token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`,
|
||||
},
|
||||
},
|
||||
});
|
||||
await oauthProvider.refresh(mockRequest, mockResponse);
|
||||
|
||||
@@ -212,19 +212,15 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
|
||||
const forwardReq = Object.assign(req, { scope, refreshToken });
|
||||
|
||||
// get new access_token
|
||||
const response = await this.handlers.refresh(
|
||||
forwardReq as OAuthRefreshRequest,
|
||||
);
|
||||
const { response, refreshToken: newRefreshToken } =
|
||||
await this.handlers.refresh(forwardReq as OAuthRefreshRequest);
|
||||
|
||||
const backstageIdentity = await this.populateIdentity(
|
||||
response.backstageIdentity,
|
||||
);
|
||||
|
||||
if (
|
||||
response.providerInfo.refreshToken &&
|
||||
response.providerInfo.refreshToken !== refreshToken
|
||||
) {
|
||||
this.setRefreshTokenCookie(res, response.providerInfo.refreshToken);
|
||||
if (newRefreshToken && newRefreshToken !== refreshToken) {
|
||||
this.setRefreshTokenCookie(res, newRefreshToken);
|
||||
}
|
||||
|
||||
res.status(200).json({ ...response, backstageIdentity });
|
||||
|
||||
@@ -79,10 +79,6 @@ export type OAuthProviderInfo = {
|
||||
* Scopes granted for the access token.
|
||||
*/
|
||||
scope: string;
|
||||
/**
|
||||
* A refresh token issued for the signed in user
|
||||
*/
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
export type OAuthState = {
|
||||
@@ -130,7 +126,10 @@ export interface OAuthHandlers {
|
||||
* @param {string} refreshToken
|
||||
* @param {string} scope
|
||||
*/
|
||||
refresh?(req: OAuthRefreshRequest): Promise<OAuthResponse>;
|
||||
refresh?(req: OAuthRefreshRequest): Promise<{
|
||||
response: OAuthResponse;
|
||||
refreshToken?: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* (Optional) Sign out of the auth provider.
|
||||
|
||||
@@ -78,20 +78,22 @@ describe('createAtlassianProvider', () => {
|
||||
refreshToken: 'wacka',
|
||||
},
|
||||
});
|
||||
const { response } = await provider.handler({} as any);
|
||||
expect(response).toEqual({
|
||||
providerInfo: {
|
||||
accessToken: 'accessToken',
|
||||
expiresInSeconds: 123,
|
||||
idToken: 'idToken',
|
||||
scope: 'scope',
|
||||
refreshToken: 'wacka',
|
||||
},
|
||||
profile: {
|
||||
email: 'conrad@example.com',
|
||||
displayName: 'Conrad',
|
||||
picture: 'http://google.com/lols',
|
||||
const result = await provider.handler({} as any);
|
||||
expect(result).toEqual({
|
||||
response: {
|
||||
providerInfo: {
|
||||
accessToken: 'accessToken',
|
||||
expiresInSeconds: 123,
|
||||
idToken: 'idToken',
|
||||
scope: 'scope',
|
||||
},
|
||||
profile: {
|
||||
email: 'conrad@example.com',
|
||||
displayName: 'Conrad',
|
||||
picture: 'http://google.com/lols',
|
||||
},
|
||||
},
|
||||
refreshToken: 'wacka',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,20 +129,22 @@ describe('createAtlassianProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
const response = await provider.refresh({} as any);
|
||||
const result = await provider.refresh({} as any);
|
||||
|
||||
expect(response).toEqual({
|
||||
profile: {
|
||||
displayName: 'Mocked User',
|
||||
email: 'mockuser@gmail.com',
|
||||
picture: 'http://google.com/lols',
|
||||
},
|
||||
providerInfo: {
|
||||
accessToken: 'a.b.c',
|
||||
idToken: 'my-id',
|
||||
refreshToken: 'dont-forget-to-send-refresh',
|
||||
scope: 'read_user',
|
||||
expect(result).toEqual({
|
||||
response: {
|
||||
profile: {
|
||||
displayName: 'Mocked User',
|
||||
email: 'mockuser@gmail.com',
|
||||
picture: 'http://google.com/lols',
|
||||
},
|
||||
providerInfo: {
|
||||
accessToken: 'a.b.c',
|
||||
idToken: 'my-id',
|
||||
scope: 'read_user',
|
||||
},
|
||||
},
|
||||
refreshToken: 'dont-forget-to-send-refresh',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,9 +107,7 @@ export class AtlassianAuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result } = await executeFrameHandlerStrategy<OAuthResult>(
|
||||
req,
|
||||
this._strategy,
|
||||
@@ -117,7 +115,7 @@ export class AtlassianAuthProvider implements OAuthHandlers {
|
||||
|
||||
return {
|
||||
response: await this.handleResult(result),
|
||||
refreshToken: result.refreshToken ?? '',
|
||||
refreshToken: result.refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,7 +126,6 @@ export class AtlassianAuthProvider implements OAuthHandlers {
|
||||
providerInfo: {
|
||||
idToken: result.params.id_token,
|
||||
accessToken: result.accessToken,
|
||||
refreshToken: result.refreshToken,
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
},
|
||||
@@ -152,28 +149,27 @@ export class AtlassianAuthProvider implements OAuthHandlers {
|
||||
return response;
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const {
|
||||
accessToken,
|
||||
params,
|
||||
refreshToken: newRefreshToken,
|
||||
} = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, params, refreshToken } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
});
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,15 @@ import {
|
||||
makeProfileInfo,
|
||||
PassportDoneCallback,
|
||||
} from '../../lib/passport';
|
||||
import { RedirectInfo, AuthProviderFactory } from '../types';
|
||||
import {
|
||||
RedirectInfo,
|
||||
AuthProviderFactory,
|
||||
AuthHandler,
|
||||
SignInResolver,
|
||||
} from '../types';
|
||||
import { CatalogIdentityClient } from '../../lib/catalog';
|
||||
import { TokenIssuer } from '../../identity';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
type PrivateInfo = {
|
||||
refreshToken: string;
|
||||
@@ -44,12 +52,27 @@ type PrivateInfo = {
|
||||
|
||||
export type Auth0AuthProviderOptions = OAuthProviderOptions & {
|
||||
domain: string;
|
||||
signInResolver?: SignInResolver<OAuthResult>;
|
||||
authHandler: AuthHandler<OAuthResult>;
|
||||
tokenIssuer: TokenIssuer;
|
||||
catalogIdentityClient: CatalogIdentityClient;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
export class Auth0AuthProvider implements OAuthHandlers {
|
||||
private readonly _strategy: Auth0Strategy;
|
||||
private readonly signInResolver?: SignInResolver<OAuthResult>;
|
||||
private readonly authHandler: AuthHandler<OAuthResult>;
|
||||
private readonly tokenIssuer: TokenIssuer;
|
||||
private readonly catalogIdentityClient: CatalogIdentityClient;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(options: Auth0AuthProviderOptions) {
|
||||
this.signInResolver = options.signInResolver;
|
||||
this.authHandler = options.authHandler;
|
||||
this.tokenIssuer = options.tokenIssuer;
|
||||
this.catalogIdentityClient = options.catalogIdentityClient;
|
||||
this.logger = options.logger;
|
||||
this._strategy = new Auth0Strategy(
|
||||
{
|
||||
clientID: options.clientId,
|
||||
@@ -90,88 +113,144 @@ export class Auth0AuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
>(req, this._strategy);
|
||||
|
||||
const profile = makeProfileInfo(result.fullProfile, result.params.id_token);
|
||||
|
||||
return {
|
||||
response: await this.populateIdentity({
|
||||
profile,
|
||||
providerInfo: {
|
||||
idToken: result.params.id_token,
|
||||
accessToken: result.accessToken,
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
},
|
||||
}),
|
||||
response: await this.handleResult(result),
|
||||
refreshToken: privateInfo.refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const { accessToken, params } = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, refreshToken, params } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
const profile = makeProfileInfo(fullProfile, params.id_token);
|
||||
|
||||
return this.populateIdentity({
|
||||
providerInfo: {
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: params.expires_in,
|
||||
scope: params.scope,
|
||||
},
|
||||
profile,
|
||||
});
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
// Use this function to grab the user profile info from the token
|
||||
// Then populate the profile with it
|
||||
private async populateIdentity(
|
||||
response: OAuthResponse,
|
||||
): Promise<OAuthResponse> {
|
||||
const { profile } = response;
|
||||
private async handleResult(result: OAuthResult) {
|
||||
const { profile } = await this.authHandler(result);
|
||||
|
||||
if (!profile.email) {
|
||||
throw new Error('Profile does not contain an email');
|
||||
const response: OAuthResponse = {
|
||||
providerInfo: {
|
||||
idToken: result.params.id_token,
|
||||
accessToken: result.accessToken,
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
},
|
||||
profile,
|
||||
};
|
||||
|
||||
if (this.signInResolver) {
|
||||
response.backstageIdentity = await this.signInResolver(
|
||||
{
|
||||
result,
|
||||
profile,
|
||||
},
|
||||
{
|
||||
tokenIssuer: this.tokenIssuer,
|
||||
catalogIdentityClient: this.catalogIdentityClient,
|
||||
logger: this.logger,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const id = profile.email.split('@')[0];
|
||||
|
||||
return { ...response, backstageIdentity: { id, token: '' } };
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export type Auth0ProviderOptions = {};
|
||||
const defaultSignInResolver: SignInResolver<OAuthResult> = async info => {
|
||||
const { profile } = info;
|
||||
|
||||
if (!profile.email) {
|
||||
throw new Error('Profile does not contain an email');
|
||||
}
|
||||
|
||||
const id = profile.email.split('@')[0];
|
||||
|
||||
return { id, token: '' };
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type Auth0ProviderOptions = {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const createAuth0Provider = (
|
||||
_options?: Auth0ProviderOptions,
|
||||
options?: Auth0ProviderOptions,
|
||||
): AuthProviderFactory => {
|
||||
return ({ providerId, globalConfig, config, tokenIssuer }) =>
|
||||
return ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
catalogApi,
|
||||
logger,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const domain = envConfig.getString('domain');
|
||||
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
||||
|
||||
const catalogIdentityClient = new CatalogIdentityClient({
|
||||
catalogApi,
|
||||
tokenIssuer,
|
||||
});
|
||||
|
||||
const authHandler: AuthHandler<OAuthResult> = options?.authHandler
|
||||
? options.authHandler
|
||||
: async ({ fullProfile, params }) => ({
|
||||
profile: makeProfileInfo(fullProfile, params.id_token),
|
||||
});
|
||||
|
||||
const signInResolver = options?.signIn?.resolver ?? defaultSignInResolver;
|
||||
|
||||
const provider = new Auth0AuthProvider({
|
||||
clientId,
|
||||
clientSecret,
|
||||
callbackUrl,
|
||||
domain,
|
||||
authHandler,
|
||||
signInResolver,
|
||||
tokenIssuer,
|
||||
catalogIdentityClient,
|
||||
logger,
|
||||
});
|
||||
|
||||
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
||||
|
||||
@@ -138,9 +138,7 @@ export class BitbucketAuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
@@ -152,22 +150,25 @@ export class BitbucketAuthProvider implements OAuthHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const { accessToken, params } = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, refreshToken, params } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: req.refreshToken,
|
||||
});
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private async handleResult(result: BitbucketOAuthResult) {
|
||||
|
||||
@@ -316,24 +316,26 @@ describe('GithubAuthProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
const response = await provider.refresh({} as any);
|
||||
const result = await provider.refresh({} as any);
|
||||
|
||||
expect(response).toEqual({
|
||||
backstageIdentity: {
|
||||
id: 'mockuser',
|
||||
token: 'token-for-mockuser',
|
||||
},
|
||||
profile: {
|
||||
displayName: 'Mocked User',
|
||||
email: 'mockuser@gmail.com',
|
||||
picture: undefined,
|
||||
},
|
||||
providerInfo: {
|
||||
accessToken: 'a.b.c',
|
||||
refreshToken: 'dont-forget-to-send-refresh',
|
||||
expiresInSeconds: 123,
|
||||
scope: 'read_user',
|
||||
expect(result).toEqual({
|
||||
response: {
|
||||
backstageIdentity: {
|
||||
id: 'mockuser',
|
||||
token: 'token-for-mockuser',
|
||||
},
|
||||
profile: {
|
||||
displayName: 'Mocked User',
|
||||
email: 'mockuser@gmail.com',
|
||||
picture: undefined,
|
||||
},
|
||||
providerInfo: {
|
||||
accessToken: 'a.b.c',
|
||||
expiresInSeconds: 123,
|
||||
scope: 'read_user',
|
||||
},
|
||||
},
|
||||
refreshToken: 'dont-forget-to-send-refresh',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,26 +129,26 @@ export class GithubAuthProvider implements OAuthHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const {
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
params,
|
||||
} = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, refreshToken, params } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
});
|
||||
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private async handleResult(result: GithubOAuthResult) {
|
||||
@@ -158,7 +158,6 @@ export class GithubAuthProvider implements OAuthHandlers {
|
||||
const response: OAuthResponse = {
|
||||
providerInfo: {
|
||||
accessToken: result.accessToken,
|
||||
refreshToken: result.refreshToken, // GitHub expires the old refresh token when used
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds:
|
||||
expiresInStr === undefined ? undefined : Number(expiresInStr),
|
||||
|
||||
@@ -184,23 +184,25 @@ describe('GitlabAuthProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
const response = await provider.refresh({} as any);
|
||||
const result = await provider.refresh({} as any);
|
||||
|
||||
expect(response).toEqual({
|
||||
backstageIdentity: {
|
||||
id: 'mockuser',
|
||||
},
|
||||
profile: {
|
||||
displayName: 'Mocked User',
|
||||
email: 'mockuser@gmail.com',
|
||||
picture: 'http://gitlab.com/lols',
|
||||
},
|
||||
providerInfo: {
|
||||
accessToken: 'a.b.c',
|
||||
idToken: 'my-id',
|
||||
refreshToken: 'dont-forget-to-send-refresh',
|
||||
scope: 'read_user',
|
||||
expect(result).toEqual({
|
||||
response: {
|
||||
backstageIdentity: {
|
||||
id: 'mockuser',
|
||||
},
|
||||
profile: {
|
||||
displayName: 'Mocked User',
|
||||
email: 'mockuser@gmail.com',
|
||||
picture: 'http://gitlab.com/lols',
|
||||
},
|
||||
providerInfo: {
|
||||
accessToken: 'a.b.c',
|
||||
idToken: 'my-id',
|
||||
scope: 'read_user',
|
||||
},
|
||||
},
|
||||
refreshToken: 'dont-forget-to-send-refresh',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -132,9 +132,7 @@ export class GitlabAuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
@@ -146,28 +144,26 @@ export class GitlabAuthProvider implements OAuthHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const {
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
params,
|
||||
} = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, refreshToken, params } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken,
|
||||
});
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private async handleResult(result: OAuthResult): Promise<OAuthResponse> {
|
||||
@@ -177,7 +173,6 @@ export class GitlabAuthProvider implements OAuthHandlers {
|
||||
providerInfo: {
|
||||
idToken: result.params.id_token,
|
||||
accessToken: result.accessToken,
|
||||
refreshToken: result.refreshToken, // GitLab expires the old refresh token when used
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
},
|
||||
|
||||
@@ -113,9 +113,7 @@ export class GoogleAuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
@@ -127,22 +125,26 @@ export class GoogleAuthProvider implements OAuthHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const { accessToken, params } = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, refreshToken, params } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: req.refreshToken,
|
||||
});
|
||||
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private async handleResult(result: OAuthResult) {
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './atlassian';
|
||||
export * from './auth0';
|
||||
export * from './aws-alb';
|
||||
export * from './bitbucket';
|
||||
export * from './github';
|
||||
export * from './gitlab';
|
||||
export * from './google';
|
||||
@@ -21,9 +25,7 @@ export * from './microsoft';
|
||||
export * from './oauth2';
|
||||
export * from './oidc';
|
||||
export * from './okta';
|
||||
export * from './bitbucket';
|
||||
export * from './atlassian';
|
||||
export * from './aws-alb';
|
||||
export * from './onelogin';
|
||||
export * from './saml';
|
||||
|
||||
export { factories as defaultAuthProviderFactories } from './factories';
|
||||
|
||||
@@ -20,6 +20,9 @@ import { OAuthResult } from '../../lib/oauth';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { TokenIssuer } from '../../identity/types';
|
||||
import { CatalogIdentityClient } from '../../lib/catalog';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
|
||||
const mockFrameHandler = jest.spyOn(
|
||||
helpers,
|
||||
@@ -28,8 +31,62 @@ const mockFrameHandler = jest.spyOn(
|
||||
() => Promise<{ result: OAuthResult; privateInfo: any }>
|
||||
>;
|
||||
|
||||
const mockResult = {
|
||||
result: {
|
||||
fullProfile: {
|
||||
emails: [
|
||||
{
|
||||
type: 'work',
|
||||
value: 'conrad@example.com',
|
||||
},
|
||||
],
|
||||
displayName: 'Conrad',
|
||||
name: {
|
||||
familyName: 'Ribas',
|
||||
givenName: 'Francisco',
|
||||
},
|
||||
id: 'conrad',
|
||||
provider: 'microsoft',
|
||||
photos: [
|
||||
{
|
||||
value: 'some-data',
|
||||
},
|
||||
],
|
||||
},
|
||||
params: {
|
||||
id_token: 'idToken',
|
||||
scope: 'scope',
|
||||
expires_in: 123,
|
||||
},
|
||||
accessToken: 'accessToken',
|
||||
},
|
||||
privateInfo: {
|
||||
refreshToken: 'wacka',
|
||||
},
|
||||
};
|
||||
|
||||
const server = setupServer();
|
||||
setupRequestMockHandlers(server);
|
||||
|
||||
const setupHandlers = () => {
|
||||
server.use(
|
||||
rest.get(
|
||||
'https://graph.microsoft.com/v1.0/me/photos/*',
|
||||
async (_, res, ctx) => {
|
||||
const imageBuffer = new Uint8Array([104, 111, 119, 100, 121]).buffer;
|
||||
return res(
|
||||
ctx.set('Content-Length', imageBuffer.byteLength.toString()),
|
||||
ctx.set('Content-Type', 'image/jpeg'),
|
||||
ctx.body(imageBuffer),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
describe('createMicrosoftProvider', () => {
|
||||
it('should auth', async () => {
|
||||
setupHandlers();
|
||||
const tokenIssuer = {
|
||||
issueToken: jest.fn(),
|
||||
listPublicKeys: jest.fn(),
|
||||
@@ -55,39 +112,7 @@ describe('createMicrosoftProvider', () => {
|
||||
callbackUrl: 'mock',
|
||||
});
|
||||
|
||||
mockFrameHandler.mockResolvedValueOnce({
|
||||
result: {
|
||||
fullProfile: {
|
||||
emails: [
|
||||
{
|
||||
type: 'work',
|
||||
value: 'conrad@example.com',
|
||||
},
|
||||
],
|
||||
displayName: 'Conrad',
|
||||
name: {
|
||||
familyName: 'Ribas',
|
||||
givenName: 'Francisco',
|
||||
},
|
||||
id: 'conrad',
|
||||
provider: 'microsoft',
|
||||
photos: [
|
||||
{
|
||||
value: 'some-data',
|
||||
},
|
||||
],
|
||||
},
|
||||
params: {
|
||||
id_token: 'idToken',
|
||||
scope: 'scope',
|
||||
expires_in: 123,
|
||||
},
|
||||
accessToken: 'accessToken',
|
||||
},
|
||||
privateInfo: {
|
||||
refreshToken: 'wacka',
|
||||
},
|
||||
});
|
||||
mockFrameHandler.mockResolvedValueOnce(mockResult);
|
||||
const { response } = await provider.handler({} as any);
|
||||
expect(response).toEqual({
|
||||
providerInfo: {
|
||||
@@ -103,4 +128,45 @@ describe('createMicrosoftProvider', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the base64 encoded photo data of the profile', async () => {
|
||||
setupHandlers();
|
||||
const tokenIssuer = {
|
||||
issueToken: jest.fn(),
|
||||
listPublicKeys: jest.fn(),
|
||||
};
|
||||
const catalogIdentityClient = {
|
||||
findUser: jest.fn(),
|
||||
};
|
||||
|
||||
const provider = new MicrosoftAuthProvider({
|
||||
logger: getVoidLogger(),
|
||||
catalogIdentityClient:
|
||||
catalogIdentityClient as unknown as CatalogIdentityClient,
|
||||
tokenIssuer: tokenIssuer as unknown as TokenIssuer,
|
||||
authHandler: async ({ fullProfile }) => ({
|
||||
profile: {
|
||||
email: fullProfile.emails![0]!.value,
|
||||
displayName: fullProfile.displayName,
|
||||
picture: 'http://microsoft.com/lols',
|
||||
},
|
||||
}),
|
||||
clientId: 'mock',
|
||||
clientSecret: 'mock',
|
||||
callbackUrl: 'mock',
|
||||
// define resolver to return user `info` for photo validation
|
||||
signInResolver: async (info, _) => {
|
||||
return {
|
||||
id: 'user.name',
|
||||
token: 'token',
|
||||
info: info,
|
||||
};
|
||||
},
|
||||
});
|
||||
mockFrameHandler.mockResolvedValueOnce(mockResult);
|
||||
const { response } = await provider.handler({} as any);
|
||||
const overloadedIdentity = response.backstageIdentity as any;
|
||||
const photo = overloadedIdentity.info.result.fullProfile.photos[0];
|
||||
expect(photo.value).toEqual('data:image/jpeg;base64,aG93ZHk=');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
SignInResolver,
|
||||
} from '../types';
|
||||
import { Logger } from 'winston';
|
||||
import got from 'got';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
type PrivateInfo = {
|
||||
refreshToken: string;
|
||||
@@ -104,9 +104,7 @@ export class MicrosoftAuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
@@ -118,24 +116,27 @@ export class MicrosoftAuthProvider implements OAuthHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const { accessToken, params } = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, refreshToken, params } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: req.refreshToken,
|
||||
});
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private async handleResult(result: OAuthResult) {
|
||||
@@ -173,19 +174,17 @@ export class MicrosoftAuthProvider implements OAuthHandlers {
|
||||
|
||||
private getUserPhoto(accessToken: string): Promise<string | undefined> {
|
||||
return new Promise(resolve => {
|
||||
got
|
||||
.get('https://graph.microsoft.com/v1.0/me/photos/48x48/$value', {
|
||||
encoding: 'binary',
|
||||
responseType: 'buffer',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
.then(photoData => {
|
||||
const photoURL = `data:image/jpeg;base64,${Buffer.from(
|
||||
photoData.body,
|
||||
fetch('https://graph.microsoft.com/v1.0/me/photos/48x48/$value', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
})
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(arrayBuffer => {
|
||||
const imageUrl = `data:image/jpeg;base64,${Buffer.from(
|
||||
arrayBuffer,
|
||||
).toString('base64')}`;
|
||||
resolve(photoURL);
|
||||
resolve(imageUrl);
|
||||
})
|
||||
.catch(error => {
|
||||
this.logger.warn(
|
||||
|
||||
@@ -127,9 +127,7 @@ export class OAuth2AuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
@@ -141,29 +139,27 @@ export class OAuth2AuthProvider implements OAuthHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const refreshTokenResponse = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
const {
|
||||
accessToken,
|
||||
params,
|
||||
refreshToken: updatedRefreshToken,
|
||||
} = refreshTokenResponse;
|
||||
const { accessToken, params, refreshToken } = refreshTokenResponse;
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: updatedRefreshToken,
|
||||
});
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private async handleResult(result: OAuthResult) {
|
||||
@@ -175,7 +171,6 @@ export class OAuth2AuthProvider implements OAuthHandlers {
|
||||
accessToken: result.accessToken,
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
refreshToken: result.refreshToken,
|
||||
},
|
||||
profile,
|
||||
};
|
||||
|
||||
@@ -112,34 +112,31 @@ export class OidcAuthProvider implements OAuthHandlers {
|
||||
return await executeRedirectStrategy(req, strategy, options);
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken?: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { strategy } = await this.implementation;
|
||||
const strategyResponse = await executeFrameHandlerStrategy<
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OidcAuthResult,
|
||||
PrivateInfo
|
||||
>(req, strategy);
|
||||
const {
|
||||
result: { userinfo, tokenset },
|
||||
privateInfo,
|
||||
} = strategyResponse;
|
||||
|
||||
const identityResponse = await this.handleResult({ tokenset, userinfo });
|
||||
return {
|
||||
response: identityResponse,
|
||||
response: await this.handleResult(result),
|
||||
refreshToken: privateInfo.refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { client } = await this.implementation;
|
||||
const tokenset = await client.refresh(req.refreshToken);
|
||||
if (!tokenset.access_token) {
|
||||
throw new Error('Refresh failed');
|
||||
}
|
||||
const profile = await client.userinfo(tokenset.access_token);
|
||||
return this.handleResult({ tokenset, userinfo: profile });
|
||||
const userinfo = await client.userinfo(tokenset.access_token);
|
||||
|
||||
return {
|
||||
response: await this.handleResult({ tokenset, userinfo }),
|
||||
refreshToken: tokenset.refresh_token,
|
||||
};
|
||||
}
|
||||
|
||||
private async setupStrategy(options: Options): Promise<OidcImpl> {
|
||||
@@ -190,7 +187,6 @@ export class OidcAuthProvider implements OAuthHandlers {
|
||||
providerInfo: {
|
||||
idToken: result.tokenset.id_token,
|
||||
accessToken: result.tokenset.access_token!,
|
||||
refreshToken: result.tokenset.refresh_token,
|
||||
scope: result.tokenset.scope!,
|
||||
expiresInSeconds: result.tokenset.expires_in,
|
||||
},
|
||||
|
||||
@@ -133,9 +133,7 @@ export class OktaAuthProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
@@ -147,24 +145,27 @@ export class OktaAuthProvider implements OAuthHandlers {
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const { accessToken, params } = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, refreshToken, params } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
|
||||
return this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
refreshToken: req.refreshToken,
|
||||
});
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private async handleResult(result: OAuthResult) {
|
||||
|
||||
@@ -36,7 +36,15 @@ import {
|
||||
executeFetchUserProfileStrategy,
|
||||
PassportDoneCallback,
|
||||
} from '../../lib/passport';
|
||||
import { RedirectInfo, AuthProviderFactory } from '../types';
|
||||
import {
|
||||
RedirectInfo,
|
||||
AuthProviderFactory,
|
||||
AuthHandler,
|
||||
SignInResolver,
|
||||
} from '../types';
|
||||
import { CatalogIdentityClient } from '../../lib/catalog';
|
||||
import { Logger } from 'winston';
|
||||
import { TokenIssuer } from '../../identity';
|
||||
|
||||
type PrivateInfo = {
|
||||
refreshToken: string;
|
||||
@@ -44,12 +52,27 @@ type PrivateInfo = {
|
||||
|
||||
export type Options = OAuthProviderOptions & {
|
||||
issuer: string;
|
||||
signInResolver?: SignInResolver<OAuthResult>;
|
||||
authHandler: AuthHandler<OAuthResult>;
|
||||
tokenIssuer: TokenIssuer;
|
||||
catalogIdentityClient: CatalogIdentityClient;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
export class OneLoginProvider implements OAuthHandlers {
|
||||
private readonly _strategy: any;
|
||||
private readonly signInResolver?: SignInResolver<OAuthResult>;
|
||||
private readonly authHandler: AuthHandler<OAuthResult>;
|
||||
private readonly tokenIssuer: TokenIssuer;
|
||||
private readonly catalogIdentityClient: CatalogIdentityClient;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.signInResolver = options.signInResolver;
|
||||
this.authHandler = options.authHandler;
|
||||
this.tokenIssuer = options.tokenIssuer;
|
||||
this.catalogIdentityClient = options.catalogIdentityClient;
|
||||
this.logger = options.logger;
|
||||
this._strategy = new OneLoginStrategy(
|
||||
{
|
||||
issuer: options.issuer,
|
||||
@@ -89,86 +112,144 @@ export class OneLoginProvider implements OAuthHandlers {
|
||||
});
|
||||
}
|
||||
|
||||
async handler(
|
||||
req: express.Request,
|
||||
): Promise<{ response: OAuthResponse; refreshToken: string }> {
|
||||
async handler(req: express.Request) {
|
||||
const { result, privateInfo } = await executeFrameHandlerStrategy<
|
||||
OAuthResult,
|
||||
PrivateInfo
|
||||
>(req, this._strategy);
|
||||
|
||||
const profile = makeProfileInfo(result.fullProfile, result.params.id_token);
|
||||
|
||||
return {
|
||||
response: await this.populateIdentity({
|
||||
profile,
|
||||
providerInfo: {
|
||||
idToken: result.params.id_token,
|
||||
accessToken: result.accessToken,
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
},
|
||||
}),
|
||||
response: await this.handleResult(result),
|
||||
refreshToken: privateInfo.refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(req: OAuthRefreshRequest): Promise<OAuthResponse> {
|
||||
const { accessToken, params } = await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
async refresh(req: OAuthRefreshRequest) {
|
||||
const { accessToken, refreshToken, params } =
|
||||
await executeRefreshTokenStrategy(
|
||||
this._strategy,
|
||||
req.refreshToken,
|
||||
req.scope,
|
||||
);
|
||||
|
||||
const fullProfile = await executeFetchUserProfileStrategy(
|
||||
this._strategy,
|
||||
accessToken,
|
||||
);
|
||||
const profile = makeProfileInfo(fullProfile, params.id_token);
|
||||
|
||||
return this.populateIdentity({
|
||||
providerInfo: {
|
||||
return {
|
||||
response: await this.handleResult({
|
||||
fullProfile,
|
||||
params,
|
||||
accessToken,
|
||||
idToken: params.id_token,
|
||||
expiresInSeconds: params.expires_in,
|
||||
scope: params.scope,
|
||||
},
|
||||
profile,
|
||||
});
|
||||
}),
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
private async populateIdentity(
|
||||
response: OAuthResponse,
|
||||
): Promise<OAuthResponse> {
|
||||
const { profile } = response;
|
||||
private async handleResult(result: OAuthResult) {
|
||||
const { profile } = await this.authHandler(result);
|
||||
|
||||
if (!profile.email) {
|
||||
throw new Error('OIDC profile contained no email');
|
||||
const response: OAuthResponse = {
|
||||
providerInfo: {
|
||||
idToken: result.params.id_token,
|
||||
accessToken: result.accessToken,
|
||||
scope: result.params.scope,
|
||||
expiresInSeconds: result.params.expires_in,
|
||||
},
|
||||
profile,
|
||||
};
|
||||
|
||||
if (this.signInResolver) {
|
||||
response.backstageIdentity = await this.signInResolver(
|
||||
{
|
||||
result,
|
||||
profile,
|
||||
},
|
||||
{
|
||||
tokenIssuer: this.tokenIssuer,
|
||||
catalogIdentityClient: this.catalogIdentityClient,
|
||||
logger: this.logger,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const id = profile.email.split('@')[0];
|
||||
|
||||
return { ...response, backstageIdentity: { id, token: '' } };
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export type OneLoginProviderOptions = {};
|
||||
const defaultSignInResolver: SignInResolver<OAuthResult> = async info => {
|
||||
const { profile } = info;
|
||||
|
||||
if (!profile.email) {
|
||||
throw new Error('OIDC profile contained no email');
|
||||
}
|
||||
|
||||
const id = profile.email.split('@')[0];
|
||||
|
||||
return { id, token: '' };
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type OneLoginProviderOptions = {
|
||||
/**
|
||||
* The profile transformation function used to verify and convert the auth response
|
||||
* into the profile that will be presented to the user.
|
||||
*/
|
||||
authHandler?: AuthHandler<OAuthResult>;
|
||||
|
||||
/**
|
||||
* Configure sign-in for this provider, without it the provider can not be used to sign users in.
|
||||
*/
|
||||
signIn?: {
|
||||
/**
|
||||
* Maps an auth result to a Backstage identity for the user.
|
||||
*/
|
||||
resolver: SignInResolver<OAuthResult>;
|
||||
};
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const createOneLoginProvider = (
|
||||
_options?: OneLoginProviderOptions,
|
||||
options?: OneLoginProviderOptions,
|
||||
): AuthProviderFactory => {
|
||||
return ({ providerId, globalConfig, config, tokenIssuer }) =>
|
||||
return ({
|
||||
providerId,
|
||||
globalConfig,
|
||||
config,
|
||||
tokenIssuer,
|
||||
catalogApi,
|
||||
logger,
|
||||
}) =>
|
||||
OAuthEnvironmentHandler.mapConfig(config, envConfig => {
|
||||
const clientId = envConfig.getString('clientId');
|
||||
const clientSecret = envConfig.getString('clientSecret');
|
||||
const issuer = envConfig.getString('issuer');
|
||||
const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
||||
|
||||
const catalogIdentityClient = new CatalogIdentityClient({
|
||||
catalogApi,
|
||||
tokenIssuer,
|
||||
});
|
||||
|
||||
const authHandler: AuthHandler<OAuthResult> = options?.authHandler
|
||||
? options.authHandler
|
||||
: async ({ fullProfile, params }) => ({
|
||||
profile: makeProfileInfo(fullProfile, params.id_token),
|
||||
});
|
||||
|
||||
const signInResolver = options?.signIn?.resolver ?? defaultSignInResolver;
|
||||
|
||||
const provider = new OneLoginProvider({
|
||||
clientId,
|
||||
clientSecret,
|
||||
callbackUrl,
|
||||
issuer,
|
||||
authHandler,
|
||||
signInResolver,
|
||||
tokenIssuer,
|
||||
catalogIdentityClient,
|
||||
logger,
|
||||
});
|
||||
|
||||
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
||||
|
||||
@@ -68,7 +68,15 @@ export async function createRouter(
|
||||
if (secret) {
|
||||
router.use(cookieParser(secret));
|
||||
// TODO: Configure the server-side session storage. The default MemoryStore is not designed for production
|
||||
router.use(session({ secret, saveUninitialized: false, resave: false }));
|
||||
const enforceCookieSSL = authUrl.startsWith('https');
|
||||
router.use(
|
||||
session({
|
||||
secret,
|
||||
saveUninitialized: false,
|
||||
resave: false,
|
||||
cookie: { secure: enforceCookieSSL },
|
||||
}),
|
||||
);
|
||||
router.use(passport.initialize());
|
||||
router.use(passport.session());
|
||||
} else {
|
||||
|
||||
@@ -122,26 +122,26 @@ export const UserListPicker = ({
|
||||
const classes = useStyles();
|
||||
const configApi = useApi(configApiRef);
|
||||
const orgName = configApi.getOptionalString('organization.name') ?? 'Company';
|
||||
const { filters, updateFilters, backendEntities, queryParameters } =
|
||||
useEntityListProvider();
|
||||
|
||||
// Remove group items that aren't in availableFilters and exclude
|
||||
// any now-empty groups.
|
||||
const userAndGroupFilterIds = ['starred', 'all'];
|
||||
const filterGroups = getFilterGroups(orgName)
|
||||
.map(filterGroup => ({
|
||||
...filterGroup,
|
||||
items: filterGroup.items.filter(
|
||||
({ id }) => !availableFilters || availableFilters.includes(id),
|
||||
items: filterGroup.items.filter(({ id }) =>
|
||||
// TODO: avoid hardcoding kinds here
|
||||
['group', 'user'].some(kind => kind === queryParameters.kind)
|
||||
? userAndGroupFilterIds.includes(id)
|
||||
: !availableFilters || availableFilters.includes(id),
|
||||
),
|
||||
}))
|
||||
.filter(({ items }) => !!items.length);
|
||||
|
||||
const { filters, updateFilters, backendEntities, queryParameters } =
|
||||
useEntityListProvider();
|
||||
|
||||
const { isStarredEntity } = useStarredEntities();
|
||||
const { isOwnedEntity } = useEntityOwnership();
|
||||
const [selectedUserFilter, setSelectedUserFilter] = useState(
|
||||
[queryParameters.user].flat()[0] ?? initialFilter,
|
||||
);
|
||||
|
||||
// Static filters; used for generating counts of potentially unselected kinds
|
||||
const ownedFilter = useMemo(
|
||||
@@ -153,6 +153,19 @@ export const UserListPicker = ({
|
||||
[isOwnedEntity, isStarredEntity],
|
||||
);
|
||||
|
||||
// To show proper counts for each section, apply all other frontend filters _except_ the user
|
||||
// filter that's controlled by this picker.
|
||||
const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] =
|
||||
useState(backendEntities);
|
||||
const totalOwnedUserEntities = entitiesWithoutUserFilter.filter(entity =>
|
||||
ownedFilter.filterEntity(entity),
|
||||
).length;
|
||||
const [selectedUserFilter, setSelectedUserFilter] = useState(
|
||||
totalOwnedUserEntities > 0
|
||||
? [queryParameters.user].flat()[0] ?? initialFilter
|
||||
: 'all',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
updateFilters({
|
||||
user: selectedUserFilter
|
||||
@@ -165,10 +178,6 @@ export const UserListPicker = ({
|
||||
});
|
||||
}, [selectedUserFilter, isOwnedEntity, isStarredEntity, updateFilters]);
|
||||
|
||||
// To show proper counts for each section, apply all other frontend filters _except_ the user
|
||||
// filter that's controlled by this picker.
|
||||
const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] =
|
||||
useState(backendEntities);
|
||||
useEffect(() => {
|
||||
const filterFn = reduceEntityFilters(
|
||||
compact(Object.values({ ...filters, user: undefined })),
|
||||
@@ -179,9 +188,7 @@ export const UserListPicker = ({
|
||||
function getFilterCount(id: UserListFilterKind) {
|
||||
switch (id) {
|
||||
case 'owned':
|
||||
return entitiesWithoutUserFilter.filter(entity =>
|
||||
ownedFilter.filterEntity(entity),
|
||||
).length;
|
||||
return totalOwnedUserEntities;
|
||||
case 'starred':
|
||||
return entitiesWithoutUserFilter.filter(entity =>
|
||||
starredFilter.filterEntity(entity),
|
||||
|
||||
@@ -137,12 +137,27 @@ describe('<EntityListProvider />', () => {
|
||||
const { result, waitFor } = renderHook(() => useEntityListProvider(), {
|
||||
wrapper,
|
||||
initialProps: {
|
||||
userFilter: 'owned',
|
||||
userFilter: 'all',
|
||||
},
|
||||
});
|
||||
await waitFor(() => !!result.current.entities.length);
|
||||
expect(result.current.backendEntities.length).toBe(2);
|
||||
expect(result.current.entities.length).toBe(1);
|
||||
|
||||
act(() =>
|
||||
result.current.updateFilters({
|
||||
user: new UserListFilter(
|
||||
'owned',
|
||||
entity => entity.metadata.name === 'component-1',
|
||||
() => true,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.backendEntities.length).toBe(2);
|
||||
expect(result.current.entities.length).toBe(1);
|
||||
expect(mockCatalogApi.getEntities).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves query param filter values', async () => {
|
||||
|
||||
@@ -189,12 +189,14 @@ describe('CatalogPage', () => {
|
||||
c => c.tagName === 'SPAN',
|
||||
);
|
||||
const columnHeaderLabels = columnHeader.map(c => c.textContent);
|
||||
|
||||
expect(columnHeaderLabels).toEqual(['Foo', 'Bar', 'Baz', 'Actions']);
|
||||
}, 20_000);
|
||||
|
||||
it('should render the default actions of an item in the grid', async () => {
|
||||
const { findByTitle, findByText } = await renderWrapped(<CatalogPage />);
|
||||
const { getByTestId, findByTitle, findByText } = await renderWrapped(
|
||||
<CatalogPage />,
|
||||
);
|
||||
fireEvent.click(getByTestId('user-picker-owned'));
|
||||
expect(await findByText(/Owned \(1\)/)).toBeInTheDocument();
|
||||
expect(await findByTitle(/View/)).toBeInTheDocument();
|
||||
expect(await findByTitle(/Edit/)).toBeInTheDocument();
|
||||
@@ -221,9 +223,10 @@ describe('CatalogPage', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { findByTitle, findByText } = await renderWrapped(
|
||||
const { getByTestId, findByTitle, findByText } = await renderWrapped(
|
||||
<CatalogPage actions={actions} />,
|
||||
);
|
||||
fireEvent.click(getByTestId('user-picker-owned'));
|
||||
expect(await findByText(/Owned \(1\)/)).toBeInTheDocument();
|
||||
expect(await findByTitle(/Foo Action/)).toBeInTheDocument();
|
||||
expect(await findByTitle(/Bar Action/)).toBeInTheDocument();
|
||||
@@ -235,6 +238,7 @@ describe('CatalogPage', () => {
|
||||
// https://github.com/mbrn/material-table/issues/1293
|
||||
it('should render', async () => {
|
||||
const { findByText, getByTestId } = await renderWrapped(<CatalogPage />);
|
||||
fireEvent.click(getByTestId('user-picker-owned'));
|
||||
await expect(findByText(/Owned \(1\)/)).resolves.toBeInTheDocument();
|
||||
fireEvent.click(getByTestId('user-picker-all'));
|
||||
await expect(findByText(/All \(2\)/)).resolves.toBeInTheDocument();
|
||||
@@ -250,7 +254,8 @@ describe('CatalogPage', () => {
|
||||
// this test is for fixing the bug after favoriting an entity, the matching
|
||||
// entities defaulting to "owned" filter and not based on the selected filter
|
||||
it('should render the correct entities filtered on the selected filter', async () => {
|
||||
await renderWrapped(<CatalogPage />);
|
||||
const { getByTestId } = await renderWrapped(<CatalogPage />);
|
||||
fireEvent.click(getByTestId('user-picker-owned'));
|
||||
await expect(screen.findByText(/Owned \(1\)/)).resolves.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId('user-picker-starred'));
|
||||
await expect(
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { ProductInsights } from './ProductInsights';
|
||||
import { ProductInsightsOptions } from '../../api';
|
||||
import { costInsightsApiRef, ProductInsightsOptions } from '../../api';
|
||||
import {
|
||||
mockDefaultLoadingState,
|
||||
MockConfigProvider,
|
||||
MockCostInsightsApiProvider,
|
||||
MockCurrencyProvider,
|
||||
MockFilterProvider,
|
||||
MockBillingDateProvider,
|
||||
@@ -139,7 +138,7 @@ const costInsightsApi = {
|
||||
|
||||
function renderInContext(children: JSX.Element) {
|
||||
return renderInTestApp(
|
||||
<MockCostInsightsApiProvider costInsightsApi={costInsightsApi}>
|
||||
<TestApiProvider apis={[[costInsightsApiRef, costInsightsApi]]}>
|
||||
<MockConfigProvider>
|
||||
<MockFilterProvider>
|
||||
<MockCurrencyProvider>
|
||||
@@ -151,7 +150,7 @@ function renderInContext(children: JSX.Element) {
|
||||
</MockCurrencyProvider>
|
||||
</MockFilterProvider>
|
||||
</MockConfigProvider>
|
||||
</MockCostInsightsApiProvider>,
|
||||
</TestApiProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -15,15 +15,14 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { ProductInsightsCard } from './ProductInsightsCard';
|
||||
import { CostInsightsApi } from '../../api';
|
||||
import { CostInsightsApi, costInsightsApiRef } from '../../api';
|
||||
import {
|
||||
createMockEntity,
|
||||
mockDefaultLoadingState,
|
||||
MockComputeEngine,
|
||||
MockConfigProvider,
|
||||
MockCostInsightsApiProvider,
|
||||
MockCurrencyProvider,
|
||||
MockBillingDateProvider,
|
||||
MockScrollProvider,
|
||||
@@ -55,7 +54,7 @@ const renderProductInsightsCardInTestApp = async (
|
||||
onSelectAsync = jest.fn(() => Promise.resolve(mockProductCost)),
|
||||
) =>
|
||||
await renderInTestApp(
|
||||
<MockCostInsightsApiProvider costInsightsApi={costInsightsApi(entity)}>
|
||||
<TestApiProvider apis={[[costInsightsApiRef, costInsightsApi(entity)]]}>
|
||||
<MockConfigProvider>
|
||||
<MockCurrencyProvider>
|
||||
<MockLoadingProvider state={mockDefaultLoadingState}>
|
||||
@@ -71,7 +70,7 @@ const renderProductInsightsCardInTestApp = async (
|
||||
</MockLoadingProvider>
|
||||
</MockCurrencyProvider>
|
||||
</MockConfigProvider>
|
||||
</MockCostInsightsApiProvider>,
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
describe('<ProductInsightsCard/>', () => {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { costInsightsApiRef, CostInsightsApi } from '../api';
|
||||
import { LoadingContext, LoadingContextProps } from '../hooks/useLoading';
|
||||
import { GroupsContext, GroupsContextProps } from '../hooks/useGroups';
|
||||
import { FilterContext, FilterContextProps } from '../hooks/useFilters';
|
||||
@@ -28,12 +27,6 @@ import {
|
||||
import { ScrollContext, ScrollContextProps } from '../hooks/useScroll';
|
||||
import { Group, Duration } from '../types';
|
||||
|
||||
// TODO(Rugvip): Could be good to have a clear place to put test utils that is linted accordingly
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
type PartialPropsWithChildren<T> = PropsWithChildren<Partial<T>>;
|
||||
|
||||
export const MockGroups: Group[] = [{ id: 'tech' }, { id: 'mock-group' }];
|
||||
@@ -172,48 +165,3 @@ export const MockGroupsProvider = ({
|
||||
</GroupsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export type MockCostInsightsApiProviderProps = PartialPropsWithChildren<{
|
||||
identityApi: Partial<IdentityApi>;
|
||||
costInsightsApi: Partial<CostInsightsApi>;
|
||||
}>;
|
||||
|
||||
export const MockCostInsightsApiProvider = ({
|
||||
children,
|
||||
...context
|
||||
}: MockCostInsightsApiProviderProps) => {
|
||||
const defaultIdentityApi: IdentityApi = {
|
||||
getProfile: jest.fn(),
|
||||
getIdToken: jest.fn(),
|
||||
getUserId: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
};
|
||||
|
||||
const defaultCostInsightsApi: CostInsightsApi = {
|
||||
getAlerts: jest.fn(),
|
||||
getDailyMetricData: jest.fn(),
|
||||
getGroupDailyCost: jest.fn(),
|
||||
getGroupProjects: jest.fn(),
|
||||
getLastCompleteBillingDate: jest.fn(),
|
||||
getProductInsights: jest.fn(),
|
||||
getProjectDailyCost: jest.fn(),
|
||||
getUserGroups: jest.fn(),
|
||||
};
|
||||
|
||||
return (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, { ...defaultIdentityApi, ...context.identityApi }],
|
||||
[
|
||||
costInsightsApiRef,
|
||||
{ ...defaultCostInsightsApi, ...context.costInsightsApi },
|
||||
],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
"@backstage/backend-common": "^0.10.0",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@types/express": "^4.17.6",
|
||||
"axios": "^0.24.0",
|
||||
"camelcase-keys": "^6.2.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
@@ -44,12 +43,15 @@
|
||||
"helmet": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"morgan": "^1.10.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.3",
|
||||
"@backstage/test-utils": "^0.2.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"msw": "^0.36.3",
|
||||
"supertest": "^6.1.3"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getRequestHeaders } from './RollbarApi';
|
||||
import { getRequestHeaders, RollbarApi } from './RollbarApi';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { RollbarProject } from './types';
|
||||
|
||||
describe('RollbarApi', () => {
|
||||
describe('getRequestHeaders', () => {
|
||||
@@ -26,4 +31,31 @@ describe('RollbarApi', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllProjects', () => {
|
||||
const server = setupServer();
|
||||
setupRequestMockHandlers(server);
|
||||
|
||||
const mockBaseUrl = 'https://api.rollbar.com/api/1';
|
||||
|
||||
const mockProjects: RollbarProject[] = [
|
||||
{ id: 123, name: 'abc', accountId: 1, status: 'enabled' },
|
||||
{ id: 456, name: 'xyz', accountId: 1, status: 'enabled' },
|
||||
];
|
||||
|
||||
const setupHandlers = () => {
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/projects`, (_, res, ctx) => {
|
||||
return res(ctx.json({ result: mockProjects }));
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
it('should return all projects with a name attribute', async () => {
|
||||
setupHandlers();
|
||||
const api = new RollbarApi('my-access-token', getVoidLogger());
|
||||
const projects = await api.getAllProjects();
|
||||
expect(projects).toEqual(mockProjects);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { Logger } from 'winston';
|
||||
import camelcaseKeys from 'camelcase-keys';
|
||||
import { buildQuery } from '../util';
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
RollbarProjectAccessToken,
|
||||
RollbarTopActiveItem,
|
||||
} from './types';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const baseUrl = 'https://api.rollbar.com/api/1';
|
||||
|
||||
@@ -110,11 +110,12 @@ export class RollbarApi {
|
||||
this.logger.info(`Calling Rollbar REST API, ${fullUrl}`);
|
||||
}
|
||||
|
||||
return axios
|
||||
.get(fullUrl, getRequestHeaders(accessToken || this.accessToken || ''))
|
||||
.then(response =>
|
||||
camelcaseKeys<T>(response?.data?.result, { deep: true }),
|
||||
);
|
||||
return fetch(
|
||||
fullUrl,
|
||||
getRequestHeaders(accessToken || this.accessToken || ''),
|
||||
)
|
||||
.then(response => response.json())
|
||||
.then(json => camelcaseKeys<T>(json?.result, { deep: true }));
|
||||
}
|
||||
|
||||
private async getForProject<T>(
|
||||
|
||||
@@ -25,7 +25,11 @@ import { Schema } from 'jsonschema';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { SpawnOptionsWithoutStdio } from 'child_process';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { TaskSpecV1beta2 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateEntityV1beta2 } from '@backstage/catalog-model';
|
||||
import { TemplateMetadata } from '@backstage/plugin-scaffolder-common';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
@@ -432,52 +436,11 @@ export type TaskSecrets = {
|
||||
token: string | undefined;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
|
||||
export { TaskSpec };
|
||||
|
||||
// @public
|
||||
export interface TaskSpecV1beta2 {
|
||||
// (undocumented)
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
// (undocumented)
|
||||
baseUrl?: string;
|
||||
// (undocumented)
|
||||
metadata?: TemplateMetadata;
|
||||
// (undocumented)
|
||||
output: {
|
||||
[name: string]: string;
|
||||
};
|
||||
// (undocumented)
|
||||
steps: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
input?: JsonObject;
|
||||
if?: string | boolean;
|
||||
}>;
|
||||
// (undocumented)
|
||||
values: JsonObject;
|
||||
}
|
||||
export { TaskSpecV1beta2 };
|
||||
|
||||
// @public
|
||||
export interface TaskSpecV1beta3 {
|
||||
// (undocumented)
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3';
|
||||
// (undocumented)
|
||||
baseUrl?: string;
|
||||
// (undocumented)
|
||||
metadata?: TemplateMetadata;
|
||||
// (undocumented)
|
||||
output: {
|
||||
[name: string]: JsonValue;
|
||||
};
|
||||
// (undocumented)
|
||||
parameters: JsonObject;
|
||||
// Warning: (ae-forgotten-export) The symbol "TaskStep" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
steps: TaskStep[];
|
||||
}
|
||||
export { TaskSpecV1beta3 };
|
||||
|
||||
// @public
|
||||
export interface TaskState {
|
||||
@@ -573,8 +536,5 @@ export class TemplateActionRegistry {
|
||||
): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type TemplateMetadata = {
|
||||
name: string;
|
||||
};
|
||||
export { TemplateMetadata };
|
||||
```
|
||||
|
||||
@@ -15,6 +15,21 @@
|
||||
*/
|
||||
|
||||
import { JsonValue, JsonObject } from '@backstage/types';
|
||||
import {
|
||||
TaskSpec,
|
||||
TaskStep,
|
||||
TemplateMetadata,
|
||||
TaskSpecV1beta2,
|
||||
TaskSpecV1beta3,
|
||||
} from '@backstage/plugin-scaffolder-common';
|
||||
|
||||
export type {
|
||||
TaskSpec,
|
||||
TaskStep,
|
||||
TemplateMetadata,
|
||||
TaskSpecV1beta2,
|
||||
TaskSpecV1beta3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Status
|
||||
@@ -69,64 +84,6 @@ export type SerializedTaskEvent = {
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* TemplateMetadata
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TemplateMetadata = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* TaskSpecV1beta2
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskSpecV1beta2 {
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
baseUrl?: string;
|
||||
values: JsonObject;
|
||||
steps: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
input?: JsonObject;
|
||||
if?: string | boolean;
|
||||
}>;
|
||||
output: { [name: string]: string };
|
||||
metadata?: TemplateMetadata;
|
||||
}
|
||||
|
||||
export interface TaskStep {
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
input?: JsonObject;
|
||||
if?: string | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpecV1beta3
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskSpecV1beta3 {
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3';
|
||||
baseUrl?: string;
|
||||
parameters: JsonObject;
|
||||
steps: TaskStep[];
|
||||
output: { [name: string]: JsonValue };
|
||||
metadata?: TemplateMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpec
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
|
||||
|
||||
/**
|
||||
* TaskSecrets
|
||||
*
|
||||
|
||||
@@ -6,6 +6,60 @@
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
|
||||
// @public
|
||||
export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
|
||||
|
||||
// @public
|
||||
export interface TaskSpecV1beta2 {
|
||||
// (undocumented)
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
// (undocumented)
|
||||
baseUrl?: string;
|
||||
// (undocumented)
|
||||
metadata?: TemplateMetadata;
|
||||
// (undocumented)
|
||||
output: {
|
||||
[name: string]: string;
|
||||
};
|
||||
// (undocumented)
|
||||
steps: TaskStep[];
|
||||
// (undocumented)
|
||||
values: JsonObject;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TaskSpecV1beta3 {
|
||||
// (undocumented)
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3';
|
||||
// (undocumented)
|
||||
baseUrl?: string;
|
||||
// (undocumented)
|
||||
metadata?: TemplateMetadata;
|
||||
// (undocumented)
|
||||
output: {
|
||||
[name: string]: JsonValue;
|
||||
};
|
||||
// (undocumented)
|
||||
parameters: JsonObject;
|
||||
// (undocumented)
|
||||
steps: TaskStep[];
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TaskStep {
|
||||
// (undocumented)
|
||||
action: string;
|
||||
// (undocumented)
|
||||
id: string;
|
||||
// (undocumented)
|
||||
if?: string | boolean;
|
||||
// (undocumented)
|
||||
input?: JsonObject;
|
||||
// (undocumented)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TemplateEntityV1beta3 extends Entity {
|
||||
@@ -33,4 +87,9 @@ export interface TemplateEntityV1beta3 extends Entity {
|
||||
|
||||
// @public (undocumented)
|
||||
export const templateEntityV1beta3Schema: JSONSchema;
|
||||
|
||||
// @public
|
||||
export type TemplateMetadata = {
|
||||
name: string;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonValue, JsonObject } from '@backstage/types';
|
||||
|
||||
/**
|
||||
* TemplateMetadata
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TemplateMetadata = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* TaskStep
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskStep {
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
input?: JsonObject;
|
||||
if?: string | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpecV1beta2
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskSpecV1beta2 {
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
baseUrl?: string;
|
||||
values: JsonObject;
|
||||
steps: TaskStep[];
|
||||
output: { [name: string]: string };
|
||||
metadata?: TemplateMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpecV1beta3
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskSpecV1beta3 {
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3';
|
||||
baseUrl?: string;
|
||||
parameters: JsonObject;
|
||||
steps: TaskStep[];
|
||||
output: { [name: string]: JsonValue };
|
||||
metadata?: TemplateMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* TaskSpec
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3;
|
||||
@@ -30,3 +30,5 @@ export const templateEntityV1beta3Schema: JSONSchema = v1beta3Schema as Omit<
|
||||
JSONSchema,
|
||||
'examples'
|
||||
>;
|
||||
|
||||
export * from './TaskSpec';
|
||||
|
||||
@@ -21,12 +21,12 @@ import { IconButton } from '@material-ui/core';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateEntityV1beta2 } from '@backstage/catalog-model';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createScaffolderFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"@backstage/integration": "^0.6.10",
|
||||
"@backstage/integration-react": "^0.1.16",
|
||||
"@backstage/plugin-catalog-react": "^0.6.8",
|
||||
"@backstage/plugin-scaffolder-common": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
|
||||
@@ -22,8 +22,10 @@ import {
|
||||
Page,
|
||||
LogViewer,
|
||||
} from '@backstage/core-components';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Paper,
|
||||
StepButton,
|
||||
@@ -40,9 +42,11 @@ import Check from '@material-ui/icons/Check';
|
||||
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
|
||||
import classNames from 'classnames';
|
||||
import { DateTime, Interval } from 'luxon';
|
||||
import qs from 'qs';
|
||||
import React, { memo, useEffect, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { generatePath, useNavigate, useParams } from 'react-router';
|
||||
import { useInterval } from 'react-use';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { Status, TaskOutput } from '../../types';
|
||||
import { useTaskEventStream } from '../hooks/useEventStream';
|
||||
import { TaskPageLinks } from './TaskPageLinks';
|
||||
@@ -56,8 +60,8 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
width: '100%',
|
||||
},
|
||||
button: {
|
||||
marginTop: theme.spacing(1),
|
||||
marginRight: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
marginLeft: theme.spacing(2),
|
||||
},
|
||||
actionsContainer: {
|
||||
marginBottom: theme.spacing(2),
|
||||
@@ -215,6 +219,9 @@ const hasLinks = ({ entityRef, remoteUrl, links = [] }: TaskOutput): boolean =>
|
||||
!!(entityRef || remoteUrl || links.length > 0);
|
||||
|
||||
export const TaskPage = () => {
|
||||
const classes = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const rootLink = useRouteRef(rootRouteRef);
|
||||
const [userSelectedStepId, setUserSelectedStepId] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
@@ -266,6 +273,26 @@ export const TaskPage = () => {
|
||||
|
||||
const { output } = taskStream;
|
||||
|
||||
const handleStartOver = () => {
|
||||
if (!taskStream.task || !taskStream.task?.spec.metadata?.name) {
|
||||
navigate(generatePath(rootLink()));
|
||||
}
|
||||
|
||||
const formData =
|
||||
taskStream.task!.spec.apiVersion === 'backstage.io/v1beta2'
|
||||
? taskStream.task!.spec.values
|
||||
: taskStream.task!.spec.parameters;
|
||||
|
||||
navigate(
|
||||
generatePath(
|
||||
`${rootLink()}/templates/:templateName?${qs.stringify({ formData })}`,
|
||||
{
|
||||
templateName: taskStream.task!.spec.metadata!.name,
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
@@ -297,6 +324,15 @@ export const TaskPage = () => {
|
||||
{output && hasLinks(output) && (
|
||||
<TaskPageLinks output={output} />
|
||||
)}
|
||||
<Button
|
||||
className={classes.button}
|
||||
onClick={handleStartOver}
|
||||
disabled={!completed}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Start Over
|
||||
</Button>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
import { FormValidation, IChangeEvent } from '@rjsf/core';
|
||||
import qs from 'qs';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { generatePath, Navigate, useNavigate } from 'react-router';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -120,7 +121,13 @@ export const TemplatePage = ({
|
||||
const navigate = useNavigate();
|
||||
const rootLink = useRouteRef(rootRouteRef);
|
||||
const { schema, loading, error } = useTemplateParameterSchema(templateName);
|
||||
const [formState, setFormState] = useState({});
|
||||
const [formState, setFormState] = useState<Record<string, any>>(() => {
|
||||
const query = qs.parse(window.location.search, {
|
||||
ignoreQueryPrefix: true,
|
||||
});
|
||||
|
||||
return query.formData ?? {};
|
||||
});
|
||||
const handleFormReset = () => setFormState({});
|
||||
const handleChange = useCallback(
|
||||
(e: IChangeEvent) => setFormState(e.formData),
|
||||
@@ -129,6 +136,18 @@ export const TemplatePage = ({
|
||||
|
||||
const handleCreate = async () => {
|
||||
const id = await scaffolderApi.scaffold(templateName, formState);
|
||||
|
||||
const formParams = qs.stringify(
|
||||
{ formData: formState },
|
||||
{ addQueryPrefix: true },
|
||||
);
|
||||
const newUrl = `${window.location.pathname}${formParams}`;
|
||||
// We use direct history manipulation since useSearchParams and
|
||||
// useNavigate in react-router-dom cause unnecessary extra rerenders.
|
||||
// Also make sure to replace the state rather than pushing to avoid
|
||||
// extra back/forward slots.
|
||||
window.history?.replaceState(null, document.title, newUrl);
|
||||
|
||||
navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId: id }));
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { JSONSchema } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
|
||||
export type Status = 'open' | 'processing' | 'failed' | 'completed' | 'skipped';
|
||||
export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED';
|
||||
@@ -39,18 +39,9 @@ export type Stage = {
|
||||
endedAt?: string;
|
||||
};
|
||||
|
||||
export type ScaffolderStep = {
|
||||
id: string;
|
||||
name: string;
|
||||
action: string;
|
||||
parameters?: { [name: string]: JsonValue };
|
||||
};
|
||||
|
||||
export type ScaffolderTask = {
|
||||
id: string;
|
||||
spec: {
|
||||
steps: ScaffolderStep[];
|
||||
};
|
||||
spec: TaskSpec;
|
||||
status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled';
|
||||
lastHeartbeatAt: string;
|
||||
createdAt: string;
|
||||
|
||||
+3
-3
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@backstage/core-components';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import FindInPageIcon from '@material-ui/icons/FindInPage';
|
||||
import GroupIcon from '@material-ui/icons/Group';
|
||||
import { Button } from '@backstage/core-components';
|
||||
import { DefaultResultListItem } from '../index';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { DefaultResultListItem } from './DefaultResultListItem';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/DefaultResultListItem',
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDebounce } from 'react-use';
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Grid, makeStyles, Paper } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { Paper, Grid, makeStyles } from '@material-ui/core';
|
||||
import { SearchBar } from '../index';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchBar } from './SearchBar';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchBar',
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType } from 'react';
|
||||
import { Grid, Paper } from '@material-ui/core';
|
||||
import { SearchFilter } from '../index';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchFilter } from './SearchFilter';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchFilter',
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType } from 'react';
|
||||
import { Button } from '@material-ui/core';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { SearchModal } from '../index';
|
||||
import { useSearch } from '../SearchContext';
|
||||
import { Button } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { rootRouteRef } from '../../plugin';
|
||||
import { useSearch } from '../SearchContext';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchModal } from './SearchModal';
|
||||
|
||||
const mockResults = {
|
||||
results: [
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { useLocation, useOutlet } from 'react-router';
|
||||
import { useSearch } from '../SearchContext';
|
||||
import { SearchPage } from './';
|
||||
import { SearchPage } from './SearchPage';
|
||||
|
||||
jest.mock('react-router', () => ({
|
||||
...jest.requireActual('react-router'),
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType } from 'react';
|
||||
import { List, ListItem } from '@material-ui/core';
|
||||
import { SearchResult, DefaultResultListItem } from '../index';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { Link } from '@backstage/core-components';
|
||||
import { List, ListItem } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { DefaultResultListItem } from '../DefaultResultListItem';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchResult } from './SearchResult';
|
||||
|
||||
const mockResults = {
|
||||
results: [
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { ComponentType } from 'react';
|
||||
|
||||
import { Grid, Paper } from '@material-ui/core';
|
||||
import CatalogIcon from '@material-ui/icons/MenuBook';
|
||||
import DocsIcon from '@material-ui/icons/Description';
|
||||
import UsersGroupsIcon from '@material-ui/icons/Person';
|
||||
|
||||
import { SearchType } from '../index';
|
||||
import React, { ComponentType } from 'react';
|
||||
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
|
||||
import { SearchType } from './SearchType';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchType',
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './DefaultResultListItem';
|
||||
export * from './Filters';
|
||||
export * from './SearchBar';
|
||||
export * from './SearchContext';
|
||||
export * from './SearchFilter';
|
||||
export * from './SearchModal';
|
||||
export * from './SearchPage';
|
||||
export * from './SearchResult';
|
||||
export * from './SearchResultPager';
|
||||
export * from './SearchType';
|
||||
export * from './SidebarSearch';
|
||||
export * from './SidebarSearchModal';
|
||||
export * from './HomePageComponent';
|
||||
+20
-22
@@ -22,32 +22,30 @@
|
||||
|
||||
export { searchApiRef } from './apis';
|
||||
export type { SearchApi } from './apis';
|
||||
export {
|
||||
Filters,
|
||||
FiltersButton,
|
||||
SearchBar,
|
||||
SearchBarBase,
|
||||
SearchContextProvider,
|
||||
SearchFilter,
|
||||
SearchFilterNext,
|
||||
SearchModal,
|
||||
SearchPage as Router,
|
||||
SearchResultPager,
|
||||
SearchType,
|
||||
SidebarSearch,
|
||||
useSearch,
|
||||
} from './components';
|
||||
|
||||
export { Filters, FiltersButton } from './components/Filters';
|
||||
export type { FiltersState } from './components/Filters';
|
||||
export type { HomePageSearchBarProps } from './components/HomePageComponent';
|
||||
export { SearchBar, SearchBarBase } from './components/SearchBar';
|
||||
export type {
|
||||
SearchModalProps,
|
||||
SidebarSearchModalProps,
|
||||
HomePageSearchBarProps,
|
||||
SidebarSearchProps,
|
||||
FiltersState,
|
||||
SearchBarProps,
|
||||
SearchBarBaseProps,
|
||||
SearchBarProps,
|
||||
} from './components/SearchBar';
|
||||
export { SearchContextProvider, useSearch } from './components/SearchContext';
|
||||
export { SearchFilter, SearchFilterNext } from './components/SearchFilter';
|
||||
export { SearchModal } from './components/SearchModal';
|
||||
export type { SearchModalProps } from './components/SearchModal';
|
||||
export { SearchPage as Router } from './components/SearchPage';
|
||||
export { SearchResultPager } from './components/SearchResultPager';
|
||||
export { SearchType } from './components/SearchType';
|
||||
export type {
|
||||
SearchTypeAccordionProps,
|
||||
SearchTypeProps,
|
||||
} from './components';
|
||||
} from './components/SearchType';
|
||||
export { SidebarSearch } from './components/SidebarSearch';
|
||||
export type { SidebarSearchProps } from './components/SidebarSearch';
|
||||
export type { SidebarSearchModalProps } from './components/SidebarSearchModal';
|
||||
|
||||
export {
|
||||
DefaultResultListItem,
|
||||
HomePageSearchBar,
|
||||
|
||||
@@ -85,3 +85,34 @@ export const exampleCheck: TechInsightJsonRuleCheck = {
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
# Custom operators
|
||||
|
||||
json-rules-engine supports a limited [number of built-in operators](https://github.com/CacheControl/json-rules-engine/blob/master/docs/rules.md#operators) that can be used in conditions. You can add your own operators by adding them to the `operators` array in the `JsonRulesEngineFactCheckerFactory` constructor. For example:
|
||||
|
||||
```diff
|
||||
+ import { Operator } from 'json-rules-engine';
|
||||
|
||||
const myFactCheckerFactory = new JsonRulesEngineFactCheckerFactory({
|
||||
checks: [],
|
||||
logger,
|
||||
+ operators: [ new Operator("startsWith", (a, b) => a.startsWith(b) ]
|
||||
})
|
||||
```
|
||||
|
||||
And you can then use it in your checks like this:
|
||||
|
||||
```js
|
||||
...
|
||||
rule: {
|
||||
conditions: {
|
||||
any: [
|
||||
{
|
||||
fact: 'version',
|
||||
operator: 'startsWith',
|
||||
value: '12',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CheckResponse } from '@backstage/plugin-tech-insights-common';
|
||||
import { CheckValidationResponse } from '@backstage/plugin-tech-insights-node';
|
||||
import { FactChecker } from '@backstage/plugin-tech-insights-node';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
import { Operator } from 'json-rules-engine';
|
||||
import { TechInsightCheck } from '@backstage/plugin-tech-insights-node';
|
||||
import { TechInsightCheckRegistry } from '@backstage/plugin-tech-insights-node';
|
||||
import { TechInsightsStore } from '@backstage/plugin-tech-insights-node';
|
||||
@@ -50,6 +51,7 @@ export class JsonRulesEngineFactChecker
|
||||
repository,
|
||||
logger,
|
||||
checkRegistry,
|
||||
operators,
|
||||
}: JsonRulesEngineFactCheckerOptions);
|
||||
// (undocumented)
|
||||
getChecks(): Promise<TechInsightJsonRuleCheck[]>;
|
||||
@@ -68,6 +70,7 @@ export class JsonRulesEngineFactCheckerFactory {
|
||||
checks,
|
||||
logger,
|
||||
checkRegistry,
|
||||
operators,
|
||||
}: JsonRulesEngineFactCheckerFactoryOptions);
|
||||
// (undocumented)
|
||||
construct(repository: TechInsightsStore): JsonRulesEngineFactChecker;
|
||||
@@ -78,6 +81,7 @@ export type JsonRulesEngineFactCheckerFactoryOptions = {
|
||||
checks: TechInsightJsonRuleCheck[];
|
||||
logger: Logger_2;
|
||||
checkRegistry?: TechInsightCheckRegistry<TechInsightJsonRuleCheck>;
|
||||
operators?: Operator[];
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -86,6 +90,7 @@ export type JsonRulesEngineFactCheckerOptions = {
|
||||
repository: TechInsightsStore;
|
||||
logger: Logger_2;
|
||||
checkRegistry?: TechInsightCheckRegistry<any>;
|
||||
operators?: Operator[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
+99
-11
@@ -24,6 +24,7 @@ import {
|
||||
} from '../index';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { TechInsightJsonRuleCheck } from '../types';
|
||||
import { Operator } from 'json-rules-engine';
|
||||
|
||||
const testChecks: Record<string, TechInsightJsonRuleCheck[]> = {
|
||||
broken: [
|
||||
@@ -127,6 +128,49 @@ const testChecks: Record<string, TechInsightJsonRuleCheck[]> = {
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
customOperator: [
|
||||
{
|
||||
id: 'customOperatorTestCheck',
|
||||
name: 'customOperatorTestCheck',
|
||||
type: JSON_RULE_ENGINE_CHECK_TYPE,
|
||||
description: 'Check For Testing using Custom Operator',
|
||||
factIds: ['test-factretriever'],
|
||||
rule: {
|
||||
conditions: {
|
||||
all: [
|
||||
{
|
||||
fact: 'testnumberfact',
|
||||
operator: 'isDivisibleBy',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
invalidCustomOperator: [
|
||||
{
|
||||
id: 'invalidCustomOperatorTestCheck',
|
||||
name: 'invalidCustomOperatorTestCheck',
|
||||
type: JSON_RULE_ENGINE_CHECK_TYPE,
|
||||
description:
|
||||
'Check For Testing using a Custom Operator that is not registered',
|
||||
factIds: ['test-factretriever'],
|
||||
rule: {
|
||||
conditions: {
|
||||
all: [
|
||||
{
|
||||
fact: 'testnumberfact',
|
||||
operator: 'isOdd',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const latestSchemasMock = jest.fn().mockImplementation(() => [
|
||||
@@ -166,6 +210,9 @@ describe('JsonRulesEngineFactChecker', () => {
|
||||
const factChecker = new JsonRulesEngineFactCheckerFactory({
|
||||
checkRegistry: mockCheckRegistry,
|
||||
checks: [],
|
||||
operators: [
|
||||
new Operator<number, number>('isDivisibleBy', (a, b) => a % b === 0),
|
||||
],
|
||||
logger: getVoidLogger(),
|
||||
}).construct(mockRepository);
|
||||
|
||||
@@ -234,6 +281,42 @@ describe('JsonRulesEngineFactChecker', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should use custom operators when defined', async () => {
|
||||
const results = await factChecker.runChecks('a/a/a', ['customOperator']);
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0]).toMatchObject({
|
||||
facts: {
|
||||
testnumberfact: {
|
||||
value: 3,
|
||||
type: 'integer',
|
||||
description: '',
|
||||
},
|
||||
},
|
||||
result: false,
|
||||
check: {
|
||||
id: 'customOperatorTestCheck',
|
||||
type: JSON_RULE_ENGINE_CHECK_TYPE,
|
||||
name: 'customOperatorTestCheck',
|
||||
description: 'Check For Testing using Custom Operator',
|
||||
factIds: ['test-factretriever'],
|
||||
rule: {
|
||||
conditions: {
|
||||
all: [
|
||||
{
|
||||
fact: 'testnumberfact',
|
||||
factResult: 3,
|
||||
operator: 'isDivisibleBy',
|
||||
result: false,
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should gracefully handle multiple check at once', async () => {
|
||||
const results = await factChecker.runChecks('a/a/a', [
|
||||
'simple',
|
||||
@@ -307,17 +390,22 @@ describe('JsonRulesEngineFactChecker', () => {
|
||||
});
|
||||
|
||||
describe('when validating checks', () => {
|
||||
it('should succeed on valid rules', async () => {
|
||||
const validationResponse = await factChecker.validate(
|
||||
testChecks.simple[0],
|
||||
);
|
||||
expect(validationResponse.valid).toBeTruthy();
|
||||
});
|
||||
it('should fail on broken rules', async () => {
|
||||
const validationResponse = await factChecker.validate(
|
||||
testChecks.broken[0],
|
||||
);
|
||||
expect(validationResponse.valid).toBeFalsy();
|
||||
[testChecks.simple[0], testChecks.customOperator[0]].forEach(check => {
|
||||
it(`should succeed on valid rule: ${check.name}`, async () => {
|
||||
const validationResponse = await factChecker.validate(check);
|
||||
expect(validationResponse.valid).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
[testChecks.broken[0], testChecks.invalidCustomOperator[0]].forEach(
|
||||
check => {
|
||||
it(`should fail on broken rules: ${check.name}`, async () => {
|
||||
const validationResponse = await factChecker.validate(
|
||||
testChecks.broken[0],
|
||||
);
|
||||
expect(validationResponse.valid).toBeFalsy();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+30
-3
@@ -23,11 +23,16 @@ import {
|
||||
CheckValidationResponse,
|
||||
} from '@backstage/plugin-tech-insights-node';
|
||||
import { FactResponse } from '@backstage/plugin-tech-insights-common';
|
||||
import { Engine, EngineResult, TopLevelCondition } from 'json-rules-engine';
|
||||
import {
|
||||
Engine,
|
||||
EngineResult,
|
||||
Operator,
|
||||
TopLevelCondition,
|
||||
} from 'json-rules-engine';
|
||||
import { DefaultCheckRegistry } from './CheckRegistry';
|
||||
import { Logger } from 'winston';
|
||||
import { pick } from 'lodash';
|
||||
import Ajv from 'ajv';
|
||||
import Ajv, { SchemaObject } from 'ajv';
|
||||
import * as validationSchema from './validation-schema.json';
|
||||
import { JSON_RULE_ENGINE_CHECK_TYPE } from '../constants';
|
||||
|
||||
@@ -46,6 +51,7 @@ export type JsonRulesEngineFactCheckerOptions = {
|
||||
repository: TechInsightsStore;
|
||||
logger: Logger;
|
||||
checkRegistry?: TechInsightCheckRegistry<any>;
|
||||
operators?: Operator[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,15 +66,27 @@ export class JsonRulesEngineFactChecker
|
||||
private readonly checkRegistry: TechInsightCheckRegistry<TechInsightJsonRuleCheck>;
|
||||
private repository: TechInsightsStore;
|
||||
private readonly logger: Logger;
|
||||
private readonly validationSchema: SchemaObject;
|
||||
private readonly operators: Operator[];
|
||||
|
||||
constructor({
|
||||
checks,
|
||||
repository,
|
||||
logger,
|
||||
checkRegistry,
|
||||
operators,
|
||||
}: JsonRulesEngineFactCheckerOptions) {
|
||||
this.repository = repository;
|
||||
this.logger = logger;
|
||||
this.operators = operators || [];
|
||||
this.validationSchema = JSON.parse(JSON.stringify(validationSchema));
|
||||
|
||||
this.operators.forEach(op => {
|
||||
this.validationSchema.definitions.condition.properties.operator.anyOf.push(
|
||||
{ const: op.name },
|
||||
);
|
||||
});
|
||||
|
||||
checks.forEach(check => this.validate(check));
|
||||
this.checkRegistry =
|
||||
checkRegistry ??
|
||||
@@ -80,6 +98,10 @@ export class JsonRulesEngineFactChecker
|
||||
checks?: string[],
|
||||
): Promise<JsonRuleBooleanCheckResult[]> {
|
||||
const engine = new Engine();
|
||||
this.operators.forEach(op => {
|
||||
engine.addOperator(op);
|
||||
});
|
||||
|
||||
const techInsightChecks = checks
|
||||
? await this.checkRegistry.getAll(checks)
|
||||
: await this.checkRegistry.list();
|
||||
@@ -125,7 +147,7 @@ export class JsonRulesEngineFactChecker
|
||||
check: TechInsightJsonRuleCheck,
|
||||
): Promise<CheckValidationResponse> {
|
||||
const ajv = new Ajv({ verbose: true });
|
||||
const validator = ajv.compile(validationSchema);
|
||||
const validator = ajv.compile(this.validationSchema);
|
||||
const isValidToSchema = validator(check.rule);
|
||||
if (check.type !== JSON_RULE_ENGINE_CHECK_TYPE) {
|
||||
const msg = `Only ${JSON_RULE_ENGINE_CHECK_TYPE} checks can be registered to this fact checker`;
|
||||
@@ -317,6 +339,7 @@ export type JsonRulesEngineFactCheckerFactoryOptions = {
|
||||
checks: TechInsightJsonRuleCheck[];
|
||||
logger: Logger;
|
||||
checkRegistry?: TechInsightCheckRegistry<TechInsightJsonRuleCheck>;
|
||||
operators?: Operator[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -330,15 +353,18 @@ export class JsonRulesEngineFactCheckerFactory {
|
||||
private readonly checks: TechInsightJsonRuleCheck[];
|
||||
private readonly logger: Logger;
|
||||
private readonly checkRegistry?: TechInsightCheckRegistry<TechInsightJsonRuleCheck>;
|
||||
private readonly operators?: Operator[];
|
||||
|
||||
constructor({
|
||||
checks,
|
||||
logger,
|
||||
checkRegistry,
|
||||
operators,
|
||||
}: JsonRulesEngineFactCheckerFactoryOptions) {
|
||||
this.logger = logger;
|
||||
this.checks = checks;
|
||||
this.checkRegistry = checkRegistry;
|
||||
this.operators = operators;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -352,6 +378,7 @@ export class JsonRulesEngineFactCheckerFactory {
|
||||
logger: this.logger,
|
||||
checkRegistry: this.checkRegistry,
|
||||
repository,
|
||||
operators: this.operators,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,49 @@
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { CheckResult } from '@backstage/plugin-tech-insights-common';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { default as React_2 } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// @public
|
||||
export type Check = {
|
||||
id: string;
|
||||
type: string;
|
||||
name: string;
|
||||
description: string;
|
||||
factIds: string[];
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CheckResultRenderer = {
|
||||
type: string;
|
||||
title: string;
|
||||
description: string;
|
||||
component: React_2.ReactElement;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const EntityTechInsightsScorecardContent: () => JSX.Element;
|
||||
|
||||
// @public
|
||||
export interface TechInsightsApi {
|
||||
// (undocumented)
|
||||
getAllChecks(): Promise<Check[]>;
|
||||
// (undocumented)
|
||||
getScorecardsDefinition: (
|
||||
type: string,
|
||||
value: CheckResult[],
|
||||
) => CheckResultRenderer | undefined;
|
||||
// (undocumented)
|
||||
runChecks(entityParams: EntityName, checks?: Check[]): Promise<CheckResult[]>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const techInsightsApiRef: ApiRef<TechInsightsApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const techInsightsPlugin: BackstagePlugin<
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -20,10 +20,20 @@ import { Check } from './types';
|
||||
import { CheckResultRenderer } from '../components/CheckResultRenderer';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
|
||||
/**
|
||||
* {@link @backstage/core-plugin-api#ApiRef} for the {@link TechInsightsApi}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const techInsightsApiRef = createApiRef<TechInsightsApi>({
|
||||
id: 'plugin.techinsights.service',
|
||||
});
|
||||
|
||||
/**
|
||||
* API client interface for the Tech Insights plugin
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TechInsightsApi {
|
||||
getScorecardsDefinition: (
|
||||
type: string,
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a single check defined on the TechInsights backend.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type Check = {
|
||||
id: string;
|
||||
type: string;
|
||||
|
||||
@@ -18,6 +18,11 @@ import { CheckResult } from '@backstage/plugin-tech-insights-common';
|
||||
import React from 'react';
|
||||
import { BooleanCheck } from './BooleanCheck';
|
||||
|
||||
/**
|
||||
* Defines a react component that is responsible for rendering a results of a given type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CheckResultRenderer = {
|
||||
type: string;
|
||||
title: string;
|
||||
|
||||
@@ -17,3 +17,8 @@ export {
|
||||
techInsightsPlugin,
|
||||
EntityTechInsightsScorecardContent,
|
||||
} from './plugin';
|
||||
|
||||
export { techInsightsApiRef } from './api/TechInsightsApi';
|
||||
export type { TechInsightsApi } from './api/TechInsightsApi';
|
||||
export type { Check } from './api/types';
|
||||
export type { CheckResultRenderer } from './components/CheckResultRenderer';
|
||||
|
||||
@@ -123,6 +123,11 @@ export class DefaultTechDocsCollator implements DocumentCollator {
|
||||
techDocsBaseUrl,
|
||||
entityInfo,
|
||||
),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const searchIndex = await searchIndexResponse.json();
|
||||
|
||||
|
||||
@@ -127,15 +127,7 @@
|
||||
resolved "https://registry.npmjs.org/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7"
|
||||
integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg==
|
||||
|
||||
"@azure/core-auth@^1.1.3":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.1.4.tgz#af9a334acf3cb9c49e6013e6caf6dc9d43476030"
|
||||
integrity sha512-+j1embyH1jqf04AIfJPdLafd5SC1y6z1Jz4i+USR1XkTp6KM8P5u4/AjmWMVoEQdM/M29PJcRDZcCEWjK9S1bw==
|
||||
dependencies:
|
||||
"@azure/abort-controller" "^1.0.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@azure/core-auth@^1.3.0":
|
||||
"@azure/core-auth@^1.1.3", "@azure/core-auth@^1.3.0":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0"
|
||||
integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA==
|
||||
@@ -225,6 +217,14 @@
|
||||
"@opentelemetry/api" "^1.0.0"
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/core-tracing@1.0.0-preview.13":
|
||||
version "1.0.0-preview.13"
|
||||
resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644"
|
||||
integrity sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==
|
||||
dependencies:
|
||||
"@opentelemetry/api" "^1.0.1"
|
||||
tslib "^2.2.0"
|
||||
|
||||
"@azure/core-tracing@1.0.0-preview.9":
|
||||
version "1.0.0-preview.9"
|
||||
resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.9.tgz#84f3b85572013f9d9b85e1e5d89787aa180787eb"
|
||||
@@ -234,29 +234,35 @@
|
||||
"@opentelemetry/api" "^0.10.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@azure/identity@^1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/@azure/identity/-/identity-1.5.0.tgz#0ac832b95adaac00b4718d92b43b2c9c5ab42d2d"
|
||||
integrity sha512-djgywuWtX6720seqNOPmGM1hY54oHnjRT0MLIOzacMARTZuEtAIaFFvMPBlUIMQdtSGhdjH+/MS1/9PE8j83eA==
|
||||
"@azure/core-util@^1.0.0-beta.1":
|
||||
version "1.0.0-beta.1"
|
||||
resolved "https://registry.npmjs.org/@azure/core-util/-/core-util-1.0.0-beta.1.tgz#2efd2c74b4b0a38180369f50fe274a3c4cd36e98"
|
||||
integrity sha512-pS6cup979/qyuyNP9chIybK2qVkJ3MarbY/bx3JcGKE6An6dRweLnsfJfU2ydqUI/B51Rjnn59ajHIhCUTwWZw==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@azure/identity@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/@azure/identity/-/identity-2.0.1.tgz#31107506371e520bc874647a9e4384cfd2f85103"
|
||||
integrity sha512-gdGGuLKlKIQaf2RefA84keoBfmWfiAntbW2SzcdKvwLSGzsio/qkyY3sYUpXRz/sqLDxguuimgZukp7TPgwIlg==
|
||||
dependencies:
|
||||
"@azure/abort-controller" "^1.0.0"
|
||||
"@azure/core-auth" "^1.3.0"
|
||||
"@azure/core-client" "^1.0.0"
|
||||
"@azure/core-rest-pipeline" "^1.1.0"
|
||||
"@azure/core-tracing" "1.0.0-preview.12"
|
||||
"@azure/core-tracing" "1.0.0-preview.13"
|
||||
"@azure/core-util" "^1.0.0-beta.1"
|
||||
"@azure/logger" "^1.0.0"
|
||||
"@azure/msal-node" "1.0.0-beta.6"
|
||||
"@azure/msal-browser" "^2.16.0"
|
||||
"@azure/msal-common" "^4.5.1"
|
||||
"@azure/msal-node" "^1.3.0"
|
||||
"@types/stoppable" "^1.1.0"
|
||||
axios "^0.21.1"
|
||||
events "^3.0.0"
|
||||
jws "^4.0.0"
|
||||
msal "^1.0.2"
|
||||
open "^7.0.0"
|
||||
qs "^6.7.0"
|
||||
open "^8.0.0"
|
||||
stoppable "^1.1.0"
|
||||
tslib "^2.0.0"
|
||||
tslib "^2.2.0"
|
||||
uuid "^8.3.0"
|
||||
optionalDependencies:
|
||||
keytar "^7.3.0"
|
||||
|
||||
"@azure/logger@^1.0.0":
|
||||
version "1.0.1"
|
||||
@@ -265,36 +271,33 @@
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@azure/msal-common@^4.0.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-4.4.0.tgz#818526042f78838ebc332fb735e7de64d8bccb45"
|
||||
integrity sha512-Qrs33Ctt2KM7NxArFPIUKc8UbIcm7zYxJFdJeQ9k7HKBhVk3e88CUz1Mw33cS/Jr+YA1H02OAzHg++bJ+4SFyQ==
|
||||
"@azure/msal-browser@^2.16.0":
|
||||
version "2.20.0"
|
||||
resolved "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.20.0.tgz#78e34395048c4a8842400d4168b2fb3bdd3c854e"
|
||||
integrity sha512-Fl8boo38fPNlEm84fRCulbTfHJo+Z/i+1gcdJTG+PqmrkMOUVTdpkwznGh6ZQdAM34uumEgzukmqMr8lVKrytA==
|
||||
dependencies:
|
||||
"@azure/msal-common" "^5.2.0"
|
||||
|
||||
"@azure/msal-common@^4.5.1":
|
||||
version "4.5.1"
|
||||
resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-4.5.1.tgz#f35af8b634ae24aebd0906deb237c0db1afa5826"
|
||||
integrity sha512-/i5dXM+QAtO+6atYd5oHGBAx48EGSISkXNXViheliOQe+SIFMDo3gSq3lL54W0suOSAsVPws3XnTaIHlla0PIQ==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
|
||||
"@azure/msal-common@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-5.0.1.tgz#234030b340cc63575e84190b96a8a336b69c7698"
|
||||
integrity sha512-CmPR3XM9+CGUu7V/+bAwDxyN6XqWJJhVLmv7utT3sbgay4l5roVXsD1t4wURTs8PwzxmmnJOrhvvGhoDxUW69g==
|
||||
"@azure/msal-common@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-5.2.0.tgz#49440e04f4d0961fc5a1a1718fbe5e4eae2db5db"
|
||||
integrity sha512-oVc4soy5MEZOp9NvCDqBk57mtiUTJXQQ8Z8S/4UiRQP8RG8snuCFQUs9xxdIfvl2FWIvgiBz+SMByyjTaRX42Q==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
|
||||
"@azure/msal-node@1.0.0-beta.6":
|
||||
version "1.0.0-beta.6"
|
||||
resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-beta.6.tgz#da6bc3a3a861057c85586055960e069f162548ee"
|
||||
integrity sha512-ZQI11Uz1j0HJohb9JZLRD8z0moVcPks1AFW4Q/Gcl67+QvH4aKEJti7fjCcipEEZYb/qzLSO8U6IZgPYytsiJQ==
|
||||
"@azure/msal-node@^1.1.0", "@azure/msal-node@^1.3.0":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.4.0.tgz#660685804fbdc533b10cc699f16323e27ec582c6"
|
||||
integrity sha512-Ek6hqOFUi5QEAxZ55awM8y1N+9SzS9Qh8ijF4RDLtFuHzqP7xXmMnVC1lae45FlH55DUOo7dg/smuDJnb4kw6g==
|
||||
dependencies:
|
||||
"@azure/msal-common" "^4.0.0"
|
||||
axios "^0.21.1"
|
||||
jsonwebtoken "^8.5.1"
|
||||
uuid "^8.3.0"
|
||||
|
||||
"@azure/msal-node@^1.1.0":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.3.2.tgz#17665397c04b9cad57b42eb6e21d5d6f35d9b83a"
|
||||
integrity sha512-aKU2lVRKhZa1IJ/Za/Ir6qlythQ3FHz0g0px3SbM4iC1otyr3ANS4mIn/6fmkpZDIHc8eAgJh2KMep1Yn2zpig==
|
||||
dependencies:
|
||||
"@azure/msal-common" "^5.0.1"
|
||||
"@azure/msal-common" "^5.2.0"
|
||||
axios "^0.21.4"
|
||||
jsonwebtoken "^8.5.1"
|
||||
uuid "^8.3.0"
|
||||
@@ -4963,7 +4966,7 @@
|
||||
"@types/set-cookie-parser" "^2.4.0"
|
||||
set-cookie-parser "^2.4.6"
|
||||
|
||||
"@mswjs/interceptors@^0.12.6":
|
||||
"@mswjs/interceptors@^0.12.6", "@mswjs/interceptors@^0.12.7":
|
||||
version "0.12.7"
|
||||
resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909"
|
||||
integrity sha512-eGjZ3JRAt0Fzi5FgXiV/P3bJGj0NqsN7vBS0J0FO2AQRQ0jCKQS4lEFm4wvlSgKQNfeuc/Vz6d81VtU3Gkx/zg==
|
||||
@@ -5437,6 +5440,11 @@
|
||||
resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.1.tgz#03c72f548431da5820a0c8864d1401e348e7e79f"
|
||||
integrity sha512-H5Djcc2txGAINgf3TNaq4yFofYSIK3722PM89S/3R8FuI/eqi1UscajlXk7EBkG9s2pxss/q6SHlpturaavXaw==
|
||||
|
||||
"@opentelemetry/api@^1.0.1":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz#a167e46c10d05a07ab299fc518793b0cff8f6924"
|
||||
integrity sha512-BuJuXRSJNQ3QoKA6GWWDyuLpOUck+9hAXNMCnrloc1aWVoy6Xq6t9PUV08aBZ4Lutqq2LEHM486bpZqoViScog==
|
||||
|
||||
"@opentelemetry/context-base@^0.10.2":
|
||||
version "0.10.2"
|
||||
resolved "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.10.2.tgz#55bea904b2b91aa8a8675df9eaba5961bddb1def"
|
||||
@@ -7011,11 +7019,6 @@
|
||||
resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
|
||||
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==
|
||||
|
||||
"@tsconfig/node16@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1"
|
||||
integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==
|
||||
|
||||
"@tsconfig/node16@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
|
||||
@@ -7853,9 +7856,9 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/mock-fs@^4.10.0", "@types/mock-fs@^4.13.0":
|
||||
version "4.13.0"
|
||||
resolved "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.0.tgz#b8b01cd2db588668b2532ecd21b1babd3fffb2c0"
|
||||
integrity sha512-FUqxhURwqFtFBCuUj3uQMp7rPSQs//b3O9XecAVxhqS9y4/W8SIJEZFq2mmpnFVZBXwR/2OyPLE97CpyYiB8Mw==
|
||||
version "4.13.1"
|
||||
resolved "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.1.tgz#9201554ceb23671badbfa8ac3f1fa9e0706305be"
|
||||
integrity sha512-m6nFAJ3lBSnqbvDZioawRvpLXSaPyn52Srf7OfzjubYbYX8MTUdIgDxQl0wEapm4m/pNYSd9TXocpQ0TvZFlYA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@@ -10032,13 +10035,6 @@ axios@^0.21.1, axios@^0.21.4:
|
||||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
axios@^0.24.0:
|
||||
version "0.24.0"
|
||||
resolved "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
|
||||
integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.4"
|
||||
|
||||
axobject-query@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
||||
@@ -11145,6 +11141,14 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@4.1.1, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
|
||||
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^1.0.0, chalk@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
@@ -11164,14 +11168,6 @@ chalk@^3.0.0:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
|
||||
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
change-case-all@1.0.14:
|
||||
version "1.0.14"
|
||||
resolved "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1"
|
||||
@@ -13232,10 +13228,10 @@ delegates@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||
|
||||
denque@^1.4.1:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de"
|
||||
integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==
|
||||
denque@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a"
|
||||
integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==
|
||||
|
||||
depd@^1.1.2, depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
@@ -14689,11 +14685,6 @@ expand-brackets@^2.1.4:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
expand-template@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
|
||||
|
||||
expect@^24.8.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca"
|
||||
@@ -15284,7 +15275,7 @@ fn.name@1.x.x:
|
||||
resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
|
||||
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
||||
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.4:
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.14.0:
|
||||
version "1.14.6"
|
||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd"
|
||||
integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==
|
||||
@@ -15840,11 +15831,6 @@ gitconfiglocal@^1.0.0:
|
||||
dependencies:
|
||||
ini "^1.3.2"
|
||||
|
||||
github-from-package@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
|
||||
integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
|
||||
|
||||
glob-base@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
|
||||
@@ -16132,7 +16118,7 @@ google-p12-pem@^3.0.3:
|
||||
dependencies:
|
||||
node-forge "^0.10.0"
|
||||
|
||||
got@^11.5.2, got@^11.8.0, got@^11.8.2:
|
||||
got@^11.8.0, got@^11.8.2:
|
||||
version "11.8.2"
|
||||
resolved "https://registry.npmjs.org/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
|
||||
integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
|
||||
@@ -17003,10 +16989,10 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
iconv-lite@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
|
||||
integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
|
||||
iconv-lite@^0.6.2, iconv-lite@^0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
@@ -17278,7 +17264,7 @@ inquirer@^8.0.0:
|
||||
strip-ansi "^6.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
inquirer@^8.1.1:
|
||||
inquirer@^8.1.1, inquirer@^8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a"
|
||||
integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==
|
||||
@@ -19266,14 +19252,6 @@ kafkajs@^1.16.0-beta.6:
|
||||
resolved "https://registry.npmjs.org/kafkajs/-/kafkajs-1.16.0-beta.21.tgz#5736bcef7b505714642a82d6dc0d1507fc0ae817"
|
||||
integrity sha512-6iarOOnKTaei0EK+a+K2V/bBA7YgvpA69tZwnVF85PxGlvoG/wqKpfRNh2Mb04uiNTEwBYNEIO7hAFElEM6/AA==
|
||||
|
||||
keytar@^7.3.0:
|
||||
version "7.7.0"
|
||||
resolved "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz#3002b106c01631aa79b1aa9ee0493b94179bbbd2"
|
||||
integrity sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==
|
||||
dependencies:
|
||||
node-addon-api "^3.0.0"
|
||||
prebuild-install "^6.0.0"
|
||||
|
||||
keyv-memcache@^1.2.5:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.npmjs.org/keyv-memcache/-/keyv-memcache-1.2.7.tgz#b8a43eeecdb11ad8f4d6d64abd4298d014c74955"
|
||||
@@ -21290,11 +21268,6 @@ mkdirp-classic@^0.5.2:
|
||||
resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b"
|
||||
integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==
|
||||
|
||||
mkdirp-classic@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
||||
mkdirp-infer-owner@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz#55d3b368e7d89065c38f32fd38e638f0ab61d316"
|
||||
@@ -21386,13 +21359,6 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
msal@^1.0.2:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.npmjs.org/msal/-/msal-1.4.4.tgz#3f9b5a4442aa711c12ab8e88b8ed89b293f99711"
|
||||
integrity sha512-aOBD/L6jAsizDFzKxxvXxH0FEDjp6Inr3Ufi/Y2o7KCFKN+akoE2sLeszEb/0Y3VxHxK0F0ea7xQ/HHTomKivw==
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
|
||||
msw@^0.35.0:
|
||||
version "0.35.0"
|
||||
resolved "https://registry.npmjs.org/msw/-/msw-0.35.0.tgz#18a4ceb6c822ef226a30421d434413bc45030d38"
|
||||
@@ -21419,6 +21385,32 @@ msw@^0.35.0:
|
||||
type-fest "^1.2.2"
|
||||
yargs "^17.0.1"
|
||||
|
||||
msw@^0.36.3:
|
||||
version "0.36.3"
|
||||
resolved "https://registry.npmjs.org/msw/-/msw-0.36.3.tgz#7feb243a5fcf563806d45edc027bc36144741170"
|
||||
integrity sha512-Itzp/QhKaleZoslXDrNik3ramW9ynqzOdbwydX2ehBSSaZd5QoiAl/bHYcV33R6CEZcJgIX1N4s+G6XkF/bhkA==
|
||||
dependencies:
|
||||
"@mswjs/cookies" "^0.1.6"
|
||||
"@mswjs/interceptors" "^0.12.7"
|
||||
"@open-draft/until" "^1.0.3"
|
||||
"@types/cookie" "^0.4.1"
|
||||
"@types/inquirer" "^8.1.3"
|
||||
"@types/js-levenshtein" "^1.1.0"
|
||||
chalk "4.1.1"
|
||||
chokidar "^3.4.2"
|
||||
cookie "^0.4.1"
|
||||
graphql "^15.5.1"
|
||||
headers-utils "^3.0.2"
|
||||
inquirer "^8.2.0"
|
||||
is-node-process "^1.0.1"
|
||||
js-levenshtein "^1.1.6"
|
||||
node-fetch "^2.6.1"
|
||||
path-to-regexp "^6.2.0"
|
||||
statuses "^2.0.0"
|
||||
strict-event-emitter "^0.2.0"
|
||||
type-fest "^1.2.2"
|
||||
yargs "^17.3.0"
|
||||
|
||||
multicast-dns-service-types@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
|
||||
@@ -21458,13 +21450,13 @@ mv@~2:
|
||||
rimraf "~2.4.0"
|
||||
|
||||
mysql2@^2.2.5:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz#72624ffb4816f80f96b9c97fedd8c00935f9f340"
|
||||
integrity sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==
|
||||
version "2.3.3"
|
||||
resolved "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz#944f3deca4b16629052ff8614fbf89d5552545a0"
|
||||
integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==
|
||||
dependencies:
|
||||
denque "^1.4.1"
|
||||
denque "^2.0.1"
|
||||
generate-function "^2.3.1"
|
||||
iconv-lite "^0.6.2"
|
||||
iconv-lite "^0.6.3"
|
||||
long "^4.0.0"
|
||||
lru-cache "^6.0.0"
|
||||
named-placeholders "^1.1.2"
|
||||
@@ -21538,11 +21530,6 @@ nanomatch@^1.2.9:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
napi-build-utils@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
|
||||
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
|
||||
|
||||
native-url@^0.2.6:
|
||||
version "0.2.6"
|
||||
resolved "https://registry.npmjs.org/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae"
|
||||
@@ -21618,13 +21605,6 @@ no-case@^3.0.4:
|
||||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
node-abi@^2.21.0:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.npmjs.org/node-abi/-/node-abi-2.30.0.tgz#8be53bf3e7945a34eea10e0fc9a5982776cf550b"
|
||||
integrity sha512-g6bZh3YCKQRdwuO/tSZZYJAw622SjsRfJ2X0Iy4sSOHZ34/sPPdVBn8fev2tj7njzLwuqPw9uMtGsGkO5kIQvg==
|
||||
dependencies:
|
||||
semver "^5.4.1"
|
||||
|
||||
node-abort-controller@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz#f91fa50b1dee3f909afabb7e261b1e1d6b0cb74e"
|
||||
@@ -22037,7 +22017,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
|
||||
dependencies:
|
||||
path-key "^3.0.0"
|
||||
|
||||
"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2:
|
||||
"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.2, npmlog@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
||||
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
|
||||
@@ -22283,7 +22263,7 @@ onetime@^5.1.0, onetime@^5.1.2:
|
||||
dependencies:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
open@^7.0.0, open@^7.0.2, open@^7.0.3:
|
||||
open@^7.0.2, open@^7.0.3:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.npmjs.org/open/-/open-7.3.1.tgz#111119cb919ca1acd988f49685c4fdd0f4755356"
|
||||
integrity sha512-f2wt9DCBKKjlFbjzGb8MOAW8LH8F0mrs1zc7KTjAJ9PZNQbfenzWbNP1VZJvw6ICMG9r14Ah6yfwPn7T7i646A==
|
||||
@@ -22291,10 +22271,10 @@ open@^7.0.0, open@^7.0.2, open@^7.0.3:
|
||||
is-docker "^2.0.0"
|
||||
is-wsl "^2.1.1"
|
||||
|
||||
open@^8.0.9:
|
||||
version "8.2.1"
|
||||
resolved "https://registry.npmjs.org/open/-/open-8.2.1.tgz#82de42da0ccbf429bc12d099dad2e0975e14e8af"
|
||||
integrity sha512-rXILpcQlkF/QuFez2BJDf3GsqpjGKbkUUToAIGo9A0Q6ZkoSGogZJulrUdwRkrAsoQvoZsrjCYt8+zblOk7JQQ==
|
||||
open@^8.0.0, open@^8.0.9:
|
||||
version "8.4.0"
|
||||
resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
|
||||
integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
|
||||
dependencies:
|
||||
define-lazy-prop "^2.0.0"
|
||||
is-docker "^2.1.1"
|
||||
@@ -23033,6 +23013,11 @@ path-to-regexp@^1.7.0:
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
path-to-regexp@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38"
|
||||
integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==
|
||||
|
||||
path-type@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
|
||||
@@ -23744,25 +23729,6 @@ postgres-interval@^1.1.0:
|
||||
dependencies:
|
||||
xtend "^4.0.0"
|
||||
|
||||
prebuild-install@^6.0.0:
|
||||
version "6.1.3"
|
||||
resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.3.tgz#8ea1f9d7386a0b30f7ef20247e36f8b2b82825a2"
|
||||
integrity sha512-iqqSR84tNYQUQHRXalSKdIaM8Ov1QxOVuBNWI7+BzZWv6Ih9k75wOnH1rGQ9WWTaaLkTpxWKIciOF0KyfM74+Q==
|
||||
dependencies:
|
||||
detect-libc "^1.0.3"
|
||||
expand-template "^2.0.3"
|
||||
github-from-package "0.0.0"
|
||||
minimist "^1.2.3"
|
||||
mkdirp-classic "^0.5.3"
|
||||
napi-build-utils "^1.0.1"
|
||||
node-abi "^2.21.0"
|
||||
npmlog "^4.0.1"
|
||||
pump "^3.0.0"
|
||||
rc "^1.2.7"
|
||||
simple-get "^3.0.3"
|
||||
tar-fs "^2.0.0"
|
||||
tunnel-agent "^0.6.0"
|
||||
|
||||
precond@0.2:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac"
|
||||
@@ -24189,7 +24155,7 @@ qs@6.9.6:
|
||||
resolved "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee"
|
||||
integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==
|
||||
|
||||
qs@^6.10.0, qs@^6.10.1, qs@^6.7.0, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6:
|
||||
qs@^6.10.0, qs@^6.10.1, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6:
|
||||
version "6.10.1"
|
||||
resolved "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
|
||||
integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
|
||||
@@ -25874,6 +25840,11 @@ safe-stable-stringify@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz#c8a220ab525cd94e60ebf47ddc404d610dc5d84a"
|
||||
integrity sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==
|
||||
|
||||
safe-stable-stringify@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73"
|
||||
integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
@@ -27005,6 +26976,15 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
"string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa"
|
||||
@@ -27102,6 +27082,13 @@ strip-ansi@^4.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.0:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
|
||||
@@ -27517,7 +27504,7 @@ tar-fs@2.0.0:
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-fs@^2.0.0, tar-fs@^2.1.1:
|
||||
tar-fs@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
|
||||
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
|
||||
@@ -28134,23 +28121,7 @@ ts-log@^2.2.3:
|
||||
resolved "https://registry.npmjs.org/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb"
|
||||
integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w==
|
||||
|
||||
ts-node@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.0.0.tgz#05f10b9a716b0b624129ad44f0ea05dac84ba3be"
|
||||
integrity sha512-ROWeOIUvfFbPZkoDis0L/55Fk+6gFQNZwwKPLinacRl6tsxstTF1DbAcLKkovwnpKMVvOMHP1TIbnwXwtLg1gg==
|
||||
dependencies:
|
||||
"@tsconfig/node10" "^1.0.7"
|
||||
"@tsconfig/node12" "^1.0.7"
|
||||
"@tsconfig/node14" "^1.0.0"
|
||||
"@tsconfig/node16" "^1.0.1"
|
||||
arg "^4.1.0"
|
||||
create-require "^1.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
source-map-support "^0.5.17"
|
||||
yn "3.1.1"
|
||||
|
||||
ts-node@^10.2.1, ts-node@^10.4.0:
|
||||
ts-node@^10.0.0, ts-node@^10.2.1, ts-node@^10.4.0:
|
||||
version "10.4.0"
|
||||
resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7"
|
||||
integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==
|
||||
@@ -28369,33 +28340,33 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript-json-schema@^0.51.0:
|
||||
version "0.51.0"
|
||||
resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.51.0.tgz#e2abff69b8564c98c0edef2c13d55ef10fd71427"
|
||||
integrity sha512-POhWbUNs2oaBti1W9k/JwS+uDsaZD9J/KQiZ/iXRQEOD0lTn9VmshIls9tn+A9X6O+smPjeEz5NEy6WTkCCzrQ==
|
||||
typescript-json-schema@^0.52.0:
|
||||
version "0.52.0"
|
||||
resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.52.0.tgz#954560ec90e5486e8f7a5b7706ec59286a708e29"
|
||||
integrity sha512-3ZdHzx116gZ+D9LmMl5/+d1G3Rpt8baWngKzepYWHnXbAa8Winv64CmFRqLlMKneE1c40yugYDFcWdyX1FjGzQ==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@types/node" "^16.9.2"
|
||||
glob "^7.1.7"
|
||||
json-stable-stringify "^1.0.1"
|
||||
safe-stable-stringify "^2.2.0"
|
||||
ts-node "^10.2.1"
|
||||
typescript "~4.2.3"
|
||||
typescript "~4.4.4"
|
||||
yargs "^17.1.1"
|
||||
|
||||
typescript@^4.0.3, typescript@~4.2.3:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"
|
||||
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
|
||||
typescript@^4.0.3, typescript@~4.5.2:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
|
||||
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
|
||||
|
||||
typescript@~4.3.5:
|
||||
version "4.3.5"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
|
||||
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
|
||||
|
||||
typescript@~4.5.2:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
|
||||
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
|
||||
typescript@~4.4.4:
|
||||
version "4.4.4"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
|
||||
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
|
||||
|
||||
ua-parser-js@^0.7.18:
|
||||
version "0.7.28"
|
||||
@@ -29949,6 +29920,11 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^21.0.0:
|
||||
version "21.0.0"
|
||||
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55"
|
||||
integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==
|
||||
|
||||
yargs-parser@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-3.2.0.tgz#5081355d19d9d0c8c5d81ada908cb4e6d186664f"
|
||||
@@ -29987,20 +29963,7 @@ yargs@^16.1.1, yargs@^16.2.0:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yargs@^17.0.0, yargs@^17.0.1:
|
||||
version "17.0.1"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb"
|
||||
integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==
|
||||
dependencies:
|
||||
cliui "^7.0.2"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
require-directory "^2.1.1"
|
||||
string-width "^4.2.0"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yargs@^17.1.1:
|
||||
yargs@^17.0.0, yargs@^17.0.1, yargs@^17.1.1:
|
||||
version "17.2.1"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
|
||||
integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
|
||||
@@ -30013,6 +29976,19 @@ yargs@^17.1.1:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yargs@^17.3.0:
|
||||
version "17.3.0"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz#295c4ffd0eef148ef3e48f7a2e0f58d0e4f26b1c"
|
||||
integrity sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew==
|
||||
dependencies:
|
||||
cliui "^7.0.2"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
require-directory "^2.1.1"
|
||||
string-width "^4.2.3"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.0.0"
|
||||
|
||||
yargs@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-5.0.0.tgz#3355144977d05757dbb86d6e38ec056123b3a66e"
|
||||
|
||||
Reference in New Issue
Block a user