From 58d39061dda10e79d942de8b81b7348cad11313c Mon Sep 17 00:00:00 2001 From: Asher Date: Mon, 13 May 2024 15:46:14 -0800 Subject: [PATCH 1/4] Parse git urls --- .../package.json | 2 + .../processors/DevcontainersProcessor.test.ts | 10 +++ .../src/processors/DevcontainersProcessor.ts | 33 +++++++++- .../src/utils/git.test.ts | 64 +++++++++++++++++++ .../src/utils/git.ts | 12 ++++ .../README.md | 8 ++- .../ExampleDevcontainersComponent.tsx | 2 +- .../src/hooks/useDevcontainers.test.tsx | 7 +- .../src/hooks/useDevcontainers.ts | 21 +----- yarn.lock | 36 ++++++++++- 10 files changed, 164 insertions(+), 31 deletions(-) create mode 100644 plugins/backstage-plugin-devcontainers-backend/src/utils/git.test.ts create mode 100644 plugins/backstage-plugin-devcontainers-backend/src/utils/git.ts diff --git a/plugins/backstage-plugin-devcontainers-backend/package.json b/plugins/backstage-plugin-devcontainers-backend/package.json index 57867586..17fdb4c2 100644 --- a/plugins/backstage-plugin-devcontainers-backend/package.json +++ b/plugins/backstage-plugin-devcontainers-backend/package.json @@ -31,8 +31,10 @@ "@backstage/plugin-catalog-common": "^1.0.21", "@backstage/plugin-catalog-node": "^1.7.2", "@types/express": "*", + "@types/git-url-parse": "^9.0.3", "express": "^4.17.1", "express-promise-router": "^4.1.0", + "git-url-parse": "^14.0.0", "winston": "^3.2.1", "yn": "^4.0.0" }, diff --git a/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.test.ts b/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.test.ts index 7e454f31..2eabf6b7 100644 --- a/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.test.ts +++ b/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.test.ts @@ -196,6 +196,11 @@ describe(`${DevcontainersProcessor.name}`, () => { expect(inputEntity).toEqual(inputSnapshot); const metadataCompare = structuredClone(inputSnapshot.metadata); + metadataCompare.annotations = { + ...(metadataCompare.annotations ?? {}), + vsCodeUrl: + 'vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/example-company/example-repo', + }; delete metadataCompare.tags; expect(outputEntity).toEqual( @@ -226,6 +231,11 @@ describe(`${DevcontainersProcessor.name}`, () => { expect(inputEntity).toEqual(inputSnapshot); const metadataCompare = structuredClone(inputSnapshot.metadata); + metadataCompare.annotations = { + ...(metadataCompare.annotations ?? {}), + vsCodeUrl: + 'vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/example-company/example-repo', + }; delete metadataCompare.tags; expect(outputEntity).toEqual( diff --git a/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.ts b/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.ts index 9a2f2732..ed8f0a00 100644 --- a/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.ts +++ b/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.ts @@ -9,6 +9,7 @@ import { type Config } from '@backstage/config'; import { isError, NotFoundError } from '@backstage/errors'; import { type UrlReader, UrlReaders } from '@backstage/backend-common'; import { type Logger } from 'winston'; +import { parseGitUrl } from '../utils/git'; export const DEFAULT_TAG_NAME = 'devcontainers'; export const PROCESSOR_NAME_PREFIX = 'backstage-plugin-devcontainers-backend'; @@ -89,7 +90,12 @@ export class DevcontainersProcessor implements CatalogProcessor { try { const jsonUrl = await this.findDevcontainerJson(rootUrl, entityLogger); entityLogger.info('Found devcontainer config', { url: jsonUrl }); - return this.addTag(entity, this.options.tagName, entityLogger); + return this.addMetadata( + entity, + this.options.tagName, + location, + entityLogger, + ); } catch (error) { if (!isError(error) || error.name !== 'NotFoundError') { emit( @@ -115,16 +121,25 @@ export class DevcontainersProcessor implements CatalogProcessor { return entity; } - private addTag(entity: Entity, newTag: string, logger: Logger): Entity { + private addMetadata( + entity: Entity, + newTag: string, + location: LocationSpec, + logger: Logger, + ): Entity { if (entity.metadata.tags?.includes(newTag)) { return entity; } - logger.info(`Adding "${newTag}" tag to component`); + logger.info(`Adding VS Code URL and "${newTag}" tag to component`); return { ...entity, metadata: { ...entity.metadata, + annotations: { + ...(entity.metadata.annotations ?? {}), + vsCodeUrl: serializeVsCodeUrl(location.target), + }, tags: [...(entity.metadata?.tags ?? []), newTag], }, }; @@ -185,3 +200,15 @@ export class DevcontainersProcessor implements CatalogProcessor { return url; } } + +/** + * Current implementation for generating the URL will likely need to change as + * we flesh out the backend plugin. For example, it would be nice if there was + * a way to specify the branch instead of always checking out the default. + */ +function serializeVsCodeUrl(repoUrl: string): string { + const cleaners: readonly RegExp[] = [/^url: */]; + const cleanedUrl = cleaners.reduce((str, re) => str.replace(re, ''), repoUrl); + const rootUrl = parseGitUrl(cleanedUrl); + return `vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=${rootUrl}`; +} diff --git a/plugins/backstage-plugin-devcontainers-backend/src/utils/git.test.ts b/plugins/backstage-plugin-devcontainers-backend/src/utils/git.test.ts new file mode 100644 index 00000000..cc8a2450 --- /dev/null +++ b/plugins/backstage-plugin-devcontainers-backend/src/utils/git.test.ts @@ -0,0 +1,64 @@ +import { parseGitUrl } from './git'; + +describe('git', () => { + it('parses urls', () => { + // List of forges and the various ways URLs can be formed. + const forges = { + github: { + saas: 'github.com', + paths: [ + '/tree/foo', + '/blob/foo', + '/tree/foo/dir', + '/blob/foo/dir/file.ts', + ], + }, + gitlab: { + saas: 'gitlab.com', + paths: [ + '/-/tree/foo', + '/-/blob/foo', + '/-/tree/foo/dir?ref_type=heads', + '/-/blob/foo/dir/file.ts?ref_type=heads', + ], + }, + bitbucket: { + saas: 'bitbucket.org', + paths: [ + '/src/hashOrTag', + '/src/hashOrTag?at=foo', + '/src/hashOrTag/dir', + '/src/hashOrTag/dir?at=foo', + '/src/hashOrTag/dir/file.ts', + '/src/hashOrTag/dir/file.ts?at=foo', + ], + }, + }; + + for (const [forge, test] of Object.entries(forges)) { + // These are URLs that point to the root of the repository. To these we + // append the above paths to test that the original root URL is extracted. + const baseUrls = [ + // Most common format. + `https://${test.saas}/coder/backstage-plugins`, + // GitLab lets you have a sub-group. + `https://${test.saas}/coder/group/backstage-plugins`, + // Self-hosted. + `https://${forge}.coder.com/coder/backstage-plugins`, + // Self-hosted at a port. + `https://${forge}.coder.com:9999/coder/backstage-plugins`, + // Self-hosted at base path. + `https://${forge}.coder.com/base/path/coder/backstage-plugins`, + // Self-hosted without the forge anywhere in the domain. + 'https://coder.com/coder/backstage-plugins', + ]; + for (const baseUrl of baseUrls) { + expect(parseGitUrl(baseUrl)).toEqual(baseUrl); + for (const path of test.paths) { + const url = `${baseUrl}${path}`; + expect(parseGitUrl(url)).toEqual(baseUrl); + } + } + } + }); +}); diff --git a/plugins/backstage-plugin-devcontainers-backend/src/utils/git.ts b/plugins/backstage-plugin-devcontainers-backend/src/utils/git.ts new file mode 100644 index 00000000..68a554bd --- /dev/null +++ b/plugins/backstage-plugin-devcontainers-backend/src/utils/git.ts @@ -0,0 +1,12 @@ +import parse from 'git-url-parse'; + +/** + * Given a repository URL, figure out the base repository. + */ +export function parseGitUrl(url: string): String { + const parsed = parse(url); + // Although it seems to have a `host` property, it is not on the types, so we + // will have to reconstruct it. + const host = parsed.resource + (parsed.port ? `:${parsed.port}` : ''); + return `${parsed.protocol}://${host}/${parsed.full_name}`; +} diff --git a/plugins/backstage-plugin-devcontainers-react/README.md b/plugins/backstage-plugin-devcontainers-react/README.md index b35786b4..9857ab94 100644 --- a/plugins/backstage-plugin-devcontainers-react/README.md +++ b/plugins/backstage-plugin-devcontainers-react/README.md @@ -14,11 +14,15 @@ _Note: While this plugin can be used standalone, it has been designed to be a fr ### Standalone features -- Custom hooks for reading your special Dev Container metadata tag inside your repo entities, and providing ready-made links to opening that repo in VS Code +- Custom hooks for reading your special Dev Container metadata tag and VS Code + launch URI inside your repo entities, and exposing that URI for opening the + repo in VS Code ### When combined with the backend plugin -- Provides an end-to-end solution for automatically adding/removing Dev Containers metadata in your Backstage installation, while letting you read them from custom hooks and components +- Provides an end-to-end solution for automatically adding/removing Dev + Containers metadata in your Backstage installation (including tags and the VS + Code launch URI), while letting you read them from custom hooks and components ## Setup diff --git a/plugins/backstage-plugin-devcontainers-react/src/components/ExampleDevcontainersComponent/ExampleDevcontainersComponent.tsx b/plugins/backstage-plugin-devcontainers-react/src/components/ExampleDevcontainersComponent/ExampleDevcontainersComponent.tsx index c9a24846..53e96b80 100644 --- a/plugins/backstage-plugin-devcontainers-react/src/components/ExampleDevcontainersComponent/ExampleDevcontainersComponent.tsx +++ b/plugins/backstage-plugin-devcontainers-react/src/components/ExampleDevcontainersComponent/ExampleDevcontainersComponent.tsx @@ -23,7 +23,7 @@ export const ExampleDevcontainersComponent = () => { return (

- Searched component entity for tag:{' '} + Searched component entity for VS Code URL and tag:{' '} {state.tagName}

diff --git a/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.test.tsx b/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.test.tsx index 14f82354..e9999871 100644 --- a/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.test.tsx +++ b/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.test.tsx @@ -4,7 +4,6 @@ import { useDevcontainers } from './useDevcontainers'; import { type DevcontainersConfig, DevcontainersProvider } from '../plugin'; import { wrapInTestApp } from '@backstage/test-utils'; import { EntityProvider, useEntity } from '@backstage/plugin-catalog-react'; -import { ANNOTATION_SOURCE_LOCATION } from '@backstage/catalog-model'; const mockTagName = 'devcontainers-test'; const mockUrlRoot = 'https://www.github.com/example-company/example-repo'; @@ -17,7 +16,7 @@ const baseEntity: BackstageEntity = { name: 'metadata', tags: [mockTagName, 'other', 'random', 'values'], annotations: { - [ANNOTATION_SOURCE_LOCATION]: `${mockUrlRoot}/tree/main`, + vsCodeUrl: `vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=${mockUrlRoot}`, }, }, }; @@ -61,7 +60,7 @@ describe(`${useDevcontainers.name}`, () => { expect(result2.current.vsCodeUrl).toBe(undefined); }); - it('Does not expose a link when the entity lacks a repo URL', async () => { + it('Does not expose a link when the entity lacks one', async () => { const { result } = await render(mockTagName, { ...baseEntity, metadata: { @@ -73,7 +72,7 @@ describe(`${useDevcontainers.name}`, () => { expect(result.current.vsCodeUrl).toBe(undefined); }); - it('Provides a VS Code-formatted link when the current entity has a designated devcontainers tag', async () => { + it('Exposes the link when the entity has both the tag and link', async () => { const { result } = await render(mockTagName, baseEntity); expect(result.current.vsCodeUrl).toEqual( `vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=${mockUrlRoot}`, diff --git a/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.ts b/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.ts index e1b360c1..0022d01e 100644 --- a/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.ts +++ b/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.ts @@ -1,6 +1,5 @@ import { useDevcontainersConfig } from '../components/DevcontainersProvider'; import { useEntity } from '@backstage/plugin-catalog-react'; -import { ANNOTATION_SOURCE_LOCATION } from '@backstage/catalog-model'; export type UseDevcontainersResult = Readonly< { @@ -38,8 +37,8 @@ export function useDevcontainers(): UseDevcontainersResult { }; } - const repoUrl = entity.metadata.annotations?.[ANNOTATION_SOURCE_LOCATION]; - if (!repoUrl) { + const vsCodeUrl = entity.metadata.annotations?.vsCodeUrl; + if (!vsCodeUrl) { return { tagName, hasUrl: false, @@ -50,20 +49,6 @@ export function useDevcontainers(): UseDevcontainersResult { return { tagName, hasUrl: true, - vsCodeUrl: serializeVsCodeUrl(repoUrl), + vsCodeUrl, }; } - -/** - * Current implementation for generating the URL will likely need to change as - * we flesh out the backend plugin. - * - * It might make more sense to add the direct VSCode link to the entity data - * from the backend plugin via an annotation field, and remove the need for data - * cleaning here in this function - */ -function serializeVsCodeUrl(repoUrl: string): string { - const cleaners: readonly RegExp[] = [/^url: */, /\/tree\/main\/?$/]; - const cleanedUrl = cleaners.reduce((str, re) => str.replace(re, ''), repoUrl); - return `vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=${cleanedUrl}`; -} diff --git a/yarn.lock b/yarn.lock index d1df1176..b13b38c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8419,6 +8419,11 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/git-url-parse@^9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@types/git-url-parse/-/git-url-parse-9.0.3.tgz#7ee022f8fa06ea74148aa28521cbff85915ac09d" + integrity sha512-Wrb8zeghhpKbYuqAOg203g+9YSNlrZWNZYvwxJuDF4dTmerijqpnGbI79yCuPtHSXHPEwv1pAFUB4zsSqn82Og== + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -21913,7 +21918,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -21987,7 +22001,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -22001,6 +22015,13 @@ strip-ansi@5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -23809,7 +23830,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -23827,6 +23848,15 @@ wrap-ansi@^6.0.1: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 013b84511e2ba37a52ab91a873478610fb192e59 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 14 May 2024 09:31:36 -0800 Subject: [PATCH 2/4] Unwrap readme --- plugins/backstage-plugin-devcontainers-react/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/backstage-plugin-devcontainers-react/README.md b/plugins/backstage-plugin-devcontainers-react/README.md index 9857ab94..2e14637c 100644 --- a/plugins/backstage-plugin-devcontainers-react/README.md +++ b/plugins/backstage-plugin-devcontainers-react/README.md @@ -14,15 +14,11 @@ _Note: While this plugin can be used standalone, it has been designed to be a fr ### Standalone features -- Custom hooks for reading your special Dev Container metadata tag and VS Code - launch URI inside your repo entities, and exposing that URI for opening the - repo in VS Code +- Custom hooks for reading your special Dev Container metadata tag and VS Code launch URI inside your repo entities, and exposing that URI for opening the repo in VS Code ### When combined with the backend plugin -- Provides an end-to-end solution for automatically adding/removing Dev - Containers metadata in your Backstage installation (including tags and the VS - Code launch URI), while letting you read them from custom hooks and components +- Provides an end-to-end solution for automatically adding/removing Dev Containers metadata in your Backstage installation (including tags and the VS Code launch URI), while letting you read them from custom hooks and components ## Setup From 0a6747ed52a135f1a52256f3a7a61b23f12778fb Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 14 May 2024 09:45:49 -0800 Subject: [PATCH 3/4] Use type to validate annotation key --- .../backstage-plugin-devcontainers-backend/src/index.ts | 5 ++++- .../src/processors/DevcontainersProcessor.ts | 9 ++++++++- .../src/hooks/useDevcontainers.ts | 8 +++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/plugins/backstage-plugin-devcontainers-backend/src/index.ts b/plugins/backstage-plugin-devcontainers-backend/src/index.ts index 1155207e..47c37ec2 100644 --- a/plugins/backstage-plugin-devcontainers-backend/src/index.ts +++ b/plugins/backstage-plugin-devcontainers-backend/src/index.ts @@ -1,2 +1,5 @@ export * from './service/router'; -export { DevcontainersProcessor } from './processors/DevcontainersProcessor'; +export { + DevcontainersProcessor, + type VsCodeUrlKey, +} from './processors/DevcontainersProcessor'; diff --git a/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.ts b/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.ts index ed8f0a00..a42f5ba5 100644 --- a/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.ts +++ b/plugins/backstage-plugin-devcontainers-backend/src/processors/DevcontainersProcessor.ts @@ -14,6 +14,13 @@ import { parseGitUrl } from '../utils/git'; export const DEFAULT_TAG_NAME = 'devcontainers'; export const PROCESSOR_NAME_PREFIX = 'backstage-plugin-devcontainers-backend'; +const vsCodeUrlKey = 'vsCodeUrl'; + +// We export this type instead of the actual constant so we can validate the +// constant on the frontend at compile-time instead of making the backend plugin +// a run-time dependency, so it can continue to run standalone. +export type VsCodeUrlKey = typeof vsCodeUrlKey; + type ProcessorOptions = Readonly<{ tagName: string; logger: Logger; @@ -138,7 +145,7 @@ export class DevcontainersProcessor implements CatalogProcessor { ...entity.metadata, annotations: { ...(entity.metadata.annotations ?? {}), - vsCodeUrl: serializeVsCodeUrl(location.target), + [vsCodeUrlKey]: serializeVsCodeUrl(location.target), }, tags: [...(entity.metadata?.tags ?? []), newTag], }, diff --git a/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.ts b/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.ts index 0022d01e..67a067b6 100644 --- a/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.ts +++ b/plugins/backstage-plugin-devcontainers-react/src/hooks/useDevcontainers.ts @@ -1,5 +1,11 @@ import { useDevcontainersConfig } from '../components/DevcontainersProvider'; import { useEntity } from '@backstage/plugin-catalog-react'; +import type { VsCodeUrlKey } from '@coder/backstage-plugin-devcontainers-backend'; + +// We avoid importing the actual constant to prevent making the backend plugin a +// run-time dependency, but we can use the type at compile-time to validate the +// string is the same. +const vsCodeUrlKey: VsCodeUrlKey = 'vsCodeUrl'; export type UseDevcontainersResult = Readonly< { @@ -37,7 +43,7 @@ export function useDevcontainers(): UseDevcontainersResult { }; } - const vsCodeUrl = entity.metadata.annotations?.vsCodeUrl; + const vsCodeUrl = entity.metadata.annotations?.[vsCodeUrlKey]; if (!vsCodeUrl) { return { tagName, From 63b34b3cd8641f33eaa269b0467875fbc0cfc37f Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 14 May 2024 09:46:57 -0800 Subject: [PATCH 4/4] Make @types/git-url-parse a dev dependency Accidentally added it as a run-time dependency. --- plugins/backstage-plugin-devcontainers-backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-devcontainers-backend/package.json b/plugins/backstage-plugin-devcontainers-backend/package.json index 17fdb4c2..5fde7234 100644 --- a/plugins/backstage-plugin-devcontainers-backend/package.json +++ b/plugins/backstage-plugin-devcontainers-backend/package.json @@ -31,7 +31,6 @@ "@backstage/plugin-catalog-common": "^1.0.21", "@backstage/plugin-catalog-node": "^1.7.2", "@types/express": "*", - "@types/git-url-parse": "^9.0.3", "express": "^4.17.1", "express-promise-router": "^4.1.0", "git-url-parse": "^14.0.0", @@ -40,6 +39,7 @@ }, "devDependencies": { "@backstage/cli": "^0.25.1", + "@types/git-url-parse": "^9.0.3", "@types/supertest": "^2.0.12", "msw": "^1.0.0", "supertest": "^6.2.4"