From 4891a8f2e6f8e3c0993b6826676ea97bf0592895 Mon Sep 17 00:00:00 2001 From: Dede Hamzah Date: Wed, 3 Nov 2021 16:08:27 +0700 Subject: [PATCH 01/95] Add Props Icon for Sidebar Item SidebarSearchField and Settings Signed-off-by: Dede Hamzah --- .../src/layout/Sidebar/Items.tsx | 4 +++- .../user-settings/src/components/Settings.tsx | 17 +++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 883e0395cd..75317ed9ac 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -293,11 +293,13 @@ export const SidebarItem = forwardRef((props, ref) => { type SidebarSearchFieldProps = { onSearch: (input: string) => void; to?: string; + icon?: IconComponent; }; export function SidebarSearchField(props: SidebarSearchFieldProps) { const [input, setInput] = useState(''); const classes = useStyles(); + const Icon = props.icon ? props.icon : SearchIcon; const search = () => { props.onSearch(input); @@ -329,7 +331,7 @@ export function SidebarSearchField(props: SidebarSearchFieldProps) { return (
- + { - return ( - - ); +type SettingsProps = { + icon?: IconComponent; +}; + +export const Settings = (props: SettingsProps) => { + const Icon = props.icon ? props.icon : SettingsIcon; + + return ; }; From 274a4fc6335eff6091e995254636c98b7650d79e Mon Sep 17 00:00:00 2001 From: Dede Hamzah Date: Wed, 3 Nov 2021 16:11:23 +0700 Subject: [PATCH 02/95] add changeset Signed-off-by: Dede Hamzah --- .changeset/khaki-rice-kick.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/khaki-rice-kick.md diff --git a/.changeset/khaki-rice-kick.md b/.changeset/khaki-rice-kick.md new file mode 100644 index 0000000000..54819b855a --- /dev/null +++ b/.changeset/khaki-rice-kick.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-user-settings': patch +--- + +Add Props Icon for Sidebar Item SidebarSearchField and Settings From b90fc74d7099b815d8a22eab4f04bbf106a65b33 Mon Sep 17 00:00:00 2001 From: Bryce Larson Date: Thu, 4 Nov 2021 20:08:58 +1100 Subject: [PATCH 03/95] feat: Add getDefaultProcessors to CatalogBuilder Signed-off-by: Bryce Larson --- .changeset/violet-panthers-care.md | 5 +++ .../src/service/NextCatalogBuilder.ts | 38 +++++++++++++------ 2 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 .changeset/violet-panthers-care.md diff --git a/.changeset/violet-panthers-care.md b/.changeset/violet-panthers-care.md new file mode 100644 index 0000000000..7a43c87f37 --- /dev/null +++ b/.changeset/violet-panthers-care.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +adds getDefaultProcessor method to CatalogBuilder diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index 835c1dc700..5e0bcd9a75 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -265,7 +265,8 @@ export class NextCatalogBuilder { * Sets what entity processors to use. These are responsible for reading, * parsing, and processing entities before they are persisted in the catalog. * - * This function replaces the default set of processors; use with care. + * This function replaces the default set of processors, consider using with + * {@link NextCatalogBuilder#getDefaultProcessors}; use with care. * * @param processors One or more processors */ @@ -275,6 +276,30 @@ export class NextCatalogBuilder { return this; } + /** + * Returns the default list of entity processors. These are responsible for reading, + * parsing, and processing entities before they are persisted in the catalog. Changing + * the order of processing can give more control to custom processors. + * + * Consider using with with {@link NextCatalogBuilder#replaceProcessors} + * + */ + getDefaultProcessors(): CatalogProcessor[] { + const { config, logger, reader } = this.env; + const integrations = ScmIntegrations.fromConfig(config); + + return [ + new FileReaderProcessor(), + BitbucketDiscoveryProcessor.fromConfig(config, { logger }), + GithubDiscoveryProcessor.fromConfig(config, { logger }), + GithubOrgReaderProcessor.fromConfig(config, { logger }), + GitLabDiscoveryProcessor.fromConfig(config, { logger }), + new UrlReaderProcessor({ reader, logger }), + CodeOwnersProcessor.fromConfig(config, { logger, reader }), + new AnnotateLocationEntityProcessor({ integrations }), + ]; + } + /** * Sets up the catalog to use a custom parser for entity data. * @@ -416,16 +441,7 @@ export class NextCatalogBuilder { // These are only added unless the user replaced them all if (!this.processorsReplace) { - processors.push( - new FileReaderProcessor(), - BitbucketDiscoveryProcessor.fromConfig(config, { logger }), - GithubDiscoveryProcessor.fromConfig(config, { logger }), - GithubOrgReaderProcessor.fromConfig(config, { logger }), - GitLabDiscoveryProcessor.fromConfig(config, { logger }), - new UrlReaderProcessor({ reader, logger }), - CodeOwnersProcessor.fromConfig(config, { logger, reader }), - new AnnotateLocationEntityProcessor({ integrations }), - ); + processors.push(...this.getDefaultProcessors()); } // Add the ones (if any) that the user added From b8b67b574011074720b9f7d252b9ee2005b9a97b Mon Sep 17 00:00:00 2001 From: Gabriele Mambrini Date: Fri, 5 Nov 2021 09:52:50 +0100 Subject: [PATCH 04/95] Add allowed paths to backend.reading.allow Signed-off-by: Gabriele Mambrini --- .../src/reading/FetchUrlReader.test.ts | 7 +++++++ .../backend-common/src/reading/FetchUrlReader.ts | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/backend-common/src/reading/FetchUrlReader.test.ts b/packages/backend-common/src/reading/FetchUrlReader.test.ts index 124abb799c..9d6edee765 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.test.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.test.ts @@ -77,6 +77,10 @@ describe('FetchUrlReader', () => { { host: 'example.com:700' }, { host: '*.examples.org' }, { host: '*.examples.org:700' }, + { + host: 'foobar.org', + paths: ['/dir1/'], + }, ], }, }, @@ -106,6 +110,9 @@ describe('FetchUrlReader', () => { expect(predicate(new URL('https://examples.org:700/test'))).toBe(false); expect(predicate(new URL('https://a.examples.org:700/test'))).toBe(true); expect(predicate(new URL('https://a.b.examples.org:700/test'))).toBe(true); + expect(predicate(new URL('https://foobar.org/dir1/subpath'))).toBe(true); + expect(predicate(new URL('https://foobar.org/dir12'))).toBe(false); + expect(predicate(new URL('https://foobar.org/'))).toBe(false); }); describe('read', () => { diff --git a/packages/backend-common/src/reading/FetchUrlReader.ts b/packages/backend-common/src/reading/FetchUrlReader.ts index 732d3b9f59..1ae9677a85 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.ts @@ -24,6 +24,7 @@ import { SearchResponse, UrlReader, } from './types'; +import { normalize as normalizePath } from 'path'; /** * A UrlReader that does a plain fetch of the URL. @@ -39,18 +40,28 @@ export class FetchUrlReader implements UrlReader { * `host`: * Either full hostnames to match, or subdomain wildcard matchers with a leading `*`. * For example `example.com` and `*.example.com` are valid values, `prod.*.example.com` is not. + * + * `paths`: + * An optional list of paths which are allowed. If the list is omitted all paths are allowed. */ static factory: ReaderFactory = ({ config }) => { const predicates = config .getOptionalConfigArray('backend.reading.allow') ?.map(allowConfig => { + const paths = allowConfig.getOptionalStringArray('paths'); + const checkPath = paths + ? (url: URL) => { + const targetPath = normalizePath(url.pathname); + return paths.some(path => targetPath.startsWith(path)); + } + : (_url: URL) => true; const host = allowConfig.getString('host'); if (host.startsWith('*.')) { const suffix = host.slice(1); - return (url: URL) => url.host.endsWith(suffix); + return (url: URL) => url.host.endsWith(suffix) && checkPath(url); } - return (url: URL) => url.host === host; + return (url: URL) => url.host === host && checkPath(url); }) ?? []; const reader = new FetchUrlReader(); From bfc837a97b5cd16916ac241067df90e62a149479 Mon Sep 17 00:00:00 2001 From: Gabriele Mambrini Date: Fri, 5 Nov 2021 09:53:45 +0100 Subject: [PATCH 05/95] Update docs for allowed paths in backend.reading.allow Signed-off-by: Gabriele Mambrini --- docs/features/software-catalog/descriptor-format.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 7100e86ed0..57473ce4c7 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -144,7 +144,8 @@ spec: Note that to be able to read from targets that are outside of the normal integration points such as `github.com`, you'll need to explicitly allow it by -adding an entry in the `backend.reading.allow` list. For example: +adding an entry in the `backend.reading.allow` list. Paths can be specified to +further restrict targets For example: ```yml backend: @@ -153,6 +154,8 @@ backend: allow: - host: example.com - host: '*.examples.org' + - host: example.net + paths: ['/api/'] ``` ## Common to All Kinds: The Envelope From 1daada3a06f511ee31614822e7d00cd4f88fd0fe Mon Sep 17 00:00:00 2001 From: Gabriele Mambrini Date: Fri, 5 Nov 2021 10:00:17 +0100 Subject: [PATCH 06/95] Added changeset Signed-off-by: Gabriele Mambrini --- .changeset/nasty-impalas-travel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nasty-impalas-travel.md diff --git a/.changeset/nasty-impalas-travel.md b/.changeset/nasty-impalas-travel.md new file mode 100644 index 0000000000..f6eb321628 --- /dev/null +++ b/.changeset/nasty-impalas-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Paths can be specified in backend.reading.allow to further restrict allowed targets From 23348dd5864a2427c40288b6cf30a103bb267556 Mon Sep 17 00:00:00 2001 From: Bryce Larson <85923137+bryce-od@users.noreply.github.com> Date: Fri, 5 Nov 2021 10:25:28 +1100 Subject: [PATCH 07/95] Update plugins/catalog-backend/src/service/NextCatalogBuilder.ts Co-authored-by: Jussi Hallila Signed-off-by: Bryce Larson --- plugins/catalog-backend/src/service/NextCatalogBuilder.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index 5e0bcd9a75..bbda6f070c 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -281,7 +281,7 @@ export class NextCatalogBuilder { * parsing, and processing entities before they are persisted in the catalog. Changing * the order of processing can give more control to custom processors. * - * Consider using with with {@link NextCatalogBuilder#replaceProcessors} + * Consider using with {@link NextCatalogBuilder#replaceProcessors} * */ getDefaultProcessors(): CatalogProcessor[] { @@ -417,7 +417,7 @@ export class NextCatalogBuilder { } private buildProcessors(): CatalogProcessor[] { - const { config, logger, reader } = this.env; + const { config, reader } = this.env; const integrations = ScmIntegrations.fromConfig(config); this.checkDeprecatedReaderProcessors(); From 0514f55ff1a12000c2f29014bd98f5ab8bcf1780 Mon Sep 17 00:00:00 2001 From: Gabriele Mambrini Date: Mon, 8 Nov 2021 17:56:07 +0100 Subject: [PATCH 08/95] Update config schema Signed-off-by: Gabriele Mambrini --- packages/backend-common/config.d.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index bc5e026375..dfc845f3b2 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -130,6 +130,14 @@ export interface Config { * The host may also contain a port, for example `example.com:8080`. */ host: string; + + /** + * An optional list of paths. In case they are present only targets matching + * any of them will are allowed. You can use trailing slashes to make sure only + * subdirectories are allowed, for example `/mydir/` will allow targets with + * paths like `/mydir/a` but will block paths like `/mydir2`. + */ + paths?: string[]; }>; }; From f14ac64f2e8446844a99f639727c3dd3feae125c Mon Sep 17 00:00:00 2001 From: Gabriele Mambrini Date: Mon, 8 Nov 2021 18:06:53 +0100 Subject: [PATCH 09/95] Use posix normaliser for URL paths Signed-off-by: Gabriele Mambrini --- packages/backend-common/src/reading/FetchUrlReader.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/backend-common/src/reading/FetchUrlReader.ts b/packages/backend-common/src/reading/FetchUrlReader.ts index 1ae9677a85..19bf5fb140 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.ts @@ -24,7 +24,7 @@ import { SearchResponse, UrlReader, } from './types'; -import { normalize as normalizePath } from 'path'; +import path from 'path'; /** * A UrlReader that does a plain fetch of the URL. @@ -52,8 +52,10 @@ export class FetchUrlReader implements UrlReader { const paths = allowConfig.getOptionalStringArray('paths'); const checkPath = paths ? (url: URL) => { - const targetPath = normalizePath(url.pathname); - return paths.some(path => targetPath.startsWith(path)); + const targetPath = path.posix.normalize(url.pathname); + return paths.some(allowedPath => + targetPath.startsWith(allowedPath), + ); } : (_url: URL) => true; const host = allowConfig.getString('host'); From 41e5f28ab77b8f0b60d2e8d6aba62bcfccc4dd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Tue, 9 Nov 2021 12:36:18 +0300 Subject: [PATCH 10/95] OSS lowercase migration added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- packages/techdocs-common/package.json | 2 +- .../src/stages/publish/openStackSwift.ts | 66 +++++++++++++++++-- yarn.lock | 64 +++++++++++++++--- 3 files changed, 117 insertions(+), 15 deletions(-) diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 152b9b04f1..2bc3e80f26 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -45,7 +45,7 @@ "@backstage/search-common": "^0.2.1", "@backstage/integration": "^0.6.9", "@google-cloud/storage": "^5.6.0", - "@trendyol-js/openstack-swift-sdk": "^0.0.4", + "@trendyol-js/openstack-swift-sdk": "^0.0.5", "@types/express": "^4.17.6", "aws-sdk": "^2.840.0", "express": "^4.17.1", diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index a463157552..83e17fa466 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -24,7 +24,7 @@ import { SwiftClient } from '@trendyol-js/openstack-swift-sdk'; import { NotFound } from '@trendyol-js/openstack-swift-sdk/lib/types'; import { Stream, Readable } from 'stream'; import { Logger } from 'winston'; -import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; +import { getFileTreeRecursively, getHeadersForFileExtension, lowerCaseEntityTripletInStoragePath } from './helpers'; import { PublisherBase, PublishRequest, @@ -77,7 +77,7 @@ export class OpenStackSwiftPublish implements PublisherBase { } catch (error) { throw new Error( "Since techdocs.publisher.type is set to 'openStackSwift' in your app config, " + - 'techdocs.publisher.openStackSwift.containerName is required.', + 'techdocs.publisher.openStackSwift.containerName is required.', ); } @@ -115,9 +115,9 @@ export class OpenStackSwiftPublish implements PublisherBase { } this.logger.error( `Could not retrieve metadata about the OpenStack Swift container ${this.containerName}. ` + - 'Make sure the container exists. Also make sure that authentication is setup either by ' + - 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' + - 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', + 'Make sure the container exists. Also make sure that authentication is setup either by ' + + 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' + + 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', ); return { isAvailable: false, @@ -289,4 +289,60 @@ export class OpenStackSwiftPublish implements PublisherBase { return false; } } + + + async migrateDocsCase({ + removeOriginal = false, + concurrency = 25, + }): Promise { + // Iterate through every file in the root of the publisher. + const allObjects = await this.getAllObjectsFromContainer(); + const limiter = createLimiter(concurrency); + await Promise.all( + allObjects.map(f => + limiter(async file => { + let newPath; + try { + newPath = lowerCaseEntityTripletInStoragePath(file); + } catch (e) { + assertError(e); + this.logger.warn(e.message); + return; + } + + // If all parts are already lowercase, ignore. + if (file === newPath) { + return; + } + + try { + this.logger.verbose(`Migrating ${file} to ${newPath}`); + await this.storageClient.copy(this.containerName, file, this.containerName, newPath); + if (removeOriginal) { + await this.storageClient.delete(this.containerName, file); + } + } catch (e) { + assertError(e); + this.logger.warn(`Unable to migrate ${file}: ${e.message}`); + } + }, f), + ), + ); + } + + /** + * Returns a list of all object keys from the configured container. + */ + protected async getAllObjectsFromContainer( + { prefix } = { prefix: '' }, + ): Promise { + let objects: string[] = []; + let allObjects: any; + const OSS_MAX_LIMIT = Math.pow(2, 31) - 1; + + allObjects = await this.storageClient.list(this.containerName, prefix, OSS_MAX_LIMIT) + objects = allObjects.map((object: any) => object = object.name) + + return objects; + } } diff --git a/yarn.lock b/yarn.lock index 42c43fe4b7..915dfdfbda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4613,7 +4613,7 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@mswjs/cookies@^0.1.6": +"@mswjs/cookies@^0.1.5", "@mswjs/cookies@^0.1.6": version "0.1.6" resolved "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.6.tgz#176f77034ab6d7373ae5c94bcbac36fee8869249" integrity sha512-A53XD5TOfwhpqAmwKdPtg1dva5wrng2gH5xMvklzbd9WLTSVU953eCRa8rtrrm6G7Cy60BOGsBRN89YQK0mlKA== @@ -4621,6 +4621,17 @@ "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" +"@mswjs/interceptors@^0.10.0": + version "0.10.0" + resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.10.0.tgz#f5aad03c2c0591d164e3ed178b21942f1c2f8061" + integrity sha512-/M0GGpid5q2EDI+Keas1sLYF3VZFXHDE5gCmX/jHdp+OJFruVNca3PUk7A8KnGdPpuycZogdPsmRBSOXwjyA7A== + dependencies: + "@open-draft/until" "^1.0.3" + debug "^4.3.0" + headers-utils "^3.0.2" + strict-event-emitter "^0.2.0" + xmldom "^0.6.0" + "@mswjs/interceptors@^0.12.6": version "0.12.7" resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" @@ -6695,10 +6706,10 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@trendyol-js/openstack-swift-sdk@^0.0.4": - version "0.0.4" - resolved "https://registry.npmjs.org/@trendyol-js/openstack-swift-sdk/-/openstack-swift-sdk-0.0.4.tgz#570c6ab950319156c175ace005b4fb4d9f895d47" - integrity sha512-9YKOjov+V+yzptei6+B9QPuC5pOMTBTg/NQpb1ZbxvlOaYpWU4HHpSH2BkIFYZ8vYyAfzFNG1T2rjpQ2ZQDUtQ== +"@trendyol-js/openstack-swift-sdk@^0.0.5": + version "0.0.5" + resolved "https://registry.npmjs.org/@trendyol-js/openstack-swift-sdk/-/openstack-swift-sdk-0.0.5.tgz#65be3c42b8dbafc57f2f2a46c327e2ad51e5a70e" + integrity sha512-KS5nz0cvd35UUyMzhZm+btGV4prtA1KNE7CCMOGBdVxoMGl06Qidli3HgHoc2I9jLPmky1SPp5yzQUwrsyWa0g== dependencies: agentkeepalive "^4.1.4" axios "^0.21.1" @@ -6912,7 +6923,7 @@ dependencies: "@types/express" "*" -"@types/cookie@^0.4.1": +"@types/cookie@^0.4.0", "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== @@ -12586,7 +12597,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.2, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: +debug@4, debug@4.3.2, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.0, debug@^4.3.1, debug@^4.3.2: version "4.3.2" resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -15722,6 +15733,11 @@ graphql@^15.3.0: resolved "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad" integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw== +graphql@^15.4.0: + version "15.7.2" + resolved "https://registry.npmjs.org/graphql/-/graphql-15.7.2.tgz#85ab0eeb83722977151b3feb4d631b5f2ab287ef" + integrity sha512-AnnKk7hFQFmU/2I9YSQf3xw44ctnSFCfp3zE0N6W174gqe9fWG/2rKaKxROK7CcI3XtERpjEKFqts8o319Kf7A== + graphql@^15.5.1: version "15.6.1" resolved "https://registry.npmjs.org/graphql/-/graphql-15.6.1.tgz#9125bdf057553525da251e19e96dab3d3855ddfc" @@ -16649,7 +16665,7 @@ inquirer@^8.0.0: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^8.1.1: +inquirer@^8.1.0, inquirer@^8.1.1: version "8.2.0" resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== @@ -20678,6 +20694,31 @@ msal@^1.0.2: dependencies: tslib "^1.9.3" +msw@^0.29.0: + version "0.29.0" + resolved "https://registry.npmjs.org/msw/-/msw-0.29.0.tgz#7242d575cb01db0c925241587df1fc2b79230d78" + integrity sha512-C/wz1d5uAEZRvAPAYrXG1rwLxXl0+BOs+JPrCzasoABZW3ATwS6ifSze+/DAgA93e9M86RXwvy6yDtZeZWmCFQ== + dependencies: + "@mswjs/cookies" "^0.1.5" + "@mswjs/interceptors" "^0.10.0" + "@open-draft/until" "^1.0.3" + "@types/cookie" "^0.4.0" + "@types/inquirer" "^7.3.1" + "@types/js-levenshtein" "^1.1.0" + chalk "^4.1.1" + chokidar "^3.4.2" + cookie "^0.4.1" + graphql "^15.4.0" + headers-utils "^3.0.2" + inquirer "^8.1.0" + js-levenshtein "^1.1.6" + node-fetch "^2.6.1" + node-match-path "^0.6.3" + statuses "^2.0.0" + strict-event-emitter "^0.2.0" + type-fest "^1.1.3" + yargs "^17.0.1" + msw@^0.35.0: version "0.35.0" resolved "https://registry.npmjs.org/msw/-/msw-0.35.0.tgz#18a4ceb6c822ef226a30421d434413bc45030d38" @@ -27640,7 +27681,7 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^1.2.2: +type-fest@^1.1.3, type-fest@^1.2.2: version "1.4.0" resolved "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== @@ -29155,6 +29196,11 @@ xmlchars@^2.2.0: resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmldom@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" + integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== + xpath@0.0.32: version "0.0.32" resolved "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" From c79b30420b462cb39bf8ef1a412072d53aeb13a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Tue, 9 Nov 2021 12:36:47 +0300 Subject: [PATCH 11/95] lowercase routing issue fixed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- plugins/techdocs/src/home/components/DocsCardGrid.tsx | 2 +- plugins/techdocs/src/home/components/DocsTable.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/home/components/DocsCardGrid.tsx b/plugins/techdocs/src/home/components/DocsCardGrid.tsx index e84454ae54..eadfd0c8b6 100644 --- a/plugins/techdocs/src/home/components/DocsCardGrid.tsx +++ b/plugins/techdocs/src/home/components/DocsCardGrid.tsx @@ -39,7 +39,7 @@ export const DocsCardGrid = ({ 'techdocs.legacyUseCaseSensitiveTripletPaths', ) ? (str: string) => str - : (str: string) => str.toLocaleLowerCase(); + : (str: string) => str.toLocaleLowerCase("en-US"); if (!entities) return null; return ( diff --git a/plugins/techdocs/src/home/components/DocsTable.tsx b/plugins/techdocs/src/home/components/DocsTable.tsx index f210e5f678..9d88f50ab3 100644 --- a/plugins/techdocs/src/home/components/DocsTable.tsx +++ b/plugins/techdocs/src/home/components/DocsTable.tsx @@ -56,7 +56,7 @@ export const DocsTable = ({ 'techdocs.legacyUseCaseSensitiveTripletPaths', ) ? (str: string) => str - : (str: string) => str.toLocaleLowerCase(); + : (str: string) => str.toLocaleLowerCase("en-US"); if (!entities) return null; From c5e5b7815588a986d4232103b112f6a7542b3f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Tue, 9 Nov 2021 12:51:40 +0300 Subject: [PATCH 12/95] changeset added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .changeset/chatty-months-report.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/chatty-months-report.md diff --git a/.changeset/chatty-months-report.md b/.changeset/chatty-months-report.md new file mode 100644 index 0000000000..a0925be239 --- /dev/null +++ b/.changeset/chatty-months-report.md @@ -0,0 +1,6 @@ +--- +'@backstage/techdocs-common': minor +'@backstage/plugin-techdocs': minor +--- + +OpenStack Swift Migration Support added From 884c6d0bc5c3e2cca96a7f46671b3ccc16bc50c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Tue, 9 Nov 2021 13:08:34 +0300 Subject: [PATCH 13/95] prettier runned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .../src/stages/publish/openStackSwift.ts | 35 ++++++++++++------- .../src/home/components/DocsCardGrid.tsx | 2 +- .../src/home/components/DocsTable.tsx | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.ts index 83e17fa466..7b623933e3 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.ts @@ -24,7 +24,11 @@ import { SwiftClient } from '@trendyol-js/openstack-swift-sdk'; import { NotFound } from '@trendyol-js/openstack-swift-sdk/lib/types'; import { Stream, Readable } from 'stream'; import { Logger } from 'winston'; -import { getFileTreeRecursively, getHeadersForFileExtension, lowerCaseEntityTripletInStoragePath } from './helpers'; +import { + getFileTreeRecursively, + getHeadersForFileExtension, + lowerCaseEntityTripletInStoragePath, +} from './helpers'; import { PublisherBase, PublishRequest, @@ -77,7 +81,7 @@ export class OpenStackSwiftPublish implements PublisherBase { } catch (error) { throw new Error( "Since techdocs.publisher.type is set to 'openStackSwift' in your app config, " + - 'techdocs.publisher.openStackSwift.containerName is required.', + 'techdocs.publisher.openStackSwift.containerName is required.', ); } @@ -115,9 +119,9 @@ export class OpenStackSwiftPublish implements PublisherBase { } this.logger.error( `Could not retrieve metadata about the OpenStack Swift container ${this.containerName}. ` + - 'Make sure the container exists. Also make sure that authentication is setup either by ' + - 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' + - 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', + 'Make sure the container exists. Also make sure that authentication is setup either by ' + + 'explicitly defining credentials and region in techdocs.publisher.openStackSwift in app config or ' + + 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', ); return { isAvailable: false, @@ -290,7 +294,6 @@ export class OpenStackSwiftPublish implements PublisherBase { } } - async migrateDocsCase({ removeOriginal = false, concurrency = 25, @@ -317,7 +320,12 @@ export class OpenStackSwiftPublish implements PublisherBase { try { this.logger.verbose(`Migrating ${file} to ${newPath}`); - await this.storageClient.copy(this.containerName, file, this.containerName, newPath); + await this.storageClient.copy( + this.containerName, + file, + this.containerName, + newPath, + ); if (removeOriginal) { await this.storageClient.delete(this.containerName, file); } @@ -331,17 +339,20 @@ export class OpenStackSwiftPublish implements PublisherBase { } /** - * Returns a list of all object keys from the configured container. - */ + * Returns a list of all object keys from the configured container. + */ protected async getAllObjectsFromContainer( { prefix } = { prefix: '' }, ): Promise { let objects: string[] = []; - let allObjects: any; const OSS_MAX_LIMIT = Math.pow(2, 31) - 1; - allObjects = await this.storageClient.list(this.containerName, prefix, OSS_MAX_LIMIT) - objects = allObjects.map((object: any) => object = object.name) + const allObjects = await this.storageClient.list( + this.containerName, + prefix, + OSS_MAX_LIMIT, + ); + objects = allObjects.map((object: any) => object.name); return objects; } diff --git a/plugins/techdocs/src/home/components/DocsCardGrid.tsx b/plugins/techdocs/src/home/components/DocsCardGrid.tsx index eadfd0c8b6..77e69995ae 100644 --- a/plugins/techdocs/src/home/components/DocsCardGrid.tsx +++ b/plugins/techdocs/src/home/components/DocsCardGrid.tsx @@ -39,7 +39,7 @@ export const DocsCardGrid = ({ 'techdocs.legacyUseCaseSensitiveTripletPaths', ) ? (str: string) => str - : (str: string) => str.toLocaleLowerCase("en-US"); + : (str: string) => str.toLocaleLowerCase('en-US'); if (!entities) return null; return ( diff --git a/plugins/techdocs/src/home/components/DocsTable.tsx b/plugins/techdocs/src/home/components/DocsTable.tsx index 9d88f50ab3..0e64c2e2e8 100644 --- a/plugins/techdocs/src/home/components/DocsTable.tsx +++ b/plugins/techdocs/src/home/components/DocsTable.tsx @@ -56,7 +56,7 @@ export const DocsTable = ({ 'techdocs.legacyUseCaseSensitiveTripletPaths', ) ? (str: string) => str - : (str: string) => str.toLocaleLowerCase("en-US"); + : (str: string) => str.toLocaleLowerCase('en-US'); if (!entities) return null; From 740e06ca28eeb7ca66828d61af6565fb8843b597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Wed, 10 Nov 2021 13:41:46 +0300 Subject: [PATCH 14/95] yarn lock update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- yarn.lock | 66 +++++++++---------------------------------------------- 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/yarn.lock b/yarn.lock index 915dfdfbda..c360ca8ec3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4613,7 +4613,7 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@mswjs/cookies@^0.1.5", "@mswjs/cookies@^0.1.6": +"@mswjs/cookies@^0.1.6": version "0.1.6" resolved "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.6.tgz#176f77034ab6d7373ae5c94bcbac36fee8869249" integrity sha512-A53XD5TOfwhpqAmwKdPtg1dva5wrng2gH5xMvklzbd9WLTSVU953eCRa8rtrrm6G7Cy60BOGsBRN89YQK0mlKA== @@ -4621,17 +4621,6 @@ "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" -"@mswjs/interceptors@^0.10.0": - version "0.10.0" - resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.10.0.tgz#f5aad03c2c0591d164e3ed178b21942f1c2f8061" - integrity sha512-/M0GGpid5q2EDI+Keas1sLYF3VZFXHDE5gCmX/jHdp+OJFruVNca3PUk7A8KnGdPpuycZogdPsmRBSOXwjyA7A== - dependencies: - "@open-draft/until" "^1.0.3" - debug "^4.3.0" - headers-utils "^3.0.2" - strict-event-emitter "^0.2.0" - xmldom "^0.6.0" - "@mswjs/interceptors@^0.12.6": version "0.12.7" resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" @@ -6706,10 +6695,10 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@trendyol-js/openstack-swift-sdk@^0.0.5": - version "0.0.5" - resolved "https://registry.npmjs.org/@trendyol-js/openstack-swift-sdk/-/openstack-swift-sdk-0.0.5.tgz#65be3c42b8dbafc57f2f2a46c327e2ad51e5a70e" - integrity sha512-KS5nz0cvd35UUyMzhZm+btGV4prtA1KNE7CCMOGBdVxoMGl06Qidli3HgHoc2I9jLPmky1SPp5yzQUwrsyWa0g== +"@trendyol-js/openstack-swift-sdk@^0.0.4": + version "0.0.4" + resolved "https://registry.npmjs.org/@trendyol-js/openstack-swift-sdk/-/openstack-swift-sdk-0.0.4.tgz#570c6ab950319156c175ace005b4fb4d9f895d47" + integrity sha512-9YKOjov+V+yzptei6+B9QPuC5pOMTBTg/NQpb1ZbxvlOaYpWU4HHpSH2BkIFYZ8vYyAfzFNG1T2rjpQ2ZQDUtQ== dependencies: agentkeepalive "^4.1.4" axios "^0.21.1" @@ -6923,7 +6912,7 @@ dependencies: "@types/express" "*" -"@types/cookie@^0.4.0", "@types/cookie@^0.4.1": +"@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== @@ -12597,7 +12586,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.2, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.0, debug@^4.3.1, debug@^4.3.2: +debug@4, debug@4.3.2, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.2" resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -15733,11 +15722,6 @@ graphql@^15.3.0: resolved "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad" integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw== -graphql@^15.4.0: - version "15.7.2" - resolved "https://registry.npmjs.org/graphql/-/graphql-15.7.2.tgz#85ab0eeb83722977151b3feb4d631b5f2ab287ef" - integrity sha512-AnnKk7hFQFmU/2I9YSQf3xw44ctnSFCfp3zE0N6W174gqe9fWG/2rKaKxROK7CcI3XtERpjEKFqts8o319Kf7A== - graphql@^15.5.1: version "15.6.1" resolved "https://registry.npmjs.org/graphql/-/graphql-15.6.1.tgz#9125bdf057553525da251e19e96dab3d3855ddfc" @@ -16665,7 +16649,7 @@ inquirer@^8.0.0: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^8.1.0, inquirer@^8.1.1: +inquirer@^8.1.1: version "8.2.0" resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== @@ -20694,31 +20678,6 @@ msal@^1.0.2: dependencies: tslib "^1.9.3" -msw@^0.29.0: - version "0.29.0" - resolved "https://registry.npmjs.org/msw/-/msw-0.29.0.tgz#7242d575cb01db0c925241587df1fc2b79230d78" - integrity sha512-C/wz1d5uAEZRvAPAYrXG1rwLxXl0+BOs+JPrCzasoABZW3ATwS6ifSze+/DAgA93e9M86RXwvy6yDtZeZWmCFQ== - dependencies: - "@mswjs/cookies" "^0.1.5" - "@mswjs/interceptors" "^0.10.0" - "@open-draft/until" "^1.0.3" - "@types/cookie" "^0.4.0" - "@types/inquirer" "^7.3.1" - "@types/js-levenshtein" "^1.1.0" - chalk "^4.1.1" - chokidar "^3.4.2" - cookie "^0.4.1" - graphql "^15.4.0" - headers-utils "^3.0.2" - inquirer "^8.1.0" - js-levenshtein "^1.1.6" - node-fetch "^2.6.1" - node-match-path "^0.6.3" - statuses "^2.0.0" - strict-event-emitter "^0.2.0" - type-fest "^1.1.3" - yargs "^17.0.1" - msw@^0.35.0: version "0.35.0" resolved "https://registry.npmjs.org/msw/-/msw-0.35.0.tgz#18a4ceb6c822ef226a30421d434413bc45030d38" @@ -27681,7 +27640,7 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^1.1.3, type-fest@^1.2.2: +type-fest@^1.2.2: version "1.4.0" resolved "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== @@ -29196,11 +29155,6 @@ xmlchars@^2.2.0: resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xmldom@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" - integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== - xpath@0.0.32: version "0.0.32" resolved "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" @@ -29495,4 +29449,4 @@ zwitch@^1.0.0: zwitch@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz#91f8d0e901ffa3d66599756dde7f57b17c95dce1" - integrity sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA== + integrity sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA== \ No newline at end of file From 38d6df6bb9ecdce798ea9033f5b68490dcfe9823 Mon Sep 17 00:00:00 2001 From: Dominik Schwank Date: Thu, 4 Nov 2021 16:05:15 +0100 Subject: [PATCH 15/95] feat(catalog): remove 'View Api' link from AboutCard Signed-off-by: Dominik Schwank --- .changeset/big-months-float.md | 6 +++++ .../src/components/AboutCard/AboutCard.tsx | 25 +------------------ 2 files changed, 7 insertions(+), 24 deletions(-) create mode 100644 .changeset/big-months-float.md diff --git a/.changeset/big-months-float.md b/.changeset/big-months-float.md new file mode 100644 index 0000000000..9acb7002e7 --- /dev/null +++ b/.changeset/big-months-float.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Remove the "View Api" icon in the AboutCard, as the information is misleading for some users and is +duplicated in the tabs above. diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 68777d4109..7e44ae4efb 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -18,8 +18,6 @@ import { Entity, ENTITY_DEFAULT_NAMESPACE, LOCATION_ANNOTATION, - RELATION_CONSUMES_API, - RELATION_PROVIDES_API, stringifyEntityRef, } from '@backstage/catalog-model'; import { @@ -36,7 +34,6 @@ import { import { catalogApiRef, getEntityMetadataEditUrl, - getEntityRelations, getEntitySourceLocation, useEntity, } from '@backstage/plugin-catalog-react'; @@ -51,7 +48,6 @@ import { import CachedIcon from '@material-ui/icons/Cached'; import DocsIcon from '@material-ui/icons/Description'; import EditIcon from '@material-ui/icons/Edit'; -import ExtensionIcon from '@material-ui/icons/Extension'; import React, { useCallback } from 'react'; import { viewTechDocRouteRef } from '../../routes'; import { AboutContent } from './AboutContent'; @@ -95,16 +91,6 @@ export function AboutCard({ variant }: AboutCardProps) { scmIntegrationsApi, ); const entityMetadataEditUrl = getEntityMetadataEditUrl(entity); - const providesApiRelations = getEntityRelations( - entity, - RELATION_PROVIDES_API, - ); - const consumesApiRelations = getEntityRelations( - entity, - RELATION_CONSUMES_API, - ); - const hasApis = - providesApiRelations.length > 0 || consumesApiRelations.length > 0; const viewInSource: IconLinkVerticalProps = { label: 'View Source', @@ -126,13 +112,6 @@ export function AboutCard({ variant }: AboutCardProps) { name: entity.metadata.name, }), }; - const viewApi: IconLinkVerticalProps = { - title: hasApis ? '' : 'No APIs available', - label: 'View API', - disabled: !hasApis, - icon: , - href: 'api', - }; let cardClass = ''; if (variant === 'gridItem') { @@ -181,9 +160,7 @@ export function AboutCard({ variant }: AboutCardProps) { } - subheader={ - - } + subheader={} /> From 55ced4d6288ff08919882846adf79a9700a746e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Nov 2021 04:08:39 +0000 Subject: [PATCH 16/95] build(deps): bump @rollup/plugin-commonjs from 17.1.0 to 21.0.1 Bumps [@rollup/plugin-commonjs](https://github.com/rollup/plugins/tree/HEAD/packages/commonjs) from 17.1.0 to 21.0.1. - [Release notes](https://github.com/rollup/plugins/releases) - [Changelog](https://github.com/rollup/plugins/blob/master/packages/commonjs/CHANGELOG.md) - [Commits](https://github.com/rollup/plugins/commits/commonjs-v21.0.1/packages/commonjs) --- updated-dependencies: - dependency-name: "@rollup/plugin-commonjs" dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- packages/cli/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 23a2bef027..89a210290b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -37,7 +37,7 @@ "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", "@octokit/request": "^5.4.12", - "@rollup/plugin-commonjs": "^17.1.0", + "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-json": "^4.0.2", "@rollup/plugin-node-resolve": "^13.0.0", "@rollup/plugin-yaml": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 42c43fe4b7..5fc4c40365 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5309,10 +5309,10 @@ react-router-dom "6.0.0-beta.0" react-use "^17.2.4" -"@rollup/plugin-commonjs@^17.1.0": - version "17.1.0" - resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d" - integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew== +"@rollup/plugin-commonjs@^21.0.1": + version "21.0.1" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz#1e57c81ae1518e4df0954d681c642e7d94588fee" + integrity sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg== dependencies: "@rollup/pluginutils" "^3.1.0" commondir "^1.0.1" From 2163e83fa22e12b2f1727b6b9cbc49c6bd42028e Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Thu, 28 Oct 2021 19:57:23 -0400 Subject: [PATCH 17/95] Refactor create-app tasks and introduce regression tests - Tasks were moved from the script entrypoint to the lib directory - Doc comments were added for each task function defined in `src/lib/tasks.ts` - The `yarn test` script was added to package.json - Unit tests were written for each task -- verying file operations using fs-mock Signed-off-by: Colton Padden --- .changeset/many-sloths-cross.md | 5 + packages/create-app/package.json | 2 + packages/create-app/src/createApp.ts | 92 ++-------- packages/create-app/src/lib/tasks.test.ts | 208 ++++++++++++++++++++++ packages/create-app/src/lib/tasks.ts | 114 +++++++++++- 5 files changed, 342 insertions(+), 79 deletions(-) create mode 100644 .changeset/many-sloths-cross.md create mode 100644 packages/create-app/src/lib/tasks.test.ts diff --git a/.changeset/many-sloths-cross.md b/.changeset/many-sloths-cross.md new file mode 100644 index 0000000000..07cb6d2f61 --- /dev/null +++ b/.changeset/many-sloths-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Refactor and add regression tests for create-app tasks diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 2820d41ed6..7c1feec683 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -23,6 +23,7 @@ "scripts": { "build": "backstage-cli build --outputs cjs", "lint": "backstage-cli lint", + "test": "backstage-cli test", "clean": "backstage-cli clean", "start": "nodemon --" }, @@ -40,6 +41,7 @@ "@types/fs-extra": "^9.0.1", "@types/inquirer": "^7.3.1", "@types/recursive-readdir": "^2.2.0", + "mock-fs": "^5.1.1", "ts-node": "^10.0.0" }, "peerDependencies": { diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index d9719742fb..8823e6c858 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -14,85 +14,21 @@ * limitations under the License. */ -import fs from 'fs-extra'; -import { promisify } from 'util'; import chalk from 'chalk'; import { Command } from 'commander'; import inquirer, { Answers, Question } from 'inquirer'; -import { exec as execCb } from 'child_process'; import { resolve as resolvePath } from 'path'; import { findPaths } from '@backstage/cli-common'; import os from 'os'; -import { Task, templatingTask } from './lib/tasks'; - -const exec = promisify(execCb); - -async function checkAppExists(rootDir: string, name: string) { - await Task.forItem('checking', name, async () => { - const destination = resolvePath(rootDir, name); - - if (await fs.pathExists(destination)) { - const existing = chalk.cyan(destination.replace(`${rootDir}/`, '')); - throw new Error( - `A directory with the same name already exists: ${existing}\nPlease try again with a different app name`, - ); - } - }); -} - -async function checkPathExists(path: string) { - await Task.forItem('checking', path, async () => { - try { - await fs.mkdirs(path); - } catch (error) { - // will fail if a file already exists at given `path` - throw new Error(`Failed to create app directory: ${error.message}`); - } - }); -} - -async function createTemporaryAppFolder(tempDir: string) { - await Task.forItem('creating', 'temporary directory', async () => { - try { - await fs.mkdir(tempDir); - } catch (error) { - throw new Error(`Failed to create temporary app directory, ${error}`); - } - }); -} - -async function buildApp(appDir: string) { - const runCmd = async (cmd: string) => { - await Task.forItem('executing', cmd, async () => { - process.chdir(appDir); - - await exec(cmd).catch(error => { - process.stdout.write(error.stderr); - process.stdout.write(error.stdout); - throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); - }); - }); - }; - - await runCmd('yarn install'); - await runCmd('yarn tsc'); -} - -async function moveApp(tempDir: string, destination: string, id: string) { - await Task.forItem('moving', id, async () => { - await fs - .move(tempDir, destination) - .catch(error => { - throw new Error( - `Failed to move app from ${tempDir} to ${destination}: ${error.message}`, - ); - }) - .finally(() => { - // remove temporary files on both success and failure - fs.removeSync(tempDir); - }); - }); -} +import { + Task, + buildAppTask, + checkAppExistsTask, + checkPathExistsTask, + createTemporaryAppFolderTask, + moveAppTask, + templatingTask, +} from './lib/tasks'; export default async (cmd: Command): Promise => { /* eslint-disable-next-line no-restricted-syntax */ @@ -143,7 +79,7 @@ export default async (cmd: Command): Promise => { // Template directly to specified path Task.section('Checking that supplied path exists'); - await checkPathExists(appDir); + await checkPathExistsTask(appDir); Task.section('Preparing files'); await templatingTask(templateDir, cmd.path, answers); @@ -151,21 +87,21 @@ export default async (cmd: Command): Promise => { // Template to temporary location, and then move files Task.section('Checking if the directory is available'); - await checkAppExists(paths.targetDir, answers.name); + await checkAppExistsTask(paths.targetDir, answers.name); Task.section('Creating a temporary app directory'); - await createTemporaryAppFolder(tempDir); + await createTemporaryAppFolderTask(tempDir); Task.section('Preparing files'); await templatingTask(templateDir, tempDir, answers); Task.section('Moving to final location'); - await moveApp(tempDir, appDir, answers.name); + await moveAppTask(tempDir, appDir, answers.name); } if (!cmd.skipInstall) { Task.section('Building the app'); - await buildApp(appDir); + await buildAppTask(appDir); } Task.log(); diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts new file mode 100644 index 0000000000..0abbb8e0a2 --- /dev/null +++ b/packages/create-app/src/lib/tasks.test.ts @@ -0,0 +1,208 @@ +/* + * 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 fs from 'fs-extra'; +import mockFs from 'mock-fs'; +import child_process, { ChildProcess } from 'child_process'; +import path from 'path'; +import { + buildAppTask, + checkAppExistsTask, + checkPathExistsTask, + createTemporaryAppFolderTask, + moveAppTask, + templatingTask, +} from './tasks'; + +jest.mock('child_process'); + +beforeEach(() => { + mockFs({ + 'projects/my-module.ts': '', + 'projects/dir/my-file.txt': '', + 'tmp/mockApp/.gitignore': '', + 'tmp/mockApp/package.json': '', + 'tmp/mockApp/packages/app/package.json': '', + // load templates into mock filesystem + 'templates/': mockFs.load(path.resolve(__dirname, '../../templates/')), + }); +}); + +afterEach(() => { + mockFs.restore(); +}); + +describe('checkAppExistsTask', () => { + it('should do nothing if the directory does not exist', async () => { + const dir = 'projects/'; + const name = 'MyNewApp'; + await expect(checkAppExistsTask(dir, name)).resolves.not.toThrow(); + }); + + it('should throw an error when a file of the same name exists', async () => { + const dir = 'projects/'; + const name = 'my-module.ts'; + await expect(checkAppExistsTask(dir, name)).rejects.toThrow( + 'already exists', + ); + }); + + it('should throw an error when a directory of the same name exists', async () => { + const dir = 'projects/'; + const name = 'dir'; + await expect(checkAppExistsTask(dir, name)).rejects.toThrow( + 'already exists', + ); + }); +}); + +describe('checkPathExistsTask', () => { + it('should create a directory at the given path', async () => { + const appDir = 'projects/newProject'; + await expect(checkPathExistsTask(appDir)).resolves.not.toThrow(); + expect(fs.existsSync(appDir)).toBe(true); + }); + + it('should do nothing if a directory of the same name exists', async () => { + const appDir = 'projects/dir'; + await expect(checkPathExistsTask(appDir)).resolves.not.toThrow(); + expect(fs.existsSync(appDir)).toBe(true); + }); + + it('should fail if a file of the same name exists', async () => { + await expect(checkPathExistsTask('projects/my-module.ts')).rejects.toThrow( + 'already exists', + ); + }); +}); + +describe('createTemporaryAppFolderTask', () => { + it('should create a directory at a given path', async () => { + const tempDir = 'projects/tmpFolder'; + await expect(createTemporaryAppFolderTask(tempDir)).resolves.not.toThrow(); + expect(fs.existsSync(tempDir)).toBe(true); + }); + + it('should fail if a directory of the same name exists', async () => { + const tempDir = 'projects/dir'; + await expect(createTemporaryAppFolderTask(tempDir)).rejects.toThrow( + 'file already exists', + ); + }); + + it('should fail if a file of the same name exists', async () => { + const tempDir = 'projects/dir/my-file.txt'; + await expect(createTemporaryAppFolderTask(tempDir)).rejects.toThrow( + 'file already exists', + ); + }); +}); + +describe('buildAppTask', () => { + it('should change to `appDir` and run `yarn install` and `yarn tsc`', async () => { + const mockChdir = jest.spyOn(process, 'chdir'); + const mockExec = jest.spyOn(child_process, 'exec'); + + // requires callback implementation to support `promisify` wrapper + // https://stackoverflow.com/a/60579617/10044859 + mockExec.mockImplementation((_: string, callback?: any): ChildProcess => { + callback(null, 'stdout', 'stderr'); + return; + }); + + const appDir = 'projects/dir'; + await expect(buildAppTask(appDir)).resolves.not.toThrow(); + + expect(mockChdir).toBeCalledTimes(2); + expect(mockChdir).toHaveBeenNthCalledWith(1, appDir); + expect(mockChdir).toHaveBeenNthCalledWith(2, appDir); + + expect(mockExec).toBeCalledTimes(2); + expect(mockExec).toHaveBeenNthCalledWith( + 1, + 'yarn install', + expect.any(Function), + ); + expect(mockExec).toHaveBeenNthCalledWith( + 2, + 'yarn tsc', + expect.any(Function), + ); + }); + + it('should fail if project directory does not exist', async () => { + const appDir = 'projects/missingProject'; + await expect(buildAppTask(appDir)).rejects.toThrow( + 'no such file or directory', + ); + }); +}); + +describe('moveAppTask', () => { + const tempDir = 'tmp/mockApp/'; + const id = 'myApp'; + + it('should move all files in the temp dir to the target dir', async () => { + const destination = 'projects/mockApp'; + await moveAppTask(tempDir, destination, id); + expect(fs.existsSync('projects/mockApp/.gitignore')).toBe(true); + expect(fs.existsSync('projects/mockApp/package.json')).toBe(true); + expect(fs.existsSync('projects/mockApp/packages/app/package.json')).toBe( + true, + ); + }); + + it('should fail to move files if destination already exists', async () => { + const destination = 'projects'; + await expect(moveAppTask(tempDir, destination, id)).rejects.toThrow( + 'dest already exists', + ); + }); + + it('should remove temporary files if move succeeded', async () => { + const destination = 'projects/mockApp'; + await moveAppTask(tempDir, destination, id); + expect(fs.existsSync('tmp/mockApp')).toBe(false); + }); + + it('should remove temporary files if move failed', async () => { + const destination = 'projects'; + await expect(moveAppTask(tempDir, destination, id)).rejects.toThrow(); + expect(fs.existsSync('tmp/mockApp')).toBe(false); + }); +}); + +describe('templatingTask', () => { + it('should generate a project populating context parameters', async () => { + const templateDir = 'templates/default-app'; + const destinationDir = 'templatedApp'; + const context = { + name: 'SuperCoolBackstageInstance', + dbTypeSqlite: true, + }; + await templatingTask(templateDir, destinationDir, context); + expect(fs.existsSync('templatedApp/package.json')).toBe(true); + expect(fs.existsSync('templatedApp/.dockerignore')).toBe(true); + // catalog was populated with `context.name` + expect( + fs.readFileSync('templatedApp/catalog-info.yaml', 'utf-8'), + ).toContain('name: SuperCoolBackstageInstance'); + // backend dependencies include `sqlite3` from `context.SQLite` + expect( + fs.readFileSync('templatedApp/packages/backend/package.json', 'utf-8'), + ).toContain('"sqlite3"'); + }); +}); diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index 19f0ed66b8..0b3ca63728 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -18,11 +18,14 @@ import chalk from 'chalk'; import fs from 'fs-extra'; import handlebars from 'handlebars'; import ora from 'ora'; -import { basename, dirname } from 'path'; import recursive from 'recursive-readdir'; +import { basename, dirname, resolve as resolvePath } from 'path'; +import { exec as execCb } from 'child_process'; import { packageVersions } from './versions'; +import { promisify } from 'util'; const TASK_NAME_MAX_LENGTH = 14; +const exec = promisify(execCb); export class Task { static log(name: string = '') { @@ -65,6 +68,13 @@ export class Task { } } +/** + * Generate a templated backstage project + * + * @param templateDir - location containing template files + * @param destinationDir - location to save templated project + * @param context - template parameters + */ export async function templatingTask( templateDir: string, destinationDir: string, @@ -116,3 +126,105 @@ export async function templatingTask( } } } + +/** + * Verify that application target does not already exist + * + * @param rootDir - The directory to create application folder `name` + * @param name - The specified name of the application + * @Throws Error - If directory with name of `destination` already exists + */ +export async function checkAppExistsTask(rootDir: string, name: string) { + await Task.forItem('checking', name, async () => { + const destination = resolvePath(rootDir, name); + + if (await fs.pathExists(destination)) { + const existing = chalk.cyan(destination.replace(`${rootDir}/`, '')); + throw new Error( + `A directory with the same name already exists: ${existing}\nPlease try again with a different app name`, + ); + } + }); +} + +/** + * Verify that application `path` exists, otherwise create the directory + * + * @param {string} path - target to create directory + * @throws {Error} if `path` is a file, or `fs.mkdir` fails + */ +export async function checkPathExistsTask(path: string) { + await Task.forItem('checking', path, async () => { + try { + await fs.mkdirs(path); + } catch (error) { + // will fail if a file already exists at given `path` + throw new Error(`Failed to create app directory: ${error.message}`); + } + }); +} + +/** + * Create a folder to store templated files + * + * @param {string} tempDir - target temporary directory + * @throws {Error} if `fs.mkdir` fails + */ +export async function createTemporaryAppFolderTask(tempDir: string) { + await Task.forItem('creating', 'temporary directory', async () => { + try { + await fs.mkdir(tempDir); + } catch (error) { + throw new Error(`Failed to create temporary app directory, ${error}`); + } + }); +} + +/** + * Run `yarn install` and `run tsc` in application directory + * + * @param {string} appDir - location of application to build + */ +export async function buildAppTask(appDir: string) { + const runCmd = async (cmd: string) => { + await Task.forItem('executing', cmd, async () => { + process.chdir(appDir); + await exec(cmd).catch(error => { + process.stdout.write(error.stderr); + process.stdout.write(error.stdout); + throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); + }); + }); + }; + + await runCmd('yarn install'); + await runCmd('yarn tsc'); +} + +/** + * Move temporary directory to destination application folder + * + * @param {string} tempDir source path to copy files from + * @param {string} destination target path to copy files + * @param {string} id + * @throws {Error} if `fs.move` fails + */ +export async function moveAppTask( + tempDir: string, + destination: string, + id: string, +) { + await Task.forItem('moving', id, async () => { + await fs + .move(tempDir, destination) + .catch(error => { + throw new Error( + `Failed to move app from ${tempDir} to ${destination}: ${error.message}`, + ); + }) + .finally(() => { + // remove temporary files on both success and failure + fs.removeSync(tempDir); + }); + }); +} From 1d666cb67e28900c3ada3d1937708df7cdd1f1df Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Fri, 29 Oct 2021 10:44:32 -0400 Subject: [PATCH 18/95] Add regression tests to createApp entrypoint Ensure that appropriate tasks are called depending on inquirer prompt answers, and command-line argument options (--path, and --skip-install). Signed-off-by: Colton Padden --- packages/create-app/src/createApp.test.ts | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 packages/create-app/src/createApp.test.ts diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts new file mode 100644 index 0000000000..b2d946f307 --- /dev/null +++ b/packages/create-app/src/createApp.test.ts @@ -0,0 +1,70 @@ +/* + * 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 inquirer from 'inquirer'; +import { Command } from 'commander'; +import * as tasks from './lib/tasks'; +import createApp from './createApp'; + +jest.mock('./lib/tasks'); + +const promptMock = jest.spyOn(inquirer, 'prompt'); +const checkPathExistsMock = jest.spyOn(tasks, 'checkPathExistsTask'); +const templatingMock = jest.spyOn(tasks, 'templatingTask'); +const checkAppExistsMock = jest.spyOn(tasks, 'checkAppExistsTask'); +const createTemporaryAppFolderMock = jest.spyOn( + tasks, + 'createTemporaryAppFolderTask', +); +const moveAppMock = jest.spyOn(tasks, 'moveAppTask'); +const buildAppMock = jest.spyOn(tasks, 'buildAppTask'); + +describe('command entrypoint', () => { + beforeEach(() => { + promptMock.mockResolvedValueOnce({ + name: 'MyApp', + dbType: 'PostgreSQL', + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('should call expected tasks with no path option', async () => { + const cmd = {} as unknown as Command; + await createApp(cmd); + expect(checkAppExistsMock).toHaveBeenCalled(); + expect(createTemporaryAppFolderMock).toHaveBeenCalled(); + expect(templatingMock).toHaveBeenCalled(); + expect(moveAppMock).toHaveBeenCalled(); + expect(buildAppMock).toHaveBeenCalled(); + }); + + it('should call expected tasks with path option', async () => { + const cmd = { path: 'myDirectory' } as unknown as Command; + await createApp(cmd); + expect(checkPathExistsMock).toHaveBeenCalled(); + expect(templatingMock).toHaveBeenCalled(); + expect(buildAppMock).toHaveBeenCalled(); + }); + + it('should not call `buildAppTask` when `skipInstall` is supplied', async () => { + const cmd = { skipInstall: true } as unknown as Command; + await createApp(cmd); + expect(buildAppMock).not.toHaveBeenCalled(); + }); +}); From bd90ded8b7d93c140ade4802cdacda9ccc22a465 Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Fri, 29 Oct 2021 11:32:23 -0400 Subject: [PATCH 19/95] Add type annotations for mocked child_process.exec Signed-off-by: Colton Padden --- packages/create-app/src/lib/tasks.test.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index 0abbb8e0a2..d69526cb49 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -16,7 +16,7 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; -import child_process, { ChildProcess } from 'child_process'; +import child_process from 'child_process'; import path from 'path'; import { buildAppTask, @@ -114,22 +114,24 @@ describe('createTemporaryAppFolderTask', () => { describe('buildAppTask', () => { it('should change to `appDir` and run `yarn install` and `yarn tsc`', async () => { const mockChdir = jest.spyOn(process, 'chdir'); - const mockExec = jest.spyOn(child_process, 'exec'); + const mockExec = child_process.exec as unknown as jest.MockedFunction< + ( + command: string, + callback: (error: null, stdout: string, stderr: string) => void, + ) => void + >; // requires callback implementation to support `promisify` wrapper // https://stackoverflow.com/a/60579617/10044859 - mockExec.mockImplementation((_: string, callback?: any): ChildProcess => { - callback(null, 'stdout', 'stderr'); - return; + mockExec.mockImplementation((_command, callback) => { + callback(null, 'standard out', 'standard error'); }); const appDir = 'projects/dir'; await expect(buildAppTask(appDir)).resolves.not.toThrow(); - expect(mockChdir).toBeCalledTimes(2); expect(mockChdir).toHaveBeenNthCalledWith(1, appDir); expect(mockChdir).toHaveBeenNthCalledWith(2, appDir); - expect(mockExec).toBeCalledTimes(2); expect(mockExec).toHaveBeenNthCalledWith( 1, From 1b3fab7193b7b6c7bda547c12ed763ac144f210b Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Fri, 29 Oct 2021 11:45:01 -0400 Subject: [PATCH 20/95] Use mock-fs during createApp to prevent filesystem modifications Signed-off-by: Colton Padden --- packages/create-app/src/createApp.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index b2d946f307..7b3b8f124c 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -15,12 +15,25 @@ */ import inquirer from 'inquirer'; +import mockFs from 'mock-fs'; +import path from 'path'; import { Command } from 'commander'; import * as tasks from './lib/tasks'; import createApp from './createApp'; jest.mock('./lib/tasks'); +beforeAll(() => { + mockFs({ + 'package.json': '', // required by `findPaths(__dirname)` + 'templates/': mockFs.load(path.resolve(__dirname, '../templates/')), + }); +}); + +afterAll(() => { + mockFs.restore(); +}); + const promptMock = jest.spyOn(inquirer, 'prompt'); const checkPathExistsMock = jest.spyOn(tasks, 'checkPathExistsTask'); const templatingMock = jest.spyOn(tasks, 'templatingTask'); From 8acb23b29f72e29913069386783985bab262d0bf Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 17 Oct 2021 13:45:36 +0200 Subject: [PATCH 21/95] cli: initial create command Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 42 ++++++++++++++++++++++ packages/cli/src/commands/index.ts | 18 ++++++++++ 2 files changed, 60 insertions(+) create mode 100644 packages/cli/src/commands/create/create.ts diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts new file mode 100644 index 0000000000..ed4bcc9c20 --- /dev/null +++ b/packages/cli/src/commands/create/create.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Command } from 'commander'; + +function parseOptions(optionStrings: string[]): Record { + const options: Record = {}; + + for (const str of optionStrings) { + const [key] = str.split('=', 1); + const value = str.slice(key.length + 1); + if (!key || !value) { + throw new Error( + `Invalid option '${str}', must be of the format =`, + ); + } + options[key] = value; + } + + return options; +} + +export default async (cmd: Command) => { + const selected = cmd.opts().select; + console.log('DEBUG: selected =', selected); + + const options = parseOptions(cmd.opts().option); + console.log('DEBUG: options =', options); +}; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 4e01a8ca9f..06832c466b 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -75,6 +75,24 @@ export function registerCommands(program: CommanderStatic) { .option(...configOption) .action(lazy(() => import('./backend/dev').then(m => m.default))); + program + .command('create') + .storeOptionsAsProperties(false) + .description( + 'Open up an interactive guide to creating new things in your app', + ) + .option( + '--select ', + 'Select the thing you want to be creating upfront', + ) + .option( + '--option =', + 'Pre-fill options for the creation process', + (opt, arr: string[]) => [...arr, opt], + [], + ) + .action(lazy(() => import('./create/create').then(m => m.default))); + program .command('create-plugin') .option( From e607456dbcb77d779ee28af8c1980ca4f9e713be Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 17 Oct 2021 16:59:59 +0200 Subject: [PATCH 22/95] cli: create command factory foundations Signed-off-by: Patrik Oldsberg --- .../src/commands/create/FactoryRegistry.ts | 76 +++++++++++++++++++ packages/cli/src/commands/create/create.ts | 13 +++- .../create/factories/frontendPlugin.ts | 46 +++++++++++ .../src/commands/create/factories/index.ts | 17 +++++ packages/cli/src/commands/create/types.ts | 34 +++++++++ 5 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 packages/cli/src/commands/create/FactoryRegistry.ts create mode 100644 packages/cli/src/commands/create/factories/frontendPlugin.ts create mode 100644 packages/cli/src/commands/create/factories/index.ts create mode 100644 packages/cli/src/commands/create/types.ts diff --git a/packages/cli/src/commands/create/FactoryRegistry.ts b/packages/cli/src/commands/create/FactoryRegistry.ts new file mode 100644 index 0000000000..0f48acc337 --- /dev/null +++ b/packages/cli/src/commands/create/FactoryRegistry.ts @@ -0,0 +1,76 @@ +/* + * 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 inquirer from 'inquirer'; +import { AnyFactory } from './types'; +import * as factories from './factories'; +import partition from 'lodash/partition'; + +export class FactoryRegistry { + private static factoryMap = new Map( + Object.values(factories).map(factory => [factory.name, factory]), + ); + + static async interactiveSelect(preselected?: string): Promise { + let selected = preselected; + + if (!selected) { + const answers = await inquirer.prompt<{ name: string }>([ + { + type: 'list', + name: 'name', + message: 'What do you want to create?', + choices: Array.from(this.factoryMap.values()).map(factory => ({ + name: `${factory.name} - ${factory.description}`, + value: factory.name, + })), + }, + ]); + selected = answers.name; + } + + const factory = this.factoryMap.get(selected); + if (!factory) { + throw new Error(`Unknown selection '${selected}'`); + } + return factory; + } + + static async populateOptions( + factory: AnyFactory, + provided: Record, + ): Promise> { + const [hasAnswers, needsAnswers] = partition( + factory.options, + option => option.name in provided, + ); + + for (const option of hasAnswers) { + const value = provided[option.name]; + + if (option.validate) { + const result = option.validate(value); + if (result !== true) { + throw new Error(`Invalid option '${option.name}'. ${result}`); + } + } + } + + const answers = await inquirer.prompt(needsAnswers); + + return { ...provided, ...answers }; + } +} diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index ed4bcc9c20..2397e7ec58 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -15,6 +15,7 @@ */ import { Command } from 'commander'; +import { FactoryRegistry } from './FactoryRegistry'; function parseOptions(optionStrings: string[]): Record { const options: Record = {}; @@ -34,9 +35,13 @@ function parseOptions(optionStrings: string[]): Record { } export default async (cmd: Command) => { - const selected = cmd.opts().select; - console.log('DEBUG: selected =', selected); + const factory = await FactoryRegistry.interactiveSelect(cmd.opts().select); - const options = parseOptions(cmd.opts().option); - console.log('DEBUG: options =', options); + const providedOptions = parseOptions(cmd.opts().option); + const options = await FactoryRegistry.populateOptions( + factory, + providedOptions, + ); + + await factory.create(options); }; diff --git a/packages/cli/src/commands/create/factories/frontendPlugin.ts b/packages/cli/src/commands/create/factories/frontendPlugin.ts new file mode 100644 index 0000000000..195953e780 --- /dev/null +++ b/packages/cli/src/commands/create/factories/frontendPlugin.ts @@ -0,0 +1,46 @@ +/* + * 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 { createFactory } from '../types'; + +type Options = { + id: string; +}; + +export const frontendPlugin = createFactory({ + name: 'plugin', + description: 'A new frontend plugin', + options: [ + { + type: 'input', + name: 'id', + message: 'Enter an ID for the plugin', + validate: (value: string) => { + if (!value) { + return 'Please enter an ID for the plugin'; + } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { + return 'Plugin IDs must be lowercase and contain only letters, digits, and dashes.'; + } + return true; + }, + }, + ], + async create(options: Options) { + console.log( + `Creating ${this.name} with options ${JSON.stringify(options)}`, + ); + }, +}); diff --git a/packages/cli/src/commands/create/factories/index.ts b/packages/cli/src/commands/create/factories/index.ts new file mode 100644 index 0000000000..4375ba2a45 --- /dev/null +++ b/packages/cli/src/commands/create/factories/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { frontendPlugin } from './frontendPlugin'; diff --git a/packages/cli/src/commands/create/types.ts b/packages/cli/src/commands/create/types.ts new file mode 100644 index 0000000000..e5252df6bc --- /dev/null +++ b/packages/cli/src/commands/create/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DistinctQuestion } from 'inquirer'; + +export type AnyOptions = Record; + +export interface Factory { + name: string; + description: string; + options: ReadonlyArray & { name: string }>; + create(options: Options): Promise; +} + +export type AnyFactory = Factory; + +export function createFactory( + config: Factory, +): AnyFactory { + return config as AnyFactory; +} From 2ca3f5ff4c3585505a72fdd285382c44451b51d6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 11:22:30 +0100 Subject: [PATCH 23/95] cli: added support for dynamically discovering create options Signed-off-by: Patrik Oldsberg --- .../src/commands/create/FactoryRegistry.ts | 42 ++++++++++++------- .../create/factories/frontendPlugin.ts | 29 ++++++++++++- packages/cli/src/commands/create/types.ts | 3 +- 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/commands/create/FactoryRegistry.ts b/packages/cli/src/commands/create/FactoryRegistry.ts index 0f48acc337..a959896bd0 100644 --- a/packages/cli/src/commands/create/FactoryRegistry.ts +++ b/packages/cli/src/commands/create/FactoryRegistry.ts @@ -53,24 +53,36 @@ export class FactoryRegistry { factory: AnyFactory, provided: Record, ): Promise> { - const [hasAnswers, needsAnswers] = partition( - factory.options, - option => option.name in provided, - ); + let currentOptions = provided; - for (const option of hasAnswers) { - const value = provided[option.name]; - - if (option.validate) { - const result = option.validate(value); - if (result !== true) { - throw new Error(`Invalid option '${option.name}'. ${result}`); - } - } + if (factory.optionsDiscovery) { + const discoveredOptions = await factory.optionsDiscovery(); + currentOptions = { + ...currentOptions, + ...(discoveredOptions as Record), + }; } - const answers = await inquirer.prompt(needsAnswers); + if (factory.optionsPrompts) { + const [hasAnswers, needsAnswers] = partition( + factory.optionsPrompts, + option => option.name in currentOptions, + ); - return { ...provided, ...answers }; + for (const option of hasAnswers) { + const value = provided[option.name]; + + if (option.validate) { + const result = option.validate(value); + if (result !== true) { + throw new Error(`Invalid option '${option.name}'. ${result}`); + } + } + } + + currentOptions = await inquirer.prompt(needsAnswers, currentOptions); + } + + return currentOptions; } } diff --git a/packages/cli/src/commands/create/factories/frontendPlugin.ts b/packages/cli/src/commands/create/factories/frontendPlugin.ts index 195953e780..35e18b9e09 100644 --- a/packages/cli/src/commands/create/factories/frontendPlugin.ts +++ b/packages/cli/src/commands/create/factories/frontendPlugin.ts @@ -14,20 +14,27 @@ * limitations under the License. */ +import { paths } from '../../../lib/paths'; +import { getCodeownersFilePath, parseOwnerIds } from '../../../lib/codeowners'; import { createFactory } from '../types'; type Options = { id: string; + owner?: string; + codeOwnersPath?: string; }; export const frontendPlugin = createFactory({ name: 'plugin', description: 'A new frontend plugin', - options: [ + optionsDiscovery: async () => ({ + codeOwnersPath: await getCodeownersFilePath(paths.targetRoot), + }), + optionsPrompts: [ { type: 'input', name: 'id', - message: 'Enter an ID for the plugin', + message: 'Enter an ID for the plugin [required]', validate: (value: string) => { if (!value) { return 'Please enter an ID for the plugin'; @@ -37,6 +44,24 @@ export const frontendPlugin = createFactory({ return true; }, }, + { + type: 'input', + name: 'owner', + message: 'Enter an owner of the plugin to add to CODEOWNERS [optional]', + when: opts => Boolean(opts.codeOwnersPath), + validate: (value: string) => { + if (!value) { + return true; + } + + const ownerIds = parseOwnerIds(value); + if (!ownerIds) { + return 'The owner must be a space separated list of team names (e.g. @org/team-name), usernames (e.g. @username), or the email addresses (e.g. user@example.com).'; + } + + return true; + }, + }, ], async create(options: Options) { console.log( diff --git a/packages/cli/src/commands/create/types.ts b/packages/cli/src/commands/create/types.ts index e5252df6bc..a797664e37 100644 --- a/packages/cli/src/commands/create/types.ts +++ b/packages/cli/src/commands/create/types.ts @@ -21,7 +21,8 @@ export type AnyOptions = Record; export interface Factory { name: string; description: string; - options: ReadonlyArray & { name: string }>; + optionsDiscovery?(): Promise>; + optionsPrompts?: ReadonlyArray & { name: string }>; create(options: Options): Promise; } From 55e58c55684109303b8559db2ea578bf88e72c5d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 11:24:40 +0100 Subject: [PATCH 24/95] cli: moved bulk of create implementation over to lib Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 2 +- packages/cli/src/{commands => lib}/create/FactoryRegistry.ts | 0 .../src/{commands => lib}/create/factories/frontendPlugin.ts | 0 packages/cli/src/{commands => lib}/create/factories/index.ts | 0 packages/cli/src/{commands => lib}/create/types.ts | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename packages/cli/src/{commands => lib}/create/FactoryRegistry.ts (100%) rename packages/cli/src/{commands => lib}/create/factories/frontendPlugin.ts (100%) rename packages/cli/src/{commands => lib}/create/factories/index.ts (100%) rename packages/cli/src/{commands => lib}/create/types.ts (100%) diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index 2397e7ec58..4c1ca68557 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -15,7 +15,7 @@ */ import { Command } from 'commander'; -import { FactoryRegistry } from './FactoryRegistry'; +import { FactoryRegistry } from '../../lib/create/FactoryRegistry'; function parseOptions(optionStrings: string[]): Record { const options: Record = {}; diff --git a/packages/cli/src/commands/create/FactoryRegistry.ts b/packages/cli/src/lib/create/FactoryRegistry.ts similarity index 100% rename from packages/cli/src/commands/create/FactoryRegistry.ts rename to packages/cli/src/lib/create/FactoryRegistry.ts diff --git a/packages/cli/src/commands/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts similarity index 100% rename from packages/cli/src/commands/create/factories/frontendPlugin.ts rename to packages/cli/src/lib/create/factories/frontendPlugin.ts diff --git a/packages/cli/src/commands/create/factories/index.ts b/packages/cli/src/lib/create/factories/index.ts similarity index 100% rename from packages/cli/src/commands/create/factories/index.ts rename to packages/cli/src/lib/create/factories/index.ts diff --git a/packages/cli/src/commands/create/types.ts b/packages/cli/src/lib/create/types.ts similarity index 100% rename from packages/cli/src/commands/create/types.ts rename to packages/cli/src/lib/create/types.ts From 3ca0d3368eb6ec86690941ef7300b312bdfcbd5e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 11:28:54 +0100 Subject: [PATCH 25/95] cli: display create prompt messages in blue and errors in red Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/create/FactoryRegistry.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/lib/create/FactoryRegistry.ts b/packages/cli/src/lib/create/FactoryRegistry.ts index a959896bd0..1b25f5950a 100644 --- a/packages/cli/src/lib/create/FactoryRegistry.ts +++ b/packages/cli/src/lib/create/FactoryRegistry.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import chalk from 'chalk'; import inquirer from 'inquirer'; import { AnyFactory } from './types'; import * as factories from './factories'; @@ -80,7 +81,22 @@ export class FactoryRegistry { } } - currentOptions = await inquirer.prompt(needsAnswers, currentOptions); + currentOptions = await inquirer.prompt( + needsAnswers.map(option => ({ + ...option, + message: option.message && chalk.blue(option.message), + validate: + option.validate && + (async (...args) => { + const result = await option.validate!(...args); + if (typeof result === 'string') { + return chalk.red(result); + } + return result; + }), + })), + currentOptions, + ); } return currentOptions; From 84936dbcbe9fac0244c71ca90da7404a5e5d2a78 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 12:03:23 +0100 Subject: [PATCH 26/95] cli: cmd options and discovery for additional creation context Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 37 +++++++++++++++++-- packages/cli/src/commands/index.ts | 6 +++ .../lib/create/factories/frontendPlugin.ts | 8 ++-- packages/cli/src/lib/create/types.ts | 15 +++++++- 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index 4c1ca68557..07378579d2 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -14,8 +14,11 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { Command } from 'commander'; import { FactoryRegistry } from '../../lib/create/FactoryRegistry'; +import { paths } from '../../lib/paths'; +import { assertError } from '@backstage/errors'; function parseOptions(optionStrings: string[]): Record { const options: Record = {}; @@ -35,13 +38,41 @@ function parseOptions(optionStrings: string[]): Record { } export default async (cmd: Command) => { - const factory = await FactoryRegistry.interactiveSelect(cmd.opts().select); + const cmdOpts = cmd.opts(); - const providedOptions = parseOptions(cmd.opts().option); + const factory = await FactoryRegistry.interactiveSelect(cmdOpts.select); + + const providedOptions = parseOptions(cmdOpts.option); const options = await FactoryRegistry.populateOptions( factory, providedOptions, ); - await factory.create(options); + const rootPackageJson = await fs.readJson( + paths.resolveTargetRoot('package.json'), + ); + const isMonoRepo = Boolean(rootPackageJson.workspaces); + + let defaultVersion = '0.1.0'; + try { + const rootLernaJson = await fs.readJson( + paths.resolveTargetRoot('lerna.json'), + ); + if (rootLernaJson.version) { + defaultVersion = rootLernaJson.version; + } + } catch (error) { + assertError(error); + if (error.code !== 'ENOENT') { + throw error; + } + } + + await factory.create(options, { + isMonoRepo, + defaultVersion, + scope: cmdOpts.scope.replace(/^@/, ''), + npmRegistry: cmdOpts.npmRegistry, + private: Boolean(cmdOpts.private), + }); }; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 06832c466b..a37d5e9ffe 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -91,6 +91,12 @@ export function registerCommands(program: CommanderStatic) { (opt, arr: string[]) => [...arr, opt], [], ) + .option('--scope ', 'The scope to use for new packages') + .option( + '--npm-registry ', + 'The package registry to use for new packages', + ) + .option('--no-private', 'Do not mark new packages as private') .action(lazy(() => import('./create/create').then(m => m.default))); program diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index 35e18b9e09..1b13cff13d 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -16,7 +16,7 @@ import { paths } from '../../../lib/paths'; import { getCodeownersFilePath, parseOwnerIds } from '../../../lib/codeowners'; -import { createFactory } from '../types'; +import { createFactory, CreateContext } from '../types'; type Options = { id: string; @@ -63,9 +63,11 @@ export const frontendPlugin = createFactory({ }, }, ], - async create(options: Options) { + async create(options: Options, context: CreateContext) { console.log( - `Creating ${this.name} with options ${JSON.stringify(options)}`, + `Creating ${this.name} with options ${JSON.stringify( + options, + )} and context ${JSON.stringify(context)}`, ); }, }); diff --git a/packages/cli/src/lib/create/types.ts b/packages/cli/src/lib/create/types.ts index a797664e37..82bdd4978a 100644 --- a/packages/cli/src/lib/create/types.ts +++ b/packages/cli/src/lib/create/types.ts @@ -16,6 +16,19 @@ import { DistinctQuestion } from 'inquirer'; +export interface CreateContext { + /** The package scope to use for new packages */ + scope?: string; + /** The NPM registry to use for new packages */ + npmRegistry?: string; + /** Whether new packages should be marked as private */ + private: boolean; + /** Whether we are creating something in a monorepo or not */ + isMonoRepo: boolean; + /** The default version to use for new packages */ + defaultVersion: string; +} + export type AnyOptions = Record; export interface Factory { @@ -23,7 +36,7 @@ export interface Factory { description: string; optionsDiscovery?(): Promise>; optionsPrompts?: ReadonlyArray & { name: string }>; - create(options: Options): Promise; + create(options: Options, context?: CreateContext): Promise; } export type AnyFactory = Factory; From 14e8162d199cbf80dd1e4dfcce6006758e6c89ea Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 13:08:25 +0100 Subject: [PATCH 27/95] cli: added temp dir utility to create context Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 36 +++++++++++++++++----- packages/cli/src/lib/create/types.ts | 3 ++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index 07378579d2..d39fa2b9d0 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -14,7 +14,9 @@ * limitations under the License. */ +import os from 'os'; import fs from 'fs-extra'; +import { join as joinPath } from 'path'; import { Command } from 'commander'; import { FactoryRegistry } from '../../lib/create/FactoryRegistry'; import { paths } from '../../lib/paths'; @@ -68,11 +70,31 @@ export default async (cmd: Command) => { } } - await factory.create(options, { - isMonoRepo, - defaultVersion, - scope: cmdOpts.scope.replace(/^@/, ''), - npmRegistry: cmdOpts.npmRegistry, - private: Boolean(cmdOpts.private), - }); + const tempDirs = new Array(); + async function createTemporaryDirectory(name: string): Promise { + const dir = await fs.mkdtemp(joinPath(os.tmpdir(), name)); + tempDirs.push(dir); + return dir; + } + + try { + await factory.create(options, { + isMonoRepo, + defaultVersion, + scope: cmdOpts.scope.replace(/^@/, ''), + npmRegistry: cmdOpts.npmRegistry, + private: Boolean(cmdOpts.private), + createTemporaryDirectory, + }); + } finally { + for (const dir of tempDirs) { + try { + await fs.remove(dir); + } catch (error) { + console.error( + `Failed to remove temporary directory '${dir}', ${error}`, + ); + } + } + } }; diff --git a/packages/cli/src/lib/create/types.ts b/packages/cli/src/lib/create/types.ts index 82bdd4978a..629506b5f7 100644 --- a/packages/cli/src/lib/create/types.ts +++ b/packages/cli/src/lib/create/types.ts @@ -27,6 +27,9 @@ export interface CreateContext { isMonoRepo: boolean; /** The default version to use for new packages */ defaultVersion: string; + + /** Creates a temporary directory. This will always be deleted after creation is done. */ + createTemporaryDirectory(name: string): Promise; } export type AnyOptions = Record; From 867ea81d15f7a6bb8b5e05b55e432eb3c932b7ec Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Fri, 12 Nov 2021 14:47:31 +0100 Subject: [PATCH 28/95] Add changeset Signed-off-by: Ben Lambert --- .changeset/tender-gorillas-peel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tender-gorillas-peel.md diff --git a/.changeset/tender-gorillas-peel.md b/.changeset/tender-gorillas-peel.md new file mode 100644 index 0000000000..f7d7022740 --- /dev/null +++ b/.changeset/tender-gorillas-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +bump `@rollup/plugin-commonjs` from 17.1.0 to 21.0.1 From e04cce9cdb5cf02e7c9748c5bb95d9b59391e421 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 15:22:11 +0100 Subject: [PATCH 29/95] cli: wrap create factories up in a bit more built-in logging Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 28 ++++++++++++++++++++++ packages/cli/src/lib/create/types.ts | 3 +++ 2 files changed, 31 insertions(+) diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index d39fa2b9d0..7004341c3e 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -21,6 +21,7 @@ import { Command } from 'commander'; import { FactoryRegistry } from '../../lib/create/FactoryRegistry'; import { paths } from '../../lib/paths'; import { assertError } from '@backstage/errors'; +import { Task } from '../../lib/tasks'; function parseOptions(optionStrings: string[]): Record { const options: Record = {}; @@ -77,7 +78,11 @@ export default async (cmd: Command) => { return dir; } + let modified = false; try { + Task.log(); + Task.log(`Creating new ${factory.name}`); + await factory.create(options, { isMonoRepo, defaultVersion, @@ -85,7 +90,30 @@ export default async (cmd: Command) => { npmRegistry: cmdOpts.npmRegistry, private: Boolean(cmdOpts.private), createTemporaryDirectory, + markAsModified() { + modified = true; + }, }); + + Task.log(); + Task.log(`🎉 Successfully created ${factory.name}`); + Task.log(); + } catch (error) { + assertError(error); + Task.error(error.message); + + if (modified) { + Task.log('It seems that something went wrong in the creation process 🤔'); + Task.log(); + Task.log( + 'We have left the changes that were made intact in case you want to', + ); + Task.log( + 'continue manually, but you can also revert the changes and try again.', + ); + + Task.error(`🔥 Failed to create ${factory.name}!`); + } } finally { for (const dir of tempDirs) { try { diff --git a/packages/cli/src/lib/create/types.ts b/packages/cli/src/lib/create/types.ts index 629506b5f7..6a45067e9b 100644 --- a/packages/cli/src/lib/create/types.ts +++ b/packages/cli/src/lib/create/types.ts @@ -30,6 +30,9 @@ export interface CreateContext { /** Creates a temporary directory. This will always be deleted after creation is done. */ createTemporaryDirectory(name: string): Promise; + + /** Signal that the creation process got to a point where permanent modifications were made */ + markAsModified(): void; } export type AnyOptions = Record; From f8adebde77b0a6c299f1463f4a759942fab7be55 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 15:23:32 +0100 Subject: [PATCH 30/95] cli: build out task lib a bit with command execution, adding deps Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/tasks.ts | 81 +++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index e17015d6cf..691e77bd17 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -18,9 +18,14 @@ import chalk from 'chalk'; import fs from 'fs-extra'; import handlebars from 'handlebars'; import ora from 'ora'; +import { promisify } from 'util'; import { basename, dirname } from 'path'; import recursive from 'recursive-readdir'; +import { exec as execCb } from 'child_process'; import { paths } from './paths'; +import { assertError } from '@backstage/errors'; + +const exec = promisify(execCb); const TASK_NAME_MAX_LENGTH = 14; @@ -42,11 +47,11 @@ export class Task { process.exit(code); } - static async forItem( + static async forItem( task: string, item: string, - taskFunc: () => Promise, - ): Promise { + taskFunc: () => Promise, + ): Promise { const paddedTask = chalk.green(task.padEnd(TASK_NAME_MAX_LENGTH)); const spinner = ora({ @@ -56,13 +61,40 @@ export class Task { }).start(); try { - await taskFunc(); + const result = await taskFunc(); spinner.succeed(); + return result; } catch (error) { spinner.fail(); throw error; } } + + static async forCommand( + command: string, + options?: { cwd?: string; optional?: boolean }, + ) { + try { + await Task.forItem('executing', command, async () => { + await exec(command, { cwd: options?.cwd }); + }); + } catch (error) { + assertError(error); + if (error.stderr) { + process.stdout.write(error.stderr as Buffer); + } + if (error.stdout) { + process.stdout.write(error.stdout as Buffer); + } + if (options?.optional) { + Task.error(`Warning: Failed to execute command ${chalk.cyan(command)}`); + } else { + throw new Error( + `Failed to execute command '${chalk.cyan(command)}', ${error}`, + ); + } + } + } } export async function templatingTask( @@ -122,3 +154,44 @@ export async function templatingTask( } } } + +export async function addPackageDependency( + path: string, + options: { + dependencies?: Record; + devDependencies?: Record; + peerDependencies?: Record; + }, +) { + try { + const pkgJson = await fs.readJson(path); + + const normalize = (obj: Record) => { + if (Object.keys(obj).length === 0) { + return undefined; + } + return Object.fromEntries( + Object.keys(obj) + .sort() + .map(key => [key, obj[key]]), + ); + }; + + pkgJson.dependencies = normalize({ + ...pkgJson.dependencies, + ...options.dependencies, + }); + pkgJson.devDependencies = normalize({ + ...pkgJson.devDependencies, + ...options.devDependencies, + }); + pkgJson.peerDependencies = normalize({ + ...pkgJson.peerDependencies, + ...options.peerDependencies, + }); + + await fs.writeJson(path, pkgJson, { spaces: 2 }); + } catch (error) { + throw new Error(`Failed to add package dependencies, ${error}`); + } +} From 602cf4d80dabe6a24e4585da37b9a0af09a3b320 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 15:24:08 +0100 Subject: [PATCH 31/95] cli: run templating tasks in strict mode Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/tasks.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 691e77bd17..9e67f7f30c 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -117,7 +117,9 @@ export async function templatingTask( const destination = destinationFile.replace(/\.hbs$/, ''); const template = await fs.readFile(file); - const compiled = handlebars.compile(template.toString()); + const compiled = handlebars.compile(template.toString(), { + strict: true, + }); const contents = compiled( { name: basename(destination), ...context }, { From 248c8eaa25af0f4147fc383fe553765068530f44 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 15:33:45 +0100 Subject: [PATCH 32/95] cli: allow empty create option values Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index 7004341c3e..946918d77c 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -29,7 +29,7 @@ function parseOptions(optionStrings: string[]): Record { for (const str of optionStrings) { const [key] = str.split('=', 1); const value = str.slice(key.length + 1); - if (!key || !value) { + if (!key || str[key.length] !== '=') { throw new Error( `Invalid option '${str}', must be of the format =`, ); From 7b8d432de5842a420ca711e5895462118176ed12 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 15:39:17 +0100 Subject: [PATCH 33/95] cli: complete frontendPlugin create factory implementation Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/codeowners/codeowners.ts | 2 +- .../lib/create/factories/frontendPlugin.ts | 144 +++++++++++++++++- 2 files changed, 138 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/lib/codeowners/codeowners.ts b/packages/cli/src/lib/codeowners/codeowners.ts index 563bd1052d..b3fac12109 100644 --- a/packages/cli/src/lib/codeowners/codeowners.ts +++ b/packages/cli/src/lib/codeowners/codeowners.ts @@ -55,7 +55,7 @@ export function isValidSingleOwnerId(id: string): boolean { } export function parseOwnerIds( - spaceSeparatedOwnerIds: string, + spaceSeparatedOwnerIds: string | undefined, ): string[] | undefined { if (!spaceSeparatedOwnerIds || typeof spaceSeparatedOwnerIds !== 'string') { return undefined; diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index 1b13cff13d..a85403e8ed 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -14,9 +14,20 @@ * limitations under the License. */ -import { paths } from '../../../lib/paths'; -import { getCodeownersFilePath, parseOwnerIds } from '../../../lib/codeowners'; +import fs from 'fs-extra'; +import camelCase from 'lodash/camelCase'; +import upperFirst from 'lodash/upperFirst'; +import chalk from 'chalk'; +import { paths } from '../../paths'; +import { + addCodeownersEntry, + getCodeownersFilePath, + parseOwnerIds, +} from '../../codeowners'; import { createFactory, CreateContext } from '../types'; +import { Lockfile } from '../../versioning'; +import { addPackageDependency, Task, templatingTask } from '../../tasks'; +import { createPackageVersionProvider } from '../../version'; type Options = { id: string; @@ -63,11 +74,130 @@ export const frontendPlugin = createFactory({ }, }, ], - async create(options: Options, context: CreateContext) { - console.log( - `Creating ${this.name} with options ${JSON.stringify( - options, - )} and context ${JSON.stringify(context)}`, + async create(options: Options, ctx: CreateContext) { + const { id } = options; + + const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; + const extensionName = `${upperFirst(camelCase(id))}Page`; + + const pluginDir = ctx.isMonoRepo + ? paths.resolveTargetRoot('plugins', id) + : paths.resolveTargetRoot(`backstage-plugin-${id}`); + + let lockfile: Lockfile | undefined; + try { + lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); + } catch (error) { + console.warn(`No yarn.lock available, ${error}`); + } + + Task.section('Validating prerequisites'); + const shortPluginDir = pluginDir.replace(`${paths.targetRoot}/`, ''); + await Task.forItem('availability', shortPluginDir, async () => { + if (await fs.pathExists(pluginDir)) { + throw new Error( + `A plugin with the same ID already exists at ${chalk.cyan( + shortPluginDir, + )}. Please try again with a different ID.`, + ); + } + }); + + const tempDir = await Task.forItem('creating', 'temp dir', async () => { + return await ctx.createTemporaryDirectory(`backstage-plugin-${id}`); + }); + + Task.section('Executing plugin template'); + await templatingTask( + paths.resolveOwn('templates/default-plugin'), + tempDir, + { + id, + pluginVar: `${camelCase(id)}Plugin`, + pluginVersion: ctx.defaultVersion, + extensionName, + name, + privatePackage: ctx.private, + npmRegistry: ctx.npmRegistry, + }, + createPackageVersionProvider(lockfile), ); + + Task.section('Installing plugin'); + await Task.forItem('moving', shortPluginDir, async () => { + await fs.move(tempDir, pluginDir).catch(error => { + throw new Error( + `Failed to move plugin from ${tempDir} to ${pluginDir}, ${error.message}`, + ); + }); + }); + + ctx.markAsModified(); + + if (await fs.pathExists(paths.resolveTargetRoot('packages/app'))) { + await Task.forItem('app', 'adding dependency', async () => { + await addPackageDependency( + paths.resolveTargetRoot('packages/app/package.json'), + { + dependencies: { + [name]: `^${ctx.defaultVersion}`, + }, + }, + ); + }); + + await Task.forItem('app', 'adding import', async () => { + const pluginsFilePath = paths.resolveTargetRoot( + 'packages/app/src/App.tsx', + ); + if (!(await fs.pathExists(pluginsFilePath))) { + return; + } + + const content = await fs.readFile(pluginsFilePath, 'utf8'); + const revLines = content.split('\n').reverse(); + + const lastImportIndex = revLines.findIndex(line => + line.match(/ from ("|').*("|')/), + ); + const lastRouteIndex = revLines.findIndex(line => + line.match(/<\/FlatRoutes/), + ); + + if (lastImportIndex !== -1 && lastRouteIndex !== -1) { + const importLine = `import { ${extensionName} } from '${name}';`; + if (!content.includes(importLine)) { + revLines.splice(lastImportIndex, 0, importLine); + } + + const componentLine = `}/>`; + if (!content.includes(componentLine)) { + const [indentation] = + revLines[lastRouteIndex + 1].match(/^\s*/) ?? []; + revLines.splice(lastRouteIndex + 1, 0, indentation + componentLine); + } + + const newContent = revLines.reverse().join('\n'); + await fs.writeFile(pluginsFilePath, newContent, 'utf8'); + } + }); + } + + if (options.codeOwnersPath && options.owner) { + const ownerIds = parseOwnerIds(options.owner); + if (ownerIds && ownerIds.length > 0) { + await addCodeownersEntry( + options.codeOwnersPath, + `/plugins/${id}`, + ownerIds, + ); + } + } + + await Task.forCommand('yarn install', { cwd: pluginDir, optional: true }); + await Task.forCommand('yarn lint --fix', { + cwd: pluginDir, + optional: true, + }); }, }); From 1c0ac6291cdaa278aa58cf00592e6ab7b562e92d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 16:00:54 +0100 Subject: [PATCH 34/95] cli: added create backend plugin factory Signed-off-by: Patrik Oldsberg --- .../src/lib/create/factories/backendPlugin.ts | 164 ++++++++++++++++++ .../cli/src/lib/create/factories/index.ts | 1 + 2 files changed, 165 insertions(+) create mode 100644 packages/cli/src/lib/create/factories/backendPlugin.ts diff --git a/packages/cli/src/lib/create/factories/backendPlugin.ts b/packages/cli/src/lib/create/factories/backendPlugin.ts new file mode 100644 index 0000000000..9cf78900a7 --- /dev/null +++ b/packages/cli/src/lib/create/factories/backendPlugin.ts @@ -0,0 +1,164 @@ +/* + * 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 fs from 'fs-extra'; +import camelCase from 'lodash/camelCase'; +import upperFirst from 'lodash/upperFirst'; +import chalk from 'chalk'; +import { paths } from '../../paths'; +import { + addCodeownersEntry, + getCodeownersFilePath, + parseOwnerIds, +} from '../../codeowners'; +import { createFactory, CreateContext } from '../types'; +import { Lockfile } from '../../versioning'; +import { addPackageDependency, Task, templatingTask } from '../../tasks'; +import { createPackageVersionProvider } from '../../version'; + +type Options = { + id: string; + owner?: string; + codeOwnersPath?: string; +}; + +export const backendPlugin = createFactory({ + name: 'backend-plugin', + description: 'A new backend plugin', + optionsDiscovery: async () => ({ + codeOwnersPath: await getCodeownersFilePath(paths.targetRoot), + }), + optionsPrompts: [ + { + type: 'input', + name: 'id', + message: 'Enter an ID for the plugin [required]', + validate: (value: string) => { + if (!value) { + return 'Please enter an ID for the plugin'; + } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { + return 'Plugin IDs must be lowercase and contain only letters, digits, and dashes.'; + } + return true; + }, + }, + { + type: 'input', + name: 'owner', + message: 'Enter an owner of the plugin to add to CODEOWNERS [optional]', + when: opts => Boolean(opts.codeOwnersPath), + validate: (value: string) => { + if (!value) { + return true; + } + + const ownerIds = parseOwnerIds(value); + if (!ownerIds) { + return 'The owner must be a space separated list of team names (e.g. @org/team-name), usernames (e.g. @username), or the email addresses (e.g. user@example.com).'; + } + + return true; + }, + }, + ], + async create(options: Options, ctx: CreateContext) { + const id = `${options.id}-backend`; + const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; + + const pluginDir = ctx.isMonoRepo + ? paths.resolveTargetRoot('plugins', id) + : paths.resolveTargetRoot(`backstage-plugin-${id}`); + + let lockfile: Lockfile | undefined; + try { + lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); + } catch (error) { + console.warn(`No yarn.lock available, ${error}`); + } + + Task.section('Validating prerequisites'); + const shortPluginDir = pluginDir.replace(`${paths.targetRoot}/`, ''); + await Task.forItem('availability', shortPluginDir, async () => { + if (await fs.pathExists(pluginDir)) { + throw new Error( + `A backend plugin with the same ID already exists at ${chalk.cyan( + shortPluginDir, + )}. Please try again with a different ID.`, + ); + } + }); + + const tempDir = await Task.forItem('creating', 'temp dir', async () => { + return await ctx.createTemporaryDirectory(`backstage-plugin-${id}`); + }); + + Task.section('Executing plugin template'); + await templatingTask( + paths.resolveOwn('templates/default-backend-plugin'), + tempDir, + { + id, + name, + pluginVar: `${camelCase(id)}Plugin`, + pluginVersion: ctx.defaultVersion, + privatePackage: ctx.private, + npmRegistry: ctx.npmRegistry, + }, + createPackageVersionProvider(lockfile), + ); + + Task.section('Installing plugin'); + await Task.forItem('moving', shortPluginDir, async () => { + await fs.move(tempDir, pluginDir).catch(error => { + throw new Error( + `Failed to move plugin from ${tempDir} to ${pluginDir}, ${error.message}`, + ); + }); + }); + + ctx.markAsModified(); + + if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { + await Task.forItem('backend', 'adding dependency', async () => { + await addPackageDependency( + paths.resolveTargetRoot('packages/backend/package.json'), + { + dependencies: { + [name]: `^${ctx.defaultVersion}`, + }, + }, + ); + }); + } + + if (options.codeOwnersPath && options.owner) { + const ownerIds = parseOwnerIds(options.owner); + if (ownerIds && ownerIds.length > 0) { + await addCodeownersEntry( + options.codeOwnersPath, + `/plugins/${id}`, + ownerIds, + ); + } + } + + await Task.forCommand('yarn install', { cwd: pluginDir, optional: true }); + await Task.forCommand('yarn lint --fix', { + cwd: pluginDir, + optional: true, + }); + }, +}); diff --git a/packages/cli/src/lib/create/factories/index.ts b/packages/cli/src/lib/create/factories/index.ts index 4375ba2a45..2e16979e50 100644 --- a/packages/cli/src/lib/create/factories/index.ts +++ b/packages/cli/src/lib/create/factories/index.ts @@ -15,3 +15,4 @@ */ export { frontendPlugin } from './frontendPlugin'; +export { backendPlugin } from './backendPlugin'; From 142cd41b7e351c20f23a6a9f611586b28bcf81ce Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 16:08:32 +0100 Subject: [PATCH 35/95] cli: refactor create factories to use common prompts Signed-off-by: Patrik Oldsberg --- .../src/lib/create/factories/backendPlugin.ts | 35 +---------- .../lib/create/factories/common/prompts.ts | 58 +++++++++++++++++++ .../lib/create/factories/frontendPlugin.ts | 35 +---------- packages/cli/src/lib/create/types.ts | 14 +++-- 4 files changed, 70 insertions(+), 72 deletions(-) create mode 100644 packages/cli/src/lib/create/factories/common/prompts.ts diff --git a/packages/cli/src/lib/create/factories/backendPlugin.ts b/packages/cli/src/lib/create/factories/backendPlugin.ts index 9cf78900a7..d7855d5d68 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.ts @@ -28,6 +28,7 @@ import { createFactory, CreateContext } from '../types'; import { Lockfile } from '../../versioning'; import { addPackageDependency, Task, templatingTask } from '../../tasks'; import { createPackageVersionProvider } from '../../version'; +import { ownerPrompt, pluginIdPrompt } from './common/prompts'; type Options = { id: string; @@ -41,39 +42,7 @@ export const backendPlugin = createFactory({ optionsDiscovery: async () => ({ codeOwnersPath: await getCodeownersFilePath(paths.targetRoot), }), - optionsPrompts: [ - { - type: 'input', - name: 'id', - message: 'Enter an ID for the plugin [required]', - validate: (value: string) => { - if (!value) { - return 'Please enter an ID for the plugin'; - } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { - return 'Plugin IDs must be lowercase and contain only letters, digits, and dashes.'; - } - return true; - }, - }, - { - type: 'input', - name: 'owner', - message: 'Enter an owner of the plugin to add to CODEOWNERS [optional]', - when: opts => Boolean(opts.codeOwnersPath), - validate: (value: string) => { - if (!value) { - return true; - } - - const ownerIds = parseOwnerIds(value); - if (!ownerIds) { - return 'The owner must be a space separated list of team names (e.g. @org/team-name), usernames (e.g. @username), or the email addresses (e.g. user@example.com).'; - } - - return true; - }, - }, - ], + optionsPrompts: [pluginIdPrompt(), ownerPrompt()], async create(options: Options, ctx: CreateContext) { const id = `${options.id}-backend`; const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; diff --git a/packages/cli/src/lib/create/factories/common/prompts.ts b/packages/cli/src/lib/create/factories/common/prompts.ts new file mode 100644 index 0000000000..9c7672ddfb --- /dev/null +++ b/packages/cli/src/lib/create/factories/common/prompts.ts @@ -0,0 +1,58 @@ +/* + * 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 { Prompt } from '../../types'; +import { parseOwnerIds } from '../../../codeowners'; + +export function pluginIdPrompt(): Prompt<{ id: string }> { + return { + type: 'input', + name: 'id', + message: 'Enter the ID of the plugin [required]', + validate: (value: string) => { + if (!value) { + return 'Please enter the ID of the plugin'; + } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { + return 'Plugin IDs must be lowercase and contain only letters, digits, and dashes.'; + } + return true; + }, + }; +} + +export function ownerPrompt(): Prompt<{ + owner?: string; + codeOwnersPath?: string; +}> { + return { + type: 'input', + name: 'owner', + message: 'Enter an owner to add to CODEOWNERS [optional]', + when: opts => Boolean(opts.codeOwnersPath), + validate: (value: string) => { + if (!value) { + return true; + } + + const ownerIds = parseOwnerIds(value); + if (!ownerIds) { + return 'The owner must be a space separated list of team names (e.g. @org/team-name), usernames (e.g. @username), or the email addresses (e.g. user@example.com).'; + } + + return true; + }, + }; +} diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index a85403e8ed..3fe01b7325 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -28,6 +28,7 @@ import { createFactory, CreateContext } from '../types'; import { Lockfile } from '../../versioning'; import { addPackageDependency, Task, templatingTask } from '../../tasks'; import { createPackageVersionProvider } from '../../version'; +import { ownerPrompt, pluginIdPrompt } from './common/prompts'; type Options = { id: string; @@ -41,39 +42,7 @@ export const frontendPlugin = createFactory({ optionsDiscovery: async () => ({ codeOwnersPath: await getCodeownersFilePath(paths.targetRoot), }), - optionsPrompts: [ - { - type: 'input', - name: 'id', - message: 'Enter an ID for the plugin [required]', - validate: (value: string) => { - if (!value) { - return 'Please enter an ID for the plugin'; - } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { - return 'Plugin IDs must be lowercase and contain only letters, digits, and dashes.'; - } - return true; - }, - }, - { - type: 'input', - name: 'owner', - message: 'Enter an owner of the plugin to add to CODEOWNERS [optional]', - when: opts => Boolean(opts.codeOwnersPath), - validate: (value: string) => { - if (!value) { - return true; - } - - const ownerIds = parseOwnerIds(value); - if (!ownerIds) { - return 'The owner must be a space separated list of team names (e.g. @org/team-name), usernames (e.g. @username), or the email addresses (e.g. user@example.com).'; - } - - return true; - }, - }, - ], + optionsPrompts: [pluginIdPrompt(), ownerPrompt()], async create(options: Options, ctx: CreateContext) { const { id } = options; diff --git a/packages/cli/src/lib/create/types.ts b/packages/cli/src/lib/create/types.ts index 6a45067e9b..fd2e684e11 100644 --- a/packages/cli/src/lib/create/types.ts +++ b/packages/cli/src/lib/create/types.ts @@ -37,18 +37,20 @@ export interface CreateContext { export type AnyOptions = Record; -export interface Factory { +export type Prompt = DistinctQuestion & { name: string }; + +export interface Factory { name: string; description: string; - optionsDiscovery?(): Promise>; - optionsPrompts?: ReadonlyArray & { name: string }>; - create(options: Options, context?: CreateContext): Promise; + optionsDiscovery?(): Promise>; + optionsPrompts?: ReadonlyArray>; + create(options: TOptions, context?: CreateContext): Promise; } export type AnyFactory = Factory; -export function createFactory( - config: Factory, +export function createFactory( + config: Factory, ): AnyFactory { return config as AnyFactory; } From 0ebcf168d25d04a7ba39b75929fbca273ae6446d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 16:36:57 +0100 Subject: [PATCH 36/95] cli: common helper for executing plugin package templates Signed-off-by: Patrik Oldsberg --- .../src/lib/create/factories/backendPlugin.ts | 57 +++----------- .../src/lib/create/factories/common/tasks.ts | 76 +++++++++++++++++++ .../lib/create/factories/frontendPlugin.ts | 60 +++------------ 3 files changed, 96 insertions(+), 97 deletions(-) create mode 100644 packages/cli/src/lib/create/factories/common/tasks.ts diff --git a/packages/cli/src/lib/create/factories/backendPlugin.ts b/packages/cli/src/lib/create/factories/backendPlugin.ts index d7855d5d68..68c684f6ab 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.ts @@ -16,8 +16,6 @@ import fs from 'fs-extra'; import camelCase from 'lodash/camelCase'; -import upperFirst from 'lodash/upperFirst'; -import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, @@ -25,10 +23,9 @@ import { parseOwnerIds, } from '../../codeowners'; import { createFactory, CreateContext } from '../types'; -import { Lockfile } from '../../versioning'; -import { addPackageDependency, Task, templatingTask } from '../../tasks'; -import { createPackageVersionProvider } from '../../version'; +import { addPackageDependency, Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; +import { executePluginPackageTemplate } from './common/tasks'; type Options = { id: string; @@ -47,38 +44,14 @@ export const backendPlugin = createFactory({ const id = `${options.id}-backend`; const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; - const pluginDir = ctx.isMonoRepo + const targetDir = ctx.isMonoRepo ? paths.resolveTargetRoot('plugins', id) : paths.resolveTargetRoot(`backstage-plugin-${id}`); - let lockfile: Lockfile | undefined; - try { - lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); - } catch (error) { - console.warn(`No yarn.lock available, ${error}`); - } - - Task.section('Validating prerequisites'); - const shortPluginDir = pluginDir.replace(`${paths.targetRoot}/`, ''); - await Task.forItem('availability', shortPluginDir, async () => { - if (await fs.pathExists(pluginDir)) { - throw new Error( - `A backend plugin with the same ID already exists at ${chalk.cyan( - shortPluginDir, - )}. Please try again with a different ID.`, - ); - } - }); - - const tempDir = await Task.forItem('creating', 'temp dir', async () => { - return await ctx.createTemporaryDirectory(`backstage-plugin-${id}`); - }); - - Task.section('Executing plugin template'); - await templatingTask( - paths.resolveOwn('templates/default-backend-plugin'), - tempDir, - { + await executePluginPackageTemplate(ctx, { + targetDir, + templateName: 'default-backend-plugin', + values: { id, name, pluginVar: `${camelCase(id)}Plugin`, @@ -86,20 +59,8 @@ export const backendPlugin = createFactory({ privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, }, - createPackageVersionProvider(lockfile), - ); - - Task.section('Installing plugin'); - await Task.forItem('moving', shortPluginDir, async () => { - await fs.move(tempDir, pluginDir).catch(error => { - throw new Error( - `Failed to move plugin from ${tempDir} to ${pluginDir}, ${error.message}`, - ); - }); }); - ctx.markAsModified(); - if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { await Task.forItem('backend', 'adding dependency', async () => { await addPackageDependency( @@ -124,9 +85,9 @@ export const backendPlugin = createFactory({ } } - await Task.forCommand('yarn install', { cwd: pluginDir, optional: true }); + await Task.forCommand('yarn install', { cwd: targetDir, optional: true }); await Task.forCommand('yarn lint --fix', { - cwd: pluginDir, + cwd: targetDir, optional: true, }); }, diff --git a/packages/cli/src/lib/create/factories/common/tasks.ts b/packages/cli/src/lib/create/factories/common/tasks.ts new file mode 100644 index 0000000000..b644110a9f --- /dev/null +++ b/packages/cli/src/lib/create/factories/common/tasks.ts @@ -0,0 +1,76 @@ +/* + * 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 fs from 'fs-extra'; +import chalk from 'chalk'; +import { paths } from '../../../paths'; +import { Task, templatingTask } from '../../../tasks'; +import { Lockfile } from '../../../versioning'; +import { createPackageVersionProvider } from '../../../version'; +import { CreateContext } from '../../types'; + +export async function executePluginPackageTemplate( + ctx: CreateContext, + options: { + templateName: string; + targetDir: string; + values: Record; + }, +) { + const { targetDir } = options; + + let lockfile: Lockfile | undefined; + try { + lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); + } catch { + /* ignored */ + } + + Task.section('Checking Prerequisites'); + const shortPluginDir = targetDir.replace(`${paths.targetRoot}/`, ''); + await Task.forItem('availability', shortPluginDir, async () => { + if (await fs.pathExists(targetDir)) { + throw new Error( + `A package with the same plugin ID already exists at ${chalk.cyan( + shortPluginDir, + )}. Please try again with a different ID.`, + ); + } + }); + + const tempDir = await Task.forItem('creating', 'temp dir', async () => { + return await ctx.createTemporaryDirectory('backstage-create'); + }); + + Task.section('Executing Template'); + await templatingTask( + paths.resolveOwn('templates', options.templateName), + tempDir, + options.values, + createPackageVersionProvider(lockfile), + ); + + Task.section('Installing'); + await Task.forItem('moving', shortPluginDir, async () => { + await fs.move(tempDir, targetDir).catch(error => { + throw new Error( + `Failed to move package from ${tempDir} to ${targetDir}, ${error.message}`, + ); + }); + }); + + ctx.markAsModified(); +} diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index 3fe01b7325..bb93bd311c 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -17,7 +17,6 @@ import fs from 'fs-extra'; import camelCase from 'lodash/camelCase'; import upperFirst from 'lodash/upperFirst'; -import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, @@ -25,10 +24,9 @@ import { parseOwnerIds, } from '../../codeowners'; import { createFactory, CreateContext } from '../types'; -import { Lockfile } from '../../versioning'; -import { addPackageDependency, Task, templatingTask } from '../../tasks'; -import { createPackageVersionProvider } from '../../version'; +import { addPackageDependency, Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; +import { executePluginPackageTemplate } from './common/tasks'; type Options = { id: string; @@ -49,60 +47,24 @@ export const frontendPlugin = createFactory({ const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; const extensionName = `${upperFirst(camelCase(id))}Page`; - const pluginDir = ctx.isMonoRepo + const targetDir = ctx.isMonoRepo ? paths.resolveTargetRoot('plugins', id) : paths.resolveTargetRoot(`backstage-plugin-${id}`); - let lockfile: Lockfile | undefined; - try { - lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); - } catch (error) { - console.warn(`No yarn.lock available, ${error}`); - } - - Task.section('Validating prerequisites'); - const shortPluginDir = pluginDir.replace(`${paths.targetRoot}/`, ''); - await Task.forItem('availability', shortPluginDir, async () => { - if (await fs.pathExists(pluginDir)) { - throw new Error( - `A plugin with the same ID already exists at ${chalk.cyan( - shortPluginDir, - )}. Please try again with a different ID.`, - ); - } - }); - - const tempDir = await Task.forItem('creating', 'temp dir', async () => { - return await ctx.createTemporaryDirectory(`backstage-plugin-${id}`); - }); - - Task.section('Executing plugin template'); - await templatingTask( - paths.resolveOwn('templates/default-plugin'), - tempDir, - { + await executePluginPackageTemplate(ctx, { + targetDir, + templateName: 'default-plugin', + values: { id, + name, + extensionName, pluginVar: `${camelCase(id)}Plugin`, pluginVersion: ctx.defaultVersion, - extensionName, - name, privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, }, - createPackageVersionProvider(lockfile), - ); - - Task.section('Installing plugin'); - await Task.forItem('moving', shortPluginDir, async () => { - await fs.move(tempDir, pluginDir).catch(error => { - throw new Error( - `Failed to move plugin from ${tempDir} to ${pluginDir}, ${error.message}`, - ); - }); }); - ctx.markAsModified(); - if (await fs.pathExists(paths.resolveTargetRoot('packages/app'))) { await Task.forItem('app', 'adding dependency', async () => { await addPackageDependency( @@ -163,9 +125,9 @@ export const frontendPlugin = createFactory({ } } - await Task.forCommand('yarn install', { cwd: pluginDir, optional: true }); + await Task.forCommand('yarn install', { cwd: targetDir, optional: true }); await Task.forCommand('yarn lint --fix', { - cwd: pluginDir, + cwd: targetDir, optional: true, }); }, From 367e09e31fd828561f94024bf90355576774a295 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 16:45:56 +0100 Subject: [PATCH 37/95] cli: refactor to simplify codeowners logic Signed-off-by: Patrik Oldsberg --- .../commands/create-plugin/createPlugin.ts | 9 ++---- .../remove-plugin/removePlugin.test.ts | 4 +-- packages/cli/src/lib/codeowners/codeowners.ts | 30 ++++++++++++++----- .../src/lib/create/factories/backendPlugin.ts | 17 ++--------- .../lib/create/factories/frontendPlugin.ts | 17 ++--------- 5 files changed, 33 insertions(+), 44 deletions(-) diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index a5ea565cae..c80129662c 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -263,7 +263,6 @@ export default async (cmd: Command) => { const pluginDir = isMonoRepo ? paths.resolveTargetRoot('plugins', pluginId) : paths.resolveTargetRoot(pluginId); - const ownerIds = parseOwnerIds(answers.owner); const { version: pluginVersion } = isMonoRepo ? await fs.readJson(paths.resolveTargetRoot('lerna.json')) : { version: '0.1.0' }; @@ -318,12 +317,8 @@ export default async (cmd: Command) => { await addPluginExtensionToApp(pluginId, extensionName, name); } - if (ownerIds && ownerIds.length) { - await addCodeownersEntry( - codeownersPath!, - `/plugins/${pluginId}`, - ownerIds, - ); + if (answers.owner) { + await addCodeownersEntry(`/plugins/${pluginId}`, answers.owner); } Task.log(); diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts index 0390320128..8859b5d4cd 100644 --- a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts +++ b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts @@ -177,9 +177,9 @@ describe('removePlugin', () => { fse.readFileSync(mockedCodeownersPath, 'utf8'), ); await addCodeownersEntry( - testFilePath!, path.join('plugins', testPluginName), - ['@thisIsAtestTeam', 'test@gmail.com'], + '@thisIsAtestTeam test@gmail.com', + testFilePath, ); await removePluginFromCodeOwners(testFilePath, testPluginName); expect(testFileContent).toBe(codeOwnersFileContent); diff --git a/packages/cli/src/lib/codeowners/codeowners.ts b/packages/cli/src/lib/codeowners/codeowners.ts index b3fac12109..5734be95b8 100644 --- a/packages/cli/src/lib/codeowners/codeowners.ts +++ b/packages/cli/src/lib/codeowners/codeowners.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import path from 'path'; +import { paths } from '../paths'; const TEAM_ID_RE = /^@[-\w]+\/[-\w]+$/; const USER_ID_RE = /^@[-\w]+$/; @@ -30,14 +31,14 @@ type CodeownersEntry = { export async function getCodeownersFilePath( rootDir: string, ): Promise { - const paths = [ + const possiblePaths = [ path.join(rootDir, '.github', 'CODEOWNERS'), path.join(rootDir, '.gitlab', 'CODEOWNERS'), path.join(rootDir, 'docs', 'CODEOWNERS'), path.join(rootDir, 'CODEOWNERS'), ]; - for (const p of paths) { + for (const p of possiblePaths) { if (await fs.pathExists(p)) { return p; } @@ -70,11 +71,24 @@ export function parseOwnerIds( } export async function addCodeownersEntry( - codeownersFilePath: string, ownedPath: string, - ownerIds: string[], -): Promise { - const allLines = (await fs.readFile(codeownersFilePath, 'utf8')).split('\n'); + ownerStr: string, + codeownersFilePath?: string, +): Promise { + const ownerIds = parseOwnerIds(ownerStr); + if (!ownerIds || ownerIds.length === 0) { + return false; + } + + let filePath = codeownersFilePath; + if (!filePath) { + filePath = await getCodeownersFilePath(paths.targetRoot); + if (!filePath) { + return false; + } + } + + const allLines = (await fs.readFile(filePath, 'utf8')).split('\n'); // Only keep comments from the top of the file const commentLines = []; @@ -117,5 +131,7 @@ export async function addCodeownersEntry( const newLines = [...commentLines, '', ...newDeclarationLines, '']; - await fs.writeFile(codeownersFilePath, newLines.join('\n'), 'utf8'); + await fs.writeFile(filePath, newLines.join('\n'), 'utf8'); + + return true; } diff --git a/packages/cli/src/lib/create/factories/backendPlugin.ts b/packages/cli/src/lib/create/factories/backendPlugin.ts index 68c684f6ab..2fd461c2a9 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.ts @@ -17,11 +17,7 @@ import fs from 'fs-extra'; import camelCase from 'lodash/camelCase'; import { paths } from '../../paths'; -import { - addCodeownersEntry, - getCodeownersFilePath, - parseOwnerIds, -} from '../../codeowners'; +import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; import { createFactory, CreateContext } from '../types'; import { addPackageDependency, Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; @@ -74,15 +70,8 @@ export const backendPlugin = createFactory({ }); } - if (options.codeOwnersPath && options.owner) { - const ownerIds = parseOwnerIds(options.owner); - if (ownerIds && ownerIds.length > 0) { - await addCodeownersEntry( - options.codeOwnersPath, - `/plugins/${id}`, - ownerIds, - ); - } + if (options.owner) { + await addCodeownersEntry(`/plugins/${id}`, options.owner); } await Task.forCommand('yarn install', { cwd: targetDir, optional: true }); diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index bb93bd311c..1d30d0235d 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -18,11 +18,7 @@ import fs from 'fs-extra'; import camelCase from 'lodash/camelCase'; import upperFirst from 'lodash/upperFirst'; import { paths } from '../../paths'; -import { - addCodeownersEntry, - getCodeownersFilePath, - parseOwnerIds, -} from '../../codeowners'; +import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; import { createFactory, CreateContext } from '../types'; import { addPackageDependency, Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; @@ -114,15 +110,8 @@ export const frontendPlugin = createFactory({ }); } - if (options.codeOwnersPath && options.owner) { - const ownerIds = parseOwnerIds(options.owner); - if (ownerIds && ownerIds.length > 0) { - await addCodeownersEntry( - options.codeOwnersPath, - `/plugins/${id}`, - ownerIds, - ); - } + if (options.owner) { + await addCodeownersEntry(`/plugins/${id}`, options.owner); } await Task.forCommand('yarn install', { cwd: targetDir, optional: true }); From 26eb174ce8f9ecd163738cc919aa8e27f51057c1 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Fri, 12 Nov 2021 15:33:22 +0000 Subject: [PATCH 38/95] Skip empty file names during scaffolder Signed-off-by: Tim Jacomb --- .changeset/ninety-spies-prove.md | 5 +++++ .../actions/builtin/fetch/template.test.ts | 17 +++++++++++++++++ .../actions/builtin/fetch/template.ts | 5 +++++ 3 files changed, 27 insertions(+) create mode 100644 .changeset/ninety-spies-prove.md diff --git a/.changeset/ninety-spies-prove.md b/.changeset/ninety-spies-prove.md new file mode 100644 index 0000000000..509aedd66f --- /dev/null +++ b/.changeset/ninety-spies-prove.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Skip empty file names when scaffolding with nunjucks diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 4befec5a81..755b435149 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -144,6 +144,7 @@ describe('fetch:template', () => { name: 'test-project', count: 1234, itemList: ['first', 'second', 'third'], + showDummyFile: false, }, }); @@ -163,6 +164,10 @@ describe('fetch:template', () => { }, '.${{ values.name }}': '${{ values.itemList | dump }}', 'a-binary-file.png': aBinaryFile, + '{% if values.showDummyFile %}dummy-file.txt{% else %}{% endif %}': + 'dummy file', + '${{ "dummy-file2.txt" if values.showDummyFile else "" }}': + 'some dummy file', }, }); @@ -181,6 +186,18 @@ describe('fetch:template', () => { ); }); + it('skips empty filename', async () => { + await expect( + fs.pathExists(`${workspacePath}/target/dummy-file.txt`), + ).resolves.toEqual(false); + }); + + it('skips empty filename syntax #2', async () => { + await expect( + fs.pathExists(`${workspacePath}/target/dummy-file2.txt`), + ).resolves.toEqual(false); + }); + it('copies files with no templating in names or content successfully', async () => { await expect( fs.readFile(`${workspacePath}/target/static.txt`, 'utf-8'), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index bb568c25e6..47bfcccfdf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -241,6 +241,11 @@ export function createFetchTemplateAction(options: { localOutputPath = templater.renderString(localOutputPath, context); } const outputPath = resolvePath(outputDir, localOutputPath); + // variables have been expanded to make an empty file name + // this is due to a conditional like if values.my_condition then file-name.txt else empty string so skip + if (outputDir === outputPath) { + continue; + } if (!renderContents && !extension) { ctx.logger.info( From 033f6abfcebc0b7ae1c276b412fcb306efe2443d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 18:15:03 +0100 Subject: [PATCH 39/95] cli: add tests for frontendPlugin create factory + minor fixes Signed-off-by: Patrik Oldsberg --- .../create/factories/frontendPlugin.test.ts | 232 ++++++++++++++++++ .../lib/create/factories/frontendPlugin.ts | 2 +- packages/cli/src/lib/tasks.ts | 8 +- 3 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 packages/cli/src/lib/create/factories/frontendPlugin.test.ts diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts new file mode 100644 index 0000000000..fe815ce80f --- /dev/null +++ b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts @@ -0,0 +1,232 @@ +/* + * 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 fs from 'fs-extra'; +import mockFs from 'mock-fs'; +import { resolve as resolvePath } from 'path'; +import { WriteStream } from 'tty'; +import { paths } from '../../paths'; +import { Task } from '../../tasks'; +import { FactoryRegistry } from '../FactoryRegistry'; +import { frontendPlugin } from './frontendPlugin'; + +function createMockOutputStream() { + const output = new Array(); + return [ + output, + { + cursorTo: () => {}, + clearLine: () => {}, + moveCursor: () => {}, + write: (msg: string) => + // Clean up colors and whitespace + // eslint-disable-next-line no-control-regex + output.push(msg.replace(/\x1B\[\d\dm/g, '').trim()), + } as unknown as WriteStream & { fd: any }, + ] as const; +} + +const appTsxContent = ` +import { createApp } from '@backstage/app-defaults'; + +const router = ( + + } /> + +) +`; + +describe('frontendPlugin factory', () => { + beforeEach(() => { + jest.spyOn(paths, 'targetRoot', 'get').mockReturnValue('/root'); + jest + .spyOn(paths, 'resolveTargetRoot') + .mockImplementation((...ps) => resolvePath('/root', ...ps)); + }); + + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('should create a frontend plugin', async () => { + mockFs({ + '/root': { + packages: { + app: { + 'package.json': JSON.stringify({}), + src: { + 'App.tsx': appTsxContent, + }, + }, + }, + plugins: mockFs.directory(), + }, + [paths.resolveOwn('templates')]: mockFs.load( + paths.resolveOwn('templates'), + ), + }); + + const options = await FactoryRegistry.populateOptions(frontendPlugin, { + id: 'test', + }); + + let modified = false; + + const [output, mockStream] = createMockOutputStream(); + jest.spyOn(process, 'stderr', 'get').mockReturnValue(mockStream); + jest.spyOn(Task, 'forCommand').mockResolvedValue(); + + await frontendPlugin.create(options, { + private: true, + isMonoRepo: true, + defaultVersion: '1.0.0', + markAsModified: () => { + modified = true; + }, + createTemporaryDirectory: () => fs.mkdtemp('test'), + }); + + expect(modified).toBe(true); + + expect(output).toEqual([ + 'Checking Prerequisites:', + 'availability plugins/test ✔', + 'creating temp dir ✔', + 'Executing Template:', + 'copying .eslintrc.js ✔', + 'templating README.md.hbs ✔', + 'templating package.json.hbs ✔', + 'copying tsconfig.json ✔', + 'templating index.tsx.hbs ✔', + 'templating index.ts.hbs ✔', + 'templating plugin.test.ts.hbs ✔', + 'templating plugin.ts.hbs ✔', + 'templating routes.ts.hbs ✔', + 'copying setupTests.ts ✔', + 'templating ExampleComponent.test.tsx.hbs ✔', + 'templating ExampleComponent.tsx.hbs ✔', + 'copying index.ts ✔', + 'templating ExampleFetchComponent.test.tsx.hbs ✔', + 'templating ExampleFetchComponent.tsx.hbs ✔', + 'copying index.ts ✔', + 'Installing:', + 'moving plugins/test ✔', + 'app adding dependency ✔', + 'app adding import ✔', + ]); + + await expect( + fs.readJson('/root/packages/app/package.json'), + ).resolves.toEqual({ + dependencies: { + 'plugin-test': '^1.0.0', + }, + }); + + await expect(fs.readFile('/root/packages/app/src/App.tsx', 'utf8')).resolves + .toBe(` +import { createApp } from '@backstage/app-defaults'; +import { TestPage } from 'plugin-test'; + +const router = ( + + } /> + } /> + +) +`); + + expect(Task.forCommand).toHaveBeenCalledTimes(2); + expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { + cwd: '/root/plugins/test', + optional: true, + }); + expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { + cwd: '/root/plugins/test', + optional: true, + }); + }); + + it('should create a frontend plugin with more options and codeowners', async () => { + mockFs({ + '/root': { + CODEOWNERS: '', + packages: { + app: { + 'package.json': JSON.stringify({}), + src: { + 'App.tsx': appTsxContent, + }, + }, + }, + plugins: mockFs.directory(), + }, + [paths.resolveOwn('templates')]: mockFs.load( + paths.resolveOwn('templates'), + ), + }); + + const options = await FactoryRegistry.populateOptions(frontendPlugin, { + id: 'test', + owner: '@test-user', + }); + + const [, mockStream] = createMockOutputStream(); + jest.spyOn(process, 'stderr', 'get').mockReturnValue(mockStream); + jest.spyOn(Task, 'forCommand').mockResolvedValue(); + + await frontendPlugin.create(options, { + scope: 'internal', + private: true, + isMonoRepo: true, + defaultVersion: '1.0.0', + markAsModified: () => {}, + createTemporaryDirectory: () => fs.mkdtemp('test'), + }); + + await expect( + fs.readJson('/root/packages/app/package.json'), + ).resolves.toEqual({ + dependencies: { + '@internal/plugin-test': '^1.0.0', + }, + }); + + await expect(fs.readFile('/root/packages/app/src/App.tsx', 'utf8')).resolves + .toBe(` +import { createApp } from '@backstage/app-defaults'; +import { TestPage } from '@internal/plugin-test'; + +const router = ( + + } /> + } /> + +) +`); + + expect(Task.forCommand).toHaveBeenCalledTimes(2); + expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { + cwd: '/root/plugins/test', + optional: true, + }); + expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { + cwd: '/root/plugins/test', + optional: true, + }); + }); +}); diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index 1d30d0235d..50e8dd42a3 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -97,7 +97,7 @@ export const frontendPlugin = createFactory({ revLines.splice(lastImportIndex, 0, importLine); } - const componentLine = `}/>`; + const componentLine = `} />`; if (!content.includes(componentLine)) { const [indentation] = revLines[lastRouteIndex + 1].match(/^\s*/) ?? []; diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 9e67f7f30c..c84eed435e 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -31,16 +31,16 @@ const TASK_NAME_MAX_LENGTH = 14; export class Task { static log(name: string = '') { - process.stdout.write(`${chalk.green(name)}\n`); + process.stderr.write(`${chalk.green(name)}\n`); } static error(message: string = '') { - process.stdout.write(`\n${chalk.red(message)}\n\n`); + process.stderr.write(`\n${chalk.red(message)}\n\n`); } static section(name: string) { const title = chalk.green(`${name}:`); - process.stdout.write(`\n ${title}\n`); + process.stderr.write(`\n ${title}\n`); } static exit(code: number = 0) { @@ -81,7 +81,7 @@ export class Task { } catch (error) { assertError(error); if (error.stderr) { - process.stdout.write(error.stderr as Buffer); + process.stderr.write(error.stderr as Buffer); } if (error.stdout) { process.stdout.write(error.stdout as Buffer); From 1cf00cbe334488b62f4c36f1f59170bea8ab0382 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 18:19:33 +0100 Subject: [PATCH 40/95] cli: added test for backendPlugin create factory Signed-off-by: Patrik Oldsberg --- .../create/factories/backendPlugin.test.ts | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 packages/cli/src/lib/create/factories/backendPlugin.test.ts diff --git a/packages/cli/src/lib/create/factories/backendPlugin.test.ts b/packages/cli/src/lib/create/factories/backendPlugin.test.ts new file mode 100644 index 0000000000..1100b3001e --- /dev/null +++ b/packages/cli/src/lib/create/factories/backendPlugin.test.ts @@ -0,0 +1,130 @@ +/* + * 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 fs from 'fs-extra'; +import mockFs from 'mock-fs'; +import { resolve as resolvePath } from 'path'; +import { WriteStream } from 'tty'; +import { paths } from '../../paths'; +import { Task } from '../../tasks'; +import { FactoryRegistry } from '../FactoryRegistry'; +import { backendPlugin } from './backendPlugin'; + +function createMockOutputStream() { + const output = new Array(); + return [ + output, + { + cursorTo: () => {}, + clearLine: () => {}, + moveCursor: () => {}, + write: (msg: string) => + // Clean up colors and whitespace + // eslint-disable-next-line no-control-regex + output.push(msg.replace(/\x1B\[\d\dm/g, '').trim()), + } as unknown as WriteStream & { fd: any }, + ] as const; +} + +describe('backendPlugin factory', () => { + beforeEach(() => { + jest.spyOn(paths, 'targetRoot', 'get').mockReturnValue('/root'); + jest + .spyOn(paths, 'resolveTargetRoot') + .mockImplementation((...ps) => resolvePath('/root', ...ps)); + }); + + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('should create a backend plugin', async () => { + mockFs({ + '/root': { + packages: { + backend: { + 'package.json': JSON.stringify({}), + }, + }, + plugins: mockFs.directory(), + }, + [paths.resolveOwn('templates')]: mockFs.load( + paths.resolveOwn('templates'), + ), + }); + + const options = await FactoryRegistry.populateOptions(backendPlugin, { + id: 'test', + }); + + let modified = false; + + const [output, mockStream] = createMockOutputStream(); + jest.spyOn(process, 'stderr', 'get').mockReturnValue(mockStream); + jest.spyOn(Task, 'forCommand').mockResolvedValue(); + + await backendPlugin.create(options, { + private: true, + isMonoRepo: true, + defaultVersion: '1.0.0', + markAsModified: () => { + modified = true; + }, + createTemporaryDirectory: () => fs.mkdtemp('test'), + }); + + expect(modified).toBe(true); + + expect(output).toEqual([ + 'Checking Prerequisites:', + 'availability plugins/test-backend ✔', + 'creating temp dir ✔', + 'Executing Template:', + 'copying .eslintrc.js ✔', + 'templating README.md.hbs ✔', + 'templating package.json.hbs ✔', + 'copying tsconfig.json ✔', + 'copying index.ts ✔', + 'templating run.ts.hbs ✔', + 'copying setupTests.ts ✔', + 'copying router.test.ts ✔', + 'copying router.ts ✔', + 'templating standaloneServer.ts.hbs ✔', + 'Installing:', + 'moving plugins/test-backend ✔', + 'backend adding dependency ✔', + ]); + + await expect( + fs.readJson('/root/packages/backend/package.json'), + ).resolves.toEqual({ + dependencies: { + 'plugin-test-backend': '^1.0.0', + }, + }); + + expect(Task.forCommand).toHaveBeenCalledTimes(2); + expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { + cwd: '/root/plugins/test-backend', + optional: true, + }); + expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { + cwd: '/root/plugins/test-backend', + optional: true, + }); + }); +}); From 8a60033962c0e529c81f12c1695225377c59242b Mon Sep 17 00:00:00 2001 From: Zach Falen Date: Fri, 12 Nov 2021 12:53:13 -0700 Subject: [PATCH 41/95] hotfix for Backstage token generation, prefer .token over .idToken Signed-off-by: Zach Falen --- .../src/layout/SignInPage/commonProvider.tsx | 4 +++- plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core-components/src/layout/SignInPage/commonProvider.tsx b/packages/core-components/src/layout/SignInPage/commonProvider.tsx index 103050a2d9..515ae01e4d 100644 --- a/packages/core-components/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/commonProvider.tsx @@ -49,7 +49,9 @@ const Component: ProviderComponent = ({ config, onResult }) => { userId: identity!.id, profile: profile!, getIdToken: () => { - return authApi.getBackstageIdentity().then(i => i!.idToken); + return authApi + .getBackstageIdentity() + .then(i => i!.token ?? i!.idToken); }, signOut: async () => { await authApi.signOut(); diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index a5128711bc..24b9a0f210 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -233,10 +233,12 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { return; } - if (!identity.idToken) { - identity.idToken = await this.options.tokenIssuer.issueToken({ + if (!(identity.token || identity.idToken)) { + identity.token = await this.options.tokenIssuer.issueToken({ claims: { sub: identity.id }, }); + } else if (!identity.token && identity.idToken) { + identity.token = identity.idToken; } } From 892c1d9202f42856e7585d670abb75f1d7059eaf Mon Sep 17 00:00:00 2001 From: Zach Falen Date: Fri, 12 Nov 2021 12:56:33 -0700 Subject: [PATCH 42/95] add changeset Signed-off-by: Zach Falen --- .changeset/ninety-grapes-love.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/ninety-grapes-love.md diff --git a/.changeset/ninety-grapes-love.md b/.changeset/ninety-grapes-love.md new file mode 100644 index 0000000000..9143bddd30 --- /dev/null +++ b/.changeset/ninety-grapes-love.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-auth-backend': patch +--- + +Update OAuthAdapter to create identity.token from identity.idToken if it does not exist, and prevent overwrites to identity.toke. Update login page commonProvider to prefer .token over .idToken From 7d6ab03ebbfc14ec4f1c8d0ebf9fa7957a200457 Mon Sep 17 00:00:00 2001 From: Zach Falen Date: Fri, 12 Nov 2021 13:32:48 -0700 Subject: [PATCH 43/95] update test Signed-off-by: Zach Falen --- plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index 27b629cc07..dfb3a0a79a 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -22,7 +22,7 @@ import { OAuthHandlers } from './types'; const mockResponseData = { providerInfo: { accessToken: 'ACCESS_TOKEN', - idToken: 'ID_TOKEN', + token: 'ID_TOKEN', expiresInSeconds: 10, scope: 'email', }, @@ -216,7 +216,7 @@ describe('OAuthAdapter', () => { ...mockResponseData, backstageIdentity: { id: mockResponseData.backstageIdentity.id, - idToken: 'my-id-token', + token: 'my-id-token', }, }); }); From 8f2a7184386e1fdbb43b3d35d1bf90bbf681fdf4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 18:38:47 +0100 Subject: [PATCH 44/95] cli: added create factory and template for plugin common package Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/create/factories/index.ts | 1 + .../lib/create/factories/pluginCommon.test.ts | 123 ++++++++++++++++++ .../src/lib/create/factories/pluginCommon.ts | 67 ++++++++++ .../.eslintrc.js | 3 + .../README.md.hbs | 5 + .../package.json.hbs | 32 +++++ .../src/index.ts | 16 +++ .../src/setupTests.ts | 1 + .../tsconfig.json | 9 ++ 9 files changed, 257 insertions(+) create mode 100644 packages/cli/src/lib/create/factories/pluginCommon.test.ts create mode 100644 packages/cli/src/lib/create/factories/pluginCommon.ts create mode 100644 packages/cli/templates/default-common-plugin-package/.eslintrc.js create mode 100644 packages/cli/templates/default-common-plugin-package/README.md.hbs create mode 100644 packages/cli/templates/default-common-plugin-package/package.json.hbs create mode 100644 packages/cli/templates/default-common-plugin-package/src/index.ts create mode 100644 packages/cli/templates/default-common-plugin-package/src/setupTests.ts create mode 100644 packages/cli/templates/default-common-plugin-package/tsconfig.json diff --git a/packages/cli/src/lib/create/factories/index.ts b/packages/cli/src/lib/create/factories/index.ts index 2e16979e50..e4df9062f7 100644 --- a/packages/cli/src/lib/create/factories/index.ts +++ b/packages/cli/src/lib/create/factories/index.ts @@ -16,3 +16,4 @@ export { frontendPlugin } from './frontendPlugin'; export { backendPlugin } from './backendPlugin'; +export { pluginCommon } from './pluginCommon'; diff --git a/packages/cli/src/lib/create/factories/pluginCommon.test.ts b/packages/cli/src/lib/create/factories/pluginCommon.test.ts new file mode 100644 index 0000000000..f7251dc443 --- /dev/null +++ b/packages/cli/src/lib/create/factories/pluginCommon.test.ts @@ -0,0 +1,123 @@ +/* + * 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 fs from 'fs-extra'; +import mockFs from 'mock-fs'; +import { resolve as resolvePath } from 'path'; +import { WriteStream } from 'tty'; +import { paths } from '../../paths'; +import { Task } from '../../tasks'; +import { FactoryRegistry } from '../FactoryRegistry'; +import { pluginCommon } from './pluginCommon'; + +function createMockOutputStream() { + const output = new Array(); + return [ + output, + { + cursorTo: () => {}, + clearLine: () => {}, + moveCursor: () => {}, + write: (msg: string) => + // Clean up colors and whitespace + // eslint-disable-next-line no-control-regex + output.push(msg.replace(/\x1B\[\d\dm/g, '').trim()), + } as unknown as WriteStream & { fd: any }, + ] as const; +} + +describe('pluginCommon factory', () => { + beforeEach(() => { + jest.spyOn(paths, 'targetRoot', 'get').mockReturnValue('/root'); + jest + .spyOn(paths, 'resolveTargetRoot') + .mockImplementation((...ps) => resolvePath('/root', ...ps)); + }); + + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('should create a common plugin package', async () => { + mockFs({ + '/root': { + plugins: mockFs.directory(), + }, + [paths.resolveOwn('templates')]: mockFs.load( + paths.resolveOwn('templates'), + ), + }); + + const options = await FactoryRegistry.populateOptions(pluginCommon, { + id: 'test', + }); + + let modified = false; + + const [output, mockStream] = createMockOutputStream(); + jest.spyOn(process, 'stderr', 'get').mockReturnValue(mockStream); + jest.spyOn(Task, 'forCommand').mockResolvedValue(); + + await pluginCommon.create(options, { + private: true, + isMonoRepo: true, + defaultVersion: '1.0.0', + markAsModified: () => { + modified = true; + }, + createTemporaryDirectory: () => fs.mkdtemp('test'), + }); + + expect(modified).toBe(true); + + expect(output).toEqual([ + 'Checking Prerequisites:', + 'availability plugins/test-common ✔', + 'creating temp dir ✔', + 'Executing Template:', + 'copying .eslintrc.js ✔', + 'templating README.md.hbs ✔', + 'templating package.json.hbs ✔', + 'copying tsconfig.json ✔', + 'copying index.ts ✔', + 'copying setupTests.ts ✔', + 'Installing:', + 'moving plugins/test-common ✔', + ]); + + await expect( + fs.readJson('/root/plugins/test-common/package.json'), + ).resolves.toEqual( + expect.objectContaining({ + name: 'plugin-test-common', + description: 'Common functionalities for the test-common plugin', + private: true, + version: '1.0.0', + }), + ); + + expect(Task.forCommand).toHaveBeenCalledTimes(2); + expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { + cwd: '/root/plugins/test-common', + optional: true, + }); + expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { + cwd: '/root/plugins/test-common', + optional: true, + }); + }); +}); diff --git a/packages/cli/src/lib/create/factories/pluginCommon.ts b/packages/cli/src/lib/create/factories/pluginCommon.ts new file mode 100644 index 0000000000..5a6dd30046 --- /dev/null +++ b/packages/cli/src/lib/create/factories/pluginCommon.ts @@ -0,0 +1,67 @@ +/* + * 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 { paths } from '../../paths'; +import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; +import { createFactory, CreateContext } from '../types'; +import { Task } from '../../tasks'; +import { ownerPrompt, pluginIdPrompt } from './common/prompts'; +import { executePluginPackageTemplate } from './common/tasks'; + +type Options = { + id: string; + owner?: string; + codeOwnersPath?: string; +}; + +export const pluginCommon = createFactory({ + name: 'plugin-common', + description: 'A new isomorphic common plugin package', + optionsDiscovery: async () => ({ + codeOwnersPath: await getCodeownersFilePath(paths.targetRoot), + }), + optionsPrompts: [pluginIdPrompt(), ownerPrompt()], + async create(options: Options, ctx: CreateContext) { + const id = `${options.id}-common`; + const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; + + const targetDir = ctx.isMonoRepo + ? paths.resolveTargetRoot('plugins', id) + : paths.resolveTargetRoot(`backstage-plugin-${id}`); + + await executePluginPackageTemplate(ctx, { + targetDir, + templateName: 'default-common-plugin-package', + values: { + id, + name, + privatePackage: ctx.private, + npmRegistry: ctx.npmRegistry, + pluginVersion: ctx.defaultVersion, + }, + }); + + if (options.owner) { + await addCodeownersEntry(`/plugins/${id}`, options.owner); + } + + await Task.forCommand('yarn install', { cwd: targetDir, optional: true }); + await Task.forCommand('yarn lint --fix', { + cwd: targetDir, + optional: true, + }); + }, +}); diff --git a/packages/cli/templates/default-common-plugin-package/.eslintrc.js b/packages/cli/templates/default-common-plugin-package/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/packages/cli/templates/default-common-plugin-package/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/packages/cli/templates/default-common-plugin-package/README.md.hbs b/packages/cli/templates/default-common-plugin-package/README.md.hbs new file mode 100644 index 0000000000..917e18d4b9 --- /dev/null +++ b/packages/cli/templates/default-common-plugin-package/README.md.hbs @@ -0,0 +1,5 @@ +# {{name}} + +Welcome to the common package for the {{id}} plugin! + +_This plugin was created through the Backstage CLI_ diff --git a/packages/cli/templates/default-common-plugin-package/package.json.hbs b/packages/cli/templates/default-common-plugin-package/package.json.hbs new file mode 100644 index 0000000000..c7ba25ac49 --- /dev/null +++ b/packages/cli/templates/default-common-plugin-package/package.json.hbs @@ -0,0 +1,32 @@ +{ + "name": "{{name}}", + "description": "Common functionalities for the {{id}} plugin", + "version": "{{pluginVersion}}", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", +{{#if privatePackage}} "private": {{privatePackage}}, +{{/if}} + "publishConfig": { +{{#if npmRegistry}} "registry": "{{npmRegistry}}", +{{/if}} + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "devDependencies": { + "@backstage/cli": "{{versionQuery '@backstage/cli'}}" + }, + "files": [ + "dist" + ] +} diff --git a/packages/cli/templates/default-common-plugin-package/src/index.ts b/packages/cli/templates/default-common-plugin-package/src/index.ts new file mode 100644 index 0000000000..6ee452b5bb --- /dev/null +++ b/packages/cli/templates/default-common-plugin-package/src/index.ts @@ -0,0 +1,16 @@ +/** + * Common functionalities for the {{id}} plugin. + */ + +/** + * In this package you might for example declare types that are common + * between the frontend and backend plugin packages. + */ +export type CommonType = { + field: string +} + +/** + * Or you might declare some common constants. + */ +export const COMMON_CONSTANT = 1 diff --git a/packages/cli/templates/default-common-plugin-package/src/setupTests.ts b/packages/cli/templates/default-common-plugin-package/src/setupTests.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/cli/templates/default-common-plugin-package/src/setupTests.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cli/templates/default-common-plugin-package/tsconfig.json b/packages/cli/templates/default-common-plugin-package/tsconfig.json new file mode 100644 index 0000000000..5ae9aeb62d --- /dev/null +++ b/packages/cli/templates/default-common-plugin-package/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "dist-types", + "rootDir": "." + } +} From 827fb840b46c59b4322d11e5400a2e6364aadcb8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 18:45:51 +0100 Subject: [PATCH 45/95] cli: fix for create crashing if no scope is specified Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index 946918d77c..5fff07b194 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -86,7 +86,7 @@ export default async (cmd: Command) => { await factory.create(options, { isMonoRepo, defaultVersion, - scope: cmdOpts.scope.replace(/^@/, ''), + scope: cmdOpts.scope?.replace(/^@/, ''), npmRegistry: cmdOpts.npmRegistry, private: Boolean(cmdOpts.private), createTemporaryDirectory, From a9f8363b5d66bc55830ad9f90e1367c74efb5f15 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Nov 2021 18:46:25 +0100 Subject: [PATCH 46/95] cli: format package.json after creation to simplify template Signed-off-by: Patrik Oldsberg --- .../src/lib/create/factories/common/tasks.ts | 8 ++ .../default-backend-plugin/package.json.hbs | 76 ++++++++++--------- .../package.json.hbs | 6 +- .../templates/default-plugin/package.json.hbs | 6 +- 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/packages/cli/src/lib/create/factories/common/tasks.ts b/packages/cli/src/lib/create/factories/common/tasks.ts index b644110a9f..6fe2e77fc7 100644 --- a/packages/cli/src/lib/create/factories/common/tasks.ts +++ b/packages/cli/src/lib/create/factories/common/tasks.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import chalk from 'chalk'; +import { resolve as resolvePath } from 'path'; import { paths } from '../../../paths'; import { Task, templatingTask } from '../../../tasks'; import { Lockfile } from '../../../versioning'; @@ -63,6 +64,13 @@ export async function executePluginPackageTemplate( createPackageVersionProvider(lockfile), ); + // Format package.json if it exists + const targetPkgJsonPath = resolvePath(targetDir, 'package.json'); + if (await fs.pathExists(targetPkgJsonPath)) { + const pkgJson = await fs.readJson(targetPkgJsonPath); + await fs.writeJson(targetPkgJsonPath, pkgJson, { spaces: 2 }); + } + Task.section('Installing'); await Task.forItem('moving', shortPluginDir, async () => { await fs.move(tempDir, targetDir).catch(error => { diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index 5cca7dcb07..37bcd94186 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -4,41 +4,43 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - {{#if privatePackage}} "private": {{privatePackage}}, - {{/if}} +{{#if privatePackage}} + "private": {{privatePackage}}, +{{/if}} "publishConfig": { - {{#if npmRegistry}} "registry": "{{npmRegistry}}", - {{/if}} - "access": "public", - "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" - }, - "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" - }, - "dependencies": { - "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", - "@backstage/config": "{{versionQuery '@backstage/config'}}", - "@types/express": "{{versionQuery '@types/express' '4.17.6'}}", - "express": "{{versionQuery 'express' '4.17.1'}}", - "express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}", - "winston": "{{versionQuery 'winston' '3.2.1'}}", - "cross-fetch": "{{versionQuery 'cross-fetch' '3.0.6'}}", - "yn": "{{versionQuery 'yn' '4.0.0'}}" - }, - "devDependencies": { - "@backstage/cli": "{{versionQuery '@backstage/cli'}}", - "@types/supertest": "{{versionQuery '@types/supertest' '2.0.8'}}", - "supertest": "{{versionQuery 'supertest' '4.0.2'}}", - "msw": "{{versionQuery 'msw' '0.35.0'}}" - }, - "files": [ - "dist" - ] - } +{{#if npmRegistry}} + "registry": "{{npmRegistry}}", +{{/if}} + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", + "@backstage/config": "{{versionQuery '@backstage/config'}}", + "@types/express": "{{versionQuery '@types/express' '4.17.6'}}", + "express": "{{versionQuery 'express' '4.17.1'}}", + "express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}", + "winston": "{{versionQuery 'winston' '3.2.1'}}", + "cross-fetch": "{{versionQuery 'cross-fetch' '3.0.6'}}", + "yn": "{{versionQuery 'yn' '4.0.0'}}" + }, + "devDependencies": { + "@backstage/cli": "{{versionQuery '@backstage/cli'}}", + "@types/supertest": "{{versionQuery '@types/supertest' '2.0.8'}}", + "supertest": "{{versionQuery 'supertest' '4.0.2'}}", + "msw": "{{versionQuery 'msw' '0.35.0'}}" + }, + "files": [ + "dist" + ] +} diff --git a/packages/cli/templates/default-common-plugin-package/package.json.hbs b/packages/cli/templates/default-common-plugin-package/package.json.hbs index c7ba25ac49..efaee496e7 100644 --- a/packages/cli/templates/default-common-plugin-package/package.json.hbs +++ b/packages/cli/templates/default-common-plugin-package/package.json.hbs @@ -5,10 +5,12 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", -{{#if privatePackage}} "private": {{privatePackage}}, +{{#if privatePackage}} + "private": {{privatePackage}}, {{/if}} "publishConfig": { -{{#if npmRegistry}} "registry": "{{npmRegistry}}", +{{#if npmRegistry}} + "registry": "{{npmRegistry}}", {{/if}} "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 41376821ba..624302da93 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -4,10 +4,12 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", -{{#if privatePackage}} "private": {{privatePackage}}, +{{#if privatePackage}} + "private": {{privatePackage}}, {{/if}} "publishConfig": { -{{#if npmRegistry}} "registry": "{{npmRegistry}}", +{{#if npmRegistry}} + "registry": "{{npmRegistry}}", {{/if}} "access": "public", "main": "dist/index.esm.js", From 1c291cb66f2d61467b7a0eeb1c967dc5675456ac Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 10:32:25 +0100 Subject: [PATCH 47/95] package.json: add backstage-create script Signed-off-by: Patrik Oldsberg --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e6d376c46..a7c5e2fbf7 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "lint:all": "lerna run lint --", "lint:type-deps": "node scripts/check-type-dependencies.js", "docker-build": "yarn tsc && yarn workspace example-backend build --build-dependencies && yarn workspace example-backend build-image", - "create-plugin": "backstage-cli create-plugin --scope backstage --no-private", + "backstage-create": "backstage-cli create --scope backstage --no-private", + "create-plugin": "yarn backstage-create --select plugin", "remove-plugin": "backstage-cli remove-plugin", "release": "changeset version && yarn diff --yes && yarn prettier --write '{packages,plugins}/*/{package.json,CHANGELOG.md}' && yarn install", "prettier:check": "prettier --check .", From 8c83ce8084df178556d60752bf3f0f0c50db4473 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 10:38:49 +0100 Subject: [PATCH 48/95] cli: leave initial create logging to the factories Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 3 --- packages/cli/src/lib/create/factories/backendPlugin.test.ts | 2 ++ packages/cli/src/lib/create/factories/backendPlugin.ts | 4 ++++ packages/cli/src/lib/create/factories/frontendPlugin.test.ts | 2 ++ packages/cli/src/lib/create/factories/frontendPlugin.ts | 4 ++++ packages/cli/src/lib/create/factories/pluginCommon.test.ts | 2 ++ packages/cli/src/lib/create/factories/pluginCommon.ts | 4 ++++ 7 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index 5fff07b194..d80daa6ddf 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -80,9 +80,6 @@ export default async (cmd: Command) => { let modified = false; try { - Task.log(); - Task.log(`Creating new ${factory.name}`); - await factory.create(options, { isMonoRepo, defaultVersion, diff --git a/packages/cli/src/lib/create/factories/backendPlugin.test.ts b/packages/cli/src/lib/create/factories/backendPlugin.test.ts index 1100b3001e..cf597572ad 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.test.ts @@ -90,6 +90,8 @@ describe('backendPlugin factory', () => { expect(modified).toBe(true); expect(output).toEqual([ + '', + 'Creating backend plugin plugin-test-backend', 'Checking Prerequisites:', 'availability plugins/test-backend ✔', 'creating temp dir ✔', diff --git a/packages/cli/src/lib/create/factories/backendPlugin.ts b/packages/cli/src/lib/create/factories/backendPlugin.ts index 2fd461c2a9..2bdc62741d 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.ts @@ -15,6 +15,7 @@ */ import fs from 'fs-extra'; +import chalk from 'chalk'; import camelCase from 'lodash/camelCase'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; @@ -40,6 +41,9 @@ export const backendPlugin = createFactory({ const id = `${options.id}-backend`; const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; + Task.log(); + Task.log(`Creating backend plugin ${chalk.cyan(name)}`); + const targetDir = ctx.isMonoRepo ? paths.resolveTargetRoot('plugins', id) : paths.resolveTargetRoot(`backstage-plugin-${id}`); diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts index fe815ce80f..3b24f95e41 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts @@ -103,6 +103,8 @@ describe('frontendPlugin factory', () => { expect(modified).toBe(true); expect(output).toEqual([ + '', + 'Creating backend plugin plugin-test', 'Checking Prerequisites:', 'availability plugins/test ✔', 'creating temp dir ✔', diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index 50e8dd42a3..a6c0d0470d 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -15,6 +15,7 @@ */ import fs from 'fs-extra'; +import chalk from 'chalk'; import camelCase from 'lodash/camelCase'; import upperFirst from 'lodash/upperFirst'; import { paths } from '../../paths'; @@ -43,6 +44,9 @@ export const frontendPlugin = createFactory({ const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; const extensionName = `${upperFirst(camelCase(id))}Page`; + Task.log(); + Task.log(`Creating backend plugin ${chalk.cyan(name)}`); + const targetDir = ctx.isMonoRepo ? paths.resolveTargetRoot('plugins', id) : paths.resolveTargetRoot(`backstage-plugin-${id}`); diff --git a/packages/cli/src/lib/create/factories/pluginCommon.test.ts b/packages/cli/src/lib/create/factories/pluginCommon.test.ts index f7251dc443..6e797f4cca 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.test.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.test.ts @@ -85,6 +85,8 @@ describe('pluginCommon factory', () => { expect(modified).toBe(true); expect(output).toEqual([ + '', + 'Creating backend plugin plugin-test-common', 'Checking Prerequisites:', 'availability plugins/test-common ✔', 'creating temp dir ✔', diff --git a/packages/cli/src/lib/create/factories/pluginCommon.ts b/packages/cli/src/lib/create/factories/pluginCommon.ts index 5a6dd30046..97b1eccec7 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; import { createFactory, CreateContext } from '../types'; @@ -38,6 +39,9 @@ export const pluginCommon = createFactory({ const id = `${options.id}-common`; const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; + Task.log(); + Task.log(`Creating backend plugin ${chalk.cyan(name)}`); + const targetDir = ctx.isMonoRepo ? paths.resolveTargetRoot('plugins', id) : paths.resolveTargetRoot(`backstage-plugin-${id}`); From 7b8f19492edfea021cc4681c392e3e43f79d5b74 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 12:30:04 +0100 Subject: [PATCH 49/95] cli: add test for plugin package template execution + fix Signed-off-by: Patrik Oldsberg --- .../lib/create/factories/common/tasks.test.ts | 117 ++++++++++++++++++ .../src/lib/create/factories/common/tasks.ts | 8 +- .../lib/create/factories/common/testUtils.ts | 68 ++++++++++ 3 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 packages/cli/src/lib/create/factories/common/tasks.test.ts create mode 100644 packages/cli/src/lib/create/factories/common/testUtils.ts diff --git a/packages/cli/src/lib/create/factories/common/tasks.test.ts b/packages/cli/src/lib/create/factories/common/tasks.test.ts new file mode 100644 index 0000000000..b3a751ba5b --- /dev/null +++ b/packages/cli/src/lib/create/factories/common/tasks.test.ts @@ -0,0 +1,117 @@ +/* + * 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 fs from 'fs-extra'; +import mockFs from 'mock-fs'; +import { createMockOutputStream, mockPaths } from './testUtils'; +import { CreateContext } from '../../types'; +import { executePluginPackageTemplate } from './tasks'; + +mockPaths({ + ownDir: '/own', + targetRoot: '/root', +}); + +describe('executePluginPackageTemplate', () => { + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('should execute template', async () => { + mockFs({ + '/root': { + 'yarn.lock': ` +some-package@^1.1.0: + version "1.5.0" +`, + }, + '/own': { + templates: { + 'test-template': { + 'package.json.hbs': ` +{ + "name": "my-{{id}}-plugin", + {{#if makePrivate}} + "private": true, + {{/if}} + "description": "testing", + "dependencies": { + "some-package": "{{ versionQuery 'some-package' '1.3.0' }}", + "other-package": "{{ versionQuery 'other-package' '2.3.0' }}" + } +} +`, + subdir: { + 'templated.txt.hbs': 'Hello {{id}}!', + 'not-templated.txt': 'Hello {{id}}!', + }, + }, + }, + }, + }); + + const [output, mockStream] = createMockOutputStream(); + jest.spyOn(process, 'stderr', 'get').mockReturnValue(mockStream); + + let modified = false; + await executePluginPackageTemplate( + { + createTemporaryDirectory: (name: string) => fs.mkdtemp(name), + markAsModified: () => { + modified = true; + }, + } as CreateContext, + { + templateName: 'test-template', + targetDir: '/target', + values: { + id: 'testing', + makePrivate: true, + }, + }, + ); + + expect(modified).toBe(true); + expect(output).toEqual([ + 'Checking Prerequisites:', + 'availability /target ✔', + 'creating temp dir ✔', + 'Executing Template:', + 'templating package.json.hbs ✔', + 'copying not-templated.txt ✔', + 'templating templated.txt.hbs ✔', + 'Installing:', + 'moving /target ✔', + ]); + await expect(fs.readFile('/target/package.json', 'utf8')).resolves.toBe(`{ + "name": "my-testing-plugin", + "private": true, + "description": "testing", + "dependencies": { + "some-package": "^1.1.0", + "other-package": "^2.3.0" + } +} +`); + await expect( + fs.readFile('/target/subdir/templated.txt', 'utf8'), + ).resolves.toBe('Hello testing!'); + await expect( + fs.readFile('/target/subdir/not-templated.txt', 'utf8'), + ).resolves.toBe('Hello {{id}}!'); + }); +}); diff --git a/packages/cli/src/lib/create/factories/common/tasks.ts b/packages/cli/src/lib/create/factories/common/tasks.ts index 6fe2e77fc7..fd3bd4d930 100644 --- a/packages/cli/src/lib/create/factories/common/tasks.ts +++ b/packages/cli/src/lib/create/factories/common/tasks.ts @@ -65,10 +65,10 @@ export async function executePluginPackageTemplate( ); // Format package.json if it exists - const targetPkgJsonPath = resolvePath(targetDir, 'package.json'); - if (await fs.pathExists(targetPkgJsonPath)) { - const pkgJson = await fs.readJson(targetPkgJsonPath); - await fs.writeJson(targetPkgJsonPath, pkgJson, { spaces: 2 }); + const pkgJsonPath = resolvePath(tempDir, 'package.json'); + if (await fs.pathExists(pkgJsonPath)) { + const pkgJson = await fs.readJson(pkgJsonPath); + await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 }); } Task.section('Installing'); diff --git a/packages/cli/src/lib/create/factories/common/testUtils.ts b/packages/cli/src/lib/create/factories/common/testUtils.ts new file mode 100644 index 0000000000..651409451e --- /dev/null +++ b/packages/cli/src/lib/create/factories/common/testUtils.ts @@ -0,0 +1,68 @@ +/* + * 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 { WriteStream } from 'tty'; +import { resolve as resolvePath } from 'path'; +import { paths } from '../../../paths'; + +export function mockPaths(options: { + ownDir?: string; + ownRoot?: string; + targetDir?: string; + targetRoot?: string; +}): void { + const { ownDir, ownRoot, targetDir, targetRoot } = options; + if (ownDir) { + paths.ownDir = ownDir; + jest + .spyOn(paths, 'resolveOwn') + .mockImplementation((...ps) => resolvePath(ownDir, ...ps)); + } + if (ownRoot) { + jest.spyOn(paths, 'ownRoot', 'get').mockReturnValue(ownRoot); + jest + .spyOn(paths, 'resolveOwnRoot') + .mockImplementation((...ps) => resolvePath(ownRoot, ...ps)); + } + if (targetDir) { + paths.targetDir = targetDir; + jest + .spyOn(paths, 'resolveTarget') + .mockImplementation((...ps) => resolvePath(targetDir, ...ps)); + } + if (targetRoot) { + jest.spyOn(paths, 'targetRoot', 'get').mockReturnValue(targetRoot); + jest + .spyOn(paths, 'resolveTargetRoot') + .mockImplementation((...ps) => resolvePath(targetRoot, ...ps)); + } +} + +export function createMockOutputStream() { + const output = new Array(); + return [ + output, + { + cursorTo: () => {}, + clearLine: () => {}, + moveCursor: () => {}, + write: (msg: string) => + // Clean up colors and whitespace + // eslint-disable-next-line no-control-regex + output.push(msg.replace(/\x1B\[\d\dm/g, '').trim()), + } as unknown as WriteStream & { fd: any }, + ] as const; +} From 823a097b3b5a87205eefc8c086cf13e0ae0aa8c6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 12:36:47 +0100 Subject: [PATCH 50/95] cli: use common testing utils for create factory tests Signed-off-by: Patrik Oldsberg --- .../create/factories/backendPlugin.test.ts | 26 +++------------- .../create/factories/frontendPlugin.test.ts | 26 +++------------- .../lib/create/factories/pluginCommon.test.ts | 31 +++---------------- 3 files changed, 12 insertions(+), 71 deletions(-) diff --git a/packages/cli/src/lib/create/factories/backendPlugin.test.ts b/packages/cli/src/lib/create/factories/backendPlugin.test.ts index cf597572ad..409b7f476a 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.test.ts @@ -16,35 +16,17 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; -import { resolve as resolvePath } from 'path'; -import { WriteStream } from 'tty'; import { paths } from '../../paths'; import { Task } from '../../tasks'; import { FactoryRegistry } from '../FactoryRegistry'; +import { createMockOutputStream, mockPaths } from './common/testUtils'; import { backendPlugin } from './backendPlugin'; -function createMockOutputStream() { - const output = new Array(); - return [ - output, - { - cursorTo: () => {}, - clearLine: () => {}, - moveCursor: () => {}, - write: (msg: string) => - // Clean up colors and whitespace - // eslint-disable-next-line no-control-regex - output.push(msg.replace(/\x1B\[\d\dm/g, '').trim()), - } as unknown as WriteStream & { fd: any }, - ] as const; -} - describe('backendPlugin factory', () => { beforeEach(() => { - jest.spyOn(paths, 'targetRoot', 'get').mockReturnValue('/root'); - jest - .spyOn(paths, 'resolveTargetRoot') - .mockImplementation((...ps) => resolvePath('/root', ...ps)); + mockPaths({ + targetRoot: '/root', + }); }); afterEach(() => { diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts index 3b24f95e41..3e0f94f6ad 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts @@ -16,29 +16,12 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; -import { resolve as resolvePath } from 'path'; -import { WriteStream } from 'tty'; import { paths } from '../../paths'; import { Task } from '../../tasks'; import { FactoryRegistry } from '../FactoryRegistry'; +import { createMockOutputStream, mockPaths } from './common/testUtils'; import { frontendPlugin } from './frontendPlugin'; -function createMockOutputStream() { - const output = new Array(); - return [ - output, - { - cursorTo: () => {}, - clearLine: () => {}, - moveCursor: () => {}, - write: (msg: string) => - // Clean up colors and whitespace - // eslint-disable-next-line no-control-regex - output.push(msg.replace(/\x1B\[\d\dm/g, '').trim()), - } as unknown as WriteStream & { fd: any }, - ] as const; -} - const appTsxContent = ` import { createApp } from '@backstage/app-defaults'; @@ -51,10 +34,9 @@ const router = ( describe('frontendPlugin factory', () => { beforeEach(() => { - jest.spyOn(paths, 'targetRoot', 'get').mockReturnValue('/root'); - jest - .spyOn(paths, 'resolveTargetRoot') - .mockImplementation((...ps) => resolvePath('/root', ...ps)); + mockPaths({ + targetRoot: '/root', + }); }); afterEach(() => { diff --git a/packages/cli/src/lib/create/factories/pluginCommon.test.ts b/packages/cli/src/lib/create/factories/pluginCommon.test.ts index 6e797f4cca..73edc5159b 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.test.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.test.ts @@ -16,40 +16,17 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; -import { resolve as resolvePath } from 'path'; -import { WriteStream } from 'tty'; import { paths } from '../../paths'; import { Task } from '../../tasks'; import { FactoryRegistry } from '../FactoryRegistry'; +import { createMockOutputStream, mockPaths } from './common/testUtils'; import { pluginCommon } from './pluginCommon'; -function createMockOutputStream() { - const output = new Array(); - return [ - output, - { - cursorTo: () => {}, - clearLine: () => {}, - moveCursor: () => {}, - write: (msg: string) => - // Clean up colors and whitespace - // eslint-disable-next-line no-control-regex - output.push(msg.replace(/\x1B\[\d\dm/g, '').trim()), - } as unknown as WriteStream & { fd: any }, - ] as const; -} - describe('pluginCommon factory', () => { beforeEach(() => { - jest.spyOn(paths, 'targetRoot', 'get').mockReturnValue('/root'); - jest - .spyOn(paths, 'resolveTargetRoot') - .mockImplementation((...ps) => resolvePath('/root', ...ps)); - }); - - afterEach(() => { - mockFs.restore(); - jest.resetAllMocks(); + mockPaths({ + targetRoot: '/root', + }); }); it('should create a common plugin package', async () => { From 9bbc2da04ee00711983284cf4955c05769623ca4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 13:18:07 +0100 Subject: [PATCH 51/95] docs/local-dev: add cli create command docs Signed-off-by: Patrik Oldsberg --- docs/local-dev/cli-commands.md | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/local-dev/cli-commands.md b/docs/local-dev/cli-commands.md index 0b14a2e136..18fdef892a 100644 --- a/docs/local-dev/cli-commands.md +++ b/docs/local-dev/cli-commands.md @@ -41,6 +41,7 @@ lint Lint a package test Run tests, forwarding args to Jest, defaulting to watch mode clean Delete cache directories +create Open up an interactive guide to creating new things in your app create-plugin Creates a new plugin in the current repository remove-plugin Removes plugin in the current repository @@ -277,6 +278,44 @@ Options: -h, --help display help for command ``` +## create + +Scope: `root` + +The `create` command opens up an interactive guide for you to create new things +in your app. If you do not pass in any options it is completely interactive, but +it is possible to pre-select what you want to create using the `--select` flag, +and provide options using `--options`, for example: + +```bash +backstage-cli create --select plugin --option id=foo +``` + +This command is typically added as script in the root `package.json` to be +executed with `yarn backstage-create`, using options that are appropriate for +the organization that owns the app repo. For example you may have it set up like +this: + +```json +{ + "scripts": { + "backstage-create": "backstage-cli create --scope internal --no-private --npm-registry https://acme.org/npm" + } +} +``` + +```text +Usage: backstage-cli create [options] + +Options: + --select Select the thing you want to be creating upfront + --option = Pre-fill options for the creation process (default: []) + --scope The scope to use for new packages + --npm-registry The package registry to use for new packages + --no-private Do not mark new packages as private + -h, --help display help for command +``` + ## create-plugin Scope: `root` From 16d06f6ac3ec8fd1553c4b847b96e692d9dc8125 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 13:20:48 +0100 Subject: [PATCH 52/95] changesets: add changeset for CLI create command Signed-off-by: Patrik Oldsberg --- .changeset/tidy-beans-reflect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tidy-beans-reflect.md diff --git a/.changeset/tidy-beans-reflect.md b/.changeset/tidy-beans-reflect.md new file mode 100644 index 0000000000..20796075f0 --- /dev/null +++ b/.changeset/tidy-beans-reflect.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Introduces new `backstage-cli create` command to replace `create-plugin` and make space for creating a wider array of things. The create command also adds a new template for creating isomorphic common plugin packages. From 6a46eb2693223fce4f08bb3b707397a0bd7c2a82 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 15:01:50 +0100 Subject: [PATCH 53/95] cli: refactor inquirer prompt message transforms for clarity Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/create/FactoryRegistry.ts | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/lib/create/FactoryRegistry.ts b/packages/cli/src/lib/create/FactoryRegistry.ts index 1b25f5950a..9072d46c4c 100644 --- a/packages/cli/src/lib/create/FactoryRegistry.ts +++ b/packages/cli/src/lib/create/FactoryRegistry.ts @@ -16,10 +16,39 @@ import chalk from 'chalk'; import inquirer from 'inquirer'; -import { AnyFactory } from './types'; +import { AnyFactory, Prompt } from './types'; import * as factories from './factories'; import partition from 'lodash/partition'; +function applyPromptMessageTransforms( + prompt: Prompt, + transforms: { + message: (msg: string) => string; + error: (msg: string) => string; + }, +): Prompt { + return { + ...prompt, + message: + prompt.message && + (async answers => { + if (typeof prompt.message === 'function') { + return transforms.message(await prompt.message(answers)); + } + return transforms.message(await prompt.message!); + }), + validate: + prompt.validate && + (async (...args) => { + const result = await prompt.validate!(...args); + if (typeof result === 'string') { + return transforms.error(result); + } + return result; + }), + }; +} + export class FactoryRegistry { private static factoryMap = new Map( Object.values(factories).map(factory => [factory.name, factory]), @@ -82,19 +111,12 @@ export class FactoryRegistry { } currentOptions = await inquirer.prompt( - needsAnswers.map(option => ({ - ...option, - message: option.message && chalk.blue(option.message), - validate: - option.validate && - (async (...args) => { - const result = await option.validate!(...args); - if (typeof result === 'string') { - return chalk.red(result); - } - return result; - }), - })), + needsAnswers.map(option => + applyPromptMessageTransforms(option, { + message: chalk.blue, + error: chalk.red, + }), + ), currentOptions, ); } From 069934c62760d3bc00c1713ba183ee084c5386fc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 15:02:08 +0100 Subject: [PATCH 54/95] cli: some more docs for create types Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/create/types.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/cli/src/lib/create/types.ts b/packages/cli/src/lib/create/types.ts index fd2e684e11..5a2460d0df 100644 --- a/packages/cli/src/lib/create/types.ts +++ b/packages/cli/src/lib/create/types.ts @@ -40,10 +40,31 @@ export type AnyOptions = Record; export type Prompt = DistinctQuestion & { name: string }; export interface Factory { + /** + * The name used for this factory. + */ name: string; + + /** + * A description that describes what this factory creates to the user. + */ description: string; + + /** + * An optional options discovery step that is run + * before the prompts to potentially fill in some of the options. + */ optionsDiscovery?(): Promise>; + + /** + * Inquirer prompts that will be filled in either interactively or + * through command line arguments. + */ optionsPrompts?: ReadonlyArray>; + + /** + * The main method of the factory that handles creation. + */ create(options: TOptions, context?: CreateContext): Promise; } From bd3ae04de9e990ed9bad2c8051cb491721f18d94 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 15:26:23 +0100 Subject: [PATCH 55/95] cli: fixes for common package factory Signed-off-by: Patrik Oldsberg --- .../src/lib/create/factories/pluginCommon.test.ts | 9 +++++++-- .../cli/src/lib/create/factories/pluginCommon.ts | 13 ++++++++----- .../src/{index.ts => index.ts.hbs} | 3 +++ 3 files changed, 18 insertions(+), 7 deletions(-) rename packages/cli/templates/default-common-plugin-package/src/{index.ts => index.ts.hbs} (90%) diff --git a/packages/cli/src/lib/create/factories/pluginCommon.test.ts b/packages/cli/src/lib/create/factories/pluginCommon.test.ts index 73edc5159b..e329e2588f 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.test.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.test.ts @@ -29,6 +29,11 @@ describe('pluginCommon factory', () => { }); }); + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + it('should create a common plugin package', async () => { mockFs({ '/root': { @@ -72,7 +77,7 @@ describe('pluginCommon factory', () => { 'templating README.md.hbs ✔', 'templating package.json.hbs ✔', 'copying tsconfig.json ✔', - 'copying index.ts ✔', + 'templating index.ts.hbs ✔', 'copying setupTests.ts ✔', 'Installing:', 'moving plugins/test-common ✔', @@ -83,7 +88,7 @@ describe('pluginCommon factory', () => { ).resolves.toEqual( expect.objectContaining({ name: 'plugin-test-common', - description: 'Common functionalities for the test-common plugin', + description: 'Common functionalities for the test plugin', private: true, version: '1.0.0', }), diff --git a/packages/cli/src/lib/create/factories/pluginCommon.ts b/packages/cli/src/lib/create/factories/pluginCommon.ts index 97b1eccec7..af42d74b0b 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.ts @@ -36,15 +36,18 @@ export const pluginCommon = createFactory({ }), optionsPrompts: [pluginIdPrompt(), ownerPrompt()], async create(options: Options, ctx: CreateContext) { - const id = `${options.id}-common`; - const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; + const { id } = options; + const suffix = `${id}-common`; + const name = ctx.scope + ? `@${ctx.scope}/plugin-${suffix}` + : `plugin-${suffix}`; Task.log(); Task.log(`Creating backend plugin ${chalk.cyan(name)}`); const targetDir = ctx.isMonoRepo - ? paths.resolveTargetRoot('plugins', id) - : paths.resolveTargetRoot(`backstage-plugin-${id}`); + ? paths.resolveTargetRoot('plugins', suffix) + : paths.resolveTargetRoot(`backstage-plugin-${suffix}`); await executePluginPackageTemplate(ctx, { targetDir, @@ -59,7 +62,7 @@ export const pluginCommon = createFactory({ }); if (options.owner) { - await addCodeownersEntry(`/plugins/${id}`, options.owner); + await addCodeownersEntry(`/plugins/${suffix}`, options.owner); } await Task.forCommand('yarn install', { cwd: targetDir, optional: true }); diff --git a/packages/cli/templates/default-common-plugin-package/src/index.ts b/packages/cli/templates/default-common-plugin-package/src/index.ts.hbs similarity index 90% rename from packages/cli/templates/default-common-plugin-package/src/index.ts rename to packages/cli/templates/default-common-plugin-package/src/index.ts.hbs index 6ee452b5bb..2e1150d74e 100644 --- a/packages/cli/templates/default-common-plugin-package/src/index.ts +++ b/packages/cli/templates/default-common-plugin-package/src/index.ts.hbs @@ -1,5 +1,8 @@ +/***/ /** * Common functionalities for the {{id}} plugin. + * + * @packageDocumentation */ /** From 103d9e2f53e97f93ef742715a88be15f9941bb3d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 15:29:33 +0100 Subject: [PATCH 56/95] cli: fix create monorepo check Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/create/create.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/create/create.ts b/packages/cli/src/commands/create/create.ts index d80daa6ddf..64067e1b7e 100644 --- a/packages/cli/src/commands/create/create.ts +++ b/packages/cli/src/commands/create/create.ts @@ -51,10 +51,20 @@ export default async (cmd: Command) => { providedOptions, ); - const rootPackageJson = await fs.readJson( - paths.resolveTargetRoot('package.json'), - ); - const isMonoRepo = Boolean(rootPackageJson.workspaces); + let isMonoRepo = false; + try { + const rootPackageJson = await fs.readJson( + paths.resolveTargetRoot('package.json'), + ); + if (rootPackageJson.workspaces) { + isMonoRepo = true; + } + } catch (error) { + assertError(error); + if (error.code !== 'ENOENT') { + throw error; + } + } let defaultVersion = '0.1.0'; try { From edaaf2dccb8a20f26a26c20b912bc90234350f74 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 15:32:20 +0100 Subject: [PATCH 57/95] cli: fix non-monorepo package naming for create Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/create/factories/backendPlugin.test.ts | 4 ++-- packages/cli/src/lib/create/factories/backendPlugin.ts | 4 +++- .../cli/src/lib/create/factories/frontendPlugin.test.ts | 6 +++--- packages/cli/src/lib/create/factories/frontendPlugin.ts | 4 +++- packages/cli/src/lib/create/factories/pluginCommon.test.ts | 4 ++-- packages/cli/src/lib/create/factories/pluginCommon.ts | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/lib/create/factories/backendPlugin.test.ts b/packages/cli/src/lib/create/factories/backendPlugin.test.ts index 409b7f476a..5fe52c3732 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.test.ts @@ -73,7 +73,7 @@ describe('backendPlugin factory', () => { expect(output).toEqual([ '', - 'Creating backend plugin plugin-test-backend', + 'Creating backend plugin backstage-plugin-test-backend', 'Checking Prerequisites:', 'availability plugins/test-backend ✔', 'creating temp dir ✔', @@ -97,7 +97,7 @@ describe('backendPlugin factory', () => { fs.readJson('/root/packages/backend/package.json'), ).resolves.toEqual({ dependencies: { - 'plugin-test-backend': '^1.0.0', + 'backstage-plugin-test-backend': '^1.0.0', }, }); diff --git a/packages/cli/src/lib/create/factories/backendPlugin.ts b/packages/cli/src/lib/create/factories/backendPlugin.ts index 2bdc62741d..4d03a4da7b 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.ts @@ -39,7 +39,9 @@ export const backendPlugin = createFactory({ optionsPrompts: [pluginIdPrompt(), ownerPrompt()], async create(options: Options, ctx: CreateContext) { const id = `${options.id}-backend`; - const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; + const name = ctx.scope + ? `@${ctx.scope}/plugin-${id}` + : `backstage-plugin-${id}`; Task.log(); Task.log(`Creating backend plugin ${chalk.cyan(name)}`); diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts index 3e0f94f6ad..a882a130ea 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts @@ -86,7 +86,7 @@ describe('frontendPlugin factory', () => { expect(output).toEqual([ '', - 'Creating backend plugin plugin-test', + 'Creating backend plugin backstage-plugin-test', 'Checking Prerequisites:', 'availability plugins/test ✔', 'creating temp dir ✔', @@ -117,14 +117,14 @@ describe('frontendPlugin factory', () => { fs.readJson('/root/packages/app/package.json'), ).resolves.toEqual({ dependencies: { - 'plugin-test': '^1.0.0', + 'backstage-plugin-test': '^1.0.0', }, }); await expect(fs.readFile('/root/packages/app/src/App.tsx', 'utf8')).resolves .toBe(` import { createApp } from '@backstage/app-defaults'; -import { TestPage } from 'plugin-test'; +import { TestPage } from 'backstage-plugin-test'; const router = ( diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index a6c0d0470d..3862ae4883 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -41,7 +41,9 @@ export const frontendPlugin = createFactory({ async create(options: Options, ctx: CreateContext) { const { id } = options; - const name = ctx.scope ? `@${ctx.scope}/plugin-${id}` : `plugin-${id}`; + const name = ctx.scope + ? `@${ctx.scope}/plugin-${id}` + : `backstage-plugin-${id}`; const extensionName = `${upperFirst(camelCase(id))}Page`; Task.log(); diff --git a/packages/cli/src/lib/create/factories/pluginCommon.test.ts b/packages/cli/src/lib/create/factories/pluginCommon.test.ts index e329e2588f..a39bb3877d 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.test.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.test.ts @@ -68,7 +68,7 @@ describe('pluginCommon factory', () => { expect(output).toEqual([ '', - 'Creating backend plugin plugin-test-common', + 'Creating backend plugin backstage-plugin-test-common', 'Checking Prerequisites:', 'availability plugins/test-common ✔', 'creating temp dir ✔', @@ -87,7 +87,7 @@ describe('pluginCommon factory', () => { fs.readJson('/root/plugins/test-common/package.json'), ).resolves.toEqual( expect.objectContaining({ - name: 'plugin-test-common', + name: 'backstage-plugin-test-common', description: 'Common functionalities for the test plugin', private: true, version: '1.0.0', diff --git a/packages/cli/src/lib/create/factories/pluginCommon.ts b/packages/cli/src/lib/create/factories/pluginCommon.ts index af42d74b0b..1bcca2fb6a 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.ts @@ -40,7 +40,7 @@ export const pluginCommon = createFactory({ const suffix = `${id}-common`; const name = ctx.scope ? `@${ctx.scope}/plugin-${suffix}` - : `plugin-${suffix}`; + : `backstage-plugin-${suffix}`; Task.log(); Task.log(`Creating backend plugin ${chalk.cyan(name)}`); From 6dcfe227a29006a43763bf2c1d5e345a4bfc0cb3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Nov 2021 16:32:20 +0100 Subject: [PATCH 58/95] cli: add create factory for scaffolder modules Signed-off-by: Patrik Oldsberg --- .changeset/shiny-starfishes-float.md | 5 + .../cli/src/lib/create/factories/index.ts | 1 + .../create/factories/scaffolderModule.test.ts | 110 ++++++++++++++++++ .../lib/create/factories/scaffolderModule.ts | 96 +++++++++++++++ packages/cli/src/lib/version.ts | 2 + .../templates/scaffolder-module/.eslintrc.js | 3 + .../templates/scaffolder-module/README.md.hbs | 5 + .../scaffolder-module/package.json.hbs | 37 ++++++ .../src/actions/example/example.test.ts | 50 ++++++++ .../src/actions/example/example.ts | 57 +++++++++ .../src/actions/example/index.ts | 1 + .../scaffolder-module/src/actions/index.ts | 1 + .../scaffolder-module/src/index.ts.hbs | 8 ++ .../templates/scaffolder-module/tsconfig.json | 9 ++ 14 files changed, 385 insertions(+) create mode 100644 .changeset/shiny-starfishes-float.md create mode 100644 packages/cli/src/lib/create/factories/scaffolderModule.test.ts create mode 100644 packages/cli/src/lib/create/factories/scaffolderModule.ts create mode 100644 packages/cli/templates/scaffolder-module/.eslintrc.js create mode 100644 packages/cli/templates/scaffolder-module/README.md.hbs create mode 100644 packages/cli/templates/scaffolder-module/package.json.hbs create mode 100644 packages/cli/templates/scaffolder-module/src/actions/example/example.test.ts create mode 100644 packages/cli/templates/scaffolder-module/src/actions/example/example.ts create mode 100644 packages/cli/templates/scaffolder-module/src/actions/example/index.ts create mode 100644 packages/cli/templates/scaffolder-module/src/actions/index.ts create mode 100644 packages/cli/templates/scaffolder-module/src/index.ts.hbs create mode 100644 packages/cli/templates/scaffolder-module/tsconfig.json diff --git a/.changeset/shiny-starfishes-float.md b/.changeset/shiny-starfishes-float.md new file mode 100644 index 0000000000..0673791375 --- /dev/null +++ b/.changeset/shiny-starfishes-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added a scaffolder backend module template for the `create` command. diff --git a/packages/cli/src/lib/create/factories/index.ts b/packages/cli/src/lib/create/factories/index.ts index e4df9062f7..0764d33e2c 100644 --- a/packages/cli/src/lib/create/factories/index.ts +++ b/packages/cli/src/lib/create/factories/index.ts @@ -17,3 +17,4 @@ export { frontendPlugin } from './frontendPlugin'; export { backendPlugin } from './backendPlugin'; export { pluginCommon } from './pluginCommon'; +export { scaffolderModule } from './scaffolderModule'; diff --git a/packages/cli/src/lib/create/factories/scaffolderModule.test.ts b/packages/cli/src/lib/create/factories/scaffolderModule.test.ts new file mode 100644 index 0000000000..dde711b750 --- /dev/null +++ b/packages/cli/src/lib/create/factories/scaffolderModule.test.ts @@ -0,0 +1,110 @@ +/* + * 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 fs from 'fs-extra'; +import mockFs from 'mock-fs'; +import { paths } from '../../paths'; +import { Task } from '../../tasks'; +import { FactoryRegistry } from '../FactoryRegistry'; +import { createMockOutputStream, mockPaths } from './common/testUtils'; +import { scaffolderModule } from './scaffolderModule'; + +describe('scaffolderModule factory', () => { + beforeEach(() => { + mockPaths({ + targetRoot: '/root', + }); + }); + + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('should create a scaffolder backend module package', async () => { + mockFs({ + '/root': { + plugins: mockFs.directory(), + }, + [paths.resolveOwn('templates')]: mockFs.load( + paths.resolveOwn('templates'), + ), + }); + + const options = await FactoryRegistry.populateOptions(scaffolderModule, { + id: 'test', + }); + + let modified = false; + + const [output, mockStream] = createMockOutputStream(); + jest.spyOn(process, 'stderr', 'get').mockReturnValue(mockStream); + jest.spyOn(Task, 'forCommand').mockResolvedValue(); + + await scaffolderModule.create(options, { + private: true, + isMonoRepo: true, + defaultVersion: '1.0.0', + markAsModified: () => { + modified = true; + }, + createTemporaryDirectory: (name: string) => fs.mkdtemp(name), + }); + + expect(modified).toBe(true); + + expect(output).toEqual([ + '', + 'Creating module backstage-plugin-scaffolder-backend-module-test', + 'Checking Prerequisites:', + 'availability plugins/scaffolder-backend-module-test ✔', + 'creating temp dir ✔', + 'Executing Template:', + 'copying .eslintrc.js ✔', + 'templating README.md.hbs ✔', + 'templating package.json.hbs ✔', + 'copying tsconfig.json ✔', + 'templating index.ts.hbs ✔', + 'copying index.ts ✔', + 'copying example.test.ts ✔', + 'copying example.ts ✔', + 'copying index.ts ✔', + 'Installing:', + 'moving plugins/scaffolder-backend-module-test ✔', + ]); + + await expect( + fs.readJson('/root/plugins/scaffolder-backend-module-test/package.json'), + ).resolves.toEqual( + expect.objectContaining({ + name: 'backstage-plugin-scaffolder-backend-module-test', + description: 'The test module for @backstage/plugin-scaffolder-backend', + private: true, + version: '1.0.0', + }), + ); + + expect(Task.forCommand).toHaveBeenCalledTimes(2); + expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { + cwd: '/root/plugins/scaffolder-backend-module-test', + optional: true, + }); + expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { + cwd: '/root/plugins/scaffolder-backend-module-test', + optional: true, + }); + }); +}); diff --git a/packages/cli/src/lib/create/factories/scaffolderModule.ts b/packages/cli/src/lib/create/factories/scaffolderModule.ts new file mode 100644 index 0000000000..b89f0fc691 --- /dev/null +++ b/packages/cli/src/lib/create/factories/scaffolderModule.ts @@ -0,0 +1,96 @@ +/* + * 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 chalk from 'chalk'; +import { paths } from '../../paths'; +import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; +import { createFactory, CreateContext } from '../types'; +import { Task } from '../../tasks'; +import { ownerPrompt } from './common/prompts'; +import { executePluginPackageTemplate } from './common/tasks'; + +type Options = { + id: string; + owner?: string; + codeOwnersPath?: string; +}; + +export const scaffolderModule = createFactory({ + name: 'scaffolder-module', + description: + 'An module exporting custom actions for @backstage/plugin-scaffolder-backend', + optionsDiscovery: async () => ({ + codeOwnersPath: await getCodeownersFilePath(paths.targetRoot), + }), + optionsPrompts: [ + { + type: 'input', + name: 'id', + message: 'Enter the name of the module [required]', + validate: (value: string) => { + if (!value) { + return 'Please enter the name of the module'; + } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) { + return 'Module names must be lowercase and contain only letters, digits, and dashes.'; + } + return true; + }, + }, + ownerPrompt(), + ], + async create(options: Options, ctx: CreateContext) { + const { id } = options; + const slug = `scaffolder-backend-module-${id}`; + + let name = `backstage-plugin-${slug}`; + if (ctx.scope) { + if (ctx.scope === 'backstage') { + name = `@backstage/plugin-${slug}`; + } else { + name = `@${ctx.scope}/backstage-plugin-${slug}`; + } + } + + Task.log(); + Task.log(`Creating module ${chalk.cyan(name)}`); + + const targetDir = ctx.isMonoRepo + ? paths.resolveTargetRoot('plugins', slug) + : paths.resolveTargetRoot(`backstage-plugin-${slug}`); + + await executePluginPackageTemplate(ctx, { + targetDir, + templateName: 'scaffolder-module', + values: { + id, + name, + privatePackage: ctx.private, + npmRegistry: ctx.npmRegistry, + pluginVersion: ctx.defaultVersion, + }, + }); + + if (options.owner) { + await addCodeownersEntry(`/plugins/${slug}`, options.owner); + } + + await Task.forCommand('yarn install', { cwd: targetDir, optional: true }); + await Task.forCommand('yarn lint --fix', { + cwd: targetDir, + optional: true, + }); + }, +}); diff --git a/packages/cli/src/lib/version.ts b/packages/cli/src/lib/version.ts index c69072c64d..3b512b22c3 100644 --- a/packages/cli/src/lib/version.ts +++ b/packages/cli/src/lib/version.ts @@ -42,6 +42,7 @@ import { version as corePluginApi } from '@backstage/core-plugin-api/package.jso import { version as devUtils } from '@backstage/dev-utils/package.json'; import { version as testUtils } from '@backstage/test-utils/package.json'; import { version as theme } from '@backstage/theme/package.json'; +import { version as scaffolderBackend } from '@backstage/plugin-scaffolder-backend/package.json'; export const packageVersions: Record = { '@backstage/backend-common': backendCommon, @@ -53,6 +54,7 @@ export const packageVersions: Record = { '@backstage/dev-utils': devUtils, '@backstage/test-utils': testUtils, '@backstage/theme': theme, + '@backstage/plugin-scaffolder-backend': scaffolderBackend, }; export function findVersion() { diff --git a/packages/cli/templates/scaffolder-module/.eslintrc.js b/packages/cli/templates/scaffolder-module/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/packages/cli/templates/scaffolder-module/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/packages/cli/templates/scaffolder-module/README.md.hbs b/packages/cli/templates/scaffolder-module/README.md.hbs new file mode 100644 index 0000000000..8ee653a4ac --- /dev/null +++ b/packages/cli/templates/scaffolder-module/README.md.hbs @@ -0,0 +1,5 @@ +# {{name}} + +The {{id}} module for [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend). + +_This plugin was created through the Backstage CLI_ diff --git a/packages/cli/templates/scaffolder-module/package.json.hbs b/packages/cli/templates/scaffolder-module/package.json.hbs new file mode 100644 index 0000000000..de6ffd9e3a --- /dev/null +++ b/packages/cli/templates/scaffolder-module/package.json.hbs @@ -0,0 +1,37 @@ +{ + "name": "{{name}}", + "description": "The {{id}} module for @backstage/plugin-scaffolder-backend", + "version": "{{pluginVersion}}", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", +{{#if privatePackage}} + "private": {{privatePackage}}, +{{/if}} + "publishConfig": { +{{#if npmRegistry}} + "registry": "{{npmRegistry}}", +{{/if}} + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli build --output cjs,types", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/plugin-scaffolder-backend": "{{versionQuery '@backstage/plugin-scaffolder-backend'}}" + }, + "devDependencies": { + "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", + "@backstage/cli": "{{versionQuery '@backstage/cli'}}" + }, + "files": [ + "dist" + ] +} diff --git a/packages/cli/templates/scaffolder-module/src/actions/example/example.test.ts b/packages/cli/templates/scaffolder-module/src/actions/example/example.test.ts new file mode 100644 index 0000000000..e427b2c603 --- /dev/null +++ b/packages/cli/templates/scaffolder-module/src/actions/example/example.test.ts @@ -0,0 +1,50 @@ +/* + * 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 { PassThrough } from 'stream'; +import { createAcmeExampleAction } from './example'; +import { getVoidLogger } from '@backstage/backend-common'; + +describe('acme:example', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should call action', async () => { + const action = createAcmeExampleAction(); + + const logger = getVoidLogger(); + jest.spyOn(logger, 'info'); + + await action.handler({ + input: { + myParameter: 'test', + }, + workspacePath: '/tmp', + logger, + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory() { + // Usage of mock-fs is recommended for testing of filesystem operations + throw new Error('Not implemented'); + }, + }); + + expect(logger.info).toHaveBeenCalledWith( + 'Running example template with parameters: test', + ); + }); +}); diff --git a/packages/cli/templates/scaffolder-module/src/actions/example/example.ts b/packages/cli/templates/scaffolder-module/src/actions/example/example.ts new file mode 100644 index 0000000000..c20a4bdf25 --- /dev/null +++ b/packages/cli/templates/scaffolder-module/src/actions/example/example.ts @@ -0,0 +1,57 @@ +/* + * 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 { createTemplateAction } from '@backstage/plugin-scaffolder-backend'; + +/** + * Creates an `acme:example` Scaffolder action. + * + * @remarks + * + * See {@link https://example.com} for more information. + * + * @public + */ +export function createAcmeExampleAction() { + // For more information on how to define custom actions, see + // https://backstage.io/docs/features/software-templates/writing-custom-actions + return createTemplateAction<{ + myParameter: string; + }>({ + id: 'acme:example', + description: 'Runs Yeoman on an installed Yeoman generator', + schema: { + input: { + type: 'object', + required: ['myParameter'], + properties: { + myParameter: { + title: 'An example parameter', + description: 'This is the schema for our example parameter', + type: 'string', + }, + }, + }, + }, + async handler(ctx) { + ctx.logger.info( + `Running example template with parameters: ${ctx.input.myParameter}`, + ); + + await new Promise(resolve => setTimeout(resolve, 1000)); + }, + }); +} diff --git a/packages/cli/templates/scaffolder-module/src/actions/example/index.ts b/packages/cli/templates/scaffolder-module/src/actions/example/index.ts new file mode 100644 index 0000000000..e81099f333 --- /dev/null +++ b/packages/cli/templates/scaffolder-module/src/actions/example/index.ts @@ -0,0 +1 @@ +export { createAcmeExampleAction } from './example'; diff --git a/packages/cli/templates/scaffolder-module/src/actions/index.ts b/packages/cli/templates/scaffolder-module/src/actions/index.ts new file mode 100644 index 0000000000..ab6642ebb0 --- /dev/null +++ b/packages/cli/templates/scaffolder-module/src/actions/index.ts @@ -0,0 +1 @@ +export * from './example'; diff --git a/packages/cli/templates/scaffolder-module/src/index.ts.hbs b/packages/cli/templates/scaffolder-module/src/index.ts.hbs new file mode 100644 index 0000000000..3690e43b8e --- /dev/null +++ b/packages/cli/templates/scaffolder-module/src/index.ts.hbs @@ -0,0 +1,8 @@ +/***/ +/** + * The {{id}} module for @backstage/plugin-scaffolder-backend. + * + * @packageDocumentation + */ + +export * from './actions'; diff --git a/packages/cli/templates/scaffolder-module/tsconfig.json b/packages/cli/templates/scaffolder-module/tsconfig.json new file mode 100644 index 0000000000..5ae9aeb62d --- /dev/null +++ b/packages/cli/templates/scaffolder-module/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@backstage/cli/config/tsconfig.json", + "include": ["src"], + "exclude": ["node_modules"], + "compilerOptions": { + "outDir": "dist-types", + "rootDir": "." + } +} From b5eec9e04410493ceb3548286ce1c1bc9230f532 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Nov 2021 04:13:26 +0000 Subject: [PATCH 59/95] build(deps-dev): bump eslint-plugin-cypress from 2.11.3 to 2.12.1 Bumps [eslint-plugin-cypress](https://github.com/cypress-io/eslint-plugin-cypress) from 2.11.3 to 2.12.1. - [Release notes](https://github.com/cypress-io/eslint-plugin-cypress/releases) - [Commits](https://github.com/cypress-io/eslint-plugin-cypress/compare/v2.11.3...v2.12.1) --- updated-dependencies: - dependency-name: eslint-plugin-cypress dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e18f6fedb2..7f5e8b6d78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13688,9 +13688,9 @@ eslint-module-utils@^2.1.1, eslint-module-utils@^2.6.0: pkg-dir "^2.0.0" eslint-plugin-cypress@^2.10.3: - version "2.11.3" - resolved "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.3.tgz#54ee4067aa8192aa62810cd35080eb577e191ab7" - integrity sha512-hOoAid+XNFtpvOzZSNWP5LDrQBEJwbZwjib4XJ1KcRYKjeVj0mAmPmucG4Egli4j/aruv+Ow/acacoloWWCl9Q== + version "2.12.1" + resolved "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz#9aeee700708ca8c058e00cdafe215199918c2632" + integrity sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA== dependencies: globals "^11.12.0" From 34a604b34ce70c3ac21fce7ec0174be3f9b061e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Nov 2021 04:15:32 +0000 Subject: [PATCH 60/95] build(deps): bump js-base64 from 3.6.1 to 3.7.2 Bumps [js-base64](https://github.com/dankogai/js-base64) from 3.6.1 to 3.7.2. - [Release notes](https://github.com/dankogai/js-base64/releases) - [Commits](https://github.com/dankogai/js-base64/compare/3.6.1...3.7.2) --- updated-dependencies: - dependency-name: js-base64 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e18f6fedb2..b18057da72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18083,9 +18083,9 @@ joycon@^2.2.5: integrity sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ== js-base64@^3.6.0: - version "3.6.1" - resolved "https://registry.npmjs.org/js-base64/-/js-base64-3.6.1.tgz#555aae398b74694b4037af1f8a5a6209d170efbe" - integrity sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ== + version "3.7.2" + resolved "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz#816d11d81a8aff241603d19ce5761e13e41d7745" + integrity sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ== js-cookie@^2.2.1: version "2.2.1" From 872a547dd6c3abeeb1eda41573fdee1023dc9e5f Mon Sep 17 00:00:00 2001 From: Terrence Benade Date: Mon, 15 Nov 2021 16:57:43 +1100 Subject: [PATCH 61/95] small grammatical error Signed-off-by: Terrence Benade --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index c95e10ad4e..ea4ae352dd 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -117,7 +117,7 @@ through the proxy. Learn more about [the different components](overview/what-is-backstage.md) that make up Backstage. -### Why can't I dynamically install plugins without modifications the app? +### Why can't I dynamically install plugins without modifications to the app? This decision is part of the core architecture and development flow of Backstage. Plugins have a lot of freedom in what they provide and how they are From b565119196897ad86fcf67d1899b96ce87514399 Mon Sep 17 00:00:00 2001 From: Dede Hamzah Date: Mon, 15 Nov 2021 14:19:28 +0700 Subject: [PATCH 62/95] update api report docs Signed-off-by: Dede Hamzah --- plugins/user-settings/api-report.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/user-settings/api-report.md b/plugins/user-settings/api-report.md index 653e85a541..a801f2e807 100644 --- a/plugins/user-settings/api-report.md +++ b/plugins/user-settings/api-report.md @@ -37,10 +37,11 @@ export const ProviderSettingsItem: ({ // @public (undocumented) export const Router: ({ providerSettings }: Props) => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "SettingsProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "Settings" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Settings: () => JSX.Element; +export const Settings: (props: SettingsProps) => JSX.Element; // Warning: (ae-missing-release-tag) "UserSettingsAppearanceCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // From 8809b6c0ddeb0d4cde5fc571786e4876abded8be Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Mon, 15 Nov 2021 09:56:47 +0000 Subject: [PATCH 63/95] update versions of json-schema Signed-off-by: Brian Fletcher --- .changeset/empty-dots-attend.md | 8 ++++++++ packages/catalog-model/package.json | 2 +- packages/cli/package.json | 2 +- packages/config-loader/package.json | 2 +- plugins/scaffolder/package.json | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .changeset/empty-dots-attend.md diff --git a/.changeset/empty-dots-attend.md b/.changeset/empty-dots-attend.md new file mode 100644 index 0000000000..3903dfa22b --- /dev/null +++ b/.changeset/empty-dots-attend.md @@ -0,0 +1,8 @@ +--- +'@backstage/catalog-model': patch +'@backstage/cli': patch +'@backstage/config-loader': patch +'@backstage/plugin-scaffolder': patch +--- + +Update the json-schema dependency version. diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index b64e536578..17c89ed824 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -36,7 +36,7 @@ "@types/json-schema": "^7.0.5", "@types/yup": "^0.29.13", "ajv": "^7.0.3", - "json-schema": "^0.3.0", + "json-schema": "^0.4.0", "lodash": "^4.17.21", "uuid": "^8.0.0", "yup": "^0.32.9" diff --git a/packages/cli/package.json b/packages/cli/package.json index 546a1597e1..7253007b8f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -81,7 +81,7 @@ "inquirer": "^7.0.4", "jest": "^26.0.1", "jest-css-modules": "^2.1.0", - "json-schema": "^0.3.0", + "json-schema": "^0.4.0", "jest-transform-yaml": "^0.1.1", "lodash": "^4.17.21", "mini-css-extract-plugin": "^2.4.2", diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 2e25d91856..76dcec35c6 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -38,7 +38,7 @@ "ajv": "^7.0.3", "chokidar": "^3.5.2", "fs-extra": "9.1.0", - "json-schema": "^0.3.0", + "json-schema": "^0.4.0", "json-schema-merge-allof": "^0.8.1", "json-schema-traverse": "^1.0.0", "typescript-json-schema": "^0.51.0", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 1d8bc4a0ea..594a8e6423 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -52,7 +52,7 @@ "git-url-parse": "^11.6.0", "humanize-duration": "^3.25.1", "immer": "^9.0.1", - "json-schema": "^0.3.0", + "json-schema": "^0.4.0", "lodash": "^4.17.21", "luxon": "^2.0.2", "qs": "^6.9.4", From a08022254b3ea835df565431ec4dc4d5d46b8353 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Nov 2021 14:59:23 +0100 Subject: [PATCH 64/95] Add missing yarn.lock changes and reports Signed-off-by: Johan Haals --- plugins/catalog-backend/api-report.md | 1 + yarn.lock | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 143e31bc21..6660912dda 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -1290,6 +1290,7 @@ export class NextCatalogBuilder { locationService: LocationService; router: Router; }>; + getDefaultProcessors(): CatalogProcessor[]; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen replaceEntityPolicies(policies: EntityPolicy[]): NextCatalogBuilder; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen diff --git a/yarn.lock b/yarn.lock index 6898854595..37c1bd3329 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18305,10 +18305,10 @@ json-schema@0.2.3: resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= -json-schema@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.3.0.tgz#90a9c5054bd065422c00241851ce8d59475b701b" - integrity sha512-TYfxx36xfl52Rf1LU9HyWSLGPdYLL+SQ8/E/0yVyKG8wCCDaSrhPap0vEdlsZWRaS6tnKKLPGiEJGiREVC8kxQ== +json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" @@ -20635,6 +20635,11 @@ mock-fs@^5.1.0: resolved "https://registry.npmjs.org/mock-fs/-/mock-fs-5.1.0.tgz#a9aebd4e6d74a626f84b86eae8a372bd061754e8" integrity sha512-wXdQ2nIk81TYIGLphUnbXl8akQpjb9ItfZefMcTxZcoe+djMkd5POU8fQdSEErxVAeT4CgDHWveYquys4H6Cmw== +mock-fs@^5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/mock-fs/-/mock-fs-5.1.2.tgz#6fa486e06d00f8793a8d2228de980eff93ce6db7" + integrity sha512-YkjQkdLulFrz0vD4BfNQdQRVmgycXTV7ykuHMlyv+C8WCHazpkiQRDthwa02kSyo8wKnY9wRptHfQLgmf0eR+A== + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" From 0b60a051c9512e7586579f1051a1233164300567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Can=20Bilgi=C3=A7?= Date: Mon, 15 Nov 2021 17:15:39 +0300 Subject: [PATCH 65/95] changeset name and type change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Can Bilgiç --- .changeset/chatty-months-report.md | 6 ------ .changeset/techdocs-chatty-months-report.md | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 .changeset/chatty-months-report.md create mode 100644 .changeset/techdocs-chatty-months-report.md diff --git a/.changeset/chatty-months-report.md b/.changeset/chatty-months-report.md deleted file mode 100644 index a0925be239..0000000000 --- a/.changeset/chatty-months-report.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/techdocs-common': minor -'@backstage/plugin-techdocs': minor ---- - -OpenStack Swift Migration Support added diff --git a/.changeset/techdocs-chatty-months-report.md b/.changeset/techdocs-chatty-months-report.md new file mode 100644 index 0000000000..fbc4faaf0c --- /dev/null +++ b/.changeset/techdocs-chatty-months-report.md @@ -0,0 +1,6 @@ +--- +'@backstage/techdocs-common': patch +'@backstage/plugin-techdocs': patch +--- + +OpenStack Swift Migration Support added From b6a4bacdc4f35e1d5071f61224811213cb26a2af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 15 Nov 2021 16:14:48 +0100 Subject: [PATCH 66/95] core-plugin-api: added ErrorApi* prefix to Error and ErrorContext types Signed-off-by: Patrik Oldsberg --- .changeset/yellow-deers-act.md | 5 +++ packages/core-app-api/api-report.md | 7 ++-- packages/core-plugin-api/api-report.md | 32 +++++++++++-------- .../src/apis/definitions/ErrorApi.ts | 23 ++++++++++--- 4 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 .changeset/yellow-deers-act.md diff --git a/.changeset/yellow-deers-act.md b/.changeset/yellow-deers-act.md new file mode 100644 index 0000000000..a9008ea41e --- /dev/null +++ b/.changeset/yellow-deers-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': patch +--- + +Deprecated the `Error` and `ErrorContext` types, replacing them with identical `ErrorApiError` and `ErrorApiErrorContext` types. diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index d98b5ef5f8..26438ba798 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -28,8 +28,9 @@ import { bitbucketAuthApiRef } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { ConfigReader } from '@backstage/config'; import { DiscoveryApi } from '@backstage/core-plugin-api'; -import { Error as Error_2 } from '@backstage/core-plugin-api'; import { ErrorApi } from '@backstage/core-plugin-api'; +import { ErrorApiError } from '@backstage/core-plugin-api'; +import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; import { ErrorContext } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FeatureFlag } from '@backstage/core-plugin-api'; @@ -327,8 +328,8 @@ export class ErrorAlerter implements ErrorApi { constructor(alertApi: AlertApi, errorApi: ErrorApi); // (undocumented) error$(): Observable<{ - error: Error_2; - context?: ErrorContext | undefined; + error: ErrorApiError; + context?: ErrorApiErrorContext | undefined; }>; // (undocumented) post(error: Error, context?: ErrorContext): void; diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 2765b02bca..87cbf1dd5f 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -404,23 +404,31 @@ export interface ElementCollection { }): ElementCollection; } -// @public -type Error_2 = { - name: string; - message: string; - stack?: string; -}; +// @public @deprecated (undocumented) +type Error_2 = ErrorApiError; export { Error_2 as Error }; // @public export type ErrorApi = { - post(error: Error_2, context?: ErrorContext): void; + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; error$(): Observable_2<{ - error: Error_2; - context?: ErrorContext; + error: ErrorApiError; + context?: ErrorApiErrorContext; }>; }; +// @public +export type ErrorApiError = { + name: string; + message: string; + stack?: string; +}; + +// @public +export type ErrorApiErrorContext = { + hidden?: boolean; +}; + // @public export const errorApiRef: ApiRef; @@ -431,10 +439,8 @@ export type ErrorBoundaryFallbackProps = { resetError: () => void; }; -// @public -export type ErrorContext = { - hidden?: boolean; -}; +// @public @deprecated (undocumented) +export type ErrorContext = ErrorApiErrorContext; // @public export type Extension = { diff --git a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts index 820660ac52..45ee0901c8 100644 --- a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts @@ -23,22 +23,34 @@ import { Observable } from '@backstage/types'; * * @public */ -export type Error = { +export type ErrorApiError = { name: string; message: string; stack?: string; }; +/** + * @public + * @deprecated Use ErrorApiError instead + */ +export type Error = ErrorApiError; + /** * Provides additional information about an error that was posted to the application. * * @public */ -export type ErrorContext = { +export type ErrorApiErrorContext = { // If set to true, this error should not be displayed to the user. Defaults to false. hidden?: boolean; }; +/** + * @public + * @deprecated Use ErrorApiErrorContext instead + */ +export type ErrorContext = ErrorApiErrorContext; + /** * The error API is used to report errors to the app, and display them to the user. * @@ -62,12 +74,15 @@ export type ErrorApi = { /** * Post an error for handling by the application. */ - post(error: Error, context?: ErrorContext): void; + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; /** * Observe errors posted by other parts of the application. */ - error$(): Observable<{ error: Error; context?: ErrorContext }>; + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }>; }; /** From 0b1de527326325aedbf0488c454a922cc03e8b01 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 15 Nov 2021 16:21:02 +0100 Subject: [PATCH 67/95] core-app-api,test-utils: migrated to using new ErrorApi* names Signed-off-by: Patrik Oldsberg --- .changeset/nine-bananas-mate.md | 6 ++++++ docs/api/utility-apis.md | 2 +- packages/core-app-api/api-report.md | 9 ++++----- .../implementations/ErrorApi/ErrorAlerter.ts | 9 +++++++-- .../ErrorApi/ErrorApiForwarder.ts | 12 ++++++++---- .../ErrorApi/UnhandledErrorForwarder.ts | 10 +++++++--- packages/test-utils/api-report.md | 13 +++++++------ .../testUtils/apis/ErrorApi/MockErrorApi.ts | 19 +++++++++++++------ 8 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 .changeset/nine-bananas-mate.md diff --git a/.changeset/nine-bananas-mate.md b/.changeset/nine-bananas-mate.md new file mode 100644 index 0000000000..e119eb722f --- /dev/null +++ b/.changeset/nine-bananas-mate.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-app-api': patch +'@backstage/test-utils': patch +--- + +Migrated to using new `ErrorApiError` and `ErrorApiErrorContext` names. diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 1b364bcdae..133653df44 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -199,7 +199,7 @@ export a class that `implements` the target API, for example: ```ts export class IgnoringErrorApi implements ErrorApi { - post(error: Error, context?: ErrorContext) { + post(error: ErrorApiError, context?: ErrorApiErrorContext) { // ignore error } } diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 26438ba798..2c390bf132 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -31,7 +31,6 @@ import { DiscoveryApi } from '@backstage/core-plugin-api'; import { ErrorApi } from '@backstage/core-plugin-api'; import { ErrorApiError } from '@backstage/core-plugin-api'; import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; -import { ErrorContext } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FeatureFlag } from '@backstage/core-plugin-api'; import { FeatureFlagsApi } from '@backstage/core-plugin-api'; @@ -332,7 +331,7 @@ export class ErrorAlerter implements ErrorApi { context?: ErrorApiErrorContext | undefined; }>; // (undocumented) - post(error: Error, context?: ErrorContext): void; + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; } // @public @@ -340,10 +339,10 @@ export class ErrorApiForwarder implements ErrorApi { // (undocumented) error$(): Observable<{ error: Error; - context?: ErrorContext; + context?: ErrorApiErrorContext; }>; // (undocumented) - post(error: Error, context?: ErrorContext): void; + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; } // @public @@ -604,7 +603,7 @@ export type SignInResult = { // @public export class UnhandledErrorForwarder { - static forward(errorApi: ErrorApi, errorContext: ErrorContext): void; + static forward(errorApi: ErrorApi, errorContext: ErrorApiErrorContext): void; } // @public diff --git a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts index 2111798d6a..350213d938 100644 --- a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts +++ b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ErrorApi, ErrorContext, AlertApi } from '@backstage/core-plugin-api'; +import { + ErrorApi, + ErrorApiError, + ErrorApiErrorContext, + AlertApi, +} from '@backstage/core-plugin-api'; /** * Decorates an ErrorApi by also forwarding error messages @@ -27,7 +32,7 @@ export class ErrorAlerter implements ErrorApi { private readonly errorApi: ErrorApi, ) {} - post(error: Error, context?: ErrorContext) { + post(error: ErrorApiError, context?: ErrorApiErrorContext) { if (!context?.hidden) { this.alertApi.post({ message: error.message, severity: 'error' }); } diff --git a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts index cd4564a050..f67c00d991 100644 --- a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts +++ b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { ErrorApi, ErrorContext } from '@backstage/core-plugin-api'; +import { + ErrorApi, + ErrorApiError, + ErrorApiErrorContext, +} from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; import { PublishSubject } from '../../../lib/subjects'; @@ -26,14 +30,14 @@ import { PublishSubject } from '../../../lib/subjects'; export class ErrorApiForwarder implements ErrorApi { private readonly subject = new PublishSubject<{ error: Error; - context?: ErrorContext; + context?: ErrorApiErrorContext; }>(); - post(error: Error, context?: ErrorContext) { + post(error: ErrorApiError, context?: ErrorApiErrorContext) { this.subject.next({ error, context }); } - error$(): Observable<{ error: Error; context?: ErrorContext }> { + error$(): Observable<{ error: Error; context?: ErrorApiErrorContext }> { return this.subject; } } diff --git a/packages/core-app-api/src/apis/implementations/ErrorApi/UnhandledErrorForwarder.ts b/packages/core-app-api/src/apis/implementations/ErrorApi/UnhandledErrorForwarder.ts index 16e473fb3a..8e697f12b2 100644 --- a/packages/core-app-api/src/apis/implementations/ErrorApi/UnhandledErrorForwarder.ts +++ b/packages/core-app-api/src/apis/implementations/ErrorApi/UnhandledErrorForwarder.ts @@ -1,4 +1,8 @@ -import { ErrorApi, ErrorContext } from '@backstage/core-plugin-api'; +import { + ErrorApi, + ErrorApiError, + ErrorApiErrorContext, +} from '@backstage/core-plugin-api'; /* * Copyright 2020 Spotify AB @@ -25,11 +29,11 @@ export class UnhandledErrorForwarder { /** * Add event listener, such that unhandled errors can be forwarded using an given `ErrorApi` instance */ - static forward(errorApi: ErrorApi, errorContext: ErrorContext) { + static forward(errorApi: ErrorApi, errorContext: ErrorApiErrorContext) { window.addEventListener( 'unhandledrejection', (e: PromiseRejectionEvent) => { - errorApi.post(e.reason as Error, errorContext); + errorApi.post(e.reason as ErrorApiError, errorContext); }, ); } diff --git a/packages/test-utils/api-report.md b/packages/test-utils/api-report.md index c4936f6d96..62a8d9f601 100644 --- a/packages/test-utils/api-report.md +++ b/packages/test-utils/api-report.md @@ -7,7 +7,8 @@ import { AnalyticsApi } from '@backstage/core-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { ErrorApi } from '@backstage/core-plugin-api'; -import { ErrorContext } from '@backstage/core-plugin-api'; +import { ErrorApiError } from '@backstage/core-plugin-api'; +import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; import { ReactElement } from 'react'; @@ -27,8 +28,8 @@ export type CollectedLogs = { // @public export type ErrorWithContext = { - error: Error; - context?: ErrorContext; + error: ErrorApiError; + context?: ErrorApiErrorContext; }; // @public @deprecated (undocumented) @@ -109,13 +110,13 @@ export class MockErrorApi implements ErrorApi { constructor(options?: MockErrorApiOptions); // (undocumented) error$(): Observable<{ - error: Error; - context?: ErrorContext; + error: ErrorApiError; + context?: ErrorApiErrorContext; }>; // (undocumented) getErrors(): ErrorWithContext[]; // (undocumented) - post(error: Error, context?: ErrorContext): void; + post(error: ErrorApiError, context?: ErrorApiErrorContext): void; // (undocumented) waitForError(pattern: RegExp, timeoutMs?: number): Promise; } diff --git a/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts b/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts index 87601e29e2..96918b4e47 100644 --- a/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts +++ b/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { ErrorApi, ErrorContext } from '@backstage/core-plugin-api'; +import { + ErrorApi, + ErrorApiError, + ErrorApiErrorContext, +} from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; /** @@ -27,12 +31,12 @@ export type MockErrorApiOptions = { }; /** - * ErrorWithContext contains error and ErrorContext + * ErrorWithContext contains error and ErrorApiErrorContext * @public */ export type ErrorWithContext = { - error: Error; - context?: ErrorContext; + error: ErrorApiError; + context?: ErrorApiErrorContext; }; type Waiter = { @@ -59,7 +63,7 @@ export class MockErrorApi implements ErrorApi { constructor(private readonly options: MockErrorApiOptions = {}) {} - post(error: Error, context?: ErrorContext) { + post(error: ErrorApiError, context?: ErrorApiErrorContext) { if (this.options.collect) { this.errors.push({ error, context }); @@ -76,7 +80,10 @@ export class MockErrorApi implements ErrorApi { throw new Error(`MockErrorApi received unexpected error, ${error}`); } - error$(): Observable<{ error: Error; context?: ErrorContext }> { + error$(): Observable<{ + error: ErrorApiError; + context?: ErrorApiErrorContext; + }> { return nullObservable; } From 7df99cdb77d795b7fd30482b5be4bdc45177f1a6 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 10 Nov 2021 15:10:17 +0100 Subject: [PATCH 68/95] core-plugin-api: Remove exports of unused types Signed-off-by: Johan Haals --- .changeset/silent-taxis-tan.md | 5 +++++ packages/core-plugin-api/src/plugin/index.ts | 2 -- packages/core-plugin-api/src/plugin/types.ts | 17 ----------------- 3 files changed, 5 insertions(+), 19 deletions(-) create mode 100644 .changeset/silent-taxis-tan.md diff --git a/.changeset/silent-taxis-tan.md b/.changeset/silent-taxis-tan.md new file mode 100644 index 0000000000..286ef40a4c --- /dev/null +++ b/.changeset/silent-taxis-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': minor +--- + +Remove exports of unused types(`RouteOptions` and `RoutePath`). diff --git a/packages/core-plugin-api/src/plugin/index.ts b/packages/core-plugin-api/src/plugin/index.ts index 0cb9a2aede..d3272607ef 100644 --- a/packages/core-plugin-api/src/plugin/index.ts +++ b/packages/core-plugin-api/src/plugin/index.ts @@ -25,6 +25,4 @@ export type { PluginConfig, PluginHooks, PluginOutput, - RouteOptions, - RoutePath, } from './types'; diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts index 192e771092..aeb7037c51 100644 --- a/packages/core-plugin-api/src/plugin/types.ts +++ b/packages/core-plugin-api/src/plugin/types.ts @@ -17,23 +17,6 @@ import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; import { AnyApiFactory } from '../apis/system'; -/** - * Route configuration. - * - * @public - */ -export type RouteOptions = { - // Whether the route path must match exactly, defaults to true. - exact?: boolean; -}; - -/** - * Type alias for paths. - * - * @public - */ -export type RoutePath = string; - /** * Replace with using {@link RouteRef}s. * From 51d0fb3a02d73d4888b002501e895368b14af57a Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 15 Nov 2021 14:56:06 +0100 Subject: [PATCH 69/95] Fix API report Signed-off-by: Johan Haals --- packages/core-plugin-api/api-report.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 2765b02bca..824b12d6fc 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -703,14 +703,6 @@ export type RouteFunc = ( ...[params]: Params extends undefined ? readonly [] : readonly [Params] ) => string; -// @public -export type RouteOptions = { - exact?: boolean; -}; - -// @public -export type RoutePath = string; - // @public export type RouteRef = { $$routeRefType: 'absolute'; From b504cec62f31ec608891a9f4d12ef4c47ba75397 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Mon, 15 Nov 2021 17:55:02 +0100 Subject: [PATCH 70/95] Small adjustments to the Header typography to use theme variables Signed-off-by: Philipp Hugenroth --- packages/core-components/src/layout/Header/Header.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index 40495ca0e0..01baf50ad2 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -44,7 +44,7 @@ const useStyles = makeStyles( gridArea: 'pageHeader', padding: theme.spacing(3), width: '100%', - boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)', + boxShadow: theme.shadows[4], // '0 0 8px 3px rgba(20, 20, 20, 0.3)', position: 'relative', zIndex: 100, display: 'flex', @@ -65,12 +65,12 @@ const useStyles = makeStyles( title: { color: theme.palette.bursts.fontColor, wordBreak: 'break-all', - fontSize: 'calc(24px + 6 * ((100vw - 320px) / 680))', + fontSize: theme.typography.h3.fontSize, marginBottom: 0, }, subtitle: { - color: 'rgba(255, 255, 255, 0.8)', - lineHeight: '1.0em', + color: theme.palette.common.white, + opacity: 0.8, display: 'inline-block', // prevents margin collapse of adjacent siblings marginTop: theme.spacing(1), }, @@ -82,7 +82,6 @@ const useStyles = makeStyles( color: theme.palette.bursts.fontColor, }, breadcrumb: { - fontSize: 'calc(15px + 1 * ((100vw - 320px) / 680))', color: theme.palette.bursts.fontColor, }, breadcrumbType: { From 7acc18e21a7911b668e201abc7dfe2acc29087f6 Mon Sep 17 00:00:00 2001 From: therynamo Date: Wed, 3 Nov 2021 14:38:57 -0500 Subject: [PATCH 71/95] feat: Allow SSE on AWS S3 Buckets Signed-off-by: therynamo --- .changeset/orange-cougars-relax.md | 5 +++++ docs/features/techdocs/configuration.md | 5 +++++ .../techdocs-common/src/stages/publish/awsS3.test.ts | 10 ++++++++++ packages/techdocs-common/src/stages/publish/awsS3.ts | 12 +++++++++++- 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .changeset/orange-cougars-relax.md diff --git a/.changeset/orange-cougars-relax.md b/.changeset/orange-cougars-relax.md new file mode 100644 index 0000000000..2f55e23220 --- /dev/null +++ b/.changeset/orange-cougars-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Allow aws s3 buckets to pass an sse configuration so they can publish to encrypted buckets diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index fbe9f36540..cfc522adf6 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -106,6 +106,11 @@ techdocs: # This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used to host tech docs. s3ForcePathStyle: false + # (Optional) AWS Server Side Encryption + # If not set, encrypted buckets will fail to publish. + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-s3-encryption.html + sse: 'aws:kms' # or AES256 + # Required when techdocs.publisher.type is set to 'azureBlobStorage'. Skip otherwise. azureBlobStorage: diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index 1f7afa0855..ff88f12939 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -45,10 +45,12 @@ const createPublisherFromConfig = ({ bucketName = 'bucketName', bucketRootPath = '/', legacyUseCaseSensitiveTripletPaths = false, + sse, }: { bucketName?: string; bucketRootPath?: string; legacyUseCaseSensitiveTripletPaths?: boolean; + sse?: string; } = {}) => { const mockConfig = new ConfigReader({ techdocs: { @@ -62,6 +64,7 @@ const createPublisherFromConfig = ({ }, bucketName, bucketRootPath, + sse, }, }, legacyUseCaseSensitiveTripletPaths, @@ -171,6 +174,13 @@ describe('AwsS3Publish', () => { expect(await publisher.publish({ entity, directory })).toBeUndefined(); }); + it('should publish a directory when sse is specified', async () => { + const publisher = createPublisherFromConfig({ + sse: 'aws:kms', + }); + expect(await publisher.publish({ entity, directory })).toBeUndefined(); + }); + it('should fail to publish a directory', async () => { const wrongPathToGeneratedDirectory = path.join( rootDir, diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 5ebc2aab77..05ce80eaca 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -62,6 +62,7 @@ export class AwsS3Publish implements PublisherBase { private readonly legacyPathCasing: boolean; private readonly logger: Logger; private readonly bucketRootPath: string; + private readonly sse?: 'aws:kms' | 'AES256'; constructor(options: { storageClient: aws.S3; @@ -75,6 +76,7 @@ export class AwsS3Publish implements PublisherBase { this.legacyPathCasing = options.legacyPathCasing; this.logger = options.logger; this.bucketRootPath = options.bucketRootPath; + this.sse = sse; } static fromConfig(config: Config, logger: Logger): PublisherBase { @@ -92,6 +94,11 @@ export class AwsS3Publish implements PublisherBase { config.getOptionalString('techdocs.publisher.awsS3.bucketRootPath') || '', ); + const sse = config.getOptionalString('techdocs.publisher.awsS3.sse') as + | 'aws:kms' + | 'AES256' + | undefined; + // Credentials is an optional config. If missing, the default ways of authenticating AWS SDK V2 will be used. // 1. AWS environment variables // https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html @@ -138,6 +145,7 @@ export class AwsS3Publish implements PublisherBase { bucketRootPath, legacyPathCasing, logger, + sse, }); } @@ -208,6 +216,7 @@ export class AwsS3Publish implements PublisherBase { async publish({ entity, directory }: PublishRequest): Promise { const useLegacyPathCasing = this.legacyPathCasing; const bucketRootPath = this.bucketRootPath; + const sse = this.sse; // First, try to retrieve a list of all individual files currently existing let existingFiles: string[] = []; @@ -250,7 +259,8 @@ export class AwsS3Publish implements PublisherBase { bucketRootPath, ), Body: fileStream, - }; + ...(sse && { ServerSideEncryption: sse }), + } as aws.S3.PutObjectRequest; return this.storageClient.upload(params).promise(); }, From 16f7180fa2a34cb70925b941b9762e730b50266a Mon Sep 17 00:00:00 2001 From: Theryn Groetken Date: Thu, 4 Nov 2021 06:38:13 -0500 Subject: [PATCH 72/95] Update orange-cougars-relax.md Signed-off-by: therynamo --- .changeset/orange-cougars-relax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/orange-cougars-relax.md b/.changeset/orange-cougars-relax.md index 2f55e23220..0a13987551 100644 --- a/.changeset/orange-cougars-relax.md +++ b/.changeset/orange-cougars-relax.md @@ -2,4 +2,4 @@ '@backstage/techdocs-common': patch --- -Allow aws s3 buckets to pass an sse configuration so they can publish to encrypted buckets +Allow amazon web services s3 buckets to pass an server side encryption configuration so they can publish to encrypted buckets From 9e64a7ac1e0823fdef7304ce06d9e18960f8dfa7 Mon Sep 17 00:00:00 2001 From: therynamo Date: Mon, 15 Nov 2021 10:51:36 -0600 Subject: [PATCH 73/95] pr suggestions Signed-off-by: therynamo --- ...-cougars-relax.md => techdocs-orange-cougars-relax.md} | 1 + docs/features/techdocs/configuration.md | 1 + plugins/techdocs-backend/config.d.ts | 8 ++++++++ 3 files changed, 10 insertions(+) rename .changeset/{orange-cougars-relax.md => techdocs-orange-cougars-relax.md} (82%) diff --git a/.changeset/orange-cougars-relax.md b/.changeset/techdocs-orange-cougars-relax.md similarity index 82% rename from .changeset/orange-cougars-relax.md rename to .changeset/techdocs-orange-cougars-relax.md index 0a13987551..3776c9ea15 100644 --- a/.changeset/orange-cougars-relax.md +++ b/.changeset/techdocs-orange-cougars-relax.md @@ -1,5 +1,6 @@ --- '@backstage/techdocs-common': patch +'@backstage/techdocs-backend': patch --- Allow amazon web services s3 buckets to pass an server side encryption configuration so they can publish to encrypted buckets diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index cfc522adf6..61d95f55c0 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -107,6 +107,7 @@ techdocs: s3ForcePathStyle: false # (Optional) AWS Server Side Encryption + # Defaults to undefined. # If not set, encrypted buckets will fail to publish. # https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-s3-encryption.html sse: 'aws:kms' # or AES256 diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 0023a4b757..d8049daec9 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -121,6 +121,14 @@ export interface Config { * @visibility backend */ s3ForcePathStyle?: boolean; + + /** + * (Optional) AWS Server Side Encryption + * Defaults to undefined. + * If not set, encrypted buckets will fail to publish. + * https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-s3-encryption.html + */ + sse: 'aws:kms' | 'AES256'; }; } | { From dee1db5f8ac526a58fc66c56cdd4da2654389839 Mon Sep 17 00:00:00 2001 From: Soren Mathiasen Date: Mon, 15 Nov 2021 18:30:55 +0100 Subject: [PATCH 74/95] Adding Tradeshift as adopters Signed-off-by: Soren Mathiasen --- ADOPTERS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 6736538fa0..f5f5e260c9 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -64,4 +64,5 @@ | [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. | | [Volvofinans Bank](https://www.volvofinans.se) | [Johan Hammar](https://github.com/johanhammar) | Developer portal enabling engineers to manage and explore software and documentation. | | [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling | -| [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem +| [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | +| [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | From 2345b318fb49fb79c19e4ba087bffca279b3fd55 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 15 Nov 2021 22:36:41 +0100 Subject: [PATCH 75/95] create-app: fix windows file copy and mocking Signed-off-by: Patrik Oldsberg --- packages/create-app/src/createApp.test.ts | 2 +- packages/create-app/src/lib/tasks.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index 7b3b8f124c..09099e2d15 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -25,7 +25,7 @@ jest.mock('./lib/tasks'); beforeAll(() => { mockFs({ - 'package.json': '', // required by `findPaths(__dirname)` + [`${__dirname}/package.json`]: '', // required by `findPaths(__dirname)` 'templates/': mockFs.load(path.resolve(__dirname, '../templates/')), }); }); diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index 0b3ca63728..3961e84753 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -19,7 +19,12 @@ import fs from 'fs-extra'; import handlebars from 'handlebars'; import ora from 'ora'; import recursive from 'recursive-readdir'; -import { basename, dirname, resolve as resolvePath } from 'path'; +import { + basename, + dirname, + resolve as resolvePath, + relative as relativePath, +} from 'path'; import { exec as execCb } from 'child_process'; import { packageVersions } from './versions'; import { promisify } from 'util'; @@ -85,7 +90,10 @@ export async function templatingTask( }); for (const file of files) { - const destinationFile = file.replace(templateDir, destinationDir); + const destinationFile = resolvePath( + destinationDir, + relativePath(templateDir, file), + ); await fs.ensureDir(dirname(destinationFile)); if (file.endsWith('.hbs')) { From ad3afc5ad63ced76ab81d2c07607023ad5a9d705 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 16 Nov 2021 00:22:37 +0100 Subject: [PATCH 76/95] cli: trim away non-ascii in stream mock testing util Signed-off-by: Patrik Oldsberg --- .../create/factories/backendPlugin.test.ts | 28 ++++++------- .../lib/create/factories/common/tasks.test.ts | 12 +++--- .../lib/create/factories/common/testUtils.ts | 15 +++++-- .../create/factories/frontendPlugin.test.ts | 42 +++++++++---------- .../lib/create/factories/pluginCommon.test.ts | 18 ++++---- .../create/factories/scaffolderModule.test.ts | 24 +++++------ 6 files changed, 73 insertions(+), 66 deletions(-) diff --git a/packages/cli/src/lib/create/factories/backendPlugin.test.ts b/packages/cli/src/lib/create/factories/backendPlugin.test.ts index 5fe52c3732..916ba7187c 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.test.ts @@ -75,22 +75,22 @@ describe('backendPlugin factory', () => { '', 'Creating backend plugin backstage-plugin-test-backend', 'Checking Prerequisites:', - 'availability plugins/test-backend ✔', - 'creating temp dir ✔', + 'availability plugins/test-backend', + 'creating temp dir', 'Executing Template:', - 'copying .eslintrc.js ✔', - 'templating README.md.hbs ✔', - 'templating package.json.hbs ✔', - 'copying tsconfig.json ✔', - 'copying index.ts ✔', - 'templating run.ts.hbs ✔', - 'copying setupTests.ts ✔', - 'copying router.test.ts ✔', - 'copying router.ts ✔', - 'templating standaloneServer.ts.hbs ✔', + 'copying .eslintrc.js', + 'templating README.md.hbs', + 'templating package.json.hbs', + 'copying tsconfig.json', + 'copying index.ts', + 'templating run.ts.hbs', + 'copying setupTests.ts', + 'copying router.test.ts', + 'copying router.ts', + 'templating standaloneServer.ts.hbs', 'Installing:', - 'moving plugins/test-backend ✔', - 'backend adding dependency ✔', + 'moving plugins/test-backend', + 'backend adding dependency', ]); await expect( diff --git a/packages/cli/src/lib/create/factories/common/tasks.test.ts b/packages/cli/src/lib/create/factories/common/tasks.test.ts index b3a751ba5b..0892322e61 100644 --- a/packages/cli/src/lib/create/factories/common/tasks.test.ts +++ b/packages/cli/src/lib/create/factories/common/tasks.test.ts @@ -88,14 +88,14 @@ some-package@^1.1.0: expect(modified).toBe(true); expect(output).toEqual([ 'Checking Prerequisites:', - 'availability /target ✔', - 'creating temp dir ✔', + 'availability /target', + 'creating temp dir', 'Executing Template:', - 'templating package.json.hbs ✔', - 'copying not-templated.txt ✔', - 'templating templated.txt.hbs ✔', + 'templating package.json.hbs', + 'copying not-templated.txt', + 'templating templated.txt.hbs', 'Installing:', - 'moving /target ✔', + 'moving /target', ]); await expect(fs.readFile('/target/package.json', 'utf8')).resolves.toBe(`{ "name": "my-testing-plugin", diff --git a/packages/cli/src/lib/create/factories/common/testUtils.ts b/packages/cli/src/lib/create/factories/common/testUtils.ts index 651409451e..01081a7486 100644 --- a/packages/cli/src/lib/create/factories/common/testUtils.ts +++ b/packages/cli/src/lib/create/factories/common/testUtils.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +/* eslint-disable no-control-regex */ + import { WriteStream } from 'tty'; import { resolve as resolvePath } from 'path'; import { paths } from '../../../paths'; @@ -59,10 +61,15 @@ export function createMockOutputStream() { cursorTo: () => {}, clearLine: () => {}, moveCursor: () => {}, - write: (msg: string) => - // Clean up colors and whitespace - // eslint-disable-next-line no-control-regex - output.push(msg.replace(/\x1B\[\d\dm/g, '').trim()), + write: (msg: string) => { + let clean = msg; + // Remove terminal color escape sequences + clean = clean.replace(/\x1B\[\d\dm/g, ''); + // Remove any non-ascii + clean = clean.replace(/[^\x00-\x7F]+/g, ''); + clean = clean.trim(); + output.push(clean); + }, } as unknown as WriteStream & { fd: any }, ] as const; } diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts index a882a130ea..439b134478 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts @@ -88,29 +88,29 @@ describe('frontendPlugin factory', () => { '', 'Creating backend plugin backstage-plugin-test', 'Checking Prerequisites:', - 'availability plugins/test ✔', - 'creating temp dir ✔', + 'availability plugins/test', + 'creating temp dir', 'Executing Template:', - 'copying .eslintrc.js ✔', - 'templating README.md.hbs ✔', - 'templating package.json.hbs ✔', - 'copying tsconfig.json ✔', - 'templating index.tsx.hbs ✔', - 'templating index.ts.hbs ✔', - 'templating plugin.test.ts.hbs ✔', - 'templating plugin.ts.hbs ✔', - 'templating routes.ts.hbs ✔', - 'copying setupTests.ts ✔', - 'templating ExampleComponent.test.tsx.hbs ✔', - 'templating ExampleComponent.tsx.hbs ✔', - 'copying index.ts ✔', - 'templating ExampleFetchComponent.test.tsx.hbs ✔', - 'templating ExampleFetchComponent.tsx.hbs ✔', - 'copying index.ts ✔', + 'copying .eslintrc.js', + 'templating README.md.hbs', + 'templating package.json.hbs', + 'copying tsconfig.json', + 'templating index.tsx.hbs', + 'templating index.ts.hbs', + 'templating plugin.test.ts.hbs', + 'templating plugin.ts.hbs', + 'templating routes.ts.hbs', + 'copying setupTests.ts', + 'templating ExampleComponent.test.tsx.hbs', + 'templating ExampleComponent.tsx.hbs', + 'copying index.ts', + 'templating ExampleFetchComponent.test.tsx.hbs', + 'templating ExampleFetchComponent.tsx.hbs', + 'copying index.ts', 'Installing:', - 'moving plugins/test ✔', - 'app adding dependency ✔', - 'app adding import ✔', + 'moving plugins/test', + 'app adding dependency', + 'app adding import', ]); await expect( diff --git a/packages/cli/src/lib/create/factories/pluginCommon.test.ts b/packages/cli/src/lib/create/factories/pluginCommon.test.ts index a39bb3877d..3124df6b1d 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.test.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.test.ts @@ -70,17 +70,17 @@ describe('pluginCommon factory', () => { '', 'Creating backend plugin backstage-plugin-test-common', 'Checking Prerequisites:', - 'availability plugins/test-common ✔', - 'creating temp dir ✔', + 'availability plugins/test-common', + 'creating temp dir', 'Executing Template:', - 'copying .eslintrc.js ✔', - 'templating README.md.hbs ✔', - 'templating package.json.hbs ✔', - 'copying tsconfig.json ✔', - 'templating index.ts.hbs ✔', - 'copying setupTests.ts ✔', + 'copying .eslintrc.js', + 'templating README.md.hbs', + 'templating package.json.hbs', + 'copying tsconfig.json', + 'templating index.ts.hbs', + 'copying setupTests.ts', 'Installing:', - 'moving plugins/test-common ✔', + 'moving plugins/test-common', ]); await expect( diff --git a/packages/cli/src/lib/create/factories/scaffolderModule.test.ts b/packages/cli/src/lib/create/factories/scaffolderModule.test.ts index dde711b750..a5f1f8e63a 100644 --- a/packages/cli/src/lib/create/factories/scaffolderModule.test.ts +++ b/packages/cli/src/lib/create/factories/scaffolderModule.test.ts @@ -70,20 +70,20 @@ describe('scaffolderModule factory', () => { '', 'Creating module backstage-plugin-scaffolder-backend-module-test', 'Checking Prerequisites:', - 'availability plugins/scaffolder-backend-module-test ✔', - 'creating temp dir ✔', + 'availability plugins/scaffolder-backend-module-test', + 'creating temp dir', 'Executing Template:', - 'copying .eslintrc.js ✔', - 'templating README.md.hbs ✔', - 'templating package.json.hbs ✔', - 'copying tsconfig.json ✔', - 'templating index.ts.hbs ✔', - 'copying index.ts ✔', - 'copying example.test.ts ✔', - 'copying example.ts ✔', - 'copying index.ts ✔', + 'copying .eslintrc.js', + 'templating README.md.hbs', + 'templating package.json.hbs', + 'copying tsconfig.json', + 'templating index.ts.hbs', + 'copying index.ts', + 'copying example.test.ts', + 'copying example.ts', + 'copying index.ts', 'Installing:', - 'moving plugins/scaffolder-backend-module-test ✔', + 'moving plugins/scaffolder-backend-module-test', ]); await expect( From 6f279f9b8eebf4cea506baea2c6bca733de290c6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 16 Nov 2021 01:52:56 +0100 Subject: [PATCH 77/95] cli: fix failing create tests on windows Signed-off-by: Patrik Oldsberg --- .../src/lib/create/factories/backendPlugin.test.ts | 9 +++++---- .../src/lib/create/factories/common/tasks.test.ts | 5 +++-- .../cli/src/lib/create/factories/common/tasks.ts | 4 ++-- .../src/lib/create/factories/frontendPlugin.test.ts | 13 +++++++------ .../src/lib/create/factories/pluginCommon.test.ts | 9 +++++---- .../lib/create/factories/scaffolderModule.test.ts | 9 +++++---- 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/packages/cli/src/lib/create/factories/backendPlugin.test.ts b/packages/cli/src/lib/create/factories/backendPlugin.test.ts index 916ba7187c..6f4a73c02d 100644 --- a/packages/cli/src/lib/create/factories/backendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/backendPlugin.test.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; +import { sep, resolve as resolvePath } from 'path'; import { paths } from '../../paths'; import { Task } from '../../tasks'; import { FactoryRegistry } from '../FactoryRegistry'; @@ -75,7 +76,7 @@ describe('backendPlugin factory', () => { '', 'Creating backend plugin backstage-plugin-test-backend', 'Checking Prerequisites:', - 'availability plugins/test-backend', + `availability plugins${sep}test-backend`, 'creating temp dir', 'Executing Template:', 'copying .eslintrc.js', @@ -89,7 +90,7 @@ describe('backendPlugin factory', () => { 'copying router.ts', 'templating standaloneServer.ts.hbs', 'Installing:', - 'moving plugins/test-backend', + `moving plugins${sep}test-backend`, 'backend adding dependency', ]); @@ -103,11 +104,11 @@ describe('backendPlugin factory', () => { expect(Task.forCommand).toHaveBeenCalledTimes(2); expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { - cwd: '/root/plugins/test-backend', + cwd: resolvePath('/root/plugins/test-backend'), optional: true, }); expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { - cwd: '/root/plugins/test-backend', + cwd: resolvePath('/root/plugins/test-backend'), optional: true, }); }); diff --git a/packages/cli/src/lib/create/factories/common/tasks.test.ts b/packages/cli/src/lib/create/factories/common/tasks.test.ts index 0892322e61..49381676e7 100644 --- a/packages/cli/src/lib/create/factories/common/tasks.test.ts +++ b/packages/cli/src/lib/create/factories/common/tasks.test.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; +import { sep } from 'path'; import { createMockOutputStream, mockPaths } from './testUtils'; import { CreateContext } from '../../types'; import { executePluginPackageTemplate } from './tasks'; @@ -88,14 +89,14 @@ some-package@^1.1.0: expect(modified).toBe(true); expect(output).toEqual([ 'Checking Prerequisites:', - 'availability /target', + `availability ..${sep}target`, 'creating temp dir', 'Executing Template:', 'templating package.json.hbs', 'copying not-templated.txt', 'templating templated.txt.hbs', 'Installing:', - 'moving /target', + `moving ..${sep}target`, ]); await expect(fs.readFile('/target/package.json', 'utf8')).resolves.toBe(`{ "name": "my-testing-plugin", diff --git a/packages/cli/src/lib/create/factories/common/tasks.ts b/packages/cli/src/lib/create/factories/common/tasks.ts index fd3bd4d930..93af1e2b00 100644 --- a/packages/cli/src/lib/create/factories/common/tasks.ts +++ b/packages/cli/src/lib/create/factories/common/tasks.ts @@ -16,7 +16,7 @@ import fs from 'fs-extra'; import chalk from 'chalk'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath, relative as relativePath } from 'path'; import { paths } from '../../../paths'; import { Task, templatingTask } from '../../../tasks'; import { Lockfile } from '../../../versioning'; @@ -41,7 +41,7 @@ export async function executePluginPackageTemplate( } Task.section('Checking Prerequisites'); - const shortPluginDir = targetDir.replace(`${paths.targetRoot}/`, ''); + const shortPluginDir = relativePath(paths.targetRoot, targetDir); await Task.forItem('availability', shortPluginDir, async () => { if (await fs.pathExists(targetDir)) { throw new Error( diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts index 439b134478..27ee14ea2c 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; +import { sep, resolve as resolvePath } from 'path'; import { paths } from '../../paths'; import { Task } from '../../tasks'; import { FactoryRegistry } from '../FactoryRegistry'; @@ -88,7 +89,7 @@ describe('frontendPlugin factory', () => { '', 'Creating backend plugin backstage-plugin-test', 'Checking Prerequisites:', - 'availability plugins/test', + `availability plugins${sep}test`, 'creating temp dir', 'Executing Template:', 'copying .eslintrc.js', @@ -108,7 +109,7 @@ describe('frontendPlugin factory', () => { 'templating ExampleFetchComponent.tsx.hbs', 'copying index.ts', 'Installing:', - 'moving plugins/test', + `moving plugins${sep}test`, 'app adding dependency', 'app adding import', ]); @@ -136,11 +137,11 @@ const router = ( expect(Task.forCommand).toHaveBeenCalledTimes(2); expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { - cwd: '/root/plugins/test', + cwd: resolvePath('/root/plugins/test'), optional: true, }); expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { - cwd: '/root/plugins/test', + cwd: resolvePath('/root/plugins/test'), optional: true, }); }); @@ -205,11 +206,11 @@ const router = ( expect(Task.forCommand).toHaveBeenCalledTimes(2); expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { - cwd: '/root/plugins/test', + cwd: resolvePath('/root/plugins/test'), optional: true, }); expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { - cwd: '/root/plugins/test', + cwd: resolvePath('/root/plugins/test'), optional: true, }); }); diff --git a/packages/cli/src/lib/create/factories/pluginCommon.test.ts b/packages/cli/src/lib/create/factories/pluginCommon.test.ts index 3124df6b1d..602fce7bbd 100644 --- a/packages/cli/src/lib/create/factories/pluginCommon.test.ts +++ b/packages/cli/src/lib/create/factories/pluginCommon.test.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; +import { sep, resolve as resolvePath } from 'path'; import { paths } from '../../paths'; import { Task } from '../../tasks'; import { FactoryRegistry } from '../FactoryRegistry'; @@ -70,7 +71,7 @@ describe('pluginCommon factory', () => { '', 'Creating backend plugin backstage-plugin-test-common', 'Checking Prerequisites:', - 'availability plugins/test-common', + `availability plugins${sep}test-common`, 'creating temp dir', 'Executing Template:', 'copying .eslintrc.js', @@ -80,7 +81,7 @@ describe('pluginCommon factory', () => { 'templating index.ts.hbs', 'copying setupTests.ts', 'Installing:', - 'moving plugins/test-common', + `moving plugins${sep}test-common`, ]); await expect( @@ -96,11 +97,11 @@ describe('pluginCommon factory', () => { expect(Task.forCommand).toHaveBeenCalledTimes(2); expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { - cwd: '/root/plugins/test-common', + cwd: resolvePath('/root/plugins/test-common'), optional: true, }); expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { - cwd: '/root/plugins/test-common', + cwd: resolvePath('/root/plugins/test-common'), optional: true, }); }); diff --git a/packages/cli/src/lib/create/factories/scaffolderModule.test.ts b/packages/cli/src/lib/create/factories/scaffolderModule.test.ts index a5f1f8e63a..c60c58e619 100644 --- a/packages/cli/src/lib/create/factories/scaffolderModule.test.ts +++ b/packages/cli/src/lib/create/factories/scaffolderModule.test.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; +import { sep, resolve as resolvePath } from 'path'; import { paths } from '../../paths'; import { Task } from '../../tasks'; import { FactoryRegistry } from '../FactoryRegistry'; @@ -70,7 +71,7 @@ describe('scaffolderModule factory', () => { '', 'Creating module backstage-plugin-scaffolder-backend-module-test', 'Checking Prerequisites:', - 'availability plugins/scaffolder-backend-module-test', + `availability plugins${sep}scaffolder-backend-module-test`, 'creating temp dir', 'Executing Template:', 'copying .eslintrc.js', @@ -83,7 +84,7 @@ describe('scaffolderModule factory', () => { 'copying example.ts', 'copying index.ts', 'Installing:', - 'moving plugins/scaffolder-backend-module-test', + `moving plugins${sep}scaffolder-backend-module-test`, ]); await expect( @@ -99,11 +100,11 @@ describe('scaffolderModule factory', () => { expect(Task.forCommand).toHaveBeenCalledTimes(2); expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { - cwd: '/root/plugins/scaffolder-backend-module-test', + cwd: resolvePath('/root/plugins/scaffolder-backend-module-test'), optional: true, }); expect(Task.forCommand).toHaveBeenCalledWith('yarn lint --fix', { - cwd: '/root/plugins/scaffolder-backend-module-test', + cwd: resolvePath('/root/plugins/scaffolder-backend-module-test'), optional: true, }); }); From 6c1348591e513137442f6d539fae3d905f72e442 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Nov 2021 04:22:02 +0000 Subject: [PATCH 78/95] build(deps-dev): bump prettier from 2.4.0 to 2.4.1 Bumps [prettier](https://github.com/prettier/prettier) from 2.4.0 to 2.4.1. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.4.0...2.4.1) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 37c1bd3329..9bff375969 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23142,9 +23142,9 @@ prettier@^1.19.1: integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== prettier@^2.2.1: - version "2.4.0" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz#85bdfe0f70c3e777cf13a4ffff39713ca6f64cba" - integrity sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ== + version "2.4.1" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" + integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== prettier@~2.2.1: version "2.2.1" From c1858c4cf95031c9ec11fa9daa87b70f94250a68 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 16 Nov 2021 10:25:01 +0100 Subject: [PATCH 79/95] Break apart changesets and clear yarn.lock diff Signed-off-by: Eric Peterson --- .changeset/techdocs-chatt-months-report-too.md | 5 +++++ .changeset/techdocs-chatty-months-report.md | 3 +-- yarn.lock | 10 +++++----- 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 .changeset/techdocs-chatt-months-report-too.md diff --git a/.changeset/techdocs-chatt-months-report-too.md b/.changeset/techdocs-chatt-months-report-too.md new file mode 100644 index 0000000000..612a4506a5 --- /dev/null +++ b/.changeset/techdocs-chatt-months-report-too.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fixed entity triplet case handling for certain locales. diff --git a/.changeset/techdocs-chatty-months-report.md b/.changeset/techdocs-chatty-months-report.md index fbc4faaf0c..7d3fa773bf 100644 --- a/.changeset/techdocs-chatty-months-report.md +++ b/.changeset/techdocs-chatty-months-report.md @@ -1,6 +1,5 @@ --- '@backstage/techdocs-common': patch -'@backstage/plugin-techdocs': patch --- -OpenStack Swift Migration Support added +Added OpenStack Swift case migration support. diff --git a/yarn.lock b/yarn.lock index 591ee92793..adf51ccb32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6714,10 +6714,10 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@trendyol-js/openstack-swift-sdk@^0.0.4": - version "0.0.4" - resolved "https://registry.npmjs.org/@trendyol-js/openstack-swift-sdk/-/openstack-swift-sdk-0.0.4.tgz#570c6ab950319156c175ace005b4fb4d9f895d47" - integrity sha512-9YKOjov+V+yzptei6+B9QPuC5pOMTBTg/NQpb1ZbxvlOaYpWU4HHpSH2BkIFYZ8vYyAfzFNG1T2rjpQ2ZQDUtQ== +"@trendyol-js/openstack-swift-sdk@^0.0.5": + version "0.0.5" + resolved "https://registry.npmjs.org/@trendyol-js/openstack-swift-sdk/-/openstack-swift-sdk-0.0.5.tgz#65be3c42b8dbafc57f2f2a46c327e2ad51e5a70e" + integrity sha512-KS5nz0cvd35UUyMzhZm+btGV4prtA1KNE7CCMOGBdVxoMGl06Qidli3HgHoc2I9jLPmky1SPp5yzQUwrsyWa0g== dependencies: agentkeepalive "^4.1.4" axios "^0.21.1" @@ -29497,4 +29497,4 @@ zwitch@^1.0.0: zwitch@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz#91f8d0e901ffa3d66599756dde7f57b17c95dce1" - integrity sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA== \ No newline at end of file + integrity sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA== From b0c7748ab5cc2d124dd03dd87155d19c588f128f Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 16 Nov 2021 10:34:56 +0100 Subject: [PATCH 80/95] Ensure sse config is optional; pass through sse from constructor. Signed-off-by: Eric Peterson --- packages/techdocs-common/src/stages/publish/awsS3.ts | 3 ++- plugins/techdocs-backend/config.d.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts index 05ce80eaca..abfa8ddd24 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.ts @@ -70,13 +70,14 @@ export class AwsS3Publish implements PublisherBase { legacyPathCasing: boolean; logger: Logger; bucketRootPath: string; + sse?: 'aws:kms' | 'AES256'; }) { this.storageClient = options.storageClient; this.bucketName = options.bucketName; this.legacyPathCasing = options.legacyPathCasing; this.logger = options.logger; this.bucketRootPath = options.bucketRootPath; - this.sse = sse; + this.sse = options.sse; } static fromConfig(config: Config, logger: Logger): PublisherBase { diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index d8049daec9..da3cd34830 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -128,7 +128,7 @@ export interface Config { * If not set, encrypted buckets will fail to publish. * https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-s3-encryption.html */ - sse: 'aws:kms' | 'AES256'; + sse?: 'aws:kms' | 'AES256'; }; } | { From ecd1fcb80a8a0b05a78507cee9f76e8ef076c1a5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 16 Nov 2021 10:57:05 +0100 Subject: [PATCH 81/95] core-app-api: deprecate the BackstagePluginWithAnyOutput type Signed-off-by: Patrik Oldsberg --- .changeset/rare-lemons-boil.md | 5 +++++ packages/core-app-api/api-report.md | 11 +++++++++-- packages/core-app-api/src/app/types.ts | 5 ++++- 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 .changeset/rare-lemons-boil.md diff --git a/.changeset/rare-lemons-boil.md b/.changeset/rare-lemons-boil.md new file mode 100644 index 0000000000..b882ab0b91 --- /dev/null +++ b/.changeset/rare-lemons-boil.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Deprecated the `BackstagePluginWithAnyOutput` type. diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 2c390bf132..788127922f 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -197,7 +197,14 @@ export type AppOptions = { icons: AppIcons & { [key in string]: IconComponent; }; - plugins?: BackstagePluginWithAnyOutput[]; + plugins?: (Omit, 'output'> & { + output(): ( + | PluginOutput + | { + type: string; + } + )[]; + })[]; components: AppComponents; themes: (Partial & Omit)[]; configLoader?: AppConfigLoader; @@ -269,7 +276,7 @@ export type BackstageApp = { getRouter(): ComponentType<{}>; }; -// @public +// @public @deprecated export type BackstagePluginWithAnyOutput = Omit< BackstagePlugin, 'output' diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index f2d1976f87..7166136054 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -200,6 +200,7 @@ export type AppRouteBinder = < * * @public * @remarks + * @deprecated Will be removed * * The `type: string` type is there to handle output from newer or older plugin * API versions that might not be supported by this version of the app API, but @@ -246,7 +247,9 @@ export type AppOptions = { /** * A list of all plugins to include in the app. */ - plugins?: BackstagePluginWithAnyOutput[]; + plugins?: (Omit, 'output'> & { + output(): (PluginOutput | { type: string })[]; + })[]; /** * Supply components to the app to override the default ones. From 26b5da1c1aadabe65c741c0d6ee0a2610f9acae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 16 Nov 2021 11:42:10 +0100 Subject: [PATCH 82/95] Do not redact the empty string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/spicy-rice-build.md | 5 +++++ packages/backend-common/src/logging/rootLogger.ts | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .changeset/spicy-rice-build.md diff --git a/.changeset/spicy-rice-build.md b/.changeset/spicy-rice-build.md new file mode 100644 index 0000000000..d8da66f66c --- /dev/null +++ b/.changeset/spicy-rice-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Do not redact the empty string, destroying all logs diff --git a/packages/backend-common/src/logging/rootLogger.ts b/packages/backend-common/src/logging/rootLogger.ts index b05763a42c..2a6e92ac87 100644 --- a/packages/backend-common/src/logging/rootLogger.ts +++ b/packages/backend-common/src/logging/rootLogger.ts @@ -21,7 +21,7 @@ import { coloredFormat } from './formats'; import { escapeRegExp } from '../util/escapeRegExp'; let rootLogger: winston.Logger; -let redactionRegExp: RegExp; +let redactionRegExp: RegExp | undefined; /** @public */ export function getRootLogger(): winston.Logger { @@ -34,11 +34,14 @@ export function setRootLogger(newLogger: winston.Logger) { } export function setRootLoggerRedactionList(redactionList: string[]) { - if (redactionList.length) { + const filtered = redactionList.filter(Boolean); + if (filtered.length) { redactionRegExp = new RegExp( - `(${redactionList.map(escapeRegExp).join('|')})`, + `(${filtered.map(escapeRegExp).join('|')})`, 'g', ); + } else { + redactionRegExp = undefined; } } From f9daf056e52cf9632b250b87a08f1493eaa8dab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 16 Nov 2021 11:47:51 +0100 Subject: [PATCH 83/95] add test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../backend-common/src/logging/rootLogger.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/backend-common/src/logging/rootLogger.test.ts b/packages/backend-common/src/logging/rootLogger.test.ts index 192decb653..67353a2f2d 100644 --- a/packages/backend-common/src/logging/rootLogger.test.ts +++ b/packages/backend-common/src/logging/rootLogger.test.ts @@ -48,6 +48,19 @@ describe('rootLogger', () => { ); }); + it('redacts but ignores empty secrets', () => { + const logger = createRootLogger(); + jest.spyOn(logger, 'write'); + setRootLoggerRedactionList(['SECRET-1', 'SECRET_2', '']); + logger.info('Logging SECRET-1 and SECRET_2'); + + expect(logger.write).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Logging [REDACTED] and [REDACTED]', + }), + ); + }); + describe('createRootLogger', () => { it('creates a new logger', () => { const oldLogger = getRootLogger(); From f7703981a96b539f9f04b74362baf9805a2db15c Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Tue, 16 Nov 2021 11:35:30 +0100 Subject: [PATCH 84/95] Use a better checkbox rendering in a task list Signed-off-by: Dominik Henneke --- .changeset/techdocs-sharp-knives-unite.md | 5 +++++ .../techdocs/src/reader/components/Reader.tsx | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 .changeset/techdocs-sharp-knives-unite.md diff --git a/.changeset/techdocs-sharp-knives-unite.md b/.changeset/techdocs-sharp-knives-unite.md new file mode 100644 index 0000000000..aa3625b883 --- /dev/null +++ b/.changeset/techdocs-sharp-knives-unite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Use a better checkbox rendering in a task list. diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 4e12ac18d1..c0b0c99b59 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -214,6 +214,16 @@ export const useTechDocsReaderDom = (): Element | null => { .md-typeset .admonition, .md-typeset details { font-size: 1rem; } + + /* style the checkmarks of the task list */ + .md-typeset .task-list-control .task-list-indicator::before { + background-color: ${theme.palette.action.disabledBackground}; + } + .md-typeset .task-list-control [type="checkbox"]:checked + .task-list-indicator:before { + background-color: ${theme.palette.success.main}; + } + /**/ + @media screen and (max-width: 76.1875em) { .md-nav { background-color: ${theme.palette.background.default}; @@ -293,8 +303,8 @@ export const useTechDocsReaderDom = (): Element | null => { --md-details-icon: url('data:image/svg+xml;charset=utf-8,'); } :host { - --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); + --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); } `, }), @@ -305,9 +315,11 @@ export const useTechDocsReaderDom = (): Element | null => { namespace, scmIntegrationsApi, techdocsStorageApi, + theme.palette.action.disabledBackground, theme.palette.background.default, theme.palette.background.paper, theme.palette.primary.main, + theme.palette.success.main, theme.palette.text.primary, theme.typography.fontFamily, ], From 682945e23312b8cadfd2600fb8964bc900a2b400 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Tue, 16 Nov 2021 13:29:58 +0100 Subject: [PATCH 85/95] Add changeset Signed-off-by: Philipp Hugenroth --- .changeset/little-numbers-thank.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/little-numbers-thank.md diff --git a/.changeset/little-numbers-thank.md b/.changeset/little-numbers-thank.md new file mode 100644 index 0000000000..f444a904c0 --- /dev/null +++ b/.changeset/little-numbers-thank.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Changing the `Header` styles to use more theme variables. With this the title `font-size` will not change on resizing the window. From 50154fb792222551a0530846505fd10d810f04d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 16 Nov 2021 13:37:28 +0100 Subject: [PATCH 86/95] exclude one-character secrets as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/spicy-rice-build.md | 2 +- packages/backend-common/src/logging/rootLogger.test.ts | 8 ++++---- packages/backend-common/src/logging/rootLogger.ts | 6 +++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.changeset/spicy-rice-build.md b/.changeset/spicy-rice-build.md index d8da66f66c..c0322d778a 100644 --- a/.changeset/spicy-rice-build.md +++ b/.changeset/spicy-rice-build.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -Do not redact the empty string, destroying all logs +Do not redact empty or one-character strings. These imply that it's just a test or local dev, and unnecessarily ruin the log output. diff --git a/packages/backend-common/src/logging/rootLogger.test.ts b/packages/backend-common/src/logging/rootLogger.test.ts index 67353a2f2d..0a664f9656 100644 --- a/packages/backend-common/src/logging/rootLogger.test.ts +++ b/packages/backend-common/src/logging/rootLogger.test.ts @@ -48,15 +48,15 @@ describe('rootLogger', () => { ); }); - it('redacts but ignores empty secrets', () => { + it('redacts but ignores empty and one-character secrets', () => { const logger = createRootLogger(); jest.spyOn(logger, 'write'); - setRootLoggerRedactionList(['SECRET-1', 'SECRET_2', '']); - logger.info('Logging SECRET-1 and SECRET_2'); + setRootLoggerRedactionList(['SECRET-1', 'SECRET_2', 'Q', '']); + logger.info('Logging SECRET-1 and SECRET_2 and Q'); expect(logger.write).toHaveBeenCalledWith( expect.objectContaining({ - message: 'Logging [REDACTED] and [REDACTED]', + message: 'Logging [REDACTED] and [REDACTED] and Q', }), ); }); diff --git a/packages/backend-common/src/logging/rootLogger.ts b/packages/backend-common/src/logging/rootLogger.ts index 2a6e92ac87..2a4226f88f 100644 --- a/packages/backend-common/src/logging/rootLogger.ts +++ b/packages/backend-common/src/logging/rootLogger.ts @@ -34,7 +34,11 @@ export function setRootLogger(newLogger: winston.Logger) { } export function setRootLoggerRedactionList(redactionList: string[]) { - const filtered = redactionList.filter(Boolean); + // Exclude secrets that are empty or just one character in length. These + // typically mean that you are running local dev or tests, or using the + // --lax flag which sets things to just 'x'. So exclude those. + const filtered = redactionList.filter(r => r.length > 1); + if (filtered.length) { redactionRegExp = new RegExp( `(${filtered.map(escapeRegExp).join('|')})`, From 469a47250e5fcbb084ea847bd1893d65e152c8f2 Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Tue, 16 Nov 2021 13:45:16 +0100 Subject: [PATCH 87/95] Remove comment Signed-off-by: Philipp Hugenroth --- packages/core-components/src/layout/Header/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index 01baf50ad2..76d346031e 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -44,7 +44,7 @@ const useStyles = makeStyles( gridArea: 'pageHeader', padding: theme.spacing(3), width: '100%', - boxShadow: theme.shadows[4], // '0 0 8px 3px rgba(20, 20, 20, 0.3)', + boxShadow: theme.shadows[4], position: 'relative', zIndex: 100, display: 'flex', From 0da0143e85ab83d6ee0bd783001a3de496cbe4c1 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Nov 2021 14:16:55 +0100 Subject: [PATCH 88/95] core-app-api: Add missing exports and API annotations Signed-off-by: Johan Haals --- packages/core-app-api/api-report.md | 104 ++++++++++++++---- .../auth/github/GithubAuth.test.ts | 2 +- .../implementations/auth/github/GithubAuth.ts | 3 + .../src/apis/implementations/auth/index.ts | 1 + .../implementations/auth/oauth2/OAuth2.ts | 11 +- .../apis/implementations/auth/oauth2/types.ts | 1 + .../auth/onelogin/OneLoginAuth.ts | 8 +- .../implementations/auth/onelogin/index.ts | 1 + .../implementations/auth/saml/SamlAuth.ts | 3 + .../apis/implementations/auth/saml/index.ts | 1 + .../src/apis/implementations/auth/types.ts | 8 ++ .../src/apis/system/ApiFactoryRegistry.ts | 6 +- .../src/apis/system/ApiProvider.tsx | 6 +- .../core-app-api/src/apis/system/index.ts | 2 + packages/core-app-api/src/app/types.ts | 9 +- 15 files changed, 136 insertions(+), 30 deletions(-) diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 2c390bf132..1b9e66cd39 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -95,7 +95,6 @@ export class ApiFactoryRegistry implements ApiFactoryHolder { | undefined; // (undocumented) getAllApis(): Set; - // Warning: (ae-forgotten-export) The symbol "ApiFactoryScope" needs to be exported by the entry point index.d.ts register< Api, Impl extends Api, @@ -105,6 +104,9 @@ export class ApiFactoryRegistry implements ApiFactoryHolder { >(scope: ApiFactoryScope, factory: ApiFactory): boolean; } +// @public +export type ApiFactoryScope = 'default' | 'app' | 'static'; + // @public export const ApiProvider: { (props: PropsWithChildren): JSX.Element; @@ -118,6 +120,12 @@ export const ApiProvider: { }; }; +// @public +export type ApiProviderProps = { + apis: ApiHolder; + children: ReactNode; +}; + // @public export class ApiRegistry implements ApiHolder { constructor(apis: Map); @@ -204,10 +212,6 @@ export type AppOptions = { bindRoutes?(context: { bind: AppRouteBinder }): void; }; -// Warning: (ae-forgotten-export) The symbol "PartialKeys" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "TargetRouteMap" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "KeysWithType" needs to be exported by the entry point index.d.ts -// // @public export type AppRouteBinder = < ExternalRoutes extends { @@ -238,8 +242,6 @@ export class AppThemeSelector implements AppThemeApi { // @public export class AtlassianAuth { - // Warning: (ae-forgotten-export) The symbol "OAuthApiCreateOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) static create({ discoveryApi, @@ -261,6 +263,15 @@ export class Auth0Auth { }: OAuthApiCreateOptions): typeof auth0AuthApiRef.T; } +// @public +export type AuthApiCreateOptions = { + discoveryApi: DiscoveryApi; + environment?: string; + provider?: AuthProvider & { + id: string; + }; +}; + // @public export type BackstageApp = { getPlugins(): BackstagePlugin[]; @@ -378,6 +389,8 @@ export type FlatRoutesProps = { // @public export class GithubAuth implements OAuthApi, SessionApi { // Warning: (ae-forgotten-export) The symbol "SessionManager" needs to be exported by the entry point index.d.ts + // + // @deprecated constructor(sessionManager: SessionManager); // (undocumented) static create({ @@ -440,6 +453,16 @@ export class GoogleAuth { }: OAuthApiCreateOptions): typeof googleAuthApiRef.T; } +// @public +export type KeysWithType< + Obj extends { + [key in string]: any; + }, + Type, +> = { + [key in keyof Obj]: Obj[key] extends Type ? key : never; +}[keyof Obj]; + // @public export class LocalStorageFeatureFlags implements FeatureFlagsApi { // (undocumented) @@ -479,12 +502,11 @@ export class OAuth2 BackstageIdentityApi, SessionApi { + // @deprecated constructor(options: { sessionManager: SessionManager; scopeTransform: (scopes: string[]) => string[]; }); - // Warning: (ae-forgotten-export) The symbol "CreateOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) static create({ discoveryApi, @@ -493,7 +515,7 @@ export class OAuth2 oauthRequestApi, defaultScopes, scopeTransform, - }: CreateOptions): OAuth2; + }: OAuth2CreateOptions): OAuth2; // (undocumented) getAccessToken( scope?: string | string[], @@ -515,6 +537,11 @@ export class OAuth2 signOut(): Promise; } +// @public +export type OAuth2CreateOptions = OAuthApiCreateOptions & { + scopeTransform?: (scopes: string[]) => string[]; +}; + // @public export type OAuth2Session = { providerInfo: { @@ -527,6 +554,12 @@ export type OAuth2Session = { backstageIdentity: BackstageIdentity; }; +// @public +export type OAuthApiCreateOptions = AuthApiCreateOptions & { + oauthRequestApi: OAuthRequestApi; + defaultScopes?: string[]; +}; + // @public export class OAuthRequestManager implements OAuthRequestApi { // (undocumented) @@ -549,25 +582,39 @@ export class OktaAuth { // @public export class OneLoginAuth { - // Warning: (ae-forgotten-export) The symbol "CreateOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) static create({ discoveryApi, environment, provider, oauthRequestApi, - }: CreateOptions_2): typeof oneloginAuthApiRef.T; + }: OneLoginAuthCreateOptions): typeof oneloginAuthApiRef.T; } +// @public +export type OneLoginAuthCreateOptions = { + discoveryApi: DiscoveryApi; + oauthRequestApi: OAuthRequestApi; + environment?: string; + provider?: AuthProvider & { + id: string; + }; +}; + +// @public +export type PartialKeys< + Map extends { + [name in string]: any; + }, + Keys extends keyof Map, +> = Partial> & Required>; + // @public export class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi { - // Warning: (ae-forgotten-export) The symbol "SamlSession" needs to be exported by the entry point index.d.ts + // @deprecated constructor(sessionManager: SessionManager); - // Warning: (ae-forgotten-export) The symbol "AuthApiCreateOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) static create({ discoveryApi, @@ -588,6 +635,13 @@ export class SamlAuth signOut(): Promise; } +// @public +export type SamlSession = { + userId: string; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; +}; + // @public export type SignInPageProps = { onResult(result: SignInResult): void; @@ -601,6 +655,20 @@ export type SignInResult = { signOut?: () => Promise; }; +// @public +export type TargetRouteMap< + ExternalRoutes extends { + [name: string]: ExternalRouteRef; + }, +> = { + [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef< + infer Params, + any + > + ? RouteRef | SubRouteRef + : never; +}; + // @public export class UnhandledErrorForwarder { static forward(errorApi: ErrorApi, errorContext: ErrorApiErrorContext): void; @@ -632,8 +700,4 @@ export class WebStorage implements StorageApi { // (undocumented) set(key: string, data: T): Promise; } - -// Warnings were encountered during analysis: -// -// src/apis/system/ApiProvider.d.ts:15:5 - (ae-forgotten-export) The symbol "ApiProviderProps" needs to be exported by the entry point index.d.ts ``` diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts index 04bfff028d..8bcd4cb7a5 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts @@ -21,7 +21,7 @@ describe('GithubAuth', () => { const getSession = jest .fn() .mockResolvedValue({ providerInfo: { accessToken: 'access-token' } }); - const githubAuth = new GithubAuth({ getSession } as any); + const githubAuth = new (GithubAuth as any)({ getSession }) as GithubAuth; expect(await githubAuth.getAccessToken()).toBe('access-token'); expect(getSession).toBeCalledTimes(1); diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts index 885e80da7c..3e9c899346 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts @@ -116,6 +116,9 @@ export default class GithubAuth implements OAuthApi, SessionApi { return new GithubAuth(sessionManagerMux); } + /** + * @deprecated will be made private in the future. Use create method instead. + */ constructor(private readonly sessionManager: SessionManager) {} async signIn() { diff --git a/packages/core-app-api/src/apis/implementations/auth/index.ts b/packages/core-app-api/src/apis/implementations/auth/index.ts index bbc9d23ccc..50333f07a0 100644 --- a/packages/core-app-api/src/apis/implementations/auth/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/index.ts @@ -25,3 +25,4 @@ export * from './microsoft'; export * from './onelogin'; export * from './bitbucket'; export * from './atlassian'; +export type { OAuthApiCreateOptions, AuthApiCreateOptions } from './types'; diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 45937a68f8..403e8445d8 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -32,7 +32,11 @@ import { Observable } from '@backstage/types'; import { OAuth2Session } from './types'; import { OAuthApiCreateOptions } from '../types'; -type CreateOptions = OAuthApiCreateOptions & { +/** + * OAuth2 create options. + * @public + */ +export type OAuth2CreateOptions = OAuthApiCreateOptions & { scopeTransform?: (scopes: string[]) => string[]; }; @@ -73,7 +77,7 @@ export default class OAuth2 oauthRequestApi, defaultScopes = [], scopeTransform = x => x, - }: CreateOptions) { + }: OAuth2CreateOptions) { const connector = new DefaultAuthConnector({ discoveryApi, environment, @@ -114,6 +118,9 @@ export default class OAuth2 private readonly sessionManager: SessionManager; private readonly scopeTransform: (scopes: string[]) => string[]; + /** + * @deprecated will be made private in the future. Use create method instead. + */ constructor(options: { sessionManager: SessionManager; scopeTransform: (scopes: string[]) => string[]; diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts index 4ada35846a..fb8b0e6c64 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts @@ -16,6 +16,7 @@ import { ProfileInfo, BackstageIdentity } from '@backstage/core-plugin-api'; +export type { OAuth2CreateOptions } from './OAuth2'; /** * Session information for generic OAuth2 auth. * diff --git a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts index 5f933b9c6e..93b9f6634c 100644 --- a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts @@ -22,7 +22,11 @@ import { } from '@backstage/core-plugin-api'; import { OAuth2 } from '../oauth2'; -type CreateOptions = { +/** + * OneLogin auth provider create options. + * @public + */ +export type OneLoginAuthCreateOptions = { discoveryApi: DiscoveryApi; oauthRequestApi: OAuthRequestApi; environment?: string; @@ -58,7 +62,7 @@ export default class OneLoginAuth { environment = 'development', provider = DEFAULT_PROVIDER, oauthRequestApi, - }: CreateOptions): typeof oneloginAuthApiRef.T { + }: OneLoginAuthCreateOptions): typeof oneloginAuthApiRef.T { return OAuth2.create({ discoveryApi, oauthRequestApi, diff --git a/packages/core-app-api/src/apis/implementations/auth/onelogin/index.ts b/packages/core-app-api/src/apis/implementations/auth/onelogin/index.ts index e1826f17dd..8504d95611 100644 --- a/packages/core-app-api/src/apis/implementations/auth/onelogin/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/onelogin/index.ts @@ -15,3 +15,4 @@ */ export { default as OneLoginAuth } from './OneLoginAuth'; +export type { OneLoginAuthCreateOptions } from './OneLoginAuth'; diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts index 63985979de..c1b70e963d 100644 --- a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -79,6 +79,9 @@ export default class SamlAuth return this.sessionManager.sessionState$(); } + /** + * @deprecated will be made private in the future. Use create method instead. + */ constructor(private readonly sessionManager: SessionManager) {} async signIn() { diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/index.ts b/packages/core-app-api/src/apis/implementations/auth/saml/index.ts index 930e6cb115..f9dc2895af 100644 --- a/packages/core-app-api/src/apis/implementations/auth/saml/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/saml/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { default as SamlAuth } from './SamlAuth'; +export type { SamlSession } from './types'; diff --git a/packages/core-app-api/src/apis/implementations/auth/types.ts b/packages/core-app-api/src/apis/implementations/auth/types.ts index 89343e9e06..825f433cec 100644 --- a/packages/core-app-api/src/apis/implementations/auth/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/types.ts @@ -20,11 +20,19 @@ import { OAuthRequestApi, } from '@backstage/core-plugin-api'; +/** + * Create options for OAuth APIs. + * @public + */ export type OAuthApiCreateOptions = AuthApiCreateOptions & { oauthRequestApi: OAuthRequestApi; defaultScopes?: string[]; }; +/** + * Generic create options for auth APIs. + * @public + */ export type AuthApiCreateOptions = { discoveryApi: DiscoveryApi; environment?: string; diff --git a/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts b/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts index 880f075930..5f56793cae 100644 --- a/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts +++ b/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts @@ -22,7 +22,11 @@ import { AnyApiFactory, } from '@backstage/core-plugin-api'; -type ApiFactoryScope = +/** + * Scope type when registering API factories. + * @public + */ +export type ApiFactoryScope = | 'default' // Default factories registered by core and plugins | 'app' // Factories registered in the app, overriding default ones | 'static'; // APIs that can't be overridden, e.g. config diff --git a/packages/core-app-api/src/apis/system/ApiProvider.tsx b/packages/core-app-api/src/apis/system/ApiProvider.tsx index 73bbdd6f77..c75f883a51 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.tsx @@ -23,7 +23,11 @@ import { createVersionedContext, } from '@backstage/version-bridge'; -type ApiProviderProps = { +/** + * Prop types for the ApiProvider component. + * @public + */ +export type ApiProviderProps = { apis: ApiHolder; children: ReactNode; }; diff --git a/packages/core-app-api/src/apis/system/index.ts b/packages/core-app-api/src/apis/system/index.ts index 23e1a9a4b8..56c42f1e2a 100644 --- a/packages/core-app-api/src/apis/system/index.ts +++ b/packages/core-app-api/src/apis/system/index.ts @@ -15,7 +15,9 @@ */ export { ApiProvider } from './ApiProvider'; +export type { ApiProviderProps } from './ApiProvider'; export { ApiRegistry } from './ApiRegistry'; export { ApiResolver } from './ApiResolver'; export { ApiFactoryRegistry } from './ApiFactoryRegistry'; +export type { ApiFactoryScope } from './ApiFactoryRegistry'; export * from './types'; diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index f2d1976f87..39f7eb3b1b 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -152,23 +152,26 @@ export type AppConfigLoader = () => Promise; /** * Extracts a union of the keys in a map whose value extends the given type + * @public */ -type KeysWithType = { +export type KeysWithType = { [key in keyof Obj]: Obj[key] extends Type ? key : never; }[keyof Obj]; /** * Takes a map Map required values and makes all keys matching Keys optional + * @public */ -type PartialKeys< +export type PartialKeys< Map extends { [name in string]: any }, Keys extends keyof Map, > = Partial> & Required>; /** * Creates a map of target routes with matching parameters based on a map of external routes. + * @public */ -type TargetRouteMap< +export type TargetRouteMap< ExternalRoutes extends { [name: string]: ExternalRouteRef }, > = { [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef< From 10a5e819ddd6f844132c3f3bd09cded5b3861c1c Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Nov 2021 14:36:13 +0100 Subject: [PATCH 89/95] Unexport internal utility types Signed-off-by: Johan Haals --- packages/core-app-api/api-report.md | 36 +++----------------------- packages/core-app-api/src/app/types.ts | 9 +++---- 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 1b9e66cd39..e5135955dd 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -212,6 +212,10 @@ export type AppOptions = { bindRoutes?(context: { bind: AppRouteBinder }): void; }; +// Warning: (ae-forgotten-export) The symbol "PartialKeys" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "TargetRouteMap" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "KeysWithType" needs to be exported by the entry point index.d.ts +// // @public export type AppRouteBinder = < ExternalRoutes extends { @@ -453,16 +457,6 @@ export class GoogleAuth { }: OAuthApiCreateOptions): typeof googleAuthApiRef.T; } -// @public -export type KeysWithType< - Obj extends { - [key in string]: any; - }, - Type, -> = { - [key in keyof Obj]: Obj[key] extends Type ? key : never; -}[keyof Obj]; - // @public export class LocalStorageFeatureFlags implements FeatureFlagsApi { // (undocumented) @@ -601,14 +595,6 @@ export type OneLoginAuthCreateOptions = { }; }; -// @public -export type PartialKeys< - Map extends { - [name in string]: any; - }, - Keys extends keyof Map, -> = Partial> & Required>; - // @public export class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi @@ -655,20 +641,6 @@ export type SignInResult = { signOut?: () => Promise; }; -// @public -export type TargetRouteMap< - ExternalRoutes extends { - [name: string]: ExternalRouteRef; - }, -> = { - [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef< - infer Params, - any - > - ? RouteRef | SubRouteRef - : never; -}; - // @public export class UnhandledErrorForwarder { static forward(errorApi: ErrorApi, errorContext: ErrorApiErrorContext): void; diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 39f7eb3b1b..f2d1976f87 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -152,26 +152,23 @@ export type AppConfigLoader = () => Promise; /** * Extracts a union of the keys in a map whose value extends the given type - * @public */ -export type KeysWithType = { +type KeysWithType = { [key in keyof Obj]: Obj[key] extends Type ? key : never; }[keyof Obj]; /** * Takes a map Map required values and makes all keys matching Keys optional - * @public */ -export type PartialKeys< +type PartialKeys< Map extends { [name in string]: any }, Keys extends keyof Map, > = Partial> & Required>; /** * Creates a map of target routes with matching parameters based on a map of external routes. - * @public */ -export type TargetRouteMap< +type TargetRouteMap< ExternalRoutes extends { [name: string]: ExternalRouteRef }, > = { [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef< From 32bfbafb0fdf1b8be0599c641cea70a295b844f7 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 16 Nov 2021 14:38:47 +0100 Subject: [PATCH 90/95] Add changeset Signed-off-by: Johan Haals --- .changeset/rotten-clouds-kick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rotten-clouds-kick.md diff --git a/.changeset/rotten-clouds-kick.md b/.changeset/rotten-clouds-kick.md new file mode 100644 index 0000000000..2c4772c6e5 --- /dev/null +++ b/.changeset/rotten-clouds-kick.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Start exporting and marking several types as public to address errors in the API report. From 33741318d5f0aaf1695bf02b433a74b8f6118df4 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 16 Nov 2021 19:52:05 +0530 Subject: [PATCH 91/95] Add unity as adopter Signed-off-by: Himanshu Mishra --- ADOPTERS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index f5f5e260c9..86ea27ef39 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -66,3 +66,5 @@ | [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling | | [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | | [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | + +| [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. | From 51029633af38b36571c1ace492231da4366b0501 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 16 Nov 2021 20:32:42 +0530 Subject: [PATCH 92/95] fix formatting of the adopters file Signed-off-by: Himanshu Mishra --- ADOPTERS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 86ea27ef39..d075c25cb6 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -66,5 +66,4 @@ | [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling | | [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | | [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | - | [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. | From 4576f82858210146123d0378a91fb006c14f69c0 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Tue, 16 Nov 2021 08:31:45 -0700 Subject: [PATCH 93/95] Fix sample data in example Cost Insights client Signed-off-by: Tim Hansen --- .changeset/cost-insights-two-crabs-evolve.md | 5 +++++ plugins/cost-insights/src/testUtils/testUtils.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/cost-insights-two-crabs-evolve.md diff --git a/.changeset/cost-insights-two-crabs-evolve.md b/.changeset/cost-insights-two-crabs-evolve.md new file mode 100644 index 0000000000..8a932ea59c --- /dev/null +++ b/.changeset/cost-insights-two-crabs-evolve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Fixed generation of sample data in the example Cost Insights client diff --git a/plugins/cost-insights/src/testUtils/testUtils.ts b/plugins/cost-insights/src/testUtils/testUtils.ts index 2c6f6b5ef2..e0ed1aa933 100644 --- a/plugins/cost-insights/src/testUtils/testUtils.ts +++ b/plugins/cost-insights/src/testUtils/testUtils.ts @@ -61,7 +61,7 @@ export function aggregationFor( const days = DateTime.fromISO(endDate).diff( DateTime.fromISO(inclusiveStartDateOf(duration, inclusiveEndDate)), 'days', - ); + ).days; function nextDelta(): number { const varianceFromBaseline = 0.15; From 59387f759385bc39f3a659a90b966b5dd33f81f5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 16 Nov 2021 17:46:53 +0100 Subject: [PATCH 94/95] changesets: fix reference to missing package Signed-off-by: Patrik Oldsberg --- .changeset/techdocs-orange-cougars-relax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/techdocs-orange-cougars-relax.md b/.changeset/techdocs-orange-cougars-relax.md index 3776c9ea15..376de6a0e5 100644 --- a/.changeset/techdocs-orange-cougars-relax.md +++ b/.changeset/techdocs-orange-cougars-relax.md @@ -1,6 +1,6 @@ --- '@backstage/techdocs-common': patch -'@backstage/techdocs-backend': patch +'@backstage/plugin-techdocs-backend': patch --- Allow amazon web services s3 buckets to pass an server side encryption configuration so they can publish to encrypted buckets From c10bb2234334415fac77022fcd4ae5dca4e4ce67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Baroni?= Date: Tue, 16 Nov 2021 16:15:41 -0300 Subject: [PATCH 95/95] Add PicPay as adopter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Luís Baroni --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index d075c25cb6..cc9e178f82 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -67,3 +67,4 @@ | [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | | [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | | [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. | +| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services throught templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. |