Merge remote-tracking branch 'upstream/master' into expose-reading-msgraph-provider-config
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
`knex` has been bumped to major version 3 and `better-sqlite3` to major version 9, which deprecate node 16 support.
|
||||
|
||||
You can do the same in your own Backstage repository to ensure that you get future node 18+ relevant updates, by having the following lines in your `packages/backend/package.json`:
|
||||
|
||||
```
|
||||
"dependencies": {
|
||||
// ...
|
||||
"knex": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
// ...
|
||||
"better-sqlite3": "^9.0.0",
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-bazaar': patch
|
||||
---
|
||||
|
||||
Updated Readme document in bazaar plugin
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-shortcuts': patch
|
||||
---
|
||||
|
||||
Ensure that shortcuts aren't duplicate-checked against themselves
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
|
||||
'@backstage/plugin-catalog-backend-module-unprocessed': patch
|
||||
'@backstage/plugin-search-backend-module-pg': patch
|
||||
'@backstage/plugin-entity-feedback-backend': patch
|
||||
'@backstage/plugin-code-coverage-backend': patch
|
||||
'@backstage/plugin-tech-insights-backend': patch
|
||||
'@backstage/plugin-user-settings-backend': patch
|
||||
'@backstage/backend-plugin-api': patch
|
||||
'@backstage/backend-test-utils': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
'@backstage/plugin-linguist-backend': patch
|
||||
'@backstage/plugin-playlist-backend': patch
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/backend-tasks': patch
|
||||
'@backstage/plugin-badges-backend': patch
|
||||
'@backstage/plugin-bazaar-backend': patch
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
'@backstage/plugin-app-backend': patch
|
||||
---
|
||||
|
||||
`knex` has been bumped to major version 3 and `better-sqlite3` to major version 9, which deprecate node 16 support.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Implement new `AppTreeApi`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
---
|
||||
|
||||
Add new `AppTreeApi`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Added missing node-gyp dependency to fix Docker image build
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Removed unnecessary `@backstage/integration` dependency, replaced by `@backstage/integration-react`.
|
||||
@@ -1,11 +1,8 @@
|
||||
---
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- about: 'Please ask and answer usage questions in GitHub Discussions'
|
||||
name: Question
|
||||
url: 'https://github.com/backstage/backstage/discussions'
|
||||
- about: 'Alternatively, you can use the Backstage Community Discord'
|
||||
name: Chat
|
||||
- about: 'Use the Backstage Community Discord for questions & discussions'
|
||||
name: Questions
|
||||
url: 'https://discord.gg/backstage-687207715902193673'
|
||||
- about: 'Please check the FAQ before filing new issues'
|
||||
name: 'Backstage FAQ'
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Run analysis'
|
||||
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -66,6 +66,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
|
||||
uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -58,6 +58,6 @@ jobs:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=7168
|
||||
- name: Upload Snyk report
|
||||
uses: github/codeql-action/upload-sarif@v2.22.4
|
||||
uses: github/codeql-action/upload-sarif@v2.22.5
|
||||
with:
|
||||
sarif_file: snyk.sarif
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2.22.4
|
||||
uses: github/codeql-action/init@v2.22.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2.22.4
|
||||
uses: github/codeql-action/autobuild@v2.22.5
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -80,4 +80,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2.22.4
|
||||
uses: github/codeql-action/analyze@v2.22.5
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
|
||||
- run: yarn build-storybook
|
||||
|
||||
- uses: chromaui/action@c7e9d129ad2b8e728e10522270e14596473a7958 # v1
|
||||
- uses: chromaui/action@d726e4e790a99e876f71b8e09d3053bfe783d6b8 # v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# projectToken intentionally shared to allow collaborators to run Chromatic on forks
|
||||
|
||||
@@ -194,8 +194,6 @@ WORKDIR /app
|
||||
|
||||
COPY --from=packages --chown=node:node /app .
|
||||
|
||||
# Stop cypress from downloading it's massive binary.
|
||||
ENV CYPRESS_INSTALL_BINARY=0
|
||||
RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
|
||||
yarn install --frozen-lockfile --network-timeout 600000
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Nexus Repository Manager
|
||||
author: Red Hat
|
||||
authorUrl: https://redhat.com
|
||||
category: Image
|
||||
description: View information about the build artifacts in your Nexus Repository Manager in Backstage.
|
||||
documentation: https://janus-idp.io/plugins/nexus-repository-manager
|
||||
iconUrl: https://janus-idp.io/images/plugins/nexus-repository-manager.svg
|
||||
npmPackageName: '@janus-idp/backstage-plugin-nexus-repository-manager'
|
||||
addedDate: '2023-10-25'
|
||||
+6
-6
@@ -3055,9 +3055,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/luxon@npm:^3.0.0":
|
||||
version: 3.3.2
|
||||
resolution: "@types/luxon@npm:3.3.2"
|
||||
checksum: b9111132720eae0269538872a5a496b29587ecfc8edc3b0ff7d269aa93a5ff00a131b23d1e9d1f12ec39f2c779ad21bd8d9f90b122c85a182771aabde7f676b8
|
||||
version: 3.3.3
|
||||
resolution: "@types/luxon@npm:3.3.3"
|
||||
checksum: 072dd39eea3f63453788fab2fcfc83eb33917afcaffe178ce08ecd8b016824b8ab3bfa991f66266f2fc1927768a56b4334945f2eb1d83638e325c0c43d7d0e86
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3265,9 +3265,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/webpack-env@npm:^1.18.0":
|
||||
version: 1.18.2
|
||||
resolution: "@types/webpack-env@npm:1.18.2"
|
||||
checksum: 883908ade827d35a10efc574fb6f2728a7c520d4296cf1507633ac7457204ccd697bc6c8cadac99bc5d96074a6109c658ebfde59f42ba5ba0fdfffc538892b0f
|
||||
version: 1.18.3
|
||||
resolution: "@types/webpack-env@npm:1.18.3"
|
||||
checksum: f24e82485d8e325b1875608766ba6dad2b2f53a0fb182bc96173b4590110c6b791163402cdf19b50c04e8c05292f227a08aafe9230b2bba52c40c5f7ceccc101
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api';
|
||||
import { IdentityService } from '@backstage/backend-plugin-api';
|
||||
import { isChildPath } from '@backstage/cli-common';
|
||||
import { Knex } from 'knex';
|
||||
import knexFactory from 'knex';
|
||||
import { KubeConfig } from '@kubernetes/client-node';
|
||||
import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoadConfigOptionsRemote } from '@backstage/config-loader';
|
||||
@@ -222,7 +223,7 @@ export function createDatabaseClient(
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): Knex<any, any[]>;
|
||||
): knexFactory.Knex<any, any[]>;
|
||||
|
||||
// @public
|
||||
export function createRootLogger(
|
||||
|
||||
@@ -89,14 +89,14 @@
|
||||
"isomorphic-git": "^1.23.0",
|
||||
"jose": "^4.6.0",
|
||||
"keyv": "^4.5.2",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"logform": "^2.3.2",
|
||||
"luxon": "^3.0.0",
|
||||
"minimatch": "^5.0.0",
|
||||
"mysql2": "^2.2.5",
|
||||
"node-fetch": "^2.6.7",
|
||||
"pg": "^8.3.0",
|
||||
"pg": "^8.11.3",
|
||||
"raw-body": "^2.4.1",
|
||||
"tar": "^6.1.12",
|
||||
"uuid": "^8.3.2",
|
||||
@@ -129,7 +129,7 @@
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@types/yauzl": "^2.10.0",
|
||||
"aws-sdk-client-mock": "^2.0.0",
|
||||
"better-sqlite3": "^8.0.0",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"msw": "^1.0.0",
|
||||
"mysql2": "^2.2.5",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"@backstage/types": "workspace:^",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"knex": "^2.0.0"
|
||||
"knex": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"@opentelemetry/api": "^1.3.0",
|
||||
"@types/luxon": "^3.0.0",
|
||||
"cron": "^2.0.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
"uuid": "^8.0.0",
|
||||
|
||||
@@ -49,13 +49,13 @@
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"better-sqlite3": "^8.0.0",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"express": "^4.17.1",
|
||||
"fs-extra": "^10.0.1",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"msw": "^1.0.0",
|
||||
"mysql2": "^2.2.5",
|
||||
"pg": "^8.3.0",
|
||||
"pg": "^8.11.3",
|
||||
"testcontainers": "^8.1.2",
|
||||
"textextensions": "^5.16.0",
|
||||
"uuid": "^8.0.0"
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
"@opentelemetry/exporter-prometheus": "^0.44.0",
|
||||
"@opentelemetry/sdk-metrics": "^1.13.0",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"better-sqlite3": "^8.0.0",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"dockerode": "^3.3.1",
|
||||
"example-app": "link:../app",
|
||||
"express": "^4.17.1",
|
||||
@@ -92,7 +92,7 @@
|
||||
"express-promise-router": "^4.1.0",
|
||||
"luxon": "^3.0.0",
|
||||
"mysql2": "^2.2.5",
|
||||
"pg": "^8.3.0",
|
||||
"pg": "^8.11.3",
|
||||
"pg-connection-string": "^2.3.0",
|
||||
"prom-client": "^14.0.1",
|
||||
"winston": "^3.2.1"
|
||||
|
||||
+7
-3
@@ -55,8 +55,12 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
|
||||
apply(next: typeof fetch): typeof fetch {
|
||||
return async (input, init) => {
|
||||
// Skip this middleware if the header already exists, or if the URL
|
||||
// doesn't match any of the allowlist items, or if there was no token
|
||||
const request = new Request(input, init);
|
||||
// doesn't match any of the allowlist items, or if there was no token.
|
||||
// NOTE(freben): The "as any" casts here and below are because of subtle
|
||||
// undici type differences that happened in a node types bump. Those are
|
||||
// immaterial to the code at hand at runtime, as the global fetch and
|
||||
// Request are always taken from the same place.
|
||||
const request = new Request(input as any, init);
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
if (
|
||||
request.headers.get(this.headerName) ||
|
||||
@@ -64,7 +68,7 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
|
||||
!token ||
|
||||
!this.allowUrl(request.url)
|
||||
) {
|
||||
return next(input, init);
|
||||
return next(input as any, init);
|
||||
}
|
||||
|
||||
request.headers.set(this.headerName, this.headerValue(token));
|
||||
|
||||
+7
-3
@@ -34,11 +34,15 @@ export class PluginProtocolResolverFetchMiddleware implements FetchMiddleware {
|
||||
|
||||
apply(next: typeof fetch): typeof fetch {
|
||||
return async (input, init) => {
|
||||
const request = new Request(input, init);
|
||||
// NOTE(freben): The "as any" casts here and below are because of subtle
|
||||
// undici type differences that happened in a node types bump. Those are
|
||||
// immaterial to the code at hand at runtime, as the global fetch and
|
||||
// Request are always taken from the same place.
|
||||
const request = new Request(input as any, init);
|
||||
const prefix = 'plugin://';
|
||||
|
||||
if (!request.url.startsWith(prefix)) {
|
||||
return next(input, init);
|
||||
return next(input as any, init);
|
||||
}
|
||||
|
||||
// Switch to a known protocol, since browser URL parsing misbehaves wildly
|
||||
@@ -57,7 +61,7 @@ export class PluginProtocolResolverFetchMiddleware implements FetchMiddleware {
|
||||
const target = `${join(base, pathname)}${search}${hash}`;
|
||||
return next(
|
||||
target,
|
||||
typeof input === 'string' || isUrl(input) ? init : input,
|
||||
typeof input === 'string' || isUrl(input) ? init : (input as any),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,11 +34,12 @@
|
||||
"@backstage/plugin-search-backend-node": "^{{version '@backstage/plugin-search-backend-node'}}",
|
||||
"@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}",
|
||||
"app": "link:../app",
|
||||
"better-sqlite3": "^8.0.0",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"dockerode": "^3.3.1",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"pg": "^8.3.0",
|
||||
"node-gyp": "^9.0.0",
|
||||
"pg": "^8.11.3",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { RouteRef, coreExtensionData } from '@backstage/frontend-plugin-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { toLegacyPlugin } from '../wiring/createApp';
|
||||
import { BackstageRouteObject } from './types';
|
||||
import { AppNode } from '../tree';
|
||||
import { AppNode } from '@backstage/frontend-plugin-api';
|
||||
|
||||
// We always add a child that matches all subroutes but without any route refs. This makes
|
||||
// sure that we're always able to match each route no matter how deep the navigation goes.
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import { readAppExtensionsConfig } from './readAppExtensionsConfig';
|
||||
import { resolveAppTree } from './resolveAppTree';
|
||||
import { resolveAppNodeSpecs } from './resolveAppNodeSpecs';
|
||||
import { AppTree } from './types';
|
||||
import { AppTree } from '@backstage/frontend-plugin-api';
|
||||
import { Config } from '@backstage/config';
|
||||
import { instantiateAppNodeTree } from './instantiateAppNodeTree';
|
||||
|
||||
|
||||
@@ -14,10 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type {
|
||||
AppNode,
|
||||
AppNodeEdges,
|
||||
AppNodeInstance,
|
||||
AppNodeSpec,
|
||||
} from './types';
|
||||
export { createAppTree } from './createAppTree';
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
createAppNodeInstance,
|
||||
instantiateAppNodeTree,
|
||||
} from './instantiateAppNodeTree';
|
||||
import { AppNodeInstance, AppNodeSpec } from './types';
|
||||
import { AppNodeInstance, AppNodeSpec } from '@backstage/frontend-plugin-api';
|
||||
import { resolveAppTree } from './resolveAppTree';
|
||||
|
||||
const testDataRef = createExtensionDataRef<string>('test');
|
||||
|
||||
@@ -20,7 +20,11 @@ import {
|
||||
ExtensionDataRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import { AppNode, AppNodeInstance, AppNodeSpec } from './types';
|
||||
import {
|
||||
AppNode,
|
||||
AppNodeInstance,
|
||||
AppNodeSpec,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
type Mutable<T> = {
|
||||
-readonly [P in keyof T]: T[P];
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides';
|
||||
import { ExtensionParameters } from './readAppExtensionsConfig';
|
||||
import { AppNodeSpec } from './types';
|
||||
import { AppNodeSpec } from '@backstage/frontend-plugin-api';
|
||||
|
||||
/** @internal */
|
||||
export function resolveAppNodeSpecs(options: {
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AppTree, AppNode, AppNodeInstance, AppNodeSpec } from './types';
|
||||
import {
|
||||
AppTree,
|
||||
AppNode,
|
||||
AppNodeInstance,
|
||||
AppNodeSpec,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
function indent(str: string) {
|
||||
return str.replace(/^/gm, ' ');
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AppTreeApi,
|
||||
appTreeApiRef,
|
||||
createPageExtension,
|
||||
createPlugin,
|
||||
createThemeExtension,
|
||||
@@ -23,6 +25,7 @@ import { screen, waitFor } from '@testing-library/react';
|
||||
import { createApp } from './createApp';
|
||||
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
describe('createApp', () => {
|
||||
it('should allow themes to be installed', async () => {
|
||||
@@ -90,4 +93,58 @@ describe('createApp', () => {
|
||||
expect(screen.getByText('Last Page')).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should make the app structure available through the AppTreeApi', async () => {
|
||||
let appTreeApi: AppTreeApi | undefined = undefined;
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => new MockConfigApi({}),
|
||||
features: [
|
||||
createPlugin({
|
||||
id: 'my-plugin',
|
||||
extensions: [
|
||||
createPageExtension({
|
||||
id: 'plugin.my-plugin.page',
|
||||
defaultPath: '/',
|
||||
loader: async () => {
|
||||
const Component = () => {
|
||||
appTreeApi = useApi(appTreeApiRef);
|
||||
return <div>My Plugin Page</div>;
|
||||
};
|
||||
return <Component />;
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
await renderWithEffects(app.createRoot());
|
||||
|
||||
expect(appTreeApi).toBeDefined();
|
||||
const { tree } = appTreeApi!.getTree();
|
||||
|
||||
expect(String(tree.root)).toMatchInlineSnapshot(`
|
||||
"<core out=[core.reactElement]>
|
||||
root [
|
||||
<core.layout out=[core.reactElement]>
|
||||
content [
|
||||
<core.routes out=[core.reactElement]>
|
||||
routes [
|
||||
<plugin.my-plugin.page out=[core.routing.path, core.routing.ref, core.reactElement] />
|
||||
]
|
||||
</core.routes>
|
||||
]
|
||||
nav [
|
||||
<core.nav out=[core.reactElement] />
|
||||
]
|
||||
</core.layout>
|
||||
]
|
||||
themes [
|
||||
<themes.light out=[core.theme] />
|
||||
<themes.dark out=[core.theme] />
|
||||
]
|
||||
</core>"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import React, { JSX } from 'react';
|
||||
import { ConfigReader, Config } from '@backstage/config';
|
||||
import {
|
||||
AppTree,
|
||||
appTreeApiRef,
|
||||
BackstagePlugin,
|
||||
coreExtensionData,
|
||||
ExtensionDataRef,
|
||||
@@ -87,7 +89,8 @@ import { AppRouteBinder } from '../routing';
|
||||
import { RoutingProvider } from '../routing/RoutingProvider';
|
||||
import { resolveRouteBindings } from '../routing/resolveRouteBindings';
|
||||
import { collectRouteIds } from '../routing/collectRouteIds';
|
||||
import { AppNode, createAppTree } from '../tree';
|
||||
import { createAppTree } from '../tree';
|
||||
import { AppNode } from '@backstage/frontend-plugin-api';
|
||||
|
||||
const builtinExtensions = [
|
||||
Core,
|
||||
@@ -262,7 +265,7 @@ export function createApp(options: {
|
||||
const routeIds = collectRouteIds(allFeatures);
|
||||
|
||||
const App = () => (
|
||||
<ApiProvider apis={createApiHolder(tree.root, config)}>
|
||||
<ApiProvider apis={createApiHolder(tree, config)}>
|
||||
<AppContextProvider appContext={appContext}>
|
||||
<AppThemeProvider>
|
||||
<RoutingProvider
|
||||
@@ -356,17 +359,17 @@ function createLegacyAppContext(plugins: BackstagePlugin[]): AppContext {
|
||||
};
|
||||
}
|
||||
|
||||
function createApiHolder(core: AppNode, configApi: ConfigApi): ApiHolder {
|
||||
function createApiHolder(tree: AppTree, configApi: ConfigApi): ApiHolder {
|
||||
const factoryRegistry = new ApiFactoryRegistry();
|
||||
|
||||
const pluginApis =
|
||||
core.edges.attachments
|
||||
tree.root.edges.attachments
|
||||
.get('apis')
|
||||
?.map(e => e.instance?.getData(coreExtensionData.apiFactory))
|
||||
.filter((x): x is AnyApiFactory => !!x) ?? [];
|
||||
|
||||
const themeExtensions =
|
||||
core.edges.attachments
|
||||
tree.root.edges.attachments
|
||||
.get('themes')
|
||||
?.map(e => e.instance?.getData(coreExtensionData.theme))
|
||||
.filter((x): x is AppTheme => !!x) ?? [];
|
||||
@@ -414,6 +417,14 @@ function createApiHolder(core: AppNode, configApi: ConfigApi): ApiHolder {
|
||||
},
|
||||
});
|
||||
|
||||
factoryRegistry.register('static', {
|
||||
api: appTreeApiRef,
|
||||
deps: {},
|
||||
factory: () => ({
|
||||
getTree: () => ({ tree }),
|
||||
}),
|
||||
});
|
||||
|
||||
factoryRegistry.register('static', {
|
||||
api: appThemeApiRef,
|
||||
deps: {},
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { AnyApiFactory } from '@backstage/core-plugin-api';
|
||||
import { AnyApiRef } from '@backstage/core-plugin-api';
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AppTheme } from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
@@ -55,6 +56,66 @@ export type AnyRoutes = {
|
||||
[name in string]: RouteRef;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface AppNode {
|
||||
readonly edges: AppNodeEdges;
|
||||
readonly instance?: AppNodeInstance;
|
||||
readonly spec: AppNodeSpec;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface AppNodeEdges {
|
||||
// (undocumented)
|
||||
readonly attachedTo?: {
|
||||
node: AppNode;
|
||||
input: string;
|
||||
};
|
||||
// (undocumented)
|
||||
readonly attachments: ReadonlyMap<string, AppNode[]>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface AppNodeInstance {
|
||||
getData<T>(ref: ExtensionDataRef<T>): T | undefined;
|
||||
getDataRefs(): Iterable<ExtensionDataRef<unknown>>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface AppNodeSpec {
|
||||
// (undocumented)
|
||||
readonly attachTo: {
|
||||
id: string;
|
||||
input: string;
|
||||
};
|
||||
// (undocumented)
|
||||
readonly config?: unknown;
|
||||
// (undocumented)
|
||||
readonly disabled: boolean;
|
||||
// (undocumented)
|
||||
readonly extension: Extension<unknown>;
|
||||
// (undocumented)
|
||||
readonly id: string;
|
||||
// (undocumented)
|
||||
readonly source?: BackstagePlugin;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface AppTree {
|
||||
readonly nodes: ReadonlyMap<string, AppNode>;
|
||||
readonly orphans: Iterable<AppNode>;
|
||||
readonly root: AppNode;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface AppTreeApi {
|
||||
getTree(): {
|
||||
tree: AppTree;
|
||||
};
|
||||
}
|
||||
|
||||
// @public
|
||||
export const appTreeApiRef: ApiRef<AppTreeApi>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BackstagePlugin<
|
||||
Routes extends AnyRoutes = AnyRoutes,
|
||||
|
||||
+34
-21
@@ -14,20 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
BackstagePlugin,
|
||||
Extension,
|
||||
ExtensionDataRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
/*
|
||||
NOTE: These types are marked as @internal for now, but the intention is for this to be a public API in the future.
|
||||
*/
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin, Extension, ExtensionDataRef } from '../../wiring';
|
||||
|
||||
/**
|
||||
* The specification for this node in the app tree.
|
||||
* The specification for this {@link AppNode} in the {@link AppTree}.
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* The specifications for a collection of app nodes is all the information needed
|
||||
@@ -43,9 +36,9 @@ export interface AppNodeSpec {
|
||||
}
|
||||
|
||||
/**
|
||||
* The connections from this node to other nodes.
|
||||
* The connections from this {@link AppNode} to other nodes.
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* The app node edges are resolved based on the app node specs, regardless of whether
|
||||
@@ -57,9 +50,9 @@ export interface AppNodeEdges {
|
||||
}
|
||||
|
||||
/**
|
||||
* The instance of this node in the app tree.
|
||||
* The instance of this {@link AppNode} in the {@link AppTree}.
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* The app node instance is created when the `factory` function of an extension is called.
|
||||
@@ -74,8 +67,9 @@ export interface AppNodeInstance {
|
||||
}
|
||||
|
||||
/**
|
||||
* A node in the {@link AppTree}.
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
*/
|
||||
export interface AppNode {
|
||||
/** The specification for how this node should be instantiated */
|
||||
@@ -87,15 +81,34 @@ export interface AppNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* The app tree containing all nodes of the app.
|
||||
* The app tree containing all {@link AppNode}s of the app.
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
*/
|
||||
export interface AppTree {
|
||||
/** The root node of the app */
|
||||
root: AppNode;
|
||||
readonly root: AppNode;
|
||||
/** A map of all nodes in the app by ID, including orphaned or disabled nodes */
|
||||
nodes: ReadonlyMap<string /* id */, AppNode>;
|
||||
readonly nodes: ReadonlyMap<string /* id */, AppNode>;
|
||||
/** A sequence of all nodes with a parent that is not reachable from the app root node */
|
||||
orphans: Iterable<AppNode>;
|
||||
readonly orphans: Iterable<AppNode>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The API for interacting with the {@link AppTree}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface AppTreeApi {
|
||||
/**
|
||||
* Get the {@link AppTree} for the app.
|
||||
*/
|
||||
getTree(): { tree: AppTree };
|
||||
}
|
||||
|
||||
/**
|
||||
* The `ApiRef` of {@link AppTreeApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const appTreeApiRef = createApiRef<AppTreeApi>({ id: 'core.app-tree' });
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
appTreeApiRef,
|
||||
type AppNode,
|
||||
type AppNodeEdges,
|
||||
type AppNodeInstance,
|
||||
type AppNodeSpec,
|
||||
type AppTree,
|
||||
type AppTreeApi,
|
||||
} from './AppTreeApi';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 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 './definitions';
|
||||
@@ -20,6 +20,7 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './apis';
|
||||
export * from './components';
|
||||
export * from './extensions';
|
||||
export * from './routing';
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"fs-extra": "10.1.0",
|
||||
"globby": "^11.0.0",
|
||||
"helmet": "^6.0.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
"winston": "^3.2.1",
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/passport": "^1.0.3",
|
||||
"compression": "^1.7.4",
|
||||
"connect-session-knex": "^3.0.1",
|
||||
"connect-session-knex": "^4.0.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
@@ -61,7 +61,7 @@
|
||||
"google-auth-library": "^8.0.0",
|
||||
"jose": "^4.6.0",
|
||||
"jwt-decode": "^3.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
"minimatch": "^5.0.0",
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^2.4.2",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"supertest": "^6.3.3",
|
||||
"uuid": "^9.0.0",
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
### What is the Bazaar?
|
||||
|
||||
The Bazaar is a place where teams can propose projects for cross-functional team development. Essentially a marketplace for internal projects suitable for [Inner Sourcing](https://en.wikipedia.org/wiki/Inner_source). With "Inner Sourcing", we mean projects that are developed internally within a company, but with Open Source best practices.
|
||||
The Bazaar is a place where teams can propose projects for cross-functional team development. Essentially, it’s a marketplace for internal projects suitable for [Inner Sourcing](https://en.wikipedia.org/wiki/Inner_source). By “Inner Sourcing,” we mean projects that are developed internally within a company but follow Open Source best practices.
|
||||
|
||||
### Why?
|
||||
|
||||
Many companies today are of high need to increase the ease of cross-team cooperation. In large organizations, engineers often have limited ways of discovering or announcing the projects which could benefit from a wider development effort in terms of different expertise, experiences, and teams spread across the organization. With no good way to find these existing internal projects to join, the possibility of working with Inner Sourcing practices suffers.
|
||||
Many companies today have a high need to increase the ease of cross-team cooperation. In large organizations, engineers often have limited ways of discovering or announcing projects that could benefit from a wider development effort in terms of different expertise, experiences, and teams spread across the organization. With no good way to find these existing internal projects to join, the possibility of working with Inner Sourcing practices suffers.
|
||||
|
||||
### How?
|
||||
|
||||
The Bazaar allows engineers and teams to open up and announce their new and exciting projects for transparent cooperation in other parts of larger organizations. The Bazaar ensures that new Inner Sourcing friendly projects gain visibility through Backstage and a way for interested engineers to show their interest and in the future contribute with their specific skill set. The Bazaar also provides an easy way to manage, catalog, and browse these Inner Sourcing friendly projects and components.
|
||||
The Bazaar allows engineers and teams to open up and announce their new and exciting projects for transparent cooperation in other parts of larger organizations. The Bazaar ensures that new Inner Sourcing-friendly projects gain visibility through Backstage and a way for interested engineers to show their interest and, in the future, contribute with their specific skill set. The Bazaar also provides an easy way to manage, catalog, and browse these Inner Sourcing-friendly projects and components.
|
||||
|
||||
# Note
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"@types/luxon": "^3.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"luxon": "^3.0.0",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.2.1"
|
||||
|
||||
@@ -38,6 +38,6 @@
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"express-promise-router": "^4.1.1",
|
||||
"knex": "^2.4.2"
|
||||
"knex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"fs-extra": "10.1.0",
|
||||
"git-url-parse": "^13.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
"minimatch": "^5.0.0",
|
||||
@@ -93,7 +93,7 @@
|
||||
"@types/lodash": "^4.14.151",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@types/uuid": "^8.0.0",
|
||||
"better-sqlite3": "^8.0.0",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"luxon": "^3.0.0",
|
||||
"msw": "^1.0.0",
|
||||
"supertest": "^6.1.3",
|
||||
|
||||
@@ -22,7 +22,7 @@ import { PropsWithChildren } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { StyleRules } from '@material-ui/core/styles/withStyles';
|
||||
import { SystemEntity } from '@backstage/catalog-model';
|
||||
import { TableColumn } from '@backstage/core-components';
|
||||
@@ -597,7 +597,7 @@ export function getEntityRelations(
|
||||
// @public (undocumented)
|
||||
export function getEntitySourceLocation(
|
||||
entity: Entity,
|
||||
scmIntegrationsApi: ScmIntegrationRegistry,
|
||||
scmIntegrationsApi: typeof scmIntegrationsApiRef.T,
|
||||
): EntitySourceLocation | undefined;
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/integration": "workspace:^",
|
||||
"@backstage/integration-react": "workspace:^",
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"@backstage/plugin-permission-common": "workspace:^",
|
||||
"@backstage/plugin-permission-react": "workspace:^",
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
Entity,
|
||||
parseLocationRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
|
||||
/** @public */
|
||||
export type EntitySourceLocation = {
|
||||
@@ -30,7 +30,7 @@ export type EntitySourceLocation = {
|
||||
/** @public */
|
||||
export function getEntitySourceLocation(
|
||||
entity: Entity,
|
||||
scmIntegrationsApi: ScmIntegrationRegistry,
|
||||
scmIntegrationsApi: typeof scmIntegrationsApiRef.T,
|
||||
): EntitySourceLocation | undefined {
|
||||
const sourceLocation =
|
||||
entity.metadata.annotations?.[ANNOTATION_SOURCE_LOCATION];
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"body-parser-xml": "^2.0.5",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@types/express": "*",
|
||||
"express": "^4.18.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"express": "^4.18.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"linguist-js": "^2.5.3",
|
||||
"luxon": "^2.0.2",
|
||||
"node-fetch": "^2.6.7",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@types/express": "*",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"uuid": "^8.2.0",
|
||||
"winston": "^3.2.1",
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"isolated-vm": "^4.5.0",
|
||||
"isomorphic-git": "^1.23.0",
|
||||
"jsonschema": "^1.2.6",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"libsodium-wrappers": "^0.7.11",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-search-backend-node": "workspace:^",
|
||||
"@backstage/plugin-search-common": "workspace:^",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.2.1"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { useForm, SubmitHandler, Controller } from 'react-hook-form';
|
||||
import {
|
||||
@@ -58,6 +58,10 @@ export const ShortcutForm = ({
|
||||
shortcutApi.shortcut$(),
|
||||
shortcutApi.get(),
|
||||
);
|
||||
const { current: originalValues } = useRef({
|
||||
url: formValues?.url ?? '',
|
||||
title: formValues?.title ?? '',
|
||||
});
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
@@ -65,20 +69,23 @@ export const ShortcutForm = ({
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
defaultValues: {
|
||||
url: formValues?.url ?? '',
|
||||
title: formValues?.title ?? '',
|
||||
},
|
||||
defaultValues: originalValues,
|
||||
});
|
||||
|
||||
const titleIsUnique = (title: string) => {
|
||||
if (shortcutData.some(shortcutTitle => shortcutTitle.title === title))
|
||||
if (
|
||||
title !== originalValues.title &&
|
||||
shortcutData.some(shortcutTitle => shortcutTitle.title === title)
|
||||
)
|
||||
return 'A shortcut with this title already exists';
|
||||
return true;
|
||||
};
|
||||
|
||||
const urlIsUnique = (url: string) => {
|
||||
if (shortcutData.some(shortcutUrl => shortcutUrl.url === url))
|
||||
if (
|
||||
url !== originalValues.url &&
|
||||
shortcutData.some(shortcutUrl => shortcutUrl.url === url)
|
||||
)
|
||||
return 'A shortcut with this url already exists';
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"@types/luxon": "^3.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.0.0",
|
||||
"semver": "^7.5.3",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"fs-extra": "10.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-limit": "^3.1.0",
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^2.0.0",
|
||||
"knex": "^3.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user