diff --git a/README.md b/README.md
index 3f7ada6..3cee1ab 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,8 @@
# Backstage JFrog artifactory libraries plugin
-
-The Backstage `jfrog-artifactory-libs` frontend plugin provides a simple way to display generated artifact (library)
-details like - group, artifact, repository, what is the latest version, and it simply allows to copy library definition
-for the package managers.
-Nowadays, the plugin supports these package managers types in JFrog: Maven, Gradle, Sbt, Pypi, Docker.
+ The Backstage `jfrog-artifactory-libs` frontend plugin uses the JFrog Artifactory APIs to fetch details about artifacts (libraries)
+ and displays these details in the Backstage app, allowing users to view information about the artifact, such as the group,
+ artifact, repository, the latest version, and it also allows to copy library definition for the package managers. The plugin
+ supports these package managers types in JFrog: Maven, Gradle, Sbt, Pypi, Docker, NPM, Yarn, Nuget.

@@ -11,10 +10,11 @@ Nowadays, the plugin supports these package managers types in JFrog: Maven, Grad
- [Backstage jfrog-artifactory-libs plugin](#backstage-xkcd-plugin)
+ - [Authentication](#authentication)
- [Installation](#installation)
- [Integration](#integration)
- [Configuration](#configuration)
- - [Support for scaffolding](#support-for-scaffolding)
+ - [Support for scaffolding](#support-for-scaffolding)
- [How it works](#how-it-works)
- [Changes](#changes)
- [Contributing](#contributing)
@@ -23,6 +23,12 @@ Nowadays, the plugin supports these package managers types in JFrog: Maven, Grad
- [License](#license)
+## Authentication:
+
+- The jfrog-artifactory-libs plugin does not directly handle authentication. Instead, it relies on [Backstage basic HTTP proxy package](https://backstage.io/docs/plugins/proxying/)
+ which is configured to forward requests to JFrog Artifactory instance.
+
+
## Installation
To install the plugin, you'll need to add it to your Backstage app's dependencies using either Yarn or NPM.
@@ -38,26 +44,32 @@ yarn add --cwd packages/app backstage-plugin-jfrog-artifactory-libs
Once you've installed the plugin, you'll need to integrate it into your Backstage app. To do so, you'll need to following code into your BS instance:
### JFrogLibArtifactCard
+
Add the JFrogLibArtifactCard component to the `EntityPage.tsx` in your app:
```typescript jsx
-import {JFrogLibArtifactCard, isJfrogArtifactAvailable} from 'backstage-plugin-jfrog-artifactory-libs';
+import {
+ JFrogLibArtifactCard,
+ isJfrogArtifactAvailable,
+} from 'backstage-plugin-jfrog-artifactory-libs';
//....
const overviewContent = (
-// ...
-
- //...
-
-
-
-
-
- //...
-
- // ...
+ // ...
+
+ //...
+
+
+
+
+
+ //...
+
+ // ...
);
```
+
### JFrogLibVerPage
+
If you want to browse libraries you can enable this component in your `App.tsx` file. It shows all component entities containing `jfrog.com/artifactory-artifact` attribute.

@@ -66,8 +78,10 @@ If you want to browse libraries you can enable this component in your `App.tsx`
const routes = (
//....
- } />}>
-
+ } />}
+ >
//....
);
@@ -75,7 +89,9 @@ const routes = (
### Explore page - JFrogLibVerPageContent
+
This is a subcomponent of `JFrogLibVerPage` component. It's possible to integrate it for instance into your Explore page.
+
```typescript jsx
import { JFrogLibVerPageContent } from 'backstage-plugin-jfrog-artifactory-libs';
//....
@@ -100,13 +116,13 @@ import { JFrogLibVerPageContent } from 'backstage-plugin-jfrog-artifactory-libs'
Set up a proxy for the JFrog API by adding the following configuration to your `app-config.yaml` file:
```yaml
- '/artifactory-proxy/':
- target: 'https://your-jfrog-artifactory-instance.com'
- headers:
- # if you use Jfrog instance for anonymous user token is not required, but it is also required for Docker package type
- Authorization: Bearer ${ARTIFACTORY_TOKEN}
- X-Result-Detail: 'properties'
- Accept: '*'
+'/artifactory-proxy/':
+ target: 'https://your-jfrog-artifactory-instance.com'
+ headers:
+ # if you use Jfrog instance for anonymous user token is not required, but it is also required for Docker package type
+ Authorization: Bearer ${ARTIFACTORY_TOKEN}
+ X-Result-Detail: 'properties'
+ Accept: '*'
```
You have to also reference your artifactory URL (used for UI browse links) and your proxy configuration.
@@ -115,7 +131,7 @@ You have to also reference your artifactory URL (used for UI browse links) and y
jfrog:
artifactory:
url: 'https://your-jfrog-artifactory-instance.com'
- proxyPath: '/artifactory-proxy/' # optional, /artifactory-proxy/ is default value
+ proxyPath: '/artifactory-proxy/' # optional, /artifactory-proxy/ is default value
```
### Catalog-info.yaml
@@ -123,30 +139,34 @@ jfrog:
Artifact details are correlated to Backstage entities using an annotation added in the entity's catalog-info.yaml file.
```yaml
- metadata:
- annotations:
- # -- required values --
- jfrog.com/artifactory-artifact: 'artifact-name'
- jfrog.com/artifactory-repo: 'maven-local'
-
- jfrog.com/artifactory-group: 'com.mycompany' # optional string - can be blank for pypi, necessary for Maven repos
-
- # -- optional values --
- jfrog.com/artifactory-scope: 'compile' # optional string, one of these [compile, test,provided,runtime,classpath,optional]
- jfrog.com/artifactory-packaging: 'aar' #optional string, eg. `aar`
-
+#...
+metadata:
+ annotations:
+ # -- required values --
+ jfrog.com/artifactory-artifact: 'artifact-name'
+ jfrog.com/artifactory-repo: 'maven-local'
+
+ jfrog.com/artifactory-group: 'com.mycompany' # optional string - can be blank for pypi, necessary for Maven repos
+
+ # -- optional values --
+ jfrog.com/artifactory-scope: 'compile' # optional string, one of these [compile, test,provided,runtime,classpath,optional]
+ jfrog.com/artifactory-packaging: 'aar' #optional string, eg. `aar`
+#...
```
And that's it! The plugin should now be integrated into your Backstage app, and you should see the Artifact card when
you navigate to the entity page where it's included.
For a docker image you define repository and artifact name. Both formats are supported:
+
```yaml
- metadata:
- annotations:
- # -- required values --
- jfrog.com/artifactory-artifact: 'docker.mydomain.com/mygroup/my/artifact-name' # or simply 'mygroup/my/artifact-name'
- jfrog.com/artifactory-repo: 'docker-local'
+#...
+metadata:
+ annotations:
+ # -- required values --
+ jfrog.com/artifactory-artifact: 'docker.mydomain.com/mygroup/my/artifact-name' # or simply 'mygroup/my/artifact-name'
+ jfrog.com/artifactory-repo: 'docker-local'
+#...
```

@@ -163,28 +183,33 @@ LibArtifactCard.defaultProps = {
showMaven: true, // whether to show Maven package manager tab
showSbt: true, // whether to show Sbt package manager tab
showPip: true, // whether to show Pip package manager tab
+ showYarn: true, // whether to show Yarn package manager tab
showDockerfile: true, // whether to show Dockerfile tab
// it hides Maven and Gradle tabs if the current repository package type is `PyPi`
autohideTabs: true,
- showBrowseRepositoryLink: true // whether to show Browse to URL deep link under bottom of the Card
+ showBrowseRepositoryLink: true, // whether to show Browse to URL deep link under bottom of the Card
// which link to open
browseLink: browseLinkDefault,
};
```
+
### Support for scaffolding
+
In [this document](./doc/SCAFFOLDING.md) you can find detailed information how to integrate this plugin into scaffolding templates.
-It also adds a new extension UI component called `ArtifactRepositoryPicker` for interactive repository selection.
+It also adds a new extension UI component called `ArtifactRepositoryPicker` for interactive repository selection.
## How it works
Plugin uses JFrog APIs to find latest version. It's necessary to specify `ARTIFACTORY_TOKEN` in the `app-config.yaml`
file if you don't allow to access API for anonymous user.
-## Changes
-Version 1.0.9
- - JFrogLibVerPageContent and JFrogLibVerPage components added
- - (!) renamed LibArtifactCard into JFrogLibArtifactCard
+## Changes
+
+Version 1.0.9
+
+- JFrogLibVerPageContent and JFrogLibVerPage components added
+- (!) renamed LibArtifactCard into JFrogLibArtifactCard
## Contributing
diff --git a/package.json b/package.json
index 2ed7441..64a46c2 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,6 @@
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
- "private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
@@ -31,6 +30,7 @@
"src/components/types.ts"
]
},
+ "sideEffects": false,
"scripts": {
"start": "backstage-cli package start",
"dev": "concurrently \"yarn start\" \"yarn start-backend\"",
@@ -49,40 +49,42 @@
"bump": "yarn run standard-version --no-verify --releaseCommitMessageFormat 'v{{currentTag}}'"
},
"dependencies": {
- "@backstage/core-components": "^0.13.4",
- "@backstage/plugin-catalog-react": "^1.8.3",
- "@backstage/catalog-model": "^1.4.1",
- "@backstage/core-plugin-api": "^1.5.3",
- "@backstage/plugin-catalog": "^1.12.4",
- "@backstage/plugin-scaffolder": "^1.14.4",
- "@backstage/plugin-scaffolder-react": "^1.6.1",
- "@backstage/theme": "^0.4.1",
+ "@backstage/catalog-model": "^1.7.0",
+ "@backstage/config": "^1.2.0",
+ "@backstage/core-components": "^0.15.0",
+ "@backstage/core-plugin-api": "^1.9.4",
+ "@backstage/plugin-catalog": "^1.23.1",
+ "@backstage/plugin-catalog-react": "^1.13.1",
+ "@backstage/plugin-scaffolder": "^1.25.1",
+ "@backstage/plugin-scaffolder-react": "^1.12.1",
+ "@backstage/types": "^1.1.1",
"@material-ui/core": "^4.9.13",
- "@material-ui/icons": "^4.9.1",
- "@material-ui/lab": "^4.0.0-alpha.60",
+ "@material-ui/lab": "^4.0.0-alpha.61",
"@types/pluralize": "^0.0.33",
"lodash": "^4.17.21",
- "react-use": "^17.2.4"
+ "pluralize": "^8.0.0",
+ "react-use": "^17.2.4",
+ "zod": "^3.23.8"
},
"peerDependencies": {
- "react": "^16.13.1 || ^17.2.0"
+ "react": "^16.13.1 || ^17.2.0",
+ "react-router": "6.0.0-beta.0 || ^6.3.0"
},
"devDependencies": {
- "@backstage/cli": "^0.22.12",
- "@backstage/core-app-api": "^1.9.1",
- "@backstage/dev-utils": "^1.0.20",
- "@backstage/test-utils": "^1.4.2",
+ "@backstage/cli": "^0.27.1",
+ "@backstage/core-app-api": "^1.15.0",
+ "@backstage/dev-utils": "^1.1.1",
+ "@backstage/test-utils": "^1.6.0",
"@spotify/prettier-config": "^14.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
+ "@types/lodash": "^4.14.202",
"@types/node": "*",
+ "@types/pluralize": "^0.0.33",
"cross-fetch": "^3.1.5",
"msw": "^1.0.0",
- "@types/pluralize": "^0.0.33",
- "@types/lodash": "^4.14.202",
"prettier": "^2.2.1",
- "react-router": "6.0.0-beta.0",
"standard-version": "^9.5.0"
},
"prettier": "@spotify/prettier-config",
diff --git a/src/components/ArtifactRepositoryPicker/index.ts b/src/components/ArtifactRepositoryPicker/index.ts
index d23e11b..de2facf 100644
--- a/src/components/ArtifactRepositoryPicker/index.ts
+++ b/src/components/ArtifactRepositoryPicker/index.ts
@@ -18,5 +18,5 @@ export {
type RepositoryPickerUiOptions,
} from './schema';
export { ArtifactRepositoryPicker } from './ArtifactRepositoryPicker';
-export type * from './ArtifactRepositoryPicker'
-export type * from './schema'
\ No newline at end of file
+export type * from './ArtifactRepositoryPicker';
+export type * from './schema';
diff --git a/src/components/ArtifactRepositoryPicker/schema.ts b/src/components/ArtifactRepositoryPicker/schema.ts
index 6ce0f05..690be8d 100644
--- a/src/components/ArtifactRepositoryPicker/schema.ts
+++ b/src/components/ArtifactRepositoryPicker/schema.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import { z } from 'zod';
-import {CustomFieldExtensionSchema, makeFieldSchemaFromZod} from '@backstage/plugin-scaffolder';
+import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder';
+import { CustomFieldExtensionSchema } from '@backstage/plugin-scaffolder-react';
/**
* @public
diff --git a/src/components/LibArtifactCard/LibArtifactCard.tsx b/src/components/LibArtifactCard/LibArtifactCard.tsx
index 40e00d4..1102138 100644
--- a/src/components/LibArtifactCard/LibArtifactCard.tsx
+++ b/src/components/LibArtifactCard/LibArtifactCard.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { configApiRef, identityApiRef, useApi } from '@backstage/core-plugin-api';
+import { configApiRef, fetchApiRef, useApi } from '@backstage/core-plugin-api';
import { ResponseErrorPanel } from '@backstage/core-components';
import { useEntity } from '@backstage/plugin-catalog-react';
import { LibArtifactCardProps } from '../../types';
@@ -13,8 +13,8 @@ import { ArtifactInfo } from './api';
export const DEFAULT_PROXY_PATH = '/artifactory-proxy/';
export const LibArtifactCard = (props: LibArtifactCardProps) => {
const config = useApi(configApiRef);
+ const fetchApi = useApi(fetchApiRef);
const { entity } = useEntity();
- const identity = useApi (identityApiRef);
const artifactoryUrl = config.getString('jfrog.artifactory.url');
@@ -22,7 +22,7 @@ export const LibArtifactCard = (props: LibArtifactCardProps) => {
const { value, loading, error } = useAsync(async () => {
try {
- return await libraryInfo(entity, config, identity);
+ return await libraryInfo(entity, config, fetchApi);
} catch (e) {
if (!(e instanceof Error)) {
throw new Error(e as string);
@@ -65,6 +65,9 @@ LibArtifactCard.defaultProps = {
showMaven: true, // whether to show Maven package manager tab
showSbt: true, // whether to show Sbt package manager tab
showPip: true, // whether to show Pip package manager tab
+ showNpm: true, // whether to show Npm package manager tab
+ showYarn: true, // whether to show Npm package manager tab
+ showNuget: true, // whether to show Nuget package manager tab
showDockerfile: true, // whether to show Dockerfile tab
// it hides Maven and Gradle tabs if the current repository package type is `PyPi`
autohideTabs: true,
diff --git a/src/components/LibArtifactCard/api.ts b/src/components/LibArtifactCard/api.ts
index 47add76..e84b154 100644
--- a/src/components/LibArtifactCard/api.ts
+++ b/src/components/LibArtifactCard/api.ts
@@ -1,13 +1,15 @@
-// import { on } from 'events';
import { LibraryArtifact, MetadataResponse } from '../../types';
import { findLatestVersion } from './versionUtils';
-import { IdentityApi } from '@backstage/core-plugin-api';
+import { FetchApi } from '@backstage/core-plugin-api';
export type GeneratedCode = {
gradle: string;
maven: string;
sbt: string;
pip: string;
+ nuget: string;
+ npm: string;
+ yarn: string;
};
export type ArtifactInfo = {
@@ -47,23 +49,13 @@ export async function getErrorMessage(response: Response) {
}
export async function getRepositoryType(
- fetch: {
- (input: RequestInfo | URL, init?: RequestInit): Promise;
- (input: RequestInfo | URL, init?: RequestInit): Promise;
- },
url: string,
{ repo }: LibraryArtifact,
- identityApi: IdentityApi
+ fetchApi: FetchApi,
) {
- // Obtain the token
-
- const { token: idToken } = await identityApi.getCredentials();
-
- const response = await fetch(`${url}artifactory/api/repositories/${repo}`, {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },
- });
+ const response = await fetchApi.fetch(
+ `${url}artifactory/api/repositories/${repo}`,
+ );
if (response.status === 404) {
throw new Error(`Repository ${repo} was not found`);
} else {
@@ -80,22 +72,12 @@ export async function getRepositoryType(
}
export async function getMavenLatestVersion(
- fetch: {
- (input: RequestInfo | URL, init?: RequestInit): Promise;
- (input: RequestInfo | URL, init?: RequestInit): Promise;
- },
url: string,
{ group, artifact, repo }: LibraryArtifact,
- identityApi: IdentityApi,
+ fetchApi: FetchApi,
) {
-
- const { token: idToken } = await identityApi.getCredentials();
-
- const response = await fetch(
- `${url}artifactory/api/search/latestVersion?g=${group}&a=${artifact}&repos=${repo}`, {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },}
+ const response = await fetchApi.fetch(
+ `${url}artifactory/api/search/latestVersion?g=${group}&a=${artifact}&repos=${repo}`,
);
if (response.status === 404) {
return undefined;
@@ -109,13 +91,13 @@ export async function getMavenLatestVersion(
}
}
-export function getMetadataVersionQuery(artifact: string) {
+export function getMetadataVersionQuery(artifact: string, packageType: string) {
const query = {
query:
'query ($filter: VersionFilter!, $first: Int, $orderBy: VersionOrder) { versions (filter: $filter, first: $first, orderBy: $orderBy) { edges { node { name, created, modified, package { id }, repos { name, type, leadFilePath }, licenses { name, source }, size, stats { downloadCount }, vulnerabilities { critical, high, medium, low, info, unknown, skipped }, files { name, lead, size, md5, sha1, sha256, mimeType } } } } }',
variables: {
filter: {
- packageId: `docker://${artifact}`,
+ packageId: `${packageType}://${artifact}`,
name: '*',
ignorePreRelease: false,
},
@@ -148,24 +130,29 @@ export function extractArtifactFromFullDockerName(artifact: string): string {
return artifactWithoutVersion;
}
-export async function getDockerLatestVersion(
- fetch: {
- (input: RequestInfo | URL, init?: RequestInit): Promise;
- (input: RequestInfo | URL, init?: RequestInit): Promise;
- },
+function extractPathFromLeadFilePath(path: string) {
+ if (path) {
+ const lastSlash = path.lastIndexOf('/');
+ if (lastSlash) {
+ return path.substring(0, lastSlash);
+ }
+ }
+ return undefined;
+}
+
+export async function getLatestCreatedVersion(
url: string,
- { artifact }: LibraryArtifact,
- identityApi: IdentityApi,
+ libraryArtifact: LibraryArtifact,
+ packageType: string,
+ fetchApi: FetchApi,
+ artifactName: (libraryArtifact: LibraryArtifact) => string,
) {
- const { token: idToken } = await identityApi.getCredentials();
-
- const response = await fetch(`${url}/metadata/api/v1/query`, {
+ const response = await fetchApi.fetch(`${url}metadata/api/v1/query`, {
headers: {
'Content-Type': 'application/json',
- Authorization: `Bearer ${idToken}`,
},
method: 'POST',
- body: getMetadataVersionQuery(extractArtifactFromFullDockerName(artifact)),
+ body: getMetadataVersionQuery(artifactName(libraryArtifact), packageType),
});
if (response.status === 404) {
return undefined;
@@ -182,6 +169,10 @@ export async function getDockerLatestVersion(
version: node.name,
size: Number(node.size),
statsDownload: node.stats.downloadCount,
+ filePath:
+ node.repos && node.repos.length > 0
+ ? extractPathFromLeadFilePath(node.repos[0].leadFilePath)
+ : undefined,
lastModified: node.modified ? new Date(node.modified) : undefined,
};
}
@@ -190,21 +181,12 @@ export async function getDockerLatestVersion(
}
export async function getPypiLatestVersion(
- fetch: {
- (input: RequestInfo | URL, init?: RequestInit): Promise;
- (input: RequestInfo | URL, init?: RequestInit): Promise;
- },
url: string,
{ artifact, repo }: LibraryArtifact,
- identityApi: IdentityApi,
+ fetchApi: FetchApi,
) {
- const { token: idToken } = await identityApi.getCredentials();
-
- const response = await fetch(
- `${url}/artifactory/api/search/prop?pypi.name=${artifact}&repos=${repo}`, {
- headers: {
- Authorization: `Bearer ${idToken}`,
- },}
+ const response = await fetchApi.fetch(
+ `${url}artifactory/api/search/prop?pypi.name=${artifact}&repos=${repo}`,
);
if (response.status === 404) {
return undefined;
diff --git a/src/components/LibArtifactCard/codeSnippets.ts b/src/components/LibArtifactCard/codeSnippets.ts
index f6c7b57..e6fc9fe 100644
--- a/src/components/LibArtifactCard/codeSnippets.ts
+++ b/src/components/LibArtifactCard/codeSnippets.ts
@@ -1,16 +1,14 @@
import { LibraryArtifact } from '../../types';
-import {GeneratedCode} from "./api";
+import { GeneratedCode } from './api';
function removeScalaVersion(artifactName: string) {
const underscorePos = artifactName.lastIndexOf('_');
- if (underscorePos > 0) {
- if (
- artifactName.substring(underscorePos + 1).match(/^[0-9]|\?/gi) != null
- ) {
- return artifactName.substring(0, underscorePos);
- }
- }
- return artifactName;
+ const hasScalaVersion =
+ underscorePos > 0 &&
+ artifactName.substring(underscorePos + 1).match(/^[0-9]|\?/gi);
+ return hasScalaVersion
+ ? artifactName.substring(0, underscorePos)
+ : artifactName;
}
function generateGradleMavenCode(
@@ -33,40 +31,40 @@ function generateGradleMavenCode(
if (classifier !== '') {
mavenClassifier = `\n\t${classifier}`;
- gradleClassifier = ':' + classifier;
- sbtClassifier = ' classifier ' + '"' + classifier + '"';
+ gradleClassifier = `:${classifier}`;
+ sbtClassifier = ` classifier ` + `"${classifier}"`;
}
const version = lib.version !== undefined ? lib.version : '';
- const gradle =
- gradleScope +
- func1 +
- lib.group +
- ':' +
- artifactWithoutScalaVersion +
- gradleScalaVersion +
- ':' +
- version +
- gradleClassifier +
- packaging +
- func2 +
- transitive +
- '\n';
+ const gradle = `${
+ gradleScope + func1 + lib.group
+ }:${artifactWithoutScalaVersion}${gradleScalaVersion}:${version}${gradleClassifier}${packaging}${func2}${transitive}\n`;
const maven = `\n\t${lib.group}\n\t${lib.artifact}\n\t${version}${mavenClassifier}\n\t${mavenScope}\n\n`;
const sbt =
`"${lib.group}" ${sbtScalaVersion}% "${artifactWithoutScalaVersion}" % "${version}"${sbtClassifier}${sbtScope}` +
',\n';
- return { gradle: gradle, maven: maven, sbt: sbt };
+ const npm = `"${lib.artifact}": "^${version}"`;
+ // command to add the package to the project using yarn
+ const yarn = `yarn add ${lib.artifact}`;
+ const nuget = ``;
+ return {
+ gradle: gradle,
+ maven: maven,
+ sbt: sbt,
+ npm: npm,
+ nuget: nuget,
+ yarn: yarn,
+ };
}
function getScalaVersion(artifactName: string) {
const underscorePos = artifactName.lastIndexOf('_');
if (underscorePos > 0) {
const classifier = artifactName.substring(underscorePos + 1);
- if (classifier.match(/^[0-9]|\?/gi) != null) {
+ if (classifier.match(/^[0-9]|\?/gi)) {
return classifier;
}
}
@@ -82,6 +80,9 @@ export function generatePackageManagersCode(
let maven = '';
let sbt = '';
let pip = '';
+ let nuget = '';
+ let npm = '';
+ let yarn = '';
const version = lib.version !== undefined ? lib.version : '';
@@ -92,9 +93,9 @@ export function generatePackageManagersCode(
if (replaceScalaVersion && scalaVersion) {
scalaVersion = '?';
}
- let gradleScalaVersion = scalaVersion ? '_' + scalaVersion : '';
- let sbtScalaVersion = scalaVersion ? '%' : '';
- let artifactWithoutScalaVersion = removeScalaVersion(lib.artifact);
+ const gradleScalaVersion = scalaVersion ? `_${scalaVersion}` : '';
+ const sbtScalaVersion = scalaVersion ? '%' : '';
+ const artifactWithoutScalaVersion = removeScalaVersion(lib.artifact);
let gradleScope = 'implementation';
let mavenScope = 'compile';
@@ -131,7 +132,7 @@ export function generatePackageManagersCode(
}
let packaging = '';
if (lib.packaging) {
- packaging = '@' + lib.packaging;
+ packaging = `@${lib.packaging}`;
}
if (
lib.classifiers &&
@@ -156,6 +157,8 @@ export function generatePackageManagersCode(
gradle += __ret.gradle;
maven += __ret.maven;
sbt += __ret.sbt;
+ nuget += __ret.nuget;
+ npm += __ret.npm;
});
} else {
const __ret = generateGradleMavenCode(
@@ -175,11 +178,17 @@ export function generatePackageManagersCode(
gradle += __ret.gradle;
maven += __ret.maven;
sbt += __ret.sbt;
+ nuget += __ret.nuget;
+ npm += __ret.npm;
+ yarn += __ret.yarn;
}
return {
- gradle: gradle,
- maven: maven,
- sbt: sbt,
- pip: pip,
+ gradle,
+ maven,
+ sbt,
+ pip,
+ nuget,
+ npm,
+ yarn,
};
}
diff --git a/src/components/LibArtifactCard/libraryInfo.tsx b/src/components/LibArtifactCard/libraryInfo.tsx
index 9f9a509..8692adf 100644
--- a/src/components/LibArtifactCard/libraryInfo.tsx
+++ b/src/components/LibArtifactCard/libraryInfo.tsx
@@ -1,26 +1,27 @@
import { Entity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
-
-import { IdentityApi } from '@backstage/core-plugin-api';
-
import {
ENTITY_ARTIFACT,
ENTITY_GROUP,
ENTITY_PACKAGING,
ENTITY_REPO,
ENTITY_SCOPE,
- isJfrogArtifactAvailable, isJfrogRepoAvailable,
- LibraryArtifact
-} from "../../types";
+ isJfrogArtifactAvailable,
+ isJfrogRepoAvailable,
+ LibraryArtifact,
+} from '../../types';
import {
ArtifactInfo,
extractArtifactFromFullDockerName,
- getDockerLatestVersion, getMavenLatestVersion, getPypiLatestVersion,
+ getLatestCreatedVersion,
+ getMavenLatestVersion,
+ getPypiLatestVersion,
getRepositoryType,
- removeDockerVersion
-} from "./api";
-import {generatePackageManagersCode} from "./codeSnippets";
+ removeDockerVersion,
+} from './api';
+import { generatePackageManagersCode } from './codeSnippets';
+import { FetchApi } from '@backstage/core-plugin-api';
export const DEFAULT_PROXY_PATH = '/artifactory-proxy/';
@@ -46,7 +47,7 @@ export function checkAnnotationsPresent(entity: Entity) {
export async function libraryInfo(
entity: Entity,
config: Config,
- identity: IdentityApi,
+ fetchApi: FetchApi,
): Promise {
const artifactoryBackendProxy =
config.getOptionalString('jfrog.artifactory.proxyPath') ||
@@ -57,31 +58,57 @@ export async function libraryInfo(
const proxyUrl = `/proxy${artifactoryBackendProxy}`;
// try {
const url = `${backendUrl}/api${proxyUrl}`;
- const { packageType } = await getRepositoryType(fetch, url, entityArtifact,identity);
+
+ const { packageType } = await getRepositoryType(
+ url,
+ entityArtifact,
+ fetchApi,
+ );
let version;
const artInfo = { ...entityArtifact };
switch (packageType) {
+ case 'maven':
+ version = await getMavenLatestVersion(url, entityArtifact, fetchApi);
+ break;
case 'docker':
- const dockerInfo = await getDockerLatestVersion(
- fetch,
+ // eslint-disable-next-line no-case-declarations -- this just should be rewritten properly
+ const info = await getLatestCreatedVersion(
url,
entityArtifact,
- identity,
+ packageType,
+ fetchApi,
+ libArt => extractArtifactFromFullDockerName(libArt.artifact),
);
- artInfo.stats = dockerInfo?.statsDownload;
- artInfo.size = dockerInfo?.size;
+ artInfo.stats = info?.statsDownload;
+ artInfo.size = info?.size;
artInfo.artifactFullName = removeDockerVersion(artInfo.artifact);
- artInfo.lastModified = dockerInfo?.lastModified
- ? new Date(dockerInfo?.lastModified)
+ artInfo.lastModified = info?.lastModified
+ ? new Date(info?.lastModified)
: undefined;
artInfo.artifact = extractArtifactFromFullDockerName(artInfo.artifact);
- version = dockerInfo?.version;
+ version = info?.version;
break;
case 'pypi':
- version = await getPypiLatestVersion(fetch, url, entityArtifact,identity);
+ version = await getPypiLatestVersion(url, entityArtifact, fetchApi);
+ break;
+ default: {
+ const info = await getLatestCreatedVersion(
+ url,
+ entityArtifact,
+ packageType,
+ fetchApi,
+ libArt => libArt.artifact,
+ );
+ artInfo.stats = info?.statsDownload;
+ artInfo.size = info?.size;
+ artInfo.artifactFullName = artInfo.artifact;
+ artInfo.filePath = info?.filePath;
+ artInfo.lastModified = info?.lastModified
+ ? new Date(info?.lastModified)
+ : undefined;
+ version = info?.version;
break;
- default:
- version = await getMavenLatestVersion(fetch, url, entityArtifact,identity);
+ }
}
artInfo.version = version;
@@ -99,4 +126,4 @@ export async function libraryInfo(
// }
// return '';
// }
-}
\ No newline at end of file
+}
diff --git a/src/components/LibArtifactCard/utils.ts b/src/components/LibArtifactCard/utils.ts
index 5314f43..5b917ef 100644
--- a/src/components/LibArtifactCard/utils.ts
+++ b/src/components/LibArtifactCard/utils.ts
@@ -2,6 +2,7 @@ export function formatDate(date: Date): string {
if (!date) {
return 'N/A';
}
+ // eslint-disable-next-line new-cap
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const options: Intl.DateTimeFormatOptions = {
timeZone,
diff --git a/src/components/LibArtifactCard/versionUtils.ts b/src/components/LibArtifactCard/versionUtils.ts
index d777d27..513997b 100644
--- a/src/components/LibArtifactCard/versionUtils.ts
+++ b/src/components/LibArtifactCard/versionUtils.ts
@@ -1,5 +1,5 @@
function convertToNumberOrString(value: string): number | string {
- const parsedNumber = parseInt(value);
+ const parsedNumber = parseInt(value, 10);
return isNaN(parsedNumber) ? value : parsedNumber;
}
diff --git a/src/components/LibVerTabbedContent/LibVerTabbedContent.tsx b/src/components/LibVerTabbedContent/LibVerTabbedContent.tsx
index 698adc5..cac4e43 100644
--- a/src/components/LibVerTabbedContent/LibVerTabbedContent.tsx
+++ b/src/components/LibVerTabbedContent/LibVerTabbedContent.tsx
@@ -7,9 +7,10 @@ import {
} from '@backstage/core-components';
import { LibVerView } from '../LibVerView';
import React from 'react';
-import { ArtifactInfo } from '../LibArtifactCard/api';
+import { ArtifactInfo, GeneratedCode } from '../LibArtifactCard/api';
import { Divider, makeStyles } from '@material-ui/core';
import { ClassNameMap } from '@material-ui/core/styles/withStyles';
+import { capitalize } from 'lodash';
const useStyles = makeStyles({
dividerMargin: {
@@ -39,12 +40,17 @@ function isPackageType(
);
}
-function getPipTab(artifactInfo: ArtifactInfo[] | undefined) {
+function getTab(
+ type: keyof GeneratedCode,
+ language: string = 'plainText',
+ artifactInfo: ArtifactInfo[] | undefined,
+ tabTitle?: string,
+) {
return (
-
+
item.code().pip).join('\n') || ''}
+ language={language}
+ text={artifactInfo?.map(item => item.code()[type]).join('\n') || ''}
showCopyCodeButton
/>
@@ -73,42 +79,6 @@ function getDockerfileTab(artifactInfo: ArtifactInfo[] | undefined) {
);
}
-function getSbtTab(artifactInfo: ArtifactInfo[] | undefined) {
- return (
-
- item.code().sbt).join('\n') || ''}
- showCopyCodeButton
- />
-
- );
-}
-
-function getMavenTab(artifactInfo: ArtifactInfo[] | undefined) {
- return (
-
- item.code().maven).join('\n') || ''}
- showCopyCodeButton
- />
-
- );
-}
-
-function getGradleTab(artifactInfo: ArtifactInfo[] | undefined) {
- return (
-
- item.code().gradle).join('\n') || ''}
- showCopyCodeButton
- />
-
- );
-}
-
export function infoTab(
loading: boolean,
artifactInfo: ArtifactInfo[],
@@ -153,16 +123,25 @@ export const LibVerTabbedContent = ({
}
if (!loading) {
if (props.showGradle && isPackageType(props, artifactInfo, 'maven')) {
- tabs.push(getGradleTab(artifactInfo));
+ tabs.push(getTab('gradle', 'groovy', artifactInfo));
}
if (props.showMaven && isPackageType(props, artifactInfo, 'maven')) {
- tabs.push(getMavenTab(artifactInfo));
+ tabs.push(getTab('maven', 'xml', artifactInfo));
}
if (props.showSbt && isPackageType(props, artifactInfo, 'maven')) {
- tabs.push(getSbtTab(artifactInfo));
+ tabs.push(getTab('sbt', 'plainText', artifactInfo));
}
if (props.showPip && isPackageType(props, artifactInfo, 'pypi')) {
- tabs.push(getPipTab(artifactInfo));
+ tabs.push(getTab('pip', 'plainText', artifactInfo));
+ }
+ if (props.showNuget && isPackageType(props, artifactInfo, 'nuget')) {
+ tabs.push(getTab('nuget', 'xml', artifactInfo));
+ }
+ if (props.showNpm && isPackageType(props, artifactInfo, 'npm')) {
+ tabs.push(getTab('npm', 'json', artifactInfo));
+ }
+ if (props.showYarn && isPackageType(props, artifactInfo, 'npm')) {
+ tabs.push(getTab('yarn', 'plainText', artifactInfo));
}
if (props.showDockerfile && isPackageType(props, artifactInfo, 'docker')) {
tabs.push(getDockerfileTab(artifactInfo));
diff --git a/src/components/LibVerView/LibVerView.tsx b/src/components/LibVerView/LibVerView.tsx
index e7fb014..cc1075b 100644
--- a/src/components/LibVerView/LibVerView.tsx
+++ b/src/components/LibVerView/LibVerView.tsx
@@ -17,15 +17,27 @@ function getBrowseRepoUrl(artifactoryUrl: string, lib: LibraryArtifact) {
}
function getBrowsePackageUrl(artifactoryUrl: string, lib: LibraryArtifact) {
- return `${getBrowseRepoUrl(artifactoryUrl, lib)}${
- lib.group ? `/${lib.group?.replaceAll('.', '/')}` : ''
- }`;
+ let result = '';
+ if (lib.filePath) {
+ result = `/${lib.filePath}`;
+ } else {
+ if (lib.group) {
+ if (!lib.group.startsWith('/')) {
+ result += '/';
+ }
+ result += lib.group.replaceAll('.', '/');
+ }
+ }
+ return `${getBrowseRepoUrl(artifactoryUrl, lib)}${result}`;
}
export function getBrowseArtifactUrl(
artifactoryUrl: string,
lib: LibraryArtifact,
) {
+ if (lib.filePath) {
+ return `${getBrowsePackageUrl(artifactoryUrl, lib)}`;
+ }
return `${getBrowsePackageUrl(artifactoryUrl, lib)}/${lib.artifact}`;
}
@@ -33,6 +45,9 @@ export function getBrowserVersionUrl(
artUrl: string,
lib: LibraryArtifact,
): string {
+ if (lib.filePath) {
+ return `${getBrowsePackageUrl(artUrl, lib)}`;
+ }
return `${getBrowseArtifactUrl(artUrl, lib)}/${
lib.version !== undefined ? lib.version : ''
}`;
diff --git a/src/components/LibverPage/EntityLibraryFilter.tsx b/src/components/LibverPage/EntityLibraryFilter.tsx
index 41af2a4..4c5a8d0 100644
--- a/src/components/LibverPage/EntityLibraryFilter.tsx
+++ b/src/components/LibverPage/EntityLibraryFilter.tsx
@@ -8,28 +8,26 @@ import { ENTITY_ARTIFACT } from '../../types';
import { useEffect } from 'react';
class EntityLibraryFilter implements EntityFilter {
- getCatalogFilters(): Record {
- return {
- [`metadata.annotations.${ENTITY_ARTIFACT}`]: CATALOG_FILTER_EXISTS,
- };
- }
+ getCatalogFilters(): Record {
+ return {
+ [`metadata.annotations.${ENTITY_ARTIFACT}`]: CATALOG_FILTER_EXISTS,
+ };
+ }
}
export type CustomFilters = DefaultEntityFilters & {
- libraryFilters?: EntityLibraryFilter
+ libraryFilters?: EntityLibraryFilter;
};
export const EntityLibraryFilterPicker = () => {
- const {
- updateFilters
- } = useEntityList();
+ const { updateFilters } = useEntityList();
- useEffect(() => {
- const entityLibraryFilter = new EntityLibraryFilter();
- updateFilters({
- libraryFilters: entityLibraryFilter,
- });
- }, [updateFilters]);
+ useEffect(() => {
+ const entityLibraryFilter = new EntityLibraryFilter();
+ updateFilters({
+ libraryFilters: entityLibraryFilter,
+ });
+ }, [updateFilters]);
- return null;
-};
\ No newline at end of file
+ return null;
+};
diff --git a/src/components/LibverPage/LibverPage.tsx b/src/components/LibverPage/LibverPage.tsx
index bbfd88c..7161805 100644
--- a/src/components/LibverPage/LibverPage.tsx
+++ b/src/components/LibverPage/LibverPage.tsx
@@ -6,7 +6,7 @@ import {
TableColumn,
TableProps,
} from '@backstage/core-components';
-import {configApiRef, useApi} from '@backstage/core-plugin-api';
+import { configApiRef, useApi } from '@backstage/core-plugin-api';
import {
CatalogFilterLayout,
EntityKindPicker,
@@ -15,10 +15,10 @@ import {
EntityTagPicker,
EntityTypePicker,
} from '@backstage/plugin-catalog-react';
-import React, {ReactNode} from 'react';
-import {LibverTable} from '../LibverTable';
-import {EntityLibraryFilterPicker} from './EntityLibraryFilter';
-import {LibverTableRow} from '../../types';
+import React, { ReactNode } from 'react';
+import { LibverTable } from '../LibverTable';
+import { EntityLibraryFilterPicker } from './EntityLibraryFilter';
+import { LibverTableRow } from '../../types';
/**
* Props for root catalog pages.
@@ -44,7 +44,7 @@ export function LibverPageContent(props: LibverPageProps) {
-
+
@@ -68,7 +68,12 @@ export function LibverPage(props: LibverPageProps) {
return (
- {LibverPageContent(props)}
+
);
}
diff --git a/src/components/LibverPage/routes.ts b/src/components/LibverPage/routes.ts
index 0b4d3bb..4b70985 100644
--- a/src/components/LibverPage/routes.ts
+++ b/src/components/LibverPage/routes.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import {createExternalRouteRef,} from '@backstage/core-plugin-api';
+import { createExternalRouteRef } from '@backstage/core-plugin-api';
export const createComponentRouteRef = createExternalRouteRef({
id: 'create-component',
diff --git a/src/components/LibverTable/LibverTable.tsx b/src/components/LibverTable/LibverTable.tsx
index 6c5a3b6..33a397d 100644
--- a/src/components/LibverTable/LibverTable.tsx
+++ b/src/components/LibverTable/LibverTable.tsx
@@ -21,7 +21,13 @@ import { capitalize } from 'lodash';
import React, { ReactNode, useMemo, useRef, useState } from 'react';
import { columnFactories } from './columns';
import pluralize from 'pluralize';
-import { ConfigApi, configApiRef, useApi, identityApiRef, IdentityApi } from '@backstage/core-plugin-api';
+import {
+ ConfigApi,
+ configApiRef,
+ FetchApi,
+ fetchApiRef,
+ useApi,
+} from '@backstage/core-plugin-api';
import useAsync from 'react-use/lib/useAsync';
import { LibVerTabbedContent } from '../LibVerTabbedContent';
import { ArtifactInfo } from '../LibArtifactCard/api';
@@ -56,7 +62,7 @@ const refCompare = (a: LibverTableRow, b: LibverTableRow) => {
async function loadEntityItemData(
entity: Entity,
config: ConfigApi,
- identity: IdentityApi,
+ fetchApi: FetchApi,
): Promise {
const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, {
kind: 'system',
@@ -65,8 +71,9 @@ async function loadEntityItemData(
let artifactInfo;
try {
- artifactInfo = await libraryInfo(entity, config, identity);
+ artifactInfo = await libraryInfo(entity, config, fetchApi);
} catch (e) {
+ // eslint-disable-next-line no-console
console.error(e);
artifactInfo = undefined;
}
@@ -93,22 +100,35 @@ async function loadEntityItemData(
};
}
-function TableComponent(
- entities: Entity[],
- columns: TableColumn[] | undefined,
- defaultColumns: TableColumn[],
- showTypeColumn: boolean,
- filters: DefaultEntityFilters,
- titlePreamble: string,
- loading1: boolean,
- emptyContent?: ReactNode,
- tableOptions?: TableProps['options'] | undefined,
- subtitle?: string,
-) {
+type TableComponentProps = {
+ entities: Entity[];
+ columns: TableColumn[] | undefined;
+ defaultColumns: TableColumn[];
+ showTypeColumn: boolean;
+ filters: DefaultEntityFilters;
+ titlePreamble: string;
+ loading1: boolean;
+ emptyContent?: ReactNode;
+ tableOptions?: TableProps['options'] | undefined;
+ subtitle?: string;
+};
+
+const TableComponent = ({
+ entities,
+ columns,
+ defaultColumns,
+ showTypeColumn,
+ filters,
+ titlePreamble,
+ loading1,
+ emptyContent,
+ tableOptions,
+ subtitle,
+}: TableComponentProps) => {
const [rows, setRows] = useState([]);
const [selectedRows, setSelectedRows] = useState([]);
const config = useApi(configApiRef);
- const identity = useApi(identityApiRef);
+ const fetchApi = useApi(fetchApiRef);
const tableRef = useRef(); // not sure if there's a better type for this
const artifactoryUrl = useMemo(() => {
@@ -118,7 +138,7 @@ function TableComponent(
const { loading, error } = useAsync(async () => {
const results: LibverTableRow[] = [];
const libverTableRows = await Promise.allSettled(
- entities.map(entity => loadEntityItemData(entity, config, identity)),
+ entities.map(entity => loadEntityItemData(entity, config, fetchApi)),
);
libverTableRows.forEach(result => {
if (result.status === 'fulfilled') {
@@ -126,6 +146,7 @@ function TableComponent(
results.push(result.value);
} else if (result.status === 'rejected') {
// Handle rejected promise
+ // eslint-disable-next-line no-console
console.error('Rejected:', result.reason);
}
});
@@ -175,11 +196,10 @@ function TableComponent(
const items = rs
.filter(item => item.resolved.artifactInfo)
.map(item => item.resolved.artifactInfo as ArtifactInfo);
- console.log('Items ', items);
setSelectedRows(items);
}}
onRowClick={(_event, rowData) => {
- console.log('Event', rowData);
+ // console.log('Event', rowData);
if (tableRef.current) {
// noinspection JSUnresolvedReference
tableRef.current.dataManager.changeRowSelected(
@@ -189,9 +209,7 @@ function TableComponent(
// noinspection JSUnresolvedReference
tableRef.current.setState(
tableRef.current.dataManager.getRenderState(),
- function () {
- return tableRef.current.onSelectionChange();
- },
+ tableRef.current.onSelectionChange,
);
}
}}
@@ -212,6 +230,9 @@ function TableComponent(
showDockerfile: true,
showGradle: true,
showSbt: true,
+ showNpm: true,
+ showNuget: true,
+ showYarn: true,
showMaven: true,
showBrowseRepositoryLink: true,
browseRepositoryLinkTitle: 'Open repository',
@@ -224,7 +245,7 @@ function TableComponent(
);
-}
+};
export const LibverTable = (props: LibverTableProps) => {
const { columns, tableOptions, subtitle, emptyContent } = props;
@@ -290,17 +311,19 @@ export const LibverTable = (props: LibverTableProps) => {
// },
// };
// });
- return TableComponent(
- entities,
- columns,
- defaultColumns,
- showTypeColumn,
- filters,
- titlePreamble,
- loading,
- emptyContent,
- tableOptions,
- subtitle,
+ return (
+
);
};
diff --git a/src/components/LibverTable/columns.tsx b/src/components/LibverTable/columns.tsx
index d734403..15bfe2c 100644
--- a/src/components/LibverTable/columns.tsx
+++ b/src/components/LibverTable/columns.tsx
@@ -20,11 +20,14 @@ import {
EntityRefLinks,
} from '@backstage/plugin-catalog-react';
import { Chip } from '@material-ui/core';
-import {Link, OverflowTooltip, TableColumn} from '@backstage/core-components';
+import { Link, OverflowTooltip, TableColumn } from '@backstage/core-components';
import { Entity } from '@backstage/catalog-model';
import { JsonArray } from '@backstage/types';
-import {getBrowseArtifactUrl, getBrowserVersionUrl} from "../LibVerView/LibVerView";
-import {LibverTableRow} from "../../types";
+import {
+ getBrowseArtifactUrl,
+ getBrowserVersionUrl,
+} from '../LibVerView/LibVerView';
+import { LibverTableRow } from '../../types';
// The columnFactories symbol is not directly exported, but through the
// CatalogTable.columns field.
@@ -119,15 +122,15 @@ export const columnFactories = Object.freeze({
render: ({ resolved }) => {
if (resolved.artifactInfo) {
return (
-
- {resolved.artifactInfo.lib.version || '?'}
-
+
+ {resolved.artifactInfo.lib.version || '?'}
+
);
}
return null;
diff --git a/src/index.ts b/src/index.ts
index f7b80d2..766d480 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -15,4 +15,4 @@
*/
export * from './plugin';
export * from './types';
-export * from './components';
\ No newline at end of file
+export * from './components';
diff --git a/src/types.ts b/src/types.ts
index 8de1164..847ef19 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -19,6 +19,9 @@ export type LibArtifactCardProps = {
showMaven: boolean;
showSbt: boolean;
showPip: boolean;
+ showNuget: boolean;
+ showNpm: boolean;
+ showYarn: boolean;
showDockerfile: boolean;
autohideTabs: boolean;
showBrowseRepositoryLink: boolean;
@@ -47,6 +50,7 @@ export type LibraryArtifact = {
artifactFullName?: string;
repo: string;
version?: string;
+ filePath?: string;
classifiers?: string[];
scope?: string;
transitive?: boolean;