@backstage/backend-defaults 0.17.8 → 0.17.9-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,16 +5,21 @@ var fetch = require('node-fetch');
5
5
  var ReadUrlResponseFactory = require('./ReadUrlResponseFactory.cjs.js');
6
6
  var errors = require('@backstage/errors');
7
7
  var node_stream = require('node:stream');
8
+ var FetchUrlReader = require('./FetchUrlReader.cjs.js');
8
9
 
9
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
11
 
11
12
  var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
12
13
 
14
+ const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];
15
+ const MAX_REDIRECTS = 5;
13
16
  class HarnessUrlReader {
14
17
  static factory = ({ config, treeResponseFactory }) => {
18
+ const allowedRedirectPredicate = FetchUrlReader.predicateFromConfig(config);
15
19
  return integration.ScmIntegrations.fromConfig(config).harness.list().map((integration) => {
16
20
  const reader = new HarnessUrlReader(integration, {
17
- treeResponseFactory
21
+ treeResponseFactory,
22
+ allowedRedirectPredicate
18
23
  });
19
24
  const predicate = (url) => {
20
25
  return url.host === integration.config.host;
@@ -24,26 +29,19 @@ class HarnessUrlReader {
24
29
  };
25
30
  integration;
26
31
  deps;
32
+ allowedRedirectPredicate;
27
33
  constructor(integration, deps) {
28
34
  this.integration = integration;
29
35
  this.deps = deps;
36
+ this.allowedRedirectPredicate = deps.allowedRedirectPredicate ?? (() => false);
30
37
  }
31
38
  async read(url) {
32
39
  const response = await this.readUrl(url);
33
40
  return response.buffer();
34
41
  }
35
42
  async readUrl(url, options) {
36
- let response;
37
43
  const blobUrl = integration.getHarnessFileContentsUrl(this.integration.config, url);
38
- try {
39
- response = await fetch__default.default(blobUrl, {
40
- method: "GET",
41
- ...integration.getHarnessRequestOptions(this.integration.config),
42
- signal: options?.signal
43
- });
44
- } catch (e) {
45
- throw new Error(`Unable to read ${blobUrl}, ${e}`);
46
- }
44
+ const response = await this.fetchUrl(blobUrl, options?.signal);
47
45
  if (response.ok) {
48
46
  const jsonResponse = { data: response.body };
49
47
  if (jsonResponse) {
@@ -74,16 +72,7 @@ class HarnessUrlReader {
74
72
  throw new errors.NotModifiedError();
75
73
  }
76
74
  const archiveUri = integration.getHarnessArchiveUrl(this.integration.config, url);
77
- let response;
78
- try {
79
- response = await fetch__default.default(archiveUri, {
80
- method: "GET",
81
- ...integration.getHarnessRequestOptions(this.integration.config),
82
- signal: options?.signal
83
- });
84
- } catch (e) {
85
- throw new Error(`Unable to read ${archiveUri}, ${e}`);
86
- }
75
+ const response = await this.fetchUrl(archiveUri, options?.signal);
87
76
  const parsedUri = integration.parseHarnessUrl(this.integration.config, url);
88
77
  return this.deps.treeResponseFactory.fromZipArchive({
89
78
  stream: node_stream.Readable.from(response.body),
@@ -126,12 +115,56 @@ class HarnessUrlReader {
126
115
  this.integration.config.token || this.integration.config.apiKey
127
116
  )}}`;
128
117
  }
118
+ async fetchUrl(url, signal) {
119
+ const configuredOrigin = new URL(`https://${this.integration.config.host}`).origin;
120
+ let currentUrl = url;
121
+ let includeCredentials = true;
122
+ for (let redirectCount = 0; ; redirectCount += 1) {
123
+ const currentUrlObject = new URL(currentUrl);
124
+ const requestOptions = includeCredentials && currentUrlObject.origin === configuredOrigin ? integration.getHarnessRequestOptions(this.integration.config) : {};
125
+ let response;
126
+ try {
127
+ response = await fetch__default.default(currentUrl, {
128
+ method: "GET",
129
+ ...requestOptions,
130
+ redirect: "manual",
131
+ signal
132
+ });
133
+ } catch (e) {
134
+ throw new Error(`Unable to read ${currentUrl}, ${e}`);
135
+ }
136
+ const location = response.headers.get("location");
137
+ if (!REDIRECT_STATUS_CODES.includes(response.status) || !location) {
138
+ return response;
139
+ }
140
+ response.body.resume();
141
+ let redirectUrl;
142
+ try {
143
+ redirectUrl = new URL(location, currentUrl);
144
+ } catch (e) {
145
+ throw new Error(
146
+ `Unable to read ${currentUrl}, invalid redirect URL ${location}, ${e}`
147
+ );
148
+ }
149
+ if (redirectUrl.origin !== configuredOrigin && !this.allowedRedirectPredicate(redirectUrl)) {
150
+ throw new Error(
151
+ `Refusing to follow cross-origin Harness redirect to ${redirectUrl.origin}`
152
+ );
153
+ }
154
+ if (redirectUrl.origin !== configuredOrigin) {
155
+ includeCredentials = false;
156
+ }
157
+ if (redirectCount === MAX_REDIRECTS) {
158
+ throw new Error(
159
+ `Too many redirects (max ${MAX_REDIRECTS}) when reading ${url}`
160
+ );
161
+ }
162
+ currentUrl = redirectUrl.toString();
163
+ }
164
+ }
129
165
  async getLastCommitHash(url) {
130
166
  const commitUri = integration.getHarnessLatestCommitUrl(this.integration.config, url);
131
- const response = await fetch__default.default(
132
- commitUri,
133
- integration.getHarnessRequestOptions(this.integration.config)
134
- );
167
+ const response = await this.fetchUrl(commitUri);
135
168
  if (!response.ok) {
136
169
  const message = `Failed to retrieve latest commit information from ${commitUri}, ${response.status} ${response.statusText}`;
137
170
  if (response.status === 404) {
@@ -1 +1 @@
1
- {"version":3,"file":"HarnessUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/HarnessUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchResponse,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceSearchOptions,\n} from '@backstage/backend-plugin-api';\nimport {\n getHarnessRequestOptions,\n getHarnessFileContentsUrl,\n HarnessIntegration,\n ScmIntegrations,\n getHarnessLatestCommitUrl,\n getHarnessArchiveUrl,\n parseHarnessUrl,\n} from '@backstage/integration';\nimport { ReadTreeResponseFactory, ReaderFactory } from './types';\nimport fetch, { Response } from 'node-fetch';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport {\n toError,\n AuthenticationError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport { Readable } from 'node:stream';\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for the Harness code v1 api.\n *\n *\n * @public\n */\nexport class HarnessUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n return ScmIntegrations.fromConfig(config)\n .harness.list()\n .map(integration => {\n const reader = new HarnessUrlReader(integration, {\n treeResponseFactory,\n });\n const predicate = (url: URL) => {\n return url.host === integration.config.host;\n };\n return { reader, predicate };\n });\n };\n\n private readonly integration: HarnessIntegration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n };\n\n constructor(\n integration: HarnessIntegration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n },\n ) {\n this.integration = integration;\n this.deps = deps;\n }\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n let response: Response;\n const blobUrl = getHarnessFileContentsUrl(this.integration.config, url);\n\n try {\n response = await fetch(blobUrl, {\n method: 'GET',\n ...getHarnessRequestOptions(this.integration.config),\n signal: options?.signal as any,\n });\n } catch (e) {\n throw new Error(`Unable to read ${blobUrl}, ${e}`);\n }\n\n if (response.ok) {\n // Harness Code returns the raw content object\n const jsonResponse = { data: response.body };\n if (jsonResponse) {\n return ReadUrlResponseFactory.fromReadable(\n Readable.from(jsonResponse.data),\n {\n etag: response.headers.get('ETag') ?? undefined,\n },\n );\n }\n\n throw new Error(`Unknown json: ${jsonResponse}`);\n }\n\n const message = `${url} x ${blobUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.status === 403) {\n throw new AuthenticationError();\n }\n\n throw new Error(message);\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n const lastCommitHash = await this.getLastCommitHash(url);\n\n if (options?.etag && options.etag === lastCommitHash) {\n throw new NotModifiedError();\n }\n\n const archiveUri = getHarnessArchiveUrl(this.integration.config, url);\n\n let response: Response;\n try {\n response = await fetch(archiveUri, {\n method: 'GET',\n ...getHarnessRequestOptions(this.integration.config),\n signal: options?.signal as any,\n });\n } catch (e) {\n throw new Error(`Unable to read ${archiveUri}, ${e}`);\n }\n\n const parsedUri = parseHarnessUrl(this.integration.config, url);\n\n return this.deps.treeResponseFactory.fromZipArchive({\n stream: Readable.from(response.body),\n subpath: parsedUri.path,\n etag: lastCommitHash,\n filter: options?.filter,\n });\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseHarnessUrl(this.integration.config, url);\n\n if (path.match(/[*?]/)) {\n throw new Error('Unsupported search pattern URL');\n }\n\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (e) {\n const error = toError(e);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n const { host } = this.integration.config;\n return `harness{host=${host},authed=${Boolean(\n this.integration.config.token || this.integration.config.apiKey,\n )}}`;\n }\n private async getLastCommitHash(url: string): Promise<string> {\n const commitUri = getHarnessLatestCommitUrl(this.integration.config, url);\n\n const response = await fetch(\n commitUri,\n getHarnessRequestOptions(this.integration.config),\n );\n if (!response.ok) {\n const message = `Failed to retrieve latest commit information from ${commitUri}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return (await response.json()).latest_commit.sha;\n }\n}\n"],"names":["ScmIntegrations","getHarnessFileContentsUrl","fetch","getHarnessRequestOptions","ReadUrlResponseFactory","Readable","NotFoundError","NotModifiedError","AuthenticationError","getHarnessArchiveUrl","parseHarnessUrl","toError","getHarnessLatestCommitUrl"],"mappings":"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"}
1
+ {"version":3,"file":"HarnessUrlReader.cjs.js","sources":["../../../../src/entrypoints/urlReader/lib/HarnessUrlReader.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n UrlReaderService,\n UrlReaderServiceReadTreeResponse,\n UrlReaderServiceReadUrlOptions,\n UrlReaderServiceReadUrlResponse,\n UrlReaderServiceSearchResponse,\n UrlReaderServiceReadTreeOptions,\n UrlReaderServiceSearchOptions,\n} from '@backstage/backend-plugin-api';\nimport {\n getHarnessRequestOptions,\n getHarnessFileContentsUrl,\n HarnessIntegration,\n ScmIntegrations,\n getHarnessLatestCommitUrl,\n getHarnessArchiveUrl,\n parseHarnessUrl,\n} from '@backstage/integration';\nimport { ReadTreeResponseFactory, ReaderFactory } from './types';\nimport fetch, { Response } from 'node-fetch';\nimport { ReadUrlResponseFactory } from './ReadUrlResponseFactory';\nimport {\n toError,\n AuthenticationError,\n NotFoundError,\n NotModifiedError,\n} from '@backstage/errors';\nimport { Readable } from 'node:stream';\nimport { predicateFromConfig } from './FetchUrlReader';\n\nconst REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];\nconst MAX_REDIRECTS = 5;\n\n/**\n * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for the Harness code v1 api.\n *\n *\n * @public\n */\nexport class HarnessUrlReader implements UrlReaderService {\n static factory: ReaderFactory = ({ config, treeResponseFactory }) => {\n const allowedRedirectPredicate = predicateFromConfig(config);\n return ScmIntegrations.fromConfig(config)\n .harness.list()\n .map(integration => {\n const reader = new HarnessUrlReader(integration, {\n treeResponseFactory,\n allowedRedirectPredicate,\n });\n const predicate = (url: URL) => {\n return url.host === integration.config.host;\n };\n return { reader, predicate };\n });\n };\n\n private readonly integration: HarnessIntegration;\n private readonly deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n allowedRedirectPredicate?: (url: URL) => boolean;\n };\n private readonly allowedRedirectPredicate: (url: URL) => boolean;\n\n constructor(\n integration: HarnessIntegration,\n deps: {\n treeResponseFactory: ReadTreeResponseFactory;\n allowedRedirectPredicate?: (url: URL) => boolean;\n },\n ) {\n this.integration = integration;\n this.deps = deps;\n this.allowedRedirectPredicate =\n deps.allowedRedirectPredicate ?? (() => false);\n }\n async read(url: string): Promise<Buffer> {\n const response = await this.readUrl(url);\n return response.buffer();\n }\n\n async readUrl(\n url: string,\n options?: UrlReaderServiceReadUrlOptions,\n ): Promise<UrlReaderServiceReadUrlResponse> {\n const blobUrl = getHarnessFileContentsUrl(this.integration.config, url);\n const response = await this.fetchUrl(blobUrl, options?.signal);\n\n if (response.ok) {\n // Harness Code returns the raw content object\n const jsonResponse = { data: response.body };\n if (jsonResponse) {\n return ReadUrlResponseFactory.fromReadable(\n Readable.from(jsonResponse.data),\n {\n etag: response.headers.get('ETag') ?? undefined,\n },\n );\n }\n\n throw new Error(`Unknown json: ${jsonResponse}`);\n }\n\n const message = `${url} x ${blobUrl}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n\n if (response.status === 304) {\n throw new NotModifiedError();\n }\n\n if (response.status === 403) {\n throw new AuthenticationError();\n }\n\n throw new Error(message);\n }\n\n async readTree(\n url: string,\n options?: UrlReaderServiceReadTreeOptions,\n ): Promise<UrlReaderServiceReadTreeResponse> {\n const lastCommitHash = await this.getLastCommitHash(url);\n\n if (options?.etag && options.etag === lastCommitHash) {\n throw new NotModifiedError();\n }\n\n const archiveUri = getHarnessArchiveUrl(this.integration.config, url);\n\n const response = await this.fetchUrl(archiveUri, options?.signal);\n\n const parsedUri = parseHarnessUrl(this.integration.config, url);\n\n return this.deps.treeResponseFactory.fromZipArchive({\n stream: Readable.from(response.body),\n subpath: parsedUri.path,\n etag: lastCommitHash,\n filter: options?.filter,\n });\n }\n\n async search(\n url: string,\n options?: UrlReaderServiceSearchOptions,\n ): Promise<UrlReaderServiceSearchResponse> {\n const { path } = parseHarnessUrl(this.integration.config, url);\n\n if (path.match(/[*?]/)) {\n throw new Error('Unsupported search pattern URL');\n }\n\n try {\n const data = await this.readUrl(url, options);\n\n return {\n files: [\n {\n url: url,\n content: data.buffer,\n lastModifiedAt: data.lastModifiedAt,\n },\n ],\n etag: data.etag ?? '',\n };\n } catch (e) {\n const error = toError(e);\n if (error.name === 'NotFoundError') {\n return {\n files: [],\n etag: '',\n };\n }\n throw error;\n }\n }\n\n toString() {\n const { host } = this.integration.config;\n return `harness{host=${host},authed=${Boolean(\n this.integration.config.token || this.integration.config.apiKey,\n )}}`;\n }\n\n private async fetchUrl(url: string, signal?: AbortSignal): Promise<Response> {\n const configuredOrigin = new URL(`https://${this.integration.config.host}`)\n .origin;\n let currentUrl = url;\n let includeCredentials = true;\n\n for (let redirectCount = 0; ; redirectCount += 1) {\n const currentUrlObject = new URL(currentUrl);\n const requestOptions =\n includeCredentials && currentUrlObject.origin === configuredOrigin\n ? getHarnessRequestOptions(this.integration.config)\n : {};\n let response: Response;\n try {\n response = await fetch(currentUrl, {\n method: 'GET',\n ...requestOptions,\n redirect: 'manual',\n signal: signal as any,\n });\n } catch (e) {\n throw new Error(`Unable to read ${currentUrl}, ${e}`);\n }\n\n const location = response.headers.get('location');\n if (!REDIRECT_STATUS_CODES.includes(response.status) || !location) {\n return response;\n }\n\n response.body.resume();\n\n let redirectUrl: URL;\n try {\n redirectUrl = new URL(location, currentUrl);\n } catch (e) {\n throw new Error(\n `Unable to read ${currentUrl}, invalid redirect URL ${location}, ${e}`,\n );\n }\n\n if (\n redirectUrl.origin !== configuredOrigin &&\n !this.allowedRedirectPredicate(redirectUrl)\n ) {\n throw new Error(\n `Refusing to follow cross-origin Harness redirect to ${redirectUrl.origin}`,\n );\n }\n\n if (redirectUrl.origin !== configuredOrigin) {\n includeCredentials = false;\n }\n\n if (redirectCount === MAX_REDIRECTS) {\n throw new Error(\n `Too many redirects (max ${MAX_REDIRECTS}) when reading ${url}`,\n );\n }\n\n currentUrl = redirectUrl.toString();\n }\n }\n\n private async getLastCommitHash(url: string): Promise<string> {\n const commitUri = getHarnessLatestCommitUrl(this.integration.config, url);\n\n const response = await this.fetchUrl(commitUri);\n if (!response.ok) {\n const message = `Failed to retrieve latest commit information from ${commitUri}, ${response.status} ${response.statusText}`;\n if (response.status === 404) {\n throw new NotFoundError(message);\n }\n throw new Error(message);\n }\n\n return (await response.json()).latest_commit.sha;\n }\n}\n"],"names":["predicateFromConfig","ScmIntegrations","getHarnessFileContentsUrl","ReadUrlResponseFactory","Readable","NotFoundError","NotModifiedError","AuthenticationError","getHarnessArchiveUrl","parseHarnessUrl","toError","getHarnessRequestOptions","fetch","getHarnessLatestCommitUrl"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version = "0.17.8";
5
+ var version = "0.17.9-next.0";
6
6
  var packageinfo = {
7
7
  version: version};
8
8
 
@@ -287,14 +287,17 @@ declare class HarnessUrlReader implements UrlReaderService {
287
287
  static factory: ReaderFactory;
288
288
  private readonly integration;
289
289
  private readonly deps;
290
+ private readonly allowedRedirectPredicate;
290
291
  constructor(integration: HarnessIntegration, deps: {
291
292
  treeResponseFactory: ReadTreeResponseFactory;
293
+ allowedRedirectPredicate?: (url: URL) => boolean;
292
294
  });
293
295
  read(url: string): Promise<Buffer>;
294
296
  readUrl(url: string, options?: UrlReaderServiceReadUrlOptions): Promise<UrlReaderServiceReadUrlResponse>;
295
297
  readTree(url: string, options?: UrlReaderServiceReadTreeOptions): Promise<UrlReaderServiceReadTreeResponse>;
296
298
  search(url: string, options?: UrlReaderServiceSearchOptions): Promise<UrlReaderServiceSearchResponse>;
297
299
  toString(): string;
300
+ private fetchUrl;
298
301
  private getLastCommitHash;
299
302
  }
300
303
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-defaults",
3
- "version": "0.17.8",
3
+ "version": "0.17.9-next.0",
4
4
  "description": "Backend defaults used by Backstage backend apps",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -226,20 +226,20 @@
226
226
  "@aws-sdk/types": "^3.347.0",
227
227
  "@azure/identity": "^4.0.0",
228
228
  "@azure/storage-blob": "^12.5.0",
229
- "@backstage/backend-app-api": "^1.7.3",
230
- "@backstage/backend-dev-utils": "^0.1.7",
231
- "@backstage/backend-plugin-api": "^1.10.0",
232
- "@backstage/cli-node": "^0.3.4",
233
- "@backstage/config": "^1.3.8",
234
- "@backstage/config-loader": "^1.11.2",
235
- "@backstage/errors": "^1.3.1",
236
- "@backstage/integration": "^2.1.1",
237
- "@backstage/integration-aws-node": "^0.2.1",
238
- "@backstage/plugin-auth-node": "^0.7.4",
239
- "@backstage/plugin-events-node": "^0.4.25",
240
- "@backstage/plugin-permission-common": "^0.9.10",
241
- "@backstage/plugin-permission-node": "^0.11.3",
242
- "@backstage/types": "^1.2.2",
229
+ "@backstage/backend-app-api": "1.7.4-next.0",
230
+ "@backstage/backend-dev-utils": "0.1.7",
231
+ "@backstage/backend-plugin-api": "1.10.1-next.0",
232
+ "@backstage/cli-node": "0.3.4",
233
+ "@backstage/config": "1.3.8",
234
+ "@backstage/config-loader": "1.11.2",
235
+ "@backstage/errors": "1.3.1",
236
+ "@backstage/integration": "2.1.2-next.0",
237
+ "@backstage/integration-aws-node": "0.2.1",
238
+ "@backstage/plugin-auth-node": "0.7.5-next.0",
239
+ "@backstage/plugin-events-node": "0.4.26-next.0",
240
+ "@backstage/plugin-permission-common": "0.9.10",
241
+ "@backstage/plugin-permission-node": "0.11.4-next.0",
242
+ "@backstage/types": "1.2.2",
243
243
  "@google-cloud/storage": "^7.0.0",
244
244
  "@keyv/memcache": "^2.0.1",
245
245
  "@keyv/redis": "^4.0.1",
@@ -294,8 +294,8 @@
294
294
  },
295
295
  "devDependencies": {
296
296
  "@aws-sdk/util-stream-node": "^3.350.0",
297
- "@backstage/backend-test-utils": "^1.11.6",
298
- "@backstage/cli": "^0.36.5",
297
+ "@backstage/backend-test-utils": "1.11.7-next.0",
298
+ "@backstage/cli": "0.36.6-next.0",
299
299
  "@google-cloud/cloud-sql-connector": "^1.4.0",
300
300
  "@types/archiver": "^7.0.0",
301
301
  "@types/base64-stream": "^1.0.2",