diff --git a/workspaces/npm/.changeset/polite-houses-flow.md b/workspaces/npm/.changeset/polite-houses-flow.md
new file mode 100644
index 0000000000..5bb17a12cb
--- /dev/null
+++ b/workspaces/npm/.changeset/polite-houses-flow.md
@@ -0,0 +1,7 @@
+---
+'@backstage-community/plugin-npm-backend': minor
+'@backstage-community/plugin-npm-common': minor
+'@backstage-community/plugin-npm': minor
+---
+
+Added support for custom and private npm registries like GitHub and GitLab via a new backend plugin. Other npm registries that works with the npm cli should work as well.
diff --git a/workspaces/npm/README.md b/workspaces/npm/README.md
index 94044a3fa6..d3bfd2b310 100644
--- a/workspaces/npm/README.md
+++ b/workspaces/npm/README.md
@@ -1,16 +1,324 @@
-# [Backstage](https://backstage.io)
+# npm plugin for Backstage
-This is your newly scaffolded Backstage App, Good Luck!
+A Backstage plugin that shows information and latest releases/versions
+from a npm registry for catalog entities.
-To start the app, run:
+The current version can show two cards and one additional tab for an catalog entity.
-```sh
-yarn install
-yarn dev
+1. The "Npm info card" shows general information like the latest version, description, etc.
+2. The "Npm release overview card" shows the latest tags of an npm package.
+3. The "Npm release tab" shows the version hisory in detail.
+
+## Screenshots
+
+### Npm info card
+
+![Screenshot](docs/npm-info-card.png)
+
+### Npm release overview card
+
+![Screenshot](docs/npm-release-overview-card.png)
+
+### Extended catalog entity overview tab (example)
+
+![Screenshot](docs/catalog-entity-overview-tab.png)
+
+### New catalog entity npm release tab
+
+![Screenshot](docs/catalog-entity-npm-release-tab.png)
+
+## Usage
+
+### Enable npm cards for a catalog entity
+
+To enable the different npm cards you must add the `npm/package` annotation
+with the name of the npm package:
+
+```yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: react
+ annotations:
+ npm/package: react
+```
+
+### Use other npm tag then `latest`
+
+The "npm info" card shows the information of the latest 'stable' npm release
+and use the common `latest` tag by default. This could be changed with `npm/stable-tag`:
+
+```yaml
+# catalog-info.yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: react
+ annotations:
+ npm/package: react
+ npm/stable-tag: latest, stable, next, etc.
```
-To generate knip reports for this app, run:
+### Use a custom registry
+
+To use another npm registry you need to specific a registry name in your
+catalog entity that exists in your `app-config.yaml`.
-```sh
-yarn backstage-repo-tools knip-reports
+```yaml
+# catalog-info.yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: react
+ annotations:
+ npm/package: another-package
+ npm/registry: github
```
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: github
+ url: https://npm.pkg.github.com
+ token: ghp_...
+```
+
+## Installation
+
+To show information from a private package or an alternative npm registry
+you must install also the backend plugin and [configure it](./registries.md).
+
+### Frontend plugin
+
+1. Install the frontend dependency:
+
+ ```sh
+ yarn workspace app add @backstage-community/plugin-npm
+ ```
+
+2. Add cards based on your needs to `packages/app/src/components/catalog/EntityPage.tsx`:
+
+ After all other imports:
+
+ ```tsx
+ import {
+ isNpmAvailable,
+ EntityNpmInfoCard,
+ EntityNpmReleaseOverviewCard,
+ EntityNpmReleaseTableCard,
+ } from '@backstage-community/plugin-npm';
+ ```
+
+3. Add to `const overviewContent` after `EntityAboutCard`:
+
+ ```tsx
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+4. Add to `const serviceEntityPage` and `const websiteEntityPage` after the `/ci-cd` case
+ and to `const defaultEntityPage` between the `/` and `/docs` routecase.
+
+ ```tsx
+
+
+
+ ```
+
+### Alternative: Use the new frontend system (alpha)
+
+For early adaopters of the new frontend system.
+
+Your Backstage frontend app must use that new frontend system which isn't the default at the moment.
+
+1. Install the frontend dependency:
+
+ ```sh
+ yarn workspace app-next add @backstage-community/plugin-npm
+ ```
+
+2. Add the package to your `packages/app[-next]/src/App.tsx`.
+
+ ```tsx
+ import npmPlugin from '@backstage-community/plugin-npm/alpha';
+ ```
+
+ And extend your createApp:
+
+ ```tsx
+ export const app = createApp({
+ features: [
+ catalogPlugin,
+ catalogImportPlugin,
+ userSettingsPlugin,
+ npmPlugin,
+ // ...
+ ],
+ });
+ ```
+
+### Optional: Backend plugin (req. for private packages or alternative registries)
+
+1. Install the backend plugin:
+
+ ```sh
+ yarn workspace backend add @backstage-community/plugin-npm
+ ```
+
+2. Add it to `packages/backend/src/index.ts`:
+
+ ```tsx
+ backend.add(import('@backstage-community/plugin-npm-backend'));
+ ```
+
+3. The backend is only used for catalog entities with a registry by default.
+
+ If no `npm/registry` annotation is defined, the npm plugin loads the
+ information directly from the frontend.
+ (The browser of the user will connect to https://registry.npmjs.com.)
+
+ You can enforce using the backend by defining a default registry:
+
+ ```yaml
+ # app-config.yaml
+ # optional to enforce the frontend to use the backend
+ npm:
+ defaultRegistry: npmjs
+ ```
+
+For more information, please checkout the [Registries](./registries.md) documentation.
+
+### Optional: Test with plugin-example catalog entities
+
+For testing purpose you can import this catalog entities:
+
+```yaml
+# catalog-info.yaml
+catalog:
+ locations:
+ - type: url
+ target: https://github.com/backstage/community-plugins/blob/main/workspaces/npm/examples/entities.yaml
+ rules:
+ - allow: [System, Component]
+```
+
+## Registries
+
+The npm plugin supports custom and private registries starting with v1.2.
+
+### Default Configuration
+
+The plugin loads information by default from https://registry.npmjs.com
+
+This works without any additional configuration in your `app-config.yaml`
+but only for public npm packages.
+
+```yaml
+npm:
+ registries:
+ - name: npmjs
+ url: https://registry.npmjs.com
+```
+
+### Use an auth token for npmjs
+
+To load information from another registry or to load information
+from a private package, you must [install the backend](./install.md).
+
+The catalog entity `npm/registry` annotation must be defined and match
+one of the registries in the `app-config.yaml`:
+
+Example:
+
+```yaml
+# catalog-info.yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: a-component
+ annotations:
+ npm/package: private-package
+ npm/registry: npmjs
+```
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: npmjs
+ url: https://registry.npmjs.com
+ token: ...
+```
+
+The `npm/registry: npmjs` annotation is required to use the npm backend.
+
+Alternativly you can setup a default registry (also for npmjs):
+
+```yaml
+# app-config.yaml
+npm:
+ defaultRegistry: npmjs
+```
+
+### Use an alternative registry
+
+Entity example:
+
+```yaml
+# catalog-info.yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: a-component
+ annotations:
+ npm/package: private-package
+ npm/registry: private-registry
+```
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: private-registry
+ url: https://...
+ token: ...
+```
+
+### Use GitHub npm registry
+
+The GitHub npm registry reqires also a GitHub token for public entries.
+
+You need to create a token at https://github.com/settings/tokens
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: github
+ url: https://npm.pkg.github.com
+ token: ghp_...
+```
+
+### Other npm registries
+
+Other npm registries should work the same way.
+
+Please let us know if we should mention here another registry or
+if you find any issue.
+
+You can create a new [Issues on GitHub](https://github.com/backstage/community-plugins/issues/new?assignees=&labels=bug&projects=&template=1-bug.yaml&title=🐛+Npm%3A+
)
diff --git a/workspaces/npm/app-config.yaml b/workspaces/npm/app-config.yaml
index efa9f5f544..cd88cfdb71 100644
--- a/workspaces/npm/app-config.yaml
+++ b/workspaces/npm/app-config.yaml
@@ -1,9 +1,9 @@
app:
- title: Npm Plugin Example App
+ title: npm plugin example app
baseUrl: http://localhost:3000
organization:
- name: Npm Plugin Example
+ name: npm plugin example
backend:
# Used for enabling authentication, secret is shared by all backend plugins
@@ -78,21 +78,28 @@ catalog:
rules:
- allow: [Component, System, API, Resource, Location]
locations:
- # Local example data, file locations are relative to the backend process, typically `packages/backend`
+ # Plugin itself for techdocs
- type: file
- target: ../../examples/entities.yaml
-
- # Local example template
+ target: ../../catalog-info.yaml
- type: file
- target: ../../examples/template/template.yaml
- rules:
- - allow: [Template]
-
- # Local example organizational data
+ target: ../../plugins/npm/catalog-info.yaml
+ - type: file
+ target: ../../plugins/npm-backend/catalog-info.yaml
+ - type: file
+ target: ../../plugins/npm-common/catalog-info.yaml
+ # Local example data
- type: file
target: ../../examples/org.yaml
rules:
- allow: [User, Group]
+ - type: file
+ target: ../../examples/system.yaml
+ - type: file
+ target: ../../examples/npmjs-examples.yaml
+ - type: file
+ target: ../../examples/github-examples.yaml
+ - type: file
+ target: ../../examples/gitlab-examples.yaml
## Uncomment these lines to add more example data
# - type: url
@@ -104,6 +111,16 @@ catalog:
# rules:
# - allow: [User, Group]
+npm:
+ defaultRegistry: npmjs
+ registries:
+ - name: npmjs
+ url: https://registry.npmjs.com
+ - name: github
+ url: https://npm.pkg.github.com
+ - name: gitlab
+ url: https://gitlab.com/api/v4/packages/npm
+
# see https://backstage.io/docs/permissions/getting-started for more on the permission framework
permission:
# setting this to `false` will disable permissions
diff --git a/workspaces/npm/catalog-info.yaml b/workspaces/npm/catalog-info.yaml
index 5ba616ec3b..58223293bc 100644
--- a/workspaces/npm/catalog-info.yaml
+++ b/workspaces/npm/catalog-info.yaml
@@ -1,10 +1,16 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
- name: backstage-plugin-npm-workspace
- title: 'Backstage Community Npm Plugin'
+ name: backstage-plugin-npm
+ title: npm plugin for Backstage
description: A Backstage plugin that shows meta info and latest versions from a npm registry
+ annotations:
+ backstage.io/techdocs-ref: dir:.
+ links:
+ - url: https://github.com/backstage/community-plugins/tree/main/workspaces/npm
+ title: GitHub
+ icon: sourcecode
spec:
- lifecycle: experimental
+ lifecycle: production
type: backstage-plugin
owner: maintainers
diff --git a/workspaces/npm/plugins/npm/docs/catalog-entity-npm-release-tab.png b/workspaces/npm/docs/catalog-entity-npm-release-tab.png
similarity index 100%
rename from workspaces/npm/plugins/npm/docs/catalog-entity-npm-release-tab.png
rename to workspaces/npm/docs/catalog-entity-npm-release-tab.png
diff --git a/workspaces/npm/plugins/npm/docs/catalog-entity-overview-tab.png b/workspaces/npm/docs/catalog-entity-overview-tab.png
similarity index 100%
rename from workspaces/npm/plugins/npm/docs/catalog-entity-overview-tab.png
rename to workspaces/npm/docs/catalog-entity-overview-tab.png
diff --git a/workspaces/npm/docs/index.md b/workspaces/npm/docs/index.md
new file mode 100644
index 0000000000..c2a29b77aa
--- /dev/null
+++ b/workspaces/npm/docs/index.md
@@ -0,0 +1,27 @@
+# About
+
+The npm plugin for Backstage can show different information from a npm registry in your software catalog.
+
+The current version can show two cards and one additional tab for an catalog entity.
+
+1. The "Npm info card" shows general information like the latest version, description, etc.
+2. The "Npm release overview card" shows the latest tags of an npm package.
+3. The "Npm release tab" shows the version hisory in detail.
+
+## Screenshots
+
+### Npm info card
+
+![Screenshot](https://raw.githubusercontent.com/backstage/community-plugins/main/workspaces/npm/plugins/npm/docs/npm-info-card.png)
+
+### Npm release overview card
+
+![Screenshot](https://raw.githubusercontent.com/backstage/community-plugins/main/workspaces/npm/plugins/npm/docs/npm-release-overview-card.png)
+
+### Extended catalog entity overview tab (example)
+
+![Screenshot](https://raw.githubusercontent.com/backstage/community-plugins/main/workspaces/npm/plugins/npm/docs/catalog-entity-overview-tab.png)
+
+### New catalog entity npm release tab
+
+![Screenshot](https://raw.githubusercontent.com/backstage/community-plugins/main/workspaces/npm/plugins/npm/docs/catalog-entity-npm-release-tab.png)
diff --git a/workspaces/npm/docs/install.md b/workspaces/npm/docs/install.md
new file mode 100644
index 0000000000..1a7b2eb7ee
--- /dev/null
+++ b/workspaces/npm/docs/install.md
@@ -0,0 +1,132 @@
+# Installation
+
+To show information from a private package or an alternative npm registry
+you must install also the backend plugin and [configure it](./registries.md).
+
+## Frontend plugin
+
+1. Install the frontend dependency:
+
+ ```sh
+ yarn workspace app add @backstage-community/plugin-npm
+ ```
+
+2. Add cards based on your needs to `packages/app/src/components/catalog/EntityPage.tsx`:
+
+ After all other imports:
+
+ ```tsx
+ import {
+ isNpmAvailable,
+ EntityNpmInfoCard,
+ EntityNpmReleaseOverviewCard,
+ EntityNpmReleaseTableCard,
+ } from '@backstage-community/plugin-npm';
+ ```
+
+3. Add to `const overviewContent` after `EntityAboutCard`:
+
+ ```tsx
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+4. Add to `const serviceEntityPage` and `const websiteEntityPage` after the `/ci-cd` case
+ and to `const defaultEntityPage` between the `/` and `/docs` routecase.
+
+ ```tsx
+
+
+
+ ```
+
+## Alternative: Use the new frontend system (alpha)
+
+For early adaopters of the new frontend system.
+
+Your Backstage frontend app must use that new frontend system which isn't the default at the moment.
+
+1. Install the frontend dependency:
+
+ ```sh
+ yarn workspace app-next add @backstage-community/plugin-npm
+ ```
+
+2. Add the package to your `packages/app[-next]/src/App.tsx`.
+
+ ```tsx
+ import npmPlugin from '@backstage-community/plugin-npm/alpha';
+ ```
+
+ And extend your createApp:
+
+ ```tsx
+ export const app = createApp({
+ features: [
+ catalogPlugin,
+ catalogImportPlugin,
+ userSettingsPlugin,
+ npmPlugin,
+ // ...
+ ],
+ });
+ ```
+
+## Optional: Backend plugin (req. for private packages or alternative registries)
+
+1. Install the backend plugin:
+
+ ```sh
+ yarn workspace backend add @backstage-community/plugin-npm
+ ```
+
+2. Add it to `packages/backend/src/index.ts`:
+
+ ```tsx
+ backend.add(import('@backstage-community/plugin-npm-backend'));
+ ```
+
+3. The backend is only used for catalog entities with a registry by default.
+
+ If no `npm/registry` annotation is defined, the npm plugin loads the
+ information directly from the frontend.
+ (The browser of the user will connect to https://registry.npmjs.com.)
+
+ You can enforce using the backend by defining a default registry:
+
+ ```yaml
+ # app-config.yaml
+ # optional to enforce the frontend to use the backend
+ npm:
+ defaultRegistry: npmjs
+ ```
+
+For more information, please checkout the [Registries](./registries.md) documentation.
+
+## Optional: Test with plugin-example catalog entities
+
+For testing purpose you can import this catalog entities:
+
+```yaml
+# catalog-info.yaml
+catalog:
+ locations:
+ - type: url
+ target: https://github.com/backstage/community-plugins/blob/main/workspaces/npm/examples/entities.yaml
+ rules:
+ - allow: [System, Component]
+```
diff --git a/workspaces/npm/plugins/npm/docs/npm-info-card.png b/workspaces/npm/docs/npm-info-card.png
similarity index 100%
rename from workspaces/npm/plugins/npm/docs/npm-info-card.png
rename to workspaces/npm/docs/npm-info-card.png
diff --git a/workspaces/npm/plugins/npm/docs/npm-release-overview-card.png b/workspaces/npm/docs/npm-release-overview-card.png
similarity index 100%
rename from workspaces/npm/plugins/npm/docs/npm-release-overview-card.png
rename to workspaces/npm/docs/npm-release-overview-card.png
diff --git a/workspaces/npm/docs/registries.md b/workspaces/npm/docs/registries.md
new file mode 100644
index 0000000000..467a433e23
--- /dev/null
+++ b/workspaces/npm/docs/registries.md
@@ -0,0 +1,120 @@
+# Registries
+
+The npm plugin supports custom and private registries starting with v1.2.
+
+## Default Configuration
+
+The plugin loads information by default from https://registry.npmjs.com
+
+This works without any additional configuration in your `app-config.yaml`
+but only for public npm packages.
+
+```yaml
+npm:
+ registries:
+ - name: npmjs
+ url: https://registry.npmjs.com
+```
+
+## Use an auth token for npmjs
+
+To load information from another registry or to load information
+from a private package, you must [install the backend](./install.md).
+
+The catalog entity `npm/registry` annotation must be defined and match
+one of the registries in the `app-config.yaml`:
+
+Example:
+
+```yaml
+# catalog-info.yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: a-component
+ annotations:
+ npm/package: private-package
+ npm/registry: npmjs
+```
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: npmjs
+ url: https://registry.npmjs.com
+ token: ...
+```
+
+The `npm/registry: npmjs` annotation is required to use the npm backend.
+
+Alternativly you can setup a default registry (also for npmjs):
+
+```yaml
+# app-config.yaml
+npm:
+ defaultRegistry: npmjs
+```
+
+## Use an alternative registry
+
+Entity example:
+
+```yaml
+# catalog-info.yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: a-component
+ annotations:
+ npm/package: private-package
+ npm/registry: private-registry
+```
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: private-registry
+ url: https://...
+ token: ...
+```
+
+## Use GitHub npm registry
+
+The GitHub npm registry requires also a GitHub token for public packages.
+
+You need to create a token at https://github.com/settings/tokens
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: github
+ url: https://npm.pkg.github.com
+ token: ghp_...
+```
+
+## Use GitLab npm registry
+
+The GitLab npm registry might requires a token for private packages.
+
+You need to create a token at https://github.com/settings/tokens
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: gitlab
+ url: https://gitlab.com/api/v4/packages/npm
+ token: ...
+```
+
+## Other npm registries
+
+Other npm registries should work the same way.
+
+Please let us know if we should mention here another registry or
+if you find any issue.
+
+You can create a new [Issue on GitHub](https://github.com/backstage/community-plugins/issues/new?assignees=&labels=bug&projects=&template=1-bug.yaml&title=🐛+Npm%3A+)
diff --git a/workspaces/npm/docs/usage.md b/workspaces/npm/docs/usage.md
new file mode 100644
index 0000000000..16bbe703e8
--- /dev/null
+++ b/workspaces/npm/docs/usage.md
@@ -0,0 +1,58 @@
+# Usage
+
+## Enable npm cards for a catalog entity
+
+To enable the different npm cards you must add the `npm/package` annotation
+with the name of the npm package:
+
+```yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: react
+ annotations:
+ npm/package: react
+```
+
+## Use other npm tag then `latest`
+
+The "npm info" card shows the information of the latest 'stable' npm release
+and use the common `latest` tag by default. This could be changed with `npm/stable-tag`:
+
+```yaml
+# catalog-info.yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: react
+ annotations:
+ npm/package: react
+ npm/stable-tag: latest, stable, next, etc.
+```
+
+## Use a custom registry
+
+To use another npm registry you need to specific a registry name in your
+catalog entity that exists in your `app-config.yaml`.
+
+```yaml
+# catalog-info.yaml
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: react
+ annotations:
+ npm/package: another-package
+ npm/registry: github
+```
+
+```yaml
+# app-config.yaml
+npm:
+ registries:
+ - name: github
+ url: https://npm.pkg.github.com
+ token: ghp_...
+```
+
+For more informations and scenarios see [Registries](./registries.md).
diff --git a/workspaces/npm/examples/github-examples.yaml b/workspaces/npm/examples/github-examples.yaml
new file mode 100644
index 0000000000..464fc41010
--- /dev/null
+++ b/workspaces/npm/examples/github-examples.yaml
@@ -0,0 +1,21 @@
+---
+# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: github-example
+ annotations:
+ npm/registry: github
+ npm/package: '@github-packages-examples/npm-publish'
+ links:
+ - url: https://github.com/github-packages-examples
+ title: GitHub org
+ - url: https://github.com/github-packages-examples/npm-publish
+ title: GitHub repo
+ - url: https://github.com/github-packages-examples/npm-publish/pkgs/npm/npm-publish
+ title: GitHub npm packages
+spec:
+ type: library
+ lifecycle: production
+ owner: guests
+ system: backstage
diff --git a/workspaces/npm/examples/gitlab-examples.yaml b/workspaces/npm/examples/gitlab-examples.yaml
new file mode 100644
index 0000000000..ad770b64dc
--- /dev/null
+++ b/workspaces/npm/examples/gitlab-examples.yaml
@@ -0,0 +1,21 @@
+---
+# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: gitlab-example
+ annotations:
+ npm/registry: gitlab
+ npm/package: '@gitlab-examples/semantic-release-npm'
+ links:
+ - url: https://gitlab.com/gitlab-examples
+ title: GitLab org
+ - url: https://gitlab.com/gitlab-examples/semantic-release-npm
+ title: GitLab repo
+ - url: https://gitlab.com/gitlab-examples/semantic-release-npm/-/packages
+ title: GitLab npm packages
+spec:
+ type: library
+ lifecycle: production
+ owner: guests
+ system: backstage
diff --git a/workspaces/npm/examples/entities.yaml b/workspaces/npm/examples/npmjs-examples.yaml
similarity index 94%
rename from workspaces/npm/examples/entities.yaml
rename to workspaces/npm/examples/npmjs-examples.yaml
index 773b139043..3325199344 100644
--- a/workspaces/npm/examples/entities.yaml
+++ b/workspaces/npm/examples/npmjs-examples.yaml
@@ -1,12 +1,4 @@
---
-# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system
-apiVersion: backstage.io/v1alpha1
-kind: System
-metadata:
- name: backstage
-spec:
- owner: guests
----
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component
apiVersion: backstage.io/v1alpha1
kind: Component
@@ -14,6 +6,7 @@ metadata:
name: backstage-catalog-model
annotations:
npm/package: '@backstage/catalog-model'
+ npm/show-tags: latest, next
spec:
type: library
lifecycle: production
@@ -27,6 +20,7 @@ metadata:
name: backstage-catalog-client
annotations:
npm/package: '@backstage/catalog-client'
+ npm/show-tags: latest, next
spec:
type: library
lifecycle: production
@@ -42,6 +36,7 @@ metadata:
name: backstage-plugin-catalog-common
annotations:
npm/package: '@backstage/plugin-catalog-common'
+ npm/show-tags: latest, next
spec:
type: library
lifecycle: production
@@ -57,6 +52,7 @@ metadata:
name: backstage-plugin-catalog-node
annotations:
npm/package: '@backstage/plugin-catalog-node'
+ npm/show-tags: latest, next
spec:
type: library
lifecycle: production
@@ -72,6 +68,7 @@ metadata:
name: backstage-plugin-catalog-react
annotations:
npm/package: '@backstage/plugin-catalog-react'
+ npm/show-tags: latest, next
spec:
type: library
lifecycle: production
@@ -89,6 +86,7 @@ metadata:
name: backstage-plugin-catalog
annotations:
npm/package: '@backstage/plugin-catalog'
+ npm/show-tags: latest, next
spec:
type: website
lifecycle: production
diff --git a/workspaces/npm/examples/system.yaml b/workspaces/npm/examples/system.yaml
new file mode 100644
index 0000000000..f73504455e
--- /dev/null
+++ b/workspaces/npm/examples/system.yaml
@@ -0,0 +1,8 @@
+---
+# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system
+apiVersion: backstage.io/v1alpha1
+kind: System
+metadata:
+ name: backstage
+spec:
+ owner: guests
diff --git a/workspaces/npm/examples/template/content/catalog-info.yaml b/workspaces/npm/examples/template/content/catalog-info.yaml
deleted file mode 100644
index d4ccca42ef..0000000000
--- a/workspaces/npm/examples/template/content/catalog-info.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: backstage.io/v1alpha1
-kind: Component
-metadata:
- name: ${{ values.name | dump }}
-spec:
- type: service
- owner: user:guest
- lifecycle: experimental
diff --git a/workspaces/npm/examples/template/content/package.json b/workspaces/npm/examples/template/content/package.json
deleted file mode 100644
index 86f968a73b..0000000000
--- a/workspaces/npm/examples/template/content/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "${{ values.name }}",
- "private": true,
- "dependencies": {}
-}
diff --git a/workspaces/npm/examples/template/template.yaml b/workspaces/npm/examples/template/template.yaml
deleted file mode 100644
index 33f262b49c..0000000000
--- a/workspaces/npm/examples/template/template.yaml
+++ /dev/null
@@ -1,74 +0,0 @@
-apiVersion: scaffolder.backstage.io/v1beta3
-# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
-kind: Template
-metadata:
- name: example-nodejs-template
- title: Example Node.js Template
- description: An example template for the scaffolder that creates a simple Node.js service
-spec:
- owner: user:guest
- type: service
-
- # These parameters are used to generate the input form in the frontend, and are
- # used to gather input data for the execution of the template.
- parameters:
- - title: Fill in some steps
- required:
- - name
- properties:
- name:
- title: Name
- type: string
- description: Unique name of the component
- ui:autofocus: true
- ui:options:
- rows: 5
- - title: Choose a location
- required:
- - repoUrl
- properties:
- repoUrl:
- title: Repository Location
- type: string
- ui:field: RepoUrlPicker
- ui:options:
- allowedHosts:
- - github.com
-
- # These steps are executed in the scaffolder backend, using data that we gathered
- # via the parameters above.
- steps:
- # Each step executes an action, in this case one templates files into the working directory.
- - id: fetch-base
- name: Fetch Base
- action: fetch:template
- input:
- url: ./content
- values:
- name: ${{ parameters.name }}
-
- # This step publishes the contents of the working directory to GitHub.
- - id: publish
- name: Publish
- action: publish:github
- input:
- allowedHosts: ['github.com']
- description: This is ${{ parameters.name }}
- repoUrl: ${{ parameters.repoUrl }}
-
- # The final step is to register our new component in the catalog.
- - id: register
- name: Register
- action: catalog:register
- input:
- repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
- catalogInfoPath: '/catalog-info.yaml'
-
- # Outputs are displayed to the user after a successful execution of the template.
- output:
- links:
- - title: Repository
- url: ${{ steps['publish'].output.remoteUrl }}
- - title: Open in catalog
- icon: catalog
- entityRef: ${{ steps['register'].output.entityRef }}
diff --git a/workspaces/npm/mkdocs.yaml b/workspaces/npm/mkdocs.yaml
new file mode 100644
index 0000000000..8ac37b0737
--- /dev/null
+++ b/workspaces/npm/mkdocs.yaml
@@ -0,0 +1,13 @@
+site_name: npm plugin for Backstage
+site_description: A Backstage plugin that shows meta info and latest versions from a npm registry
+repo_url: https://github.com/backstage/community-plugins
+edit_uri: edit/main/workspaces/npm/docs
+
+plugins:
+ - techdocs-core
+
+nav:
+ - About: index.md
+ - Installation: install.md
+ - Usage: usage.md
+ - Registries: registries.md
diff --git a/workspaces/npm/packages/app-next/knip-report.md b/workspaces/npm/packages/app-next/knip-report.md
new file mode 100644
index 0000000000..d3589c972e
--- /dev/null
+++ b/workspaces/npm/packages/app-next/knip-report.md
@@ -0,0 +1,34 @@
+# Knip report
+
+## Unused dependencies (19)
+
+| Name | Location | Severity |
+| :------------------------------- | :----------- | :------- |
+| @backstage/plugin-catalog-common | package.json | error |
+| @backstage-community/plugin-npm | package.json | error |
+| @backstage/plugin-catalog-graph | package.json | error |
+| @backstage/plugin-catalog-react | package.json | error |
+| @backstage/integration-react | package.json | error |
+| @backstage/frontend-app-api | package.json | error |
+| @backstage/core-compat-api | package.json | error |
+| @backstage/core-components | package.json | error |
+| @backstage/core-plugin-api | package.json | error |
+| @backstage/plugin-api-docs | package.json | error |
+| @backstage/catalog-model | package.json | error |
+| @backstage/app-defaults | package.json | error |
+| @backstage/core-app-api | package.json | error |
+| @backstage/plugin-org | package.json | error |
+| @material-ui/icons | package.json | error |
+| @material-ui/core | package.json | error |
+| styled-components | package.json | error |
+| react-router-dom | package.json | error |
+| react-use | package.json | error |
+
+## Unused devDependencies (4)
+
+| Name | Location | Severity |
+| :-------------------------- | :----------- | :------- |
+| @testing-library/user-event | package.json | error |
+| @testing-library/react | package.json | error |
+| @testing-library/dom | package.json | error |
+| @playwright/test | package.json | error |
diff --git a/workspaces/npm/packages/app/e2e-tests/app.test.ts b/workspaces/npm/packages/app/e2e-tests/app.test.ts
index 839ff883de..c0d4c2eb16 100644
--- a/workspaces/npm/packages/app/e2e-tests/app.test.ts
+++ b/workspaces/npm/packages/app/e2e-tests/app.test.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Backstage Authors
+ * Copyright 2024 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.
diff --git a/workspaces/npm/packages/backend/knip-report.md b/workspaces/npm/packages/backend/knip-report.md
index 4b32479bad..aafbfad657 100644
--- a/workspaces/npm/packages/backend/knip-report.md
+++ b/workspaces/npm/packages/backend/knip-report.md
@@ -1,10 +1,11 @@
# Knip report
-## Unused dependencies (11)
+## Unused dependencies (12)
| Name | Location | Severity |
| :---------------------------------------------------- | :----------- | :------- |
| @backstage/plugin-auth-backend-module-github-provider | package.json | error |
+| @backstage-community/plugin-npm-backend | package.json | error |
| @backstage/plugin-search-backend-node | package.json | error |
| @backstage/plugin-permission-common | package.json | error |
| @backstage/plugin-permission-node | package.json | error |
diff --git a/workspaces/npm/packages/backend/package.json b/workspaces/npm/packages/backend/package.json
index b64a38ce01..ca9930ff0b 100644
--- a/workspaces/npm/packages/backend/package.json
+++ b/workspaces/npm/packages/backend/package.json
@@ -21,6 +21,7 @@
"build-image": "docker build ../.. -f Dockerfile --tag backstage"
},
"dependencies": {
+ "@backstage-community/plugin-npm-backend": "workspace:^",
"@backstage/backend-defaults": "^0.6.2",
"@backstage/config": "^1.3.1",
"@backstage/plugin-app-backend": "^0.4.3",
diff --git a/workspaces/npm/packages/backend/src/index.ts b/workspaces/npm/packages/backend/src/index.ts
index b8f532f3ea..a9b9899984 100644
--- a/workspaces/npm/packages/backend/src/index.ts
+++ b/workspaces/npm/packages/backend/src/index.ts
@@ -56,4 +56,5 @@ backend.add(import('@backstage/plugin-search-backend-module-pg/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
+backend.add(import('@backstage-community/plugin-npm-backend'));
backend.start();
diff --git a/workspaces/npm/plugins/npm-backend/.eslintrc.js b/workspaces/npm/plugins/npm-backend/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/workspaces/npm/plugins/npm-backend/README.md b/workspaces/npm/plugins/npm-backend/README.md
new file mode 120000
index 0000000000..fe84005413
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/README.md
@@ -0,0 +1 @@
+../../README.md
\ No newline at end of file
diff --git a/workspaces/npm/plugins/npm-backend/catalog-info.yaml b/workspaces/npm/plugins/npm-backend/catalog-info.yaml
new file mode 100644
index 0000000000..f071950ee5
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/catalog-info.yaml
@@ -0,0 +1,18 @@
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: backstage-plugin-npm-backend
+ title: '@backstage-community/plugin-npm-backend'
+ description: A Backstage plugin that shows meta info and latest versions from a npm registry
+ annotations:
+ backstage.io/techdocs-entity: component:default/backstage-plugin-npm-workspace
+ npm/package: '@backstage-community/plugin-npm-backend'
+ links:
+ - url: https://github.com/backstage/community-plugins/tree/main/workspaces/npm/plugins/npm-backend
+ title: GitHub
+ icon: sourcecode
+spec:
+ lifecycle: production
+ type: backstage-backend-plugin
+ owner: maintainers
+ subcomponentOf: backstage-plugin-npm
diff --git a/workspaces/npm/plugins/npm-backend/config.d.ts b/workspaces/npm/plugins/npm-backend/config.d.ts
new file mode 100644
index 0000000000..9a617aeed8
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/config.d.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 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 interface Config {
+ npm?: {
+ /**
+ * Use another default registry. This automatically enforce usage of the backend.
+ *
+ * @visibility frontend
+ */
+ defaultRegistry?: string;
+ /**
+ * List of registries that can be used to fetch packages from
+ */
+ registries?: {
+ /**
+ * Registry name
+ */
+ name?: string;
+ /**
+ * Registry base url
+ */
+ url?: string;
+ /**
+ * Registry auth token
+ * @visibility secret
+ */
+ token?: string;
+ }[];
+ };
+}
diff --git a/workspaces/npm/plugins/npm-backend/dev/index.ts b/workspaces/npm/plugins/npm-backend/dev/index.ts
new file mode 100644
index 0000000000..fef47a9589
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/dev/index.ts
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 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 { createBackend } from '@backstage/backend-defaults';
+import { mockServices } from '@backstage/backend-test-utils';
+import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
+
+// TEMPLATE NOTE:
+// This is the development setup for your plugin that wires up a
+// minimal backend that can use both real and mocked plugins and services.
+//
+// Start up the backend by running `yarn start` in the package directory.
+// Once it's up and running, try out the following requests:
+//
+// Create a new todo item, standalone or for the sample component:
+//
+// curl http://localhost:7007/api/npm/todos -H 'Content-Type: application/json' -d '{"title": "My Todo"}'
+// curl http://localhost:7007/api/npm/todos -H 'Content-Type: application/json' -d '{"title": "My Todo", "entityRef": "component:default/sample"}'
+//
+// List TODOs:
+//
+// curl http://localhost:7007/api/npm/todos
+//
+// Explicitly make an unauthenticated request, or with service auth:
+//
+// curl http://localhost:7007/api/npm/todos -H 'Authorization: Bearer mock-none-token'
+// curl http://localhost:7007/api/npm/todos -H 'Authorization: Bearer mock-service-token'
+
+const backend = createBackend();
+
+// TEMPLATE NOTE:
+// Mocking the auth and httpAuth service allows you to call your plugin API without
+// having to authenticate.
+//
+// If you want to use real auth, you can install the following instead:
+// backend.add(import('@backstage/plugin-auth-backend'));
+// backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
+backend.add(mockServices.auth.factory());
+backend.add(mockServices.httpAuth.factory());
+
+// TEMPLATE NOTE:
+// Rather than using a real catalog you can use a mock with a fixed set of entities.
+backend.add(
+ catalogServiceMock.factory({
+ entities: [
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'sample',
+ title: 'Sample Component',
+ },
+ spec: {
+ type: 'service',
+ },
+ },
+ ],
+ }),
+);
+
+backend.add(import('../src'));
+
+backend.start();
diff --git a/workspaces/npm/plugins/npm-backend/docs b/workspaces/npm/plugins/npm-backend/docs
new file mode 120000
index 0000000000..92a7f82538
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/docs
@@ -0,0 +1 @@
+../../docs
\ No newline at end of file
diff --git a/workspaces/npm/plugins/npm-backend/knip-report.md b/workspaces/npm/plugins/npm-backend/knip-report.md
new file mode 100644
index 0000000000..e67e858ddf
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/knip-report.md
@@ -0,0 +1,15 @@
+# Knip report
+
+## Unused dependencies (2)
+
+| Name | Location | Severity |
+| :------------------------------------- | :----------- | :------- |
+| @backstage-community/plugin-npm-common | package.json | error |
+| @backstage/catalog-client | package.json | error |
+
+## Unused devDependencies (2)
+
+| Name | Location | Severity |
+| :--------------- | :----------- | :------- |
+| @types/supertest | package.json | error |
+| supertest | package.json | error |
diff --git a/workspaces/npm/plugins/npm-backend/package.json b/workspaces/npm/plugins/npm-backend/package.json
new file mode 100644
index 0000000000..44f4bb2f76
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/package.json
@@ -0,0 +1,71 @@
+{
+ "name": "@backstage-community/plugin-npm-backend",
+ "version": "1.1.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "types": "dist/index.d.ts"
+ },
+ "backstage": {
+ "role": "backend-plugin",
+ "supported-versions": "1.28.0",
+ "pluginId": "npm",
+ "pluginPackage": "@backstage-community/plugin-npm-backend",
+ "pluginPackages": [
+ "@backstage-community/plugin-npm",
+ "@backstage-community/plugin-npm-backend",
+ "@backstage-community/plugin-npm-common"
+ ]
+ },
+ "scripts": {
+ "start": "backstage-cli package start",
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage-community/plugin-npm-common": "workspace:^",
+ "@backstage/backend-defaults": "^0.5.1",
+ "@backstage/backend-plugin-api": "^1.0.1",
+ "@backstage/catalog-client": "^1.7.1",
+ "@backstage/errors": "^1.2.4",
+ "@backstage/plugin-catalog-node": "^1.13.1",
+ "express": "^4.17.1",
+ "express-promise-router": "^4.1.0"
+ },
+ "devDependencies": {
+ "@backstage/backend-test-utils": "^1.0.1",
+ "@backstage/cli": "^0.28.0",
+ "@types/express": "*",
+ "@types/supertest": "^2.0.12",
+ "supertest": "^6.2.4"
+ },
+ "files": [
+ "config.d.ts",
+ "dist"
+ ],
+ "configSchema": "config.d.ts",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/community-plugins",
+ "directory": "workspaces/npm/plugins/npm-backend"
+ },
+ "keywords": [
+ "backstage",
+ "plugin",
+ "npm"
+ ],
+ "homepage": "https://github.com/backstage/community-plugins/tree/main/workspaces/npm/plugins/npm-backend",
+ "bugs": "https://github.com/backstage/community-plugins/issues",
+ "maintainers": [
+ "jerolimov",
+ "karthikjeeyar"
+ ],
+ "author": "Christoph Jerolimov"
+}
diff --git a/workspaces/npm/plugins/npm-backend/report.api.md b/workspaces/npm/plugins/npm-backend/report.api.md
new file mode 100644
index 0000000000..c5a1472325
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/report.api.md
@@ -0,0 +1,13 @@
+## API Report File for "@backstage-community/plugin-npm-backend"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { BackendFeature } from '@backstage/backend-plugin-api';
+
+// @public
+const npmPlugin: BackendFeature;
+export default npmPlugin;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/workspaces/npm/plugins/npm-backend/src/index.ts b/workspaces/npm/plugins/npm-backend/src/index.ts
new file mode 100644
index 0000000000..152f6579df
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/src/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 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 { npmPlugin as default } from './plugin';
diff --git a/workspaces/npm/plugins/npm-backend/src/plugin.ts b/workspaces/npm/plugins/npm-backend/src/plugin.ts
new file mode 100644
index 0000000000..eb0655cfc0
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/src/plugin.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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 {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
+
+import { createRouter } from './router';
+import { NpmRegistryServiceImpl } from './services/NpmRegistryServiceImpl';
+
+/**
+ * npmPlugin backend plugin
+ *
+ * @public
+ */
+export const npmPlugin = createBackendPlugin({
+ pluginId: 'npm',
+ register(env) {
+ env.registerInit({
+ deps: {
+ logger: coreServices.logger,
+ auth: coreServices.auth,
+ config: coreServices.rootConfig,
+ httpAuth: coreServices.httpAuth,
+ httpRouter: coreServices.httpRouter,
+ catalog: catalogServiceRef,
+ },
+ async init({ logger, auth, config, httpAuth, httpRouter, catalog }) {
+ const npmRegistryService = new NpmRegistryServiceImpl({
+ logger,
+ auth,
+ config,
+ catalog,
+ });
+
+ httpRouter.use(
+ await createRouter({
+ httpAuth,
+ npmRegistryService,
+ }),
+ );
+ },
+ });
+ },
+});
diff --git a/workspaces/npm/plugins/npm-backend/src/router.ts b/workspaces/npm/plugins/npm-backend/src/router.ts
new file mode 100644
index 0000000000..96161546f4
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/src/router.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 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 { HttpAuthService } from '@backstage/backend-plugin-api';
+import { ResponseError } from '@backstage/errors';
+import express from 'express';
+import Router from 'express-promise-router';
+import { NpmRegistryService } from './services';
+
+export async function createRouter({
+ httpAuth,
+ npmRegistryService,
+}: {
+ httpAuth: HttpAuthService;
+ npmRegistryService: NpmRegistryService;
+}): Promise {
+ const router = Router();
+ router.use(express.json());
+
+ router.get('/:entityRef/package-info', async (req, res) => {
+ const entityRef = req.params.entityRef;
+ const credentials = await httpAuth.credentials(req, { allow: ['user'] });
+ try {
+ const packageInfo = await npmRegistryService.getPackageInfo(entityRef, {
+ credentials,
+ });
+ res.json(packageInfo);
+ } catch (error) {
+ if (error instanceof ResponseError) {
+ res.status(error.statusCode).json(error.response);
+ } else {
+ throw error;
+ }
+ }
+ });
+
+ return router;
+}
diff --git a/workspaces/npm/plugins/npm-backend/src/services/NpmRegistryService.ts b/workspaces/npm/plugins/npm-backend/src/services/NpmRegistryService.ts
new file mode 100644
index 0000000000..efc274cda4
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/src/services/NpmRegistryService.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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 {
+ BackstageCredentials,
+ BackstageUserPrincipal,
+} from '@backstage/backend-plugin-api';
+
+import { NpmRegistryPackageInfo } from '@backstage-community/plugin-npm-common';
+
+export interface NpmRegistryService {
+ getPackageInfo(
+ entityRef: string,
+ options: {
+ credentials: BackstageCredentials;
+ },
+ ): Promise;
+}
diff --git a/workspaces/npm/plugins/npm-backend/src/services/NpmRegistryServiceImpl.ts b/workspaces/npm/plugins/npm-backend/src/services/NpmRegistryServiceImpl.ts
new file mode 100644
index 0000000000..cbb6f08ec1
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/src/services/NpmRegistryServiceImpl.ts
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2024 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 {
+ AuthService,
+ BackstageCredentials,
+ BackstageUserPrincipal,
+ LoggerService,
+ RootConfigService,
+} from '@backstage/backend-plugin-api';
+import { NotFoundError } from '@backstage/errors';
+import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
+
+import {
+ NpmAnnotation,
+ NpmRegistryClient,
+ NpmRegistryPackageInfo,
+} from '@backstage-community/plugin-npm-common';
+
+import { NpmRegistryService } from './NpmRegistryService';
+import { Config } from '../../config';
+
+export type Options = {
+ logger: LoggerService;
+ auth: AuthService;
+ config: RootConfigService;
+ // TODO update CatalogService
+ catalog: typeof catalogServiceRef.T;
+};
+
+export class NpmRegistryServiceImpl implements NpmRegistryService {
+ private readonly logger: LoggerService;
+ private readonly auth: AuthService;
+ private readonly config: RootConfigService;
+ private readonly catalog: typeof catalogServiceRef.T;
+
+ constructor(options: Options) {
+ this.logger = options.logger;
+ this.auth = options.auth;
+ this.config = options.config;
+ this.catalog = options.catalog;
+ }
+
+ async getPackageInfo(
+ entityRef: string,
+ options: { credentials: BackstageCredentials },
+ ): Promise {
+ const { token } = await this.auth.getPluginRequestToken({
+ onBehalfOf: options.credentials,
+ targetPluginId: 'catalog',
+ });
+ const entity = await this.catalog.getEntityByRef(entityRef, {
+ token,
+ });
+ if (!entity) {
+ throw new NotFoundError(`No entity found for ref '${entityRef}'`);
+ }
+
+ const config = this.config.getOptional('npm');
+ const defaultRegistry = config?.defaultRegistry;
+
+ const packageName =
+ entity.metadata.annotations?.[NpmAnnotation.PACKAGE_NAME];
+ const registryName =
+ entity.metadata.annotations?.[NpmAnnotation.REGISTRY_NAME] ||
+ defaultRegistry;
+ if (!packageName) {
+ throw new NotFoundError(
+ `Npm package name not defined at entity '${entityRef}'`,
+ );
+ }
+
+ const registry = config?.registries?.find(r => r.name === registryName);
+ if (!registry && registryName !== 'default' && registryName !== 'npmjs') {
+ throw new NotFoundError(
+ `Npm registry config '${registryName}' not found as defined in entity '${entityRef}'`,
+ );
+ }
+
+ this.logger.info('Will use npmjs package info:', {
+ entityRef,
+ packageName,
+ registryUrl: registry?.url,
+ registryToken: registry?.token ? 'yes' : 'no',
+ });
+
+ const client = new NpmRegistryClient({
+ // TODO pass apiFetch.fetch
+ baseUrl: registry?.url,
+ token: registry?.token,
+ });
+ const packageInfo = await client.getPackageInfo(packageName);
+ return packageInfo;
+ }
+}
diff --git a/workspaces/npm/plugins/npm-backend/src/services/index.ts b/workspaces/npm/plugins/npm-backend/src/services/index.ts
new file mode 100644
index 0000000000..d5b8520582
--- /dev/null
+++ b/workspaces/npm/plugins/npm-backend/src/services/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export * from './NpmRegistryService';
+export * from './NpmRegistryServiceImpl';
diff --git a/workspaces/npm/examples/template/content/index.js b/workspaces/npm/plugins/npm-backend/src/setupTests.ts
similarity index 92%
rename from workspaces/npm/examples/template/content/index.js
rename to workspaces/npm/plugins/npm-backend/src/setupTests.ts
index 29cca731de..c7ce5c0988 100644
--- a/workspaces/npm/examples/template/content/index.js
+++ b/workspaces/npm/plugins/npm-backend/src/setupTests.ts
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-console.log('Hello from ${{ values.name }}!');
+export {};
diff --git a/workspaces/npm/plugins/npm-common/.eslintrc.js b/workspaces/npm/plugins/npm-common/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/workspaces/npm/plugins/npm-common/README.md b/workspaces/npm/plugins/npm-common/README.md
new file mode 120000
index 0000000000..fe84005413
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/README.md
@@ -0,0 +1 @@
+../../README.md
\ No newline at end of file
diff --git a/workspaces/npm/plugins/npm-common/catalog-info.yaml b/workspaces/npm/plugins/npm-common/catalog-info.yaml
new file mode 100644
index 0000000000..3a5d296460
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/catalog-info.yaml
@@ -0,0 +1,18 @@
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: backstage-plugin-npm-common
+ title: '@backstage-community/plugin-npm-common'
+ description: A Backstage plugin that shows meta info and latest versions from a npm registry
+ annotations:
+ backstage.io/techdocs-entity: component:default/backstage-plugin-npm-workspace
+ npm/package: '@backstage-community/plugin-npm-common'
+ links:
+ - url: https://github.com/backstage/community-plugins/tree/main/workspaces/npm/plugins/npm-common
+ title: GitHub
+ icon: sourcecode
+spec:
+ lifecycle: production
+ type: backstage-common-library
+ owner: maintainers
+ subcomponentOf: backstage-plugin-npm
diff --git a/workspaces/npm/plugins/npm-common/docs b/workspaces/npm/plugins/npm-common/docs
new file mode 120000
index 0000000000..92a7f82538
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/docs
@@ -0,0 +1 @@
+../../docs
\ No newline at end of file
diff --git a/workspaces/npm/plugins/npm-common/knip-report.md b/workspaces/npm/plugins/npm-common/knip-report.md
new file mode 100644
index 0000000000..28921c328d
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/knip-report.md
@@ -0,0 +1 @@
+# Knip report
diff --git a/workspaces/npm/plugins/npm-common/package.json b/workspaces/npm/plugins/npm-common/package.json
new file mode 100644
index 0000000000..6ce0ee2053
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "@backstage-community/plugin-npm-common",
+ "description": "Common functionalities for the npm plugin",
+ "version": "1.1.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "module": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/community-plugins",
+ "directory": "workspaces/npm/plugins/npm-common"
+ },
+ "backstage": {
+ "role": "common-library",
+ "pluginId": "npm",
+ "pluginPackages": [
+ "@backstage-community/plugin-npm",
+ "@backstage-community/plugin-npm-backend",
+ "@backstage-community/plugin-npm-common"
+ ]
+ },
+ "sideEffects": false,
+ "scripts": {
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage/catalog-model": "^1.7.0",
+ "@backstage/errors": "^1.2.4"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.28.0"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/workspaces/npm/plugins/npm-common/report.api.md b/workspaces/npm/plugins/npm-common/report.api.md
new file mode 100644
index 0000000000..2b5b4278ff
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/report.api.md
@@ -0,0 +1,104 @@
+## API Report File for "@backstage-community/plugin-npm-common"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { Entity } from '@backstage/catalog-model';
+
+// @public
+export const isNpmAvailable: (entity: Entity) => boolean;
+
+// @public (undocumented)
+export enum NpmAnnotation {
+ // (undocumented)
+ PACKAGE_NAME = 'npm/package',
+ // (undocumented)
+ REGISTRY_NAME = 'npm/registry',
+ // (undocumented)
+ SHOW_TAGS = 'npm/show-tags',
+ // (undocumented)
+ STABLE_TAG = 'npm/stable-tag',
+}
+
+// @public (undocumented)
+export class NpmRegistryClient {
+ constructor(options: {
+ fetch?: typeof fetch;
+ baseUrl?: string;
+ token?: string;
+ });
+ // (undocumented)
+ getPackageInfo(packageName: string): Promise;
+}
+
+// @public
+export interface NpmRegistryPackageInfo {
+ // (undocumented)
+ 'dist-tags': {
+ [tag: string]: string;
+ };
+ // (undocumented)
+ _attachments?: {
+ [filename: string]: {
+ content_type: string;
+ data: string;
+ };
+ };
+ // (undocumented)
+ author?:
+ | {
+ name: string;
+ email: string;
+ url: string;
+ }
+ | string;
+ // (undocumented)
+ bugs?: {
+ url?: string;
+ };
+ // (undocumented)
+ description?: string;
+ // (undocumented)
+ homepage?: string;
+ // (undocumented)
+ _id: string;
+ // (undocumented)
+ keywords?: string[];
+ // (undocumented)
+ license?: string;
+ // (undocumented)
+ name: string;
+ // (undocumented)
+ readme?: string;
+ // (undocumented)
+ repository?: {
+ type: string;
+ url: string;
+ directory?: string;
+ };
+ // (undocumented)
+ _rev: string;
+ // (undocumented)
+ time?: {
+ created: string;
+ modified: string;
+ [version: string]: string;
+ };
+ // (undocumented)
+ versions: {
+ [version: string]: NpmRegistryPackageInfoVersion;
+ };
+}
+
+// @public
+export interface NpmRegistryPackageInfoVersion {
+ // (undocumented)
+ description: string;
+ // (undocumented)
+ homepage: string;
+ // (undocumented)
+ name: string;
+ // (undocumented)
+ version: string;
+}
+```
diff --git a/workspaces/npm/plugins/npm-common/src/NpmRegistryClient.ts b/workspaces/npm/plugins/npm-common/src/NpmRegistryClient.ts
new file mode 100644
index 0000000000..432babb64d
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/src/NpmRegistryClient.ts
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 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 { ResponseError } from '@backstage/errors';
+
+import { NpmRegistryPackageInfo } from './types';
+
+/**
+ * @public
+ */
+export class NpmRegistryClient {
+ private readonly fetch: typeof fetch;
+ private readonly baseUrl: string;
+ private readonly token?: string;
+
+ constructor(options: {
+ fetch?: typeof fetch;
+ baseUrl?: string;
+ token?: string;
+ }) {
+ this.fetch = options?.fetch || global.fetch.bind(global);
+ this.baseUrl = options.baseUrl || 'https://registry.npmjs.com';
+ this.token = options.token;
+ }
+
+ async getPackageInfo(packageName: string): Promise {
+ if (!packageName) {
+ throw new Error('No package name provided');
+ }
+
+ const url = `${this.baseUrl}/${encodeURIComponent(packageName)}`;
+
+ const response = await this.fetch(url, {
+ headers: this.token ? { Authorization: this.token } : undefined,
+ });
+
+ if (!response.ok) {
+ throw await ResponseError.fromResponse(response);
+ }
+ return response.json();
+ }
+}
diff --git a/workspaces/npm/plugins/npm/src/annotations.ts b/workspaces/npm/plugins/npm-common/src/annotations.ts
similarity index 77%
rename from workspaces/npm/plugins/npm/src/annotations.ts
rename to workspaces/npm/plugins/npm-common/src/annotations.ts
index c771885f05..f6f6665d74 100644
--- a/workspaces/npm/plugins/npm/src/annotations.ts
+++ b/workspaces/npm/plugins/npm-common/src/annotations.ts
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-export const NPM_PACKAGE_ANNOTATION = 'npm/package';
-
-export const NPM_STABLE_TAG_ANNOTATION = 'npm/stable-tag';
-
-export const NPM_SHOW_TAGS_ANNOTATION = 'npm/show-tags';
+/**
+ * @public
+ */
+export enum NpmAnnotation {
+ PACKAGE_NAME = 'npm/package',
+ REGISTRY_NAME = 'npm/registry',
+ STABLE_TAG = 'npm/stable-tag',
+ SHOW_TAGS = 'npm/show-tags',
+}
diff --git a/workspaces/npm/plugins/npm-common/src/index.ts b/workspaces/npm/plugins/npm-common/src/index.ts
new file mode 100644
index 0000000000..91e679cfc9
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/src/index.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2024 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.
+ */
+
+/**
+ * Common functionalities for the npm plugin.
+ *
+ * @packageDocumentation
+ */
+
+export * from './annotations';
+export * from './NpmRegistryClient';
+export * from './types';
+export * from './utils';
diff --git a/workspaces/npm/plugins/npm-common/src/setupTests.ts b/workspaces/npm/plugins/npm-common/src/setupTests.ts
new file mode 100644
index 0000000000..c7ce5c0988
--- /dev/null
+++ b/workspaces/npm/plugins/npm-common/src/setupTests.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2024 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 {};
diff --git a/workspaces/npm/plugins/npm/src/api.ts b/workspaces/npm/plugins/npm-common/src/types.ts
similarity index 58%
rename from workspaces/npm/plugins/npm/src/api.ts
rename to workspaces/npm/plugins/npm-common/src/types.ts
index f88f15ec1f..d8781f474f 100644
--- a/workspaces/npm/plugins/npm/src/api.ts
+++ b/workspaces/npm/plugins/npm-common/src/types.ts
@@ -14,16 +14,24 @@
* limitations under the License.
*/
-// From https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md
-export interface NpmRegistryVersion {
+/**
+ * NPM package info `versions` type definition based on https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md
+ *
+ * @public
+ */
+export interface NpmRegistryPackageInfoVersion {
name: string;
version: string;
homepage: string;
description: string;
}
-// From https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md
-export interface NpmRegistryPackage {
+/**
+ * NPM registry package info type definition based on https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md
+ *
+ * @public
+ */
+export interface NpmRegistryPackageInfo {
_id: string;
_rev: string;
name: string;
@@ -34,9 +42,10 @@ export interface NpmRegistryPackage {
[tag: string]: string;
};
versions: {
- [version: string]: NpmRegistryVersion;
+ [version: string]: NpmRegistryPackageInfoVersion;
};
- time: {
+ // Available on npmjs and GitHub, not available on GitLab
+ time?: {
created: string;
modified: string;
[version: string]: string;
@@ -65,28 +74,3 @@ export interface NpmRegistryPackage {
};
readme?: string;
}
-
-const fetchNpmPackage = async (packageName: string | undefined) => {
- if (!packageName) {
- throw new Error('No package name provided');
- }
- const response = await fetch(
- `https://registry.npmjs.org/${encodeURIComponent(packageName)}`,
- );
- if (response.status === 404) {
- throw new Error(
- `Package ${packageName} not found (private repositories are not supported yet)`,
- );
- }
- if (!response.ok) {
- throw new Error(
- `Failed to fetch info for package ${packageName}: ${response.status} ${response.statusText}`,
- );
- }
- const json = await response.json();
- return json as NpmRegistryPackage;
-};
-
-export const API = {
- fetchNpmPackage,
-};
diff --git a/workspaces/npm/plugins/npm/src/utils/isNpmAvailable.ts b/workspaces/npm/plugins/npm-common/src/utils.ts
similarity index 86%
rename from workspaces/npm/plugins/npm/src/utils/isNpmAvailable.ts
rename to workspaces/npm/plugins/npm-common/src/utils.ts
index 7839280ddb..b450c22637 100644
--- a/workspaces/npm/plugins/npm/src/utils/isNpmAvailable.ts
+++ b/workspaces/npm/plugins/npm-common/src/utils.ts
@@ -15,7 +15,7 @@
*/
import { type Entity } from '@backstage/catalog-model';
-import { NPM_PACKAGE_ANNOTATION } from '../annotations';
+import { NpmAnnotation } from './annotations';
/**
* Function that returns true if the given entity contains at least one
@@ -24,5 +24,5 @@ import { NPM_PACKAGE_ANNOTATION } from '../annotations';
* @public
*/
export const isNpmAvailable = (entity: Entity): boolean => {
- return Boolean(entity.metadata.annotations?.[NPM_PACKAGE_ANNOTATION]);
+ return Boolean(entity.metadata.annotations?.[NpmAnnotation.PACKAGE_NAME]);
};
diff --git a/workspaces/npm/plugins/npm/README.md b/workspaces/npm/plugins/npm/README.md
deleted file mode 100644
index 0baf1de690..0000000000
--- a/workspaces/npm/plugins/npm/README.md
+++ /dev/null
@@ -1,145 +0,0 @@
-# npm plugin for Backstage
-
-A Backstage plugin that shows information and latest releases/versions from a npm registry
-for catalog entities.
-
-## Screenshots
-
-### Npm info card
-
-![Screenshot](docs/npm-info-card.png)
-
-### Npm release overview card
-
-![Screenshot](docs/npm-release-overview-card.png)
-
-### Extended catalog entity overview tab (example)
-
-![Screenshot](docs/catalog-entity-overview-tab.png)
-
-### New catalog entity npm release tab
-
-![Screenshot](docs/catalog-entity-npm-release-tab.png)
-
-## For users
-
-### Enable npm cards for a catalog entity
-
-To enable the different npm cards you must add the `npm/package` annotation
-with the name of the npm package:
-
-```yaml
-apiVersion: backstage.io/v1alpha1
-kind: Component
-metadata:
- name: react
- annotations:
- npm/package: react
-```
-
-The "npm info" card shows the information of the latest 'stable' npm release
-and use the common `latest` tag by default. This could be changed with `npm/stable-tag`:
-
-```yaml
-apiVersion: backstage.io/v1alpha1
-kind: Component
-metadata:
- name: react
- annotations:
- npm/package: react
- npm/stable-tag: latest, stable, next, etc.
-```
-
-## For administrators
-
-### Install frontend and manual integrate into catalog
-
-1. Install the frontend plugin:
-
- ```sh
- yarn workspace app add @backstage-community/plugin-npm
- ```
-
-2. Add cards based on your needs to `packages/app/src/components/catalog/EntityPage.tsx`:
-
- After all other imports:
-
- ```tsx
- import {
- isNpmAvailable,
- EntityNpmInfoCard,
- EntityNpmReleaseOverviewCard,
- EntityNpmReleaseTableCard,
- } from '@backstage-community/plugin-npm';
- ```
-
-3. Add to `const overviewContent` after `EntityAboutCard`:
-
- ```tsx
-
-
-
-
-
-
-
-
-
-
-
-
- ```
-
-4. Add to `const serviceEntityPage` and `const websiteEntityPage` after the `/ci-cd` case
- and to `const defaultEntityPage` between the `/` and `/docs` routecase.
-
- ```tsx
-
-
-
- ```
-
-### Use new frontend system
-
-1. Install the frontend plugin:
-
- ```sh
- yarn workspace app add @backstage-community/plugin-npm
- ```
-
-2. Enable the plugin in your `packages/app(-next)/src/App.tsx`:
-
- After all other imports:
-
- ```tsx
- import npmPlugin from '@backstage-community/plugin-npm/alpha';
- ```
-
- ```tsx
- export const app = createApp({
- features: [
- catalogPlugin,
- catalogImportPlugin,
- userSettingsPlugin,
- npmPlugin,
- // ...
- ],
- });
- ```
-
-### Test catalog entities
-
-For testing purpose you can import this catalog entities:
-
-```yaml
-catalog:
- locations:
- - type: url
- target: https://github.com/backstage/community-plugins/blob/main/workspaces/npm/examples/entities.yaml
- rules:
- - allow: [System, Component]
-```
diff --git a/workspaces/npm/plugins/npm/README.md b/workspaces/npm/plugins/npm/README.md
new file mode 120000
index 0000000000..fe84005413
--- /dev/null
+++ b/workspaces/npm/plugins/npm/README.md
@@ -0,0 +1 @@
+../../README.md
\ No newline at end of file
diff --git a/workspaces/npm/plugins/npm/catalog-info.yaml b/workspaces/npm/plugins/npm/catalog-info.yaml
index b3de13d432..2c0b185e14 100644
--- a/workspaces/npm/plugins/npm/catalog-info.yaml
+++ b/workspaces/npm/plugins/npm/catalog-info.yaml
@@ -1,10 +1,18 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
- name: backstage-plugin-npm
+ name: backstage-plugin-npm-frontend
title: '@backstage-community/plugin-npm'
description: A Backstage plugin that shows meta info and latest versions from a npm registry
+ annotations:
+ backstage.io/techdocs-entity: component:default/backstage-plugin-npm-workspace
+ npm/package: '@backstage-community/plugin-npm'
+ links:
+ - url: https://github.com/backstage/community-plugins/tree/main/workspaces/npm/plugins/npm
+ title: GitHub
+ icon: sourcecode
spec:
- lifecycle: experimental
+ lifecycle: production
type: backstage-frontend-plugin
owner: maintainers
+ subcomponentOf: backstage-plugin-npm
diff --git a/workspaces/npm/plugins/npm/config.d.ts b/workspaces/npm/plugins/npm/config.d.ts
new file mode 100644
index 0000000000..8977e458ad
--- /dev/null
+++ b/workspaces/npm/plugins/npm/config.d.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024 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 interface Config {
+ npm?: {
+ /**
+ * Use another default registry. This automatically enforce usage of the backend.
+ *
+ * @visibility frontend
+ */
+ defaultRegistry?: string;
+ };
+}
diff --git a/workspaces/npm/plugins/npm/docs b/workspaces/npm/plugins/npm/docs
new file mode 120000
index 0000000000..92a7f82538
--- /dev/null
+++ b/workspaces/npm/plugins/npm/docs
@@ -0,0 +1 @@
+../../docs
\ No newline at end of file
diff --git a/workspaces/npm/plugins/npm/knip-report.md b/workspaces/npm/plugins/npm/knip-report.md
index 703b798295..32a9e34e05 100644
--- a/workspaces/npm/plugins/npm/knip-report.md
+++ b/workspaces/npm/plugins/npm/knip-report.md
@@ -1,5 +1,11 @@
# Knip report
+## Unused dependencies (1)
+
+| Name | Location | Severity |
+| :------------------------------------- | :----------- | :------- |
+| @backstage-community/plugin-npm-common | package.json | error |
+
## Unused devDependencies (3)
| Name | Location | Severity |
@@ -7,3 +13,10 @@
| @testing-library/user-event | package.json | error |
| @testing-library/react | package.json | error |
| @backstage/test-utils | package.json | error |
+
+## Unlisted dependencies (2)
+
+| Name | Location | Severity |
+| :----------------------------- | :-------------------------- | :------- |
+| @backstage/errors | src/hooks/usePackageInfo.ts | error |
+| @backstage/frontend-plugin-api | src/alpha.tsx | error |
diff --git a/workspaces/npm/plugins/npm/package.json b/workspaces/npm/plugins/npm/package.json
index 133613a8f0..1a5dcbe613 100644
--- a/workspaces/npm/plugins/npm/package.json
+++ b/workspaces/npm/plugins/npm/package.json
@@ -14,7 +14,9 @@
"pluginId": "npm",
"pluginPackage": "@backstage-community/plugin-npm",
"pluginPackages": [
- "@backstage-community/plugin-npm"
+ "@backstage-community/plugin-npm",
+ "@backstage-community/plugin-npm-backend",
+ "@backstage-community/plugin-npm-common"
]
},
"exports": {
@@ -43,9 +45,12 @@
"postpack": "backstage-cli package postpack"
},
"dependencies": {
+ "@backstage-community/plugin-npm-common": "workspace:^",
"@backstage/catalog-model": "^1.7.2",
"@backstage/core-components": "^0.16.2",
"@backstage/core-plugin-api": "^1.10.2",
+ "@backstage/errors": "^1.2.4",
+ "@backstage/frontend-plugin-api": "^0.9.3",
"@backstage/plugin-catalog-react": "^1.15.0",
"@backstage/theme": "^0.6.3",
"@material-ui/core": "^4.9.13",
@@ -65,8 +70,10 @@
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
},
"files": [
+ "config.d.ts",
"dist"
],
+ "configSchema": "config.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/backstage/community-plugins",
diff --git a/workspaces/npm/plugins/npm/report-alpha.api.md b/workspaces/npm/plugins/npm/report-alpha.api.md
index b16eb9a101..dd3895ce3f 100644
--- a/workspaces/npm/plugins/npm/report-alpha.api.md
+++ b/workspaces/npm/plugins/npm/report-alpha.api.md
@@ -4,6 +4,7 @@
```ts
import { FrontendPlugin } from '@backstage/frontend-plugin-api';
+import { isNpmAvailable } from '@backstage-community/plugin-npm-common';
// @alpha
const _default: FrontendPlugin<{}, {}, {}>;
@@ -18,5 +19,7 @@ export const entityNpmReleaseOverviewCard: any;
// @alpha
export const entityNpmReleaseTableCard: any;
+export { isNpmAvailable };
+
// (No @packageDocumentation comment for this package)
```
diff --git a/workspaces/npm/plugins/npm/report.api.md b/workspaces/npm/plugins/npm/report.api.md
index 05e3c31fb6..1454c0eb07 100644
--- a/workspaces/npm/plugins/npm/report.api.md
+++ b/workspaces/npm/plugins/npm/report.api.md
@@ -6,7 +6,7 @@
///
import { BackstagePlugin } from '@backstage/core-plugin-api';
-import { Entity } from '@backstage/catalog-model';
+import { isNpmAvailable } from '@backstage-community/plugin-npm-common';
import { JSX as JSX_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
@@ -19,8 +19,7 @@ export const EntityNpmReleaseOverviewCard: () => JSX_2.Element;
// @public
export const EntityNpmReleaseTableCard: () => JSX_2.Element;
-// @public
-export const isNpmAvailable: (entity: Entity) => boolean;
+export { isNpmAvailable };
// @public @deprecated (undocumented)
export const NpmInfoCard: () => JSX_2.Element;
diff --git a/workspaces/npm/plugins/npm/src/alpha.tsx b/workspaces/npm/plugins/npm/src/alpha.tsx
index 0db40b7a46..c8eecdb5b6 100644
--- a/workspaces/npm/plugins/npm/src/alpha.tsx
+++ b/workspaces/npm/plugins/npm/src/alpha.tsx
@@ -20,7 +20,9 @@ import {
EntityContentBlueprint,
} from '@backstage/plugin-catalog-react/alpha';
-import { isNpmAvailable } from './utils/isNpmAvailable';
+import { isNpmAvailable } from '@backstage-community/plugin-npm-common';
+
+export { isNpmAvailable } from '@backstage-community/plugin-npm-common';
/**
* Card for the catalog (entity page) that shows the npm
diff --git a/workspaces/npm/plugins/npm/src/api/NpmBackendApi.ts b/workspaces/npm/plugins/npm/src/api/NpmBackendApi.ts
new file mode 100644
index 0000000000..caa235f999
--- /dev/null
+++ b/workspaces/npm/plugins/npm/src/api/NpmBackendApi.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 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 { NpmRegistryPackageInfo } from '@backstage-community/plugin-npm-common';
+
+/**
+ * @public
+ */
+export interface NpmBackendApi {
+ getPackageInfo(entityRef: string): Promise;
+}
diff --git a/workspaces/npm/plugins/npm/src/api/NpmBackendClient.ts b/workspaces/npm/plugins/npm/src/api/NpmBackendClient.ts
new file mode 100644
index 0000000000..234ee1ea13
--- /dev/null
+++ b/workspaces/npm/plugins/npm/src/api/NpmBackendClient.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 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 { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
+import { NpmRegistryPackageInfo } from '@backstage-community/plugin-npm-common';
+
+import { NpmBackendApi } from './NpmBackendApi';
+
+export type Options = {
+ discoveryApi: DiscoveryApi;
+ fetchApi: FetchApi;
+};
+
+export class NpmBackendClient implements NpmBackendApi {
+ private readonly discoveryApi: DiscoveryApi;
+ private readonly fetchApi: FetchApi;
+
+ constructor(options: Options) {
+ this.discoveryApi = options.discoveryApi;
+ this.fetchApi = options.fetchApi;
+ }
+
+ async getPackageInfo(entityRef: string): Promise {
+ const baseUrl = await this.discoveryApi.getBaseUrl('npm');
+ const url = `${baseUrl}/${encodeURIComponent(entityRef)}/package-info`;
+
+ const response = await this.fetchApi.fetch(url);
+ if (!response.ok) {
+ throw new Error(
+ `Unexpected status code: ${response.status} ${response.statusText}`,
+ );
+ }
+
+ const data = await response.json();
+ return data as NpmRegistryPackageInfo;
+ }
+}
diff --git a/workspaces/npm/plugins/npm/src/api/index.ts b/workspaces/npm/plugins/npm/src/api/index.ts
new file mode 100644
index 0000000000..7fab9b4c6e
--- /dev/null
+++ b/workspaces/npm/plugins/npm/src/api/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 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 { createApiRef } from '@backstage/core-plugin-api';
+
+import { NpmBackendApi } from './NpmBackendApi';
+
+export * from './NpmBackendClient';
+
+export const NpmBackendApiRef = createApiRef({
+ id: 'plugin.npm.backend-api-ref',
+});
diff --git a/workspaces/npm/plugins/npm/src/components/EntityNpmInfoCard.tsx b/workspaces/npm/plugins/npm/src/components/EntityNpmInfoCard.tsx
index f6f0b8bd73..cbf2d15b3e 100644
--- a/workspaces/npm/plugins/npm/src/components/EntityNpmInfoCard.tsx
+++ b/workspaces/npm/plugins/npm/src/components/EntityNpmInfoCard.tsx
@@ -14,23 +14,24 @@
* limitations under the License.
*/
import React from 'react';
+
import { ErrorPanel, InfoCard } from '@backstage/core-components';
+import {
+ MissingAnnotationEmptyState,
+ useEntity,
+} from '@backstage/plugin-catalog-react';
+
+import { NpmAnnotation } from '@backstage-community/plugin-npm-common';
+
import Box from '@material-ui/core/Box';
import CircularProgress from '@material-ui/core/CircularProgress';
import Grid, { GridSize } from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
-import {
- MissingAnnotationEmptyState,
- useEntity,
-} from '@backstage/plugin-catalog-react';
-import useAsync from 'react-use/esm/useAsync';
+
import { DateTime } from 'luxon';
-import {
- NPM_PACKAGE_ANNOTATION,
- NPM_STABLE_TAG_ANNOTATION,
-} from '../annotations';
-import { API } from '../api';
+
+import { usePackageInfo } from '../hooks/usePackageInfo';
// From https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/AboutCard/AboutField.tsx
const useStyles = makeStyles(theme => ({
@@ -82,34 +83,33 @@ function GridItem({
*/
export const EntityNpmInfoCard = () => {
const { entity } = useEntity();
+ const { packageInfo, loading, error } = usePackageInfo();
- const packageName = entity.metadata.annotations?.[NPM_PACKAGE_ANNOTATION];
-
- const {
- value: packageInfo,
- loading,
- error,
- } = useAsync(() => API.fetchNpmPackage(packageName), [packageName]);
+ const packageName = entity.metadata.annotations?.[NpmAnnotation.PACKAGE_NAME];
if (!packageName) {
return (
);
}
const latestTag =
- entity.metadata.annotations?.[NPM_STABLE_TAG_ANNOTATION] ?? 'latest';
+ entity.metadata.annotations?.[NpmAnnotation.STABLE_TAG] ?? 'latest';
const latestVersion = packageInfo?.['dist-tags']?.[latestTag];
const latestPublishedAt = latestVersion
? packageInfo?.time?.[latestVersion]
: undefined;
- const npmLink = packageInfo
- ? `https://www.npmjs.com/package/${packageName}`
- : null;
+ const registryName =
+ entity.metadata.annotations?.[NpmAnnotation.REGISTRY_NAME];
+
+ const npmLink =
+ packageInfo && !registryName
+ ? `https://www.npmjs.com/package/${packageName}`
+ : null;
let repositoryLink: string | undefined;
if (packageInfo?.repository?.url) {
@@ -189,6 +189,10 @@ export const EntityNpmInfoCard = () => {
) : null}
+ {registryName ? (
+
+ ) : null}
+
{npmLink ? (
[] = [
@@ -50,11 +53,14 @@ const tagColumns: TableColumn[] = [
title: 'Published',
field: 'published',
type: 'datetime',
- render: row => (
-
- ),
+ render: row =>
+ row.published ? (
+
+ ) : (
+ '-'
+ ),
},
];
@@ -66,19 +72,18 @@ const tagColumns: TableColumn[] = [
*/
export const EntityNpmReleaseOverviewCard = () => {
const { entity } = useEntity();
+ const { packageInfo, loading, error } = usePackageInfo();
- const packageName = entity.metadata.annotations?.[NPM_PACKAGE_ANNOTATION];
-
- const {
- value: packageInfo,
- loading,
- error,
- } = useAsync(() => API.fetchNpmPackage(packageName), [packageName]);
+ const packageName = entity.metadata.annotations?.[NpmAnnotation.PACKAGE_NAME];
+ const showTags = entity.metadata.annotations?.[NpmAnnotation.SHOW_TAGS]
+ ?.split(',')
+ .map(s => s.trim())
+ .filter(Boolean);
if (!packageName) {
return (
);
@@ -87,7 +92,10 @@ export const EntityNpmReleaseOverviewCard = () => {
const data: TagRow[] = [];
if (packageInfo?.['dist-tags']) {
for (const [tag, version] of Object.entries(packageInfo['dist-tags'])) {
- const published = packageInfo.time[version];
+ if (showTags && showTags.length > 0 && !showTags.includes(tag)) {
+ continue;
+ }
+ const published = packageInfo.time?.[version];
data.push({ tag, version, published });
}
}
diff --git a/workspaces/npm/plugins/npm/src/components/EntityNpmReleaseTableCard.tsx b/workspaces/npm/plugins/npm/src/components/EntityNpmReleaseTableCard.tsx
index 3254a0e666..7cfc5c3547 100644
--- a/workspaces/npm/plugins/npm/src/components/EntityNpmReleaseTableCard.tsx
+++ b/workspaces/npm/plugins/npm/src/components/EntityNpmReleaseTableCard.tsx
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import React from 'react';
+
import {
MissingAnnotationEmptyState,
useEntity,
@@ -23,21 +24,24 @@ import {
Table,
type TableColumn,
} from '@backstage/core-components';
+
+import { NpmAnnotation } from '@backstage-community/plugin-npm-common';
+
import Box from '@material-ui/core/Box';
-import useAsync from 'react-use/esm/useAsync';
+
import { DateTime } from 'luxon';
-import { NPM_PACKAGE_ANNOTATION } from '../annotations';
-import { API } from '../api';
+
+import { usePackageInfo } from '../hooks/usePackageInfo';
interface TagRow {
tag: string;
version: string;
- published: string;
+ published?: string;
}
interface TableData {
version: string;
- published: string;
+ published?: string;
}
const tagColumns: TableColumn[] = [
@@ -55,11 +59,14 @@ const tagColumns: TableColumn[] = [
title: 'Published',
field: 'published',
type: 'datetime',
- render: row => (
-
- ),
+ render: row =>
+ row.published ? (
+
+ ) : (
+ '-'
+ ),
},
];
@@ -73,11 +80,14 @@ const columns: TableColumn[] = [
title: 'Published',
field: 'published',
type: 'datetime',
- render: row => (
-
- ),
+ render: row =>
+ row.published ? (
+
+ ) : (
+ '-'
+ ),
},
];
@@ -90,19 +100,18 @@ const columns: TableColumn[] = [
*/
export const EntityNpmReleaseTableCard = () => {
const { entity } = useEntity();
+ const { packageInfo, loading, error } = usePackageInfo();
- const packageName = entity.metadata.annotations?.[NPM_PACKAGE_ANNOTATION];
-
- const {
- value: packageInfo,
- loading,
- error,
- } = useAsync(() => API.fetchNpmPackage(packageName), [packageName]);
+ const packageName = entity.metadata.annotations?.[NpmAnnotation.PACKAGE_NAME];
+ const showTags = entity.metadata.annotations?.[NpmAnnotation.SHOW_TAGS]
+ ?.split(',')
+ .map(s => s.trim())
+ .filter(Boolean);
if (!packageName) {
return (
);
@@ -111,12 +120,16 @@ export const EntityNpmReleaseTableCard = () => {
const tagData: TagRow[] = [];
if (packageInfo?.['dist-tags']) {
for (const [tag, version] of Object.entries(packageInfo['dist-tags'])) {
- const published = packageInfo.time[version];
+ if (showTags && showTags.length > 0 && !showTags.includes(tag)) {
+ continue;
+ }
+ const published = packageInfo.time?.[version];
tagData.push({ tag, version, published });
}
}
const data: TableData[] = [];
+ // npmjs, GitHub has a time history, GitLab not
if (packageInfo?.time) {
for (const [version, published] of Object.entries(packageInfo.time)) {
if (version === 'created' || version === 'modified') {
@@ -125,6 +138,13 @@ export const EntityNpmReleaseTableCard = () => {
data.push({ version, published });
}
data.reverse();
+ } else if (packageInfo?.versions) {
+ for (const [version, _releasePackageInfo] of Object.entries(
+ packageInfo.versions,
+ )) {
+ data.push({ version });
+ }
+ data.reverse();
}
const emptyContent = error ? (
@@ -133,6 +153,7 @@ export const EntityNpmReleaseTableCard = () => {
) : null;
+ // TODO: export both tables as cards and rename `EntityNpmReleaseTableCard` to `EntityNpmReleaseContent` or `NpmReleaseEntityContent`
return (
<>