diff --git a/.changeset/eleven-otters-unite.md b/.changeset/eleven-otters-unite.md
new file mode 100644
index 0000000000..f7836a7ade
--- /dev/null
+++ b/.changeset/eleven-otters-unite.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Implement a `EntityTagsPicker` field extension
diff --git a/.changeset/fluffy-toys-tease.md b/.changeset/fluffy-toys-tease.md
index f20eaebb2c..84805b1f30 100644
--- a/.changeset/fluffy-toys-tease.md
+++ b/.changeset/fluffy-toys-tease.md
@@ -1,5 +1,5 @@
---
-'@backstage/plugin-tech-insights': minor
+'@backstage/plugin-tech-insights': patch
---
-fixes api auth in tech-insights plugin
+Fixed API auth in tech-insights plugin
diff --git a/.changeset/sharp-eagles-peel.md b/.changeset/sharp-eagles-peel.md
new file mode 100644
index 0000000000..8a1cace3f1
--- /dev/null
+++ b/.changeset/sharp-eagles-peel.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Support custom file name for `catalog:write` action
diff --git a/.changeset/strong-insects-provide.md b/.changeset/strong-insects-provide.md
new file mode 100644
index 0000000000..1a271b220b
--- /dev/null
+++ b/.changeset/strong-insects-provide.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Filter out projects with missing `default_branch` from GitLab Discovery.
diff --git a/ADOPTERS.md b/ADOPTERS.md
index a2194fd744..e0e6a818c6 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -78,3 +78,4 @@
| [Keyloop](https://www.keyloop.com/) | [Andre Wanlin](https://github.com/awanlin) | Future-motive Developer Portal to help our teams create technology to make everything about buying and owning a car better. 🚗 |
| [Simply Business](https://sbtech.simplybusiness.co.uk/) | [@addersuk](https://github.com/addersuk), [@LightningStairs](https://github.com/LightningStairs), [@punitcse](https://github.com/punitcse), [@moltenice](https://github.com/moltenice) | Central developer portal to access everything a developer needs such as docs, internal service catalog, and the ability to quickly create a new service from a template. Internally developed Backstage plugins allow us to customise the experience to how we work. |
| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding |
+| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. |
diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md
index a4ca0771df..b9b74f92dd 100644
--- a/docs/getting-started/app-custom-theme.md
+++ b/docs/getting-started/app-custom-theme.md
@@ -53,8 +53,10 @@ To add a custom theme to your Backstage app, you pass it as configuration to
For example, adding the theme that we created in the previous section can be
done like this:
-```ts
+```tsx
import { createApp } from '@backstage/app-defaults';
+import { ThemeProvider } from '@material-ui/core/styles';
+import CssBaseline from '@material-ui/core/CssBaseline';
const app = createApp({
apis: ...,
@@ -63,7 +65,11 @@ const app = createApp({
id: 'my-theme',
title: 'My Custom Theme',
variant: 'light',
- theme: myTheme,
+ Provider: ({ children }) => (
+
+ {children}
+
+ ),
}]
})
```
@@ -76,60 +82,67 @@ want to use the default themes, they are exported as `lightTheme` and
## Example of a custom theme
```ts
-const themeOptions = createThemeOptions({
+import {
+ createTheme,
+ genPageTheme,
+ lightTheme,
+ shapes,
+} from '@backstage/theme';
+
+const myTheme = createTheme({
palette: {
...lightTheme.palette,
primary: {
- main: '#123456',
+ main: '#343b58',
},
secondary: {
- main: '#123456',
+ main: '#565a6e',
},
error: {
- main: '#123456',
+ main: '#8c4351',
},
warning: {
- main: '#123456',
+ main: '#8f5e15',
},
info: {
- main: '#123456',
+ main: '#34548a',
},
success: {
- main: '#123456',
+ main: '#485e30',
},
background: {
- default: '#123456',
- paper: '#123456',
+ default: '#d5d6db',
+ paper: '#d5d6db',
},
banner: {
- info: '#123456',
- error: '#123456',
- text: '#123456',
- link: '#123456',
+ info: '#34548a',
+ error: '#8c4351',
+ text: '#343b58',
+ link: '#565a6e',
},
- errorBackground: '#123456',
- warningBackground: '#123456',
- infoBackground: '#123456',
+ errorBackground: '#8c4351',
+ warningBackground: '#8f5e15',
+ infoBackground: '#343b58',
navigation: {
- background: '#123456',
- indicator: '#123456',
- color: '#123456',
- selectedColor: '#123456',
+ background: '#343b58',
+ indicator: '#8f5e15',
+ color: '#d5d6db',
+ selectedColor: '#ffffff',
},
},
defaultPageTheme: 'home',
- fontFamily: 'Comic Sans',
+ fontFamily: 'Comic Sans MS',
/* below drives the header colors */
pageTheme: {
- home: genPageTheme(['#123456', '#123456'], shapes.wave),
- documentation: genPageTheme(['#123456', '#123456'], shapes.wave2),
- tool: genPageTheme(['#123456', '#123456'], shapes.round),
- service: genPageTheme(['#123456', '#123456'], shapes.wave),
- website: genPageTheme(['#123456', '#123456'], shapes.wave),
- library: genPageTheme(['#123456', '#123456'], shapes.wave),
- other: genPageTheme(['#123456', '#123456'], shapes.wave),
- app: genPageTheme(['#123456', '#123456'], shapes.wave),
- apis: genPageTheme(['#123456', '#123456'], shapes.wave),
+ home: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
+ documentation: genPageTheme(['#8c4351', '#343b58'], shapes.wave2),
+ tool: genPageTheme(['#8c4351', '#343b58'], shapes.round),
+ service: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
+ website: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
+ library: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
+ other: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
+ app: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
+ apis: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
},
});
```
@@ -162,7 +175,7 @@ wouldn't be enough to alter the `box-shadow` property or to add css rules that
aren't already defined like a margin. For these cases you should also create an
override.
-```ts
+```tsx
import { createApp } from '@backstage/core-app-api';
import { BackstageTheme, lightTheme } from '@backstage/theme';
/**
@@ -187,6 +200,16 @@ export const createCustomThemeOverrides = (
};
};
+const customTheme: BackstageTheme = {
+ ...lightTheme,
+ overrides: {
+ // These are the overrides that Backstage applies to `material-ui` components
+ ...lightTheme.overrides,
+ // These are your custom overrides, either to `material-ui` or Backstage components.
+ ...createCustomThemeOverrides(lightTheme),
+ },
+};
+
const app = createApp({
apis: ...,
plugins: ...,
@@ -194,15 +217,11 @@ const app = createApp({
id: 'my-theme',
title: 'My Custom Theme',
variant: 'light',
- theme: {
- ...lightTheme,
- overrides: {
- // These are the overrides that Backstage applies to `material-ui` components
- ...lightTheme.overrides,
- // These are your custom overrides, either to `material-ui` or Backstage components.
- ...createCustomThemeOverrides(lightTheme),
- },
- },
+ Provider: ({ children }) => (
+
+ {children}
+
+ ),
}]
});
```
diff --git a/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.test.ts
index 311de05673..8d85d7375f 100644
--- a/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.test.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.test.ts
@@ -184,6 +184,13 @@ describe('GitlabDiscoveryProcessor', () => {
last_activity_at: '2021-08-05T11:03:05.774Z',
web_url: 'https://gitlab.fake/3',
},
+ {
+ id: 4,
+ archived: false,
+ default_branch: undefined, // MISSING DEFAULT BRANCH
+ last_activity_at: '2021-08-05T11:03:05.774Z',
+ web_url: 'https://gitlab.fake/4',
+ },
],
};
default:
diff --git a/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.ts
index 70ccd44793..fabf3a1136 100644
--- a/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.ts
@@ -94,9 +94,16 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor {
};
for await (const project of projects) {
result.scanned++;
- if (!project.archived) {
- result.matches.push(project);
+
+ if (project.archived) {
+ continue;
}
+
+ if (branch === '*' && project.default_branch === undefined) {
+ continue;
+ }
+
+ result.matches.push(project);
}
for (const project of result.matches) {
diff --git a/plugins/catalog-backend/src/ingestion/processors/gitlab/types.ts b/plugins/catalog-backend/src/ingestion/processors/gitlab/types.ts
index a66411ddc8..d41ed73aac 100644
--- a/plugins/catalog-backend/src/ingestion/processors/gitlab/types.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/gitlab/types.ts
@@ -16,7 +16,7 @@
export type GitLabProject = {
id: number;
- default_branch: string;
+ default_branch?: string;
archived: boolean;
last_activity_at: string;
web_url: string;
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts
index 48c44f090a..8b1be0c187 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts
@@ -69,4 +69,33 @@ describe('catalog:write', () => {
yaml.stringify(entity),
);
});
+
+ it('should support a custom filename', async () => {
+ const entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'n',
+ namespace: 'ns',
+ annotations: {
+ [ORIGIN_LOCATION_ANNOTATION]: 'url:https://example.com',
+ },
+ },
+ spec: {},
+ };
+
+ await action.handler({
+ ...mockContext,
+ input: {
+ filePath: 'some-dir/entity-info.yaml',
+ entity,
+ },
+ });
+
+ expect(fsMock.writeFile).toHaveBeenCalledTimes(1);
+ expect(fsMock.writeFile).toHaveBeenCalledWith(
+ resolvePath(mockContext.workspacePath, 'some-dir/entity-info.yaml'),
+ yaml.stringify(entity),
+ );
+ });
});
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts
index b813244020..46220358c7 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts
@@ -21,13 +21,18 @@ import { Entity } from '@backstage/catalog-model';
import { resolveSafeChildPath } from '@backstage/backend-common';
export function createCatalogWriteAction() {
- return createTemplateAction<{ name?: string; entity: Entity }>({
+ return createTemplateAction<{ filePath?: string; entity: Entity }>({
id: 'catalog:write',
description: 'Writes the catalog-info.yaml for your template',
schema: {
input: {
type: 'object',
properties: {
+ filePath: {
+ title: 'Catalog file path',
+ description: 'Defaults to catalog-info.yaml',
+ type: 'string',
+ },
entity: {
title: 'Entity info to write catalog-info.yaml',
description:
@@ -39,10 +44,11 @@ export function createCatalogWriteAction() {
},
async handler(ctx) {
ctx.logStream.write(`Writing catalog-info.yaml`);
- const { entity } = ctx.input;
+ const { filePath, entity } = ctx.input;
+ const path = filePath ?? 'catalog-info.yaml';
await fs.writeFile(
- resolveSafeChildPath(ctx.workspacePath, 'catalog-info.yaml'),
+ resolveSafeChildPath(ctx.workspacePath, path),
yaml.stringify(entity),
);
},
diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md
index 4f6928e72d..5252e08299 100644
--- a/plugins/scaffolder/api-report.md
+++ b/plugins/scaffolder/api-report.md
@@ -80,6 +80,16 @@ export const EntityPicker: ({
// @public (undocumented)
export const EntityPickerFieldExtension: () => null;
+// @public
+export const EntityTagsPicker: ({
+ formData,
+ onChange,
+ uiSchema,
+}: FieldProps) => JSX.Element;
+
+// @public
+export const EntityTagsPickerFieldExtension: () => null;
+
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "FavouriteTemplate" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx
new file mode 100644
index 0000000000..b21398a3f4
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx
@@ -0,0 +1,107 @@
+/*
+ * 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 React, { useState } from 'react';
+import { useAsync, useEffectOnce } from 'react-use';
+import { CatalogEntitiesRequest } from '@backstage/catalog-client';
+import { Entity, makeValidator } from '@backstage/catalog-model';
+import { useApi } from '@backstage/core-plugin-api';
+import { catalogApiRef } from '@backstage/plugin-catalog-react';
+import { FormControl, TextField } from '@material-ui/core';
+import { Autocomplete } from '@material-ui/lab';
+import { FieldProps } from '@rjsf/core';
+
+/**
+ * EntityTagsPicker
+ * @public
+ */
+export const EntityTagsPicker = ({
+ formData,
+ onChange,
+ uiSchema,
+}: FieldProps) => {
+ const catalogApi = useApi(catalogApiRef);
+ const [inputValue, setInputValue] = useState('');
+ const [inputError, setInputError] = useState(false);
+ const tagValidator = makeValidator().isValidTag;
+ const kinds = uiSchema['ui:options']?.kinds as string[];
+
+ const { loading, value: existingTags } = useAsync(async () => {
+ const tagsRequest: CatalogEntitiesRequest = { fields: ['metadata.tags'] };
+ if (kinds) {
+ tagsRequest.filter = { kind: kinds };
+ }
+
+ const entities = await catalogApi.getEntities(tagsRequest);
+
+ return [
+ ...new Set(
+ entities.items
+ .flatMap((e: Entity) => e.metadata?.tags)
+ .filter(Boolean) as string[],
+ ),
+ ].sort();
+ });
+
+ const setTags = (_: React.ChangeEvent<{}>, values: string[] | null) => {
+ // Reset error state in case all tags were removed
+ let hasError = false;
+ let addDuplicate = false;
+ const currentTags = formData || [];
+
+ // If adding a new tag
+ if (values?.length && currentTags.length < values.length) {
+ const newTag = (values[values.length - 1] = values[values.length - 1]
+ .toLocaleLowerCase('en-US')
+ .trim());
+ hasError = !tagValidator(newTag);
+ addDuplicate = currentTags.indexOf(newTag) !== -1;
+ }
+
+ setInputError(hasError);
+ setInputValue(!hasError ? '' : inputValue);
+ if (!hasError && !addDuplicate) {
+ onChange(values || []);
+ }
+ };
+
+ // Initialize field to always return an array
+ useEffectOnce(() => onChange(formData || []));
+
+ return (
+
+ (
+ setInputValue(e.target.value)}
+ error={inputError}
+ helperText="Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"
+ />
+ )}
+ />
+
+ );
+};
diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts
new file mode 100644
index 0000000000..09f4d5c519
--- /dev/null
+++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export { EntityTagsPicker } from './EntityTagsPicker';
diff --git a/plugins/scaffolder/src/components/fields/index.ts b/plugins/scaffolder/src/components/fields/index.ts
index 299470e220..ecde218029 100644
--- a/plugins/scaffolder/src/components/fields/index.ts
+++ b/plugins/scaffolder/src/components/fields/index.ts
@@ -19,3 +19,4 @@ export * from './OwnerPicker';
export * from './RepoUrlPicker';
export * from './TextValuePicker';
export * from './OwnedEntityPicker';
+export * from './EntityTagsPicker';
diff --git a/plugins/scaffolder/src/extensions/default.ts b/plugins/scaffolder/src/extensions/default.ts
index 12a29809e0..820cc0e6d3 100644
--- a/plugins/scaffolder/src/extensions/default.ts
+++ b/plugins/scaffolder/src/extensions/default.ts
@@ -18,6 +18,7 @@ import {
EntityNamePicker,
entityNamePickerValidation,
} from '../components/fields/EntityNamePicker';
+import { EntityTagsPicker } from '../components/fields/EntityTagsPicker';
import { OwnerPicker } from '../components/fields/OwnerPicker';
import {
repoPickerValidation,
@@ -36,6 +37,10 @@ export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS: FieldExtensionOptions[] = [
name: 'EntityNamePicker',
validation: entityNamePickerValidation,
},
+ {
+ component: EntityTagsPicker,
+ name: 'EntityTagsPicker',
+ },
{
component: RepoUrlPicker,
name: 'RepoUrlPicker',
diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts
index dfbd2af296..3851b2151b 100644
--- a/plugins/scaffolder/src/index.ts
+++ b/plugins/scaffolder/src/index.ts
@@ -30,6 +30,7 @@ export type { CustomFieldValidator, FieldExtensionOptions } from './extensions';
export {
EntityPickerFieldExtension,
EntityNamePickerFieldExtension,
+ EntityTagsPickerFieldExtension,
OwnerPickerFieldExtension,
OwnedEntityPickerFieldExtension,
RepoUrlPickerFieldExtension,
@@ -40,6 +41,7 @@ export {
export {
EntityNamePicker,
EntityPicker,
+ EntityTagsPicker,
OwnerPicker,
RepoUrlPicker,
TextValuePicker,
diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts
index 95cad50eba..02b36c4bb6 100644
--- a/plugins/scaffolder/src/plugin.ts
+++ b/plugins/scaffolder/src/plugin.ts
@@ -36,6 +36,7 @@ import {
identityApiRef,
} from '@backstage/core-plugin-api';
import { OwnedEntityPicker } from './components/fields/OwnedEntityPicker';
+import { EntityTagsPicker } from './components/fields/EntityTagsPicker';
export const scaffolderPlugin = createPlugin({
id: 'scaffolder',
@@ -103,3 +104,14 @@ export const OwnedEntityPickerFieldExtension = scaffolderPlugin.provide(
name: 'OwnedEntityPicker',
}),
);
+
+/**
+ * EntityTagsPickerFieldExtension
+ * @public
+ */
+export const EntityTagsPickerFieldExtension = scaffolderPlugin.provide(
+ createScaffolderFieldExtension({
+ component: EntityTagsPicker,
+ name: 'EntityTagsPicker',
+ }),
+);
diff --git a/yarn.lock b/yarn.lock
index 7237ed33cd..7b30d6ae1f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7839,9 +7839,9 @@
integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==
"@types/jscodeshift@^0.11.0":
- version "0.11.0"
- resolved "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.0.tgz#7224cf1a4d0383b4fb2694ffed52f57b45c3325b"
- integrity sha512-OcJgr5GznWCEx2Oeg4eMUZYwssTHFj/tU8grrNCKdFQtAEAa0ezDiPHbCdSkyWrRSurXrYbNbHdhxbbB76pXNg==
+ version "0.11.3"
+ resolved "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.3.tgz#8dcab24ced39dcab1c8ff3461b3d171aafee3d48"
+ integrity sha512-pM0JD9kWVDH9DQp5Y6td16924V3MwZHei8P3cTeuFhXpzpk0K+iWraBZz8wF61QkFs9fZeAQNX0q8SG0+TFm2w==
dependencies:
ast-types "^0.14.1"
recast "^0.20.3"
@@ -14864,9 +14864,9 @@ express-prom-bundle@^6.3.6:
url-value-parser "^2.0.0"
express-promise-router@^4.1.0:
- version "4.1.0"
- resolved "https://registry.npmjs.org/express-promise-router/-/express-promise-router-4.1.0.tgz#79160e145c27610ba411bceb0552a36f11dbab4f"
- integrity sha512-nvg0X1Rj8oajPPC+fG3t4e740aNmQZRZY6dRLbiiM56Dvd8213RJ4kaxhZVTdQLut+l4DZdfeJkyx2VENPMBdw==
+ version "4.1.1"
+ resolved "https://registry.npmjs.org/express-promise-router/-/express-promise-router-4.1.1.tgz#8fac102060b9bcc868f84d34fbb12fd8fa494291"
+ integrity sha512-Lkvcy/ZGrBhzkl3y7uYBHLMtLI4D6XQ2kiFg9dq7fbktBch5gjqJ0+KovX0cvCAvTJw92raWunRLM/OM+5l4fA==
dependencies:
is-promise "^4.0.0"
lodash.flattendeep "^4.0.0"